external/boringssl: Sync to 2070f8ad9151dc8f3a73bffaa146b5e6937a583f.

This includes the following changes:

https://boringssl.googlesource.com/boringssl/+log/8ebeabf0e2e01b331e56d0a491c12539baa55d3d..2070f8ad9151dc8f3a73bffaa146b5e6937a583f

Test: BoringSSL CTS Presubmits
Change-Id: Ia779c6476e45c44e426e09afeca65b2192e783ae
diff --git a/src/ssl/d1_both.c b/src/ssl/d1_both.c
index b864e42..44e3f2e 100644
--- a/src/ssl/d1_both.c
+++ b/src/ssl/d1_both.c
@@ -123,7 +123,6 @@
 #include <openssl/mem.h>
 #include <openssl/rand.h>
 #include <openssl/type_check.h>
-#include <openssl/x509.h>
 
 #include "../crypto/internal.h"
 #include "internal.h"
diff --git a/src/ssl/handshake_client.c b/src/ssl/handshake_client.c
index c457772..1feb7d8 100644
--- a/src/ssl/handshake_client.c
+++ b/src/ssl/handshake_client.c
@@ -164,8 +164,6 @@
 #include <openssl/md5.h>
 #include <openssl/mem.h>
 #include <openssl/rand.h>
-#include <openssl/x509.h>
-#include <openssl/x509v3.h>
 
 #include "../crypto/internal.h"
 #include "internal.h"
diff --git a/src/ssl/handshake_server.c b/src/ssl/handshake_server.c
index e3a4e51..81e45ef 100644
--- a/src/ssl/handshake_server.c
+++ b/src/ssl/handshake_server.c
@@ -930,6 +930,9 @@
     case ssl_session_retry:
       ssl->rwstate = SSL_PENDING_SESSION;
       goto err;
+    case ssl_session_ticket_retry:
+      ssl->rwstate = SSL_PENDING_TICKET;
+      goto err;
   }
 
   if (session != NULL) {
diff --git a/src/ssl/internal.h b/src/ssl/internal.h
index caa9cd2..99980d8 100644
--- a/src/ssl/internal.h
+++ b/src/ssl/internal.h
@@ -226,14 +226,14 @@
 
 /* ssl_create_cipher_list evaluates |rule_str| according to the ciphers in
  * |ssl_method|. It sets |*out_cipher_list| to a newly-allocated
- * |ssl_cipher_preference_list_st| containing the result. It returns
- * |(*out_cipher_list)->ciphers| on success and NULL on failure. If |strict| is
- * true, nonsense will be rejected. If false, nonsense will be silently
- * ignored. */
-STACK_OF(SSL_CIPHER) *
-ssl_create_cipher_list(const SSL_PROTOCOL_METHOD *ssl_method,
-                       struct ssl_cipher_preference_list_st **out_cipher_list,
-                       const char *rule_str, int strict);
+ * |ssl_cipher_preference_list_st| containing the result. It returns 1 on
+ * success and 0 on failure. If |strict| is true, nonsense will be rejected. If
+ * false, nonsense will be silently ignored. An empty result is considered an
+ * error regardless of |strict|. */
+int ssl_create_cipher_list(
+    const SSL_PROTOCOL_METHOD *ssl_method,
+    struct ssl_cipher_preference_list_st **out_cipher_list,
+    const char *rule_str, int strict);
 
 /* ssl_cipher_get_value returns the cipher suite id of |cipher|. */
 uint16_t ssl_cipher_get_value(const SSL_CIPHER *cipher);
@@ -352,6 +352,8 @@
    * records. */
   uint8_t fixed_nonce[12];
   uint8_t fixed_nonce_len, variable_nonce_len;
+  /* version is the protocol version that should be used with this AEAD. */
+  uint16_t version;
   /* variable_nonce_included_in_record is non-zero if the variable nonce
    * for a record is included as a prefix before the ciphertext. */
   unsigned variable_nonce_included_in_record : 1;
@@ -927,6 +929,7 @@
   ssl_hs_x509_lookup,
   ssl_hs_channel_id_lookup,
   ssl_hs_private_key_operation,
+  ssl_hs_pending_ticket,
 };
 
 struct ssl_handshake_st {
@@ -1158,10 +1161,9 @@
 
 int ssl_ext_pre_shared_key_parse_serverhello(SSL_HANDSHAKE *hs,
                                              uint8_t *out_alert, CBS *contents);
-int ssl_ext_pre_shared_key_parse_clienthello(SSL_HANDSHAKE *hs,
-                                             SSL_SESSION **out_session,
-                                             CBS *out_binders,
-                                             uint8_t *out_alert, CBS *contents);
+int ssl_ext_pre_shared_key_parse_clienthello(
+    SSL_HANDSHAKE *hs, CBS *out_ticket, CBS *out_binders,
+    uint32_t *out_obfuscated_ticket_age, uint8_t *out_alert, CBS *contents);
 int ssl_ext_pre_shared_key_add_serverhello(SSL_HANDSHAKE *hs, CBB *out);
 
 /* ssl_is_sct_list_valid does a shallow parse of the SCT list in |contents| and
@@ -1679,6 +1681,11 @@
    *     verified Channel ID from the client: a P256 point, (x,y), where
    *     each are big-endian values. */
   uint8_t tlsext_channel_id[64];
+
+  /* ticket_age_skew is the difference, in seconds, between the client-sent
+   * ticket age and the server-computed value in TLS 1.3 server connections
+   * which resumed a session. */
+  int32_t ticket_age_skew;
 } SSL3_STATE;
 
 /* lengths of messages */
@@ -1875,7 +1882,9 @@
   size_t supported_group_list_len;
   uint16_t *supported_group_list; /* our list */
 
-  SSL_CTX *initial_ctx; /* initial ctx, used to store sessions */
+  /* session_ctx is the |SSL_CTX| used for the session cache and related
+   * settings. */
+  SSL_CTX *session_ctx;
 
   /* srtp_profiles is the list of configured SRTP protection profiles for
    * DTLS-SRTP. */
@@ -1983,13 +1992,15 @@
   ssl_session_success,
   ssl_session_error,
   ssl_session_retry,
+  ssl_session_ticket_retry,
 };
 
 /* ssl_get_prev_session looks up the previous session based on |client_hello|.
  * On success, it sets |*out_session| to the session or NULL if none was found.
  * If the session could not be looked up synchronously, it returns
- * |ssl_session_retry| and should be called again. Otherwise, it returns
- * |ssl_session_error|.  */
+ * |ssl_session_retry| and should be called again. If a ticket could not be
+ * decrypted immediately it returns |ssl_session_ticket_retry| and should also
+ * be called again. Otherwise, it returns |ssl_session_error|.  */
 enum ssl_session_result_t ssl_get_prev_session(
     SSL *ssl, SSL_SESSION **out_session, int *out_tickets_supported,
     int *out_renew_ticket, const SSL_CLIENT_HELLO *client_hello);
@@ -2027,8 +2038,6 @@
 
 void ssl_update_cache(SSL_HANDSHAKE *hs, int mode);
 
-int ssl_verify_alarm_type(long type);
-
 int ssl3_get_finished(SSL_HANDSHAKE *hs);
 int ssl3_send_alert(SSL *ssl, int level, int desc);
 int ssl3_get_message(SSL *ssl);
@@ -2159,15 +2168,19 @@
 
 #define tlsext_tick_md EVP_sha256
 
-/* tls_process_ticket processes a session ticket from the client. On success,
- * it sets |*out_session| to the decrypted session or NULL if the ticket was
- * rejected. If the ticket was valid, it sets |*out_renew_ticket| to whether
- * the ticket should be renewed. It returns one on success and zero on fatal
- * error. */
-int tls_process_ticket(SSL *ssl, SSL_SESSION **out_session,
-                       int *out_renew_ticket, const uint8_t *ticket,
-                       size_t ticket_len, const uint8_t *session_id,
-                       size_t session_id_len);
+/* ssl_process_ticket processes a session ticket from the client. It returns
+ * one of:
+ *   |ssl_ticket_aead_success|: |*out_session| is set to the parsed session and
+ *       |*out_renew_ticket| is set to whether the ticket should be renewed.
+ *   |ssl_ticket_aead_ignore_ticket|: |*out_renew_ticket| is set to whether a
+ *       fresh ticket should be sent, but the given ticket cannot be used.
+ *   |ssl_ticket_aead_retry|: the ticket could not be immediately decrypted.
+ *       Retry later.
+ *   |ssl_ticket_aead_error|: an error occured that is fatal to the connection. */
+enum ssl_ticket_aead_result_t ssl_process_ticket(
+    SSL *ssl, SSL_SESSION **out_session, int *out_renew_ticket,
+    const uint8_t *ticket, size_t ticket_len, const uint8_t *session_id,
+    size_t session_id_len);
 
 /* tls1_verify_channel_id processes the current message as a Channel ID message,
  * and verifies the signature. If the key is valid, it saves the Channel ID and
diff --git a/src/ssl/s3_both.c b/src/ssl/s3_both.c
index 6b03030..8fa51e9 100644
--- a/src/ssl/s3_both.c
+++ b/src/ssl/s3_both.c
@@ -125,7 +125,6 @@
 #include <openssl/nid.h>
 #include <openssl/rand.h>
 #include <openssl/sha.h>
-#include <openssl/x509.h>
 
 #include "../crypto/internal.h"
 #include "internal.h"
@@ -770,80 +769,6 @@
   }
 }
 
-int ssl_verify_alarm_type(long type) {
-  int al;
-
-  switch (type) {
-    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
-    case X509_V_ERR_UNABLE_TO_GET_CRL:
-    case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER:
-      al = SSL_AD_UNKNOWN_CA;
-      break;
-
-    case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
-    case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
-    case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
-    case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
-    case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
-    case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
-    case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
-    case X509_V_ERR_CERT_NOT_YET_VALID:
-    case X509_V_ERR_CRL_NOT_YET_VALID:
-    case X509_V_ERR_CERT_UNTRUSTED:
-    case X509_V_ERR_CERT_REJECTED:
-    case X509_V_ERR_HOSTNAME_MISMATCH:
-    case X509_V_ERR_EMAIL_MISMATCH:
-    case X509_V_ERR_IP_ADDRESS_MISMATCH:
-      al = SSL_AD_BAD_CERTIFICATE;
-      break;
-
-    case X509_V_ERR_CERT_SIGNATURE_FAILURE:
-    case X509_V_ERR_CRL_SIGNATURE_FAILURE:
-      al = SSL_AD_DECRYPT_ERROR;
-      break;
-
-    case X509_V_ERR_CERT_HAS_EXPIRED:
-    case X509_V_ERR_CRL_HAS_EXPIRED:
-      al = SSL_AD_CERTIFICATE_EXPIRED;
-      break;
-
-    case X509_V_ERR_CERT_REVOKED:
-      al = SSL_AD_CERTIFICATE_REVOKED;
-      break;
-
-    case X509_V_ERR_UNSPECIFIED:
-    case X509_V_ERR_OUT_OF_MEM:
-    case X509_V_ERR_INVALID_CALL:
-    case X509_V_ERR_STORE_LOOKUP:
-      al = SSL_AD_INTERNAL_ERROR;
-      break;
-
-    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
-    case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
-    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
-    case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
-    case X509_V_ERR_CERT_CHAIN_TOO_LONG:
-    case X509_V_ERR_PATH_LENGTH_EXCEEDED:
-    case X509_V_ERR_INVALID_CA:
-      al = SSL_AD_UNKNOWN_CA;
-      break;
-
-    case X509_V_ERR_APPLICATION_VERIFICATION:
-      al = SSL_AD_HANDSHAKE_FAILURE;
-      break;
-
-    case X509_V_ERR_INVALID_PURPOSE:
-      al = SSL_AD_UNSUPPORTED_CERTIFICATE;
-      break;
-
-    default:
-      al = SSL_AD_CERTIFICATE_UNKNOWN;
-      break;
-  }
-
-  return al;
-}
-
 int ssl_parse_extensions(const CBS *cbs, uint8_t *out_alert,
                          const SSL_EXTENSION_TYPE *ext_types,
                          size_t num_ext_types, int ignore_unknown) {
diff --git a/src/ssl/ssl_aead_ctx.c b/src/ssl/ssl_aead_ctx.c
index e18ba69..1af4a5a 100644
--- a/src/ssl/ssl_aead_ctx.c
+++ b/src/ssl/ssl_aead_ctx.c
@@ -66,6 +66,7 @@
   }
   OPENSSL_memset(aead_ctx, 0, sizeof(SSL_AEAD_CTX));
   aead_ctx->cipher = cipher;
+  aead_ctx->version = version;
 
   if (!EVP_AEAD_CTX_init_with_direction(
           &aead_ctx->ctx, aead, enc_key, enc_key_len,
diff --git a/src/ssl/ssl_cert.c b/src/ssl/ssl_cert.c
index 1309a18..5013b20 100644
--- a/src/ssl/ssl_cert.c
+++ b/src/ssl/ssl_cert.c
@@ -127,7 +127,6 @@
 #include <openssl/mem.h>
 #include <openssl/sha.h>
 #include <openssl/x509.h>
-#include <openssl/x509v3.h>
 
 #include "../crypto/internal.h"
 #include "internal.h"
@@ -249,18 +248,33 @@
   c->cert_cb_arg = arg;
 }
 
-int ssl_set_cert(CERT *cert, CRYPTO_BUFFER *buffer) {
+enum leaf_cert_and_privkey_result_t {
+  leaf_cert_and_privkey_error,
+  leaf_cert_and_privkey_ok,
+  leaf_cert_and_privkey_mismatch,
+};
+
+/* check_leaf_cert_and_privkey checks whether the certificate in |leaf_buffer|
+ * and the private key in |privkey| are suitable and coherent. It returns
+ * |leaf_cert_and_privkey_error| and pushes to the error queue if a problem is
+ * found. If the certificate and private key are valid, but incoherent, it
+ * returns |leaf_cert_and_privkey_mismatch|. Otherwise it returns
+ * |leaf_cert_and_privkey_ok|. */
+static enum leaf_cert_and_privkey_result_t check_leaf_cert_and_privkey(
+    CRYPTO_BUFFER *leaf_buffer, EVP_PKEY *privkey) {
+  enum leaf_cert_and_privkey_result_t ret = leaf_cert_and_privkey_error;
+
   CBS cert_cbs;
-  CRYPTO_BUFFER_init_CBS(buffer, &cert_cbs);
+  CRYPTO_BUFFER_init_CBS(leaf_buffer, &cert_cbs);
   EVP_PKEY *pubkey = ssl_cert_parse_pubkey(&cert_cbs);
   if (pubkey == NULL) {
-    return 0;
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    goto out;
   }
 
   if (!ssl_is_key_type_supported(pubkey->type)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-    EVP_PKEY_free(pubkey);
-    return 0;
+    goto out;
   }
 
   /* An ECC certificate may be usable for ECDH or ECDSA. We only support ECDSA
@@ -268,26 +282,102 @@
   if (pubkey->type == EVP_PKEY_EC &&
       !ssl_cert_check_digital_signature_key_usage(&cert_cbs)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-    EVP_PKEY_free(pubkey);
+    goto out;
+  }
+
+  if (privkey != NULL &&
+      /* Sanity-check that the private key and the certificate match. */
+      !ssl_compare_public_and_private_key(pubkey, privkey)) {
+    ERR_clear_error();
+    ret = leaf_cert_and_privkey_mismatch;
+    goto out;
+  }
+
+  ret = leaf_cert_and_privkey_ok;
+
+out:
+  EVP_PKEY_free(pubkey);
+  return ret;
+}
+
+static int cert_set_chain_and_key(
+    CERT *cert, CRYPTO_BUFFER *const *certs, size_t num_certs,
+    EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+  if (num_certs == 0 ||
+      (privkey == NULL && privkey_method == NULL)) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
     return 0;
   }
 
