Hold certificates in an SSL_SESSION as CRYPTO_BUFFERSs as well.

This change adds a STACK_OF(CRYPTO_BUFFER) to an SSL_SESSION which
contains the raw form of the received certificates. The X509-based
members still exist, but their |enc| buffer will alias the
CRYPTO_BUFFERs.

The serialisation format of SSL_SESSIONs is also changed, in a backwards
compatible way. Previously, some sessions would duplicate the leaf
certificate in the certificate chain. These sessions can still be read,
but will be written in a way incompatible with older versions of the
code. This should be fine because the situation where multiple versions
exchange serialised sessions is at the server, and the server doesn't
duplicate the leaf certifiate in the chain anyway.

Change-Id: Id3b75d24f1745795315cb7f8089a4ee4263fa938
Reviewed-on: https://boringssl-review.googlesource.com/12163
Reviewed-by: Adam Langley <alangley@gmail.com>
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 5338037..165dca1 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -149,6 +149,7 @@
 #include <openssl/hmac.h>
 #include <openssl/lhash.h>
 #include <openssl/pem.h>
+#include <openssl/pool.h>
 #include <openssl/ssl3.h>
 #include <openssl/thread.h>
 #include <openssl/tls1.h>
@@ -3706,6 +3707,11 @@
   uint8_t sid_ctx[SSL_MAX_SID_CTX_LENGTH];
 
   char *psk_identity;
+
+  /* certs contains the certificate chain from the peer, starting with the leaf
+   * certificate. */
+  STACK_OF(CRYPTO_BUFFER) *certs;
+
   /* x509_peer is the peer's certificate. */
   X509 *x509_peer;
 
@@ -3772,6 +3778,11 @@
 
   /* ticket_age_add_valid is non-zero if |ticket_age_add| is valid. */
   unsigned ticket_age_add_valid:1;
+
+  /* x509_chain_should_include_leaf is true if the |STACK_OF(X509)| certificate
+   * chain should include the leaf certificate. Due to history, this is false
+   * for server sessions and true for client sessions. */
+  unsigned x509_chain_should_include_leaf:1;
 };
 
 /* ssl_cipher_preference_list_st contains a list of SSL_CIPHERs with
diff --git a/ssl/handshake_client.c b/ssl/handshake_client.c
index 4a58af9..816255b 100644
--- a/ssl/handshake_client.c
+++ b/ssl/handshake_client.c
@@ -1041,14 +1041,15 @@
 
   CBS cbs;
   CBS_init(&cbs, ssl->init_msg, ssl->init_num);
+  STACK_OF(CRYPTO_BUFFER) *buffers = NULL;
+  STACK_OF(X509) *chain = NULL;
   uint8_t alert;
-  STACK_OF(X509) *chain = ssl_parse_cert_chain(ssl, &alert, NULL, &cbs);
-  if (chain == NULL) {
+  if (!ssl_parse_cert_chain(ssl, &buffers, &chain, &alert, NULL, &cbs)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
     goto err;
   }
 
-  if (sk_X509_num(chain) == 0 || CBS_len(&cbs) != 0) {
+  if (sk_CRYPTO_BUFFER_num(buffers) == 0 || CBS_len(&cbs) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
     goto err;
@@ -1062,8 +1063,11 @@
 
   /* NOTE: Unlike the server half, the client's copy of |x509_chain| includes
    * the leaf. */
+  sk_CRYPTO_BUFFER_pop_free(ssl->s3->new_session->certs, CRYPTO_BUFFER_free);
+  ssl->s3->new_session->certs = buffers;
   sk_X509_pop_free(ssl->s3->new_session->x509_chain, X509_free);
   ssl->s3->new_session->x509_chain = chain;
+  ssl->s3->new_session->x509_chain_should_include_leaf = 1;
 
   X509_free(ssl->s3->new_session->x509_peer);
   X509_up_ref(leaf);
@@ -1072,6 +1076,7 @@
   return 1;
 
 err:
+  sk_CRYPTO_BUFFER_pop_free(buffers, CRYPTO_BUFFER_free);
   sk_X509_pop_free(chain, X509_free);
   return -1;
 }
diff --git a/ssl/handshake_server.c b/ssl/handshake_server.c
index 1c1c708..4686b01 100644
--- a/ssl/handshake_server.c
+++ b/ssl/handshake_server.c
@@ -1475,13 +1475,14 @@
 
   CBS certificate_msg;
   CBS_init(&certificate_msg, ssl->init_msg, ssl->init_num);
+  STACK_OF(CRYPTO_BUFFER) *buffers = NULL;
+  STACK_OF(X509) *chain = NULL;
   uint8_t alert;
