Implement server support for delegated credentials.

This implements the server-side of delegated credentials, a proposed
extension for TLS:
https://tools.ietf.org/html/draft-ietf-tls-subcerts-02

Change-Id: I6a29cf1ead87b90aeca225335063aaf190a417ff
Reviewed-on: https://boringssl-review.googlesource.com/c/33666
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/crypto/err/ssl.errordata b/crypto/err/ssl.errordata
index 202d9b8..f62416c 100644
--- a/crypto/err/ssl.errordata
+++ b/crypto/err/ssl.errordata
@@ -75,6 +75,7 @@
 SSL,259,INVALID_ALPN_PROTOCOL
 SSL,158,INVALID_COMMAND
 SSL,256,INVALID_COMPRESSION_LIST
+SSL,301,INVALID_DELEGATED_CREDENTIAL
 SSL,159,INVALID_MESSAGE
 SSL,251,INVALID_OUTER_RECORD_TYPE
 SSL,269,INVALID_SCT_LIST
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index 5cfaae9..52d713a 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -3054,6 +3054,41 @@
                                                        size_t *out_params_len);
 
 
+// Delegated credentials.
+//
+// *** EXPERIMENTAL — PRONE TO CHANGE ***
+//
+// draft-ietf-tls-subcerts is a proposed extension for TLS 1.3 and above that
+// allows an end point to use its certificate to delegate credentials for
+// authentication. If the peer indicates support for this extension, then this
+// host may use a delegated credential to sign the handshake. Once issued,
+// credentials can't be revoked. In order to mitigate the damage in case the
+// credential secret key is compromised, the credential is only valid for a
+// short time (days, hours, or even minutes). This library implements draft-02
+// of the protocol spec.
+//
+// The extension ID has not been assigned; we're using 0xff02 for the time
+// being. Currently only the server side is implemented.
+//
+// Servers configure a DC for use in the handshake via
+// |SSL_set1_delegated_credential|. It must be signed by the host's end-entity
+// certificate as defined in draft-ietf-tls-subcerts-02.
+
+// SSL_set1_delegated_credential configures the delegated credential (DC) that
+// will be sent to the peer for the current connection. |dc| is the DC in wire
+// format, and |pkey| or |key_method| is the corresponding private key.
+// Currently (as of draft-02), only servers may configure a DC to use in the
+// handshake.
+//
+// The DC will only be used if the protocol version is correct and the signature
+// scheme is supported by the peer. If not, the DC will not be negotiated and
+// the handshake will use the private key (or private key method) associated
+// with the certificate.
+OPENSSL_EXPORT int SSL_set1_delegated_credential(
+    SSL *ssl, CRYPTO_BUFFER *dc, EVP_PKEY *pkey,
+    const SSL_PRIVATE_KEY_METHOD *key_method);
+
+
 // QUIC integration.
 //
 // QUIC acts as an underlying transport for the TLS 1.3 handshake. The following
@@ -4934,6 +4969,7 @@
 #define SSL_R_QUIC_INTERNAL_ERROR 298
 #define SSL_R_WRONG_ENCRYPTION_LEVEL_RECEIVED 299
 #define SSL_R_TOO_MUCH_READ_EARLY_DATA 300
+#define SSL_R_INVALID_DELEGATED_CREDENTIAL 301
 #define SSL_R_SSLV3_ALERT_CLOSE_NOTIFY 1000
 #define SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE 1010
 #define SSL_R_SSLV3_ALERT_BAD_RECORD_MAC 1020
diff --git a/include/openssl/tls1.h b/include/openssl/tls1.h
index 937be6b..384d102 100644
--- a/include/openssl/tls1.h
+++ b/include/openssl/tls1.h
@@ -231,6 +231,10 @@
 // ExtensionType value from RFC5746
 #define TLSEXT_TYPE_renegotiate 0xff01
 
+// ExtensionType value from draft-ietf-tls-subcerts. This is not an IANA defined
+// extension number.
+#define TLSEXT_TYPE_delegated_credential 0xff02
+
 // ExtensionType value from RFC6962
 #define TLSEXT_TYPE_certificate_timestamp 18
 
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 091ed44..89be48f 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -135,6 +135,7 @@
       cert_request(false),
       certificate_status_expected(false),
       ocsp_stapling_requested(false),
+      delegated_credential_requested(false),
       should_ack_sni(false),
       in_false_start(false),
       in_early_data(false),
diff --git a/ssl/internal.h b/ssl/internal.h
index 1116bad..158a233 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1370,6 +1370,49 @@
   handback_after_handshake,
 };
 
+
+// Delegated credentials.
+
+// This structure stores a delegated credential (DC) as defined by
+// draft-ietf-tls-subcerts-02.
+struct DC {
+  static constexpr bool kAllowUniquePtr = true;
+  ~DC();
+
+  // Dup returns a copy of this DC and takes references to |raw| and |pkey|.
+  UniquePtr<DC> Dup();
+
+  // Parse parses the delegated credential stored in |in|. If successful it
+  // returns the parsed structure, otherwise it returns |nullptr| and sets
+  // |*out_alert|.
+  static UniquePtr<DC> Parse(CRYPTO_BUFFER *in, uint8_t *out_alert);
+
+  // raw is the delegated credential encoded as specified in draft-ietf-tls-
+  // subcerts-02.
+  UniquePtr<CRYPTO_BUFFER> raw;
+
+  // expected_cert_verify_algorithm is the signature scheme of the DC public
+  // key.
+  uint16_t expected_cert_verify_algorithm = 0;
+
+  // expected_version is the protocol in which the DC must be used.
+  uint16_t expected_version = 0;
+
+  // pkey is the public key parsed from |public_key|.
+  UniquePtr<EVP_PKEY> pkey;
+
+ private:
+  friend DC* New<DC>();
+  DC();
+};
+
+// ssl_signing_with_dc returns true if the peer has indicated support for
+// delegated credentials and this host has sent a delegated credential in
+// response. If this is true then we've committed to using the DC in the
+// handshake.
+bool ssl_signing_with_dc(const SSL_HANDSHAKE *hs);
+
+
 struct SSL_HANDSHAKE {
   explicit SSL_HANDSHAKE(SSL *ssl);
   ~SSL_HANDSHAKE();
@@ -1541,6 +1584,10 @@
   // ocsp_stapling_requested is true if a client requested OCSP stapling.
   bool ocsp_stapling_requested : 1;
 
+  // delegated_credential_requested is true if the peer indicated support for
+  // the delegated credential extension.
+  bool delegated_credential_requested : 1;
+
   // should_ack_sni is used by a server and indicates that the SNI extension
   // should be echoed in the ServerHello.
   bool should_ack_sni : 1;
@@ -1786,6 +1833,15 @@
 // supported. It returns true on success and false on error.
 bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs, uint16_t *out);
 
