Add test coverage for session resumption with tickets.

The shim is now passed two file descriptors. In a session resumption test, the
second is used in an abbreviated handshake immediately after the first.

Change-Id: I1f348c05f1a8ff2881fb46fc9e869696f74071c6
Reviewed-on: https://boringssl-review.googlesource.com/1291
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index f8a84e6..f06ccd3 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -12,15 +12,22 @@
  * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
 
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <netinet/in.h>
 #include <arpa/inet.h>
+#include <netinet/in.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/types.h>
 #include <unistd.h>
 
-#include <openssl/ssl.h>
 #include <openssl/bio.h>
 #include <openssl/bytestring.h>
+#include <openssl/ssl.h>
+
+static int usage(const char *program) {
+  fprintf(stderr, "Usage: %s (client|server) (normal|resume) [flags...]\n",
+          program);
+  return 1;
+}
 
 static const char *expected_server_name = NULL;
 static int early_callback_called = 0;
@@ -83,14 +90,12 @@
   return SSL_TLSEXT_ERR_OK;
 }
 
-static SSL *setup_test(int is_server) {
+static SSL_CTX *setup_ctx(int is_server) {
   if (!SSL_library_init()) {
     return NULL;
   }
 
   SSL_CTX *ssl_ctx = NULL;
-  SSL *ssl = NULL;
-  BIO *bio = NULL;
 
   ssl_ctx = SSL_CTX_new(
       is_server ? SSLv23_server_method() : SSLv23_client_method());
@@ -106,23 +111,38 @@
     goto err;
   }
 
+  SSL_CTX_set_session_cache_mode(ssl_ctx, SSL_SESS_CACHE_BOTH);
+
   ssl_ctx->select_certificate_cb = select_certificate_callback;
 
   SSL_CTX_set_next_protos_advertised_cb(
       ssl_ctx, next_protos_advertised_callback, NULL);
 
+  return ssl_ctx;
+
+ err:
+  if (ssl_ctx != NULL) {
+    SSL_CTX_free(ssl_ctx);
+  }
+  return NULL;
+}
+
+static SSL *setup_ssl(SSL_CTX *ssl_ctx, int fd) {
+  SSL *ssl = NULL;
+  BIO *bio = NULL;
+
   ssl = SSL_new(ssl_ctx);
   if (ssl == NULL) {
     goto err;
   }
 
-  bio = BIO_new_fd(3, 1 /* take ownership */);
+
+  bio = BIO_new_fd(fd, 1 /* take ownership */);
   if (bio == NULL) {
     goto err;
   }
 
   SSL_set_bio(ssl, bio, bio);
-  SSL_CTX_free(ssl_ctx);
 
   return ssl;
 
@@ -133,37 +153,30 @@
   if (ssl != NULL) {
     SSL_free(ssl);
   }
-  if (ssl_ctx != NULL) {
-    SSL_CTX_free(ssl_ctx);
-  }
   return NULL;
 }
 
