Add tests for ALPN support.

Both as client and as server. Also tests that ALPN causes False Start to kick
in.

Change-Id: Ib570346f3c511834152cd2df2ef29541946d3ab4
Reviewed-on: https://boringssl-review.googlesource.com/1753
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 4ca7c58..846850c 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -139,6 +139,29 @@
   return SSL_TLSEXT_ERR_OK;
 }
 
+static int alpn_select_callback(SSL* ssl,
+                                const uint8_t** out,
+                                uint8_t* outlen,
+                                const uint8_t* in,
+                                unsigned inlen,
+                                void* arg) {
+  const TestConfig *config = GetConfigPtr(ssl);
+  if (config->select_alpn.empty())
+    return SSL_TLSEXT_ERR_NOACK;
+
+  if (!config->expected_advertised_alpn.empty() &&
+      (config->expected_advertised_alpn.size() != inlen ||
+       memcmp(config->expected_advertised_alpn.data(),
+              in, inlen) != 0)) {
+    fprintf(stderr, "bad ALPN select callback inputs\n");
+    exit(1);
+  }
+
+  *out = (const uint8_t*)config->select_alpn.data();
+  *outlen = config->select_alpn.size();
+  return SSL_TLSEXT_ERR_OK;
+}
+
 static int cookie_generate_callback(SSL *ssl, uint8_t *cookie, size_t *cookie_len) {
   *cookie_len = 32;
   memset(cookie, 42, *cookie_len);
@@ -213,8 +236,13 @@
 
   SSL_CTX_set_next_protos_advertised_cb(
       ssl_ctx, next_protos_advertised_callback, NULL);
-  SSL_CTX_set_next_proto_select_cb(
-      ssl_ctx, next_proto_select_callback, NULL);
+  if (!config->select_next_proto.empty()) {
+    SSL_CTX_set_next_proto_select_cb(ssl_ctx, next_proto_select_callback, NULL);
+  }
+
+  if (!config->select_alpn.empty()) {
+    SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_callback, NULL);
+  }
 
   SSL_CTX_set_cookie_generate_cb(ssl_ctx, cookie_generate_callback);
   SSL_CTX_set_cookie_verify_cb(ssl_ctx, cookie_verify_callback);
@@ -339,6 +367,10 @@
   if (!config->host_name.empty()) {
     SSL_set_tlsext_host_name(ssl, config->host_name.c_str());
   }
+  if (!config->advertise_alpn.empty()) {
+    SSL_set_alpn_protos(ssl, (const uint8_t *)config->advertise_alpn.data(),
+                        config->advertise_alpn.size());
+  }
 
   BIO *bio = BIO_new_fd(fd, 1 /* take ownership */);
   if (bio == NULL) {
@@ -425,6 +457,18 @@
     }
   }
 
+  if (!config->expected_alpn.empty()) {
+    const uint8_t *alpn_proto;
+    unsigned alpn_proto_len;
+    SSL_get0_alpn_selected(ssl, &alpn_proto, &alpn_proto_len);
+    if (alpn_proto_len != config->expected_alpn.size() ||
+        memcmp(alpn_proto, config->expected_alpn.data(),
+               alpn_proto_len) != 0) {
+      fprintf(stderr, "negotiated alpn proto mismatch\n");
+      return 2;
+    }
+  }
+
   if (!config->expected_channel_id.empty()) {
     uint8_t channel_id[64];
     if (!SSL_get_tls_channel_id(ssl, channel_id, sizeof(channel_id))) {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 0a5888c..25e7626 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -113,6 +113,9 @@
 	// expectChannelID controls whether the connection should have
 	// negotiated a Channel ID with channelIDKey.
 	expectChannelID bool
+	// expectedNextProto controls whether the connection should
+	// negotiate a next protocol via NPN or ALPN.
+	expectedNextProto string
 	// messageLen is the length, in bytes, of the test message that will be
 	// sent.
 	messageLen int
@@ -514,6 +517,12 @@
 		}
 	}
 