-  if (cert->privatekey != NULL) {
-    /* Sanity-check that the private key and the certificate match, unless the
-     * key is opaque (in case of, say, a smartcard). */
-    if (!EVP_PKEY_is_opaque(cert->privatekey) &&
-        !ssl_compare_public_and_private_key(pubkey, cert->privatekey)) {
-      /* don't fail for a cert/key mismatch, just free current private key
-       * (when switching to a different cert & key, first this function should
-       * be used, then ssl_set_pkey */
-      EVP_PKEY_free(cert->privatekey);
-      cert->privatekey = NULL;
-      /* clear error queue */
-      ERR_clear_error();
-    }
+  if (privkey != NULL && privkey_method != NULL) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD);
+    return 0;
   }
 
-  EVP_PKEY_free(pubkey);
+  switch (check_leaf_cert_and_privkey(certs[0], privkey)) {
+    case leaf_cert_and_privkey_error:
+      return 0;
+    case leaf_cert_and_privkey_mismatch:
+      OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_AND_PRIVATE_KEY_MISMATCH);
+      return 0;
+    case leaf_cert_and_privkey_ok:
+      break;
+  }
+
+  STACK_OF(CRYPTO_BUFFER) *certs_sk = sk_CRYPTO_BUFFER_new_null();
+  if (certs_sk == NULL) {
+    return 0;
+  }
+
+  for (size_t i = 0; i < num_certs; i++) {
+    if (!sk_CRYPTO_BUFFER_push(certs_sk, certs[i])) {
+      sk_CRYPTO_BUFFER_pop_free(certs_sk, CRYPTO_BUFFER_free);
+      return 0;
+    }
+    CRYPTO_BUFFER_up_ref(certs[i]);
+  }
+
+  EVP_PKEY_free(cert->privatekey);
+  cert->privatekey = privkey;
+  if (privkey != NULL) {
+    EVP_PKEY_up_ref(privkey);
+  }
+  cert->key_method = privkey_method;
+
+  sk_CRYPTO_BUFFER_pop_free(cert->chain, CRYPTO_BUFFER_free);
+  cert->chain = certs_sk;
+
+  return 1;
+}
+
+int SSL_set_chain_and_key(SSL *ssl, CRYPTO_BUFFER *const *certs,
+                          size_t num_certs, EVP_PKEY *privkey,
+                          const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+  return cert_set_chain_and_key(ssl->cert, certs, num_certs, privkey,
+                                privkey_method);
+}
+
+int SSL_CTX_set_chain_and_key(SSL_CTX *ctx, CRYPTO_BUFFER *const *certs,
+                              size_t num_certs, EVP_PKEY *privkey,
+                              const SSL_PRIVATE_KEY_METHOD *privkey_method) {
+  return cert_set_chain_and_key(ctx->cert, certs, num_certs, privkey,
+                                privkey_method);
+}
+
+int ssl_set_cert(CERT *cert, CRYPTO_BUFFER *buffer) {
+  switch (check_leaf_cert_and_privkey(buffer, cert->privatekey)) {
+    case leaf_cert_and_privkey_error:
+      return 0;
+    case leaf_cert_and_privkey_mismatch:
+      /* don't fail for a cert/key mismatch, just free current private key
+       * (when switching to a different cert & key, first this function should
+       * be used, then |ssl_set_pkey|. */
+      EVP_PKEY_free(cert->privatekey);
+      cert->privatekey = NULL;
+      break;
+    case leaf_cert_and_privkey_ok:
+      break;
+  }
 
   cert->x509_method->cert_flush_cached_leaf(cert);
 
@@ -495,6 +585,12 @@
 
 int ssl_compare_public_and_private_key(const EVP_PKEY *pubkey,
                                        const EVP_PKEY *privkey) {
+  if (EVP_PKEY_is_opaque(privkey)) {
+    /* We cannot check an opaque private key and have to trust that it
+     * matches. */
+    return 1;
+  }
+
   int ret = 0;
 
   switch (EVP_PKEY_cmp(pubkey, privkey)) {
diff --git a/src/ssl/ssl_cipher.c b/src/ssl/ssl_cipher.c
index 4a7459f..4ee3c12 100644
--- a/src/ssl/ssl_cipher.c
+++ b/src/ssl/ssl_cipher.c
@@ -1254,10 +1254,10 @@
   return 1;
 }
 
-STACK_OF(SSL_CIPHER) *
-ssl_create_cipher_list(const SSL_PROTOCOL_METHOD *ssl_method,
-                       struct ssl_cipher_preference_list_st **out_cipher_list,
-                       const char *rule_str, int strict) {
+int ssl_create_cipher_list(
+    const SSL_PROTOCOL_METHOD *ssl_method,
+    struct ssl_cipher_preference_list_st **out_cipher_list,
+    const char *rule_str, int strict) {
   STACK_OF(SSL_CIPHER) *cipherstack = NULL;
   CIPHER_ORDER *co_list = NULL, *head = NULL, *tail = NULL, *curr;
   uint8_t *in_group_flags = NULL;
@@ -1266,7 +1266,7 @@
 
   /* Return with error if nothing to do. */
   if (rule_str == NULL || out_cipher_list == NULL) {
-    return NULL;
+    return 0;
   }
 
   /* Now we have to collect the available ciphers from the compiled in ciphers.
@@ -1275,7 +1275,7 @@
   co_list = OPENSSL_malloc(sizeof(CIPHER_ORDER) * kCiphersLen);
   if (co_list == NULL) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return NULL;
+    return 0;
   }
 
   ssl_cipher_collect_ciphers(ssl_method, co_list, &head, &tail);
@@ -1395,7 +1395,14 @@
   *out_cipher_list = pref_list;
   pref_list = NULL;
 
-  return cipherstack;
+  /* Configuring an empty cipher list is an error but still updates the
+   * output. */
+  if (sk_SSL_CIPHER_num((*out_cipher_list)->ciphers) == 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CIPHER_MATCH);
+    return 0;
+  }
+
+  return 1;
 
 err:
   OPENSSL_free(co_list);
@@ -1405,7 +1412,7 @@
     OPENSSL_free(pref_list->in_group_flags);
   }
   OPENSSL_free(pref_list);
-  return NULL;
+  return 0;
 }
 
 uint32_t SSL_CIPHER_get_id(const SSL_CIPHER *cipher) { return cipher->id; }
diff --git a/src/ssl/ssl_lib.c b/src/ssl/ssl_lib.c
index 7ead554..d16c952 100644
--- a/src/ssl/ssl_lib.c
+++ b/src/ssl/ssl_lib.c
@@ -271,11 +271,7 @@
     goto err;
   }
 
-  ssl_create_cipher_list(ret->method, &ret->cipher_list,
-                         SSL_DEFAULT_CIPHER_LIST, 1 /* strict */);
-  if (ret->cipher_list == NULL ||
-      sk_SSL_CIPHER_num(ret->cipher_list->ciphers) <= 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_LIBRARY_HAS_NO_CIPHERS);
+  if (!SSL_CTX_set_strict_cipher_list(ret, SSL_DEFAULT_CIPHER_LIST)) {
     goto err2;
   }
 
@@ -401,7 +397,7 @@
   SSL_CTX_up_ref(ctx);
   ssl->ctx = ctx;
   SSL_CTX_up_ref(ctx);
