Implement draft-vvv-tls-alps-01.

(Original CL by svaldez, reworked by davidben.)

Change-Id: I8570808fa5e96a1c9e6e03c4877039a22e73254f
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/42404
Reviewed-by: Steven Valdez <svaldez@google.com>
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index 4a2bbcf..e48f1a1 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -125,13 +125,14 @@
 #include <openssl/nid.h>
 #include <openssl/rand.h>
 
-#include "internal.h"
 #include "../crypto/internal.h"
+#include "internal.h"
 
 
 BSSL_NAMESPACE_BEGIN
 
 static bool ssl_check_clienthello_tlsext(SSL_HANDSHAKE *hs);
+static bool ssl_check_serverhello_tlsext(SSL_HANDSHAKE *hs);
 
 static int compare_uint16_t(const void *p1, const void *p2) {
   uint16_t u1 = *((const uint16_t *)p1);
@@ -512,7 +513,7 @@
 };
 
 static bool forbid_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
-                                    CBS *contents) {
+                                     CBS *contents) {
   if (contents != NULL) {
     // Servers MUST NOT send this extension.
     *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
@@ -524,7 +525,7 @@
 }
 
 static bool ignore_parse_clienthello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
-                                    CBS *contents) {
+                                     CBS *contents) {
   // This extension from the client is handled elsewhere.
   return true;
 }
@@ -1380,7 +1381,6 @@
   CBS protocol_name_list_copy = protocol_name_list;
   while (CBS_len(&protocol_name_list_copy) > 0) {
     CBS protocol_name;
-
     if (!CBS_get_u8_length_prefixed(&protocol_name_list_copy, &protocol_name) ||
         // Empty protocol names are forbidden.
         CBS_len(&protocol_name) == 0) {
@@ -1946,6 +1946,21 @@
 //
 // https://tools.ietf.org/html/rfc8446#section-4.2.10
 
+// ssl_get_local_application_settings looks up the configured ALPS value for
+// |protocol|. If found, it sets |*out_settings| to the value and returns true.
+// Otherwise, it returns false.
+static bool ssl_get_local_application_settings(
+    const SSL_HANDSHAKE *hs, Span<const uint8_t> *out_settings,
+    Span<const uint8_t> protocol) {
+  for (const ALPSConfig &config : hs->config->alps_configs) {
+    if (protocol == config.protocol) {
+      *out_settings = config.settings;
+      return true;
+    }
+  }
+  return false;
+}
+
 static bool ext_early_data_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
   // The second ClientHello never offers early data, and we must have already
@@ -1978,13 +1993,22 @@
     return true;
   }
 
-  // In case ALPN preferences changed since this session was established, avoid
-  // reporting a confusing value in |SSL_get0_alpn_selected| and sending early
-  // data we know will be rejected.
-  if (!ssl->session->early_alpn.empty() &&
-      !ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn)) {
-    ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
-    return true;
+  if (!ssl->session->early_alpn.empty()) {
+    if (!ssl_is_alpn_protocol_allowed(hs, ssl->session->early_alpn)) {
+      // Avoid reporting a confusing value in |SSL_get0_alpn_selected|.
+      ssl->s3->early_data_reason = ssl_early_data_alpn_mismatch;
+      return true;
+    }
+
+    Span<const uint8_t> settings;
+    bool has_alps = ssl_get_local_application_settings(
+        hs, &settings, ssl->session->early_alpn);
+    if (has_alps != ssl->session->has_application_settings ||
+        settings != ssl->session->local_application_settings) {
+      // 0-RTT carries ALPS over, so we only offer it when the value matches.
+      ssl->s3->early_data_reason = ssl_early_data_alps_mismatch;
+      return true;
+    }
   }
 
   // |early_data_reason| will be filled in later when the server responds.
@@ -2797,6 +2821,144 @@
   return true;
 }
 
