Splitting SSL session state.

To prevent configuration/established session confusion, the handshake
session state is separated into the configured session (ssl->session)
and the newly created session (ssl->s3->new_session). Upon conclusion of
the handshake, the finalized session is stored
in (ssl->s3->established_session). During the handshake, any requests
for the session (SSL_get_session) return a non-resumable session, to
prevent resumption of a partially filled session. Sessions should only
be cached upon the completion of the full handshake, using the resulting
established_session. The semantics of accessors on the session are
maintained mid-renego.

Change-Id: I4358aecb71fce4fe14a6746c5af1416a69935078
Reviewed-on: https://boringssl-review.googlesource.com/8612
Reviewed-by: David Benjamin <davidben@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
CQ-Verified: CQ bot account: commit-bot@chromium.org <commit-bot@chromium.org>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 82d503e..ec8eba2 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -1658,10 +1658,16 @@
  * |session|. */
 OPENSSL_EXPORT int SSL_set_session(SSL *ssl, SSL_SESSION *session);
 
-/* SSL_get_session returns a non-owning pointer to |ssl|'s session. Prior to the
- * initial handshake beginning, this is the session to be offered, set by
- * |SSL_set_session|. After a handshake has finished, this is the currently
- * active session. Its behavior is undefined while a handshake is progress. */
+/* SSL_get_session returns a non-owning pointer to |ssl|'s session. For
+ * historical reasons, which session it returns depends on |ssl|'s state.
+ *
+ * Prior to the start of the initial handshake, it returns the session the
+ * caller set with |SSL_set_session|. After the initial handshake has finished
+ * and if no additional handshakes are in progress, it returns the currently
+ * active session. Its behavior is undefined while a handshake is in progress.
+ *
+ * Using this function to add new sessions to an external session cache is
+ * deprecated. Use |SSL_CTX_sess_set_new_cb| instead. */
 OPENSSL_EXPORT SSL_SESSION *SSL_get_session(const SSL *ssl);
 
 /* SSL_get0_session is an alias for |SSL_get_session|. */
@@ -2945,6 +2951,7 @@
 #define SSL_ST_OK 0x03
 #define SSL_ST_RENEGOTIATE (0x04 | SSL_ST_INIT)
 #define SSL_ST_TLS13 (0x05 | SSL_ST_INIT)
+#define SSL_ST_ERROR (0x06| SSL_ST_INIT)
 
 /* SSL_CB_* are possible values for the |type| parameter in the info
  * callback and the bitmasks that make them up. */
@@ -3681,9 +3688,7 @@
   /* peer_sha256_valid is non-zero if |peer_sha256| is valid. */
   unsigned peer_sha256_valid:1; /* Non-zero if peer_sha256 is valid */
 
-  /* not_resumable is used to indicate that session resumption is not allowed.
-   * Applications can also set this bit for a new session via
-   * not_resumable_session_cb to disable session caching and tickets. */
+  /* not_resumable is used to indicate that session resumption is disallowed. */
   unsigned not_resumable:1;
 };
 
@@ -4069,7 +4074,8 @@
   unsigned int sid_ctx_length;
   uint8_t sid_ctx[SSL_MAX_SID_CTX_LENGTH];
 
-  /* This can also be in the session once a session is established */
+  /* session is the configured session to be offered by the client. This session
+   * is immutable. */
   SSL_SESSION *session;
 
   int (*verify_callback)(int ok,
@@ -4139,9 +4145,6 @@
   /* verify_mode is a bitmask of |SSL_VERIFY_*| values. */
   uint8_t verify_mode;
 
-  /* hit is true if this connection is resuming a previous session. */
-  unsigned hit:1;
-
   /* server is true iff the this SSL* is the server half. Note: before the SSL*
    * is initialized by either SSL_set_accept_state or SSL_set_connect_state,
    * the side is not determined. In this state, server is always false. */
@@ -4397,6 +4400,15 @@
     uint32_t server_params_len;
   } tmp;
 
+  /* new_session is the new mutable session being established by the current
+   * handshake. It should not be cached. */
+  SSL_SESSION *new_session;
+
+  /* established_session is the session established by the connection. This
+   * session is only filled upon the completion of the handshake and is
+   * immutable. */
+  SSL_SESSION *established_session;
+
   /* Connection binding to prevent renegotiation attacks */
   uint8_t previous_client_finished[EVP_MAX_MD_SIZE];
   uint8_t previous_client_finished_len;
diff --git a/ssl/handshake_client.c b/ssl/handshake_client.c
index 5fe706c..d78bc27 100644
--- a/ssl/handshake_client.c
+++ b/ssl/handshake_client.c
@@ -252,7 +252,7 @@
           goto end;
         }
 
-        if (ssl->hit) {
+        if (ssl->session != NULL) {
           ssl->state = SSL3_ST_CR_SESSION_TICKET_A;
         } else {
           ssl->state = SSL3_ST_CR_CERT_A;
@@ -411,7 +411,7 @@
         }
         ssl->state = SSL3_ST_CW_FLUSH;
 
-        if (ssl->hit) {
+        if (ssl->session != NULL) {
           ssl->s3->tmp.next_state = SSL_ST_OK;
         } else {
           /* This is a non-resumption handshake. If it involves ChannelID, then
@@ -474,7 +474,7 @@
         }
         ssl->method->received_flight(ssl);
 
-        if (ssl->hit) {
+        if (ssl->session != NULL) {
           ssl->state = SSL3_ST_CW_CHANGE;
         } else {
           ssl->state = SSL_ST_OK;
@@ -506,6 +506,29 @@
         ssl3_cleanup_key_block(ssl);
         ssl->method->release_current_message(ssl, 1 /* free_buffer */);
 
+        SSL_SESSION_free(ssl->s3->established_session);
+        if (ssl->session != NULL) {
+          ssl->s3->established_session = SSL_SESSION_up_ref(ssl->session);
+        } else {
+          /* We make a copy of the session in order to maintain the immutability
+           * of the new established_session due to False Start. The caller may
+           * have taken a reference to the temporary session. */
+          ssl->s3->established_session =
+              SSL_SESSION_dup(ssl->s3->new_session, 1 /* include ticket */);
+          if (ssl->s3->established_session == NULL) {
+            /* Do not stay in SSL_ST_OK, to avoid confusing |SSL_in_init|
+             * callers. */
+            ssl->state = SSL_ST_ERROR;
+            skip = 1;
+            ret = -1;
+            goto end;
+          }
+          ssl->s3->established_session->not_resumable = 0;
+
+          SSL_SESSION_free(ssl->s3->new_session);
+          ssl->s3->new_session = NULL;
+        }
+
         /* Remove write buffering now. */
         ssl_free_wbio_buffer(ssl);
 
@@ -526,6 +549,11 @@
         ssl_do_info_callback(ssl, SSL_CB_HANDSHAKE_DONE, 1);
         goto end;
 
+      case SSL_ST_ERROR:
+        OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_HANDSHAKE_FAILURE);
+        ret = -1;
+        goto end;
+
       default:
         OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_STATE);
         ret = -1;
@@ -869,17 +897,16 @@
                         SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
       goto f_err;
     }
-    ssl->hit = 1;
   } else {
     /* The session wasn't resumed. Create a fresh SSL_SESSION to
      * fill out. */
-    ssl->hit = 0;
+    SSL_set_session(ssl, NULL);
     if (!ssl_get_new_session(ssl, 0 /* client */)) {
       goto f_err;
     }
     /* Note: session_id could be empty. */
-    ssl->session->session_id_length = CBS_len(&session_id);
-    memcpy(ssl->session->session_id, CBS_data(&session_id),
+    ssl->s3->new_session->session_id_length = CBS_len(&session_id);
+    memcpy(ssl->s3->new_session->session_id, CBS_data(&session_id),
            CBS_len(&session_id));
   }
 
@@ -908,7 +935,7 @@
     goto f_err;
   }
 