-  ssl->initial_ctx = ctx;
+  ssl->session_ctx = ctx;
 
   if (!ssl->ctx->x509_method->ssl_new(ssl)) {
     goto err;
@@ -484,7 +480,7 @@
   ssl_cert_free(ssl->cert);
 
   OPENSSL_free(ssl->tlsext_hostname);
-  SSL_CTX_free(ssl->initial_ctx);
+  SSL_CTX_free(ssl->session_ctx);
   OPENSSL_free(ssl->supported_group_list);
   OPENSSL_free(ssl->alpn_client_proto_list);
   EVP_PKEY_free(ssl->tlsext_channel_id_private);
@@ -914,6 +910,9 @@
 
     case SSL_PRIVATE_KEY_OPERATION:
       return SSL_ERROR_WANT_PRIVATE_KEY_OPERATION;
+
+    case SSL_PENDING_TICKET:
+      return SSL_ERROR_PENDING_TICKET;
   }
 
   return SSL_ERROR_SYSCALL;
@@ -1492,71 +1491,23 @@
 }
 
 int SSL_CTX_set_cipher_list(SSL_CTX *ctx, const char *str) {
-  STACK_OF(SSL_CIPHER) *cipher_list =
-      ssl_create_cipher_list(ctx->method, &ctx->cipher_list, str,
-                             0 /* not strict */);
-  if (cipher_list == NULL) {
-    return 0;
-  }
-
-  /* |ssl_create_cipher_list| may succeed but return an empty cipher list. */
-  if (sk_SSL_CIPHER_num(cipher_list) == 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CIPHER_MATCH);
-    return 0;
-  }
-
-  return 1;
+  return ssl_create_cipher_list(ctx->method, &ctx->cipher_list, str,
+                                0 /* not strict */);
 }
 
 int SSL_CTX_set_strict_cipher_list(SSL_CTX *ctx, const char *str) {
-  STACK_OF(SSL_CIPHER) *cipher_list =
-      ssl_create_cipher_list(ctx->method, &ctx->cipher_list, str,
-                             1 /* strict */);
-  if (cipher_list == NULL) {
-    return 0;
-  }
-
-  /* |ssl_create_cipher_list| may succeed but return an empty cipher list. */
-  if (sk_SSL_CIPHER_num(cipher_list) == 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CIPHER_MATCH);
-    return 0;
-  }
-
-  return 1;
+  return ssl_create_cipher_list(ctx->method, &ctx->cipher_list, str,
+                                1 /* strict */);
 }
 
 int SSL_set_cipher_list(SSL *ssl, const char *str) {
-  STACK_OF(SSL_CIPHER) *cipher_list =
-      ssl_create_cipher_list(ssl->ctx->method, &ssl->cipher_list, str,
-                             0 /* not strict */);
-  if (cipher_list == NULL) {
-    return 0;
-  }
-
-  /* |ssl_create_cipher_list| may succeed but return an empty cipher list. */
-  if (sk_SSL_CIPHER_num(cipher_list) == 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CIPHER_MATCH);
-    return 0;
-  }
-
-  return 1;
+  return ssl_create_cipher_list(ssl->ctx->method, &ssl->cipher_list, str,
+                                0 /* not strict */);
 }
 
 int SSL_set_strict_cipher_list(SSL *ssl, const char *str) {
-  STACK_OF(SSL_CIPHER) *cipher_list =
-      ssl_create_cipher_list(ssl->ctx->method, &ssl->cipher_list, str,
-                             1 /* strict */);
-  if (cipher_list == NULL) {
-    return 0;
-  }
-
-  /* |ssl_create_cipher_list| may succeed but return an empty cipher list. */
-  if (sk_SSL_CIPHER_num(cipher_list) == 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CIPHER_MATCH);
-    return 0;
-  }
-
-  return 1;
+  return ssl_create_cipher_list(ssl->ctx->method, &ssl->cipher_list, str,
+                                1 /* strict */);
 }
 
 const char *SSL_get_servername(const SSL *ssl, const int type) {
@@ -1598,6 +1549,10 @@
   ctx->signed_cert_timestamps_enabled = 1;
 }
 
+void SSL_CTX_i_promise_to_verify_certs_after_the_handshake(SSL_CTX *ctx) {
+  ctx->i_promise_to_verify_certs_after_the_handshake = 1;
+}
+
 void SSL_enable_signed_cert_timestamps(SSL *ssl) {
   ssl->signed_cert_timestamps_enabled = 1;
 }
@@ -1848,7 +1803,7 @@
 
 void ssl_update_cache(SSL_HANDSHAKE *hs, int mode) {
   SSL *const ssl = hs->ssl;
-  SSL_CTX *ctx = ssl->initial_ctx;
+  SSL_CTX *ctx = ssl->session_ctx;
   /* Never cache sessions with empty session IDs. */
   if (ssl->s3->established_session->session_id_length == 0 ||
       (ctx->session_cache_mode & mode) != mode) {
@@ -2033,7 +1988,7 @@
   }
 
   if (ctx == NULL) {
-    ctx = ssl->initial_ctx;
+    ctx = ssl->session_ctx;
   }
 
   ssl_cert_free(ssl->cert);
@@ -2553,6 +2508,10 @@
   ctx->grease_enabled = !!enabled;
 }
 
+int32_t SSL_get_ticket_age_skew(const SSL *ssl) {
+  return ssl->s3->ticket_age_skew;
+}
+
 int SSL_clear(SSL *ssl) {
   /* In OpenSSL, reusing a client |SSL| with |SSL_clear| causes the previously
    * established session to be offered the next time around. wpa_supplicant
@@ -2743,3 +2702,8 @@
 int SSL_set_max_version(SSL *ssl, uint16_t version) {
   return SSL_set_max_proto_version(ssl, version);
 }
+
+void SSL_CTX_set_ticket_aead_method(SSL_CTX *ctx,
+                                    const SSL_TICKET_AEAD_METHOD *aead_method) {
+  ctx->ticket_aead_method = aead_method;
+}
diff --git a/src/ssl/ssl_privkey.c b/src/ssl/ssl_privkey.c
index c5441de..e988827 100644
--- a/src/ssl/ssl_privkey.c
+++ b/src/ssl/ssl_privkey.c
@@ -64,8 +64,6 @@
 #include <openssl/evp.h>
 #include <openssl/mem.h>
 #include <openssl/type_check.h>
-#include <openssl/x509.h>
-#include <openssl/x509v3.h>
 
 #include "internal.h"
 
@@ -82,9 +80,7 @@
 
   if (cert->chain != NULL &&
       sk_CRYPTO_BUFFER_value(cert->chain, 0) != NULL &&
-      /* Sanity-check that the private key and the certificate match, unless
-       * the key is opaque (in case of, say, a smartcard). */
-      !EVP_PKEY_is_opaque(pkey) &&
+      /* Sanity-check that the private key and the certificate match. */
       !ssl_cert_check_private_key(cert, pkey)) {
     return 0;
   }
diff --git a/src/ssl/ssl_session.c b/src/ssl/ssl_session.c
index c9390d2..05ae059 100644
--- a/src/ssl/ssl_session.c
+++ b/src/ssl/ssl_session.c
@@ -535,13 +535,13 @@
   if (version >= TLS1_3_VERSION) {
     /* TLS 1.3 uses tickets as authenticators, so we are willing to use them for
      * longer. */
-    session->timeout = ssl->initial_ctx->session_psk_dhe_timeout;
+    session->timeout = ssl->session_ctx->session_psk_dhe_timeout;
     session->auth_timeout = SSL_DEFAULT_SESSION_AUTH_TIMEOUT;
   } else {
     /* TLS 1.2 resumption does not incorporate new key material, so we use a
      * much shorter timeout. */
-    session->timeout = ssl->initial_ctx->session_timeout;
-    session->auth_timeout = ssl->initial_ctx->session_timeout;
+    session->timeout = ssl->session_ctx->session_timeout;
+    session->auth_timeout = ssl->session_ctx->session_timeout;
   }
 
   if (is_server) {
@@ -581,16 +581,11 @@
   return 0;
 }
 
