Add infrastructure for better extension handling.

Rather than four massive functions that handle every extension,
organise the code by extension with four smaller functions for each.

Change-Id: I876b31dacb05aca9884ed3ae7c48462e6ffe3b49
Reviewed-on: https://boringssl-review.googlesource.com/5142
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/crypto/bytestring/cbb.c b/crypto/bytestring/cbb.c
index f1e09a2..290d16f 100644
--- a/crypto/bytestring/cbb.c
+++ b/crypto/bytestring/cbb.c
@@ -243,6 +243,12 @@
   return 1;
 }
 
+size_t CBB_len(const CBB *cbb) {
+  assert(cbb->child == NULL);
+  assert(!cbb->base->can_resize);
+
+  return cbb->base->cap - cbb->base->len;
+}
 
 static int cbb_add_length_prefixed(CBB *cbb, CBB *out_contents,
                                    size_t len_len) {
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index c4a568c..e926724 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -357,6 +357,7 @@
 SSL,reason,278,TOO_MANY_WARNING_ALERTS
 SSL,reason,238,UNABLE_TO_FIND_ECDH_PARAMETERS
 SSL,reason,239,UNABLE_TO_FIND_PUBLIC_KEY_PARAMETERS
+SSL,reason,279,UNEXPECTED_EXTENSION
 SSL,reason,240,UNEXPECTED_GROUP_CLOSE
 SSL,reason,241,UNEXPECTED_MESSAGE
 SSL,reason,242,UNEXPECTED_OPERATOR_IN_GROUP
diff --git a/include/openssl/bytestring.h b/include/openssl/bytestring.h
index b30ae5c..8583c4e 100644
--- a/include/openssl/bytestring.h
+++ b/include/openssl/bytestring.h
@@ -278,6 +278,11 @@
  * on error. */
 OPENSSL_EXPORT int CBB_flush(CBB *cbb);
 
+/* CBB_len returns the number of bytes remaining in a fixed CBB. It is a fatal
+ * error to call this on a non-fixed CBB or one with any active children. This
+ * does not flush |cbb|. */
+OPENSSL_EXPORT size_t CBB_len(const CBB *cbb);
+
 /* CBB_add_u8_length_prefixed sets |*out_contents| to a new child of |cbb|. The
  * data written to |*out_contents| will be prefixed in |cbb| with an 8-bit
  * length. It returns one on success or zero on error. */
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 84897f1..93d5efe 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -1537,9 +1537,6 @@
                        * SSLv3/TLS rollback check */
   uint16_t max_send_fragment;
   char *tlsext_hostname;
-  /* should_ack_sni is true if the SNI extension should be acked. This is
-   * only used by a server. */
-  char should_ack_sni;
   /* RFC4507 session ticket expected to be received or sent */
   int tlsext_ticket_expected;
   size_t tlsext_ecpointformatlist_length;
@@ -3056,6 +3053,7 @@
 #define SSL_R_EMS_STATE_INCONSISTENT 276
 #define SSL_R_RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION 277
 #define SSL_R_TOO_MANY_WARNING_ALERTS 278
+#define SSL_R_UNEXPECTED_EXTENSION 279
 #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/include/openssl/ssl3.h b/include/openssl/ssl3.h
index 9021309..aa1256b 100644
--- a/include/openssl/ssl3.h
+++ b/include/openssl/ssl3.h
@@ -453,6 +453,24 @@
 
     int reuse_message;
 
+    union {
+      /* sent is a bitset where the bits correspond to elements of kExtensions
+       * in t1_lib.c. Each bit is set if that extension was sent in a
+       * ClientHello. It's not used by servers. */
+      uint32_t sent;
+      /* received is a bitset, like |sent|, but is used by servers to record
+       * which extensions were received from a client. */
+      uint32_t received;
+    } extensions;
+
+
+    /* SNI extension */
+
+    /* should_ack_sni is used by a server and indicates that the SNI extension
+     * should be echoed in the ServerHello. */
+    unsigned should_ack_sni:1;
+
+
     /* Client-only: cert_req determines if a client certificate is to be sent.
      * This is 0 if no client Certificate message is to be sent, 1 if there is
      * a client certificate, and 2 to send an empty client Certificate
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index 98cb383..5d490b5 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -796,11 +796,218 @@
   }
 }
 
+/* tls_extension represents a TLS extension that is handled internally. The
+ * |init| function is called for each handshake, before any other functions of
+ * the extension. Then the add and parse callbacks are called as needed.
+ *
+ * The parse callbacks receive a |CBS| that contains the contents of the
+ * extension (i.e. not including the type and length bytes). If an extension is
+ * not received then the parse callbacks will be called with a NULL CBS so that
+ * they can do any processing needed to handle the absence of an extension.
+ *
+ * The add callbacks receive a |CBB| to which the extension can be appended but
+ * the function is responsible for appending the type and length bytes too.
+ *
+ * All callbacks return one for success and zero for error. If a parse function
+ * returns zero then a fatal alert with value |*out_alert| will be sent. If
+ * |*out_alert| isn't set, then a |decode_error| alert will be sent. */
+struct tls_extension {
+  uint16_t value;
+  void (*init)(SSL *ssl);
+
+  int (*add_clienthello)(SSL *ssl, CBB *out);
+  int (*parse_serverhello)(SSL *ssl, uint8_t *out_alert, CBS *contents);
+
+  int (*parse_clienthello)(SSL *ssl, uint8_t *out_alert, CBS *contents);
+  int (*add_serverhello)(SSL *ssl, CBB *out);
+};
+
+
+/* Server name indication (SNI).
+ *
+ * https://tools.ietf.org/html/rfc6066#section-3. */
+
+static void ext_sni_init(SSL *ssl) {
+  ssl->s3->tmp.should_ack_sni = 0;
+}
+
+static int ext_sni_add_clienthello(SSL *ssl, CBB *out) {
+  if (ssl->tlsext_hostname == NULL) {
+    return 1;
+  }
+
+  CBB contents, server_name_list, name;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_server_name) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_u16_length_prefixed(&contents, &server_name_list) ||
+      !CBB_add_u8(&server_name_list, TLSEXT_NAMETYPE_host_name) ||
+      !CBB_add_u16_length_prefixed(&server_name_list, &name) ||
+      !CBB_add_bytes(&name, (const uint8_t *)ssl->tlsext_hostname,
+                     strlen(ssl->tlsext_hostname)) ||
+      !CBB_flush(out)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+static int ext_sni_parse_serverhello(SSL *ssl, uint8_t *out_alert, CBS *contents) {
+  if (contents == NULL) {
+    return 1;
+  }
+
+  if (CBS_len(contents) != 0) {
+    return 0;
+  }
+
+  assert(ssl->tlsext_hostname != NULL);
+
+  if (!ssl->hit) {
+    assert(ssl->session->tlsext_hostname == NULL);
+    ssl->session->tlsext_hostname = BUF_strdup(ssl->tlsext_hostname);
+    if (!ssl->session->tlsext_hostname) {
+      *out_alert = SSL_AD_INTERNAL_ERROR;
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+static int ext_sni_parse_clienthello(SSL *ssl, uint8_t *out_alert, CBS *contents) {
+  if (contents == NULL) {
+    return 1;
+  }
+
+  /* The servername extension is treated as follows:
+   *
+   * - Only the hostname type is supported with a maximum length of 255.
+   * - The servername is rejected if too long or if it contains zeros, in
+   *   which case an fatal alert is generated.
+   * - The servername field is maintained together with the session cache.
+   * - When a session is resumed, the servername callback is invoked in order
+   *   to allow the application to position itself to the right context.
+   * - The servername is acknowledged if it is new for a session or when
+   *   it is identical to a previously used for the same session.
+   *   Applications can control the behaviour.  They can at any time
+   *   set a 'desirable' servername for a new SSL object. This can be the
+   *   case for example with HTTPS when a Host: header field is received and
+   *   a renegotiation is requested. In this case, a possible servername
+   *   presented in the new client hello is only acknowledged if it matches
+   *   the value of the Host: field.
+   * - Applications must  use SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+   *   if they provide for changing an explicit servername context for the
+   *   session,
+   *   i.e. when the session has been established with a servername extension.
+   */
+
+  CBS server_name_list;
+  char have_seen_host_name = 0;
+
+  if (!CBS_get_u16_length_prefixed(contents, &server_name_list) ||
+      CBS_len(&server_name_list) == 0 ||
+      CBS_len(contents) != 0) {
+    return 0;
+  }
+
+  /* Decode each ServerName in the extension. */
+  while (CBS_len(&server_name_list) > 0) {
+    uint8_t name_type;
+    CBS host_name;
+
+    if (!CBS_get_u8(&server_name_list, &name_type) ||
+        !CBS_get_u16_length_prefixed(&server_name_list, &host_name)) {
+      return 0;
+    }
+
+    /* Only host_name is supported. */
+    if (name_type != TLSEXT_NAMETYPE_host_name) {
+      continue;
+    }
+
+    if (have_seen_host_name) {
+      /* The ServerNameList MUST NOT contain more than one name of the same
+       * name_type. */
+      return 0;
+    }
+
+    have_seen_host_name = 1;
+
+    if (CBS_len(&host_name) == 0 ||
+        CBS_len(&host_name) > TLSEXT_MAXLEN_host_name ||
+        CBS_contains_zero_byte(&host_name)) {
+      *out_alert = SSL_AD_UNRECOGNIZED_NAME;
+      return 0;
+    }
+
+    if (!ssl->hit) {
+      assert(ssl->session->tlsext_hostname == NULL);
+      if (ssl->session->tlsext_hostname) {
+        /* This should be impossible. */
+        return 0;
+      }
+
+      /* Copy the hostname as a string. */
+      if (!CBS_strdup(&host_name, &ssl->session->tlsext_hostname)) {
+        *out_alert = SSL_AD_INTERNAL_ERROR;
+        return 0;
+      }
+
+      ssl->s3->tmp.should_ack_sni = 1;
+    }
+  }
+
+  return 1;
+}
+
+static int ext_sni_add_serverhello(SSL *ssl, CBB *out) {
+  if (ssl->hit ||
+      !ssl->s3->tmp.should_ack_sni ||
+      ssl->session->tlsext_hostname == NULL) {
+    return 1;
+  }
+
+  if (!CBB_add_u16(out, TLSEXT_TYPE_server_name) ||
+      !CBB_add_u16(out, 0 /* length */)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+
+/* kExtensions contains all the supported extensions. */
+static const struct tls_extension kExtensions[] = {
+  {
+    TLSEXT_TYPE_server_name,
+    ext_sni_init,
+    ext_sni_add_clienthello,
+    ext_sni_parse_serverhello,
+    ext_sni_parse_clienthello,
+    ext_sni_add_serverhello,
+  },
+};
+
+#define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
+
+static const struct tls_extension *tls_extension_find(uint32_t *out_index,
+                                                      uint16_t value) {
+  unsigned i;
+  for (i = 0; i < kNumExtensions; i++) {
+    if (kExtensions[i].value == value) {
+      *out_index = i;
+      return &kExtensions[i];
+    }
+  }
+
+  return NULL;
+}
+
 /* header_len is the length of the ClientHello header written so far, used to
  * compute padding. It does not include the record header. Pass 0 if no padding
  * is to be done. */
-uint8_t *ssl_add_clienthello_tlsext(SSL *s, uint8_t *buf, uint8_t *limit,
-                                    size_t header_len) {
+uint8_t *ssl_add_clienthello_tlsext(SSL *s, uint8_t *const buf,
+                                    uint8_t *const limit, size_t header_len) {
   int extdatalen = 0;
   uint8_t *ret = buf;
   uint8_t *orig = buf;
@@ -835,38 +1042,41 @@
     return NULL; /* should never occur. */
   }
 
-  if (s->tlsext_hostname != NULL) {
-    /* Add TLS extension servername to the Client Hello message */
-    unsigned long size_str;
-    long lenmax;
+  OPENSSL_COMPILE_ASSERT(
+      kNumExtensions <= sizeof(s->s3->tmp.extensions.sent) * 8,
+      too_many_extensions_for_bitset);
+  s->s3->tmp.extensions.sent = 0;
 
-    /* check for enough space.
-       4 for the servername type and entension length
-       2 for servernamelist length
-       1 for the hostname type
-       2 for hostname length
-       + hostname length */
+  size_t i;
+  for (i = 0; i < kNumExtensions; i++) {
+    if (kExtensions[i].init != NULL) {
+      kExtensions[i].init(s);
+    }
+  }
 
-    lenmax = limit - ret - 9;
-    size_str = strlen(s->tlsext_hostname);
-    if (lenmax < 0 || size_str > (unsigned long)lenmax) {
+  CBB cbb;
+  if (!CBB_init_fixed(&cbb, ret, limit - ret)) {
+    OPENSSL_PUT_ERROR(SSL, ssl_add_clienthello_tlsext, ERR_R_INTERNAL_ERROR);
+    return NULL;
+  }
+
+  for (i = 0; i < kNumExtensions; i++) {
+    const size_t space_before = CBB_len(&cbb);
+    if (!kExtensions[i].add_clienthello(s, &cbb)) {
+      CBB_cleanup(&cbb);
+      OPENSSL_PUT_ERROR(SSL, ssl_add_clienthello_tlsext, ERR_R_INTERNAL_ERROR);
       return NULL;
     }
+    const size_t space_after = CBB_len(&cbb);
 
-    /* extension type and length */
-    s2n(TLSEXT_TYPE_server_name, ret);
-    s2n(size_str + 5, ret);
-
-    /* length of servername list */
-    s2n(size_str + 3, ret);
-
-    /* hostname type, length and hostname */
-    *(ret++) = (uint8_t)TLSEXT_NAMETYPE_host_name;
-    s2n(size_str, ret);
-    memcpy(ret, s->tlsext_hostname, size_str);
-    ret += size_str;
+    if (space_after != space_before) {
+      s->s3->tmp.extensions.sent |= (1u << i);
+    }
   }
 
+  ret = limit - CBB_len(&cbb);
+  CBB_cleanup(&cbb);
+
   /* Add RI if renegotiating */
   if (s->s3->initial_handshake_complete) {
     int el;
@@ -1026,7 +1236,7 @@
     long lenmax;
     const uint8_t *formats;
     const uint16_t *curves;
-    size_t formats_len, curves_len, i;
+    size_t formats_len, curves_len;
 
     tls1_get_formatlist(s, &formats, &formats_len);
 
@@ -1123,7 +1333,8 @@
   return ret;
 }
 
-uint8_t *ssl_add_serverhello_tlsext(SSL *s, uint8_t *buf, uint8_t *limit) {
+uint8_t *ssl_add_serverhello_tlsext(SSL *s, uint8_t *const buf,
+                                    uint8_t *const limit) {
   int extdatalen = 0;
   uint8_t *orig = buf;
   uint8_t *ret = buf;
@@ -1143,15 +1354,29 @@
     return NULL; /* should never happen. */
   }
 
-  if (!s->hit && s->should_ack_sni && s->session->tlsext_hostname != NULL) {
-    if ((long)(limit - ret - 4) < 0) {
-      return NULL;
+  CBB cbb;
+  if (!CBB_init_fixed(&cbb, ret, limit - ret)) {
+    OPENSSL_PUT_ERROR(SSL, ssl_add_serverhello_tlsext, ERR_R_INTERNAL_ERROR);
+    return NULL;
+  }
+
+  unsigned i;
+  for (i = 0; i < kNumExtensions; i++) {
+    if (!(s->s3->tmp.extensions.received & (1u << i))) {
+      /* Don't send extensions that were not received. */
+      continue;
     }
 
-    s2n(TLSEXT_TYPE_server_name, ret);
-    s2n(0, ret);
+    if (!kExtensions[i].add_serverhello(s, &cbb)) {
+      CBB_cleanup(&cbb);
+      OPENSSL_PUT_ERROR(SSL, ssl_add_serverhello_tlsext, ERR_R_INTERNAL_ERROR);
+      return NULL;
+    }
   }
 
+  ret = limit - CBB_len(&cbb);
+  CBB_cleanup(&cbb);
+
   if (s->s3->send_connection_binding) {
     int el;
 
@@ -1362,7 +1587,6 @@
   int renegotiate_seen = 0;
   CBS extensions;
 
-  s->should_ack_sni = 0;
   s->srtp_profile = NULL;
   s->s3->next_proto_neg_seen = 0;
   s->s3->tmp.certificate_status_expected = 0;
@@ -1390,9 +1614,18 @@
   s->s3->tmp.peer_ellipticcurvelist = NULL;
   s->s3->tmp.peer_ellipticcurvelist_length = 0;
 
+  size_t i;
+  for (i = 0; i < kNumExtensions; i++) {
+    if (kExtensions[i].init != NULL) {
+      kExtensions[i].init(s);
+    }
+  }
+
+  s->s3->tmp.extensions.received = 0;
+
   /* There may be no extensions. */
   if (CBS_len(cbs) == 0) {
-    goto ri_check;
+    goto no_extensions;
   }
 
   /* Decode the extensions block and check it is valid. */
@@ -1413,93 +1646,26 @@
       return 0;
     }
 
-    /* The servername extension is treated as follows:
+    OPENSSL_COMPILE_ASSERT(
+        kNumExtensions <= sizeof(s->s3->tmp.extensions.received) * 8,
+        too_many_extensions_for_bitset);
 
-       - Only the hostname type is supported with a maximum length of 255.
-       - The servername is rejected if too long or if it contains zeros, in
-         which case an fatal alert is generated.
-       - The servername field is maintained together with the session cache.
-       - When a session is resumed, the servername call back invoked in order
-         to allow the application to position itself to the right context.
-       - The servername is acknowledged if it is new for a session or when
-         it is identical to a previously used for the same session.
-         Applications can control the behaviour.  They can at any time
-         set a 'desirable' servername for a new SSL object. This can be the
-         case for example with HTTPS when a Host: header field is received and
-         a renegotiation is requested. In this case, a possible servername
-         presented in the new client hello is only acknowledged if it matches
-         the value of the Host: field.
-       - Applications must  use SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
-         if they provide for changing an explicit servername context for the
-       session,
-         i.e. when the session has been established with a servername extension.
-       - On session reconnect, the servername extension may be absent. */
+    unsigned ext_index;
+    const struct tls_extension *const ext =
+        tls_extension_find(&ext_index, type);
 
-    if (type == TLSEXT_TYPE_server_name) {
-      CBS server_name_list;
-      char have_seen_host_name = 0;
-
-      if (!CBS_get_u16_length_prefixed(&extension, &server_name_list) ||
-          CBS_len(&server_name_list) < 1 || CBS_len(&extension) != 0) {
-        *out_alert = SSL_AD_DECODE_ERROR;
+    if (ext != NULL) {
+      s->s3->tmp.extensions.received |= (1u << ext_index);
+      uint8_t alert = SSL_AD_DECODE_ERROR;
+      if (!ext->parse_clienthello(s, &alert, &extension)) {
+        *out_alert = alert;
         return 0;
       }
 
-      /* Decode each ServerName in the extension. */
-      while (CBS_len(&server_name_list) > 0) {
-        uint8_t name_type;
-        CBS host_name;
+      continue;
+    }
 
-        /* Decode the NameType. */
-        if (!CBS_get_u8(&server_name_list, &name_type)) {
-          *out_alert = SSL_AD_DECODE_ERROR;
-          return 0;
-        }
-
-        /* Only host_name is supported. */
-        if (name_type != TLSEXT_NAMETYPE_host_name) {
-          continue;
-        }
-
-        if (have_seen_host_name) {
-          /* The ServerNameList MUST NOT contain more than one name of the same
-           * name_type. */
-          *out_alert = SSL_AD_DECODE_ERROR;
-          return 0;
-        }
-
-        have_seen_host_name = 1;
-
-        if (!CBS_get_u16_length_prefixed(&server_name_list, &host_name) ||
-            CBS_len(&host_name) < 1) {
-          *out_alert = SSL_AD_DECODE_ERROR;
-          return 0;
-        }
-
-        if (CBS_len(&host_name) > TLSEXT_MAXLEN_host_name ||
-            CBS_contains_zero_byte(&host_name)) {
-          *out_alert = SSL_AD_UNRECOGNIZED_NAME;
-          return 0;
-        }
-
-        if (!s->hit) {
-          assert(s->session->tlsext_hostname == NULL);
-          if (s->session->tlsext_hostname) {
-            /* This should be impossible. */
-            *out_alert = SSL_AD_DECODE_ERROR;
-            return 0;
-          }
-
-          /* Copy the hostname as a string. */
-          if (!CBS_strdup(&host_name, &s->session->tlsext_hostname)) {
-            *out_alert = SSL_AD_INTERNAL_ERROR;
-            return 0;
-          }
-
-          s->should_ack_sni = 1;
-        }
-      }
-    } else if (type == TLSEXT_TYPE_ec_point_formats) {
+    if (type == TLSEXT_TYPE_ec_point_formats) {
       CBS ec_point_format_list;
 
       if (!CBS_get_u8_length_prefixed(&extension, &ec_point_format_list) ||
@@ -1515,7 +1681,7 @@
       }
     } else if (type == TLSEXT_TYPE_elliptic_curves) {
       CBS elliptic_curve_list;
-      size_t i, num_curves;
+      size_t num_curves;
 
       if (!CBS_get_u16_length_prefixed(&extension, &elliptic_curve_list) ||
           CBS_len(&elliptic_curve_list) == 0 ||
@@ -1635,7 +1801,19 @@
     }
   }
 
-ri_check:
+no_extensions:
+  for (i = 0; i < kNumExtensions; i++) {
+    if (!(s->s3->tmp.extensions.received & (1u << i))) {
+      /* Extension wasn't observed so call the callback with a NULL
+       * parameter. */
+      uint8_t alert = SSL_AD_DECODE_ERROR;
+      if (!kExtensions[i].parse_clienthello(s, &alert, NULL)) {
+        *out_alert = alert;
+        return 0;
+      }
+    }
+  }
+
   /* Need RI if renegotiating */
 
   if (!renegotiate_seen && s->s3->initial_handshake_complete &&
@@ -1682,7 +1860,6 @@
 }
 
 static int ssl_scan_serverhello_tlsext(SSL *s, CBS *cbs, int *out_alert) {
-  int tlsext_servername = 0;
   int renegotiate_seen = 0;
   CBS extensions;
 
@@ -1703,9 +1880,16 @@
   s->s3->tmp.peer_ecpointformatlist = NULL;
   s->s3->tmp.peer_ecpointformatlist_length = 0;
 
+  uint32_t received = 0;
+  size_t i;
+
+  OPENSSL_COMPILE_ASSERT(
+      kNumExtensions <= sizeof(received) * 8,
+      too_many_extensions_for_bitset);
+
   /* There may be no extensions. */
   if (CBS_len(cbs) == 0) {
-    goto ri_check;
+    goto no_extensions;
   }
 
   /* Decode the extensions block and check it is valid. */
@@ -1726,21 +1910,35 @@
       return 0;
     }
 
-    if (type == TLSEXT_TYPE_server_name) {
-      /* The extension must be empty. */
-      if (CBS_len(&extension) != 0) {
+    unsigned ext_index;
+    const struct tls_extension *const ext =
+        tls_extension_find(&ext_index, type);
+
+    /* While we have extensions that don't use tls_extension this conditional
+     * needs to be guarded on |ext != NULL|. In the future, ext being NULL will
+     * be fatal. */
+    if (ext != NULL) {
+      if (!(s->s3->tmp.extensions.sent & (1u << ext_index))) {
+        /* Received an extension that was never sent. */
+        OPENSSL_PUT_ERROR(SSL, ssl_scan_serverhello_tlsext,
+                          SSL_R_UNEXPECTED_EXTENSION);
+        ERR_add_error_dataf("ext:%u", (unsigned) type);
         *out_alert = SSL_AD_DECODE_ERROR;
         return 0;
       }
 
-      /* We must have sent it in ClientHello. */
-      if (s->tlsext_hostname == NULL) {
-        *out_alert = SSL_AD_UNSUPPORTED_EXTENSION;
+      received |= (1u << ext_index);
+
+      uint8_t alert = SSL_AD_DECODE_ERROR;
+      if (!ext->parse_serverhello(s, &alert, &extension)) {
+        *out_alert = alert;
         return 0;
       }
 
-      tlsext_servername = 1;
-    } else if (type == TLSEXT_TYPE_ec_point_formats) {
+      continue;
+    }
+
+    if (type == TLSEXT_TYPE_ec_point_formats) {
       CBS ec_point_format_list;
 
       if (!CBS_get_u8_length_prefixed(&extension, &ec_point_format_list) ||
@@ -1884,20 +2082,19 @@
     }
   }
 
-  if (!s->hit && tlsext_servername == 1 && s->tlsext_hostname) {
-    if (s->session->tlsext_hostname == NULL) {
-      s->session->tlsext_hostname = BUF_strdup(s->tlsext_hostname);
-      if (!s->session->tlsext_hostname) {
-        *out_alert = SSL_AD_UNRECOGNIZED_NAME;
+no_extensions:
+  for (i = 0; i < kNumExtensions; i++) {
+    if (!(received & (1u << i))) {
+      /* Extension wasn't observed so call the callback with a NULL
+       * parameter. */
+      uint8_t alert = SSL_AD_DECODE_ERROR;
+      if (!kExtensions[i].parse_serverhello(s, &alert, NULL)) {
+        *out_alert = alert;
         return 0;
       }
-    } else {
-      *out_alert = SSL_AD_DECODE_ERROR;
-      return 0;
     }
   }
 
-ri_check:
   /* Determine if we need to see RI. Strictly speaking if we want to avoid an
    * attack we should *always* see RI even on initial server hello because the
    * client doesn't see any renegotiation during an attack. However this would
@@ -1944,7 +2141,7 @@
       return 1;
 
     case SSL_TLSEXT_ERR_NOACK:
-      s->should_ack_sni = 0;
+      s->s3->tmp.should_ack_sni = 0;
       return 1;
 
     default: