Adding code to send session as PSK Identity.

BUG=75

Change-Id: Ied864cfccbc0e68d71c55c5ab563da27b7253463
Reviewed-on: https://boringssl-review.googlesource.com/9043
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/ssl/handshake_client.c b/ssl/handshake_client.c
index 34c1adc..d7b1a2c 100644
--- a/ssl/handshake_client.c
+++ b/ssl/handshake_client.c
@@ -514,7 +514,7 @@
            * 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 */);
+              SSL_SESSION_dup(ssl->s3->new_session, SSL_SESSION_DUP_ALL);
           if (ssl->s3->established_session == NULL) {
             /* Do not stay in SSL_ST_OK, to avoid confusing |SSL_in_init|
              * callers. */
@@ -605,6 +605,16 @@
     if (!CBB_add_u16(&child, ssl_cipher_get_value(cipher))) {
       return 0;
     }
+    /* Add PSK ciphers for TLS 1.3 resumption. */
+    if (ssl->session != NULL &&
+        ssl->method->version_from_wire(ssl->session->ssl_version) >=
+            TLS1_3_VERSION) {
+      uint16_t resumption_cipher;
+      if (ssl_cipher_get_ecdhe_psk_cipher(cipher, &resumption_cipher) &&
+          !CBB_add_u16(&child, resumption_cipher)) {
+        return 0;
+      }
+    }
   }
 
   /* If all ciphers were disabled, return the error to the caller. */
@@ -708,10 +718,10 @@
   if (ssl->session != NULL) {
     uint16_t session_version =
         ssl->method->version_from_wire(ssl->session->ssl_version);
-    struct timeval now;
-    ssl_get_current_time(ssl, &now);
-    if (ssl->session->session_id_length == 0 || ssl->session->not_resumable ||
-        ssl->session->timeout < (long)now.tv_sec - ssl->session->time ||
+    if ((session_version < TLS1_3_VERSION &&
+         ssl->session->session_id_length == 0) ||
+        ssl->session->not_resumable ||
+        !ssl_session_is_time_valid(ssl, ssl->session) ||
         session_version < min_version || session_version > max_version) {
       SSL_set_session(ssl, NULL);
     }
@@ -885,18 +895,11 @@
     goto f_err;
   }
 
-  assert(ssl->session == NULL || ssl->session->session_id_length > 0);
   if (!ssl->s3->initial_handshake_complete && ssl->session != NULL &&
+      ssl->session->session_id_length != 0 &&
       CBS_mem_equal(&session_id, ssl->session->session_id,
                     ssl->session->session_id_length)) {
-    if (ssl->sid_ctx_length != ssl->session->sid_ctx_length ||
-        memcmp(ssl->session->sid_ctx, ssl->sid_ctx, ssl->sid_ctx_length)) {
-      /* actually a client application bug */
-      al = SSL_AD_ILLEGAL_PARAMETER;
-      OPENSSL_PUT_ERROR(SSL,
-                        SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
-      goto f_err;
-    }
+    ssl->s3->session_reused = 1;
   } else {
     /* The session wasn't resumed. Create a fresh SSL_SESSION to
      * fill out. */
@@ -946,6 +949,13 @@
       OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED);
       goto f_err;
     }
+    if (!ssl_session_is_context_valid(ssl, ssl->session)) {
+      /* This is actually a client application bug. */
+      al = SSL_AD_ILLEGAL_PARAMETER;
+      OPENSSL_PUT_ERROR(SSL,
+                        SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
+      goto f_err;
+    }
   } else {
     ssl->s3->new_session->cipher = c;
   }
@@ -1935,8 +1945,7 @@
     /* 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. */
-    session = SSL_SESSION_dup(ssl->session,
-                              0 /* Don't duplicate session ticket */);
+    session = SSL_SESSION_dup(ssl->session, SSL_SESSION_INCLUDE_NONAUTH);
     if (session == NULL) {
       /* This should never happen. */
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
diff --git a/ssl/handshake_server.c b/ssl/handshake_server.c
index 4e7aae2..4818fe5 100644
--- a/ssl/handshake_server.c
+++ b/ssl/handshake_server.c
@@ -713,6 +713,7 @@
       ssl->session = session;
       session = NULL;
       ssl->verify_result = ssl->session->verify_result;
+      ssl->s3->session_reused = 1;
     } else {
       SSL_set_session(ssl, NULL);
       if (!ssl_get_new_session(ssl, 1 /* server */)) {
diff --git a/ssl/internal.h b/ssl/internal.h
index 4fcf4b9..ade9416 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -241,6 +241,11 @@
 /* ssl_cipher_get_value returns the cipher suite id of |cipher|. */
 uint16_t ssl_cipher_get_value(const SSL_CIPHER *cipher);
 
+/* ssl_cipher_get_resumption_cipher returns the cipher suite id of the cipher
+ * matching |cipher| with PSK enabled. */
+int ssl_cipher_get_ecdhe_psk_cipher(const SSL_CIPHER *cipher,
+                                    uint16_t *out_cipher);
+
 /* ssl_cipher_get_key_type returns the |EVP_PKEY_*| value corresponding to the
  * server key used in |cipher| or |EVP_PKEY_NONE| if there is none. */
 int ssl_cipher_get_key_type(const SSL_CIPHER *cipher);
@@ -848,6 +853,18 @@
  * 0 for the Client Finished. */
 int tls13_finished_mac(SSL *ssl, uint8_t *out, size_t *out_len, int is_server);
 
+/* tls13_resumption_psk calculates the PSK to use for the resumption of
+ * |session| and stores the result in |out|. It returns one on success, and
+ * zero on failure. */
+int tls13_resumption_psk(SSL *ssl, uint8_t *out, size_t out_len,
+                         const SSL_SESSION *session);
+
+/* tls13_resumption_context derives the context to be used for the handshake
+ * transcript on the resumption of |session|. It returns one on success, and
+ * zero on failure. */
+int tls13_resumption_context(SSL *ssl, uint8_t *out, size_t out_len,
+                             const SSL_SESSION *session);
+
 
 /* Handshake functions. */
 
@@ -938,6 +955,13 @@
                                         uint8_t *out_alert, CBS *contents);
 int ssl_ext_key_share_add_serverhello(SSL *ssl, CBB *out);
 
+int ssl_ext_pre_shared_key_parse_serverhello(SSL *ssl, uint8_t *out_alert,
+                                             CBS *contents);
+int ssl_ext_pre_shared_key_parse_clienthello(SSL *ssl,
+                                             SSL_SESSION **out_session,
+                                             uint8_t *out_alert, CBS *contents);
+int ssl_ext_pre_shared_key_add_serverhello(SSL *ssl, CBB *out);
+
 int ssl_add_client_hello_body(SSL *ssl, CBB *body);
 
 
@@ -1232,6 +1256,14 @@
 int ssl_get_new_session(SSL *ssl, int is_server);
 int ssl_encrypt_ticket(SSL *ssl, CBB *out, const SSL_SESSION *session);
 
+/* ssl_session_is_context_valid returns one if |session|'s session ID context
+ * matches the one set on |ssl| and zero otherwise. */
+int ssl_session_is_context_valid(const SSL *ssl, const SSL_SESSION *session);
+
+/* ssl_session_is_time_valid returns one if |session| is still valid and zero if
+ * it has expired. */
+int ssl_session_is_time_valid(const SSL *ssl, const SSL_SESSION *session);
+
 enum ssl_session_result_t {
   ssl_session_success,
   ssl_session_error,
@@ -1248,11 +1280,18 @@
     SSL *ssl, SSL_SESSION **out_session, int *out_send_ticket,
     const struct ssl_early_callback_ctx *ctx);
 
+/* The following flags determine which parts of the session are duplicated. */
+#define SSL_SESSION_DUP_AUTH_ONLY 0x0
+#define SSL_SESSION_INCLUDE_TICKET 0x1
+#define SSL_SESSION_INCLUDE_NONAUTH 0x2
+#define SSL_SESSION_DUP_ALL \
+  (SSL_SESSION_INCLUDE_TICKET | SSL_SESSION_INCLUDE_NONAUTH)
+
 /* 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);
+                                            int dup_flags);
 
 void ssl_cipher_preference_list_free(
     struct ssl_cipher_preference_list_st *cipher_list);
diff --git a/ssl/ssl_cipher.c b/ssl/ssl_cipher.c
index 3810667..079c823 100644
--- a/ssl/ssl_cipher.c
+++ b/ssl/ssl_cipher.c
@@ -1660,6 +1660,30 @@
   return id & 0xffff;
 }
 
+int ssl_cipher_get_ecdhe_psk_cipher(const SSL_CIPHER *cipher,
+                                    uint16_t *out_cipher) {
+  switch (cipher->id) {
+    case TLS1_CK_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256:
+    case TLS1_CK_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256:
+    case TLS1_CK_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256:
+      *out_cipher = TLS1_CK_ECDHE_PSK_WITH_CHACHA20_POLY1305_SHA256 & 0xffff;
+      return 1;
+
+    case TLS1_CK_ECDHE_RSA_WITH_AES_128_GCM_SHA256:
+    case TLS1_CK_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256:
+    case TLS1_CK_ECDHE_PSK_WITH_AES_128_GCM_SHA256:
+      *out_cipher = TLS1_CK_ECDHE_PSK_WITH_AES_128_GCM_SHA256 & 0xffff;
+      return 1;
+
+    case TLS1_CK_ECDHE_RSA_WITH_AES_256_GCM_SHA384:
+    case TLS1_CK_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384:
+    case TLS1_CK_ECDHE_PSK_WITH_AES_256_GCM_SHA384:
+      *out_cipher = TLS1_CK_ECDHE_PSK_WITH_AES_256_GCM_SHA384 & 0xffff;
+      return 1;
+  }
+  return 0;
+}
+
 int SSL_CIPHER_is_AES(const SSL_CIPHER *cipher) {
   return (cipher->algorithm_enc & SSL_AES) != 0;
 }
diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c
index 1a950ad..e7830a1 100644
--- a/ssl/ssl_lib.c
+++ b/ssl/ssl_lib.c
@@ -2162,7 +2162,7 @@
 }
 
 int SSL_session_reused(const SSL *ssl) {
-  return ssl->session != NULL;
+  return ssl->s3->session_reused;
 }
 
 const COMP_METHOD *SSL_get_current_compression(SSL *ssl) { return NULL; }
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c
index 1d634d8..e26866b 100644
--- a/ssl/ssl_session.c
+++ b/ssl/ssl_session.c
@@ -175,22 +175,23 @@
   return session;
 }
 
-SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int include_ticket) {
+SSL_SESSION *SSL_SESSION_dup(SSL_SESSION *session, int dup_flags) {
   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->sid_ctx_length = session->sid_ctx_length;
+  memcpy(new_session->sid_ctx, session->sid_ctx, session->sid_ctx_length);
+
+  /* Copy the key material. */
   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);
+  new_session->cipher = session->cipher;
+
+  /* Copy authentication state. */
   if (session->psk_identity != NULL) {
     new_session->psk_identity = BUF_strdup(session->psk_identity);
     if (new_session->psk_identity == NULL) {
@@ -208,26 +209,15 @@
     }
   }
   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) {
+
+  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;
     }
   }