-int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session) {
+static int ssl_encrypt_ticket_with_cipher_ctx(SSL *ssl, CBB *out,
+                                              const uint8_t *session_buf,
+                                              size_t session_len) {
   int ret = 0;
 
-  /* Serialize the SSL_SESSION to be encoded into the ticket. */
-  uint8_t *session_buf = NULL;
-  size_t session_len;
-  if (!SSL_SESSION_to_bytes_for_ticket(session, &session_buf, &session_len)) {
-    return -1;
-  }
-
   EVP_CIPHER_CTX ctx;
   EVP_CIPHER_CTX_init(&ctx);
   HMAC_CTX hctx;
@@ -611,7 +606,7 @@
 
   /* Initialize HMAC and cipher contexts. If callback present it does all the
    * work otherwise use generated values from parent ctx. */
-  SSL_CTX *tctx = ssl->initial_ctx;
+  SSL_CTX *tctx = ssl->session_ctx;
   uint8_t iv[EVP_MAX_IV_LENGTH];
   uint8_t key_name[16];
   if (tctx->tlsext_ticket_key_cb != NULL) {
@@ -667,12 +662,60 @@
   ret = 1;
 
 err:
-  OPENSSL_free(session_buf);
   EVP_CIPHER_CTX_cleanup(&ctx);
   HMAC_CTX_cleanup(&hctx);
   return ret;
 }
 
+static int ssl_encrypt_ticket_with_method(SSL *ssl, CBB *out,
+                                          const uint8_t *session_buf,
+                                          size_t session_len) {
+  const SSL_TICKET_AEAD_METHOD *method = ssl->session_ctx->ticket_aead_method;
+  const size_t max_overhead = method->max_overhead(ssl);
+  const size_t max_out = session_len + max_overhead;
+  if (max_out < max_overhead) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_OVERFLOW);
+    return 0;
+  }
+
+  uint8_t *ptr;
+  if (!CBB_reserve(out, &ptr, max_out)) {
+    return 0;
+  }
+
+  size_t out_len;
+  if (!method->seal(ssl, ptr, &out_len, max_out, session_buf, session_len)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_TICKET_ENCRYPTION_FAILED);
+    return 0;
+  }
+
+  if (!CBB_did_write(out, out_len)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session) {
+  /* Serialize the SSL_SESSION to be encoded into the ticket. */
+  uint8_t *session_buf = NULL;
+  size_t session_len;
+  if (!SSL_SESSION_to_bytes_for_ticket(session, &session_buf, &session_len)) {
+    return -1;
+  }
+
+  int ret = 0;
+  if (ssl->session_ctx->ticket_aead_method) {
+    ret = ssl_encrypt_ticket_with_method(ssl, out, session_buf, session_len);
+  } else {
+    ret =
+        ssl_encrypt_ticket_with_cipher_ctx(ssl, out, session_buf, session_len);
+  }
+
+  OPENSSL_free(session_buf);
+  return ret;
+}
+
 int ssl_session_is_context_valid(const SSL *ssl, const SSL_SESSION *session) {
   if (session == NULL) {
     return 0;
@@ -736,27 +779,27 @@
 
   SSL_SESSION *session = NULL;
   /* Try the internal cache, if it exists. */
-  if (!(ssl->initial_ctx->session_cache_mode &
+  if (!(ssl->session_ctx->session_cache_mode &
         SSL_SESS_CACHE_NO_INTERNAL_LOOKUP)) {
     SSL_SESSION data;
     data.ssl_version = ssl->version;
     data.session_id_length = session_id_len;
     OPENSSL_memcpy(data.session_id, session_id, session_id_len);
 
-    CRYPTO_MUTEX_lock_read(&ssl->initial_ctx->lock);
-    session = lh_SSL_SESSION_retrieve(ssl->initial_ctx->sessions, &data);
+    CRYPTO_MUTEX_lock_read(&ssl->session_ctx->lock);
+    session = lh_SSL_SESSION_retrieve(ssl->session_ctx->sessions, &data);
     if (session != NULL) {
       SSL_SESSION_up_ref(session);
     }
     /* TODO(davidben): This should probably move it to the front of the list. */
-    CRYPTO_MUTEX_unlock_read(&ssl->initial_ctx->lock);
+    CRYPTO_MUTEX_unlock_read(&ssl->session_ctx->lock);
   }
 
   /* Fall back to the external cache, if it exists. */
   if (session == NULL &&
-      ssl->initial_ctx->get_session_cb != NULL) {
+      ssl->session_ctx->get_session_cb != NULL) {
     int copy = 1;
-    session = ssl->initial_ctx->get_session_cb(ssl, (uint8_t *)session_id,
+    session = ssl->session_ctx->get_session_cb(ssl, (uint8_t *)session_id,
                                                session_id_len, &copy);
 
     if (session == NULL) {
@@ -776,16 +819,16 @@
     }
 
     /* Add the externally cached session to the internal cache if necessary. */
-    if (!(ssl->initial_ctx->session_cache_mode &
+    if (!(ssl->session_ctx->session_cache_mode &
           SSL_SESS_CACHE_NO_INTERNAL_STORE)) {
-      SSL_CTX_add_session(ssl->initial_ctx, session);
+      SSL_CTX_add_session(ssl->session_ctx, session);
     }
   }
 
   if (session != NULL &&
       !ssl_session_is_time_valid(ssl, session)) {
     /* The session was from the cache, so remove it. */
-    SSL_CTX_remove_session(ssl->initial_ctx, session);
+    SSL_CTX_remove_session(ssl->session_ctx, session);
     SSL_SESSION_free(session);
     session = NULL;
   }
@@ -811,10 +854,18 @@
       SSL_early_callback_ctx_extension_get(
           client_hello, TLSEXT_TYPE_session_ticket, &ticket, &ticket_len);
   if (tickets_supported && ticket_len > 0) {
-    if (!tls_process_ticket(ssl, &session, &renew_ticket, ticket, ticket_len,
-                            client_hello->session_id,
-                            client_hello->session_id_len)) {
-      return ssl_session_error;
+    switch (ssl_process_ticket(ssl, &session, &renew_ticket, ticket, ticket_len,
+                               client_hello->session_id,
+                               client_hello->session_id_len)) {
+      case ssl_ticket_aead_success:
+        break;
+      case ssl_ticket_aead_ignore_ticket:
+        assert(session == NULL);
+        break;
+      case ssl_ticket_aead_error:
+        return ssl_session_error;
+      case ssl_ticket_aead_retry:
+        return ssl_session_ticket_retry;
     }
   } else {
     /* The client didn't send a ticket, so the session ID is a real ID. */
diff --git a/src/ssl/ssl_test.cc b/src/ssl/ssl_test.cc
index 4180463..6b150e8 100644
--- a/src/ssl/ssl_test.cc
+++ b/src/ssl/ssl_test.cc
@@ -1229,7 +1229,25 @@
       PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr));
 }
 
-static bssl::UniquePtr<X509> GetChainTestCertificate() {
+static bssl::UniquePtr<CRYPTO_BUFFER> BufferFromPEM(const char *pem) {
+  bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(pem, strlen(pem)));
+  char *name, *header;
+  uint8_t *data;
+  long data_len;
+  if (!PEM_read_bio(bio.get(), &name, &header, &data,
+                    &data_len)) {
+    return nullptr;
+  }
+  OPENSSL_free(name);
+  OPENSSL_free(header);
+
+  auto ret = bssl::UniquePtr<CRYPTO_BUFFER>(
+      CRYPTO_BUFFER_new(data, data_len, nullptr));
+  OPENSSL_free(data);
+  return ret;
+}
+
+static bssl::UniquePtr<CRYPTO_BUFFER> GetChainTestCertificateBuffer() {
   static const char kCertPEM[] =
       "-----BEGIN CERTIFICATE-----\n"
       "MIIC0jCCAbqgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwDzENMAsGA1UEAwwEQiBD\n"
@@ -1249,12 +1267,24 @@
       "MYgF91UDvVzvnYm6TfseM2+ewKirC00GOrZ7rEcFvtxnKSqYf4ckqfNdSU1Y+RRC\n"
       "1ngWZ7Ih\n"
       "-----END CERTIFICATE-----\n";
-  bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(kCertPEM, strlen(kCertPEM)));
-  return bssl::UniquePtr<X509>(
-      PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+  return BufferFromPEM(kCertPEM);
 }
 
-static bssl::UniquePtr<X509> GetChainTestIntermediate() {
+static bssl::UniquePtr<X509> X509FromBuffer(
+    bssl::UniquePtr<CRYPTO_BUFFER> buffer) {
+  if (!buffer) {
+    return nullptr;
+  }
+  const uint8_t *derp = CRYPTO_BUFFER_data(buffer.get());
+  return bssl::UniquePtr<X509>(
+      d2i_X509(NULL, &derp, CRYPTO_BUFFER_len(buffer.get())));
+}
+
+static bssl::UniquePtr<X509> GetChainTestCertificate() {
+  return X509FromBuffer(GetChainTestCertificateBuffer());
+}
+
+static bssl::UniquePtr<CRYPTO_BUFFER> GetChainTestIntermediateBuffer() {
   static const char kCertPEM[] =
       "-----BEGIN CERTIFICATE-----\n"
       "MIICwjCCAaqgAwIBAgICEAEwDQYJKoZIhvcNAQELBQAwFDESMBAGA1UEAwwJQyBS\n"
@@ -1273,9 +1303,11 @@
       "WhWwgM3P3X95fQ3d7oFPR/bVh0YV+Cf861INwplokXgXQ3/TCQ+HNXeAMWn3JLWv\n"
       "XFwk8owk9dq/kQGdndGgy3KTEW4ctPX5GNhf3LJ9Q7dLji4ReQ4=\n"
       "-----END CERTIFICATE-----\n";
-  bssl::UniquePtr<BIO> bio(BIO_new_mem_buf(kCertPEM, strlen(kCertPEM)));
-  return bssl::UniquePtr<X509>(
-      PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr));
+  return BufferFromPEM(kCertPEM);
+}
+
+static bssl::UniquePtr<X509> GetChainTestIntermediate() {
+  return X509FromBuffer(GetChainTestIntermediateBuffer());
 }
 
 static bssl::UniquePtr<EVP_PKEY> GetChainTestKey() {
@@ -1320,7 +1352,8 @@
     int client_err = SSL_get_error(client, client_ret);
     if (client_err != SSL_ERROR_NONE &&
         client_err != SSL_ERROR_WANT_READ &&
-        client_err != SSL_ERROR_WANT_WRITE) {
+        client_err != SSL_ERROR_WANT_WRITE &&
+        client_err != SSL_ERROR_PENDING_TICKET) {
       fprintf(stderr, "Client error: %d\n", client_err);
       return false;
     }
@@ -1329,7 +1362,8 @@
     int server_err = SSL_get_error(server, server_ret);
     if (server_err != SSL_ERROR_NONE &&
         server_err != SSL_ERROR_WANT_READ &&
-        server_err != SSL_ERROR_WANT_WRITE) {
+        server_err != SSL_ERROR_WANT_WRITE &&
+        server_err != SSL_ERROR_PENDING_TICKET) {
       fprintf(stderr, "Server error: %d\n", server_err);
       return false;
     }
@@ -3152,6 +3186,304 @@
   EXPECT_EQ(Bytes(der, der_len), Bytes(der3, der3_len));
 }
 
+TEST(SSLTest, SetChainAndKeyMismatch) {
+  bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_with_buffers_method()));
+  ASSERT_TRUE(ctx);
+
+  bssl::UniquePtr<EVP_PKEY> key = GetTestKey();
+  ASSERT_TRUE(key);
+  bssl::UniquePtr<CRYPTO_BUFFER> leaf = GetChainTestCertificateBuffer();
+  ASSERT_TRUE(leaf);
+  std::vector<CRYPTO_BUFFER*> chain = {
+      leaf.get(),
+  };
+
+  // Should fail because |GetTestKey| doesn't match the chain-test certificate.
+  ASSERT_FALSE(SSL_CTX_set_chain_and_key(ctx.get(), &chain[0], chain.size(),
+                                         key.get(), nullptr));
+  ERR_clear_error();
+}
+
+TEST(SSLTest, SetChainAndKey) {
+  bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_with_buffers_method()));
+  ASSERT_TRUE(client_ctx);
+  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_with_buffers_method()));
+  ASSERT_TRUE(server_ctx);
+
+  bssl::UniquePtr<EVP_PKEY> key = GetChainTestKey();
+  ASSERT_TRUE(key);
+  bssl::UniquePtr<CRYPTO_BUFFER> leaf = GetChainTestCertificateBuffer();
+  ASSERT_TRUE(leaf);
+  bssl::UniquePtr<CRYPTO_BUFFER> intermediate =
+      GetChainTestIntermediateBuffer();
+  ASSERT_TRUE(intermediate);
+  std::vector<CRYPTO_BUFFER*> chain = {
+      leaf.get(), intermediate.get(),
+  };
+  ASSERT_TRUE(SSL_CTX_set_chain_and_key(server_ctx.get(), &chain[0],
+                                        chain.size(), key.get(), nullptr));
+
+  SSL_CTX_i_promise_to_verify_certs_after_the_handshake(client_ctx.get());
+
+  bssl::UniquePtr<SSL> client, server;
+  ASSERT_TRUE(ConnectClientAndServer(&client, &server, client_ctx.get(),
+                                     server_ctx.get(),
+                                     nullptr /* no session */));
+}
+
+// Configuring the empty cipher list, though an error, should still modify the
+// configuration.
+TEST(SSLTest, EmptyCipherList) {
+  bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(TLS_method()));
+  ASSERT_TRUE(ctx);
+
+  // Initially, the cipher list is not empty.
+  EXPECT_NE(0u, sk_SSL_CIPHER_num(SSL_CTX_get_ciphers(ctx.get())));
+
+  // Configuring the empty cipher list fails.
+  EXPECT_FALSE(SSL_CTX_set_cipher_list(ctx.get(), ""));
+  ERR_clear_error();
+
+  // But the cipher list is still updated to empty.
+  EXPECT_EQ(0u, sk_SSL_CIPHER_num(SSL_CTX_get_ciphers(ctx.get())));
+}
+
+// ssl_test_ticket_aead_failure_mode enumerates the possible ways in which the
+// test |SSL_TICKET_AEAD_METHOD| can fail.
+enum ssl_test_ticket_aead_failure_mode {
+  ssl_test_ticket_aead_ok = 0,
+  ssl_test_ticket_aead_seal_fail,
+  ssl_test_ticket_aead_open_soft_fail,
+  ssl_test_ticket_aead_open_hard_fail,
+};
+
+struct ssl_test_ticket_aead_state {
+  unsigned retry_count;
+  ssl_test_ticket_aead_failure_mode failure_mode;
+};
+
+static int ssl_test_ticket_aead_ex_index_dup(CRYPTO_EX_DATA *to,
+                                             const CRYPTO_EX_DATA *from,
+                                             void **from_d, int index,
+                                             long argl, void *argp) {
+  abort();
+}
+
+static void ssl_test_ticket_aead_ex_index_free(void *parent, void *ptr,
+                                               CRYPTO_EX_DATA *ad, int index,
+                                               long argl, void *argp) {
+  auto state = reinterpret_cast<ssl_test_ticket_aead_state*>(ptr);
+  if (state == nullptr) {
+    return;
+  }
+
+  OPENSSL_free(state);
+}
+
+static CRYPTO_once_t g_ssl_test_ticket_aead_ex_index_once = CRYPTO_ONCE_INIT;
+static int g_ssl_test_ticket_aead_ex_index;
+
+static int ssl_test_ticket_aead_get_ex_index() {
+  CRYPTO_once(&g_ssl_test_ticket_aead_ex_index_once, [] {
+    g_ssl_test_ticket_aead_ex_index = SSL_get_ex_new_index(
+        0, nullptr, nullptr, ssl_test_ticket_aead_ex_index_dup,
+        ssl_test_ticket_aead_ex_index_free);
+  });
+  return g_ssl_test_ticket_aead_ex_index;
+}
+
+static size_t ssl_test_ticket_aead_max_overhead(SSL *ssl) {
+  return 1;
+}
+
+static int ssl_test_ticket_aead_seal(SSL *ssl, uint8_t *out, size_t *out_len,
+                                     size_t max_out_len, const uint8_t *in,
+                                     size_t in_len) {
+  auto state = reinterpret_cast<ssl_test_ticket_aead_state *>(
+      SSL_get_ex_data(ssl, ssl_test_ticket_aead_get_ex_index()));
+
+  if (state->failure_mode == ssl_test_ticket_aead_seal_fail ||
+      max_out_len < in_len + 1) {
+    return 0;
+  }
+
+  OPENSSL_memmove(out, in, in_len);
+  out[in_len] = 0xff;
+  *out_len = in_len + 1;
+
+  return 1;
+}
+
+static ssl_ticket_aead_result_t ssl_test_ticket_aead_open(
+    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out_len,
+    const uint8_t *in, size_t in_len) {
+  auto state = reinterpret_cast<ssl_test_ticket_aead_state *>(
+      SSL_get_ex_data(ssl, ssl_test_ticket_aead_get_ex_index()));
+
+  if (state->retry_count > 0) {
+    state->retry_count--;
+    return ssl_ticket_aead_retry;
+  }
+
+  switch (state->failure_mode) {
+    case ssl_test_ticket_aead_ok:
+      break;
+    case ssl_test_ticket_aead_seal_fail:
+      // If |seal| failed then there shouldn't be any ticket to try and
+      // decrypt.
+      abort();
+      break;
+    case ssl_test_ticket_aead_open_soft_fail:
+      return ssl_ticket_aead_ignore_ticket;
+    case ssl_test_ticket_aead_open_hard_fail:
+      return ssl_ticket_aead_error;
+  }
+
+  if (in_len == 0 || in[in_len - 1] != 0xff) {
+    return ssl_ticket_aead_ignore_ticket;
+  }
+
+  if (max_out_len < in_len - 1) {
+    return ssl_ticket_aead_error;
+  }
+
+  OPENSSL_memmove(out, in, in_len - 1);
+  *out_len = in_len - 1;
+  return ssl_ticket_aead_success;
+}
+
+static const SSL_TICKET_AEAD_METHOD kSSLTestTicketMethod = {
+  ssl_test_ticket_aead_max_overhead,
+  ssl_test_ticket_aead_seal,
+  ssl_test_ticket_aead_open,
+};
+
+static void ConnectClientAndServerWithTicketMethod(
+    bssl::UniquePtr<SSL> *out_client, bssl::UniquePtr<SSL> *out_server,
+    SSL_CTX *client_ctx, SSL_CTX *server_ctx, unsigned retry_count,
+    ssl_test_ticket_aead_failure_mode failure_mode, SSL_SESSION *session) {
+  bssl::UniquePtr<SSL> client(SSL_new(client_ctx)), server(SSL_new(server_ctx));
+  ASSERT_TRUE(client);
+  ASSERT_TRUE(server);
+  SSL_set_connect_state(client.get());
+  SSL_set_accept_state(server.get());
+
+  auto state = reinterpret_cast<ssl_test_ticket_aead_state *>(
+      OPENSSL_malloc(sizeof(ssl_test_ticket_aead_state)));
+  ASSERT_TRUE(state);
+  OPENSSL_memset(state, 0, sizeof(ssl_test_ticket_aead_state));
+  state->retry_count = retry_count;
+  state->failure_mode = failure_mode;
+
+  ASSERT_TRUE(SSL_set_ex_data(server.get(), ssl_test_ticket_aead_get_ex_index(),
+                              state));
+
+  SSL_set_session(client.get(), session);
+
+  BIO *bio1, *bio2;
+  ASSERT_TRUE(BIO_new_bio_pair(&bio1, 0, &bio2, 0));
+
+  // SSL_set_bio takes ownership.
+  SSL_set_bio(client.get(), bio1, bio1);
+  SSL_set_bio(server.get(), bio2, bio2);
+
+  if (CompleteHandshakes(client.get(), server.get())) {
+    *out_client = std::move(client);
+    *out_server = std::move(server);
+  } else {
+    out_client->reset();
+    out_server->reset();
+  }
+}
+
+class TicketAEADMethodTest
+    : public ::testing::TestWithParam<testing::tuple<
+          uint16_t, unsigned, ssl_test_ticket_aead_failure_mode>> {};
+
+TEST_P(TicketAEADMethodTest, Resume) {
+  bssl::UniquePtr<X509> cert = GetTestCertificate();
+  ASSERT_TRUE(cert);
+  bssl::UniquePtr<EVP_PKEY> key = GetTestKey();
+  ASSERT_TRUE(key);
+
+  bssl::UniquePtr<SSL_CTX> server_ctx(SSL_CTX_new(TLS_method()));
+  ASSERT_TRUE(server_ctx);
+  bssl::UniquePtr<SSL_CTX> client_ctx(SSL_CTX_new(TLS_method()));
+  ASSERT_TRUE(client_ctx);
+
+  const uint16_t version = testing::get<0>(GetParam());
+  const unsigned retry_count = testing::get<1>(GetParam());
+  const ssl_test_ticket_aead_failure_mode failure_mode =
+      testing::get<2>(GetParam());
+
+  ASSERT_TRUE(SSL_CTX_use_certificate(server_ctx.get(), cert.get()));
+  ASSERT_TRUE(SSL_CTX_use_PrivateKey(server_ctx.get(), key.get()));
+  ASSERT_TRUE(SSL_CTX_set_min_proto_version(client_ctx.get(), version));
+  ASSERT_TRUE(SSL_CTX_set_max_proto_version(client_ctx.get(), version));
+  ASSERT_TRUE(SSL_CTX_set_min_proto_version(server_ctx.get(), version));
+  ASSERT_TRUE(SSL_CTX_set_max_proto_version(server_ctx.get(), version));
+
+  SSL_CTX_set_session_cache_mode(client_ctx.get(), SSL_SESS_CACHE_BOTH);
+  SSL_CTX_set_session_cache_mode(server_ctx.get(), SSL_SESS_CACHE_BOTH);
+  SSL_CTX_set_current_time_cb(client_ctx.get(), FrozenTimeCallback);
+  SSL_CTX_set_current_time_cb(server_ctx.get(), FrozenTimeCallback);
+  SSL_CTX_sess_set_new_cb(client_ctx.get(), SaveLastSession);
+
+  SSL_CTX_set_ticket_aead_method(server_ctx.get(), &kSSLTestTicketMethod);
+
+  bssl::UniquePtr<SSL> client, server;
+  ConnectClientAndServerWithTicketMethod(&client, &server, client_ctx.get(),
+                                         server_ctx.get(), retry_count,
+                                         failure_mode, nullptr);
+  switch (failure_mode) {
+    case ssl_test_ticket_aead_ok:
+    case ssl_test_ticket_aead_open_hard_fail:
+    case ssl_test_ticket_aead_open_soft_fail:
+      ASSERT_TRUE(client);
+      break;
+    case ssl_test_ticket_aead_seal_fail:
+      EXPECT_FALSE(client);
+      return;
+  }
+  EXPECT_FALSE(SSL_session_reused(client.get()));
+  EXPECT_FALSE(SSL_session_reused(server.get()));
+
+  // Run the read loop to account for post-handshake tickets in TLS 1.3.
+  SSL_read(client.get(), nullptr, 0);
+
+  bssl::UniquePtr<SSL_SESSION> session = std::move(g_last_session);
+  ConnectClientAndServerWithTicketMethod(&client, &server, client_ctx.get(),
+                                         server_ctx.get(), retry_count,
+                                         failure_mode, session.get());
+  switch (failure_mode) {
+    case ssl_test_ticket_aead_ok:
+      ASSERT_TRUE(client);
+      EXPECT_TRUE(SSL_session_reused(client.get()));
+      EXPECT_TRUE(SSL_session_reused(server.get()));
+      break;
+    case ssl_test_ticket_aead_seal_fail:
+      abort();
+      break;
+    case ssl_test_ticket_aead_open_hard_fail:
+      EXPECT_FALSE(client);
+      break;
+    case ssl_test_ticket_aead_open_soft_fail:
+      ASSERT_TRUE(client);
+      EXPECT_FALSE(SSL_session_reused(client.get()));
+      EXPECT_FALSE(SSL_session_reused(server.get()));
+  }
+}
+
+INSTANTIATE_TEST_CASE_P(
+    TicketAEADMethodTests, TicketAEADMethodTest,
+    testing::Combine(
+        testing::Values(TLS1_2_VERSION, TLS1_3_VERSION),
+        testing::Values(0, 1, 2),
+        testing::Values(ssl_test_ticket_aead_ok,
+                        ssl_test_ticket_aead_seal_fail,
+                        ssl_test_ticket_aead_open_soft_fail,
+                        ssl_test_ticket_aead_open_hard_fail)));
+
 // TODO(davidben): Convert this file to GTest properly.
 TEST(SSLTest, AllTests) {
   if (!TestCipherRules() ||
diff --git a/src/ssl/ssl_x509.c b/src/ssl/ssl_x509.c
index 9a396d0..65405aa 100644
--- a/src/ssl/ssl_x509.c
+++ b/src/ssl/ssl_x509.c
@@ -565,6 +565,66 @@
   session->x509_chain_without_leaf = NULL;
 }
 
+static int ssl_verify_alarm_type(long type) {
+  switch (type) {
+    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT:
+    case X509_V_ERR_UNABLE_TO_GET_CRL:
+    case X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER:
+      return SSL_AD_UNKNOWN_CA;
+
+    case X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE:
+    case X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE:
+    case X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY:
+    case X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD:
+    case X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD:
+    case X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD:
+    case X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD:
+    case X509_V_ERR_CERT_NOT_YET_VALID:
+    case X509_V_ERR_CRL_NOT_YET_VALID:
+    case X509_V_ERR_CERT_UNTRUSTED:
+    case X509_V_ERR_CERT_REJECTED:
+    case X509_V_ERR_HOSTNAME_MISMATCH:
+    case X509_V_ERR_EMAIL_MISMATCH:
+    case X509_V_ERR_IP_ADDRESS_MISMATCH:
+      return SSL_AD_BAD_CERTIFICATE;
+
+    case X509_V_ERR_CERT_SIGNATURE_FAILURE:
+    case X509_V_ERR_CRL_SIGNATURE_FAILURE:
+      return SSL_AD_DECRYPT_ERROR;
+
+    case X509_V_ERR_CERT_HAS_EXPIRED:
+    case X509_V_ERR_CRL_HAS_EXPIRED:
+      return SSL_AD_CERTIFICATE_EXPIRED;
+
+    case X509_V_ERR_CERT_REVOKED:
+      return SSL_AD_CERTIFICATE_REVOKED;
+
+    case X509_V_ERR_UNSPECIFIED:
+    case X509_V_ERR_OUT_OF_MEM:
+    case X509_V_ERR_INVALID_CALL:
+    case X509_V_ERR_STORE_LOOKUP:
+      return SSL_AD_INTERNAL_ERROR;
+
+    case X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT:
+    case X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN:
+    case X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY:
+    case X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE:
+    case X509_V_ERR_CERT_CHAIN_TOO_LONG:
+    case X509_V_ERR_PATH_LENGTH_EXCEEDED:
+    case X509_V_ERR_INVALID_CA:
+      return SSL_AD_UNKNOWN_CA;
+
+    case X509_V_ERR_APPLICATION_VERIFICATION:
+      return SSL_AD_HANDSHAKE_FAILURE;
+
+    case X509_V_ERR_INVALID_PURPOSE:
+      return SSL_AD_UNSUPPORTED_CERTIFICATE;
+
+    default:
+      return SSL_AD_CERTIFICATE_UNKNOWN;
+  }
+}
+
 static int ssl_crypto_x509_session_verify_cert_chain(SSL_SESSION *session,
                                                      SSL *ssl) {
   STACK_OF(X509) *const cert_chain = session->x509_chain;
diff --git a/src/ssl/t1_lib.c b/src/ssl/t1_lib.c
index adc7344..759d87b 100644
--- a/src/ssl/t1_lib.c
+++ b/src/ssl/t1_lib.c
@@ -1957,18 +1957,14 @@
   return 1;
 }
 
-int ssl_ext_pre_shared_key_parse_clienthello(SSL_HANDSHAKE *hs,
-                                             SSL_SESSION **out_session,
-                                             CBS *out_binders,
-                                             uint8_t *out_alert,
-                                             CBS *contents) {
-  SSL *const ssl = hs->ssl;
+int ssl_ext_pre_shared_key_parse_clienthello(
+    SSL_HANDSHAKE *hs, CBS *out_ticket, CBS *out_binders,
+    uint32_t *out_obfuscated_ticket_age, uint8_t *out_alert, CBS *contents) {
   /* We only process the first PSK identity since we don't support pure PSK. */
-  uint32_t obfuscated_ticket_age;
-  CBS identities, ticket, binders;
+  CBS identities, binders;
   if (!CBS_get_u16_length_prefixed(contents, &identities) ||
-      !CBS_get_u16_length_prefixed(&identities, &ticket) ||
-      !CBS_get_u32(&identities, &obfuscated_ticket_age) ||
+      !CBS_get_u16_length_prefixed(&identities, out_ticket) ||
+      !CBS_get_u32(&identities, out_obfuscated_ticket_age) ||
       !CBS_get_u16_length_prefixed(contents, &binders) ||
       CBS_len(&binders) == 0 ||
       CBS_len(contents) != 0) {
@@ -2014,18 +2010,6 @@
     return 0;
   }
 
-  /* TODO(svaldez): Check that the ticket_age is valid when attempting to use
-   * the PSK for 0-RTT. http://crbug.com/boringssl/113 */
-
-  /* TLS 1.3 session tickets are renewed separately as part of the
-   * NewSessionTicket. */
-  int unused_renew;
-  if (!tls_process_ticket(ssl, out_session, &unused_renew, CBS_data(&ticket),
-                          CBS_len(&ticket), NULL, 0)) {
-    *out_alert = SSL_AD_INTERNAL_ERROR;
-    return 0;
-  }
-
   return 1;
 }
 
@@ -3016,9 +3000,9 @@
   if (ssl->ctx->tlsext_servername_callback != 0) {
     ret = ssl->ctx->tlsext_servername_callback(ssl, &al,
                                                ssl->ctx->tlsext_servername_arg);
-  } else if (ssl->initial_ctx->tlsext_servername_callback != 0) {
-    ret = ssl->initial_ctx->tlsext_servername_callback(
-        ssl, &al, ssl->initial_ctx->tlsext_servername_arg);
+  } else if (ssl->session_ctx->tlsext_servername_callback != 0) {
+    ret = ssl->session_ctx->tlsext_servername_callback(
+        ssl, &al, ssl->session_ctx->tlsext_servername_arg);
   }
 
   switch (ret) {
@@ -3046,12 +3030,12 @@
   return 1;
 }
 
-int tls_process_ticket(SSL *ssl, SSL_SESSION **out_session,
-                       int *out_renew_ticket, const uint8_t *ticket,
-                       size_t ticket_len, const uint8_t *session_id,
-                       size_t session_id_len) {
-  int ret = 1; /* Most errors are non-fatal. */
-  SSL_CTX *ssl_ctx = ssl->initial_ctx;
+static enum ssl_ticket_aead_result_t
+ssl_decrypt_ticket_with_cipher_ctx(SSL *ssl, uint8_t **out, size_t *out_len,
+                                   int *out_renew_ticket, const uint8_t *ticket,
+                                   size_t ticket_len) {
+  enum ssl_ticket_aead_result_t ret = ssl_ticket_aead_ignore_ticket;
+  const SSL_CTX *const ssl_ctx = ssl->session_ctx;
   uint8_t *plaintext = NULL;
 
   HMAC_CTX hmac_ctx;
@@ -3059,23 +3043,12 @@
   EVP_CIPHER_CTX cipher_ctx;
   EVP_CIPHER_CTX_init(&cipher_ctx);
 
-  *out_renew_ticket = 0;
-  *out_session = NULL;
-
-  if (SSL_get_options(ssl) & SSL_OP_NO_TICKET) {
-    goto done;
-  }
-
-  if (session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
-    goto done;
-  }
-
   /* Ensure there is room for the key name and the largest IV
    * |tlsext_ticket_key_cb| may try to consume. The real limit may be lower, but
    * the maximum IV length should be well under the minimum size for the
    * session material and HMAC. */
   if (ticket_len < SSL_TICKET_KEY_NAME_LEN + EVP_MAX_IV_LENGTH) {
-    goto done;
+    goto out;
   }
   const uint8_t *iv = ticket + SSL_TICKET_KEY_NAME_LEN;
 
@@ -3084,28 +3057,26 @@
         ssl, (uint8_t *)ticket /* name */, (uint8_t *)iv, &cipher_ctx,
         &hmac_ctx, 0 /* decrypt */);
     if (cb_ret < 0) {
-      ret = 0;
-      goto done;
-    }
-    if (cb_ret == 0) {
-      goto done;
-    }
-    if (cb_ret == 2) {
+      ret = ssl_ticket_aead_error;
+      goto out;
+    } else if (cb_ret == 0) {
+      goto out;
+    } else if (cb_ret == 2) {
       *out_renew_ticket = 1;
     }
   } else {
     /* Check the key name matches. */
     if (OPENSSL_memcmp(ticket, ssl_ctx->tlsext_tick_key_name,
                        SSL_TICKET_KEY_NAME_LEN) != 0) {
-      goto done;
+      goto out;
     }
     if (!HMAC_Init_ex(&hmac_ctx, ssl_ctx->tlsext_tick_hmac_key,
                       sizeof(ssl_ctx->tlsext_tick_hmac_key), tlsext_tick_md(),
                       NULL) ||
         !EVP_DecryptInit_ex(&cipher_ctx, EVP_aes_128_cbc(), NULL,
                             ssl_ctx->tlsext_tick_aes_key, iv)) {
-      ret = 0;
-      goto done;
+      ret = ssl_ticket_aead_error;
+      goto out;
     }
   }
   size_t iv_len = EVP_CIPHER_CTX_iv_length(&cipher_ctx);
@@ -3115,7 +3086,7 @@
   size_t mac_len = HMAC_size(&hmac_ctx);
   if (ticket_len < SSL_TICKET_KEY_NAME_LEN + iv_len + 1 + mac_len) {
     /* The ticket must be large enough for key name, IV, data, and MAC. */
-    goto done;
+    goto out;
   }
   HMAC_Update(&hmac_ctx, ticket, ticket_len - mac_len);
   HMAC_Final(&hmac_ctx, mac, NULL);
@@ -3125,7 +3096,7 @@
   mac_ok = 1;
 #endif
   if (!mac_ok) {
-    goto done;
+    goto out;
   }
 
   /* Decrypt the session data. */
@@ -3134,8 +3105,8 @@
                           mac_len;
   plaintext = OPENSSL_malloc(ciphertext_len);
   if (plaintext == NULL) {
-    ret = 0;
-    goto done;
+    ret = ssl_ticket_aead_error;
+    goto out;
   }
   size_t plaintext_len;
 #if defined(BORINGSSL_UNSAFE_FUZZER_MODE)
@@ -3143,24 +3114,89 @@
   plaintext_len = ciphertext_len;
 #else
   if (ciphertext_len >= INT_MAX) {
-    goto done;
+    goto out;
   }
   int len1, len2;
   if (!EVP_DecryptUpdate(&cipher_ctx, plaintext, &len1, ciphertext,
                          (int)ciphertext_len) ||
       !EVP_DecryptFinal_ex(&cipher_ctx, plaintext + len1, &len2)) {
-    ERR_clear_error(); /* Don't leave an error on the queue. */
-    goto done;
+    ERR_clear_error();
+    goto out;
   }
-  plaintext_len = (size_t)(len1 + len2);
+  plaintext_len = (size_t)(len1) + len2;
 #endif
 
+  *out = plaintext;
+  plaintext = NULL;
+  *out_len = plaintext_len;
+  ret = ssl_ticket_aead_success;
+
+out:
+  OPENSSL_free(plaintext);
+  HMAC_CTX_cleanup(&hmac_ctx);
+  EVP_CIPHER_CTX_cleanup(&cipher_ctx);
+  return ret;
+}
+
+static enum ssl_ticket_aead_result_t ssl_decrypt_ticket_with_method(
+    SSL *ssl, uint8_t **out, size_t *out_len, int *out_renew_ticket,
+    const uint8_t *ticket, size_t ticket_len) {
+  uint8_t *plaintext = OPENSSL_malloc(ticket_len);
+  if (plaintext == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return ssl_ticket_aead_error;
+  }
+
+  size_t plaintext_len;
+  const enum ssl_ticket_aead_result_t result =
+      ssl->session_ctx->ticket_aead_method->open(
+          ssl, plaintext, &plaintext_len, ticket_len, ticket, ticket_len);
+
+  if (result == ssl_ticket_aead_success) {
+    *out = plaintext;
+    plaintext = NULL;
+    *out_len = plaintext_len;
+  }
+
+  OPENSSL_free(plaintext);
+  return result;
+}
+
+enum ssl_ticket_aead_result_t ssl_process_ticket(
+    SSL *ssl, SSL_SESSION **out_session, int *out_renew_ticket,
+    const uint8_t *ticket, size_t ticket_len, const uint8_t *session_id,
+    size_t session_id_len) {
+  *out_renew_ticket = 0;
+  *out_session = NULL;
+
+  if ((SSL_get_options(ssl) & SSL_OP_NO_TICKET) ||
+      session_id_len > SSL_MAX_SSL_SESSION_ID_LENGTH) {
+    return ssl_ticket_aead_ignore_ticket;
+  }
+
+  uint8_t *plaintext = NULL;
+  size_t plaintext_len;
+  enum ssl_ticket_aead_result_t result;
+  if (ssl->session_ctx->ticket_aead_method != NULL) {
+    result = ssl_decrypt_ticket_with_method(
+        ssl, &plaintext, &plaintext_len, out_renew_ticket, ticket, ticket_len);
+  } else {
+    result = ssl_decrypt_ticket_with_cipher_ctx(
+        ssl, &plaintext, &plaintext_len, out_renew_ticket, ticket, ticket_len);
+  }
+
+  if (result != ssl_ticket_aead_success) {
+    return result;
+  }
+
   /* Decode the session. */
   SSL_SESSION *session =
       SSL_SESSION_from_bytes(plaintext, plaintext_len, ssl->ctx);
+  OPENSSL_free(plaintext);
+
   if (session == NULL) {
     ERR_clear_error(); /* Don't leave an error on the queue. */
-    goto done;
+    return ssl_ticket_aead_ignore_ticket;
   }
 
   /* Copy the client's session ID into the new session, to denote the ticket has
@@ -3169,12 +3205,7 @@
   session->session_id_length = session_id_len;
 
   *out_session = session;
-
-done:
-  OPENSSL_free(plaintext);
-  HMAC_CTX_cleanup(&hmac_ctx);
-  EVP_CIPHER_CTX_cleanup(&cipher_ctx);
-  return ret;
+  return ssl_ticket_aead_success;
 }
 
 int tls1_parse_peer_sigalgs(SSL_HANDSHAKE *hs, const CBS *in_sigalgs) {
diff --git a/src/ssl/test/bssl_shim.cc b/src/ssl/test/bssl_shim.cc
index 984136c..804cbbb 100644
--- a/src/ssl/test/bssl_shim.cc
+++ b/src/ssl/test/bssl_shim.cc
@@ -1611,6 +1611,13 @@
     return false;
   }
 
+  if (is_resume && config->expect_ticket_age_skew != 0 &&
+      SSL_get_ticket_age_skew(ssl) != config->expect_ticket_age_skew) {
+    fprintf(stderr, "Ticket age skew was %" PRId32 ", wanted %d\n",
+            SSL_get_ticket_age_skew(ssl), config->expect_ticket_age_skew);
+    return false;
+  }
+
   return true;
 }
 
diff --git a/src/ssl/test/runner/common.go b/src/ssl/test/runner/common.go
index d316ddd..167e872 100644
--- a/src/ssl/test/runner/common.go
+++ b/src/ssl/test/runner/common.go
@@ -980,6 +980,10 @@
 	// server receives from the client.
 	ExpectTicketAge time.Duration
 
+	// SendTicketAge, if non-zero, is the ticket age to be sent by the
+	// client.
+	SendTicketAge time.Duration
+
 	// FailIfSessionOffered, if true, causes the server to fail any
 	// connections where the client offers a non-empty session ID or session
 	// ticket.
diff --git a/src/ssl/test/runner/handshake_client.go b/src/ssl/test/runner/handshake_client.go
index eb072ad..bf38c1a 100644
--- a/src/ssl/test/runner/handshake_client.go
+++ b/src/ssl/test/runner/handshake_client.go
@@ -270,6 +270,9 @@
 			// TODO(nharper): Support sending more
 			// than one PSK identity.
 			ticketAge := uint32(c.config.time().Sub(session.ticketCreationTime) / time.Millisecond)
+			if c.config.Bugs.SendTicketAge != 0 {
+				ticketAge = uint32(c.config.Bugs.SendTicketAge / time.Millisecond)
+			}
 			psk := pskIdentity{
 				ticket:              ticket,
 				obfuscatedTicketAge: session.ticketAgeAdd + ticketAge,
diff --git a/src/ssl/test/runner/handshake_server.go b/src/ssl/test/runner/handshake_server.go
index 9b4bff8..64edd01 100644
--- a/src/ssl/test/runner/handshake_server.go
+++ b/src/ssl/test/runner/handshake_server.go
@@ -342,19 +342,23 @@
 		}
 	}
 
-	if config.Bugs.IgnorePeerSignatureAlgorithmPreferences {
-		hs.clientHello.signatureAlgorithms = config.signSignatureAlgorithms()
-	}
-	if config.Bugs.IgnorePeerCurvePreferences {
-		hs.clientHello.supportedCurves = config.curvePreferences()
-	}
-	if config.Bugs.IgnorePeerCipherPreferences {
-		hs.clientHello.cipherSuites = config.cipherSuites()
-	}
+	applyBugsToClientHello(hs.clientHello, config)
 
 	return nil
 }
 
+func applyBugsToClientHello(clientHello *clientHelloMsg, config *Config) {
+	if config.Bugs.IgnorePeerSignatureAlgorithmPreferences {
+		clientHello.signatureAlgorithms = config.signSignatureAlgorithms()
+	}
+	if config.Bugs.IgnorePeerCurvePreferences {
+		clientHello.supportedCurves = config.curvePreferences()
+	}
+	if config.Bugs.IgnorePeerCipherPreferences {
+		clientHello.cipherSuites = config.cipherSuites()
+	}
+}
+
 func (hs *serverHandshakeState) doTLS13Handshake() error {
 	c := hs.c
 	config := c.config
@@ -587,6 +591,8 @@
 		}
 		hs.writeClientHash(newClientHello.marshal())
 
+		applyBugsToClientHello(newClientHello, config)
+
 		// Check that the new ClientHello matches the old ClientHello,
 		// except for relevant modifications.
 		//
diff --git a/src/ssl/test/runner/runner.go b/src/ssl/test/runner/runner.go
index 55685b0..d90485c 100644
--- a/src/ssl/test/runner/runner.go
+++ b/src/ssl/test/runner/runner.go
@@ -8601,6 +8601,38 @@
 		expectedLocalError: "tls: invalid ticket age",
 	})
 
+	// Test that the server's ticket age skew reporting works.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-TicketAgeSkew-Forward",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketAge: 15 * time.Second,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-resumption-delay", "10",
+			"-expect-ticket-age-skew", "5",
+		},
+	})
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "TLS13-TicketAgeSkew-Backward",
+		config: Config{
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				SendTicketAge: 5 * time.Second,
+			},
+		},
+		resumeSession: true,
+		flags: []string{
+			"-resumption-delay", "10",
+			"-expect-ticket-age-skew", "-5",
+		},
+	})
+
 	testCases = append(testCases, testCase{
 		testType: clientTest,
 		name:     "TLS13-SendTicketEarlyDataInfo",
diff --git a/src/ssl/test/test_config.cc b/src/ssl/test/test_config.cc
index fefe376..e581581 100644
--- a/src/ssl/test/test_config.cc
+++ b/src/ssl/test/test_config.cc
@@ -180,6 +180,7 @@
   { "-resumption-delay", &TestConfig::resumption_delay },
   { "-max-send-fragment", &TestConfig::max_send_fragment },
   { "-read-size", &TestConfig::read_size },
+  { "-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew },
 };
 
 const Flag<std::vector<int>> kIntVectorFlags[] = {
diff --git a/src/ssl/test/test_config.h b/src/ssl/test/test_config.h
index ee3d462..7057b48 100644
--- a/src/ssl/test/test_config.h
+++ b/src/ssl/test/test_config.h
@@ -133,6 +133,7 @@
   int read_size = 0;
   bool expect_session_id = false;
   bool expect_no_session_id = false;
+  int expect_ticket_age_skew = 0;
 };
 
 bool ParseConfig(int argc, char **argv, TestConfig *out_config);
diff --git a/src/ssl/tls13_both.c b/src/ssl/tls13_both.c
index 5bd58eb..6243923 100644
--- a/src/ssl/tls13_both.c
+++ b/src/ssl/tls13_both.c
@@ -23,7 +23,6 @@
 #include <openssl/mem.h>
 #include <openssl/stack.h>
 #include <openssl/x509.h>
-#include <openssl/x509v3.h>
 
 #include "../crypto/internal.h"
 #include "internal.h"
@@ -80,6 +79,11 @@
         hs->wait = ssl_hs_ok;
         return -1;
 
+      case ssl_hs_pending_ticket:
+        ssl->rwstate = SSL_PENDING_TICKET;
+        hs->wait = ssl_hs_ok;
+        return -1;
+
       case ssl_hs_ok:
         break;
     }
diff --git a/src/ssl/tls13_client.c b/src/ssl/tls13_client.c
index c0eb135..f13a4f7 100644
--- a/src/ssl/tls13_client.c
+++ b/src/ssl/tls13_client.c
@@ -23,7 +23,6 @@
 #include <openssl/err.h>
 #include <openssl/mem.h>
 #include <openssl/stack.h>
-#include <openssl/x509.h>
 
 #include "../crypto/internal.h"
 #include "internal.h"
@@ -259,7 +258,7 @@
 
     /* Resumption incorporates fresh key material, so refresh the timeout. */
     ssl_session_renew_timeout(ssl, hs->new_session,
-                              ssl->initial_ctx->session_psk_dhe_timeout);
+                              ssl->session_ctx->session_psk_dhe_timeout);
   } else if (!ssl_get_new_session(hs, 0)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     return ssl_hs_error;
diff --git a/src/ssl/tls13_server.c b/src/ssl/tls13_server.c
index e7cc296..9c8d1a1 100644
--- a/src/ssl/tls13_server.c
+++ b/src/ssl/tls13_server.c
@@ -36,6 +36,7 @@
 
 enum server_hs_state_t {
   state_select_parameters = 0,
+  state_select_session,
   state_send_hello_retry_request,
   state_process_second_client_hello,
   state_send_server_hello,
@@ -134,6 +135,8 @@
 }
 
 static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
+  /* At this point, most ClientHello extensions have already been processed by
+   * the common handshake logic. Resolve the remaining non-PSK parameters. */
   SSL *const ssl = hs->ssl;
 
   SSL_CLIENT_HELLO client_hello;
@@ -152,6 +155,14 @@
     return ssl_hs_error;
   }
 