+// Application-level Protocol Settings
+//
+// https://tools.ietf.org/html/draft-vvv-tls-alps-01
+
+static bool ext_alps_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
+  SSL *const ssl = hs->ssl;
+  if (// ALPS requires TLS 1.3.
+      hs->max_version < TLS1_3_VERSION ||
+      // Do not offer ALPS without ALPN.
+      hs->config->alpn_client_proto_list.empty() ||
+      // Do not offer ALPS if not configured.
+      hs->config->alps_configs.empty() ||
+      // Do not offer ALPS on renegotiation handshakes.
+      ssl->s3->initial_handshake_complete) {
+    return true;
+  }
+
+  CBB contents, proto_list, proto;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_application_settings) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_u16_length_prefixed(&contents, &proto_list)) {
+    return false;
+  }
+
+  for (const ALPSConfig &config : hs->config->alps_configs) {
+    if (!CBB_add_u8_length_prefixed(&proto_list, &proto) ||
+        !CBB_add_bytes(&proto, config.protocol.data(),
+                       config.protocol.size())) {
+      return false;
+    }
+  }
+
+  return CBB_flush(out);
+}
+
+static bool ext_alps_parse_serverhello(SSL_HANDSHAKE *hs, uint8_t *out_alert,
+                                       CBS *contents) {
+  SSL *const ssl = hs->ssl;
+  if (contents == nullptr) {
+    return true;
+  }
+
+  assert(!ssl->s3->initial_handshake_complete);
+  assert(!hs->config->alpn_client_proto_list.empty());
+  assert(!hs->config->alps_configs.empty());
+
+  // ALPS requires TLS 1.3.
+  if (ssl_protocol_version(ssl) < TLS1_3_VERSION) {
+    *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
+    return false;
+  }
+
+  // Note extension callbacks may run in any order, so we defer checking
+  // consistency with ALPN to |ssl_check_serverhello_tlsext|.
+  if (!hs->new_session->peer_application_settings.CopyFrom(*contents)) {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+    return false;
+  }
+
+  hs->new_session->has_application_settings = true;
+  return true;
+}
+
+static bool ext_alps_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
+  SSL *const ssl = hs->ssl;
+  // If early data is accepted, we omit the ALPS extension. It is implicitly
+  // carried over from the previous connection.
+  if (hs->new_session == nullptr ||
+      !hs->new_session->has_application_settings ||
+      ssl->s3->early_data_accepted) {
+    return true;
+  }
+
+  CBB contents;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_application_settings) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_bytes(&contents,
+                     hs->new_session->local_application_settings.data(),
+                     hs->new_session->local_application_settings.size()) ||
+      !CBB_flush(out)) {
+    return false;
+  }
+
+  return true;
+}
+
+bool ssl_negotiate_alps(SSL_HANDSHAKE *hs, uint8_t *out_alert,
+                        const SSL_CLIENT_HELLO *client_hello) {
+  SSL *const ssl = hs->ssl;
+  if (ssl->s3->alpn_selected.empty()) {
+    return true;
+  }
+
+  // If we negotiate ALPN over TLS 1.3, try to negotiate ALPS.
+  CBS alps_contents;
+  Span<const uint8_t> settings;
+  if (ssl_protocol_version(ssl) >= TLS1_3_VERSION &&
+      ssl_get_local_application_settings(hs, &settings,
+                                         ssl->s3->alpn_selected) &&
+      ssl_client_hello_get_extension(client_hello, &alps_contents,
+                                     TLSEXT_TYPE_application_settings)) {
+    // Check if the client supports ALPS with the selected ALPN.
+    bool found = false;
+    CBS alps_list;
+    if (!CBS_get_u16_length_prefixed(&alps_contents, &alps_list) ||
+        CBS_len(&alps_contents) != 0 ||
+        CBS_len(&alps_list) == 0) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      *out_alert = SSL_AD_DECODE_ERROR;
+      return false;
+    }
+    while (CBS_len(&alps_list) > 0) {
+      CBS protocol_name;
+      if (!CBS_get_u8_length_prefixed(&alps_list, &protocol_name) ||
+          // Empty protocol names are forbidden.
+          CBS_len(&protocol_name) == 0) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+        *out_alert = SSL_AD_DECODE_ERROR;
+        return false;
+      }
+      if (protocol_name == MakeConstSpan(ssl->s3->alpn_selected)) {
+        found = true;
+      }
+    }
+
+    // Negotiate ALPS if both client also supports ALPS for this protocol.
+    if (found) {
+      hs->new_session->has_application_settings = true;
+      if (!hs->new_session->local_application_settings.CopyFrom(settings)) {
+        *out_alert = SSL_AD_INTERNAL_ERROR;
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
 
 // kExtensions contains all the supported extensions.
 static const struct tls_extension kExtensions[] = {
@@ -2978,6 +3140,15 @@
     ext_delegated_credential_parse_clienthello,
     dont_add_serverhello,
   },
+  {
+    TLSEXT_TYPE_application_settings,
+    NULL,
+    ext_alps_add_clienthello,
+    ext_alps_parse_serverhello,
+    // ALPS is negotiated late in |ssl_negotiate_alpn|.
+    ignore_parse_clienthello,
+    ext_alps_add_serverhello,
+  },
 };
 
 #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
@@ -3370,6 +3541,36 @@
   }
 }
 
+static bool ssl_check_serverhello_tlsext(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  // ALPS and ALPN have a dependency between each other, so we defer checking
+  // consistency to after the callbacks run.
+  if (hs->new_session != nullptr && hs->new_session->has_application_settings) {
+    // ALPN must be negotiated.
+    if (ssl->s3->alpn_selected.empty()) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_NEGOTIATED_ALPS_WITHOUT_ALPN);
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      return false;
+    }
+
+    // The negotiated protocol must be one of the ones we advertised for ALPS.
+    Span<const uint8_t> settings;
+    if (!ssl_get_local_application_settings(hs, &settings,
+                                            ssl->s3->alpn_selected)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_ALPN_PROTOCOL);
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      return false;
+    }
+
+    if (!hs->new_session->local_application_settings.CopyFrom(settings)) {
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+      return false;
+    }
+  }
+
+  return true;
+}
+
 bool ssl_parse_serverhello_tlsext(SSL_HANDSHAKE *hs, CBS *cbs) {
   SSL *const ssl = hs->ssl;
   int alert = SSL_AD_DECODE_ERROR;
@@ -3378,6 +3579,10 @@
     return false;
   }
 
+  if (!ssl_check_serverhello_tlsext(hs)) {
+    return false;
+  }
+
   return true;
 }