+// tls1_get_peer_verify_algorithms returns the signature schemes for which the
+// peer indicated support.
+//
+// NOTE: The related function |SSL_get0_peer_verify_algorithms| only has
+// well-defined behavior during the callbacks set by |SSL_CTX_set_cert_cb| and
+// |SSL_CTX_set_client_cert_cb|, or when the handshake is paused because of
+// them.
+Span<const uint16_t> tls1_get_peer_verify_algorithms(const SSL_HANDSHAKE *hs);
+
 // tls12_add_verify_sigalgs adds the signature algorithms acceptable for the
 // peer signature to |out|. It returns true on success and false on error. If
 // |for_certs| is true, the potentially more restrictive list of algorithms for
@@ -1879,6 +1935,19 @@
   // ticket key. Only sessions with a matching value will be accepted.
   uint8_t sid_ctx_length = 0;
   uint8_t sid_ctx[SSL_MAX_SID_CTX_LENGTH] = {0};
+
+  // Delegated credentials.
+
+  // dc is the delegated credential to send to the peer (if requested).
+  UniquePtr<DC> dc = nullptr;
+
+  // dc_privatekey is used instead of |privatekey| or |key_method| to
+  // authenticate the host if a delegated credential is used in the handshake.
+  UniquePtr<EVP_PKEY> dc_privatekey = nullptr;
+
+  // dc_key_method, if not NULL, is used instead of |dc_privatekey| to
+  // authenticate the host.
+  const SSL_PRIVATE_KEY_METHOD *dc_key_method = nullptr;
 };
 
 // |SSL_PROTOCOL_METHOD| abstracts between TLS and DTLS.
diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc
index 9551810..d23e1e6 100644
--- a/ssl/ssl_cert.cc
+++ b/ssl/ssl_cert.cc
@@ -180,6 +180,16 @@
   ret->sid_ctx_length = cert->sid_ctx_length;
   OPENSSL_memcpy(ret->sid_ctx, cert->sid_ctx, sizeof(ret->sid_ctx));
 
+  if (cert->dc) {
+    ret->dc = cert->dc->Dup();
+    if (!ret->dc) {
+       return nullptr;
+    }
+  }
+
+  ret->dc_privatekey = UpRef(cert->dc_privatekey);
+  ret->dc_key_method = cert->dc_key_method;
+
   return ret;
 }
 
@@ -194,6 +204,10 @@
   cert->chain.reset();
   cert->privatekey.reset();
   cert->key_method = nullptr;
+
+  cert->dc.reset();
+  cert->dc_privatekey.reset();
+  cert->dc_key_method = nullptr;
 }
 
 static void ssl_cert_set_cert_cb(CERT *cert, int (*cb)(SSL *ssl, void *arg),
@@ -741,10 +755,152 @@
   CRYPTO_BUFFER_init_CBS(
       sk_CRYPTO_BUFFER_value(hs->config->cert->chain.get(), 0), &leaf);
 
-  hs->local_pubkey = ssl_cert_parse_pubkey(&leaf);
+  if (ssl_signing_with_dc(hs)) {
+    hs->local_pubkey = UpRef(hs->config->cert->dc->pkey);
+  } else {
+    hs->local_pubkey = ssl_cert_parse_pubkey(&leaf);
+  }
   return hs->local_pubkey != NULL;
 }
 
+
+// Delegated credentials.
+
+DC::DC() = default;
+DC::~DC() = default;
+
+UniquePtr<DC> DC::Dup() {
+  bssl::UniquePtr<DC> ret = MakeUnique<DC>();
+  if (!ret) {
+    return nullptr;
+  }
+
+  ret->raw = UpRef(raw);
+  ret->expected_cert_verify_algorithm = expected_cert_verify_algorithm;
+  ret->expected_version = expected_version;
+  ret->pkey = UpRef(pkey);
+  return ret;
+}
+
+// static
+UniquePtr<DC> DC::Parse(CRYPTO_BUFFER *in, uint8_t *out_alert) {
+  UniquePtr<DC> dc = MakeUnique<DC>();
+  if (!dc) {
+    *out_alert = SSL_AD_INTERNAL_ERROR;
+    return nullptr;
+  }
+
+  dc->raw = UpRef(in);
+
+  CBS pubkey, deleg, sig;
+  uint32_t valid_time;
+  uint16_t algorithm;
+  CRYPTO_BUFFER_init_CBS(dc->raw.get(), &deleg);
+  if (!CBS_get_u32(&deleg, &valid_time) ||
+      !CBS_get_u16(&deleg, &dc->expected_cert_verify_algorithm) ||
+      !CBS_get_u16(&deleg, &dc->expected_version) ||
+      !CBS_get_u24_length_prefixed(&deleg, &pubkey) ||
+      !CBS_get_u16(&deleg, &algorithm) ||
+      !CBS_get_u16_length_prefixed(&deleg, &sig) ||
+      CBS_len(&deleg) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return nullptr;
+  }
+
+  dc->pkey.reset(EVP_parse_public_key(&pubkey));
+  if (dc->pkey == nullptr) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    *out_alert = SSL_AD_DECODE_ERROR;
+    return nullptr;
+  }
+
+  return dc;
+}
+
+// ssl_can_serve_dc returns true if the host has configured a DC that it can
+// serve in the handshake. Specifically, it checks that a DC has been
+// configured, that the DC protocol version is the same as the negotiated
+// protocol version, and that the DC signature algorithm is supported by the
+// peer.
+static bool ssl_can_serve_dc(const SSL_HANDSHAKE *hs) {
+  // Check that a DC has been configured.
+  const CERT *cert = hs->config->cert.get();
+  if (cert->dc == nullptr ||
+      cert->dc->raw == nullptr ||
+      (cert->dc_privatekey == nullptr && cert->dc_key_method == nullptr)) {
+    return false;
+  }
+
+  // Check that the negotiated version matches the protocol version to which the
+  // DC is bound, and that 1.3 or higher has been negotiated.
+  //
+  // NOTE: We use |hs->ssl->version| for checking the DC expected version. We
+  // don't call |ssl_protocol_version| because we need the version sent on the
+  // wire. For example, a delegated credential can be bound to a draft of TLS
+  // 1.3.
+  const DC *dc = cert->dc.get();
+  assert(hs->ssl->s3->have_version);
+  if (hs->ssl->version != dc->expected_version ||
+      ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) {
+    return false;
+  }
+
+  // Check that the DC signature algorithm is supported by the peer.
+  Span<const uint16_t> peer_sigalgs = tls1_get_peer_verify_algorithms(hs);
+  bool sigalg_found = false;
+  for (uint16_t peer_sigalg : peer_sigalgs) {
+    if (dc->expected_cert_verify_algorithm == peer_sigalg) {
+      sigalg_found = true;
+      break;
+    }
+  }
+
+  return sigalg_found;
+}
+
+bool ssl_signing_with_dc(const SSL_HANDSHAKE *hs) {
+  // As of draft-ietf-tls-subcert-02, only the server may use delegated
+  // credentials to authenticate itself.
+  return hs->ssl->server &&
+         hs->delegated_credential_requested &&
+         ssl_can_serve_dc(hs);
+}
+
+static int cert_set_dc(CERT *cert, CRYPTO_BUFFER *const raw, EVP_PKEY *privkey,
+                       const SSL_PRIVATE_KEY_METHOD *key_method) {
+  if (privkey == nullptr && key_method == nullptr) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_PASSED_NULL_PARAMETER);
+    return 0;
+  }
+
+  if (privkey != nullptr && key_method != nullptr) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD);
+    return 0;
+  }
+
+  uint8_t alert;
+  UniquePtr<DC> dc = DC::Parse(raw, &alert);
+  if (dc == nullptr) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_DELEGATED_CREDENTIAL);
+    return 0;
+  }
+
+  if (privkey) {
+    // Check that the public and private keys match.
+    if (!ssl_compare_public_and_private_key(dc->pkey.get(), privkey)) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_AND_PRIVATE_KEY_MISMATCH);
+      return 0;
+    }
+  }
+
+  cert->dc = std::move(dc);
+  cert->dc_privatekey = UpRef(privkey);
+  cert->dc_key_method = key_method;
+
+  return 1;
+}
+
 BSSL_NAMESPACE_END
 
 using namespace bssl;
