Add an option to allow unknown ALPN protocols.

We received an external request to add an option to undo the check added
in 3e51757de2bf9beef7d249f22d255e4dd9ddb012.

Change-Id: Ifdd4b07705f2fa3d781d775d5cd139ea72d36734
Reviewed-on: https://boringssl-review.googlesource.com/14644
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 75862fc..6a6cd85 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -2586,6 +2586,13 @@
                                            const uint8_t **out_data,
                                            unsigned *out_len);
 
+/* SSL_CTX_set_allow_unknown_alpn_protos configures client connections on |ctx|
+ * to allow unknown ALPN protocols from the server. Otherwise, by default, the
+ * client will require that the protocol be advertised in
+ * |SSL_CTX_set_alpn_protos|. */
+OPENSSL_EXPORT void SSL_CTX_set_allow_unknown_alpn_protos(SSL_CTX *ctx,
+                                                          int enabled);
+
 
 /* Next protocol negotiation.
  *
@@ -4280,6 +4287,10 @@
    * that this currently requires post-handshake verification of
    * certificates. */
   unsigned i_promise_to_verify_certs_after_the_handshake:1;
+
+  /* allow_unknown_alpn_protos is one if the client allows unsolicited ALPN
+   * protocols from the peer. */
+  unsigned allow_unknown_alpn_protos:1;
 };
 
 
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 17e6521..7adf103 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -1753,6 +1753,9 @@
   }
 }
 
+void SSL_CTX_set_allow_unknown_alpn_protos(SSL_CTX *ctx, int enabled) {
+  ctx->allow_unknown_alpn_protos = !!enabled;
+}
 
 void SSL_CTX_set_tls_channel_id_enabled(SSL_CTX *ctx, int enabled) {
   ctx->tlsext_channel_id_enabled = !!enabled;
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 45a04c1..793e2d7 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1432,31 +1432,33 @@
     return 0;
   }
 
-  /* Check that the protcol name is one of the ones we advertised. */
-  int protocol_ok = 0;
-  CBS client_protocol_name_list, client_protocol_name;
-  CBS_init(&client_protocol_name_list, ssl->alpn_client_proto_list,
-           ssl->alpn_client_proto_list_len);
-  while (CBS_len(&client_protocol_name_list) > 0) {
-    if (!CBS_get_u8_length_prefixed(&client_protocol_name_list,
-                                    &client_protocol_name)) {
-      *out_alert = SSL_AD_INTERNAL_ERROR;
+  if (!ssl->ctx->allow_unknown_alpn_protos) {
+    /* Check that the protocol name is one of the ones we advertised. */
+    int protocol_ok = 0;
+    CBS client_protocol_name_list, client_protocol_name;
+    CBS_init(&client_protocol_name_list, ssl->alpn_client_proto_list,
+             ssl->alpn_client_proto_list_len);
+    while (CBS_len(&client_protocol_name_list) > 0) {
+      if (!CBS_get_u8_length_prefixed(&client_protocol_name_list,
+                                      &client_protocol_name)) {
+        *out_alert = SSL_AD_INTERNAL_ERROR;
+        return 0;
+      }
+
+      if (CBS_len(&client_protocol_name) == CBS_len(&protocol_name) &&
+          OPENSSL_memcmp(CBS_data(&client_protocol_name),
+                         CBS_data(&protocol_name),
+                         CBS_len(&protocol_name)) == 0) {
+        protocol_ok = 1;
+        break;
+      }
+    }
+
+    if (!protocol_ok) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPN_PROTOCOL);
+      *out_alert = SSL_AD_ILLEGAL_PARAMETER;
       return 0;
     }
-
-    if (CBS_len(&client_protocol_name) == CBS_len(&protocol_name) &&
-        OPENSSL_memcmp(CBS_data(&client_protocol_name),
-                       CBS_data(&protocol_name),
-                       CBS_len(&protocol_name)) == 0) {
-      protocol_ok = 1;
-      break;
-    }
-  }
-
-  if (!protocol_ok) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPN_PROTOCOL);
-    *out_alert = SSL_AD_ILLEGAL_PARAMETER;
-    return 0;
   }
 
   if (!CBS_stow(&protocol_name, &ssl->s3->alpn_selected,
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 70ea664..b60c809 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -1187,6 +1187,10 @@
     SSL_CTX_set_early_data_enabled(ssl_ctx.get(), 1);
   }
 
+  if (config->allow_unknown_alpn_protos) {
+    SSL_CTX_set_allow_unknown_alpn_protos(ssl_ctx.get(), 1);
+  }
+
   return ssl_ctx;
 }
 
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 8444c21..ed328b6 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -4889,7 +4889,7 @@
 		})
 		testCases = append(testCases, testCase{
 			testType: clientTest,
-			name:     "ALPNClient-Mismatch-" + ver.name,
+			name:     "ALPNClient-RejectUnknown-" + ver.name,
 			config: Config{
 				MaxVersion: ver.version,
 				Bugs: ProtocolBugs{
@@ -4904,6 +4904,20 @@
 			expectedLocalError: "remote error: illegal parameter",
 		})
 		testCases = append(testCases, testCase{
+			testType: clientTest,
+			name:     "ALPNClient-AllowUnknown-" + ver.name,
+			config: Config{
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					SendALPN: "baz",
+				},
+			},
+			flags: []string{
+				"-advertise-alpn", "\x03foo\x03bar",
+				"-allow-unknown-alpn-protos",
+			},
+		})
+		testCases = append(testCases, testCase{
 			testType: serverTest,
 			name:     "ALPNServer-" + ver.name,
 			config: Config{
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index a8cf755..1b23b02 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -130,6 +130,7 @@
   { "-expect-no-resume-alpn", &TestConfig::expect_no_resume_alpn },
   { "-no-op-extra-handshake", &TestConfig::no_op_extra_handshake },
   { "-handshake-twice", &TestConfig::handshake_twice },
+  { "-allow-unknown-alpn-protos", &TestConfig::allow_unknown_alpn_protos },
 };
 
 const Flag<std::string> kStringFlags[] = {
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index ef14f15..839c0fc 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -143,6 +143,7 @@
   int expect_ticket_age_skew = 0;
   bool no_op_extra_handshake = false;
   bool handshake_twice = false;
+  bool allow_unknown_alpn_protos = false;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);