-  if (ssl->hit) {
+  if (ssl->session != NULL) {
     if (ssl->session->cipher != c) {
       al = SSL_AD_ILLEGAL_PARAMETER;
       OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED);
@@ -920,7 +947,7 @@
       goto f_err;
     }
   } else {
-    ssl->session->cipher = c;
+    ssl->s3->new_session->cipher = c;
   }
   ssl->s3->tmp.new_cipher = c;
 
@@ -932,7 +959,8 @@
   /* If doing a full handshake, the server may request a client certificate
    * which requires hashing the handshake transcript. Otherwise, the handshake
    * buffer may be released. */
-  if (ssl->hit || !ssl_cipher_uses_certificate_auth(ssl->s3->tmp.new_cipher)) {
+  if (ssl->session != NULL ||
+      !ssl_cipher_uses_certificate_auth(ssl->s3->tmp.new_cipher)) {
     ssl3_free_handshake_buffer(ssl);
   }
 
@@ -957,7 +985,7 @@
     goto f_err;
   }
 
-  if (ssl->hit &&
+  if (ssl->session != NULL &&
       ssl->s3->tmp.extended_master_secret !=
           ssl->session->extended_master_secret) {
     al = SSL_AD_HANDSHAKE_FAILURE;
@@ -1007,13 +1035,13 @@
 
   /* NOTE: Unlike the server half, the client's copy of |cert_chain| includes
    * the leaf. */
-  sk_X509_pop_free(ssl->session->cert_chain, X509_free);
-  ssl->session->cert_chain = chain;
+  sk_X509_pop_free(ssl->s3->new_session->cert_chain, X509_free);
+  ssl->s3->new_session->cert_chain = chain;
 
-  X509_free(ssl->session->peer);
-  ssl->session->peer = X509_up_ref(leaf);
+  X509_free(ssl->s3->new_session->peer);
+  ssl->s3->new_session->peer = X509_up_ref(leaf);
 
-  ssl->session->verify_result = ssl->verify_result;
+  ssl->s3->new_session->verify_result = ssl->verify_result;
 
   return 1;
 
@@ -1050,8 +1078,8 @@
     goto f_err;
   }
 
-  if (!CBS_stow(&ocsp_response, &ssl->session->ocsp_response,
-                &ssl->session->ocsp_response_length)) {
+  if (!CBS_stow(&ocsp_response, &ssl->s3->new_session->ocsp_response,
+                &ssl->s3->new_session->ocsp_response_length)) {
     al = SSL_AD_INTERNAL_ERROR;
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     goto f_err;
@@ -1064,7 +1092,7 @@
 }
 
 static int ssl3_verify_server_cert(SSL *ssl) {
-  int ret = ssl_verify_cert_chain(ssl, ssl->session->cert_chain);
+  int ret = ssl_verify_cert_chain(ssl, ssl->s3->new_session->cert_chain);
   if (ssl->verify_mode != SSL_VERIFY_NONE && ret <= 0) {
     int al = ssl_verify_alarm_type(ssl->verify_result);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, al);
@@ -1173,11 +1201,11 @@
       goto err;
     }
 
-    ssl->session->key_exchange_info = DH_num_bits(dh);
-    if (ssl->session->key_exchange_info < 1024) {
+    ssl->s3->new_session->key_exchange_info = DH_num_bits(dh);
+    if (ssl->s3->new_session->key_exchange_info < 1024) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_BAD_DH_P_LENGTH);
       goto err;
-    } else if (ssl->session->key_exchange_info > 4096) {
+    } else if (ssl->s3->new_session->key_exchange_info > 4096) {
       /* Overly large DHE groups are prohibitively expensive, so enforce a limit
        * to prevent a server from causing us to perform too expensive of a
        * computation. */
@@ -1210,7 +1238,7 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
       goto f_err;
     }
-    ssl->session->key_exchange_info = group_id;
+    ssl->s3->new_session->key_exchange_info = group_id;
 
     /* Ensure the group is consistent with preferences. */
     if (!tls1_check_group_id(ssl, group_id)) {
@@ -1261,7 +1289,7 @@
 
   /* ServerKeyExchange should be signed by the server's public key. */
   if (ssl_cipher_uses_certificate_auth(ssl->s3->tmp.new_cipher)) {
-    pkey = X509_get_pubkey(ssl->session->peer);
+    pkey = X509_get_pubkey(ssl->s3->new_session->peer);
     if (pkey == NULL) {
       goto err;
     }
@@ -1518,9 +1546,9 @@
     }
     assert(psk_len <= PSK_MAX_PSK_LEN);
 
-    OPENSSL_free(ssl->session->psk_identity);
-    ssl->session->psk_identity = BUF_strdup(identity);
-    if (ssl->session->psk_identity == NULL) {
+    OPENSSL_free(ssl->s3->new_session->psk_identity);
+    ssl->s3->new_session->psk_identity = BUF_strdup(identity);
+    if (ssl->s3->new_session->psk_identity == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto err;
     }
@@ -1544,7 +1572,7 @@
       goto err;
     }
 
-    EVP_PKEY *pkey = X509_get_pubkey(ssl->session->peer);
+    EVP_PKEY *pkey = X509_get_pubkey(ssl->s3->new_session->peer);
     if (pkey == NULL) {
       goto err;
     }
@@ -1654,12 +1682,14 @@
   }
   ssl->state = SSL3_ST_CW_KEY_EXCH_B;
 
-  ssl->session->master_key_length =
-      tls1_generate_master_secret(ssl, ssl->session->master_key, pms, pms_len);
-  if (ssl->session->master_key_length == 0) {
+  ssl->s3->new_session->master_key_length =
+      tls1_generate_master_secret(ssl, ssl->s3->new_session->master_key, pms,
+                                  pms_len);
+  if (ssl->s3->new_session->master_key_length == 0) {
     goto err;
   }
-  ssl->session->extended_master_secret = ssl->s3->tmp.extended_master_secret;
+  ssl->s3->new_session->extended_master_secret =
+      ssl->s3->tmp.extended_master_secret;
   OPENSSL_cleanse(pms, pms_len);
   OPENSSL_free(pms);
 
@@ -1879,7 +1909,6 @@
 }
 
 static int ssl3_get_new_session_ticket(SSL *ssl) {
-  int al;
   int ret = ssl->method->ssl_get_message(ssl, SSL3_MT_NEW_SESSION_TICKET,
                                          ssl_hash_message);
   if (ret <= 0) {
@@ -1892,9 +1921,9 @@
   if (!CBS_get_u32(&new_session_ticket, &ticket_lifetime_hint) ||
       !CBS_get_u16_length_prefixed(&new_session_ticket, &ticket) ||
       CBS_len(&new_session_ticket) != 0) {
-    al = SSL_AD_DECODE_ERROR;
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    goto f_err;
+    return -1;
   }
 
   if (CBS_len(&ticket) == 0) {
@@ -1906,46 +1935,47 @@
     return 1;
   }
 
-  if (ssl->hit) {
+  int session_renewed = ssl->session != NULL;
+  SSL_SESSION *session = ssl->s3->new_session;
+  if (session_renewed) {
     /* The server is sending a new ticket for an existing session. Sessions are
      * immutable once established, so duplicate all but the ticket of the
      * existing session. */
-    uint8_t *bytes;
-    size_t bytes_len;
-    if (!SSL_SESSION_to_bytes_for_ticket(ssl->session, &bytes, &bytes_len)) {
-      goto err;
-    }
-    SSL_SESSION *new_session = SSL_SESSION_from_bytes(bytes, bytes_len);
-    OPENSSL_free(bytes);
-    if (new_session == NULL) {
+    session = SSL_SESSION_dup(ssl->session,
+                              0 /* Don't duplicate session ticket */);
+    if (session == NULL) {
       /* This should never happen. */
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       goto err;
     }
-
-    SSL_SESSION_free(ssl->session);
-    ssl->session = new_session;
   }
 
-  if (!CBS_stow(&ticket, &ssl->session->tlsext_tick,
-                &ssl->session->tlsext_ticklen)) {
+  if (!CBS_stow(&ticket, &session->tlsext_tick, &session->tlsext_ticklen)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     goto err;
   }
-  ssl->session->tlsext_tick_lifetime_hint = ticket_lifetime_hint;
+  session->tlsext_tick_lifetime_hint = ticket_lifetime_hint;
 
   /* Generate a session ID for this session based on the session ticket. We use
    * the session ID mechanism for detecting ticket resumption. This also fits in
    * with assumptions elsewhere in OpenSSL.*/
-  if (!EVP_Digest(CBS_data(&ticket), CBS_len(&ticket), ssl->session->session_id,
-                  &ssl->session->session_id_length, EVP_sha256(), NULL)) {
+  if (!EVP_Digest(CBS_data(&ticket), CBS_len(&ticket),
+                  session->session_id, &session->session_id_length,
+                  EVP_sha256(), NULL)) {
     goto err;
   }
 
+  if (session_renewed) {
+    session->not_resumable = 0;
+    SSL_SESSION_free(ssl->session);
+    ssl->session = session;
+  }
+
   return 1;
 
-f_err:
-  ssl3_send_alert(ssl, SSL3_AL_FATAL, al);
 err:
+  if (session_renewed) {
+    SSL_SESSION_free(session);
+  }
   return -1;
 }
diff --git a/ssl/handshake_server.c b/ssl/handshake_server.c
index 9f4aae3..caa2681 100644
--- a/ssl/handshake_server.c
+++ b/ssl/handshake_server.c
@@ -243,7 +243,7 @@
         if (ret <= 0) {
           goto end;
         }
-        if (ssl->hit) {
+        if (ssl->session != NULL) {
           ssl->state = SSL3_ST_SW_SESSION_TICKET_A;
         } else {
           ssl->state = SSL3_ST_SW_CERT_A;
@@ -391,16 +391,16 @@
         }
 
         ssl->method->received_flight(ssl);
-        if (ssl->hit) {
+        if (ssl->session != NULL) {
           ssl->state = SSL_ST_OK;
         } else {
           ssl->state = SSL3_ST_SW_SESSION_TICKET_A;
         }
 
-        /* If this is a full handshake with ChannelID then record the hashshake
-         * hashes in |ssl->session| in case we need them to verify a ChannelID
-         * signature on a resumption of this session in the future. */
-        if (!ssl->hit && ssl->s3->tlsext_channel_id_valid) {
+        /* If this is a full handshake with ChannelID then record the handshake
+         * hashes in |ssl->s3->new_session| in case we need them to verify a
+         * ChannelID signature on a resumption of this session in the future. */
+        if (ssl->session == NULL && ssl->s3->tlsext_channel_id_valid) {
           ret = tls1_record_handshake_hashes_for_channel_id(ssl);
           if (ret <= 0) {
             goto end;
@@ -442,7 +442,7 @@
           goto end;
         }
         ssl->state = SSL3_ST_SW_FLUSH;
-        if (ssl->hit) {
+        if (ssl->session != NULL) {
           ssl->s3->tmp.next_state = SSL3_ST_SR_CHANGE;
         } else {
           ssl->s3->tmp.next_state = SSL_ST_OK;
@@ -475,21 +475,31 @@
         ssl3_cleanup_key_block(ssl);
         ssl->method->release_current_message(ssl, 1 /* free_buffer */);
 
+        /* If we aren't retaining peer certificates then we can discard it
+         * now. */
+        if (ssl->s3->new_session != NULL &&
+            ssl->ctx->retain_only_sha256_of_client_certs) {
+          X509_free(ssl->s3->new_session->peer);
+          ssl->s3->new_session->peer = NULL;
+          sk_X509_pop_free(ssl->s3->new_session->cert_chain, X509_free);
+          ssl->s3->new_session->cert_chain = NULL;
+        }
+
+        SSL_SESSION_free(ssl->s3->established_session);
+        if (ssl->session != NULL) {
+          ssl->s3->established_session = SSL_SESSION_up_ref(ssl->session);
+        } else {
+          ssl->s3->established_session = ssl->s3->new_session;
+          ssl->s3->established_session->not_resumable = 0;
+          ssl->s3->new_session = NULL;
+        }
+
         /* remove buffering on output */
         ssl_free_wbio_buffer(ssl);
 
         ssl_handshake_free(ssl->s3->hs);
         ssl->s3->hs = NULL;
 
-        /* If we aren't retaining peer certificates then we can discard it
-         * now. */
-        if (ssl->ctx->retain_only_sha256_of_client_certs) {
-          X509_free(ssl->session->peer);
-          ssl->session->peer = NULL;
-          sk_X509_pop_free(ssl->session->cert_chain, X509_free);
-          ssl->session->cert_chain = NULL;
-        }
-
         ssl->s3->initial_handshake_complete = 1;
 
         ssl_update_cache(ssl, SSL_SESS_CACHE_SERVER);
@@ -653,7 +663,6 @@
     }
   }
 
-  ssl->hit = 0;
   int send_new_ticket = 0;
   switch (ssl_get_prev_session(ssl, &session, &send_new_ticket, &early_ctx)) {
     case ssl_session_success:
@@ -679,6 +688,7 @@
                                            &ems_data, &ems_len) &&
       ems_len == 0;
 
+  int has_session = 0;
   if (session != NULL) {
     if (session->extended_master_secret &&
         !have_extended_master_secret) {
@@ -689,7 +699,7 @@
       goto f_err;
     }
 
-    ssl->hit =
+    has_session =
         /* Only resume if the session's version matches the negotiated version:
          * most clients do not accept a mismatch. */
         ssl->version == session->ssl_version &&
@@ -698,21 +708,20 @@
         have_extended_master_secret == session->extended_master_secret;
   }
 
-  if (ssl->hit) {
-    /* Use the new session. */
-    SSL_SESSION_free(ssl->session);
+  if (has_session) {
+    /* Use the old session. */
     ssl->session = session;
     session = NULL;
-
     ssl->verify_result = ssl->session->verify_result;
   } else {
+    SSL_set_session(ssl, NULL);
     if (!ssl_get_new_session(ssl, 1 /* server */)) {
       goto err;
     }
 
     /* Clear the session ID if we want the session to be single-use. */
     if (!(ssl->ctx->session_cache_mode & SSL_SESS_CACHE_SERVER)) {
-      ssl->session->session_id_length = 0;
+      ssl->s3->new_session->session_id_length = 0;
     }
   }
 
@@ -740,7 +749,7 @@
   }
 
   /* If it is a hit, check that the cipher is in the list. */
-  if (ssl->hit) {
+  if (ssl->session != NULL) {
     size_t j;
     int found_cipher = 0;
     uint32_t id = ssl->session->cipher->id;
@@ -792,7 +801,7 @@
   }
 
   /* Given ciphers and SSL_get_ciphers, we must pick a cipher */
-  if (!ssl->hit) {
+  if (ssl->session == NULL) {
     /* Let cert callback update server certificates if required */
     if (ssl->cert->cert_cb) {
       int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
@@ -813,7 +822,7 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER);
       goto f_err;
     }
-    ssl->session->cipher = c;
+    ssl->s3->new_session->cipher = c;
     ssl->s3->tmp.new_cipher = c;
 
     /* Determine whether to request a client certificate. */
@@ -843,16 +852,6 @@
     ssl3_free_handshake_buffer(ssl);
   }
 
-  /* we now have the following setup;
-   * client_random
-   * cipher_list        - our prefered list of ciphers
-   * ciphers            - the clients prefered list of ciphers
-   * compression        - basically ignored right now
-   * ssl version is set - sslv3
-   * ssl->session         - The ssl session has been setup.
-   * ssl->hit             - session reuse flag
-   * ssl->tmp.new_cipher  - the new cipher to use. */
-
   ret = 1;
 
   if (0) {
@@ -883,7 +882,8 @@
   /* If this is a resumption and the original handshake didn't support
    * ChannelID then we didn't record the original handshake hashes in the
    * session and so cannot resume with ChannelIDs. */
-  if (ssl->hit && ssl->session->original_handshake_hash_len == 0) {
+  if (ssl->session != NULL &&
+      ssl->session->original_handshake_hash_len == 0) {
     ssl->s3->tlsext_channel_id_valid = 0;
   }
 
@@ -911,13 +911,18 @@
     memcpy(ssl->s3->server_random + SSL3_RANDOM_SIZE - 8, kDowngradeTLS12, 8);
   }
 
+  const SSL_SESSION *session = ssl->s3->new_session;
+  if (ssl->session != NULL) {
+    session = ssl->session;
+  }
+
   CBB cbb, body, session_id;
   if (!ssl->method->init_message(ssl, &cbb, &body, SSL3_MT_SERVER_HELLO) ||
       !CBB_add_u16(&body, ssl->version) ||
       !CBB_add_bytes(&body, ssl->s3->server_random, SSL3_RANDOM_SIZE) ||
       !CBB_add_u8_length_prefixed(&body, &session_id) ||
-      !CBB_add_bytes(&session_id, ssl->session->session_id,
-                     ssl->session->session_id_length) ||
+      !CBB_add_bytes(&session_id, session->session_id,
+                     session->session_id_length) ||
       !CBB_add_u16(&body, ssl_cipher_get_value(ssl->s3->tmp.new_cipher)) ||
       !CBB_add_u8(&body, 0 /* no compression */) ||
       !ssl_add_serverhello_tlsext(ssl, &body) ||
@@ -1010,7 +1015,7 @@
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
         goto err;
       }
-      ssl->session->key_exchange_info = DH_num_bits(params);
+      ssl->s3->new_session->key_exchange_info = DH_num_bits(params);
 
       /* Set up DH, generate a key, and emit the public half. */
       DH *dh = DHparams_dup(params);
@@ -1035,7 +1040,7 @@
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
         goto err;
       }
-      ssl->session->key_exchange_info = group_id;
+      ssl->s3->new_session->key_exchange_info = group_id;
 
       /* Set up ECDH, generate a key, and emit the public half. */
       if (!SSL_ECDH_CTX_init(&ssl->s3->tmp.ecdh_ctx, group_id) ||
@@ -1291,9 +1296,9 @@
   CBS_init(&certificate_msg, ssl->init_msg, ssl->init_num);
   uint8_t alert;
   STACK_OF(X509) *chain = ssl_parse_cert_chain(
-      ssl, &alert,
-      ssl->ctx->retain_only_sha256_of_client_certs ? ssl->session->peer_sha256
-                                                   : NULL,
+      ssl, &alert, ssl->ctx->retain_only_sha256_of_client_certs
+                       ? ssl->s3->new_session->peer_sha256
+                       : NULL,
       &certificate_msg);
   if (chain == NULL) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
@@ -1325,7 +1330,7 @@
   } else {
     /* The hash would have been filled in. */
     if (ssl->ctx->retain_only_sha256_of_client_certs) {
-      ssl->session->peer_sha256_valid = 1;
+      ssl->s3->new_session->peer_sha256_valid = 1;
     }
 
     if (ssl_verify_cert_chain(ssl, chain) <= 0) {
@@ -1336,12 +1341,12 @@
     }
   }
 
-  X509_free(ssl->session->peer);
-  ssl->session->peer = sk_X509_shift(chain);
-  ssl->session->verify_result = ssl->verify_result;
+  X509_free(ssl->s3->new_session->peer);
+  ssl->s3->new_session->peer = sk_X509_shift(chain);
+  ssl->s3->new_session->verify_result = ssl->verify_result;
 
-  sk_X509_pop_free(ssl->session->cert_chain, X509_free);
-  ssl->session->cert_chain = chain;
+  sk_X509_pop_free(ssl->s3->new_session->cert_chain, X509_free);
+  ssl->s3->new_session->cert_chain = chain;
   /* Inconsistency alert: cert_chain does *not* include the peer's own
    * certificate, while we do include it in s3_clnt.c */
 
@@ -1402,15 +1407,15 @@
       goto f_err;
     }
 
-    if (!CBS_strdup(&psk_identity, &ssl->session->psk_identity)) {
+    if (!CBS_strdup(&psk_identity, &ssl->s3->new_session->psk_identity)) {
       al = SSL_AD_INTERNAL_ERROR;
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto f_err;
     }
 
     /* Look up the key for the identity. */
-    psk_len = ssl->psk_server_callback(ssl, ssl->session->psk_identity, psk,
-                                       sizeof(psk));
+    psk_len = ssl->psk_server_callback(ssl, ssl->s3->new_session->psk_identity,
+                                       psk, sizeof(psk));
     if (psk_len > PSK_MAX_PSK_LEN) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       al = SSL_AD_INTERNAL_ERROR;
@@ -1597,12 +1602,14 @@
   }
 
   /* Compute the master secret */
-  ssl->session->master_key_length = tls1_generate_master_secret(
-      ssl, ssl->session->master_key, premaster_secret, premaster_secret_len);
-  if (ssl->session->master_key_length == 0) {
+  ssl->s3->new_session->master_key_length = tls1_generate_master_secret(
+      ssl, ssl->s3->new_session->master_key, premaster_secret,
+      premaster_secret_len);
+  if (ssl->s3->new_session->master_key_length == 0) {
     goto err;
   }
-  ssl->session->extended_master_secret = ssl->s3->tmp.extended_master_secret;
+  ssl->s3->new_session->extended_master_secret =
+      ssl->s3->tmp.extended_master_secret;
 
   OPENSSL_cleanse(premaster_secret, premaster_secret_len);
   OPENSSL_free(premaster_secret);
@@ -1623,7 +1630,7 @@
 static int ssl3_get_cert_verify(SSL *ssl) {
   int al, ret = 0;
   CBS certificate_verify, signature;
-  X509 *peer = ssl->session->peer;
+  X509 *peer = ssl->s3->new_session->peer;
   EVP_PKEY *pkey = NULL;
 
   /* Only RSA and ECDSA client certificates are supported, so a
@@ -1865,8 +1872,9 @@
   /* Serialize the SSL_SESSION to be encoded into the ticket. */
   uint8_t *session = NULL;
   size_t session_len;
-  if (!SSL_SESSION_to_bytes_for_ticket(ssl->session, &session,
-                                       &session_len)) {
+  if (!SSL_SESSION_to_bytes_for_ticket(
+          ssl->session != NULL ? ssl->session : ssl->s3->new_session,
+          &session, &session_len)) {
     return -1;
   }
 
@@ -1881,7 +1889,8 @@
       /* Ticket lifetime hint (advisory only): We leave this unspecified for
        * resumed session (for simplicity), and guess that tickets for new
        * sessions will live as long as their sessions. */
-      !CBB_add_u32(&body, ssl->hit ? 0 : ssl->session->timeout) ||
+      !CBB_add_u32(&body, ssl->session != NULL ? 0 :
+                   ssl->s3->new_session->timeout) ||
       !CBB_add_u16_length_prefixed(&body, &ticket)) {
     goto err;
   }
diff --git a/ssl/internal.h b/ssl/internal.h
index 7a4a958..e3d0463 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -157,6 +157,10 @@
 #include <sys/time.h>
 #endif
 
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
 
 /* Cipher suites. */
 
@@ -1216,6 +1220,12 @@
     SSL *ssl, SSL_SESSION **out_session, int *out_send_ticket,
     const struct ssl_early_callback_ctx *ctx);
 
+/* SSL_SESSION_dup returns a newly-allocated |SSL_SESSION| with a copy of the
+ * fields in |session| or NULL on error. The new session is non-resumable and
+ * must be explicitly marked resumable once it has been filled in. */
+OPENSSL_EXPORT SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session,
+                                            int include_ticket);
+
 STACK_OF(SSL_CIPHER) *
     ssl_bytes_to_cipher_list(SSL *ssl, const CBS *cbs, uint16_t max_version);
 void ssl_cipher_preference_list_free(
@@ -1453,4 +1463,9 @@
                             uint16_t signature_algorithm);
 void ssl_set_client_disabled(SSL *ssl);
 
+
+#if defined(__cplusplus)
+} /* extern C */
+#endif
+
 #endif /* OPENSSL_HEADER_SSL_INTERNAL_H */
diff --git a/ssl/s3_both.c b/ssl/s3_both.c
index 0dd67bc..cb5d0da 100644
--- a/ssl/s3_both.c
+++ b/ssl/s3_both.c
@@ -216,8 +216,9 @@
   ssl->s3->tmp.finish_md_len = n;
 
   /* Log the master secret, if logging is enabled. */
-  if (!ssl_log_secret(ssl, "CLIENT_RANDOM", ssl->session->master_key,
-                      ssl->session->master_key_length)) {
+  if (!ssl_log_secret(ssl, "CLIENT_RANDOM",
+                      SSL_get_session(ssl)->master_key,
+                      SSL_get_session(ssl)->master_key_length)) {
     return 0;
   }
 
diff --git a/ssl/s3_enc.c b/ssl/s3_enc.c
index a9edcf5..2209e80 100644
--- a/ssl/s3_enc.c
+++ b/ssl/s3_enc.c
@@ -331,12 +331,16 @@
 
   n = EVP_MD_CTX_size(&ctx);
 
+  SSL_SESSION *session = ssl->session;
+  if (ssl->s3->new_session != NULL) {
+    session = ssl->s3->new_session;
+  }
+
   npad = (48 / n) * n;
   if (sender != NULL) {
     EVP_DigestUpdate(&ctx, sender, sender_len);
   }
-  EVP_DigestUpdate(&ctx, ssl->session->master_key,
-                   ssl->session->master_key_length);
+  EVP_DigestUpdate(&ctx, session->master_key, session->master_key_length);
   EVP_DigestUpdate(&ctx, kPad1, npad);
   EVP_DigestFinal_ex(&ctx, md_buf, &i);
 
@@ -345,8 +349,7 @@
     OPENSSL_PUT_ERROR(SSL, ERR_LIB_EVP);
     return 0;
   }
-  EVP_DigestUpdate(&ctx, ssl->session->master_key,
-                   ssl->session->master_key_length);
+  EVP_DigestUpdate(&ctx, session->master_key, session->master_key_length);
   EVP_DigestUpdate(&ctx, kPad2, npad);
   EVP_DigestUpdate(&ctx, md_buf, i);
   EVP_DigestFinal_ex(&ctx, p, &ret);
diff --git a/ssl/s3_lib.c b/ssl/s3_lib.c
index c93722a..fa29e69 100644
--- a/ssl/s3_lib.c
+++ b/ssl/s3_lib.c
@@ -211,6 +211,8 @@
   OPENSSL_free(ssl->s3->tmp.certificate_types);
   OPENSSL_free(ssl->s3->tmp.peer_supported_group_list);
   OPENSSL_free(ssl->s3->tmp.peer_psk_identity_hint);
+  SSL_SESSION_free(ssl->s3->new_session);
+  SSL_SESSION_free(ssl->s3->established_session);
   ssl3_free_handshake_buffer(ssl);
   ssl3_free_handshake_hash(ssl);
   ssl_handshake_free(ssl->s3->hs);
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 4da5233..c161950 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -999,17 +999,25 @@
 uint32_t SSL_get_mode(const SSL *ssl) { return ssl->mode; }
 
 X509 *SSL_get_peer_certificate(const SSL *ssl) {
-  if (ssl == NULL || ssl->session == NULL || ssl->session->peer == NULL) {
+  if (ssl == NULL) {
     return NULL;
   }
-  return X509_up_ref(ssl->session->peer);
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL || session->peer == NULL) {
+    return NULL;
+  }
+  return X509_up_ref(session->peer);
 }
 
 STACK_OF(X509) *SSL_get_peer_cert_chain(const SSL *ssl) {
-  if (ssl == NULL || ssl->session == NULL) {
+  if (ssl == NULL) {
     return NULL;
   }
-  return ssl->session->cert_chain;
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL) {
+    return NULL;
+  }
+  return session->cert_chain;
 }
 
 int SSL_get_tls_unique(const SSL *ssl, uint8_t *out, size_t *out_len,
@@ -1019,7 +1027,7 @@
    * https://tools.ietf.org/html/rfc5929#section-3.1. */
   const uint8_t *finished = ssl->s3->previous_client_finished;
   size_t finished_len = ssl->s3->previous_client_finished_len;
-  if (ssl->hit) {
+  if (ssl->session != NULL) {
     /* tls-unique is broken for resumed sessions unless EMS is used. */
     if (!ssl->session->extended_master_secret) {
       goto err;
@@ -1447,13 +1455,14 @@
 uint16_t SSL_get_curve_id(const SSL *ssl) {
   /* TODO(davidben): This checks the wrong session if there is a renegotiation in
    * progress. */
-  if (ssl->session == NULL ||
-      ssl->session->cipher == NULL ||
-      !SSL_CIPHER_is_ECDHE(ssl->session->cipher)) {
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL ||
+      session->cipher == NULL ||
+      !SSL_CIPHER_is_ECDHE(session->cipher)) {
     return 0;
   }
 
-  return (uint16_t)ssl->session->key_exchange_info;
+  return (uint16_t)session->key_exchange_info;
 }
 
 int SSL_CTX_set_tmp_dh(SSL_CTX *ctx, const DH *dh) {
@@ -1679,18 +1688,19 @@
     return ssl->tlsext_hostname;
   }
 
-  if (ssl->session == NULL) {
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL) {
     return NULL;
   }
-  return ssl->session->tlsext_hostname;
+  return session->tlsext_hostname;
 }
 
 int SSL_get_servername_type(const SSL *ssl) {
-  if (ssl->session != NULL && ssl->session->tlsext_hostname != NULL) {
-    return TLSEXT_NAMETYPE_host_name;
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL || session->tlsext_hostname == NULL) {
+    return -1;
   }
-
-  return -1;
+  return TLSEXT_NAMETYPE_host_name;
 }
 
 void SSL_CTX_enable_signed_cert_timestamps(SSL_CTX *ctx) {
@@ -1713,7 +1723,7 @@
 
 void SSL_get0_signed_cert_timestamp_list(const SSL *ssl, const uint8_t **out,
                                          size_t *out_len) {
-  SSL_SESSION *session = ssl->session;
+  SSL_SESSION *session = SSL_get_session(ssl);
 
   *out_len = 0;
   *out = NULL;
@@ -1727,7 +1737,7 @@
 
 void SSL_get0_ocsp_response(const SSL *ssl, const uint8_t **out,
                             size_t *out_len) {
-  SSL_SESSION *session = ssl->session;
+  SSL_SESSION *session = SSL_get_session(ssl);
 
   *out_len = 0;
   *out = NULL;
@@ -2044,7 +2054,7 @@
 void ssl_update_cache(SSL *ssl, int mode) {
   SSL_CTX *ctx = ssl->initial_ctx;
   /* Never cache sessions with empty session IDs. */
-  if (ssl->session->session_id_length == 0 ||
+  if (ssl->s3->established_session->session_id_length == 0 ||
       (ctx->session_cache_mode & mode) != mode) {
     return;
   }
@@ -2056,14 +2066,16 @@
   /* A client may see new sessions on abbreviated handshakes if the server
    * decides to renew the ticket. Once the handshake is completed, it should be
    * inserted into the cache. */
-  if (!ssl->hit || (!ssl->server && ssl->tlsext_ticket_expected)) {
+  if (ssl->s3->established_session != ssl->session ||
+      (!ssl->server && ssl->tlsext_ticket_expected)) {
     if (use_internal_cache) {
-      SSL_CTX_add_session(ctx, ssl->session);
+      SSL_CTX_add_session(ctx, ssl->s3->established_session);
     }
     if (ctx->new_session_cb != NULL &&
-        !ctx->new_session_cb(ssl, SSL_SESSION_up_ref(ssl->session))) {
+        !ctx->new_session_cb(ssl, SSL_SESSION_up_ref(
+            ssl->s3->established_session))) {
       /* |new_session_cb|'s return value signals whether it took ownership. */
-      SSL_SESSION_free(ssl->session);
+      SSL_SESSION_free(ssl->s3->established_session);
     }
   }
 
@@ -2161,7 +2173,7 @@
 }
 
 int SSL_session_reused(const SSL *ssl) {
-  return ssl->hit;
+  return ssl->session != NULL;
 }
 
 const COMP_METHOD *SSL_get_current_compression(SSL *ssl) { return NULL; }
@@ -2384,13 +2396,14 @@
 unsigned SSL_get_dhe_group_size(const SSL *ssl) {
   /* TODO(davidben): This checks the wrong session if there is a renegotiation in
    * progress. */
-  if (ssl->session == NULL ||
-      ssl->session->cipher == NULL ||
-      !SSL_CIPHER_is_DHE(ssl->session->cipher)) {
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL ||
+      session->cipher == NULL ||
+      !SSL_CIPHER_is_DHE(session->cipher)) {
     return 0;
   }
 
-  return ssl->session->key_exchange_info;
+  return session->key_exchange_info;
 }
 
 int SSL_CTX_use_psk_identity_hint(SSL_CTX *ctx, const char *identity_hint) {
@@ -2445,11 +2458,14 @@
 }
 
 const char *SSL_get_psk_identity(const SSL *ssl) {
-  if (ssl == NULL || ssl->session == NULL) {
+  if (ssl == NULL) {
     return NULL;
   }
-
-  return ssl->session->psk_identity;
+  SSL_SESSION *session = SSL_get_session(ssl);
+  if (session == NULL) {
+    return NULL;
+  }
+  return session->psk_identity;
 }
 
 void SSL_set_psk_client_callback(
@@ -2871,8 +2887,6 @@
     ssl->session = NULL;
   }
 
-  ssl->hit = 0;
-
   /* SSL_clear may be called before or after the |ssl| is initialized in either
    * accept or connect state. In the latter case, SSL_clear should preserve the
    * half and reset |ssl->state| accordingly. */
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c
index 8e51a6a..0a9ccba 100644
--- a/ssl/ssl_session.c
+++ b/ssl/ssl_session.c
@@ -175,6 +175,95 @@
   return session;
 }
 
+SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int include_ticket) {
+  SSL_SESSION *new_session = SSL_SESSION_new();
+  if (new_session == NULL) {
+    goto err;
+  }
+
+  new_session->ssl_version = session->ssl_version;
+  new_session->key_exchange_info = session->key_exchange_info;
+  new_session->master_key_length = session->master_key_length;
+  memcpy(new_session->master_key, session->master_key,
+         session->master_key_length);
+  new_session->session_id_length = session->session_id_length;
+  memcpy(new_session->session_id, session->session_id,
+         session->session_id_length);
+  new_session->sid_ctx_length = session->sid_ctx_length;
+  memcpy(new_session->sid_ctx, session->sid_ctx, session->sid_ctx_length);
+  if (session->psk_identity != NULL) {
+    new_session->psk_identity = BUF_strdup(session->psk_identity);
+    if (new_session->psk_identity == NULL) {
+      goto err;
+    }
+  }
+  if (session->peer != NULL) {
+    new_session->peer = X509_up_ref(session->peer);
+  }
+  if (session->cert_chain != NULL) {
+    new_session->cert_chain = X509_chain_up_ref(session->cert_chain);
+    if (new_session->cert_chain == NULL) {
+      goto err;
+    }
+  }
+  new_session->verify_result = session->verify_result;
+  new_session->timeout = session->timeout;
+  new_session->time = session->time;
+  new_session->cipher = session->cipher;
+  /* The new_session does not get a copy of the ex_data. */
+  if (session->tlsext_hostname != NULL) {
+    new_session->tlsext_hostname = BUF_strdup(session->tlsext_hostname);
+    if (new_session->tlsext_hostname == NULL) {
+      goto err;
+    }
+  }
+  if (include_ticket) {
+    if (session->tlsext_tick != NULL) {
+      new_session->tlsext_tick = BUF_memdup(session->tlsext_tick,
+                                            session->tlsext_ticklen);
+      if (new_session->tlsext_tick == NULL) {
+        goto err;
+      }
+    }
+    new_session->tlsext_ticklen = session->tlsext_ticklen;
+  }
+
+  new_session->tlsext_signed_cert_timestamp_list_length =
+      session->tlsext_signed_cert_timestamp_list_length;
+  if (session->tlsext_signed_cert_timestamp_list != NULL) {
+    new_session->tlsext_signed_cert_timestamp_list =
+        BUF_memdup(session->tlsext_signed_cert_timestamp_list,
+                   session->tlsext_signed_cert_timestamp_list_length);
+    if (new_session->tlsext_signed_cert_timestamp_list == NULL) {
+      goto err;
+    }
+  }
+  new_session->ocsp_response_length = session->ocsp_response_length;
+  if (session->ocsp_response != NULL) {
+    new_session->ocsp_response = BUF_memdup(session->ocsp_response,
+                                            session->ocsp_response_length);
+    if (new_session->ocsp_response == NULL) {
+      goto err;
+    }
+  }
+  memcpy(new_session->peer_sha256, session->peer_sha256, SHA256_DIGEST_LENGTH);
+  memcpy(new_session->original_handshake_hash,
+         session->original_handshake_hash,
+         session->original_handshake_hash_len);
+  new_session->original_handshake_hash_len =
+      session->original_handshake_hash_len;
+  new_session->tlsext_tick_lifetime_hint = session->tlsext_tick_lifetime_hint;
+  new_session->extended_master_secret = session->extended_master_secret;
+  new_session->peer_sha256_valid = session->peer_sha256_valid;
+  new_session->not_resumable = 1;
+  return new_session;
+
+err:
+  SSL_SESSION_free(new_session);
+  OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+  return 0;
+}
+
 SSL_SESSION *SSL_SESSION_up_ref(SSL_SESSION *session) {
   if (session != NULL) {
     CRYPTO_refcount_inc(&session->references);
@@ -281,13 +370,21 @@
 
 SSL_SESSION *SSL_get_session(const SSL *ssl)
 {
-  /* aka SSL_get0_session; gets 0 objects, just returns a copy of the pointer */
+  /* Once the handshake completes we return the established session. Otherwise
+   * we return the intermediate session, either |session| (for resumption) or
+   * |new_session| if doing a full handshake. */
+  if (!SSL_in_init(ssl)) {
+    return ssl->s3->established_session;
+  }
+  if (ssl->s3->new_session != NULL) {
+    return ssl->s3->new_session;
+  }
   return ssl->session;
 }
 
 SSL_SESSION *SSL_get1_session(SSL *ssl) {
   /* variant of SSL_get_session: caller really gets something */
-  return SSL_SESSION_up_ref(ssl->session);
+  return SSL_SESSION_up_ref(SSL_get_session(ssl));
 }
 
 int SSL_SESSION_get_ex_new_index(long argl, void *argp,
@@ -358,10 +455,13 @@
   memcpy(session->sid_ctx, ssl->sid_ctx, ssl->sid_ctx_length);
   session->sid_ctx_length = ssl->sid_ctx_length;
 
+  /* The session is marked not resumable until it is completely filled in. */
+  session->not_resumable = 1;
   session->verify_result = X509_V_OK;
 
-  SSL_SESSION_free(ssl->session);
-  ssl->session = session;
+  SSL_SESSION_free(ssl->s3->new_session);
+  ssl->s3->new_session = session;
+  SSL_set_session(ssl, NULL);
   return 1;
 
 err:
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index f26962f..5dd689b 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -30,6 +30,7 @@
 #include <openssl/ssl.h>
 #include <openssl/x509.h>
 
+#include "internal.h"
 #include "test/scoped_types.h"
 #include "../crypto/test/test_util.h"
 
@@ -1253,6 +1254,48 @@
 
   return true;
 }
+static bool TestSessionDuplication() {
+  ScopedSSL_CTX client_ctx(SSL_CTX_new(TLS_method()));
+  ScopedSSL_CTX server_ctx(SSL_CTX_new(TLS_method()));
+  if (!client_ctx || !server_ctx) {
+    return false;
+  }
+
+  ScopedX509 cert = GetTestCertificate();
+  ScopedEVP_PKEY key = GetTestKey();
+  if (!cert || !key ||
+      !SSL_CTX_use_certificate(server_ctx.get(), cert.get()) ||
+      !SSL_CTX_use_PrivateKey(server_ctx.get(), key.get())) {
+    return false;
+  }
+
+  ScopedSSL client, server;
+  if (!ConnectClientAndServer(&client, &server, client_ctx.get(),
+                              server_ctx.get())) {
+    return false;
+  }
+
+  SSL_SESSION *session0 = SSL_get_session(client.get());
+  ScopedSSL_SESSION session1(SSL_SESSION_dup(session0, 1));
+  if (!session1) {
+    return false; 
+  }
+  
+  uint8_t *s0_bytes, *s1_bytes;
+  size_t s0_len, s1_len;
+
+  if (!SSL_SESSION_to_bytes(session0, &s0_bytes, &s0_len)) {
+    return false;
+  }
+  ScopedOpenSSLBytes free_s0(s0_bytes);
+
+  if (!SSL_SESSION_to_bytes(session1.get(), &s1_bytes, &s1_len)) {
+    return false;
+  }
+  ScopedOpenSSLBytes free_s1(s1_bytes);
+
+  return s0_len == s1_len && memcmp(s0_bytes, s1_bytes, s0_len) == 0;
+}
 
 static bool ExpectFDs(const SSL *ssl, int rfd, int wfd) {
   if (SSL_get_rfd(ssl) != rfd || SSL_get_wfd(ssl) != wfd) {
@@ -1501,6 +1544,7 @@
       !TestSequenceNumber(false /* TLS */) ||
       !TestSequenceNumber(true /* DTLS */) ||
       !TestOneSidedShutdown() ||
+      !TestSessionDuplication() ||
       !TestSetFD() ||
       !TestGetPeerCertificate() ||
       !TestRetainOnlySHA256OfCerts()) {
diff --git a/ssl/t1_enc.c b/ssl/t1_enc.c
index 74c5d41..4b8fe45 100644
--- a/ssl/t1_enc.c
+++ b/ssl/t1_enc.c
@@ -327,8 +327,8 @@
 
 int SSL_generate_key_block(const SSL *ssl, uint8_t *out, size_t out_len) {
   return ssl->s3->enc_method->prf(
-      ssl, out, out_len, ssl->session->master_key,
-      ssl->session->master_key_length, TLS_MD_KEY_EXPANSION_CONST,
+      ssl, out, out_len, SSL_get_session(ssl)->master_key,
+      SSL_get_session(ssl)->master_key_length, TLS_MD_KEY_EXPANSION_CONST,
       TLS_MD_KEY_EXPANSION_CONST_SIZE, ssl->s3->server_random, SSL3_RANDOM_SIZE,
       ssl->s3->client_random, SSL3_RANDOM_SIZE);
 }
@@ -338,12 +338,16 @@
     return 1;
   }
 
+  SSL_SESSION *session = ssl->session;
+  if (ssl->s3->new_session != NULL) {
+    session = ssl->s3->new_session;
+  }
+
   const EVP_AEAD *aead = NULL;
   size_t mac_secret_len, fixed_iv_len;
-  if (ssl->session->cipher == NULL ||
+  if (session->cipher == NULL ||
       !ssl_cipher_get_evp_aead(&aead, &mac_secret_len, &fixed_iv_len,
-                               ssl->session->cipher,
-                               ssl3_protocol_version(ssl))) {
+                               session->cipher, ssl3_protocol_version(ssl))) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_CIPHER_OR_HASH_UNAVAILABLE);
     return 0;
   }
@@ -454,8 +458,8 @@
 
   static const size_t kFinishedLen = 12;
   if (!ssl->s3->enc_method->prf(ssl, out, kFinishedLen,
-                                ssl->session->master_key,
-                                ssl->session->master_key_length, label,
+                                SSL_get_session(ssl)->master_key,
+                                SSL_get_session(ssl)->master_key_length, label,
                                 label_len, buf, digests_len, NULL, 0)) {
     return 0;
   }
@@ -529,8 +533,9 @@
   }
 
   int ret =
-      ssl->s3->enc_method->prf(ssl, out, out_len, ssl->session->master_key,
-                               ssl->session->master_key_length, label,
+      ssl->s3->enc_method->prf(ssl, out, out_len,
+                               SSL_get_session(ssl)->master_key,
+                               SSL_get_session(ssl)->master_key_length, label,
                                label_len, seed, seed_len, NULL, 0);
   OPENSSL_free(seed);
   return ret;
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index b1e3b13..6b0c16a 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -713,10 +713,10 @@
 
   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) {
+  if (ssl->session == NULL) {
+    assert(ssl->s3->new_session->tlsext_hostname == NULL);
+    ssl->s3->new_session->tlsext_hostname = BUF_strdup(ssl->tlsext_hostname);
+    if (!ssl->s3->new_session->tlsext_hostname) {
       *out_alert = SSL_AD_INTERNAL_ERROR;
       return 0;
     }
@@ -759,11 +759,11 @@
   /* TODO(davidben): SNI should be resolved before resumption. We have the
    * early callback as a replacement, but we should fix the current callback
    * and avoid the need for |SSL_CTX_set_session_id_context|. */
-  if (!ssl->hit) {
-    assert(ssl->session->tlsext_hostname == NULL);
+  if (ssl->session == NULL) {
+    assert(ssl->s3->new_session->tlsext_hostname == NULL);
 
     /* Copy the hostname as a string. */
-    if (!CBS_strdup(&host_name, &ssl->session->tlsext_hostname)) {
+    if (!CBS_strdup(&host_name, &ssl->s3->new_session->tlsext_hostname)) {
       *out_alert = SSL_AD_INTERNAL_ERROR;
       return 0;
     }
@@ -775,9 +775,9 @@
 }
 
 static int ext_sni_add_serverhello(SSL *ssl, CBB *out) {
-  if (ssl->hit ||
+  if (ssl->session != NULL ||
       !ssl->s3->tmp.should_ack_sni ||
-      ssl->session->tlsext_hostname == NULL) {
+      ssl->s3->new_session->tlsext_hostname == NULL) {
     return 1;
   }
 
@@ -1210,8 +1210,8 @@
     return 0;
   }
 
-  if (!CBS_stow(&ocsp_response, &ssl->session->ocsp_response,
-                &ssl->session->ocsp_response_length)) {
+  if (!CBS_stow(&ocsp_response, &ssl->s3->new_session->ocsp_response,
+                &ssl->s3->new_session->ocsp_response_length)) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     return 0;
   }
@@ -1246,7 +1246,7 @@
 
   if (ssl3_protocol_version(ssl) < TLS1_3_VERSION) {
     /* The extension shouldn't be sent when resuming sessions. */
-    if (ssl->hit) {
+    if (ssl->session != NULL) {
       return 1;
     }
 
@@ -1436,9 +1436,11 @@
   }
 
   /* Session resumption uses the original session information. */
-  if (!ssl->hit &&
-      !CBS_stow(contents, &ssl->session->tlsext_signed_cert_timestamp_list,
-                &ssl->session->tlsext_signed_cert_timestamp_list_length)) {
+  if (ssl->session == NULL &&
+      !CBS_stow(
+          contents,
+          &ssl->s3->new_session->tlsext_signed_cert_timestamp_list,
+          &ssl->s3->new_session->tlsext_signed_cert_timestamp_list_length)) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     return 0;
   }
@@ -1453,7 +1455,7 @@
 
 static int ext_sct_add_serverhello(SSL *ssl, CBB *out) {
   /* The extension shouldn't be sent when resuming sessions. */
-  if (ssl->hit ||
+  if (ssl->session != NULL ||
       ssl->ctx->signed_cert_timestamp_list_length == 0) {
     return 1;
   }
@@ -2976,7 +2978,7 @@
   static const char kClientIDMagic[] = "TLS Channel ID signature";
   EVP_DigestUpdate(&ctx, kClientIDMagic, sizeof(kClientIDMagic));
 
-  if (ssl->hit) {
+  if (ssl->session != NULL) {
     static const char kResumptionMagic[] = "Resumption";
     EVP_DigestUpdate(&ctx, kResumptionMagic, sizeof(kResumptionMagic));
     if (ssl->session->original_handshake_hash_len == 0) {
@@ -3006,25 +3008,26 @@
 }
 
 /* tls1_record_handshake_hashes_for_channel_id records the current handshake
- * hashes in |ssl->session| so that Channel ID resumptions can sign that
+ * hashes in |ssl->s3->new_session| so that Channel ID resumptions can sign that
  * data. */
 int tls1_record_handshake_hashes_for_channel_id(SSL *ssl) {
   int digest_len;
   /* This function should never be called for a resumed session because the
    * handshake hashes that we wish to record are for the original, full
    * handshake. */
-  if (ssl->hit) {
+  if (ssl->session != NULL) {
     return -1;
   }
 
   digest_len =
-      tls1_handshake_digest(ssl, ssl->session->original_handshake_hash,
-                            sizeof(ssl->session->original_handshake_hash));
+      tls1_handshake_digest(
+          ssl, ssl->s3->new_session->original_handshake_hash,
+          sizeof(ssl->s3->new_session->original_handshake_hash));
   if (digest_len < 0) {
     return -1;
   }
 
-  ssl->session->original_handshake_hash_len = digest_len;
+  ssl->s3->new_session->original_handshake_hash_len = digest_len;
 
   return 1;
 }
diff --git a/ssl/tls13_both.c b/ssl/tls13_both.c
index aa11b2b..7b276df 100644
--- a/ssl/tls13_both.c
+++ b/ssl/tls13_both.c
@@ -194,9 +194,9 @@
   int ret = 0;
   uint8_t alert;
   STACK_OF(X509) *chain = ssl_parse_cert_chain(
-      ssl, &alert,
-      ssl->ctx->retain_only_sha256_of_client_certs ? ssl->session->peer_sha256
-                                                   : NULL,
+      ssl, &alert, ssl->ctx->retain_only_sha256_of_client_certs
+                       ? ssl->s3->new_session->peer_sha256
+                       : NULL,
       &cbs);
   if (chain == NULL) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
@@ -232,7 +232,7 @@
 
   if (ssl->server && ssl->ctx->retain_only_sha256_of_client_certs) {
     /* The hash was filled in by |ssl_parse_cert_chain|. */
-    ssl->session->peer_sha256_valid = 1;
+    ssl->s3->new_session->peer_sha256_valid = 1;
   }
 
   X509 *leaf = sk_X509_value(chain, 0);
@@ -251,19 +251,19 @@
   }
   ERR_clear_error();
 
-  ssl->session->verify_result = ssl->verify_result;
+  ssl->s3->new_session->verify_result = ssl->verify_result;
 
-  X509_free(ssl->session->peer);
+  X509_free(ssl->s3->new_session->peer);
   /* For historical reasons, the client and server differ on whether the chain
    * includes the leaf. */
   if (ssl->server) {
-    ssl->session->peer = sk_X509_shift(chain);
+    ssl->s3->new_session->peer = sk_X509_shift(chain);
   } else {
-    ssl->session->peer = X509_up_ref(leaf);
+    ssl->s3->new_session->peer = X509_up_ref(leaf);
   }
 
-  sk_X509_pop_free(ssl->session->cert_chain, X509_free);
-  ssl->session->cert_chain = chain;
+  sk_X509_pop_free(ssl->s3->new_session->cert_chain, X509_free);
+  ssl->s3->new_session->cert_chain = chain;
   chain = NULL;
 
   ret = 1;
@@ -275,7 +275,7 @@
 
 int tls13_process_certificate_verify(SSL *ssl) {
   int ret = 0;
-  X509 *peer = ssl->session->peer;
+  X509 *peer = ssl->s3->new_session->peer;
   EVP_PKEY *pkey = NULL;
   uint8_t *msg = NULL;
   size_t msg_len;
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c
index 742dcd8..c38358d 100644
--- a/ssl/tls13_client.c
+++ b/ssl/tls13_client.c
@@ -183,7 +183,7 @@
   assert(ssl->s3->have_version);
   memcpy(ssl->s3->server_random, CBS_data(&server_random), SSL3_RANDOM_SIZE);
 
-  ssl->hit = 0;
+  SSL_set_session(ssl, NULL);
   if (!ssl_get_new_session(ssl, 0)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     return ssl_hs_error;
@@ -207,7 +207,7 @@
     return ssl_hs_error;
   }
 
-  ssl->session->cipher = cipher;
+  ssl->s3->new_session->cipher = cipher;
   ssl->s3->tmp.new_cipher = cipher;
 
   /* The PRF hash is now known. Set up the key schedule. */
diff --git a/ssl/tls13_enc.c b/ssl/tls13_enc.c
index e0f7d39..1f4fe21 100644
--- a/ssl/tls13_enc.c
+++ b/ssl/tls13_enc.c
@@ -176,7 +176,7 @@
   const EVP_MD *digest = ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl));
   size_t mac_secret_len, fixed_iv_len;
   if (!ssl_cipher_get_evp_aead(&aead, &mac_secret_len, &fixed_iv_len,
-                               ssl->session->cipher,
+                               ssl->s3->new_session->cipher,
                                ssl3_protocol_version(ssl))) {
     return 0;
   }
@@ -206,11 +206,9 @@
     return 0;
   }
 
-  SSL_AEAD_CTX *traffic_aead = SSL_AEAD_CTX_new(direction,
-                                                ssl3_protocol_version(ssl),
-                                                ssl->session->cipher,
-                                                key, key_len, NULL, 0,
-                                                iv, iv_len);
+  SSL_AEAD_CTX *traffic_aead = SSL_AEAD_CTX_new(
+      direction, ssl3_protocol_version(ssl), ssl->s3->new_session->cipher, key,
+      key_len, NULL, 0, iv, iv_len);
   if (traffic_aead == NULL) {
     return 0;
   }
@@ -276,12 +274,12 @@
   SSL_HANDSHAKE *hs = ssl->s3->hs;
 
   ssl->s3->exporter_secret_len = hs->hash_len;
-  ssl->session->master_key_length = hs->hash_len;
+  ssl->s3->new_session->master_key_length = hs->hash_len;
   if (!derive_secret(
           ssl, ssl->s3->exporter_secret, ssl->s3->exporter_secret_len,
           (const uint8_t *)kTLS13LabelExporter, strlen(kTLS13LabelExporter)) ||
-      !derive_secret(ssl, ssl->session->master_key,
-                     ssl->session->master_key_length,
+      !derive_secret(ssl, ssl->s3->new_session->master_key,
+                     ssl->s3->new_session->master_key_length,
                      (const uint8_t *)kTLS13LabelResumption,
                      strlen(kTLS13LabelResumption))) {
     return 0;
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index a3517ba..cf0aea2 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -136,7 +136,7 @@
   /* Load the client random. */
   memcpy(ssl->s3->client_random, CBS_data(&client_random), SSL3_RANDOM_SIZE);
 
-  ssl->hit = 0;
+  SSL_set_session(ssl, NULL);
   if (!ssl_get_new_session(ssl, 1 /* server */)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     return ssl_hs_error;
@@ -212,7 +212,7 @@
     return ssl_hs_error;
   }
 
-  ssl->session->cipher = cipher;
+  ssl->s3->new_session->cipher = cipher;
   ssl->s3->tmp.new_cipher = cipher;
 
   ssl->method->received_flight(ssl);
@@ -470,7 +470,7 @@
 
 static enum ssl_hs_wait_t do_process_client_certificate_verify(
     SSL *ssl, SSL_HANDSHAKE *hs) {
-  if (ssl->session->peer == NULL) {
+  if (ssl->s3->new_session->peer == NULL) {
     /* Skip this state. */
     hs->state = state_process_client_finished;
     return ssl_hs_ok;