-  STACK_OF(X509) *chain = ssl_parse_cert_chain(
-      ssl, &alert, ssl->retain_only_sha256_of_client_certs
-                       ? ssl->s3->new_session->peer_sha256
-                       : NULL,
-      &certificate_msg);
-  if (chain == NULL) {
+  if (!ssl_parse_cert_chain(ssl, &buffers, &chain, &alert,
+                            ssl->retain_only_sha256_of_client_certs
+                                ? ssl->s3->new_session->peer_sha256
+                                : NULL,
+                            &certificate_msg)) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, alert);
     goto err;
   }
@@ -1492,7 +1493,7 @@
     goto err;
   }
 
-  if (sk_X509_num(chain) == 0) {
+  if (sk_CRYPTO_BUFFER_num(buffers) == 0) {
     /* No client certificate so the handshake buffer may be discarded. */
     ssl3_free_handshake_buffer(ssl);
 
@@ -1533,10 +1534,13 @@
   ssl->s3->new_session->x509_chain = chain;
   /* Inconsistency alert: x509_chain does *not* include the peer's own
    * certificate, while we do include it in s3_clnt.c */
+  sk_CRYPTO_BUFFER_pop_free(ssl->s3->new_session->certs, CRYPTO_BUFFER_free);
+  ssl->s3->new_session->certs = buffers;
 
   return 1;
 
 err:
+  sk_CRYPTO_BUFFER_pop_free(buffers, CRYPTO_BUFFER_free);
   sk_X509_pop_free(chain, X509_free);
   return -1;
 }
diff --git a/ssl/internal.h b/ssl/internal.h
index 995adcf..08ccd07 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -748,18 +748,30 @@
  * configured and zero otherwise. */
 int ssl_has_certificate(const SSL *ssl);
 
-/* ssl_parse_x509 parses a X509 certificate from |cbs|. It returns NULL
- * on error. */
-X509 *ssl_parse_x509(CBS *cbs);
+/* x509_chain_from_buffers parses a number of X.509 certificates from |buffers|
+ * and sets |*out| to a corresponding stack of |X509*|. It returns one on
+ * success or zero on error. */
+int x509_chain_from_buffers(STACK_OF(X509) **out,
+                            STACK_OF(CRYPTO_BUFFER) *buffers);
+
+/* ssl_session_set_x509_peer sets |sess->x509_peer| to the first element of
+ * |sess->x509_chain| and copies |chain_should_include_leaf| to
+ * |sess->x509_chain_should_include_leaf|. If |chain_should_include_leaf| is
+ * true then |x509_peer| is a additional reference to the first element of the
+ * chain. Otherwise the first element of the chain is removed and |x509_peer|
+ * is the only reference to it. */
+void ssl_session_set_x509_peer(SSL_SESSION *sess,
+                               int chain_should_include_leaf);
 
 /* ssl_parse_cert_chain parses a certificate list from |cbs| in the format used
- * by a TLS Certificate message. On success, it returns a newly-allocated
- * |X509| list and advances |cbs|. Otherwise, it returns NULL and sets
- * |*out_alert| to an alert to send to the peer. If the list is non-empty and
- * |out_leaf_sha256| is non-NULL, it writes the SHA-256 hash of the leaf to
- * |out_leaf_sha256|. */
-STACK_OF(X509) *ssl_parse_cert_chain(SSL *ssl, uint8_t *out_alert,
-                                     uint8_t *out_leaf_sha256, CBS *cbs);
+ * by a TLS Certificate message. On success, it returns newly-allocated
+ * |CRYPTO_BUFFER| and |X509| lists, advances |cbs| and returns one. Otherwise,
+ * it returns zero and sets |*out_alert| to an alert to send to the peer. If
+ * the list is non-empty and |out_leaf_sha256| is non-NULL, it writes the
+ * SHA-256 hash of the leaf to |out_leaf_sha256|. */
+int ssl_parse_cert_chain(SSL *ssl, STACK_OF(CRYPTO_BUFFER) **out_buffers,
+                         STACK_OF(X509) **out_x509s, uint8_t *out_alert,
+                         uint8_t *out_leaf_sha256, CBS *cbs);
 
 /* ssl_add_cert_to_cbb adds |x509| to |cbb|. It returns one on success and zero
  * on error. */
diff --git a/ssl/ssl_asn1.c b/ssl/ssl_asn1.c
index aab6052..39c52ed 100644
--- a/ssl/ssl_asn1.c
+++ b/ssl/ssl_asn1.c
@@ -122,6 +122,7 @@
  *     keyExchangeInfo         [18] INTEGER OPTIONAL,
  *     certChain               [19] SEQUENCE OF Certificate OPTIONAL,
  *     ticketAgeAdd            [21] OCTET STRING OPTIONAL,
+ *     x509ChainIncludeLeaf    [22] BOOLEAN OPTIONAL,
  * }
  *
  * Note: historically this serialization has included other optional
@@ -132,7 +133,16 @@
  *     compressionMethod       [11] OCTET STRING OPTIONAL,
  *     srpUsername             [12] OCTET STRING OPTIONAL,
  *     ticketFlags             [20] INTEGER OPTIONAL,