@@ -870,3 +1026,12 @@
   ssl->ctx->x509_method->ssl_flush_cached_client_CA(ssl->config.get());
   ssl->config->client_CA.reset(name_list);
 }
+
+int SSL_set1_delegated_credential(SSL *ssl, CRYPTO_BUFFER *dc, EVP_PKEY *pkey,
+                                  const SSL_PRIVATE_KEY_METHOD *key_method) {
+  if (!ssl->config) {
+    return 0;
+  }
+
+  return cert_set_dc(ssl->config->cert.get(), dc, pkey, key_method);
+}
diff --git a/ssl/ssl_privkey.cc b/ssl/ssl_privkey.cc
index d45670a..1ddb1b1 100644
--- a/ssl/ssl_privkey.cc
+++ b/ssl/ssl_privkey.cc
@@ -134,8 +134,13 @@
 }
 
 bool ssl_has_private_key(const SSL_HANDSHAKE *hs) {
-  return (hs->config->cert->privatekey != nullptr ||
-          hs->config->cert->key_method != nullptr);
+  if (hs->config->cert->privatekey != nullptr ||
+      hs->config->cert->key_method != nullptr ||
+      ssl_signing_with_dc(hs)) {
+    return true;
+  }
+
+  return false;
 }
 
 static bool pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
@@ -196,13 +201,20 @@
     SSL_HANDSHAKE *hs, uint8_t *out, size_t *out_len, size_t max_out,
     uint16_t sigalg, Span<const uint8_t> in) {
   SSL *const ssl = hs->ssl;
-  if (hs->config->cert->key_method != NULL) {
+  const SSL_PRIVATE_KEY_METHOD *key_method = hs->config->cert->key_method;
+  EVP_PKEY *privatekey = hs->config->cert->privatekey.get();
+  if (ssl_signing_with_dc(hs)) {
+    key_method = hs->config->cert->dc_key_method;
+    privatekey = hs->config->cert->dc_privatekey.get();
+  }
+
+  if (key_method != NULL) {
     enum ssl_private_key_result_t ret;
     if (hs->pending_private_key_op) {
-      ret = hs->config->cert->key_method->complete(ssl, out, out_len, max_out);
+      ret = key_method->complete(ssl, out, out_len, max_out);
     } else {
-      ret = hs->config->cert->key_method->sign(ssl, out, out_len, max_out,
-                                               sigalg, in.data(), in.size());
+      ret = key_method->sign(ssl, out, out_len, max_out,
+                             sigalg, in.data(), in.size());
     }
     if (ret == ssl_private_key_failure) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PRIVATE_KEY_OPERATION_FAILED);
@@ -213,8 +225,7 @@
 
   *out_len = max_out;
   ScopedEVP_MD_CTX ctx;
-  if (!setup_ctx(ssl, ctx.get(), hs->config->cert->privatekey.get(), sigalg,
-                 false /* sign */) ||
+  if (!setup_ctx(ssl, ctx.get(), privatekey, sigalg, false /* sign */) ||
       !EVP_DigestSign(ctx.get(), out, out_len, in.data(), in.size())) {
     return ssl_private_key_failure;
   }
diff --git a/ssl/t1_lib.cc b/ssl/t1_lib.cc
index 5e65f81..140ab45 100644
--- a/ssl/t1_lib.cc
+++ b/ssl/t1_lib.cc
@@ -2715,6 +2715,36 @@
   return true;
 }
 
+// Delegated credentials.
+//
+// https://tools.ietf.org/html/draft-ietf-tls-subcerts
+
+static bool ext_delegated_credential_add_clienthello(SSL_HANDSHAKE *hs,
+                                                     CBB *out) {
+  return true;
+}
+
+static bool ext_delegated_credential_parse_clienthello(SSL_HANDSHAKE *hs,
+                                                       uint8_t *out_alert,
+                                                       CBS *contents) {
+  assert(TLSEXT_TYPE_delegated_credential == 0xff02);
+  // TODO: Check that the extension is empty.
+  //
+  // As of draft-02, the client sends an empty extension in order indicate
+  // support for delegated credentials. This could change, however, since the
+  // spec is not yet finalized. This assertion is here to remind us to enforce
+  // this check once the extension ID is assigned.
+
+  if (contents == nullptr || ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) {
+    // Don't use delegated credentials unless we're negotiating TLS 1.3 or
+    // higher.
+    return true;
+  }
+
+  hs->delegated_credential_requested = true;
+  return true;
+}
+
 // Certificate compression
 
 static bool cert_compression_add_clienthello(SSL_HANDSHAKE *hs, CBB *out) {
@@ -3003,6 +3033,14 @@
     cert_compression_parse_clienthello,
     cert_compression_add_serverhello,
   },
+  {
+    TLSEXT_TYPE_delegated_credential,
+    NULL,
+    ext_delegated_credential_add_clienthello,
+    forbid_parse_serverhello,
+    ext_delegated_credential_parse_clienthello,
+    dont_add_serverhello,
+  },
 };
 
 #define kNumExtensions (sizeof(kExtensions) / sizeof(struct tls_extension))
@@ -3629,6 +3667,7 @@
 bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs, uint16_t *out) {
   SSL *const ssl = hs->ssl;
   CERT *cert = hs->config->cert.get();
+  DC *dc = cert->dc.get();
 
   // Before TLS 1.2, the signature algorithm isn't negotiated as part of the
   // handshake.
@@ -3641,19 +3680,13 @@
   }
 
   Span<const uint16_t> sigalgs = kSignSignatureAlgorithms;
-  if (!cert->sigalgs.empty()) {
+  if (ssl_signing_with_dc(hs)) {
+    sigalgs = MakeConstSpan(&dc->expected_cert_verify_algorithm, 1);
+  } else if (!cert->sigalgs.empty()) {
     sigalgs = cert->sigalgs;
   }
 
-  Span<const uint16_t> peer_sigalgs = hs->peer_sigalgs;
-  if (peer_sigalgs.empty() && ssl_protocol_version(ssl) < TLS1_3_VERSION) {
-    // If the client didn't specify any signature_algorithms extension then
-    // we can assume that it supports SHA1. See
-    // http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
-    static const uint16_t kDefaultPeerAlgorithms[] = {SSL_SIGN_RSA_PKCS1_SHA1,
-                                                      SSL_SIGN_ECDSA_SHA1};
-    peer_sigalgs = kDefaultPeerAlgorithms;
-  }
+  Span<const uint16_t> peer_sigalgs = tls1_get_peer_verify_algorithms(hs);
 
   for (uint16_t sigalg : sigalgs) {
     // SSL_SIGN_RSA_PKCS1_MD5_SHA1 is an internal value and should never be
@@ -3675,6 +3708,19 @@
   return false;
 }
 
+Span<const uint16_t> tls1_get_peer_verify_algorithms(const SSL_HANDSHAKE *hs) {
+  Span<const uint16_t> peer_sigalgs = hs->peer_sigalgs;
+  if (peer_sigalgs.empty() && ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) {
+    // If the client didn't specify any signature_algorithms extension then
+    // we can assume that it supports SHA1. See
+    // http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
+    static const uint16_t kDefaultPeerAlgorithms[] = {SSL_SIGN_RSA_PKCS1_SHA1,
+                                                      SSL_SIGN_ECDSA_SHA1};
+    peer_sigalgs = kDefaultPeerAlgorithms;
+  }
+  return peer_sigalgs;
+}
+
 bool tls1_verify_channel_id(SSL_HANDSHAKE *hs, const SSLMessage &msg) {
   SSL *const ssl = hs->ssl;
   // A Channel ID handshake message is structured to contain multiple
diff --git a/ssl/test/runner/cipher_suites.go b/ssl/test/runner/cipher_suites.go
index 3246f0b..e827c52 100644
--- a/ssl/test/runner/cipher_suites.go
+++ b/ssl/test/runner/cipher_suites.go
@@ -33,7 +33,7 @@
 
 	// This method may not be called if the server doesn't send a
 	// ServerKeyExchange message.
-	processServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg, *x509.Certificate, *serverKeyExchangeMsg) error
+	processServerKeyExchange(*Config, *clientHelloMsg, *serverHelloMsg, crypto.PublicKey, *serverKeyExchangeMsg) error
 	generateClientKeyExchange(*Config, *clientHelloMsg, *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error)
 
 	// peerSignatureAlgorithm returns the signature algorithm used by the
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 86f5a2c..bbcacf5 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -125,6 +125,7 @@
 	extensionRenegotiationInfo          uint16 = 0xff01
 	extensionQUICTransportParams        uint16 = 0xffa5 // draft-ietf-quic-tls-13
 	extensionChannelID                  uint16 = 30032  // not IANA assigned
+	extensionDelegatedCredentials       uint16 = 0xff02 // not IANA assigned
 )
 
 // TLS signaling cipher suite values
@@ -1636,6 +1637,18 @@
 	// ExpectKeyShares, if not nil, lists (in order) the curves that a ClientHello
 	// should have key shares for.
 	ExpectedKeyShares []CurveID
+
+	// ExpectDelegatedCredentials, if true, requires that the handshake present
+	// delegated credentials.
+	ExpectDelegatedCredentials bool
+
+	// FailIfDelegatedCredentials, if true, causes a handshake failure if the
+	// server returns delegated credentials.
+	FailIfDelegatedCredentials bool
+
+	// DisableDelegatedCredentials, if true, disables client support for delegated
+	// credentials.
+	DisableDelegatedCredentials bool
 }
 
 func (c *Config) serverInit() {
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index 8003514..2472fc9 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -32,6 +32,8 @@
 	masterSecret  []byte
 	session       *ClientSessionState
 	finishedBytes []byte
+	peerPublicKey crypto.PublicKey
+	skxAlgo       signatureAlgorithm
 }
 
 func mapClientHelloVersion(vers uint16, isDTLS bool) uint16 {
@@ -126,6 +128,7 @@
 		pskBinderFirst:          c.config.Bugs.PSKBinderFirst,
 		omitExtensions:          c.config.Bugs.OmitExtensions,
 		emptyExtensions:         c.config.Bugs.EmptyExtensions,
+		delegatedCredentials:    !c.config.Bugs.DisableDelegatedCredentials,
 	}
 
 	if maxVersion >= VersionTLS13 {
@@ -978,7 +981,6 @@
 		if err := hs.verifyCertificates(certMsg); err != nil {
 			return err
 		}
-		leaf := c.peerCertificates[0]
 		c.ocspResponse = certMsg.certificates[0].ocspResponse
 		c.sctList = certMsg.certificates[0].sctList
 
@@ -994,7 +996,7 @@
 
 		c.peerSignatureAlgorithm = certVerifyMsg.signatureAlgorithm
 		input := hs.finishedHash.certificateVerifyInput(serverCertificateVerifyContextTLS13)
-		err = verifyMessage(c.vers, getCertificatePublicKey(leaf), c.config, certVerifyMsg.signatureAlgorithm, input, certVerifyMsg.signature)
+		err = verifyMessage(c.vers, hs.peerPublicKey, c.config, certVerifyMsg.signatureAlgorithm, input, certVerifyMsg.signature)
 		if err != nil {
 			return err
 		}
@@ -1233,7 +1235,7 @@
 	skx, ok := msg.(*serverKeyExchangeMsg)
 	if ok {
 		hs.writeServerHash(skx.marshal())
-		err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, leaf, skx)
+		err = keyAgreement.processServerKeyExchange(c.config, hs.hello, hs.serverHello, hs.peerPublicKey, skx)
 		if err != nil {
 			c.sendAlert(alertUnexpectedMessage)
 			return err
@@ -1377,6 +1379,23 @@
 	return nil
 }
 
+// delegatedCredentialSignedMessage returns the bytes that are signed in order
+// to authenticate a delegated credential.
+func delegatedCredentialSignedMessage(credBytes []byte, algorithm signatureAlgorithm, leafDER []byte) []byte {
+	// https://tools.ietf.org/html/draft-ietf-tls-subcerts-02#section-3
+	ret := make([]byte, 64, 128)
+	for i := range ret {
+		ret[i] = 0x20
+	}
+
+	ret = append(ret, []byte("TLS, server delegated credentials\x00")...)
+	ret = append(ret, leafDER...)
+	ret = append(ret, byte(algorithm>>8), byte(algorithm))
+	ret = append(ret, credBytes...)
+
+	return ret
+}
+
 func (hs *clientHandshakeState) verifyCertificates(certMsg *certificateMsg) error {
 	c := hs.c
 
@@ -1385,6 +1404,7 @@
 		return errors.New("tls: no certificates sent")
 	}
 
+	var dc *delegatedCredential
 	certs := make([]*x509.Certificate, len(certMsg.certificates))
 	for i, certEntry := range certMsg.certificates {
 		cert, err := x509.ParseCertificate(certEntry.data)
@@ -1393,6 +1413,22 @@
 			return errors.New("tls: failed to parse certificate from server: " + err.Error())
 		}
 		certs[i] = cert
+
+		if certEntry.delegatedCredential != nil {
+			if c.config.Bugs.FailIfDelegatedCredentials {
+				c.sendAlert(alertIllegalParameter)
+				return errors.New("tls: unexpected delegated credential")
+			}
+			if i != 0 {
+				c.sendAlert(alertIllegalParameter)
+				return errors.New("tls: non-leaf certificate has a delegated credential")
+			}
+			if c.config.Bugs.DisableDelegatedCredentials {
+				c.sendAlert(alertIllegalParameter)
+				return errors.New("tls: server sent delegated credential without it being requested")
+			}
+			dc = certEntry.delegatedCredential
+		}
 	}
 
 	if !c.config.InsecureSkipVerify {
@@ -1417,16 +1453,50 @@
 		}
 	}
 
-	publicKey := getCertificatePublicKey(certs[0])
-	switch publicKey.(type) {
+	leafPublicKey := getCertificatePublicKey(certs[0])
+	switch leafPublicKey.(type) {
 	case *rsa.PublicKey, *ecdsa.PublicKey, ed25519.PublicKey:
 		break
 	default:
 		c.sendAlert(alertUnsupportedCertificate)
-		return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", publicKey)
+		return fmt.Errorf("tls: server's certificate contains an unsupported type of public key: %T", leafPublicKey)
 	}
 
 	c.peerCertificates = certs
+
+	if dc != nil {
+		// Note that this doesn't check a) the delegated credential temporal
+		// validity nor b) that the certificate has the special OID asserted.
+		if dc.expectedTLSVersion != c.wireVersion {
+			c.sendAlert(alertBadCertificate)
+			return errors.New("tls: delegated credential is for wrong TLS version")
+		}
+
+		hs.skxAlgo = dc.expectedCertVerifyAlgo
+
+		var err error
+		if hs.peerPublicKey, err = x509.ParsePKIXPublicKey(dc.pkixPublicKey); err != nil {
+			c.sendAlert(alertBadCertificate)
+			return errors.New("tls: failed to parse public key from delegated credential: " + err.Error())
+		}
+
+		verifier, err := getSigner(c.vers, hs.peerPublicKey, c.config, dc.algorithm, true)
+		if err != nil {
+			c.sendAlert(alertBadCertificate)
+			return errors.New("tls: failed to get verifier for delegated credential: " + err.Error())
+		}
+
+		if err := verifier.verifyMessage(leafPublicKey, delegatedCredentialSignedMessage(dc.signedBytes, dc.algorithm, certs[0].Raw), dc.signature); err != nil {
+			c.sendAlert(alertBadCertificate)
+			return errors.New("tls: failed to verify delegated credential: " + err.Error())
+		}
+	} else if c.config.Bugs.ExpectDelegatedCredentials {
+		c.sendAlert(alertInternalError)
+		return errors.New("tls: delegated credentials missing")
+	} else {
+		hs.peerPublicKey = leafPublicKey
+	}
+
 	return nil
 }
 
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index 823c6c8..8ff1def 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -297,6 +297,7 @@
 	emptyExtensions         bool
 	pad                     int
 	compressedCertAlgs      []uint16
+	delegatedCredentials    bool
 }
 
 func (m *clientHelloMsg) equal(i interface{}) bool {
@@ -350,7 +351,8 @@
 		m.omitExtensions == m1.omitExtensions &&
 		m.emptyExtensions == m1.emptyExtensions &&
 		m.pad == m1.pad &&
-		eqUint16s(m.compressedCertAlgs, m1.compressedCertAlgs)
+		eqUint16s(m.compressedCertAlgs, m1.compressedCertAlgs) &&
+		m.delegatedCredentials == m1.delegatedCredentials
 }
 
 func (m *clientHelloMsg) marshalKeyShares(bb *byteBuilder) {
@@ -592,6 +594,10 @@
 			algIDs.addU16(v)
 		}
 	}
+	if m.delegatedCredentials {
+		extensions.addU16(extensionDelegatedCredentials)
+		extensions.addU16(0) // Length is always 0
+	}
 	// The PSK extension must be last. See https://tools.ietf.org/html/rfc8446#section-4.2.11
 	if len(m.pskIdentities) > 0 && !m.pskBinderFirst {
 		extensions.addU16(extensionPreSharedKey)
@@ -717,6 +723,7 @@
 	m.alpnProtocols = nil
 	m.extendedMasterSecret = false
 	m.customExtension = ""
+	m.delegatedCredentials = false
 
 	if len(reader) == 0 {
 		// ClientHello is optionally followed by extension data
@@ -947,6 +954,11 @@
 					return false
 				}
 			}
+		case extensionDelegatedCredentials:
+			if len(body) != 0 {
+				return false
+			}
+			m.delegatedCredentials = true
 		}
 
 		if isGREASEValue(extension) {
@@ -1602,6 +1614,18 @@
 	sctList             []byte
 	duplicateExtensions bool
 	extraExtension      []byte
+	delegatedCredential *delegatedCredential
+}
+
+type delegatedCredential struct {
+	// https://tools.ietf.org/html/draft-ietf-tls-subcerts-02#section-3
+	signedBytes            []byte
+	lifetimeSecs           uint32
+	expectedCertVerifyAlgo signatureAlgorithm
+	expectedTLSVersion     uint16
+	pkixPublicKey          []byte
+	algorithm              signatureAlgorithm
+	signature              []byte
 }
 
 type certificateMsg struct {
@@ -1700,6 +1724,30 @@
 					}
 				case extensionSignedCertificateTimestamp:
 					cert.sctList = []byte(body)
+				case extensionDelegatedCredentials:
+					// https://tools.ietf.org/html/draft-ietf-tls-subcerts-02#section-3
+					if cert.delegatedCredential != nil {
+						return false
+					}
+
+					dc := new(delegatedCredential)
+					origBody := body
+					var expectedCertVerifyAlgo, algorithm uint16
+
+					if !body.readU32(&dc.lifetimeSecs) ||
+						!body.readU16(&expectedCertVerifyAlgo) ||
+						!body.readU16(&dc.expectedTLSVersion) ||
+						!body.readU24LengthPrefixedBytes(&dc.pkixPublicKey) ||
+						!body.readU16(&algorithm) ||
+						!body.readU16LengthPrefixedBytes(&dc.signature) ||
+						len(body) != 0 {
+						return false
+					}
+
+					dc.expectedCertVerifyAlgo = signatureAlgorithm(expectedCertVerifyAlgo)
+					dc.algorithm = signatureAlgorithm(algorithm)
+					dc.signedBytes = []byte(origBody)[:4+2+2+3+len(dc.pkixPublicKey)]
+					cert.delegatedCredential = dc
 				default:
 					return false
 				}
diff --git a/ssl/test/runner/key_agreement.go b/ssl/test/runner/key_agreement.go
index f40552d..13e78bc 100644
--- a/ssl/test/runner/key_agreement.go
+++ b/ssl/test/runner/key_agreement.go
@@ -5,6 +5,7 @@
 package runner
 
 import (
+	"crypto"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rsa"
@@ -133,7 +134,7 @@
 	return preMasterSecret, nil
 }
 
-func (ka *rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
+func (ka *rsaKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, key crypto.PublicKey, skx *serverKeyExchangeMsg) error {
 	return errors.New("tls: unexpected ServerKeyExchange")
 }
 
@@ -456,7 +457,7 @@
 // to authenticate the ServerKeyExchange parameters.
 type keyAgreementAuthentication interface {
 	signParameters(config *Config, cert *Certificate, clientHello *clientHelloMsg, hello *serverHelloMsg, params []byte) (*serverKeyExchangeMsg, error)
-	verifyParameters(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, params []byte, sig []byte) error
+	verifyParameters(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, key crypto.PublicKey, params []byte, sig []byte) error
 }
 
 // nilKeyAgreementAuthentication does not authenticate the key
@@ -469,7 +470,7 @@
 	return skx, nil
 }
 
-func (ka *nilKeyAgreementAuthentication) verifyParameters(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, params []byte, sig []byte) error {
+func (ka *nilKeyAgreementAuthentication) verifyParameters(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, key crypto.PublicKey, params []byte, sig []byte) error {
 	return nil
 }
 
@@ -529,9 +530,8 @@
 	return skx, nil
 }
 