+  /* HTTP/2 negotiation depends on the cipher suite, so ALPN negotiation was
+   * deferred. Complete it now. */
+  uint8_t alert = SSL_AD_DECODE_ERROR;
+  if (!ssl_negotiate_alpn(hs, &alert, &client_hello)) {
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+    return ssl_hs_error;
+  }
+
   /* The PRF hash is now known. Set up the key schedule and hash the
    * ClientHello. */
   if (!tls13_init_key_schedule(hs) ||
@@ -159,94 +170,159 @@
     return ssl_hs_error;
   }
 
+  hs->tls13_state = state_select_session;
+  return ssl_hs_ok;
+}
 
-  /* Decode the ticket if we agree on a PSK key exchange mode. */
+static enum ssl_ticket_aead_result_t select_session(
+    SSL_HANDSHAKE *hs, uint8_t *out_alert, SSL_SESSION **out_session,
+    int32_t *out_ticket_age_skew, const SSL_CLIENT_HELLO *client_hello) {
+  SSL *const ssl = hs->ssl;
+  *out_session = NULL;
+
+  /* Decode the ticket if we agreed on a PSK key exchange mode. */
+  CBS pre_shared_key;
+  if (!hs->accept_psk_mode ||
+      !ssl_client_hello_get_extension(client_hello, &pre_shared_key,
+                                      TLSEXT_TYPE_pre_shared_key)) {
+    return ssl_ticket_aead_ignore_ticket;
+  }
+
+  /* Verify that the pre_shared_key extension is the last extension in
+   * ClientHello. */
+  if (CBS_data(&pre_shared_key) + CBS_len(&pre_shared_key) !=
+      client_hello->extensions + client_hello->extensions_len) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_PRE_SHARED_KEY_MUST_BE_LAST);
+    *out_alert = SSL_AD_ILLEGAL_PARAMETER;
+    return ssl_ticket_aead_error;
+  }
+
+  CBS ticket, binders;
+  uint32_t client_ticket_age;
+  if (!ssl_ext_pre_shared_key_parse_clienthello(hs, &ticket, &binders,
+                                                &client_ticket_age, out_alert,
+                                                &pre_shared_key)) {
+    return ssl_ticket_aead_error;
+  }
+
+  /* TLS 1.3 session tickets are renewed separately as part of the
+   * NewSessionTicket. */
+  int unused_renew;
+  SSL_SESSION *session = NULL;
+  enum ssl_ticket_aead_result_t ret =
+      ssl_process_ticket(ssl, &session, &unused_renew, CBS_data(&ticket),
+                         CBS_len(&ticket), NULL, 0);
+  switch (ret) {
+    case ssl_ticket_aead_success:
+      break;
+    case ssl_ticket_aead_error:
+      *out_alert = SSL_AD_INTERNAL_ERROR;
+      return ret;
+    default:
+      return ret;
+  }
+
+  if (!ssl_session_is_resumable(hs, session) ||
+      /* Historically, some TLS 1.3 tickets were missing ticket_age_add. */
+      !session->ticket_age_add_valid) {
+    SSL_SESSION_free(session);
+    return ssl_ticket_aead_ignore_ticket;
+  }
+
+  /* Recover the client ticket age and convert to seconds. */
+  client_ticket_age -= session->ticket_age_add;
+  client_ticket_age /= 1000;
+
+  struct OPENSSL_timeval now;
+  ssl_get_current_time(ssl, &now);
+
+  /* Compute the server ticket age in seconds. */
+  assert(now.tv_sec >= session->time);
+  uint64_t server_ticket_age = now.tv_sec - session->time;
+
+  /* To avoid overflowing |hs->ticket_age_skew|, we will not resume
+   * 68-year-old sessions. */
+  if (server_ticket_age > INT32_MAX) {
+    SSL_SESSION_free(session);
+    return ssl_ticket_aead_ignore_ticket;
+  }
+
+  /* TODO(davidben,svaldez): Measure this value to decide on tolerance. For
+   * now, accept all values. https://crbug.com/boringssl/113. */
+  *out_ticket_age_skew =
+      (int32_t)client_ticket_age - (int32_t)server_ticket_age;
+
+  /* Check the PSK binder. */
+  if (!tls13_verify_psk_binder(hs, session, &binders)) {
+    SSL_SESSION_free(session);
+    *out_alert = SSL_AD_DECRYPT_ERROR;
+    return ssl_ticket_aead_error;
+  }
+
+  *out_session = session;
+  return ssl_ticket_aead_success;
+}
+
+static enum ssl_hs_wait_t do_select_session(SSL_HANDSHAKE *hs) {
+  SSL *const ssl = hs->ssl;
+  SSL_CLIENT_HELLO client_hello;
+  if (!ssl_client_hello_init(ssl, &client_hello, ssl->init_msg,
+                             ssl->init_num)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_CLIENTHELLO_PARSE_FAILED);
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+    return ssl_hs_error;
+  }
+
   uint8_t alert = SSL_AD_DECODE_ERROR;
   SSL_SESSION *session = NULL;