+	if expected := test.expectedNextProto; expected != "" {
+		if actual := tlsConn.ConnectionState().NegotiatedProtocol; actual != expected {
+			return fmt.Errorf("next proto mismatch: got %s, wanted %s", actual, expected)
+		}
+	}
+
 	if test.shimWritesFirst {
 		var buf [5]byte
 		_, err := io.ReadFull(tlsConn, buf[:])
@@ -1129,13 +1138,13 @@
 			protocol: protocol,
 			name:     "NPN-Client" + suffix,
 			config: Config{
-				CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
-				NextProtos:   []string{"foo"},
+				NextProtos: []string{"foo"},
 				Bugs: ProtocolBugs{
 					MaxHandshakeRecordLength: maxHandshakeRecordLength,
 				},
 			},
-			flags: append(flags, "-select-next-proto", "foo"),
+			flags:             append(flags, "-select-next-proto", "foo"),
+			expectedNextProto: "foo",
 		})
 		testCases = append(testCases, testCase{
 			protocol: protocol,
@@ -1150,6 +1159,7 @@
 			flags: append(flags,
 				"-advertise-npn", "\x03foo\x03bar\x03baz",
 				"-expect-next-proto", "bar"),
+			expectedNextProto: "bar",
 		})
 
 		// Client does False Start and negotiates NPN.
@@ -1171,6 +1181,25 @@
 			resumeSession:   true,
 		})
 
+		// Client does False Start and negotiates ALPN.
+		testCases = append(testCases, testCase{
+			protocol: protocol,
+			name:     "FalseStart-ALPN" + suffix,
+			config: Config{
+				CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+				NextProtos:   []string{"foo"},
+				Bugs: ProtocolBugs{
+					ExpectFalseStart:         true,
+					MaxHandshakeRecordLength: maxHandshakeRecordLength,
+				},
+			},
+			flags: append(flags,
+				"-false-start",
+				"-advertise-alpn", "\x03foo"),
+			shimWritesFirst: true,
+			resumeSession:   true,
+		})
+
 		// False Start without session tickets.
 		testCases = append(testCases, testCase{
 			name: "FalseStart-SessionTicketsDisabled",
@@ -1407,6 +1436,32 @@
 		flags:         []string{"-expect-server-name", "example.com"},
 		resumeSession: true,
 	})
+	testCases = append(testCases, testCase{
+		testType: clientTest,
+		name:     "ALPNClient",
+		config: Config{
+			NextProtos: []string{"foo"},
+		},
+		flags: []string{
+			"-advertise-alpn", "\x03foo\x03bar\x03baz",
+			"-expect-alpn", "foo",
+		},
+		expectedNextProto: "foo",
+		resumeSession:     true,
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "ALPNServer",
+		config: Config{
+			NextProtos: []string{"foo", "bar", "baz"},
+		},
+		flags: []string{
+			"-expect-advertised-alpn", "\x03foo\x03bar\x03baz",
+			"-select-alpn", "foo",
+		},
+		expectedNextProto: "foo",
+		resumeSession:     true,
+	})
 }
 
 func worker(statusChan chan statusMsg, c chan *testCase, buildDir string, wg *sync.WaitGroup) {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index 77d51a1..e96b242 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -68,6 +68,10 @@
   { "-select-next-proto", &TestConfig::select_next_proto },
   { "-send-channel-id", &TestConfig::send_channel_id },
   { "-host-name", &TestConfig::host_name },
+  { "-advertise-alpn", &TestConfig::advertise_alpn },
+  { "-expect-alpn", &TestConfig::expected_alpn },
+  { "-expect-advertised-alpn", &TestConfig::expected_advertised_alpn },
+  { "-select-alpn", &TestConfig::select_alpn },
 };
 
 const size_t kNumStringFlags = sizeof(kStringFlags) / sizeof(kStringFlags[0]);
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index c3ba63f..62c5c9e 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -48,6 +48,10 @@
   bool shim_writes_first;
   bool tls_d5_bug;
   std::string host_name;
+  std::string advertise_alpn;
+  std::string expected_alpn;
+  std::string expected_advertised_alpn;
+  std::string select_alpn;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);