-func (ka *signedKeyAgreement) verifyParameters(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, params []byte, sig []byte) error {
+func (ka *signedKeyAgreement) verifyParameters(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, publicKey crypto.PublicKey, params []byte, sig []byte) error {
 	// The peer's key must match the cipher type.
-	publicKey := getCertificatePublicKey(cert)
 	switch ka.keyType {
 	case keyTypeECDSA:
 		_, edsaOk := publicKey.(*ecdsa.PublicKey)
@@ -646,7 +646,7 @@
 	return ka.curve.finish(ckx.ciphertext[1:])
 }
 
-func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
+func (ka *ecdheKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, key crypto.PublicKey, skx *serverKeyExchangeMsg) error {
 	if len(skx.key) < 4 {
 		return errServerKeyExchange
 	}
@@ -671,7 +671,7 @@
 	// Check the signature.
 	serverECDHParams := skx.key[:4+publicLen]
 	sig := skx.key[4+publicLen:]
-	return ka.auth.verifyParameters(config, clientHello, serverHello, cert, serverECDHParams, sig)
+	return ka.auth.verifyParameters(config, clientHello, serverHello, key, serverECDHParams, sig)
 }
 
 func (ka *ecdheKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
@@ -722,7 +722,7 @@
 	return nil, nil
 }
 
-func (ka *nilKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
+func (ka *nilKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, key crypto.PublicKey, skx *serverKeyExchangeMsg) error {
 	if len(skx.key) != 0 {
 		return errServerKeyExchange
 	}
@@ -820,7 +820,7 @@
 	return makePSKPremaster(otherSecret, config.PreSharedKey), nil
 }
 
-func (ka *pskKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, cert *x509.Certificate, skx *serverKeyExchangeMsg) error {
+func (ka *pskKeyAgreement) processServerKeyExchange(config *Config, clientHello *clientHelloMsg, serverHello *serverHelloMsg, key crypto.PublicKey, skx *serverKeyExchangeMsg) error {
 	if len(skx.key) < 2 {
 		return errServerKeyExchange
 	}
@@ -833,7 +833,7 @@
 	// Process the remainder of the ServerKeyExchange.
 	newSkx := new(serverKeyExchangeMsg)
 	newSkx.key = skx.key[2+identityLen:]
-	return ka.base.processServerKeyExchange(config, clientHello, serverHello, cert, newSkx)
+	return ka.base.processServerKeyExchange(config, clientHello, serverHello, key, newSkx)
 }
 
 func (ka *pskKeyAgreement) generateClientKeyExchange(config *Config, clientHello *clientHelloMsg, cert *x509.Certificate) ([]byte, *clientKeyExchangeMsg, error) {
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index b11663a..f0587f4 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -16,9 +16,11 @@
 
 import (
 	"bytes"
+	"crypto"
 	"crypto/ecdsa"
 	"crypto/elliptic"
 	"crypto/rand"
+	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509/pkix"
 	"encoding/base64"
@@ -246,6 +248,145 @@
 	garbageCertificate.PrivateKey = rsaCertificate.PrivateKey
 }
 
+// delegatedCredentialConfig specifies the shape of a delegated credential, not
+// including the keys themselves.
+type delegatedCredentialConfig struct {
+	// lifetime is the amount of time, from the notBefore of the parent
+	// certificate, that the delegated credential is valid for. If zero, then 24
+	// hours is assumed.
+	lifetime time.Duration
+	// expectedAlgo is the signature scheme that should be used with this
+	// delegated credential. If zero, ECDSA with P-256 is assumed.
+	expectedAlgo signatureAlgorithm
+	// tlsVersion is the version of TLS that should be used with this delegated
+	// credential. If zero, TLS 1.3 is assumed.
+	tlsVersion uint16
+	// algo is the signature algorithm that the delegated credential itself is
+	// signed with. Cannot be zero.
+	algo signatureAlgorithm
+}
+
+func loadRSAPrivateKey(filename string) (priv *rsa.PrivateKey, privPKCS8 []byte, err error) {
+	pemPath := path.Join(*resourceDir, filename)
+	pemBytes, err := ioutil.ReadFile(pemPath)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	block, _ := pem.Decode(pemBytes)
+	if block == nil {
+		return nil, nil, fmt.Errorf("no PEM block found in %q", pemPath)
+	}
+	privPKCS8 = block.Bytes
+
+	parsed, err := x509.ParsePKCS8PrivateKey(privPKCS8)
+	if err != nil {
+		return nil, nil, fmt.Errorf("failed to parse PKCS#8 key from %q", pemPath)
+	}
+
+	priv, ok := parsed.(*rsa.PrivateKey)
+	if !ok {
+		return nil, nil, fmt.Errorf("found %T in %q rather than an RSA private key", parsed, pemPath)
+	}
+
+	return priv, privPKCS8, nil
+}
+
+func createDelegatedCredential(config delegatedCredentialConfig, parentDER []byte, parentPriv crypto.PrivateKey) (dc, privPKCS8 []uint8, err error) {
+	expectedAlgo := config.expectedAlgo
+	if expectedAlgo == signatureAlgorithm(0) {
+		expectedAlgo = signatureECDSAWithP256AndSHA256
+	}
+
+	var pub crypto.PublicKey
+
+	switch expectedAlgo {
+	case signatureRSAPKCS1WithMD5, signatureRSAPKCS1WithSHA1, signatureRSAPKCS1WithSHA256, signatureRSAPKCS1WithSHA384, signatureRSAPKCS1WithSHA512, signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, signatureRSAPSSWithSHA512:
+		// RSA keys are expensive to generate so load from disk instead.
+		var priv *rsa.PrivateKey
+		if priv, privPKCS8, err = loadRSAPrivateKey(rsaKeyFile); err != nil {
+			return nil, nil, err
+		}
+
+		pub = &priv.PublicKey
+
+	case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256, signatureECDSAWithP384AndSHA384, signatureECDSAWithP521AndSHA512:
+		var curve elliptic.Curve
+		switch expectedAlgo {
+		case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256:
+			curve = elliptic.P256()
+		case signatureECDSAWithP384AndSHA384:
+			curve = elliptic.P384()
+		case signatureECDSAWithP521AndSHA512:
+			curve = elliptic.P521()
+		default:
+			panic("internal error")
+		}
+
+		priv, err := ecdsa.GenerateKey(curve, rand.Reader)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		if privPKCS8, err = x509.MarshalPKCS8PrivateKey(priv); err != nil {
+			return nil, nil, err
+		}
+
+		pub = &priv.PublicKey
+
+	default:
+		return nil, nil, fmt.Errorf("unsupported expected signature algorithm: %x", expectedAlgo)
+	}
+
+	lifetime := config.lifetime
+	if lifetime == 0 {
+		lifetime = 24 * time.Hour
+	}
+	lifetimeSecs := int64(lifetime.Seconds())
+	if lifetimeSecs > 1<<32 {
+		return nil, nil, fmt.Errorf("lifetime %s is too long to be expressed", lifetime)
+	}
+
+	tlsVersion := config.tlsVersion
+	if tlsVersion == 0 {
+		tlsVersion = VersionTLS13
+	}
+
+	if tlsVersion < VersionTLS13 {
+		return nil, nil, fmt.Errorf("delegated credentials require TLS 1.3")
+	}
+
+	// https://tools.ietf.org/html/draft-ietf-tls-subcerts-02#section-3
+	dc = append(dc, byte(lifetimeSecs>>24), byte(lifetimeSecs>>16), byte(lifetimeSecs>>8), byte(lifetimeSecs))
+	dc = append(dc, byte(expectedAlgo>>8), byte(expectedAlgo))
+	dc = append(dc, byte(tlsVersion>>8), byte(tlsVersion))
+
+	pubBytes, err := x509.MarshalPKIXPublicKey(pub)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	dc = append(dc, byte(len(pubBytes)>>16), byte(len(pubBytes)>>8), byte(len(pubBytes)))
+	dc = append(dc, pubBytes...)
+
+	var dummyConfig Config
+	parentSigner, err := getSigner(tlsVersion, parentPriv, &dummyConfig, config.algo, false /* not for verification */)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	parentSignature, err := parentSigner.signMessage(parentPriv, &dummyConfig, delegatedCredentialSignedMessage(dc, config.algo, parentDER))
+	if err != nil {
+		return nil, nil, err
+	}
+
+	dc = append(dc, byte(config.algo>>8), byte(config.algo))
+	dc = append(dc, byte(len(parentSignature)>>8), byte(len(parentSignature)))
+	dc = append(dc, parentSignature...)
+
+	return dc, privPKCS8, nil
+}
+
 func getRunnerCertificate(t testCert) Certificate {
 	for _, cert := range testCerts {
 		if cert.id == t {
@@ -12418,7 +12559,7 @@
 		config: Config{
 			MaxVersion: VersionTLS13,
 			// Require a HelloRetryRequest for every curve.
-			DefaultCurves: []CurveID{},
+			DefaultCurves:    []CurveID{},
 			CurvePreferences: []CurveID{CurveX25519},
 		},
 		expectedCurveID: CurveX25519,
@@ -12428,8 +12569,8 @@
 		testType: serverTest,
 		name:     "SendHelloRetryRequest-2-TLS13",
 		config: Config{
-			MaxVersion:    VersionTLS13,
-			DefaultCurves: []CurveID{CurveP384},
+			MaxVersion:       VersionTLS13,
+			DefaultCurves:    []CurveID{CurveP384},
 			CurvePreferences: []CurveID{CurveX25519, CurveP384},
 		},
 		// Although the ClientHello did not predict our preferred curve,
@@ -14597,6 +14738,128 @@
 	}
 }
 
+func addDelegatedCredentialTests() {
+	certPath := path.Join(*resourceDir, rsaCertificateFile)
+	pemBytes, err := ioutil.ReadFile(certPath)
+	if err != nil {
+		panic(err)
+	}
+
+	block, _ := pem.Decode(pemBytes)
+	if block == nil {
+		panic(fmt.Sprintf("no PEM block found in %q", certPath))
+	}
+	parentDER := block.Bytes
+
+	rsaPriv, _, err := loadRSAPrivateKey(rsaKeyFile)
+	if err != nil {
+		panic(err)
+	}
+
+	ecdsaDC, ecdsaPKCS8, err := createDelegatedCredential(delegatedCredentialConfig{
+		algo: signatureRSAPSSWithSHA256,
+	}, parentDER, rsaPriv)
+	if err != nil {
+		panic(err)
+	}
+	ecdsaFlagValue := fmt.Sprintf("%x,%x", ecdsaDC, ecdsaPKCS8)
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "DelegatedCredentials-NoClientSupport",
+		config: Config{
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				DisableDelegatedCredentials: true,
+			},
+		},
+		flags: []string{
+			"-delegated-credential", ecdsaFlagValue,
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "DelegatedCredentials-Basic",
+		config: Config{
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				ExpectDelegatedCredentials: true,
+			},
+		},
+		flags: []string{
+			"-delegated-credential", ecdsaFlagValue,
+		},
+	})
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "DelegatedCredentials-SigAlgoMissing",
+		config: Config{
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				FailIfDelegatedCredentials: true,
+			},
+			// If the client doesn't support the delegated credential signature
+			// algorithm then the handshake should complete without using delegated
+			// credentials.
+			VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+		},
+		flags: []string{
+			"-delegated-credential", ecdsaFlagValue,
+		},
+	})
+
+	badTLSVersionDC, badTLSVersionPKCS8, err := createDelegatedCredential(delegatedCredentialConfig{
+		algo:       signatureRSAPSSWithSHA256,
+		tlsVersion: 0x1234,
+	}, parentDER, rsaPriv)
+	if err != nil {
+		panic(err)
+	}
+	badTLSVersionFlagValue := fmt.Sprintf("%x,%x", badTLSVersionDC, badTLSVersionPKCS8)
+
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "DelegatedCredentials-BadTLSVersion",
+		config: Config{
+			// The delegated credential specifies a crazy TLS version, which should
+			// prevent its use.
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				FailIfDelegatedCredentials: true,
+			},
+		},
+		flags: []string{
+			"-delegated-credential", badTLSVersionFlagValue,
+		},
+	})
+
+	// This flag value has mismatched public and private keys which should cause a
+	// configuration error in the shim.
+	mismatchFlagValue := fmt.Sprintf("%x,%x", ecdsaDC, badTLSVersionPKCS8)
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "DelegatedCredentials-KeyMismatch",
+		config: Config{
+			MinVersion: VersionTLS13,
+			MaxVersion: VersionTLS13,
+			Bugs: ProtocolBugs{
+				FailIfDelegatedCredentials: true,
+			},
+		},
+		flags: []string{
+			"-delegated-credential", mismatchFlagValue,
+		},
+		shouldFail:    true,
+		expectedError: ":KEY_VALUES_MISMATCH:",
+	})
+}
+
 func worker(statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -14732,6 +14995,7 @@
 	addOmitExtensionsTests()
 	addCertCompressionTests()
 	addJDK11WorkaroundTests()
+	addDelegatedCredentialTests()
 
 	testCases = append(testCases, convertToSplitHandshakeTests(testCases)...)
 
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index edbede6..2f53156 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -177,6 +177,7 @@
   { "-expect-client-ca-list", &TestConfig::expected_client_ca_list },
   { "-expect-msg-callback", &TestConfig::expect_msg_callback },
   { "-handshaker-path", &TestConfig::handshaker_path },
+  { "-delegated-credential", &TestConfig::delegated_credential },
 };
 
 const Flag<std::string> kBase64Flags[] = {
@@ -1670,5 +1671,40 @@
     }
   }
 