-  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;
@@ -239,25 +229,52 @@
       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->ticket_flags = session->ticket_flags;
-  new_session->ticket_age_add = session->ticket_age_add;
-  new_session->extended_master_secret = session->extended_master_secret;
   new_session->peer_sha256_valid = session->peer_sha256_valid;
+
+  /* Copy non-authentication connection properties. */
+  if (dup_flags & SSL_SESSION_INCLUDE_NONAUTH) {
+    new_session->session_id_length = session->session_id_length;
+    memcpy(new_session->session_id, session->session_id,
+           session->session_id_length);
+
+    new_session->key_exchange_info = session->key_exchange_info;
+    new_session->timeout = session->timeout;
+    new_session->time = session->time;
+
+    if (session->tlsext_hostname != NULL) {
+      new_session->tlsext_hostname = BUF_strdup(session->tlsext_hostname);
+      if (new_session->tlsext_hostname == NULL) {
+        goto err;
+      }
+    }
+
+    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->ticket_flags = session->ticket_flags;
+    new_session->ticket_age_add = session->ticket_age_add;
+    new_session->extended_master_secret = session->extended_master_secret;
+  }
+
+  /* Copy the ticket. */
+  if (dup_flags & SSL_SESSION_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;
+  }
+
+  /* The new_session does not get a copy of the ex_data. */
+
   new_session->not_resumable = 1;
   return new_session;
 
@@ -564,6 +581,25 @@
   return ret;
 }
 
+int ssl_session_is_context_valid(const SSL *ssl, const SSL_SESSION *session) {
+  if (session == NULL) {
+    return 0;
+  }
+
+  return session->sid_ctx_length == ssl->sid_ctx_length &&
+         memcmp(session->sid_ctx, ssl->sid_ctx, ssl->sid_ctx_length) == 0;
+}
+
+int ssl_session_is_time_valid(const SSL *ssl, const SSL_SESSION *session) {
+  if (session == NULL) {
+    return 0;
+  }
+
+  struct timeval now;
+  ssl_get_current_time(ssl, &now);
+  return session->timeout >= (long)now.tv_sec - session->time;
+}
+
 /* ssl_lookup_session looks up |session_id| in the session cache and sets
  * |*out_session| to an |SSL_SESSION| object if found. The caller takes
  * ownership of the result. */
@@ -576,7 +612,7 @@
     return ssl_session_success;
   }
 