-  CBS pre_shared_key, binders;
-  if (hs->accept_psk_mode &&
-      ssl_client_hello_get_extension(&client_hello, &pre_shared_key,
-                                     TLSEXT_TYPE_pre_shared_key)) {
-    /* Verify that the pre_shared_key extension is the last extension in
-     * ClientHello. */
-    if (CBS_data(&pre_shared_key) + CBS_len(&pre_shared_key) !=
-        client_hello.extensions + client_hello.extensions_len) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_PRE_SHARED_KEY_MUST_BE_LAST);
-      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-      return ssl_hs_error;
-    }
-
-    if (!ssl_ext_pre_shared_key_parse_clienthello(hs, &session, &binders,
-                                                  &alert, &pre_shared_key)) {
-      ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-      return ssl_hs_error;
-    }
-  }
-
-  if (session != NULL &&
-      !ssl_session_is_resumable(hs, session)) {
-    SSL_SESSION_free(session);
-    session = NULL;
-  }
-
-  /* Set up the new session, either using the original one as a template or
-   * creating a fresh one. */
-  if (session == NULL) {
-    if (!ssl_get_new_session(hs, 1 /* server */)) {
-      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return ssl_hs_error;
-    }
-
-    hs->new_session->cipher = hs->new_cipher;
-
-    /* On new sessions, stash the SNI value in the session. */
-    if (hs->hostname != NULL) {
-      OPENSSL_free(hs->new_session->tlsext_hostname);
-      hs->new_session->tlsext_hostname = BUF_strdup(hs->hostname);
-      if (hs->new_session->tlsext_hostname == NULL) {
+  switch (select_session(hs, &alert, &session, &ssl->s3->ticket_age_skew,
+                         &client_hello)) {
+    case ssl_ticket_aead_ignore_ticket:
+      assert(session == NULL);
+      if (!ssl_get_new_session(hs, 1 /* server */)) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
         return ssl_hs_error;
       }
-    }
-  } else {
-    /* Check the PSK binder. */
-    if (!tls13_verify_psk_binder(hs, session, &binders)) {
-      SSL_SESSION_free(session);
-      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECRYPT_ERROR);
-      return ssl_hs_error;
-    }
+      break;
 
-    /* Only authentication information carries over in TLS 1.3. */
-    hs->new_session = SSL_SESSION_dup(session, SSL_SESSION_DUP_AUTH_ONLY);
-    if (hs->new_session == NULL) {
+    case ssl_ticket_aead_success:
+      /* Carry over authentication information from the previous handshake into
+       * a fresh session. */
+      hs->new_session = SSL_SESSION_dup(session, SSL_SESSION_DUP_AUTH_ONLY);
+      SSL_SESSION_free(session);
+      if (hs->new_session == NULL) {
+        ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+        return ssl_hs_error;
+      }
+
+      ssl->s3->session_reused = 1;
+
+      /* Resumption incorporates fresh key material, so refresh the timeout. */
+      ssl_session_renew_timeout(ssl, hs->new_session,
+                                ssl->session_ctx->session_psk_dhe_timeout);
+      break;
+
+    case ssl_ticket_aead_error:
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+      return ssl_hs_error;
+
+    case ssl_ticket_aead_retry:
+      hs->tls13_state = state_select_session;
+      return ssl_hs_pending_ticket;
+  }
+
+  /* Record connection properties in the new session. */
+  hs->new_session->cipher = hs->new_cipher;
+
+  if (hs->hostname != NULL) {
+    OPENSSL_free(hs->new_session->tlsext_hostname);
+    hs->new_session->tlsext_hostname = BUF_strdup(hs->hostname);
+    if (hs->new_session->tlsext_hostname == NULL) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_hs_error;
     }
-    ssl->s3->session_reused = 1;
-    SSL_SESSION_free(session);
-
-    /* Resumption incorporates fresh key material, so refresh the timeout. */
-    ssl_session_renew_timeout(ssl, hs->new_session,
-                              ssl->initial_ctx->session_psk_dhe_timeout);
   }
 
