Add ALPS codepoint supports for split handshake

This change adds ALPS codepoint supports on split handshake.
serialize_features sends list of codepoints, handshaker will serialize
the codepoint when calling SSL_serialize_handback, and send the chosen
codepoint to the server, server can apply the codepoint when
SSL_apply_handback.

Change-Id: Id7bc985c4b9847b7c337595f1bc23b2af93d96e7
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/63265
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index 7e588c5..a8e6e08 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -83,6 +83,7 @@
 SSL,321,INCONSISTENT_ECH_NEGOTIATION
 SSL,259,INVALID_ALPN_PROTOCOL
 SSL,315,INVALID_ALPN_PROTOCOL_LIST
+SSL,322,INVALID_ALPS_CODEPOINT
 SSL,314,INVALID_CLIENT_HELLO_INNER
 SSL,158,INVALID_COMMAND
 SSL,256,INVALID_COMPRESSION_LIST
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index e500dd7..003e0a5 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -5840,6 +5840,7 @@
 #define SSL_R_ECH_REJECTED 319
 #define SSL_R_INVALID_OUTER_EXTENSION 320
 #define SSL_R_INCONSISTENT_ECH_NEGOTIATION 321
+#define SSL_R_INVALID_ALPS_CODEPOINT 322
 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/ssl/handoff.cc b/ssl/handoff.cc
index 037e070..7f78a1a 100644
--- a/ssl/handoff.cc
+++ b/ssl/handoff.cc
@@ -41,7 +41,7 @@
 
 // serialize_features adds a description of features supported by this binary to
 // |out|.  Returns true on success and false on error.
-static bool serialize_features(CBB *out, uint16_t alps_extension_type) {
+static bool serialize_features(CBB *out) {
   CBB ciphers;
   if (!CBB_add_asn1(out, &ciphers, CBS_ASN1_OCTETSTRING)) {
     return false;
@@ -68,7 +68,8 @@
   // removed.
   CBB alps;
   if (!CBB_add_asn1(out, &alps, kHandoffTagALPS) ||
-      !CBB_add_u16(&alps, alps_extension_type)) {
+      !CBB_add_u16(&alps, TLSEXT_TYPE_application_settings_old) ||
+      !CBB_add_u16(&alps, TLSEXT_TYPE_application_settings)) {
     return false;
   }
   return CBB_flush(out);
@@ -87,17 +88,13 @@
   SSLMessage msg;
   Span<const uint8_t> transcript = s3->hs->transcript.buffer();
 
-  uint16_t alps_extension_type = TLSEXT_TYPE_application_settings_old;
-  if (s3->hs->config->alps_use_new_codepoint) {
-    alps_extension_type = TLSEXT_TYPE_application_settings;
-  }
   if (!CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
       !CBB_add_asn1_uint64(&seq, kHandoffVersion) ||
       !CBB_add_asn1_octet_string(&seq, transcript.data(), transcript.size()) ||
       !CBB_add_asn1_octet_string(&seq,
                                  reinterpret_cast<uint8_t *>(s3->hs_buf->data),
                                  s3->hs_buf->length) ||
-      !serialize_features(&seq, alps_extension_type) ||
+      !serialize_features(&seq) ||
       !CBB_flush(out) ||
       !ssl->method->get_message(ssl, &msg) ||
       !ssl_client_hello_init(ssl, out_hello, msg.body)) {
@@ -450,6 +447,16 @@
                                    hs->early_traffic_secret().size())) {
       return false;
     }
+
+    if (session->has_application_settings) {
+      uint16_t alps_codepoint = TLSEXT_TYPE_application_settings_old;
+      if (hs->config->alps_use_new_codepoint) {
+        alps_codepoint = TLSEXT_TYPE_application_settings;
+      }
+      if (!CBB_add_asn1_uint64(&seq, alps_codepoint)) {
+        return false;
+      }
+    }
   }
   return CBB_flush(out);
 }
@@ -469,7 +476,8 @@
   }
 
   SSL3_STATE *const s3 = ssl->s3;
-  uint64_t handback_version, unused_token_binding_param, cipher, type_u64;
+  uint64_t handback_version, unused_token_binding_param, cipher, type_u64,
+           alps_codepoint;
 
   CBS seq, read_seq, write_seq, server_rand, client_rand, read_iv, write_iv,
       next_proto, alpn, hostname, unused_channel_id, transcript, key_share;
@@ -569,6 +577,28 @@
         !CBS_get_asn1(&seq, &early_traffic_secret, CBS_ASN1_OCTETSTRING)) {
       return false;
     }