-  SSL_SESSION *session;
+  SSL_SESSION *session = NULL;
   /* Try the internal cache, if it exists. */
   if (!(ssl->initial_ctx->session_cache_mode &
         SSL_SESS_CACHE_NO_INTERNAL_LOOKUP)) {
@@ -592,39 +628,48 @@
     }
     /* TODO(davidben): This should probably move it to the front of the list. */
     CRYPTO_MUTEX_unlock_read(&ssl->initial_ctx->lock);
-
-    if (session != NULL) {
-      *out_session = session;
-      return ssl_session_success;
-    }
   }
 
   /* Fall back to the external cache, if it exists. */
-  if (ssl->initial_ctx->get_session_cb == NULL) {
-    return ssl_session_success;
+  if (session == NULL &&
+      ssl->initial_ctx->get_session_cb != NULL) {
+    int copy = 1;
+    session = ssl->initial_ctx->get_session_cb(ssl, (uint8_t *)session_id,
+                                               session_id_len, &copy);
+
+    if (session == SSL_magic_pending_session_ptr()) {
+      return ssl_session_retry;
+    }
+
+    /* Increment reference count now if the session callback asks us to do so
+     * (note that if the session structures returned by the callback are shared
+     * between threads, it must handle the reference count itself [i.e. copy ==
+     * 0], or things won't be thread-safe). */
+    if (copy) {
+      SSL_SESSION_up_ref(session);
+    }
+
+    /* Add the externally cached session to the internal cache if necessary. */
+    if (session != NULL &&
+        !(ssl->initial_ctx->session_cache_mode &
+          SSL_SESS_CACHE_NO_INTERNAL_STORE)) {
+      SSL_CTX_add_session(ssl->initial_ctx, session);
+    }
   }
-  int copy = 1;
-  session = ssl->initial_ctx->get_session_cb(ssl, (uint8_t *)session_id,
-                                             session_id_len, &copy);
+
   if (session == NULL) {
     return ssl_session_success;
   }
-  if (session == SSL_magic_pending_session_ptr()) {
-    return ssl_session_retry;
-  }
 
-  /* Increment reference count now if the session callback asks us to do so
-   * (note that if the session structures returned by the callback are shared
-   * between threads, it must handle the reference count itself [i.e. copy ==
-   * 0], or things won't be thread-safe). */
-  if (copy) {
-    SSL_SESSION_up_ref(session);
-  }
-
-  /* Add the externally cached session to the internal cache if necessary. */
-  if (!(ssl->initial_ctx->session_cache_mode &
-        SSL_SESS_CACHE_NO_INTERNAL_STORE)) {
-    SSL_CTX_add_session(ssl->initial_ctx, session);
+  if (!ssl_session_is_context_valid(ssl, session)) {
+    /* The client did not offer a suitable ticket or session ID. */
+    SSL_SESSION_free(session);
+    session = NULL;
+  } else if (!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_SESSION_free(session);
+    session = NULL;
   }
 
   *out_session = session;
@@ -647,7 +692,6 @@
       ssl->version > SSL3_VERSION &&
       SSL_early_callback_ctx_extension_get(ctx, TLSEXT_TYPE_session_ticket,
                                            &ticket, &ticket_len);
-  int from_cache = 0;
   if (tickets_supported && ticket_len > 0) {
     if (!tls_process_ticket(ssl, &session, &renew_ticket, ticket, ticket_len,
                             ctx->session_id, ctx->session_id_len)) {
@@ -660,35 +704,14 @@
     if (lookup_ret != ssl_session_success) {
       return lookup_ret;
     }
-    from_cache = 1;
-  }
-
-  if (session == NULL ||
-      session->sid_ctx_length != ssl->sid_ctx_length ||
-      memcmp(session->sid_ctx, ssl->sid_ctx, ssl->sid_ctx_length) != 0) {
-    /* The client did not offer a suitable ticket or session ID. If supported,
-     * the new session should use a ticket. */
-    goto no_session;
-  }
-
-  struct timeval now;
-  ssl_get_current_time(ssl, &now);
-  if (session->timeout < (long)now.tv_sec - session->time) {
-    if (from_cache) {
-      /* The session was from the cache, so remove it. */
-      SSL_CTX_remove_session(ssl->initial_ctx, session);
-    }
-    goto no_session;
   }
 
   *out_session = session;
-  *out_send_ticket = renew_ticket;
-  return ssl_session_success;
-
-no_session:
-  *out_session = NULL;
-  *out_send_ticket = tickets_supported;
-  SSL_SESSION_free(session);
+  if (session != NULL) {
+    *out_send_ticket = renew_ticket;
+  } else {
+    *out_send_ticket = tickets_supported;
+  }
   return ssl_session_success;
 }
 
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 4c4f6d9..f73c37d 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -1301,7 +1301,7 @@
   }
 
   SSL_SESSION *session0 = SSL_get_session(client.get());
-  ScopedSSL_SESSION session1(SSL_SESSION_dup(session0, 1));
+  ScopedSSL_SESSION session1(SSL_SESSION_dup(session0, SSL_SESSION_DUP_ALL));
   if (!session1) {
     return false;
   }
@@ -1796,11 +1796,6 @@
   static const uint8_t kContext2[] = {2};
 
   for (uint16_t version : kVersions) {
-    // TODO(davidben): Enable this when TLS 1.3 resumption is implemented.
-    if (version == TLS1_3_VERSION) {
-      continue;
-    }
-
     ScopedSSL_CTX server_ctx(SSL_CTX_new(TLS_method()));
     ScopedSSL_CTX client_ctx(SSL_CTX_new(TLS_method()));
     if (!server_ctx || !client_ctx ||
@@ -1864,11 +1859,6 @@
   }
 
   for (uint16_t version : kVersions) {
-    // TODO(davidben): Enable this when TLS 1.3 resumption is implemented.
-    if (version == TLS1_3_VERSION) {
-      continue;
-    }
-
     ScopedSSL_CTX server_ctx(SSL_CTX_new(TLS_method()));
     ScopedSSL_CTX client_ctx(SSL_CTX_new(TLS_method()));
     if (!server_ctx || !client_ctx ||
diff --git a/ssl/t1_lib.c b/ssl/t1_lib.c
index bb345b4..dc6c6dd 100644
--- a/ssl/t1_lib.c
+++ b/ssl/t1_lib.c
@@ -1030,7 +1030,10 @@
    * without upstream's 3c3f0259238594d77264a78944d409f2127642c4. */
   if (!ssl->s3->initial_handshake_complete &&
       ssl->session != NULL &&
-      ssl->session->tlsext_tick != NULL) {
+      ssl->session->tlsext_tick != NULL &&
+      /* Don't send TLS 1.3 session tickets in the ticket extension. */
+      ssl->method->version_from_wire(ssl->session->ssl_version) <
+          TLS1_3_VERSION) {
     ticket_data = ssl->session->tlsext_tick;
     ticket_len = ssl->session->tlsext_ticklen;
   }
@@ -1428,7 +1431,7 @@
   }
 
   /* Session resumption uses the original session information. */
-  if (ssl->session == NULL &&
+  if (!ssl->s3->session_reused &&
       !CBS_stow(
           contents,
           &ssl->s3->new_session->tlsext_signed_cert_timestamp_list,
@@ -1447,7 +1450,7 @@
 
 static int ext_sct_add_serverhello(SSL *ssl, CBB *out) {
   /* The extension shouldn't be sent when resuming sessions. */
-  if (ssl->session != NULL ||
+  if (ssl->s3->session_reused ||
       ssl->ctx->signed_cert_timestamp_list_length == 0) {
     return 1;
   }
@@ -1972,6 +1975,89 @@
 }
 
 
+/* Pre Shared Key
+ *
+ * https://tools.ietf.org/html/draft-ietf-tls-tls13-14 */
+
+static int ext_pre_shared_key_add_clienthello(SSL *ssl, CBB *out) {
+  uint16_t min_version, max_version;
+  if (!ssl_get_version_range(ssl, &min_version, &max_version)) {
+    return 0;
+  }
+
+  if (max_version < TLS1_3_VERSION || ssl->session == NULL ||
+      ssl->method->version_from_wire(ssl->session->ssl_version) <
+          TLS1_3_VERSION) {
+    return 1;
+  }
+
+  CBB contents, identities, identity;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_pre_shared_key) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      !CBB_add_u16_length_prefixed(&contents, &identities) ||
+      !CBB_add_u16_length_prefixed(&identities, &identity) ||
+      !CBB_add_bytes(&identity, ssl->session->tlsext_tick,
+                     ssl->session->tlsext_ticklen)) {
+    return 0;
+  }
+
+  return CBB_flush(out);
+}
+
+int ssl_ext_pre_shared_key_parse_serverhello(SSL *ssl, uint8_t *out_alert,
+                                             CBS *contents) {
+  uint16_t psk_id;
+  if (!CBS_get_u16(contents, &psk_id) ||
+      CBS_len(contents) != 0) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return 0;
+  }
+
+  if (psk_id != 0) {
+    *out_alert = SSL_AD_UNKNOWN_PSK_IDENTITY;
+    return 0;
+  }
+
+  return 1;
+}
+
+int ssl_ext_pre_shared_key_parse_clienthello(SSL *ssl,
+                                             SSL_SESSION **out_session,
+                                             uint8_t *out_alert,
+                                             CBS *contents) {
+  CBS identities, identity;
+  if (!CBS_get_u16_length_prefixed(contents, &identities) ||
+      !CBS_get_u16_length_prefixed(&identities, &identity) ||
+      CBS_len(contents) != 0) {
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return 0;
+  }
+
+  /* TLS 1.3 session tickets are renewed separately as part of the
+   * NewSessionTicket. */
+  int renew;
+  return tls_process_ticket(ssl, out_session, &renew, CBS_data(&identity),
+                            CBS_len(&identity), NULL, 0);
+}
+
+int ssl_ext_pre_shared_key_add_serverhello(SSL *ssl, CBB *out) {
+  if (!ssl->s3->session_reused) {
+    return 1;
+  }
+
+  CBB contents;
+  if (!CBB_add_u16(out, TLSEXT_TYPE_pre_shared_key) ||
+      !CBB_add_u16_length_prefixed(out, &contents) ||
+      /* We only consider the first identity for resumption */
+      !CBB_add_u16(&contents, 0) ||
+      !CBB_flush(out)) {
+    return 0;
+  }
+
+  return 1;
+}
+
+
 /* Key Share
  *
  * https://tools.ietf.org/html/draft-ietf-tls-tls13-12 */
@@ -2361,6 +2447,14 @@
     ignore_parse_clienthello,
     dont_add_serverhello,
   },
+  {
+    TLSEXT_TYPE_pre_shared_key,
+    NULL,
+    ext_pre_shared_key_add_clienthello,
+    forbid_parse_serverhello,
+    ignore_parse_clienthello,
+    dont_add_serverhello,
+  },
   /* The final extension must be non-empty. WebSphere Application Server 7.0 is
    * intolerant to the last extension being zero-length. See
    * https://crbug.com/363583. */
@@ -2786,6 +2880,10 @@
   *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;
   }
@@ -2875,6 +2973,12 @@
   memcpy(session->session_id, session_id, session_id_len);
   session->session_id_length = session_id_len;
 
+  if (!ssl_session_is_context_valid(ssl, session) ||
+      !ssl_session_is_time_valid(ssl, session)) {
+    SSL_SESSION_free(session);
+    session = NULL;
+  }
+
   *out_session = session;
 
 done:
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 2a4db6b..13229e9 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -101,6 +101,7 @@
   // operation has been retried.
   unsigned private_key_retries = 0;
   bool got_new_session = false;
+  ScopedSSL_SESSION new_session;
   bool ticket_decrypt_done = false;
   bool alpn_select_done = false;
 };
@@ -645,8 +646,7 @@
 
 static int NewSessionCallback(SSL *ssl, SSL_SESSION *session) {
   GetTestState(ssl)->got_new_session = true;
-  // BoringSSL passes a reference to |session|.
-  SSL_SESSION_free(session);
+  GetTestState(ssl)->new_session.reset(session);
   return 1;
 }
 
@@ -1621,7 +1621,7 @@
   }
 
   if (out_session) {
-    out_session->reset(SSL_get1_session(ssl.get()));
+    *out_session = std::move(GetTestState(ssl.get())->new_session);
   }
 
   ret = DoShutdown(ssl.get());
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 45d3e13..d064919 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -2313,9 +2313,6 @@
 					expectedClientError = ":WRONG_CIPHER_RETURNED:"
 				}
 
-				// TODO(davidben,svaldez): Implement resumption for TLS 1.3.
-				resumeSession := ver.version < VersionTLS13
-
 				testCases = append(testCases, testCase{
 					testType: serverTest,
 					protocol: protocol,
@@ -2336,7 +2333,7 @@
 					certFile:      certFile,
 					keyFile:       keyFile,
 					flags:         flags,
-					resumeSession: resumeSession,
+					resumeSession: true,
 					shouldFail:    shouldServerFail,
 					expectedError: expectedServerError,
 				})
@@ -2358,7 +2355,7 @@
 						},
 					},
 					flags:         flags,
-					resumeSession: resumeSession,
+					resumeSession: true,
 					shouldFail:    shouldClientFail,
 					expectedError: expectedClientError,
 				})
@@ -3426,9 +3423,7 @@
 			base64.StdEncoding.EncodeToString(testOCSPResponse),
 			"-verify-peer",
 		},
-		// TODO(davidben): Enable this when resumption is implemented
-		// in TLS 1.3.
-		resumeSession: false,
+		resumeSession: true,
 	})
 	tests = append(tests, testCase{
 		testType: serverTest,
@@ -3441,9 +3436,7 @@
 			"-ocsp-response",
 			base64.StdEncoding.EncodeToString(testOCSPResponse),
 		},
-		// TODO(davidben): Enable this when resumption is implemented
-		// in TLS 1.3.
-		resumeSession: false,
+		resumeSession: true,
 	})
 
 	// Certificate verification tests.