-  if (ssl->ctx->dos_protection_cb != NULL &&
-      ssl->ctx->dos_protection_cb(&client_hello) == 0) {
-    /* Connection rejected for DOS reasons. */
-    OPENSSL_PUT_ERROR(SSL, SSL_R_CONNECTION_REJECTED);
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-    return ssl_hs_error;
-  }
-
-  /* HTTP/2 negotiation depends on the cipher suite, so ALPN negotiation was
-   * deferred. Complete it now. */
-  alert = SSL_AD_DECODE_ERROR;
-  if (!ssl_negotiate_alpn(hs, &alert, &client_hello)) {
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
-    return ssl_hs_error;
-  }
-
-  /* Store the initial negotiated ALPN in the session. */
   if (ssl->s3->alpn_selected != NULL) {
     hs->new_session->early_alpn =
         BUF_memdup(ssl->s3->alpn_selected, ssl->s3->alpn_selected_len);
@@ -257,6 +333,14 @@
     hs->new_session->early_alpn_len = ssl->s3->alpn_selected_len;
   }
 
+  if (ssl->ctx->dos_protection_cb != NULL &&
+      ssl->ctx->dos_protection_cb(&client_hello) == 0) {
+    /* Connection rejected for DOS reasons. */
+    OPENSSL_PUT_ERROR(SSL, SSL_R_CONNECTION_REJECTED);
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+    return ssl_hs_error;
+  }
+
   /* Incorporate the PSK into the running secret. */
   if (ssl->s3->session_reused) {
     if (!tls13_advance_key_schedule(hs, hs->new_session->master_key,
@@ -565,6 +649,7 @@
     if (!RAND_bytes((uint8_t *)&session->ticket_age_add, 4)) {
       goto err;
     }
+    session->ticket_age_add_valid = 1;
 
     CBB body, ticket, extensions;
     if (!ssl->method->init_message(ssl, &cbb, &body,
@@ -618,6 +703,9 @@
       case state_select_parameters:
         ret = do_select_parameters(hs);
         break;
+      case state_select_session:
+        ret = do_select_session(hs);
+        break;
       case state_send_hello_retry_request:
         ret = do_send_hello_retry_request(hs);
         break;
diff --git a/src/ssl/tls_method.c b/src/ssl/tls_method.c
index 7778310..6144f86 100644
--- a/src/ssl/tls_method.c
+++ b/src/ssl/tls_method.c
@@ -278,7 +278,14 @@
 static void ssl_noop_x509_session_clear(SSL_SESSION *session) {}
 static int ssl_noop_x509_session_verify_cert_chain(SSL_SESSION *session,
                                                    SSL *ssl) {
-  return 0;
+  if (!ssl->ctx->i_promise_to_verify_certs_after_the_handshake) {
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNKNOWN_CA);
+    OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_VERIFY_FAILED);
+    return 0;
+  }
+
+  session->verify_result = X509_V_OK;
+  return 1;
 }
 
 static void ssl_noop_x509_hs_flush_cached_ca_names(SSL_HANDSHAKE *hs) {}
diff --git a/src/ssl/tls_record.c b/src/ssl/tls_record.c
index bf9735c..aafb6f5 100644
--- a/src/ssl/tls_record.c
+++ b/src/ssl/tls_record.c
@@ -140,7 +140,7 @@
  * state needs record-splitting and zero otherwise. */
 static int ssl_needs_record_splitting(const SSL *ssl) {
   return ssl->s3->aead_write_ctx != NULL &&
-         ssl3_protocol_version(ssl) < TLS1_1_VERSION &&
+         ssl->s3->aead_write_ctx->version < TLS1_1_VERSION &&
          (ssl->mode & SSL_MODE_CBC_RECORD_SPLITTING) != 0 &&
          SSL_CIPHER_is_block_cipher(ssl->s3->aead_write_ctx->cipher);
 }
@@ -190,8 +190,8 @@
   size_t ret = SSL3_RT_HEADER_LENGTH;
   ret += SSL_AEAD_CTX_max_overhead(ssl->s3->aead_write_ctx);
   /* TLS 1.3 needs an extra byte for the encrypted record type. */
-  if (ssl->s3->have_version &&
-      ssl3_protocol_version(ssl) >= TLS1_3_VERSION) {
+  if (ssl->s3->aead_write_ctx != NULL &&
+      ssl->s3->aead_write_ctx->version >= TLS1_3_VERSION) {
     ret += 1;
   }
   if (ssl_needs_record_splitting(ssl)) {
@@ -287,9 +287,8 @@
   }
 
   /* TLS 1.3 hides the record type inside the encrypted data. */
-  if (ssl->s3->have_version &&
-      ssl3_protocol_version(ssl) >= TLS1_3_VERSION &&
-      ssl->s3->aead_read_ctx != NULL) {
+  if (ssl->s3->aead_read_ctx != NULL &&
+      ssl->s3->aead_read_ctx->version >= TLS1_3_VERSION) {
     /* The outer record type is always application_data. */
     if (type != SSL3_RT_APPLICATION_DATA) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_OUTER_RECORD_TYPE);
@@ -357,9 +356,8 @@
   assert(!buffers_alias(in, in_len, out, max_out));
 
   /* TLS 1.3 hides the actual record type inside the encrypted data. */
-  if (ssl->s3->have_version &&
-      ssl3_protocol_version(ssl) >= TLS1_3_VERSION &&
-      ssl->s3->aead_write_ctx != NULL) {
+  if (ssl->s3->aead_write_ctx != NULL &&
+      ssl->s3->aead_write_ctx->version >= TLS1_3_VERSION) {
     if (in_len > in_len + SSL3_RT_HEADER_LENGTH + 1 ||
         max_out < in_len + SSL3_RT_HEADER_LENGTH + 1) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_BUFFER_TOO_SMALL);