- */
+ *
+ *
+ * When serialising, the first element of |certChain| does not duplicate the
+ * contents of |peer|, but that was not always true. When parsing, the returned
+ * |x509_chain| sometimes needs to contain the leaf certificate at the
+ * beginning (clients) and sometimes not (servers). The |x509ChainIncludeLeaf|
+ * value specifies whether this duplication should be done for the in-memory
+ * representation. If not given, the parsing code looks to see whether the
+ * first cert in |certChain| matches the |peer| and assumes that it's parsing
+ * old session data and thus that |x509ChainIncludeLeaf| should be true. */
 
 static const unsigned kVersion = 1;
 
@@ -170,6 +180,8 @@
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 19;
 static const int kTicketAgeAddTag =
     CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 21;
+static const int kX509ChainIncludeLeafTag =
+    CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 22;
 
 static int SSL_SESSION_to_bytes_full(const SSL_SESSION *in, uint8_t **out_data,
                                      size_t *out_len, int for_ticket) {
@@ -218,16 +230,14 @@
     goto err;
   }
 
-  /* The peer certificate is only serialized if the SHA-256 isn't
-   * serialized instead. */
-  if (in->x509_peer && !in->peer_sha256_valid) {
-    if (!CBB_add_asn1(&session, &child, kPeerTag)) {
+  if (sk_CRYPTO_BUFFER_num(in->certs) > 0 && !in->peer_sha256_valid) {
+    const CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(in->certs, 0);
+    if (!CBB_add_asn1(&session, &child, kPeerTag) ||
+        !CBB_add_bytes(&child, CRYPTO_BUFFER_data(buffer),
+                       CRYPTO_BUFFER_len(buffer))) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto err;
     }
-    if (!ssl_add_cert_to_cbb(&child, in->x509_peer)) {
-      goto err;
-    }
   }
 
   /* Although it is OPTIONAL and usually empty, OpenSSL has
@@ -340,13 +350,18 @@
 
   /* The certificate chain is only serialized if the leaf's SHA-256 isn't
    * serialized instead. */
-  if (in->x509_chain != NULL && !in->peer_sha256_valid) {
+  if (in->certs != NULL &&
+      !in->peer_sha256_valid &&
+      sk_CRYPTO_BUFFER_num(in->certs) > 1) {
     if (!CBB_add_asn1(&session, &child, kCertChainTag)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto err;
     }
-    for (size_t i = 0; i < sk_X509_num(in->x509_chain); i++) {
-      if (!ssl_add_cert_to_cbb(&child, sk_X509_value(in->x509_chain, i))) {
+    for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(in->certs); i++) {
+      const CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(in->certs, i);
+      if (!CBB_add_bytes(&child, CRYPTO_BUFFER_data(buffer),
+                         CRYPTO_BUFFER_len(buffer))) {
+        OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
         goto err;
       }
     }
@@ -361,6 +376,15 @@
     }
   }
 
+  if (in->x509_chain_should_include_leaf) {
+    if (!CBB_add_asn1(&session, &child, kX509ChainIncludeLeafTag) ||
+        !CBB_add_asn1(&child, &child2, CBS_ASN1_BOOLEAN) ||
+        !CBB_add_u8(&child2, 0xff)) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
+  }
+
   if (!CBB_finish(&cbb, out_data, out_len)) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     goto err;
@@ -572,22 +596,12 @@
 
   CBS peer;
   int has_peer;
-  if (!CBS_get_optional_asn1(&session, &peer, &has_peer, kPeerTag)) {
+  if (!CBS_get_optional_asn1(&session, &peer, &has_peer, kPeerTag) ||
+      (has_peer && CBS_len(&peer) == 0)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     goto err;
   }
-  X509_free(ret->x509_peer);
-  ret->x509_peer = NULL;
-  if (has_peer) {
-    ret->x509_peer = ssl_parse_x509(&peer);
-    if (ret->x509_peer == NULL) {
-      goto err;
-    }
-    if (CBS_len(&peer) != 0) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
-      goto err;
-    }
-  }
+  /* |peer| is processed with the certificate chain. */
 
   if (!SSL_SESSION_parse_bounded_octet_string(
           &session, ret->sid_ctx, &ret->sid_ctx_length, sizeof(ret->sid_ctx),
@@ -650,28 +664,70 @@
   }
 
   CBS cert_chain;
+  CBS_init(&cert_chain, NULL, 0);
   int has_cert_chain;
   if (!CBS_get_optional_asn1(&session, &cert_chain, &has_cert_chain,
-                             kCertChainTag)) {
+                             kCertChainTag) ||
+      (has_cert_chain && CBS_len(&cert_chain) == 0)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     goto err;
   }
-  sk_X509_pop_free(ret->x509_chain, X509_free);
-  ret->x509_chain = NULL;
-  if (has_cert_chain) {
-    ret->x509_chain = sk_X509_new_null();
-    if (ret->x509_chain == NULL) {
+  sk_CRYPTO_BUFFER_pop_free(ret->certs, CRYPTO_BUFFER_free);
+  ret->certs = NULL;
+
+  int x509_chain_should_include_leaf_default = 0;
+
+  if (has_peer || has_cert_chain) {
+    ret->certs = sk_CRYPTO_BUFFER_new_null();
+    if (ret->certs == NULL) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto err;
     }
-    while (CBS_len(&cert_chain) > 0) {
-      X509 *x509 = ssl_parse_x509(&cert_chain);
-      if (x509 == NULL) {
+
+    if (has_peer) {
+      CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new_from_CBS(&peer, NULL);
+      if (buffer == NULL) {
+        OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
         goto err;
       }
-      if (!sk_X509_push(ret->x509_chain, x509)) {
+
+      if (!sk_CRYPTO_BUFFER_push(ret->certs, buffer)) {
+        CRYPTO_BUFFER_free(buffer);
         OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-        X509_free(x509);
+        goto err;
+      }
+    }
+
+    size_t chain_i = 0;
+    while (CBS_len(&cert_chain) > 0) {
+      chain_i++;
+
+      CBS cert;
+      if (!CBS_get_any_asn1_element(&cert_chain, &cert, NULL, NULL) ||
+          CBS_len(&cert) == 0) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
+        goto err;
+      }
+
+      if (has_peer && chain_i == 1) {
+        /* If a certificate was parsed from |peer| then it may, or may not, be
+         * duplicated as the first element of |cert_chain|. */
+        if (CBS_len(&peer) == CBS_len(&cert) &&
+            memcmp(CBS_data(&peer), CBS_data(&cert), CBS_len(&peer)) == 0) {
+          x509_chain_should_include_leaf_default = 1;
+          continue;
+        }
+      }
+
+      CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new_from_CBS(&cert, NULL);
+      if (buffer == NULL) {
+        OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+        goto err;
+      }
+
+      if (!sk_CRYPTO_BUFFER_push(ret->certs, buffer)) {
+        CRYPTO_BUFFER_free(buffer);
+        OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
         goto err;
       }
     }
@@ -688,11 +744,25 @@
   }
   ret->ticket_age_add_valid = age_add_present;
 
+  int x509_chain_should_include_leaf;
+  if (!CBS_get_optional_asn1_bool(&session, &x509_chain_should_include_leaf,
+                                  kX509ChainIncludeLeafTag,
+                                  x509_chain_should_include_leaf_default)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
+    goto err;
+  }
+
   if (CBS_len(&session) != 0) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SSL_SESSION);
     goto err;
   }
 
+  if (!x509_chain_from_buffers(&ret->x509_chain, ret->certs)) {
+    goto err;
+  }
+
+  ssl_session_set_x509_peer(ret, x509_chain_should_include_leaf);
+
   return ret;
 
 err:
diff --git a/ssl/ssl_cert.c b/ssl/ssl_cert.c
index c0bdb5c..1ddf69a 100644
--- a/ssl/ssl_cert.c
+++ b/ssl/ssl_cert.c
@@ -448,30 +448,18 @@
   return ssl->cert->x509_leaf != NULL && ssl_has_private_key(ssl);
 }
 
-X509 *ssl_parse_x509(CBS *cbs) {
-  if (CBS_len(cbs) > LONG_MAX) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return NULL;
-  }
-  const uint8_t *ptr = CBS_data(cbs);
-  X509 *ret = d2i_X509(NULL, &ptr, (long)CBS_len(cbs));
-  if (ret == NULL) {
-    return NULL;
-  }
-  CBS_skip(cbs, ptr - CBS_data(cbs));
-  return ret;
-}
+int ssl_parse_cert_chain(SSL *ssl, STACK_OF(CRYPTO_BUFFER) **out_buffers,
+                         STACK_OF(X509) **out_x509s, uint8_t *out_alert,
+                         uint8_t *out_leaf_sha256, CBS *cbs) {
+  *out_buffers = NULL;
 
-STACK_OF(X509) *ssl_parse_cert_chain(SSL *ssl, uint8_t *out_alert,
-                                     uint8_t *out_leaf_sha256, CBS *cbs) {
-  STACK_OF(X509) *ret = sk_X509_new_null();
-  if (ret == NULL) {
+  STACK_OF(CRYPTO_BUFFER) *buffers = sk_CRYPTO_BUFFER_new_null();
+  if (buffers == NULL) {
     *out_alert = SSL_AD_INTERNAL_ERROR;
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-    return NULL;
+    return 0;
   }
 
-  X509 *x = NULL;
   CBS certificate_list;
   if (!CBS_get_u24_length_prefixed(cbs, &certificate_list)) {
     *out_alert = SSL_AD_DECODE_ERROR;
@@ -481,36 +469,45 @@
 
   while (CBS_len(&certificate_list) > 0) {
     CBS certificate;
-    if (!CBS_get_u24_length_prefixed(&certificate_list, &certificate)) {
+    if (!CBS_get_u24_length_prefixed(&certificate_list, &certificate) ||
+        CBS_len(&certificate) == 0) {
       *out_alert = SSL_AD_DECODE_ERROR;
       OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_LENGTH_MISMATCH);
       goto err;
     }
 
     /* Retain the hash of the leaf certificate if requested. */
-    if (sk_X509_num(ret) == 0 && out_leaf_sha256 != NULL) {
+    if (sk_CRYPTO_BUFFER_num(buffers) == 0 && out_leaf_sha256 != NULL) {
       SHA256(CBS_data(&certificate), CBS_len(&certificate), out_leaf_sha256);
     }
 
-    x = ssl_parse_x509(&certificate);
-    if (x == NULL || CBS_len(&certificate) != 0) {
-      *out_alert = SSL_AD_DECODE_ERROR;
-      goto err;
-    }
-    if (!sk_X509_push(ret, x)) {
+    CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new_from_CBS(&certificate, NULL);
+    if (buffer == NULL) {
       *out_alert = SSL_AD_INTERNAL_ERROR;
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto err;
     }
-    x = NULL;
+
+    if (!sk_CRYPTO_BUFFER_push(buffers, buffer)) {
+      CRYPTO_BUFFER_free(buffer);
+      *out_alert = SSL_AD_INTERNAL_ERROR;
+      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
   }
 
-  return ret;
+  STACK_OF(X509) *x509s;
+  if (!x509_chain_from_buffers(&x509s, buffers)) {
+    goto err;
+  }
+
+  *out_buffers = buffers;
+  *out_x509s = x509s;
+  return 1;
 
 err:
-  X509_free(x);
-  sk_X509_pop_free(ret, X509_free);
-  return NULL;
+  sk_CRYPTO_BUFFER_pop_free(buffers, CRYPTO_BUFFER_free);
+  return 0;
 }
 
 int ssl_add_cert_to_cbb(CBB *cbb, X509 *x509) {
diff --git a/ssl/ssl_session.c b/ssl/ssl_session.c
index 67e809d..51c2138 100644
--- a/ssl/ssl_session.c
+++ b/ssl/ssl_session.c
@@ -199,6 +199,20 @@
       goto err;
     }
   }
+  if (session->certs != NULL) {
+    new_session->certs = sk_CRYPTO_BUFFER_new_null();
+    if (new_session->certs == NULL) {
+      goto err;
+    }
+    for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(session->certs); i++) {
+      CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(session->certs, i);
+      CRYPTO_BUFFER_up_ref(buffer);
+      if (!sk_CRYPTO_BUFFER_push(new_session->certs, buffer)) {
+        CRYPTO_BUFFER_free(buffer);
+        goto err;
+      }
+    }
+  }
   if (session->x509_peer != NULL) {
     X509_up_ref(session->x509_peer);
     new_session->x509_peer = session->x509_peer;
@@ -326,6 +340,7 @@
   OPENSSL_cleanse(session->master_key, sizeof(session->master_key));
   OPENSSL_cleanse(session->session_id, sizeof(session->session_id));
   X509_free(session->x509_peer);
+  sk_CRYPTO_BUFFER_pop_free(session->certs, CRYPTO_BUFFER_free);
   sk_X509_pop_free(session->x509_chain, X509_free);
   OPENSSL_free(session->tlsext_hostname);
   OPENSSL_free(session->tlsext_tick);
@@ -966,6 +981,61 @@
   }
 }
 
+int x509_chain_from_buffers(STACK_OF(X509) **out,
+                            STACK_OF(CRYPTO_BUFFER) *buffers) {
+  *out = NULL;
+
+  if (sk_CRYPTO_BUFFER_num(buffers) == 0) {
+    return 1;
+  }
+
+  STACK_OF(X509) *ret = sk_X509_new_null();
+  if (ret == NULL) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+    return 0;
+  }
+
+  for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(buffers); i++) {
+    CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(buffers, i);
+    X509 *x509 = X509_parse_from_buffer(buffer);
+    if (x509 == NULL) {
+      OPENSSL_PUT_ERROR(SSL, SSL_AD_DECODE_ERROR);
+      goto err;
+    }
+    if (!sk_X509_push(ret, x509)) {
+      X509_free(x509);
+      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
+      goto err;
+    }
+  }
+
+  *out = ret;
+  return 1;
+
+err:
+  sk_X509_pop_free(ret, X509_free);
+  return 0;
+}
+
+void ssl_session_set_x509_peer(SSL_SESSION *sess,
+                               int chain_should_include_leaf) {
+  sess->x509_chain_should_include_leaf = chain_should_include_leaf;
+  X509_free(sess->x509_peer);
+  sess->x509_peer = NULL;
+
+  if (sk_X509_num(sess->x509_chain) > 0) {
+    if (chain_should_include_leaf) {
+      X509 *leaf = sk_X509_value(sess->x509_chain, 0);
+      X509_up_ref(leaf);
+      sess->x509_peer = leaf;
+    } else {
+      sess->x509_peer = sk_X509_shift(sess->x509_chain);
+      /* |sess->x509_chain| may now be empty, but code still expects it to be
+       * non-NULL. */
+    }
+  }
+}
+
 void SSL_CTX_sess_set_new_cb(SSL_CTX *ctx,
                              int (*cb)(SSL *ssl, SSL_SESSION *session)) {
   ctx->new_session_cb = cb;
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index 9bdaad2..ad4d1f4 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -546,6 +546,81 @@
 
 // kBoringSSLSession is a serialized SSL_SESSION generated from bssl client.
 static const char kBoringSSLSession[] =
+    "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"
+    "n8JzIdPmX4DkjYUiOnMDsWCOrmji9D6X52ASCWg23jrW4kOVWzeBkoEfu43XrVJk"
+    "FleW2V40fsg12DCCA30wggLmoAMCAQICAxK75jANBgkqhkiG9w0BAQUFADBOMQsw"
+    "CQYDVQQGEwJVUzEQMA4GA1UEChMHRXF1aWZheDEtMCsGA1UECxMkRXF1aWZheCBT"
+    "ZWN1cmUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTAyMDUyMTA0MDAwMFoXDTE4"
+    "MDgyMTA0MDAwMFowQjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUdlb1RydXN0IElu"
+    "Yy4xGzAZBgNVBAMTEkdlb1RydXN0IEdsb2JhbCBDQTCCASIwDQYJKoZIhvcNAQEB"
+    "BQADggEPADCCAQoCggEBANrMGGMw/fQXIxpWflvfPGw45HG3eJHUvKHYTPioQ7YD"
+    "6U0hBwiI2lgvZjkpvQV4i5046AW3an5xpObEYKaw74DkiSgPniXW7YPzraaRx5jJ"
+    "Qhg1FJ2tmEaSLk/K8YdDwRaVVy1Q74ktgHpXrfLuX2vSAI25FPgUFTXZwEaje3LI"
+    "kb/JVSvN0Jc+nCZkzN/Ogxlxyk7m1NV7qRnNVd7I7NJeOFPlXE+MLf5QIzb8ZubL"
+    "jqQ5GQC3lQI5kQsO/jgu0R0FmvZNPm8PBx2vLB6PYDni+jZTEznUXiYr2z2oFL0y"
+    "6xgDKFIEceWrMz3hOLsHNoRinHnqFjD0X8Ar6HFr5PkCAwEAAaOB8DCB7TAfBgNV"
+    "HSMEGDAWgBRI5mj5K9KylddH2CMgEE8zmJCf1DAdBgNVHQ4EFgQUwHqYaI2J+6sF"
+    "ZAwRfap9ZbjKzE4wDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwOgYD"
+    "VR0fBDMwMTAvoC2gK4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9zZWN1"
+    "cmVjYS5jcmwwTgYDVR0gBEcwRTBDBgRVHSAAMDswOQYIKwYBBQUHAgEWLWh0dHBz"
+    "Oi8vd3d3Lmdlb3RydXN0LmNvbS9yZXNvdXJjZXMvcmVwb3NpdG9yeTANBgkqhkiG"
+    "9w0BAQUFAAOBgQB24RJuTksWEoYwBrKBCM/wCMfHcX5m7sLt1Dsf//DwyE7WQziw"
+    "uTB9GNBVg6JqyzYRnOhIZqNtf7gT1Ef+i1pcc/yu2RsyGTirlzQUqpbS66McFAhJ"
+    "trvlke+DNusdVm/K2rxzY5Dkf3s+Iss9B+1fOHSc4wNQTqGvmO5h8oQ/Eg==";
+
+// kLeafInChainSession is a serialized SSL_SESSION generated from bssl client.
+// It contains a chain that includes the leaf certificate.
+static const char kLeafInChainSession[] =
     "MIIRwQIBAQICAwMEAsAvBCDdoGxGK26mR+8lM0uq6+k9xYuxPnwAjpcF9n0Yli9R"
     "kQQwbyshfWhdi5XQ1++7n2L1qqrcVlmHBPpr6yknT/u4pUrpQB5FZ7vqvNn8MdHf"
     "9rWgoQYCBFXgs7uiBAICHCCjggR6MIIEdjCCA16gAwIBAgIIf+yfD7Y6UicwDQYJ"
@@ -706,7 +781,8 @@
   }
 
   // Verify the SSL_SESSION decodes.
-  bssl::UniquePtr<SSL_SESSION> session(SSL_SESSION_from_bytes(input.data(), input.size()));
+  bssl::UniquePtr<SSL_SESSION> session(
+      SSL_SESSION_from_bytes(input.data(), input.size()));
   if (!session) {
     fprintf(stderr, "SSL_SESSION_from_bytes failed\n");
     return false;
@@ -775,7 +851,8 @@
   }
 
   // Verify that the SSL_SESSION fails to decode.
-  bssl::UniquePtr<SSL_SESSION> session(SSL_SESSION_from_bytes(input.data(), input.size()));
+  bssl::UniquePtr<SSL_SESSION> session(
+      SSL_SESSION_from_bytes(input.data(), input.size()));
   if (session) {
     fprintf(stderr, "SSL_SESSION_from_bytes unexpectedly succeeded\n");
     return false;
@@ -784,6 +861,79 @@
   return true;
 }
 
+static bool TestLeafInChainSession() {
+  std::vector<uint8_t> input;
+  if (!DecodeBase64(&input, kLeafInChainSession)) {
+    return false;
+  }
+
+  bssl::UniquePtr<SSL_SESSION> session(
+      SSL_SESSION_from_bytes(input.data(), input.size()));
+  if (!session) {
+    fprintf(stderr, "TestLeafInChainSession: failed to decode.\n");
+    return false;
+  }
+
+  // kLeafInChainSession contains a session where the leaf is duplicated in the
+  // certificate chain. |certs| should contain the chain without duplication,
+  // but |x509_chain| should reflect the structure from the serialised session.
+  if (X509_cmp(session->x509_peer, sk_X509_value(session->x509_chain, 0)) !=
+      0) {
+    fprintf(stderr,
+            "TestLeafInChainSession: leaf is not first element of chain.\n");
+    return false;
+  }
+
+  if (sk_X509_num(session->x509_chain) !=
+      sk_CRYPTO_BUFFER_num(session->certs)) {
+    fprintf(
+        stderr,
+        "TestLeafInChainSession: chain length differs from certs length.\n");
+    return false;
+  }
+
+  if (!session->x509_chain_should_include_leaf) {
+    fprintf(stderr,
+            "TestLeafInChainSession: x509_chain_should_include_leaf flag not "
+            "set.\n");
+    return false;
+  }
+
+  size_t encoded_len;
+  bssl::UniquePtr<uint8_t> encoded;
+  uint8_t *encoded_raw;
+  if (!SSL_SESSION_to_bytes(session.get(), &encoded_raw, &encoded_len)) {
+    fprintf(stderr, "TestLeafInChainSession: SSL_SESSION_to_bytes failed\n");
+    return false;
+  }
+  encoded.reset(encoded_raw);
+
+  // After reencoding, the duplication should be eliminated.
+  if (encoded_len >= input.size()) {
+    fprintf(stderr,
+            "TestLeafInChainSession: reencoding didn't end up smaller\n");
+    return false;
+  }
+
+  // But the duplication should be preserved when parsing again.
+  bssl::UniquePtr<SSL_SESSION> session2(
+      SSL_SESSION_from_bytes(encoded_raw, encoded_len));
+  if (!session2) {
+    fprintf(stderr, "TestLeafInChainSession: failed to redecode.\n");
+    return false;
+  }
+
+  if (X509_cmp(session2->x509_peer, sk_X509_value(session2->x509_chain, 0)) !=
+          0 ||
+      sk_X509_num(session2->x509_chain) != sk_X509_num(session->x509_chain)) {
+    fprintf(stderr,
+            "TestLeafInChainSession: redecode didn't preserve duplication.\n");
+    return false;
+  }
+
+  return true;
+}
+
 static bool TestDefaultVersion(uint16_t min_version, uint16_t max_version,
                                const SSL_METHOD *(*method)(void)) {
   bssl::UniquePtr<SSL_CTX> ctx(SSL_CTX_new(method()));
@@ -2827,6 +2977,7 @@
       !TestBadSSL_SESSIONEncoding(kBadSessionExtraField) ||
       !TestBadSSL_SESSIONEncoding(kBadSessionVersion) ||
       !TestBadSSL_SESSIONEncoding(kBadSessionTrailingData) ||
+      !TestLeafInChainSession() ||
       // TODO(svaldez): Update this when TLS 1.3 is enabled by default.
       !TestDefaultVersion(SSL3_VERSION, TLS1_2_VERSION, &TLS_method) ||
       !TestDefaultVersion(SSL3_VERSION, SSL3_VERSION, &SSLv3_method) ||
diff --git a/ssl/tls13_both.c b/ssl/tls13_both.c
index 1a1d8b9..8639089 100644
--- a/ssl/tls13_both.c
+++ b/ssl/tls13_both.c
@@ -177,8 +177,9 @@
       ssl->server && ssl->retain_only_sha256_of_client_certs;
   int ret = 0;
 
-  STACK_OF(X509) *chain = sk_X509_new_null();
-  if (chain == NULL) {
+  STACK_OF(X509) *chain = NULL;
+  STACK_OF(CRYPTO_BUFFER) *buffers = sk_CRYPTO_BUFFER_new_null();
+  if (buffers == NULL) {
     ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
     OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
     goto err;
@@ -193,28 +194,30 @@
   while (CBS_len(&certificate_list) > 0) {
     CBS certificate, extensions;
     if (!CBS_get_u24_length_prefixed(&certificate_list, &certificate) ||
-        !CBS_get_u16_length_prefixed(&certificate_list, &extensions)) {
+        !CBS_get_u16_length_prefixed(&certificate_list, &extensions) ||
+        CBS_len(&certificate) == 0) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
       OPENSSL_PUT_ERROR(SSL, SSL_R_CERT_LENGTH_MISMATCH);
       goto err;
     }
 
     /* Retain the hash of the leaf certificate if requested. */
-    if (sk_X509_num(chain) == 0 && retain_sha256) {
+    if (sk_CRYPTO_BUFFER_num(buffers) == 0 && retain_sha256) {
       SHA256(CBS_data(&certificate), CBS_len(&certificate),
              ssl->s3->new_session->peer_sha256);
     }
 
-    X509 *x = ssl_parse_x509(&certificate);
-    if (x == NULL || CBS_len(&certificate) != 0) {
-      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_DECODE_ERROR);
-      X509_free(x);
-      goto err;
-    }
-    if (!sk_X509_push(chain, x)) {
+    CRYPTO_BUFFER *buffer = CRYPTO_BUFFER_new_from_CBS(&certificate, NULL);
+    if (buffer == NULL) {
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
-      X509_free(x);
+      goto err;
+    }
+
+    if (!sk_CRYPTO_BUFFER_push(buffers, buffer)) {
+      CRYPTO_BUFFER_free(buffer);
+      ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+      OPENSSL_PUT_ERROR(SSL, ERR_R_MALLOC_FAILURE);
       goto err;
     }
 
@@ -253,7 +256,7 @@
         goto err;
       }
 
-      if (sk_X509_num(chain) == 1 &&
+      if (sk_CRYPTO_BUFFER_num(buffers) == 1 &&
           !CBS_stow(&ocsp_response, &ssl->s3->new_session->ocsp_response,
                     &ssl->s3->new_session->ocsp_response_length)) {
         ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
@@ -274,7 +277,7 @@
         goto err;
       }
 
-      if (sk_X509_num(chain) == 1 &&
+      if (sk_CRYPTO_BUFFER_num(buffers) == 1 &&
           !CBS_stow(&sct,
                     &ssl->s3->new_session->tlsext_signed_cert_timestamp_list,
                     &ssl->s3->new_session
@@ -291,7 +294,7 @@
     goto err;
   }
 
-  if (sk_X509_num(chain) == 0) {
+  if (sk_CRYPTO_BUFFER_num(buffers) == 0) {
     if (!allow_anonymous) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PEER_DID_NOT_RETURN_A_CERTIFICATE);
       ssl3_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_CERTIFICATE_REQUIRED);
@@ -309,24 +312,30 @@
 
   ssl->s3->new_session->peer_sha256_valid = retain_sha256;
 
+  if (!x509_chain_from_buffers(&chain, buffers)) {
+    goto err;
+  }
+
   if (!ssl_verify_cert_chain(ssl, &ssl->s3->new_session->verify_result,
                              chain)) {
     goto err;
   }
 
-  X509_free(ssl->s3->new_session->x509_peer);
-  X509 *leaf = sk_X509_value(chain, 0);
-  X509_up_ref(leaf);
-  ssl->s3->new_session->x509_peer = leaf;
-
+  sk_CRYPTO_BUFFER_pop_free(ssl->s3->new_session->certs, CRYPTO_BUFFER_free);
+  ssl->s3->new_session->certs = buffers;
+  buffers = NULL;
   sk_X509_pop_free(ssl->s3->new_session->x509_chain, X509_free);
   ssl->s3->new_session->x509_chain = chain;
   chain = NULL;
 
+  ssl_session_set_x509_peer(ssl->s3->new_session,
+                            1 /* chain should include leaf */);
+
   ret = 1;
 
 err:
   sk_X509_pop_free(chain, X509_free);
+  sk_CRYPTO_BUFFER_pop_free(buffers, CRYPTO_BUFFER_free);
   return ret;
 }
 
diff --git a/ssl/tls13_server.c b/ssl/tls13_server.c
index 626d2ee..d7545f0 100644
--- a/ssl/tls13_server.c
+++ b/ssl/tls13_server.c
@@ -557,6 +557,7 @@
   if (sk_X509_num(ssl->s3->new_session->x509_chain) > 0) {
     X509_free(sk_X509_shift(ssl->s3->new_session->x509_chain));
   }
+  ssl->s3->new_session->x509_chain_should_include_leaf = 0;
 
   hs->state = state_process_client_certificate_verify;
   return ssl_hs_read_message;