@@ -3474,9 +3467,7 @@
 					flag,
 					"-expect-verify-result",
 				},
-				// TODO(davidben): Enable this when resumption is
-				// implemented in TLS 1.3.
-				resumeSession: vers.version != VersionTLS13,
+				resumeSession: true,
 			})
 			tests = append(tests, testCase{
 				testType: testType,
@@ -3507,9 +3498,7 @@
 				"-verify-fail",
 				"-expect-verify-result",
 			},
-			// TODO(davidben): Enable this when resumption is
-			// implemented in TLS 1.3.
-			resumeSession: vers.version != VersionTLS13,
+			resumeSession: true,
 		})
 	}
 
@@ -3822,7 +3811,6 @@
 
 func addDDoSCallbackTests() {
 	// DDoS callback.
-	// TODO(davidben): Implement DDoS resumption tests for TLS 1.3.
 	for _, resume := range []bool{false, true} {
 		suffix := "Resume"
 		if resume {
@@ -3838,17 +3826,15 @@
 			flags:         []string{"-install-ddos-callback"},
 			resumeSession: resume,
 		})
-		if !resume {
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "Server-DDoS-OK-" + suffix + "-TLS13",
-				config: Config{
-					MaxVersion: VersionTLS13,
-				},
-				flags:         []string{"-install-ddos-callback"},
-				resumeSession: resume,
-			})
-		}
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "Server-DDoS-OK-" + suffix + "-TLS13",
+			config: Config{
+				MaxVersion: VersionTLS13,
+			},
+			flags:         []string{"-install-ddos-callback"},
+			resumeSession: resume,
+		})
 
 		failFlag := "-fail-ddos-callback"
 		if resume {
@@ -3865,19 +3851,17 @@
 			shouldFail:    true,
 			expectedError: ":CONNECTION_REJECTED:",
 		})
-		if !resume {
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "Server-DDoS-Reject-" + suffix + "-TLS13",
-				config: Config{
-					MaxVersion: VersionTLS13,
-				},
-				flags:         []string{"-install-ddos-callback", failFlag},
-				resumeSession: resume,
-				shouldFail:    true,
-				expectedError: ":CONNECTION_REJECTED:",
-			})
-		}
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "Server-DDoS-Reject-" + suffix + "-TLS13",
+			config: Config{
+				MaxVersion: VersionTLS13,
+			},
+			flags:         []string{"-install-ddos-callback", failFlag},
+			resumeSession: resume,
+			shouldFail:    true,
+			expectedError: ":CONNECTION_REJECTED:",
+		})
 	}
 }
 
@@ -4223,9 +4207,6 @@
 			continue
 		}
 
-		// TODO(davidben): Implement resumption in TLS 1.3.
-		resumeSession := ver.version < VersionTLS13
-
 		// Test that duplicate extensions are rejected.
 		testCases = append(testCases, testCase{
 			testType: clientTest,
@@ -4297,7 +4278,7 @@
 				ServerName: "example.com",
 			},
 			flags:         []string{"-expect-server-name", "example.com"},
-			resumeSession: resumeSession,
+			resumeSession: true,
 		})
 
 		// Test ALPN.
@@ -4314,7 +4295,7 @@
 			},
 			expectedNextProto:     "foo",
 			expectedNextProtoType: alpn,
-			resumeSession:         resumeSession,
+			resumeSession:         true,
 		})
 		testCases = append(testCases, testCase{
 			testType: clientTest,
@@ -4345,7 +4326,7 @@
 			},
 			expectedNextProto:     "foo",
 			expectedNextProtoType: alpn,
-			resumeSession:         resumeSession,
+			resumeSession:         true,
 		})
 		testCases = append(testCases, testCase{
 			testType: serverTest,
@@ -4356,7 +4337,7 @@
 			},
 			flags:             []string{"-decline-alpn"},
 			expectNoNextProto: true,
-			resumeSession:     resumeSession,
+			resumeSession:     true,
 		})
 
 		// Test ALPN in async mode as well to ensure that extensions callbacks are only
@@ -4375,7 +4356,7 @@
 			},
 			expectedNextProto:     "foo",
 			expectedNextProtoType: alpn,
-			resumeSession:         resumeSession,
+			resumeSession:         true,
 		})
 
 		var emptyString string
@@ -4430,7 +4411,7 @@
 				},
 				expectedNextProto:     "foo",
 				expectedNextProtoType: alpn,
-				resumeSession:         resumeSession,
+				resumeSession:         true,
 			})
 			testCases = append(testCases, testCase{
 				testType: serverTest,
@@ -4449,7 +4430,7 @@
 				},
 				expectedNextProto:     "foo",
 				expectedNextProtoType: alpn,
-				resumeSession:         resumeSession,
+				resumeSession:         true,
 			})
 
 			// Test that negotiating both NPN and ALPN is forbidden.
@@ -4503,66 +4484,65 @@
 		}
 
 		// Test ticket behavior.