+  if (!delegated_credential.empty()) {
+    std::string::size_type comma = delegated_credential.find(',');
+    if (comma == std::string::npos) {
+      fprintf(stderr, "failed to find comma in delegated credential argument");
+      return nullptr;
+    }
+
+    const std::string dc_hex = delegated_credential.substr(0, comma);
+    const std::string pkcs8_hex = delegated_credential.substr(comma + 1);
+    std::string dc, pkcs8;
+    if (!HexDecode(&dc, dc_hex) || !HexDecode(&pkcs8, pkcs8_hex)) {
+      fprintf(stderr, "failed to hex decode delegated credential argument");
+      return nullptr;
+    }
+
+    CBS dc_cbs(bssl::Span<const uint8_t>(
+        reinterpret_cast<const uint8_t *>(dc.data()), dc.size()));
+    CBS pkcs8_cbs(bssl::Span<const uint8_t>(
+        reinterpret_cast<const uint8_t *>(pkcs8.data()), pkcs8.size()));
+
+    bssl::UniquePtr<EVP_PKEY> priv(EVP_parse_private_key(&pkcs8_cbs));
+    if (!priv) {
+      fprintf(stderr, "failed to parse delegated credential private key");
+      return nullptr;
+    }
+
+    bssl::UniquePtr<CRYPTO_BUFFER> dc_buf(
+        CRYPTO_BUFFER_new_from_CBS(&dc_cbs, nullptr));
+    if (!SSL_set1_delegated_credential(ssl.get(), dc_buf.get(),
+                                      priv.get(), nullptr)) {
+      fprintf(stderr, "SSL_set1_delegated_credential failed.\n");
+      return nullptr;
+    }
+  }
+
   return ssl;
 }
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 41709ab..8b63bc8 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -172,6 +172,7 @@
   bool server_preference = false;
   bool export_traffic_secrets = false;
   bool key_update = false;
+  std::string delegated_credential;
 
   int argc;
   char **argv;
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 7674d99..eb1c15e 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -418,6 +418,7 @@
 bool tls13_add_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   CERT *const cert = hs->config->cert.get();
+  DC *const dc = cert->dc.get();
 
   ScopedCBB cbb;
   CBB *body, body_storage, certificate_list;
@@ -484,6 +485,19 @@
     }
   }
 
+  if (ssl_signing_with_dc(hs)) {
+    const CRYPTO_BUFFER *raw = dc->raw.get();
+    if (!CBB_add_u16(&extensions, TLSEXT_TYPE_delegated_credential) ||
+        !CBB_add_u16(&extensions, CRYPTO_BUFFER_len(raw)) ||
+        !CBB_add_bytes(&extensions,
+                       CRYPTO_BUFFER_data(raw),
+                       CRYPTO_BUFFER_len(raw)) ||
+        !CBB_flush(&extensions)) {
+      OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
+      return 0;
+    }
+  }
+
   for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(cert->chain.get()); i++) {
     CRYPTO_BUFFER *cert_buf = sk_CRYPTO_BUFFER_value(cert->chain.get(), i);
     CBB child;