+
+    if (session->has_application_settings) {
+      // Making it optional to keep compatibility with older handshakers.
+      // Older handshakers won't send the field.
+      if (CBS_len(&seq) == 0) {
+        hs->config->alps_use_new_codepoint = false;
+      } else {
+        if (!CBS_get_asn1_uint64(&seq, &alps_codepoint)) {
+          return false;
+        }
+
+        if (alps_codepoint == TLSEXT_TYPE_application_settings) {
+          hs->config->alps_use_new_codepoint = true;
+        } else if (alps_codepoint == TLSEXT_TYPE_application_settings_old) {
+          hs->config->alps_use_new_codepoint = false;
+        } else {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPS_CODEPOINT);
+          return false;
+        }
+      }
+    }
+
     if (ticket_age_skew > std::numeric_limits<int32_t>::max() ||
         ticket_age_skew < std::numeric_limits<int32_t>::min()) {
       return false;
@@ -750,13 +780,8 @@
 
 int SSL_serialize_capabilities(const SSL *ssl, CBB *out) {
   CBB seq;
-  const SSL_HANDSHAKE *hs = ssl->s3->hs.get();
-  uint16_t alps_extension_type = TLSEXT_TYPE_application_settings_old;
-  if (hs->config->alps_use_new_codepoint) {
-    alps_extension_type = TLSEXT_TYPE_application_settings;
-  }
   if (!CBB_add_asn1(out, &seq, CBS_ASN1_SEQUENCE) ||
-      !serialize_features(&seq, alps_extension_type) ||  //
+      !serialize_features(&seq) ||  //
       !CBB_flush(out)) {
     return 0;
   }
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index fc3d4c3..a8f4f21 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -1677,6 +1677,38 @@
   return std::move(g_last_session);
 }
 
+static void SetUpExpectedNewCodePoint(SSL_CTX *ctx) {
+  SSL_CTX_set_select_certificate_cb(
+      ctx,
+      [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
+        const uint8_t *data;
+        size_t len;
+        if (!SSL_early_callback_ctx_extension_get(
+                client_hello, TLSEXT_TYPE_application_settings, &data,
+                &len)) {
+          ADD_FAILURE() << "Could not find alps new codepoint.";
+          return ssl_select_cert_error;
+        }
+        return ssl_select_cert_success;
+      });
+}
+
+static void SetUpExpectedOldCodePoint(SSL_CTX *ctx) {
+  SSL_CTX_set_select_certificate_cb(
+      ctx,
+      [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
+        const uint8_t *data;
+        size_t len;
+        if (!SSL_early_callback_ctx_extension_get(
+                client_hello, TLSEXT_TYPE_application_settings_old, &data,
+                &len)) {
+          ADD_FAILURE() << "Could not find alps old codepoint.";
+          return ssl_select_cert_error;
+        }
+        return ssl_select_cert_success;
+      });
+}
+
 // Test that |SSL_get_client_CA_list| echoes back the configured parameter even
 // before configuring as a server.
 TEST(SSLTest, ClientCAList) {
@@ -5231,7 +5263,11 @@
   SSL_set0_wbio(src, nullptr);
 }
 
-TEST(SSLTest, Handoff) {
+void VerifyHandoff(bool use_new_alps_codepoint) {
+  static const uint8_t alpn[] = {0x03, 'f', 'o', 'o'};
+  static const uint8_t proto[] = {'f', 'o', 'o'};
+  static const uint8_t alps[] = {0x04, 'a', 'l', 'p', 's'};
+
   bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
   bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
   bssl::UniquePtr<SSL_CTX> handshaker_ctx(
@@ -5240,6 +5276,12 @@
   ASSERT_TRUE(server_ctx);
   ASSERT_TRUE(handshaker_ctx);
 
+  if (!use_new_alps_codepoint) {
+    SetUpExpectedOldCodePoint(server_ctx.get());
+  } else {
+    SetUpExpectedNewCodePoint(server_ctx.get());
+  }
+
   SSL_CTX_set_session_cache_mode(client_ctx.get(), SSL_SESS_CACHE_CLIENT);
   SSL_CTX_sess_set_new_cb(client_ctx.get(), SaveLastSession);
   SSL_CTX_set_handoff_mode(server_ctx.get(), true);
@@ -5255,6 +5297,12 @@
       ASSERT_TRUE(CreateClientAndServer(&client, &server, client_ctx.get(),
                                         server_ctx.get()));
       SSL_set_early_data_enabled(client.get(), early_data);
+
+      // Set up client ALPS settings.
+      SSL_set_alps_use_new_codepoint(client.get(), use_new_alps_codepoint);
+      ASSERT_TRUE(SSL_set_alpn_protos(client.get(), alpn, sizeof(alpn)) == 0);
+      ASSERT_TRUE(SSL_add_application_settings(client.get(), proto,
+                                              sizeof(proto), nullptr, 0));
       if (is_resume) {
         ASSERT_TRUE(g_last_session);
         SSL_set_session(client.get(), g_last_session.get());
@@ -5295,6 +5343,23 @@
       // handshake and newly-issued tickets, entirely by |handshaker|. There is
       // no need to call |SSL_set_early_data_enabled| on |server|.
       SSL_set_early_data_enabled(handshaker.get(), 1);
+
+      // Set up handshaker ALPS settings.
+      SSL_set_alps_use_new_codepoint(handshaker.get(), use_new_alps_codepoint);
+      SSL_CTX_set_alpn_select_cb(
+          handshaker_ctx.get(),
+          [](SSL *ssl, const uint8_t **out, uint8_t *out_len, const uint8_t *in,
+              unsigned in_len, void *arg) -> int {
+            return SSL_select_next_proto(
+                        const_cast<uint8_t **>(out), out_len, in, in_len,
+                        alpn, sizeof(alpn)) == OPENSSL_NPN_NEGOTIATED
+                        ? SSL_TLSEXT_ERR_OK
+                        : SSL_TLSEXT_ERR_NOACK;
+          },
+          nullptr);
+      ASSERT_TRUE(SSL_add_application_settings(handshaker.get(), proto,
+                                              sizeof(proto), alps, sizeof(alps)));
+
       ASSERT_TRUE(SSL_apply_handoff(handshaker.get(), handoff));
 
       MoveBIOs(handshaker.get(), server.get());
@@ -5322,6 +5387,8 @@
       MoveBIOs(server2.get(), handshaker.get());
       ASSERT_TRUE(CompleteHandshakes(client.get(), server2.get()));
       EXPECT_EQ(is_resume, SSL_session_reused(client.get()));
+      // Verify application settings.
+      ASSERT_TRUE(SSL_has_application_settings(client.get()));
 
       if (early_data && is_resume) {
         // In this case, one byte of early data has already been written above.
@@ -5342,6 +5409,13 @@
   }
 }
 
+TEST(SSLTest, Handoff) {
+  for (bool use_new_alps_codepoint : {false, true}) {
+    SCOPED_TRACE(use_new_alps_codepoint);
+    VerifyHandoff(use_new_alps_codepoint);
+  }
+}
+
 TEST(SSLTest, HandoffDeclined) {
   bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
   bssl::UniquePtr<SSL_CTX> server_ctx(
@@ -7994,38 +8068,6 @@
     ASSERT_TRUE(server_ctx_);
   }
 
-  void SetUpExpectedNewCodePoint() {
-    SSL_CTX_set_select_certificate_cb(
-      server_ctx_.get(),
-      [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
-        const uint8_t *data;
-        size_t len;
-        if (!SSL_early_callback_ctx_extension_get(
-                client_hello, TLSEXT_TYPE_application_settings, &data,
-                &len)) {
-          ADD_FAILURE() << "Could not find alps new codpoint.";
-          return ssl_select_cert_error;
-        }
-        return ssl_select_cert_success;
-      });
-  }
-
-  void SetUpExpectedOldCodePoint() {
-    SSL_CTX_set_select_certificate_cb(
-      server_ctx_.get(),
-      [](const SSL_CLIENT_HELLO *client_hello) -> ssl_select_cert_result_t {
-        const uint8_t *data;
-        size_t len;
-        if (!SSL_early_callback_ctx_extension_get(
-                client_hello, TLSEXT_TYPE_application_settings_old, &data,
-                &len)) {
-          ADD_FAILURE() << "Could not find alps old codpoint.";
-          return ssl_select_cert_error;
-        }
-        return ssl_select_cert_success;
-      });
-  }
-
   void SetUpApplicationSetting() {
     static const uint8_t alpn[] = {0x03, 'f', 'o', 'o'};
     static const uint8_t proto[] = {'f', 'o', 'o'};
@@ -8058,7 +8100,7 @@
 };
 
 TEST_F(AlpsNewCodepointTest, Enabled) {
-  SetUpExpectedNewCodePoint();
+  SetUpExpectedNewCodePoint(server_ctx_.get());
 
   ASSERT_TRUE(CreateClientAndServer(&client_, &server_, client_ctx_.get(),
                                     server_ctx_.get()));
@@ -8073,7 +8115,7 @@
 
 TEST_F(AlpsNewCodepointTest, Disabled) {
   // Both client and server disable alps new codepoint.
-  SetUpExpectedOldCodePoint();
+  SetUpExpectedOldCodePoint(server_ctx_.get());
 
   ASSERT_TRUE(CreateClientAndServer(&client_, &server_, client_ctx_.get(),
                                     server_ctx_.get()));
@@ -8088,7 +8130,7 @@
 
 TEST_F(AlpsNewCodepointTest, ClientOnly) {
   // If client set new codepoint but server doesn't set, server ignores it.
-  SetUpExpectedNewCodePoint();
+  SetUpExpectedNewCodePoint(server_ctx_.get());
 
   ASSERT_TRUE(CreateClientAndServer(&client_, &server_, client_ctx_.get(),
                                     server_ctx_.get()));
@@ -8103,7 +8145,7 @@
 
 TEST_F(AlpsNewCodepointTest, ServerOnly) {
   // If client doesn't set new codepoint, while server set.
-  SetUpExpectedOldCodePoint();
+  SetUpExpectedOldCodePoint(server_ctx_.get());
 
   ASSERT_TRUE(CreateClientAndServer(&client_, &server_, client_ctx_.get(),
                                     server_ctx_.get()));