-		//
-		// TODO(davidben): Add TLS 1.3 versions of these.
+
+		// Resume with a corrupt ticket.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "CorruptTicket-" + ver.name,
+			config: Config{
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					CorruptTicket: true,
+				},
+			},
+			resumeSession:        true,
+			expectResumeRejected: true,
+		})
+		// Test the ticket callback, with and without renewal.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "TicketCallback-" + ver.name,
+			config: Config{
+				MaxVersion: ver.version,
+			},
+			resumeSession: true,
+			flags:         []string{"-use-ticket-callback"},
+		})
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "TicketCallback-Renew-" + ver.name,
+			config: Config{
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					ExpectNewTicket: true,
+				},
+			},
+			flags:         []string{"-use-ticket-callback", "-renew-ticket"},
+			resumeSession: true,
+		})
+
+		// Test that the ticket callback is only called once when everything before
+		// it in the ClientHello is asynchronous. This corrupts the ticket so
+		// certificate selection callbacks run.
+		testCases = append(testCases, testCase{
+			testType: serverTest,
+			name:     "TicketCallback-SingleCall-" + ver.name,
+			config: Config{
+				MaxVersion: ver.version,
+				Bugs: ProtocolBugs{
+					CorruptTicket: true,
+				},
+			},
+			resumeSession:        true,
+			expectResumeRejected: true,
+			flags: []string{
+				"-use-ticket-callback",
+				"-async",
+			},
+		})
+
+		// Resume with an oversized session id.
 		if ver.version < VersionTLS13 {
-			// Resume with a corrupt ticket.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "CorruptTicket-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						CorruptTicket: true,
-					},
-				},
-				resumeSession:        true,
-				expectResumeRejected: true,
-			})
-			// Test the ticket callback, with and without renewal.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TicketCallback-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-				},
-				resumeSession: true,
-				flags:         []string{"-use-ticket-callback"},
-			})
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TicketCallback-Renew-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						ExpectNewTicket: true,
-					},
-				},
-				flags:         []string{"-use-ticket-callback", "-renew-ticket"},
-				resumeSession: true,
-			})
-
-			// Test that the ticket callback is only called once when everything before
-			// it in the ClientHello is asynchronous. This corrupts the ticket so
-			// certificate selection callbacks run.
-			testCases = append(testCases, testCase{
-				testType: serverTest,
-				name:     "TicketCallback-SingleCall-" + ver.name,
-				config: Config{
-					MaxVersion: ver.version,
-					Bugs: ProtocolBugs{
-						CorruptTicket: true,
-					},
-				},
-				resumeSession:        true,
-				expectResumeRejected: true,
-				flags: []string{
-					"-use-ticket-callback",
-					"-async",
-				},
-			})
-
-			// Resume with an oversized session id.
 			testCases = append(testCases, testCase{
 				testType: serverTest,
 				name:     "OversizedSessionId-" + ver.name,
@@ -4674,7 +4654,7 @@
 				"-expect-signed-cert-timestamps",
 				base64.StdEncoding.EncodeToString(testSCTList),
 			},
-			resumeSession: resumeSession,
+			resumeSession: true,
 		})
 		testCases = append(testCases, testCase{
 			name: "SendSCTListOnResume-" + ver.name,
@@ -4689,7 +4669,7 @@
 				"-expect-signed-cert-timestamps",
 				base64.StdEncoding.EncodeToString(testSCTList),
 			},
-			resumeSession: resumeSession,
+			resumeSession: true,
 		})
 		testCases = append(testCases, testCase{
 			name:     "SignedCertificateTimestampList-Server-" + ver.name,
@@ -4702,7 +4682,7 @@
 				base64.StdEncoding.EncodeToString(testSCTList),
 			},
 			expectedSCTList: testSCTList,
-			resumeSession:   resumeSession,
+			resumeSession:   true,
 		})
 	}
 
@@ -4874,14 +4854,7 @@
 
 func addResumptionVersionTests() {
 	for _, sessionVers := range tlsVersions {
-		// TODO(davidben,svaldez): Implement resumption in TLS 1.3.
-		if sessionVers.version >= VersionTLS13 {
-			continue
-		}
 		for _, resumeVers := range tlsVersions {
-			if resumeVers.version >= VersionTLS13 {
-				continue
-			}
 			cipher := TLS_RSA_WITH_AES_128_CBC_SHA
 			if sessionVers.version >= VersionTLS13 || resumeVers.version >= VersionTLS13 {
 				// TLS 1.3 only shares ciphers with TLS 1.2, so
@@ -4916,6 +4889,14 @@
 						expectedResumeVersion: resumeVers.version,
 					})
 				} else {
+					var localError, error string
+					if (resumeVers.version >= VersionTLS13) != (sessionVers.version >= VersionTLS13) {
+						// TLS 1.3 sessions are incompatible with TLS 1.2 sessions.
+						localError = "didResume is false, but we expected the opposite"
+					} else {
+						error = ":OLD_SESSION_VERSION_NOT_RETURNED:"
+					}
+
 					testCases = append(testCases, testCase{
 						protocol:      protocol,
 						name:          "Resume-Client-Mismatch" + suffix,
@@ -4934,7 +4915,8 @@
 						},
 						expectedResumeVersion: resumeVers.version,
 						shouldFail:            true,
-						expectedError:         ":OLD_SESSION_VERSION_NOT_RETURNED:",
+						expectedLocalError:    localError,
+						expectedError:         error,
 					})
 				}
 
@@ -4977,7 +4959,6 @@
 		}
 	}
 
-	// TODO(davidben): This test should have a TLS 1.3 variant later.
 	testCases = append(testCases, testCase{
 		name:          "Resume-Client-CipherMismatch",
 		resumeSession: true,
@@ -4995,6 +4976,24 @@
 		shouldFail:    true,
 		expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:",
 	})
+
+	testCases = append(testCases, testCase{
+		name:          "Resume-Client-CipherMismatch-TLS13",
+		resumeSession: true,
+		config: Config{
+			MaxVersion:   VersionTLS13,
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+		},
+		resumeConfig: &Config{
+			MaxVersion:   VersionTLS13,
+			CipherSuites: []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			Bugs: ProtocolBugs{
+				SendCipherSuite: TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA,
+			},
+		},
+		shouldFail:    true,
+		expectedError: ":OLD_SESSION_CIPHER_NOT_RETURNED:",
+	})
 }
 
 func addRenegotiationTests() {
diff --git a/ssl/tls13_client.c b/ssl/tls13_client.c
index 61e1414..d58f72d 100644
--- a/ssl/tls13_client.c
+++ b/ssl/tls13_client.c
@@ -151,8 +151,8 @@
   }
 
   /* Parse out the extensions. */