-int main(int argc, char **argv) {
-  int i, is_server, ret;
+static int do_exchange(SSL_SESSION **out_session,
+                       SSL_CTX *ssl_ctx,
+                       int argc,
+                       char **argv,
+                       int is_server,
+                       int is_resume,
+                       int fd,
+                       SSL_SESSION *session) {
   const char *expected_certificate_types = NULL;
   const char *expected_next_proto = NULL;
+  expected_server_name = NULL;
+  early_callback_called = 0;
+  advertise_npn = NULL;
 
-  if (argc < 2) {
-    fprintf(stderr, "Usage: %s (client|server) [flags...]\n", argv[0]);
-    return 1;
-  }
-  if (strcmp(argv[1], "client") == 0) {
-    is_server = 0;
-  } else if (strcmp(argv[1], "server") == 0) {
-    is_server = 1;
-  } else {
-    fprintf(stderr, "Usage: %s (client|server) [flags...]\n", argv[0]);
-    return 1;
-  }
-
-  SSL *ssl = setup_test(is_server);
+  SSL *ssl = setup_ssl(ssl_ctx, fd);
   if (ssl == NULL) {
     BIO_print_errors_fp(stdout);
     return 1;
   }
 
-  for (i = 2; i < argc; i++) {
+  for (int i = 0; i < argc; i++) {
     if (strcmp(argv[i], "-fallback-scsv") == 0) {
       if (!SSL_enable_fallback_scsv(ssl)) {
         BIO_print_errors_fp(stdout);
@@ -227,6 +240,14 @@
     }
   }
 
+  if (session != NULL) {
+    if (SSL_set_session(ssl, session) != 1) {
+      fprintf(stderr, "failed to set session\n");
+      return 2;
+    }
+  }
+
+  int ret;
   if (is_server) {
     ret = SSL_accept(ssl);
   } else {
@@ -238,6 +259,11 @@
     return 2;
   }
 
+  if (is_resume && !SSL_session_reused(ssl)) {
+    fprintf(stderr, "session was not reused\n");
+    return 2;
+  }
+
   if (expected_server_name) {
     const char *server_name =
         SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name);
@@ -299,6 +325,68 @@
     }
   }
 
+  if (out_session) {
+    *out_session = SSL_get1_session(ssl);
+  }
+
+  SSL_shutdown(ssl);
   SSL_free(ssl);
   return 0;
 }
+
+int main(int argc, char **argv) {
+  int is_server, resume;
+
+  signal(SIGPIPE, SIG_IGN);
+
+  if (argc < 3) {
+    return usage(argv[0]);
+  }
+
+  if (strcmp(argv[1], "client") == 0) {
+    is_server = 0;
+  } else if (strcmp(argv[1], "server") == 0) {
+    is_server = 1;
+  } else {
+    return usage(argv[0]);
+  }
+
+  if (strcmp(argv[2], "normal") == 0) {
+    resume = 0;
+  } else if (strcmp(argv[2], "resume") == 0) {
+    resume = 1;
+  } else {
+    return usage(argv[0]);
+  }
+
+  SSL_CTX *ssl_ctx = setup_ctx(is_server);
+  if (ssl_ctx == NULL) {
+    BIO_print_errors_fp(stdout);
+    return 1;
+  }
+
+  SSL_SESSION *session;
+  int ret = do_exchange(&session,
+                        ssl_ctx,
+                        argc - 3, argv + 3,
+                        is_server, 0 /* is_resume */,
+                        3 /* fd */, NULL /* session */);
+  if (ret != 0) {
+    return ret;
+  }
+
+  if (resume) {
+    int ret = do_exchange(NULL,
+                          ssl_ctx, argc - 3, argv + 3,
+                          is_server, 1 /* is_resume */,
+                          4 /* fd */,
+                          is_server ? NULL : session);
+    if (ret != 0) {
+      return ret;
+    }
+  }
+
+  SSL_SESSION_free(session);
+  SSL_CTX_free(ssl_ctx);
+  return 0;
+}
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 06ca860..539a746 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -76,6 +76,9 @@
 	certFile string
 	// keyFile is the path to the private key to use for the server.
 	keyFile string
+	// resumeSession controls whether a second connection should be tested
+	// which resumes the first session.
+	resumeSession bool
 	// flags, if not empty, contains a list of command-line flags that will
 	// be passed to the shim program.
 	flags []string
@@ -261,7 +264,15 @@
 	},
 }
 
-func doExchange(tlsConn *Conn, messageLen int) error {
+func doExchange(testType testType, config *Config, conn net.Conn, messageLen int) error {
+	var tlsConn *Conn
+	if testType == clientTest {
+		tlsConn = Server(conn, config)
+	} else {
+		config.InsecureSkipVerify = true
+		tlsConn = Client(conn, config)
+	}
+
 	if err := tlsConn.Handshake(); err != nil {
 		return err
 	}
@@ -308,7 +319,7 @@
 	return exec.Command("xterm", xtermArgs...)
 }
 
-func runTest(test *testCase) error {
+func openSocketPair() (shimEnd *os.File, conn net.Conn) {
 	socks, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
 	if err != nil {
 		panic(err)
@@ -316,13 +327,22 @@
 
 	syscall.CloseOnExec(socks[0])
 	syscall.CloseOnExec(socks[1])
-	shimEnd := os.NewFile(uintptr(socks[0]), "shim end")
+	shimEnd = os.NewFile(uintptr(socks[0]), "shim end")
 	connFile := os.NewFile(uintptr(socks[1]), "our end")
-	conn, err := net.FileConn(connFile)
+	conn, err = net.FileConn(connFile)
+	if err != nil {
+		panic(err)
+	}
 	connFile.Close()
 	if err != nil {
 		panic(err)
 	}
+	return shimEnd, conn
+}
+
+func runTest(test *testCase) error {
+	shimEnd, conn := openSocketPair()
+	shimEndResume, connResume := openSocketPair()
 
 	const shim_path = "../../../build/ssl/test/bssl_shim"
 	flags := []string{}
@@ -330,7 +350,15 @@
 		flags = append(flags, "client")
 	} else {
 		flags = append(flags, "server")
+	}
 
+	if test.resumeSession {
+		flags = append(flags, "resume")
+	} else {
+		flags = append(flags, "normal")
+	}
+
+	if test.testType == serverTest {
 		flags = append(flags, "-key-file")
 		if test.keyFile == "" {
 			flags = append(flags, rsaKeyFile)
@@ -354,7 +382,7 @@
 		shim = exec.Command(shim_path, flags...)
 	}
 	// shim = gdbOf(shim_path, flags...)
-	shim.ExtraFiles = []*os.File{shimEnd}
+	shim.ExtraFiles = []*os.File{shimEnd, shimEndResume}
 	shim.Stdin = os.Stdin
 	var stdoutBuf, stderrBuf bytes.Buffer
 	shim.Stdout = &stdoutBuf
@@ -364,22 +392,23 @@
 		panic(err)
 	}
 	shimEnd.Close()
+	shimEndResume.Close()
 
 	config := test.config
-
-	var tlsConn *Conn
+	config.ClientSessionCache = NewLRUClientSessionCache(1)
 	if test.testType == clientTest {
 		if len(config.Certificates) == 0 {
 			config.Certificates = []Certificate{getRSACertificate()}
 		}
-		tlsConn = Server(conn, &config)
-	} else {
-		config.InsecureSkipVerify = true
-		tlsConn = Client(conn, &config)
 	}
-	err = doExchange(tlsConn, test.messageLen)
 
+	err := doExchange(test.testType, &config, conn, test.messageLen)
 	conn.Close()
+	if err == nil && test.resumeSession {
+		err = doExchange(test.testType, &config, connResume, test.messageLen)
+		connResume.Close()
+	}
+
 	childErr := shim.Wait()
 
 	stdout := string(stdoutBuf.Bytes())
@@ -473,6 +502,11 @@
 				continue
 			}
 
+			// Go's TLS implementation only implements session
+			// resumption with tickets, so SSLv3 cannot resume
+			// sessions.
+			resumeSession := ver.version != VersionSSL30
+
 			testCases = append(testCases, testCase{
 				testType: clientTest,
 				name:     ver.name + "-" + suite.name + "-client",
@@ -482,6 +516,7 @@
 					CipherSuites: []uint16{suite.id},
 					Certificates: []Certificate{cert},
 				},
+				resumeSession: resumeSession,
 			})
 
 			// Go's TLS implementation implements SSLv3 as a server,
@@ -499,8 +534,9 @@
 						CipherSuites: []uint16{suite.id},
 						Certificates: []Certificate{cert},
 					},
-					certFile: certFile,
-					keyFile:  keyFile,
+					certFile:      certFile,
+					keyFile:       keyFile,
+					resumeSession: resumeSession,
 				})
 			}
 		}