-  int have_key_share = 0;
-  CBS key_share;
+  int have_key_share = 0, have_pre_shared_key = 0;
+  CBS key_share, pre_shared_key;
   while (CBS_len(&extensions) != 0) {
     uint16_t type;
     CBS extension;
@@ -173,6 +173,15 @@
         key_share = extension;
         have_key_share = 1;
         break;
+      case TLSEXT_TYPE_pre_shared_key:
+        if (have_pre_shared_key) {
+          OPENSSL_PUT_ERROR(SSL, SSL_R_DUPLICATE_EXTENSION);
+          ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+          return ssl_hs_error;
+        }
+        pre_shared_key = extension;
+        have_pre_shared_key = 1;
+        break;
       default:
         OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
@@ -183,10 +192,48 @@
   assert(ssl->s3->have_version);
   memcpy(ssl->s3->server_random, CBS_data(&server_random), SSL3_RANDOM_SIZE);
 
-  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;
+  uint8_t alert = SSL_AD_DECODE_ERROR;
+  if (have_pre_shared_key) {
+    if (ssl->session == NULL) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_UNEXPECTED_EXTENSION);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
+      return ssl_hs_error;
+    }
+
+    if (!ssl_ext_pre_shared_key_parse_serverhello(ssl, &alert,
+                                                  &pre_shared_key)) {
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+      return ssl_hs_error;
+    }
+
+    if (ssl->session->ssl_version != ssl->version) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_VERSION_NOT_RETURNED);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      return ssl_hs_error;
+    }
+
+    if (!ssl_session_is_context_valid(ssl, ssl->session)) {
+      /* This is actually a client application bug. */
+      OPENSSL_PUT_ERROR(SSL,
+                        SSL_R_ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      return ssl_hs_error;
+    }
+
+    ssl->s3->session_reused = 1;
+    /* Only authentication information carries over in TLS 1.3. */
+    ssl->s3->new_session =
+        SSL_SESSION_dup(ssl->session, SSL_SESSION_DUP_AUTH_ONLY);
+    if (ssl->s3->new_session == NULL) {
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+    SSL_set_session(ssl, NULL);
+  } else {
+    if (!ssl_get_new_session(ssl, 0)) {
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
   }
 
   const SSL_CIPHER *cipher = SSL_get_cipher_by_value(cipher_suite);
@@ -196,15 +243,26 @@
     return ssl_hs_error;
   }
 
-  /* Check if the cipher is disabled. */
-  if ((cipher->algorithm_mkey & ssl->cert->mask_k) ||
-      (cipher->algorithm_auth & ssl->cert->mask_a) ||
-      SSL_CIPHER_get_min_version(cipher) > ssl3_protocol_version(ssl) ||
-      SSL_CIPHER_get_max_version(cipher) < ssl3_protocol_version(ssl) ||
-      !sk_SSL_CIPHER_find(ssl_get_ciphers_by_id(ssl), NULL, cipher)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
-    return ssl_hs_error;
+  if (!ssl->s3->session_reused) {
+    /* Check if the cipher is disabled. */
+    if ((cipher->algorithm_mkey & ssl->cert->mask_k) ||
+        (cipher->algorithm_auth & ssl->cert->mask_a) ||
+        SSL_CIPHER_get_min_version(cipher) > ssl3_protocol_version(ssl) ||
+        SSL_CIPHER_get_max_version(cipher) < ssl3_protocol_version(ssl) ||
+        !sk_SSL_CIPHER_find(ssl_get_ciphers_by_id(ssl), NULL, cipher)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_CIPHER_RETURNED);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      return ssl_hs_error;
+    }
+  } else {
+    uint16_t resumption_cipher;
+    if (!ssl_cipher_get_ecdhe_psk_cipher(ssl->s3->new_session->cipher,
+                                         &resumption_cipher) ||
+        resumption_cipher != ssl_cipher_get_value(cipher)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_OLD_SESSION_CIPHER_NOT_RETURNED);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_ILLEGAL_PARAMETER);
+      return ssl_hs_error;
+    }
   }
 
   ssl->s3->new_session->cipher = cipher;
@@ -212,18 +270,35 @@
 
   /* The PRF hash is now known. Set up the key schedule. */
   static const uint8_t kZeroes[EVP_MAX_MD_SIZE] = {0};
-  size_t hash_len =
+  size_t resumption_ctx_len =
       EVP_MD_size(ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl)));
-  if (!tls13_init_key_schedule(ssl, kZeroes, hash_len)) {
+  if (ssl->s3->session_reused) {
+    uint8_t resumption_ctx[EVP_MAX_MD_SIZE];
+    if (!tls13_resumption_context(ssl, resumption_ctx, resumption_ctx_len,
+                                  ssl->s3->new_session) ||
+        !tls13_init_key_schedule(ssl, resumption_ctx, resumption_ctx_len)) {
+      return ssl_hs_error;
+    }
+  } else if (!tls13_init_key_schedule(ssl, kZeroes, resumption_ctx_len)) {
     return ssl_hs_error;
   }
 
   /* Resolve PSK and incorporate it into the secret. */
   if (cipher->algorithm_auth == SSL_aPSK) {
-    /* TODO(davidben): Support PSK. */
-    OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-    return ssl_hs_error;
-  } else if (!tls13_advance_key_schedule(ssl, kZeroes, hash_len)) {
+    if (!ssl->s3->session_reused) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+
+    uint8_t resumption_psk[EVP_MAX_MD_SIZE];
+    if (!tls13_resumption_psk(ssl, resumption_psk, hs->hash_len,
+                              ssl->s3->new_session) ||
+        !tls13_advance_key_schedule(ssl, resumption_psk, hs->hash_len)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
+      return ssl_hs_error;
+    }
+  } else if (!tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len)) {
     return ssl_hs_error;
   }
 
@@ -237,7 +312,6 @@
 
     uint8_t *dhe_secret;
     size_t dhe_secret_len;
-    uint8_t alert = SSL_AD_DECODE_ERROR;
     if (!ssl_ext_key_share_parse_serverhello(ssl, &dhe_secret, &dhe_secret_len,
                                              &alert, &key_share)) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
@@ -255,7 +329,7 @@
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_UNSUPPORTED_EXTENSION);
       return ssl_hs_error;
     }
-    if (!tls13_advance_key_schedule(ssl, kZeroes, hash_len)) {
+    if (!tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len)) {
       return ssl_hs_error;
     }
   }
@@ -568,8 +642,9 @@
 }
 
 int tls13_process_new_session_ticket(SSL *ssl) {
-  SSL_SESSION *session = SSL_SESSION_dup(ssl->s3->established_session,
-                                         0 /* don't include ticket */);
+  SSL_SESSION *session =
+      SSL_SESSION_dup(ssl->s3->established_session,
+                      SSL_SESSION_INCLUDE_NONAUTH);
   if (session == NULL) {
     return 0;
   }
diff --git a/ssl/tls13_enc.c b/ssl/tls13_enc.c
index 70b041a..518d2d3 100644
--- a/ssl/tls13_enc.c
+++ b/ssl/tls13_enc.c
@@ -351,6 +351,28 @@
   return 1;
 }
 
+static const char kTLS13LabelResumptionPSK[] = "resumption psk";
+static const char kTLS13LabelResumptionContext[] = "resumption context";
+
+int tls13_resumption_psk(SSL *ssl, uint8_t *out, size_t out_len,
+                         const SSL_SESSION *session) {
+  const EVP_MD *digest = ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl));
+  return hkdf_expand_label(out, digest, session->master_key,
+                           session->master_key_length,
+                           (const uint8_t *)kTLS13LabelResumptionPSK,
+                           strlen(kTLS13LabelResumptionPSK), NULL, 0, out_len);
+}
+
+int tls13_resumption_context(SSL *ssl, uint8_t *out, size_t out_len,
+                             const SSL_SESSION *session) {
+  const EVP_MD *digest = ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl));
+  return hkdf_expand_label(out, digest, session->master_key,
+                           session->master_key_length,
+                           (const uint8_t *)kTLS13LabelResumptionContext,
+                           strlen(kTLS13LabelResumptionContext), NULL, 0,
+                           out_len);
+}
+
 int tls13_export_keying_material(SSL *ssl, uint8_t *out, size_t out_len,
                                  const char *label, size_t label_len,
                                  const uint8_t *context, size_t context_len,
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index a1aeeea..1f4475e 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -58,9 +58,14 @@
     return tls13_advance_key_schedule(ssl, kZeroes, hs->hash_len);
   }
 
-  /* TODO(davidben): Support PSK. */
-  OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
-  return 0;
+  uint8_t resumption_psk[EVP_MAX_MD_SIZE];
+  if (!tls13_resumption_psk(ssl, resumption_psk, hs->hash_len,
+                            ssl->s3->new_session) ||
+      !tls13_advance_key_schedule(ssl, resumption_psk, hs->hash_len)) {
+    return 0;
+  }
+
+  return 1;
 }
 
 static int resolve_ecdhe_secret(SSL *ssl, int *out_need_retry,
@@ -123,10 +128,45 @@
   }
   memcpy(ssl->s3->client_random, client_hello.random, client_hello.random_len);
 
-  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;
+  uint8_t alert = SSL_AD_DECODE_ERROR;
+  SSL_SESSION *session = NULL;
+  CBS pre_shared_key;
+  if (ssl_early_callback_get_extension(&client_hello, &pre_shared_key,
+                                       TLSEXT_TYPE_pre_shared_key) &&
+      !ssl_ext_pre_shared_key_parse_clienthello(ssl, &session, &alert,
+                                                &pre_shared_key)) {
+    ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
+    return 0;
+  }
+
+  uint16_t resumption_cipher;
+  if (session != NULL &&
+      /* We currently only support ECDHE-PSK resumption. */
+      ((session->ticket_flags & SSL_TICKET_ALLOW_DHE_RESUMPTION) == 0 ||
+       /* Only resume if the session's version matches. */
+       session->ssl_version != ssl->version ||
+       !ssl_cipher_get_ecdhe_psk_cipher(session->cipher, &resumption_cipher) ||
+       !ssl_client_cipher_list_contains_cipher(&client_hello,
+                                               resumption_cipher))) {
+    SSL_SESSION_free(session);
+    session = NULL;
+  }
+
+  if (session == NULL) {
+    if (!ssl_get_new_session(ssl, 1 /* server */)) {
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+  } else {
+    /* Only authentication information carries over in TLS 1.3. */
+    ssl->s3->new_session = SSL_SESSION_dup(session, SSL_SESSION_DUP_AUTH_ONLY);
+    if (ssl->s3->new_session == NULL) {
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+      return ssl_hs_error;
+    }
+    ssl->s3->session_reused = 1;
+    ssl->verify_result = session->verify_result;
+    SSL_SESSION_free(session);
   }
 
   if (ssl->ctx->dos_protection_cb != NULL &&
@@ -156,17 +196,19 @@
 }
 
 static enum ssl_hs_wait_t do_select_parameters(SSL *ssl, SSL_HANDSHAKE *hs) {
-  /* Call |cert_cb| to update server certificates if required. */
-  if (ssl->cert->cert_cb != NULL) {
-    int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
-    if (rv == 0) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
-      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return ssl_hs_error;
-    }
-    if (rv < 0) {
-      hs->state = state_select_parameters;
-      return ssl_hs_x509_lookup;
+  if (!ssl->s3->session_reused) {
+    /* Call |cert_cb| to update server certificates if required. */
+    if (ssl->cert->cert_cb != NULL) {
+      int rv = ssl->cert->cert_cb(ssl, ssl->cert->cert_cb_arg);
+      if (rv == 0) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_CB_ERROR);
+        ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+        return ssl_hs_error;
+      }
+      if (rv < 0) {
+        hs->state = state_select_parameters;
+        return ssl_hs_x509_lookup;
+      }
     }
   }
 
@@ -178,25 +220,45 @@
     return ssl_hs_error;
   }
 
-  const SSL_CIPHER *cipher =
-      ssl3_choose_cipher(ssl, &client_hello, ssl_get_cipher_preferences(ssl));
-  if (cipher == NULL) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER);
-    ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-    return ssl_hs_error;
-  }
+  if (!ssl->s3->session_reused) {
+    const SSL_CIPHER *cipher =
+        ssl3_choose_cipher(ssl, &client_hello, ssl_get_cipher_preferences(ssl));
+    if (cipher == NULL) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
+      return ssl_hs_error;
+    }
 
-  ssl->s3->new_session->cipher = cipher;
-  ssl->s3->tmp.new_cipher = cipher;
+    ssl->s3->new_session->cipher = cipher;
+    ssl->s3->tmp.new_cipher = cipher;
+  } else {
+    uint16_t resumption_cipher;
+    if (!ssl_cipher_get_ecdhe_psk_cipher(ssl->s3->new_session->cipher,
+                                         &resumption_cipher)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_NO_SHARED_CIPHER);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
+      return ssl_hs_error;
+    }
+    ssl->s3->tmp.new_cipher = SSL_get_cipher_by_value(resumption_cipher);
+  }
 
   ssl->method->received_flight(ssl);
 
   /* The PRF hash is now known. Set up the key schedule and hash the
    * ClientHello. */
-  size_t hash_len =
+  size_t resumption_ctx_len =
       EVP_MD_size(ssl_get_handshake_digest(ssl_get_algorithm_prf(ssl)));
-  if (!tls13_init_key_schedule(ssl, kZeroes, hash_len)) {
-    return ssl_hs_error;
+  if (ssl->s3->session_reused) {
+    uint8_t resumption_ctx[EVP_MAX_MD_SIZE];
+    if (!tls13_resumption_context(ssl, resumption_ctx, resumption_ctx_len,
+                                  ssl->s3->new_session) ||
+        !tls13_init_key_schedule(ssl, resumption_ctx, resumption_ctx_len)) {
+      return ssl_hs_error;
+    }
+  } else {
+    if (!tls13_init_key_schedule(ssl, kZeroes, resumption_ctx_len)) {
+      return ssl_hs_error;
+    }
   }
 
   /* Resolve PSK and incorporate it into the secret. */
@@ -285,6 +347,7 @@
       !CBB_add_bytes(&body, ssl->s3->server_random, SSL3_RANDOM_SIZE) ||
       !CBB_add_u16(&body, ssl_cipher_get_value(ssl->s3->tmp.new_cipher)) ||
       !CBB_add_u16_length_prefixed(&body, &extensions) ||
+      !ssl_ext_pre_shared_key_add_serverhello(ssl, &extensions) ||
       !ssl_ext_key_share_add_serverhello(ssl, &extensions) ||
       !ssl->method->finish_message(ssl, &cbb)) {
     CBB_cleanup(&cbb);
@@ -428,7 +491,7 @@
                                                         SSL_HANDSHAKE *hs) {
   if (!ssl->s3->tmp.cert_request) {
     /* Skip this state. */
-    hs->state = state_process_client_certificate_verify;
+    hs->state = state_process_client_finished;
     return ssl_hs_ok;
   }