Add an SSL_CREDENTIAL API for ECDSA/RSA and delegated credentials

This adds a notion of "credentials" to BoringSSL's API, to support
certificate selection by key type (typically ECDSA vs RSA), though the
aim is for it to be generalizable to other certificate types and other
kinds of selection criteria, such as Trust Expressions, or Merkle Tree
Certificates. Since we already had some nascent delegated credentials
I've reworked that feature with SSL_CREDENTIALs as well.

The model is that you create an SSL_CREDENTIAL object containing all the
configuration for what you are authenticating as. An X.509
SSL_CREDENTIAL has a certificate chain, private key, optionally an OCSP
response and SCT list. Delegated credentials are similar. In the future,
we might use this for raw public keys, other certificate types, etc.
Once you set those up, you configure those on the SSL or SSL_CTX in
preference order, and BoringSSL will internally pick the first one that
is usable.

The current implementation ends up redundantly selecting the signature
algorithm a couple of times. This works but is a little goofy. A
follow-up change will remove this redundancy. The protocol between the
runner and shim for tests is also a little weird, but it was the easiest
way I could think of for injecting that. Long-term, I think we should
just replace that protocol with a JSON structure. (See
https://crbug.com/boringssl/704.)

As split handshakes are in the process of being replaced with handshake
hints, this won't work with split handshakes. It works with handshake
hints without any extra work.

Update-Note: The delegated credentials API has been revamped.
Previously, it worked by configuring an optional delegated credential
and key with your normal certificate chain. This has the side effect of
forcing your DC issuer and your fallback certificate to be the same. The
SSL_CREDENTIAL API lifts this restriction.

A delegated credential is now just a different kind of credential. It
may use the same certificate chain as an X.509 credential or be
completely separate. All the SSL_CREDENTIAL APIs take CRYPTO_BUFFERs,
so, if common, the buffers may be shared to reduce memory.

The SSL_delegated_credential_used API is also removed, in favor of the
more general SSL_get0_selected_credential API. Callers can use ex_data
or pointer equality to identify the credential.

Bug: 249
Change-Id: Ibc290df3b7b95f148df12625e41cf55c50566602
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/66690
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: David Benjamin <davidben@google.com>
diff --git a/include/openssl/base.h b/include/openssl/base.h
index 70f9f52..57095d0 100644
--- a/include/openssl/base.h
+++ b/include/openssl/base.h
@@ -109,7 +109,7 @@
 // A consumer may use this symbol in the preprocessor to temporarily build
 // against multiple revisions of BoringSSL at the same time. It is not
 // recommended to do so for longer than is necessary.
-#define BORINGSSL_API_VERSION 31
+#define BORINGSSL_API_VERSION 32
 
 #if defined(BORINGSSL_SHARED_LIBRARY)
 
@@ -358,6 +358,7 @@
 typedef struct spake2_ctx_st SPAKE2_CTX;
 typedef struct srtp_protection_profile_st SRTP_PROTECTION_PROFILE;
 typedef struct ssl_cipher_st SSL_CIPHER;
+typedef struct ssl_credential_st SSL_CREDENTIAL;
 typedef struct ssl_ctx_st SSL_CTX;
 typedef struct ssl_early_callback_ctx SSL_CLIENT_HELLO;
 typedef struct ssl_ech_keys_st SSL_ECH_KEYS;
diff --git a/include/openssl/ssl.h b/include/openssl/ssl.h
index f8fd947..d10bb02 100644
--- a/include/openssl/ssl.h
+++ b/include/openssl/ssl.h
@@ -550,8 +550,8 @@
 // a private key operation was unfinished. The caller may retry the operation
 // when the private key operation is complete.
 //
-// See also |SSL_set_private_key_method| and
-// |SSL_CTX_set_private_key_method|.
+// See also |SSL_set_private_key_method|, |SSL_CTX_set_private_key_method|, and
+// |SSL_CREDENTIAL_set_private_key_method|.
 #define SSL_ERROR_WANT_PRIVATE_KEY_OPERATION 13
 
 // SSL_ERROR_PENDING_TICKET indicates that a ticket decryption is pending. The
@@ -841,6 +841,141 @@
                                              CRYPTO_BUFFER_POOL *pool);
 
 
+// Credentials.
+//
+// TLS endpoints may present authentication during the handshake, usually using
+// X.509 certificates. This is typically required for servers and optional for
+// clients. BoringSSL uses the |SSL_CREDENTIAL| object to abstract between
+// different kinds of credentials, as well as configure automatic selection
+// between multiple credentials. This may be used to select between ECDSA and
+// RSA certificates.
+//
+// |SSL_CTX| and |SSL| objects maintain lists of credentials in preference
+// order. During the handshake, BoringSSL will select the first usable
+// credential from the list. Non-credential APIs, such as
+// |SSL_CTX_use_certificate|, configure a "default credential", which is
+// appended to this list if configured.
+//
+// When selecting credentials, BoringSSL considers the credential's type, its
+// cryptographic capabilities, and capabilities advertised by the peer. This
+// varies between TLS versions but includes:
+//
+// - Whether the peer supports the leaf certificate key
+// - Whether there is a common signature algorithm that is compatible with the
+//   credential
+// - Whether there is a common cipher suite that is compatible with the
+//   credential
+//
+// WARNING: In TLS 1.2 and below, there is no mechanism for servers to advertise
+// supported ECDSA curves to the client. BoringSSL clients will assume the
+// server accepts all ECDSA curves in client certificates.
+//
+// By default, BoringSSL does not check the following, though we may add APIs
+// in the future to enable them on a per-credential basis.
+//
+// - Whether the peer supports the signature algorithms in the certificate chain
+// - Whether the a server certificate is compatible with the server_name
+//   extension (SNI)
+// - Whether the peer supports the certificate authority that issued the
+//   certificate
+//
+// Credentials may be configured before the handshake or dynamically in the
+// early callback (see |SSL_CTX_set_select_certificate_cb|) and certificate
+// callback (see |SSL_CTX_set_cert_cb|). These callbacks allow applications to
+// use BoringSSL's built-in selection logic in tandem with custom logic. For
+// example, a callback could evaluate application-specific SNI rules to filter
+// down to an ECDSA and RSA credential, then configure both for BoringSSL to
+// select between the two.
+
+// SSL_CREDENTIAL_new_x509 returns a new, empty X.509 credential, or NULL on
+// error. Callers should release the result with |SSL_CREDENTIAL_free| when
+// done.
+//
+// Callers should configure a certificate chain and private key on the
+// credential, along with other properties, then add it with
+// |SSL_CTX_add1_credential|.
+OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_x509(void);
+
+// SSL_CREDENTIAL_up_ref increments the reference count of |cred|.
+OPENSSL_EXPORT void SSL_CREDENTIAL_up_ref(SSL_CREDENTIAL *cred);
+
+// SSL_CREDENTIAL_free decrements the reference count of |cred|. If it reaches
+// zero, all data referenced by |cred| and |cred| itself are released.
+OPENSSL_EXPORT void SSL_CREDENTIAL_free(SSL_CREDENTIAL *cred);
+
+// SSL_CREDENTIAL_set1_private_key sets |cred|'s private key to |cred|. It
+// returns one on success and zero on failure.
+OPENSSL_EXPORT int SSL_CREDENTIAL_set1_private_key(SSL_CREDENTIAL *cred,
+                                                   EVP_PKEY *key);
+
+// SSL_CREDENTIAL_set1_signing_algorithm_prefs configures |cred| to use |prefs|
+// as the preference list when signing with |cred|'s private key. It returns one
+// on success and zero on error. |prefs| should not include the internal-only
+// value |SSL_SIGN_RSA_PKCS1_MD5_SHA1|.
+//
+// It is an error to call this function with delegated credentials (see
+// |SSL_CREDENTIAL_new_delegated|) because delegated credentials already
+// constrain the key to a single algorithm.
+OPENSSL_EXPORT int SSL_CREDENTIAL_set1_signing_algorithm_prefs(
+    SSL_CREDENTIAL *cred, const uint16_t *prefs, size_t num_prefs);
+
+// SSL_CREDENTIAL_set1_cert_chain sets |cred|'s certificate chain to |num_cert|s
+// certificates from |certs|. It returns one on success and zero on error.
+OPENSSL_EXPORT int SSL_CREDENTIAL_set1_cert_chain(SSL_CREDENTIAL *cred,
+                                                  CRYPTO_BUFFER *const *certs,
+                                                  size_t num_certs);
+
+// SSL_CREDENTIAL_set1_ocsp_response sets |cred|'s stapled OCSP response to
+// |ocsp|. It returns one on success and zero on error.
+OPENSSL_EXPORT int SSL_CREDENTIAL_set1_ocsp_response(SSL_CREDENTIAL *cred,
+                                                     CRYPTO_BUFFER *ocsp);
+
+// SSL_CREDENTIAL_set1_signed_cert_timestamp_list sets |cred|'s list of signed
+// certificate timestamps |sct_list|. |sct_list| must contain one or more SCT
+// structures serialised as a SignedCertificateTimestampList (see
+// https://tools.ietf.org/html/rfc6962#section-3.3) – i.e. each SCT is prefixed
+// by a big-endian, uint16 length and the concatenation of one or more such
+// prefixed SCTs are themselves also prefixed by a uint16 length. It returns one
+// on success and zero on error.
+OPENSSL_EXPORT int SSL_CREDENTIAL_set1_signed_cert_timestamp_list(
+    SSL_CREDENTIAL *cred, CRYPTO_BUFFER *sct_list);
+
+// SSL_CTX_add1_credential appends |cred| to |ctx|'s credential list. It returns
+// one on success and zero on error. The credential list is maintained in order
+// of decreasing preference, so earlier calls are preferred over later calls.
+//
+// After calling this function, it is an error to modify |cred|. Doing so may
+// result in inconsistent handshake behavior or race conditions.
+OPENSSL_EXPORT int SSL_CTX_add1_credential(SSL_CTX *ctx, SSL_CREDENTIAL *cred);
+
+// SSL_add1_credential appends |cred| to |ssl|'s credential list. It returns one
+// on success and zero on error. The credential list is maintained in order of
+// decreasing preference, so earlier calls are preferred over later calls.
+//
+// After calling this function, it is an error to modify |cred|. Doing so may
+// result in inconsistent handshake behavior or race conditions.
+OPENSSL_EXPORT int SSL_add1_credential(SSL *ssl, SSL_CREDENTIAL *cred);
+
+// SSL_certs_clear removes all credentials configured on |ssl|. It also removes
+// the certificate chain and private key on the default credential.
+OPENSSL_EXPORT void SSL_certs_clear(SSL *ssl);
+
+// SSL_get0_selected_credential returns the credential in use in the current
+// handshake on |ssl|. If there is current handshake on |ssl| or if the
+// handshake has not progressed to this point, it returns NULL.
+//
+// This function is intended for use with |SSL_CREDENTIAL_get_ex_data|. It may
+// be called from handshake callbacks, such as those in
+// |SSL_PRIVATE_KEY_METHOD|, to trigger credential-specific behavior.
+//
+// In applications that use the older APIs, such as |SSL_use_certificate|, this
+// function may return an internal |SSL_CREDENTIAL| object. This internal object
+// will have no ex_data installed. To avoid this, it is recommended that callers
+// moving to |SSL_CREDENTIAL| use the new APIs consistently.
+OPENSSL_EXPORT const SSL_CREDENTIAL *SSL_get0_selected_credential(
+    const SSL *ssl);
+
+
 // Configuring certificates and private keys.
 //
 // These functions configure the connection's leaf certificate, private key, and
@@ -853,8 +988,8 @@
 // than return an error. Additionally, overwriting a previously-configured
 // certificate and key pair only works if the certificate is configured first.
 //
-// Certificates and keys may be configured before the handshake or dynamically
-// in the early callback and certificate callback.
+// Each of these functions configures the default credential. To select between
+// multiple certificates, see |SSL_CREDENTIAL_new_x509| and related APIs.
 
 // SSL_CTX_use_certificate sets |ctx|'s leaf certificate to |x509|. It returns
 // one on success and zero on failure. If |ctx| has a private key which is
@@ -993,10 +1128,6 @@
 SSL_get0_peer_delegation_algorithms(const SSL *ssl,
                                     const uint16_t **out_sigalgs);
 
-// SSL_certs_clear resets the private key, leaf certificate, and certificate
-// chain of |ssl|.
-OPENSSL_EXPORT void SSL_certs_clear(SSL *ssl);
-
 // SSL_CTX_get0_certificate returns |ctx|'s leaf certificate.
 OPENSSL_EXPORT X509 *SSL_CTX_get0_certificate(const SSL_CTX *ctx);
 
@@ -1268,11 +1399,6 @@
 // key hooks. This is used to off-load signing operations to a custom,
 // potentially asynchronous, backend. Metadata about the key such as the type
 // and size are parsed out of the certificate.
-//
-// Callers that use this structure should additionally call
-// |SSL_set_signing_algorithm_prefs| or |SSL_CTX_set_signing_algorithm_prefs|
-// with the private key's capabilities. This ensures BoringSSL will select a
-// suitable signature algorithm for the private key.
 struct ssl_private_key_method_st {
   // sign signs the message |in| in using the specified signature algorithm. On
   // success, it returns |ssl_private_key_success| and writes at most |max_out|
@@ -1325,14 +1451,39 @@
 
 // SSL_set_private_key_method configures a custom private key on |ssl|.
 // |key_method| must remain valid for the lifetime of |ssl|.
+//
+// If using an RSA or ECDSA key, callers should configure signing capabilities
+// with |SSL_set_signing_algorithm_prefs|. Otherwise, BoringSSL may select a
+// signature algorithm that |key_method| does not support.
 OPENSSL_EXPORT void SSL_set_private_key_method(
     SSL *ssl, const SSL_PRIVATE_KEY_METHOD *key_method);
 
 // SSL_CTX_set_private_key_method configures a custom private key on |ctx|.
 // |key_method| must remain valid for the lifetime of |ctx|.
+//
+// If using an RSA or ECDSA key, callers should configure signing capabilities
+// with |SSL_CTX_set_signing_algorithm_prefs|. Otherwise, BoringSSL may select a
+// signature algorithm that |key_method| does not support.
 OPENSSL_EXPORT void SSL_CTX_set_private_key_method(
     SSL_CTX *ctx, const SSL_PRIVATE_KEY_METHOD *key_method);
 
+// SSL_CREDENTIAL_set_private_key_method configures a custom private key on
+// |cred|. |key_method| must remain valid for the lifetime of |cred|. It returns
+// one on success and zero if |cred| does not use private keys.
+//
+// If using an RSA or ECDSA key, callers should configure signing capabilities
+// with |SSL_CREDENTIAL_set1_signing_algorithm_prefs|. Otherwise, BoringSSL may
+// select a signature algorithm that |key_method| does not support. This is not
+// necessary for delegated credentials (see |SSL_CREDENTIAL_new_delegated|)
+// because delegated credentials only support a single signature algorithm.
+//
+// Functions in |key_method| will be passed an |SSL| object, but not |cred|
+// directly. Use |SSL_get0_selected_credential| to determine the selected
+// credential. From there, |SSL_CREDENTIAL_get_ex_data| can be used to look up
+// credential-specific state, such as a handle to the private key.
+OPENSSL_EXPORT int SSL_CREDENTIAL_set_private_key_method(
+    SSL_CREDENTIAL *cred, const SSL_PRIVATE_KEY_METHOD *key_method);
+
 // SSL_can_release_private_key returns one if |ssl| will no longer call into the
 // private key and zero otherwise. If the function returns one, the caller can
 // release state associated with the private key.
@@ -3337,38 +3488,34 @@
 
 // Delegated credentials.
 //
-// *** EXPERIMENTAL — PRONE TO CHANGE ***
+// Delegated credentials (RFC 9345) allow a TLS 1.3 endpoint to use its
+// certificate to issue new credentials for authentication. 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).
 //
-// Delegated credentials (RFC 9345) allow a TLS 1.3 end point to use its
-// certificate to issue new 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).
-//
-// 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 RFC 9345.
+// Currently only the authenticating side, as a server, is implemented. To
+// authenticate with delegated credentials, construct an |SSL_CREDENTIAL| with
+// |SSL_CREDENTIAL_new_delegated| and add it to the credential list. See also
+// |SSL_CTX_add1_credential|. Callers may configure a mix of delegated
+// credentials and X.509 credentials on the same |SSL| or |SSL_CTX| to support a
+// range of clients.
 
-// 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, only servers may configure a DC to use in the handshake.
+// SSL_CREDENTIAL_new_delegated returns a new, empty delegated credential, or
+// NULL on error. Callers should release the result with |SSL_CREDENTIAL_free|
+// when done.
 //
-// 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);
+// Callers should configure a delegated credential, certificate chain and
+// private key on the credential, along with other properties, then add it with
+// |SSL_CTX_add1_credential|.
+OPENSSL_EXPORT SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void);
 
-// SSL_delegated_credential_used returns one if a delegated credential was used
-// and zero otherwise.
-OPENSSL_EXPORT int SSL_delegated_credential_used(const SSL *ssl);
+// SSL_CREDENTIAL_set1_delegated_credential sets |cred|'s delegated credentials
+// structure to |dc|. It returns one on success and zero on error, including if
+// |dc| is malformed. This should be a DelegatedCredential structure, signed by
+// the end-entity certificate, as described in RFC 9345.
+OPENSSL_EXPORT int SSL_CREDENTIAL_set1_delegated_credential(
+    SSL_CREDENTIAL *cred, CRYPTO_BUFFER *dc);
 
 
 // QUIC integration.
@@ -4033,6 +4180,15 @@
                                             CRYPTO_EX_dup *dup_unused,
                                             CRYPTO_EX_free *free_func);
 
+OPENSSL_EXPORT int SSL_CREDENTIAL_set_ex_data(SSL_CREDENTIAL *cred, int idx,
+                                              void *data);
+OPENSSL_EXPORT void *SSL_CREDENTIAL_get_ex_data(const SSL_CREDENTIAL *cred,
+                                                int idx);
+OPENSSL_EXPORT int SSL_CREDENTIAL_get_ex_new_index(long argl, void *argp,
+                                                   CRYPTO_EX_unused *unused,
+                                                   CRYPTO_EX_dup *dup_unused,
+                                                   CRYPTO_EX_free *free_func);
+
 
 // Low-level record-layer state.
 
@@ -5579,6 +5735,8 @@
 BSSL_NAMESPACE_BEGIN
 
 BORINGSSL_MAKE_DELETER(SSL, SSL_free)
+BORINGSSL_MAKE_DELETER(SSL_CREDENTIAL, SSL_CREDENTIAL_free)
+BORINGSSL_MAKE_UP_REF(SSL_CREDENTIAL, SSL_CREDENTIAL_up_ref)
 BORINGSSL_MAKE_DELETER(SSL_CTX, SSL_CTX_free)
 BORINGSSL_MAKE_UP_REF(SSL_CTX, SSL_CTX_up_ref)
 BORINGSSL_MAKE_DELETER(SSL_ECH_KEYS, SSL_ECH_KEYS_free)
diff --git a/ssl/CMakeLists.txt b/ssl/CMakeLists.txt
index 314ea4f..c009317 100644
--- a/ssl/CMakeLists.txt
+++ b/ssl/CMakeLists.txt
@@ -22,6 +22,7 @@
   ssl_buffer.cc
   ssl_cert.cc
   ssl_cipher.cc
+  ssl_credential.cc
   ssl_file.cc
   ssl_key_share.cc
   ssl_lib.cc
diff --git a/ssl/extensions.cc b/ssl/extensions.cc
index e9c38a6..a5e4ec8 100644
--- a/ssl/extensions.cc
+++ b/ssl/extensions.cc
@@ -1128,7 +1128,8 @@
 static bool ext_ocsp_add_serverhello(SSL_HANDSHAKE *hs, CBB *out) {
   SSL *const ssl = hs->ssl;
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION ||
-      !hs->ocsp_stapling_requested || hs->config->cert->ocsp_response == NULL ||
+      !hs->ocsp_stapling_requested ||
+      hs->credential->ocsp_response == nullptr ||  //
       ssl->s3->session_reused ||
       !ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
     return true;
@@ -1347,19 +1348,18 @@
   SSL *const ssl = hs->ssl;
   // The extension shouldn't be sent when resuming sessions.
   if (ssl_protocol_version(ssl) >= TLS1_3_VERSION || ssl->s3->session_reused ||
-      hs->config->cert->signed_cert_timestamp_list == NULL) {
+      hs->credential->signed_cert_timestamp_list == nullptr) {
     return true;
   }
 
   CBB contents;
   return CBB_add_u16(out, TLSEXT_TYPE_certificate_timestamp) &&
          CBB_add_u16_length_prefixed(out, &contents) &&
-         CBB_add_bytes(
-             &contents,
-             CRYPTO_BUFFER_data(
-                 hs->config->cert->signed_cert_timestamp_list.get()),
-             CRYPTO_BUFFER_len(
-                 hs->config->cert->signed_cert_timestamp_list.get())) &&
+         CBB_add_bytes(&contents,
+                       CRYPTO_BUFFER_data(
+                           hs->credential->signed_cert_timestamp_list.get()),
+                       CRYPTO_BUFFER_len(
+                           hs->credential->signed_cert_timestamp_list.get())) &&
          CBB_flush(out);
 }
 
@@ -4104,41 +4104,53 @@
   }
 }
 
-bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs, uint16_t *out) {
+bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs,
+                                     const SSL_CREDENTIAL *cred,
+                                     uint16_t *out) {
   SSL *const ssl = hs->ssl;
-  CERT *cert = hs->config->cert.get();
-  DC *dc = cert->dc.get();
+  if (!cred->UsesPrivateKey()) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+    return false;
+  }
 
   // Before TLS 1.2, the signature algorithm isn't negotiated as part of the
   // handshake.
-  if (ssl_protocol_version(ssl) < TLS1_2_VERSION) {
-    if (!tls1_get_legacy_signature_algorithm(out, hs->local_pubkey.get())) {
+  uint16_t version = ssl_protocol_version(ssl);
+  if (version < TLS1_2_VERSION) {
+    if (!tls1_get_legacy_signature_algorithm(out, cred->pubkey.get())) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS);
       return false;
     }
     return true;
   }
 
-  Span<const uint16_t> sigalgs, peer_sigalgs;
-  if (ssl_signing_with_dc(hs)) {
-    sigalgs = MakeConstSpan(&dc->dc_cert_verify_algorithm, 1);
+  Span<const uint16_t> peer_sigalgs;
+  if (cred->type == SSLCredentialType::kDelegated) {
     peer_sigalgs = hs->peer_delegated_credential_sigalgs;
   } else {
-    sigalgs = cert->sigalgs.empty() ? MakeConstSpan(kSignSignatureAlgorithms)
-                                    : cert->sigalgs;
-    peer_sigalgs = tls1_get_peer_verify_algorithms(hs);
+    peer_sigalgs = hs->peer_sigalgs;
+    if (peer_sigalgs.empty() && version == TLS1_2_VERSION) {
+      // If the client didn't specify any signature_algorithms extension, it is
+      // interpreted as SHA-1. See
+      // http://tools.ietf.org/html/rfc5246#section-7.4.1.4.1
+      static const uint16_t kTLS12Default[] = {SSL_SIGN_RSA_PKCS1_SHA1,
+                                               SSL_SIGN_ECDSA_SHA1};
+      peer_sigalgs = kTLS12Default;
+    }
   }
 
+  Span<const uint16_t> sigalgs = cred->sigalgs.empty()
+                                     ? MakeConstSpan(kSignSignatureAlgorithms)
+                                     : cred->sigalgs;
   for (uint16_t sigalg : sigalgs) {
-    if (!ssl_private_key_supports_signature_algorithm(hs, sigalg)) {
+    if (!ssl_pkey_supports_algorithm(ssl, cred->pubkey.get(), sigalg)) {
       continue;
     }
 
-    for (uint16_t peer_sigalg : peer_sigalgs) {
-      if (sigalg == peer_sigalg) {
-        *out = sigalg;
-        return true;
-      }
+    if (std::find(peer_sigalgs.begin(), peer_sigalgs.end(), sigalg) !=
+        peer_sigalgs.end()) {
+      *out = sigalg;
+      return true;
     }
   }
 
@@ -4146,19 +4158,6 @@
   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/handoff.cc b/ssl/handoff.cc
index 7f78a1a..9417eb9 100644
--- a/ssl/handoff.cc
+++ b/ssl/handoff.cc
@@ -244,7 +244,7 @@
 // uses_disallowed_feature returns true iff |ssl| enables a feature that
 // disqualifies it for split handshakes.
 static bool uses_disallowed_feature(const SSL *ssl) {
-  return ssl->method->is_dtls || (ssl->config->cert && ssl->config->cert->dc) ||
+  return ssl->method->is_dtls || !ssl->config->cert->credentials.empty() ||
          ssl->config->quic_transport_params.size() > 0 || ssl->ctx->ech_keys;
 }
 
diff --git a/ssl/handshake.cc b/ssl/handshake.cc
index 2a4fb11..165614f 100644
--- a/ssl/handshake.cc
+++ b/ssl/handshake.cc
@@ -572,8 +572,9 @@
     return false;
   }
 
-  if (ssl_has_certificate(hs)) {
-    STACK_OF(CRYPTO_BUFFER) *chain = hs->config->cert->chain.get();
+  if (hs->credential != nullptr) {
+    assert(hs->credential->type == SSLCredentialType::kX509);
+    STACK_OF(CRYPTO_BUFFER) *chain = hs->credential->chain.get();
     for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(chain); i++) {
       CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(chain, i);
       if (!CBB_add_u24_length_prefixed(&certs, &cert) ||
diff --git a/ssl/handshake_client.cc b/ssl/handshake_client.cc
index 2262999..b10a27b 100644
--- a/ssl/handshake_client.cc
+++ b/ssl/handshake_client.cc
@@ -153,6 +153,7 @@
 #include <limits.h>
 #include <string.h>
 
+#include <algorithm>
 #include <utility>
 
 #include <openssl/aead.h>
@@ -1331,6 +1332,42 @@
   return ssl_hs_ok;
 }
 
+static bool check_credential(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred) {
+  if (cred->type != SSLCredentialType::kX509) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+    return false;
+  }
+
+  if (hs->config->check_client_certificate_type) {
+    // Check the certificate types advertised by the peer.
+    uint8_t cert_type;
+    switch (EVP_PKEY_id(cred->pubkey.get())) {
+      case EVP_PKEY_RSA:
+        cert_type = SSL3_CT_RSA_SIGN;
+        break;
+      case EVP_PKEY_EC:
+      case EVP_PKEY_ED25519:
+        cert_type = TLS_CT_ECDSA_SIGN;
+        break;
+      default:
+        OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+        return false;
+    }
+    if (std::find(hs->certificate_types.begin(), hs->certificate_types.end(),
+                  cert_type) == hs->certificate_types.end()) {
+      OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+      return false;
+    }
+  }
+
+  // Check that we will be able to generate a signature. Note this does not
+  // check the ECDSA curve. Prior to TLS 1.3, there is no way to determine which
+  // ECDSA curves are supported by the peer, so we must assume all curves are
+  // supported.
+  uint16_t unused;
+  return tls1_choose_signature_algorithm(hs, cred, &unused);
+}
+
 static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
@@ -1358,42 +1395,38 @@
     }
   }
 
-  if (!ssl_on_certificate_selected(hs)) {
+  Array<SSL_CREDENTIAL *> creds;
+  if (!ssl_get_credential_list(hs, &creds)) {
     return ssl_hs_error;
   }
 
-  if (ssl_has_certificate(hs)) {
-    if (hs->config->check_client_certificate_type) {
-      // Check the certificate types advertised by the peer.
-      uint8_t cert_type;
-      switch (EVP_PKEY_id(hs->local_pubkey.get())) {
-        case EVP_PKEY_RSA:
-          cert_type = SSL3_CT_RSA_SIGN;
-          break;
-        case EVP_PKEY_EC:
-        case EVP_PKEY_ED25519:
-          cert_type = TLS_CT_ECDSA_SIGN;
-          break;
-        default:
-          OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-          return ssl_hs_error;
-      }
-      if (std::find(hs->certificate_types.begin(), hs->certificate_types.end(),
-                    cert_type) == hs->certificate_types.end()) {
-        OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-        return ssl_hs_error;
+  if (creds.empty()) {
+    // If there were no credentials, proceed without a client certificate. In
+    // this case, the handshake buffer may be released early.
+    hs->transcript.FreeBuffer();
+  } else {
+    // Select the credential to use.
+    //
+    // TODO(davidben): In doing so, we pick the signature algorithm. Save that
+    // decision to avoid redoing it later.
+    for (SSL_CREDENTIAL *cred : creds) {
+      ERR_clear_error();
+      if (check_credential(hs, cred)) {
+        hs->credential = UpRef(cred);
+        break;
       }
     }
-  } else {
-    // Without a client certificate, the handshake buffer may be released.
-    hs->transcript.FreeBuffer();
+    if (hs->credential == nullptr) {
+      // The error from the last attempt is in the error queue.
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
+      return ssl_hs_error;
+    }
   }
 
   if (!ssl_send_tls12_certificate(hs)) {
     return ssl_hs_error;
   }
 
-
   hs->state = state_send_client_key_exchange;
   return ssl_hs_ok;
 }
@@ -1570,12 +1603,11 @@
 static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
-  if (!hs->cert_request || !ssl_has_certificate(hs)) {
+  if (!hs->cert_request || hs->credential == nullptr) {
     hs->state = state_send_client_finished;
     return ssl_hs_ok;
   }
 
-  assert(ssl_has_private_key(hs));
   ScopedCBB cbb;
   CBB body, child;
   if (!ssl->method->init_message(ssl, cbb.get(), &body,
@@ -1584,7 +1616,8 @@
   }
 
   uint16_t signature_algorithm;
-  if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
+  if (!tls1_choose_signature_algorithm(hs, hs->credential.get(),
+                                       &signature_algorithm)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
     return ssl_hs_error;
   }
@@ -1597,7 +1630,7 @@
   }
 
   // Set aside space for the signature.
-  const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey.get());
+  const size_t max_sig_len = EVP_PKEY_size(hs->credential->pubkey.get());
   uint8_t *ptr;
   if (!CBB_add_u16_length_prefixed(&body, &child) ||
       !CBB_reserve(&child, &ptr, max_sig_len)) {
diff --git a/ssl/handshake_server.cc b/ssl/handshake_server.cc
index 510062a..fc5202a 100644
--- a/ssl/handshake_server.cc
+++ b/ssl/handshake_server.cc
@@ -298,6 +298,7 @@
 // exchange mask and |*out_mask_a| to the authentication mask. It returns true
 // on success and false on error.
 static bool ssl_get_compatible_server_ciphers(SSL_HANDSHAKE *hs,
+                                              const SSL_CREDENTIAL *cred,
                                               uint32_t *out_mask_k,
                                               uint32_t *out_mask_a) {
   uint32_t mask_k = 0;
@@ -315,15 +316,15 @@
     mask_a |= SSL_aPSK;
   }
 
-  if (ssl_has_certificate(hs)) {
-    bool sign_ok = tls1_choose_signature_algorithm(hs, &unused);
+  if (cred != nullptr && cred->type == SSLCredentialType::kX509) {
+    bool sign_ok = tls1_choose_signature_algorithm(hs, cred, &unused);
     ERR_clear_error();
 
     // ECDSA keys must additionally be checked against the peer's supported
     // curve list.
-    int key_type = EVP_PKEY_id(hs->local_pubkey.get());
+    int key_type = EVP_PKEY_id(cred->pubkey.get());
     if (hs->config->check_ecdsa_curve && key_type == EVP_PKEY_EC) {
-      EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(hs->local_pubkey.get());
+      EC_KEY *ec_key = EVP_PKEY_get0_EC_KEY(cred->pubkey.get());
       uint16_t group_id;
       if (!ssl_nid_to_group_id(
               &group_id, EC_GROUP_get_curve_name(EC_KEY_get0_group(ec_key))) ||
@@ -342,7 +343,7 @@
       }
     }
 
-    mask_a |= ssl_cipher_auth_mask_for_key(hs->local_pubkey.get(), sign_ok);
+    mask_a |= ssl_cipher_auth_mask_for_key(cred->pubkey.get(), sign_ok);
     if (key_type == EVP_PKEY_RSA) {
       mask_k |= SSL_kRSA;
     }
@@ -354,8 +355,8 @@
 }
 
 static const SSL_CIPHER *choose_cipher(
-    SSL_HANDSHAKE *hs, const SSL_CLIENT_HELLO *client_hello,
-    const SSLCipherPreferenceList *server_pref) {
+    SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred,
+    const STACK_OF(SSL_CIPHER) *client_pref) {
   SSL *const ssl = hs->ssl;
   const STACK_OF(SSL_CIPHER) *prio, *allow;
   // in_group_flags will either be NULL, or will point to an array of bytes
@@ -367,24 +368,21 @@
   // such value exists yet.
   int group_min = -1;
 
-  UniquePtr<STACK_OF(SSL_CIPHER)> client_pref =
-      ssl_parse_client_cipher_list(client_hello);
-  if (!client_pref) {
-    return nullptr;
-  }
-
+  const SSLCipherPreferenceList *server_pref =
+      hs->config->cipher_list ? hs->config->cipher_list.get()
+                              : ssl->ctx->cipher_list.get();
   if (ssl->options & SSL_OP_CIPHER_SERVER_PREFERENCE) {
     prio = server_pref->ciphers.get();
     in_group_flags = server_pref->in_group_flags;
-    allow = client_pref.get();
+    allow = client_pref;
   } else {
-    prio = client_pref.get();
+    prio = client_pref;
     in_group_flags = NULL;
     allow = server_pref->ciphers.get();
   }
 
   uint32_t mask_k, mask_a;
-  if (!ssl_get_compatible_server_ciphers(hs, &mask_k, &mask_a)) {
+  if (!ssl_get_compatible_server_ciphers(hs, cred, &mask_k, &mask_a)) {
     return nullptr;
   }
 
@@ -779,11 +777,11 @@
     return ssl_hs_error;
   }
 
-  hs->state = state12_select_certificate;
+  hs->state = state12_cert_callback;
   return ssl_hs_ok;
 }
 
-static enum ssl_hs_wait_t do_select_certificate(SSL_HANDSHAKE *hs) {
+static enum ssl_hs_wait_t do_cert_callback(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
   // Call |cert_cb| to update server certificates if required.
@@ -799,10 +797,6 @@
     }
   }
 
-  if (!ssl_on_certificate_selected(hs)) {
-    return ssl_hs_error;
-  }
-
   if (hs->ocsp_stapling_requested &&
       ssl->ctx->legacy_ocsp_callback != nullptr) {
     switch (ssl->ctx->legacy_ocsp_callback(
@@ -832,23 +826,6 @@
 
   ssl->s3->early_data_reason = ssl_early_data_protocol_version;
 
-  SSLMessage msg_unused;
-  SSL_CLIENT_HELLO client_hello;
-  if (!hs->GetClientHello(&msg_unused, &client_hello)) {
-    return ssl_hs_error;
-  }
-
-  // Negotiate the cipher suite. This must be done after |cert_cb| so the
-  // certificate is finalized.
-  SSLCipherPreferenceList *prefs = hs->config->cipher_list
-                                       ? hs->config->cipher_list.get()
-                                       : ssl->ctx->cipher_list.get();
-  hs->new_cipher = choose_cipher(hs, &client_hello, prefs);
-  if (hs->new_cipher == NULL) {
-    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
-    return ssl_hs_error;
-  }
-
   hs->state = state12_select_parameters;
   return ssl_hs_ok;
 }
@@ -865,14 +842,45 @@
 
 static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
-
   SSLMessage msg;
-  if (!ssl->method->get_message(ssl, &msg)) {
-    return ssl_hs_read_message;
+  SSL_CLIENT_HELLO client_hello;
+  if (!hs->GetClientHello(&msg, &client_hello)) {
+    return ssl_hs_error;
   }
 
-  SSL_CLIENT_HELLO client_hello;
-  if (!ssl_client_hello_init(ssl, &client_hello, msg.body)) {
+  // Select the credential and cipher suite. This must be done after |cert_cb|
+  // runs, so the final credential list is known.
+  //
+  // TODO(davidben): In the course of picking these, we also pick the ECDHE
+  // group and signature algorithm. It would be tidier if we saved that decision
+  // and avoided redoing it later.
+  UniquePtr<STACK_OF(SSL_CIPHER)> client_pref =
+      ssl_parse_client_cipher_list(&client_hello);
+  if (client_pref == nullptr) {
+    return ssl_hs_error;
+  }
+  Array<SSL_CREDENTIAL *> creds;
+  if (!ssl_get_credential_list(hs, &creds)) {
+    return ssl_hs_error;
+  }
+  if (creds.empty()) {
+    // The caller may have configured no credentials, but set a PSK callback.
+    hs->new_cipher = choose_cipher(hs, /*cred=*/nullptr, client_pref.get());
+  } else {
+    // Select the first credential which works.
+    for (SSL_CREDENTIAL *cred : creds) {
+      ERR_clear_error();
+      hs->new_cipher = choose_cipher(hs, cred, client_pref.get());
+      if (hs->new_cipher != nullptr) {
+        hs->credential = UpRef(cred);
+        break;
+      }
+    }
+  }
+
+  if (hs->new_cipher == nullptr) {
+    // The error from the last attempt is in the error queue.
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
     return ssl_hs_error;
   }
 
@@ -1088,11 +1096,7 @@
   ScopedCBB cbb;
 
   if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-    if (!ssl_has_certificate(hs)) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
-      return ssl_hs_error;
-    }
-
+    assert(hs->credential != nullptr);
     if (!ssl_send_tls12_certificate(hs)) {
       return ssl_hs_error;
     }
@@ -1105,8 +1109,8 @@
           !CBB_add_u24_length_prefixed(&body, &ocsp_response) ||
           !CBB_add_bytes(
               &ocsp_response,
-              CRYPTO_BUFFER_data(hs->config->cert->ocsp_response.get()),
-              CRYPTO_BUFFER_len(hs->config->cert->ocsp_response.get())) ||
+              CRYPTO_BUFFER_data(hs->credential->ocsp_response.get()),
+              CRYPTO_BUFFER_len(hs->credential->ocsp_response.get())) ||
           !ssl_add_message_cbb(ssl, cbb.get())) {
         OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
         return ssl_hs_error;
@@ -1227,14 +1231,10 @@
 
   // Add a signature.
   if (ssl_cipher_uses_certificate_auth(hs->new_cipher)) {
-    if (!ssl_has_private_key(hs)) {
-      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
-      return ssl_hs_error;
-    }
-
     // Determine the signature algorithm.
     uint16_t signature_algorithm;
-    if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
+    if (!tls1_choose_signature_algorithm(hs, hs->credential.get(),
+                                         &signature_algorithm)) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
       return ssl_hs_error;
     }
@@ -1247,7 +1247,7 @@
     }
 
     // Add space for the signature.
-    const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey.get());
+    const size_t max_sig_len = EVP_PKEY_size(hs->credential->pubkey.get());
     uint8_t *ptr;
     if (!CBB_add_u16_length_prefixed(&body, &child) ||
         !CBB_reserve(&child, &ptr, max_sig_len)) {
@@ -1455,7 +1455,7 @@
 
     // Allocate a buffer large enough for an RSA decryption.
     Array<uint8_t> decrypt_buf;
-    if (!decrypt_buf.Init(EVP_PKEY_size(hs->local_pubkey.get()))) {
+    if (!decrypt_buf.Init(EVP_PKEY_size(hs->credential->pubkey.get()))) {
       return ssl_hs_error;
     }
 
@@ -1890,8 +1890,8 @@
       case state12_read_client_hello_after_ech:
         ret = do_read_client_hello_after_ech(hs);
         break;
-      case state12_select_certificate:
-        ret = do_select_certificate(hs);
+      case state12_cert_callback:
+        ret = do_cert_callback(hs);
         break;
       case state12_tls13:
         ret = do_tls13(hs);
@@ -1972,8 +1972,8 @@
       return "TLS server read_client_hello";
     case state12_read_client_hello_after_ech:
       return "TLS server read_client_hello_after_ech";
-    case state12_select_certificate:
-      return "TLS server select_certificate";
+    case state12_cert_callback:
+      return "TLS server cert_callback";
     case state12_tls13:
       return tls13_server_handshake_state(hs);
     case state12_select_parameters:
diff --git a/ssl/internal.h b/ssl/internal.h
index 9f61536..cc98538 100644
--- a/ssl/internal.h
+++ b/ssl/internal.h
@@ -1079,9 +1079,6 @@
 
 // Private key operations.
 
-// ssl_has_private_key returns whether |hs| has a private key configured.
-bool ssl_has_private_key(const SSL_HANDSHAKE *hs);
-
 // ssl_private_key_* perform the corresponding operation on
 // |SSL_PRIVATE_KEY_METHOD|. If there is a custom private key configured, they
 // call the corresponding function or |complete| depending on whether there is a
@@ -1098,10 +1095,10 @@
                                                       size_t max_out,
                                                       Span<const uint8_t> in);
 
-// ssl_private_key_supports_signature_algorithm returns whether |hs|'s private
-// key supports |sigalg|.
-bool ssl_private_key_supports_signature_algorithm(SSL_HANDSHAKE *hs,
-                                                  uint16_t sigalg);
+// ssl_pkey_supports_algorithm returns whether |pkey| may be used to sign
+// |sigalg|.
+bool ssl_pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
+                                 uint16_t sigalg);
 
 // ssl_public_key_verify verifies that the |signature| is valid for the public
 // key |pkey| and input |in|, using the signature algorithm |sigalg|.
@@ -1339,10 +1336,6 @@
 
 // Certificate functions.
 
-// ssl_has_certificate returns whether a certificate and private key are
-// configured.
-bool ssl_has_certificate(const SSL_HANDSHAKE *hs);
-
 // ssl_parse_cert_chain parses a certificate list from |cbs| in the format used
 // by a TLS Certificate message. On success, it advances |cbs| and returns
 // true. Otherwise, it returns false and sets |*out_alert| to an alert to send
@@ -1398,11 +1391,6 @@
 bool ssl_check_leaf_certificate(SSL_HANDSHAKE *hs, EVP_PKEY *pkey,
                                const CRYPTO_BUFFER *leaf);
 
-// ssl_on_certificate_selected is called once the certificate has been selected.
-// It finalizes the certificate and initializes |hs->local_pubkey|. It returns
-// true on success and false on error.
-bool ssl_on_certificate_selected(SSL_HANDSHAKE *hs);
-
 
 // TLS 1.3 key derivation.
 
@@ -1611,45 +1599,113 @@
 bool ssl_encrypt_client_hello(SSL_HANDSHAKE *hs, Span<const uint8_t> enc);
 
 
-// Delegated credentials.
+// Credentials.
 
-// This structure stores a delegated credential (DC) as defined by RFC 9345.
-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 RFC 9345.
-  UniquePtr<CRYPTO_BUFFER> raw;
-
-  // dc_cert_verify_algorithm is the signature scheme of the DC public key. This
-  // is used for the CertificateVerify message.
-  uint16_t dc_cert_verify_algorithm = 0;
-
-  // algorithm is the signature scheme of the signature over the delegated
-  // credential itself, made by the end-entity certificate's public key.
-  uint16_t algorithm = 0;
-
-  // pkey is the public key parsed from |public_key|.
-  UniquePtr<EVP_PKEY> pkey;
-
- private:
-  friend DC* New<DC>();
-  DC();
+enum class SSLCredentialType {
+  kX509,
+  kDelegated,
 };
 
-// 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);
+BSSL_NAMESPACE_END
+
+// SSL_CREDENTIAL is exported to C, so it must be defined outside the namespace.
+struct ssl_credential_st : public bssl::RefCounted<ssl_credential_st> {
+  explicit ssl_credential_st(bssl::SSLCredentialType type);
+  ssl_credential_st(const ssl_credential_st &) = delete;
+  ssl_credential_st &operator=(const ssl_credential_st &) = delete;
+
+  // Dup returns a copy of the credential, or nullptr on error. The |ex_data|
+  // values are not copied. This is only used on the default credential, whose
+  // |ex_data| is inaccessible.
+  bssl::UniquePtr<SSL_CREDENTIAL> Dup() const;
+
+  // ClearCertAndKey erases any certificate and private key on the credential.
+  void ClearCertAndKey();
+
+  // UsesX509 returns true if the credential type uses an X.509 certificate.
+  bool UsesX509() const;
+
+  // UsesPrivateKey returns true if the credential type uses an asymmetric
+  // private key.
+  bool UsesPrivateKey() const;
+
+  // IsComplete returns whether all required fields in the credential have been
+  // filled in.
+  bool IsComplete() const;
+
+  // SetLeafCert sets the leaf certificate to |leaf|, leaving the remaining
+  // certificates unmodified. It returns true on success and false on error. If
+  // |discard_key_on_mismatch| is true and the private key is inconsistent with
+  // the new leaf certificate, it is silently discarded.
+  bool SetLeafCert(bssl::UniquePtr<CRYPTO_BUFFER> leaf,
+                   bool discard_key_on_mismatch);
+
+  // AppendIntermediateCert appends |cert| to the certificate chain. If there is
+  // no leaf certificate configured, it leaves a placeholder null in |chain|. It
+  // returns one on success and zero on error.
+  bool AppendIntermediateCert(bssl::UniquePtr<CRYPTO_BUFFER> cert);
+
+  // type is the credential type and determines which other fields apply.
+  bssl::SSLCredentialType type;
+
+  // pubkey is the cached public key of the credential. Unlike |privkey|, it is
+  // always present and is extracted from the certificate, delegated credential,
+  // etc.
+  bssl::UniquePtr<EVP_PKEY> pubkey;
+
+  // privkey is the private key of the credential. It may be omitted in favor of
+  // |key_method|.
+  bssl::UniquePtr<EVP_PKEY> privkey;
+
+  // key_method, if non-null, is a set of callbacks to call for private key
+  // operations.
+  const SSL_PRIVATE_KEY_METHOD *key_method = nullptr;
+
+  // sigalgs, if non-empty, is the set of signature algorithms supported by the
+  // private key in decreasing order of preference. If empty, the default list
+  // is used.
+  //
+  // In delegated credentials, this field is not configurable and is instead
+  // computed from the dc_cert_verify_algorithm field.
+  bssl::Array<uint16_t> sigalgs;
+
+  // chain contains the certificate chain, with the leaf at the beginning. The
+  // first element of |chain| may be nullptr to indicate that the leaf
+  // certificate has not yet been set.
+  //   If |chain| != nullptr -> len(chain) >= 1
+  //   If |chain[0]| == nullptr -> len(chain) >= 2.
+  //   |chain[1..]| != nullptr
+  bssl::UniquePtr<STACK_OF(CRYPTO_BUFFER)> chain;
+
+  // dc is the DelegatedCredential structure, if this is a delegated credential.
+  bssl::UniquePtr<CRYPTO_BUFFER> dc;
+
+  // dc_algorithm is the signature scheme of the signature over the delegated
+  // credential itself, made by the end-entity certificate's public key.
+  uint16_t dc_algorithm = 0;
+
+  // Signed certificate timestamp list to be sent to the client, if requested
+  bssl::UniquePtr<CRYPTO_BUFFER> signed_cert_timestamp_list;
+
+  // OCSP response to be sent to the client, if requested.
+  bssl::UniquePtr<CRYPTO_BUFFER> ocsp_response;
+
+  CRYPTO_EX_DATA ex_data;
+
+ private:
+  friend RefCounted;
+  ~ssl_credential_st();
+};
+
+BSSL_NAMESPACE_BEGIN
+
+// ssl_get_credential_list computes |hs|'s credential list. On success, it
+// writes it to |*out| and returns true. Otherwise, it returns false. The
+// credential list may be empty, in which case this function will successfully
+// return an empty array.
+//
+// The pointers in the result are only valid until |hs| is next mutated.
+bool ssl_get_credential_list(SSL_HANDSHAKE *hs, Array<SSL_CREDENTIAL *> *out);
 
 
 // Handshake functions.
@@ -1690,7 +1746,7 @@
   state12_start_accept = 0,
   state12_read_client_hello,
   state12_read_client_hello_after_ech,
-  state12_select_certificate,
+  state12_cert_callback,
   state12_tls13,
   state12_select_parameters,
   state12_send_server_hello,
@@ -1969,8 +2025,8 @@
   // received in a CertificateRequest message.
   Array<uint8_t> certificate_types;
 
-  // local_pubkey is the public key we are authenticating as.
-  UniquePtr<EVP_PKEY> local_pubkey;
+  // credential is the credential we are using for the handshake.
+  UniquePtr<SSL_CREDENTIAL> credential;
 
   // peer_pubkey is the public key parsed from the peer's leaf certificate.
   UniquePtr<EVP_PKEY> peer_pubkey;
@@ -2380,18 +2436,10 @@
 bool tls1_get_legacy_signature_algorithm(uint16_t *out, const EVP_PKEY *pkey);
 
 // tls1_choose_signature_algorithm sets |*out| to a signature algorithm for use
-// with |hs|'s private key based on the peer's preferences and the algorithms
-// 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);
+// with |cred| based on the peer's preferences and the algorithms supported. It
+// returns true on success and false on error.
+bool tls1_choose_signature_algorithm(SSL_HANDSHAKE *hs,
+                                     const SSL_CREDENTIAL *cred, uint16_t *out);
 
 // tls12_add_verify_sigalgs adds the signature algorithms acceptable for the
 // peer signature to |out|. It returns true on success and false on error.
@@ -2419,42 +2467,36 @@
   explicit CERT(const SSL_X509_METHOD *x509_method);
   ~CERT();
 
-  UniquePtr<EVP_PKEY> privatekey;
+  bool is_valid() const { return default_credential != nullptr; }
 
-  // chain contains the certificate chain, with the leaf at the beginning. The
-  // first element of |chain| may be NULL to indicate that the leaf certificate
-  // has not yet been set.
-  //   If |chain| != NULL -> len(chain) >= 1
-  //   If |chain[0]| == NULL -> len(chain) >= 2.
-  //   |chain[1..]| != NULL
-  UniquePtr<STACK_OF(CRYPTO_BUFFER)> chain;
+  // credentials is the list of credentials to select between. Elements of this
+  // array immutable.
+  GrowableArray<UniquePtr<SSL_CREDENTIAL>> credentials;
 
-  // x509_chain may contain a parsed copy of |chain[1..]|. This is only used as
-  // a cache in order to implement “get0” functions that return a non-owning
-  // pointer to the certificate chain.
-  STACK_OF(X509) *x509_chain = nullptr;
-
-  // x509_leaf may contain a parsed copy of the first element of |chain|. This
-  // is only used as a cache in order to implement “get0” functions that return
-  // a non-owning pointer to the certificate chain.
-  X509 *x509_leaf = nullptr;
-
-  // x509_stash contains the last |X509| object append to the chain. This is a
-  // workaround for some third-party code that continue to use an |X509| object
-  // even after passing ownership with an “add0” function.
-  X509 *x509_stash = nullptr;
-
-  // key_method, if non-NULL, is a set of callbacks to call for private key
-  // operations.
-  const SSL_PRIVATE_KEY_METHOD *key_method = nullptr;
+  // default_credential is the credential configured by the legacy,
+  // non-credential-based APIs. If IsComplete() returns true, it is appended to
+  // the list of credentials.
+  UniquePtr<SSL_CREDENTIAL> default_credential;
 
   // x509_method contains pointers to functions that might deal with |X509|
   // compatibility, or might be a no-op, depending on the application.
   const SSL_X509_METHOD *x509_method = nullptr;
 
-  // sigalgs, if non-empty, is the set of signature algorithms supported by
-  // |privatekey| in decreasing order of preference.
-  Array<uint16_t> sigalgs;
+  // x509_chain may contain a parsed copy of |chain[1..]| from the default
+  // credential. This is only used as a cache in order to implement “get0”
+  // functions that return a non-owning pointer to the certificate chain.
+  STACK_OF(X509) *x509_chain = nullptr;
+
+  // x509_leaf may contain a parsed copy of the first element of |chain| from
+  // the default credential. This is only used as a cache in order to implement
+  // “get0” functions that return a non-owning pointer to the certificate chain.
+  X509 *x509_leaf = nullptr;
+
+  // x509_stash contains the last |X509| object append to the default
+  // credential's chain. This is a workaround for some third-party code that
+  // continue to use an |X509| object even after passing ownership with an
+  // “add0” function.
+  X509 *x509_stash = nullptr;
 
   // Certificate setup callback: if set is called whenever a
   // certificate may be required (client or server). the callback
@@ -2469,29 +2511,10 @@
   // store is used instead.
   X509_STORE *verify_store = nullptr;
 
-  // Signed certificate timestamp list to be sent to the client, if requested
-  UniquePtr<CRYPTO_BUFFER> signed_cert_timestamp_list;
-
-  // OCSP response to be sent to the client, if requested.
-  UniquePtr<CRYPTO_BUFFER> ocsp_response;
-
   // sid_ctx partitions the session space within a shared session cache or
   // 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.
@@ -2795,10 +2818,6 @@
   // session_reused indicates whether a session was resumed.
   bool session_reused : 1;
 
-  // delegated_credential_used is whether we presented a delegated credential to
-  // the peer.
-  bool delegated_credential_used : 1;
-
   bool send_connection_binding : 1;
 
   // channel_id_valid is true if, on the server, the client has negotiated a
diff --git a/ssl/s3_lib.cc b/ssl/s3_lib.cc
index 8e8a034..b8b37b4 100644
--- a/ssl/s3_lib.cc
+++ b/ssl/s3_lib.cc
@@ -171,7 +171,6 @@
       has_message(false),
       initial_handshake_complete(false),
       session_reused(false),
-      delegated_credential_used(false),
       send_connection_binding(false),
       channel_id_valid(false),
       key_update_pending(false),
diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc
index 3cc9bae..39798ba 100644
--- a/ssl/ssl_cert.cc
+++ b/ssl/ssl_cert.cc
@@ -118,7 +118,6 @@
 #include <limits.h>
 #include <string.h>
 
-#include <algorithm>
 #include <utility>
 
 #include <openssl/bn.h>
@@ -136,33 +135,28 @@
 BSSL_NAMESPACE_BEGIN
 
 CERT::CERT(const SSL_X509_METHOD *x509_method_arg)
-    : x509_method(x509_method_arg) {}
+    : default_credential(MakeUnique<SSL_CREDENTIAL>(SSLCredentialType::kX509)),
+      x509_method(x509_method_arg) {}
 
 CERT::~CERT() { x509_method->cert_free(this); }
 
-static CRYPTO_BUFFER *buffer_up_ref(const CRYPTO_BUFFER *buffer) {
-  CRYPTO_BUFFER_up_ref(const_cast<CRYPTO_BUFFER *>(buffer));
-  return const_cast<CRYPTO_BUFFER *>(buffer);
-}
-
 UniquePtr<CERT> ssl_cert_dup(CERT *cert) {
   UniquePtr<CERT> ret = MakeUnique<CERT>(cert->x509_method);
   if (!ret) {
     return nullptr;
   }
 
-  if (cert->chain) {
-    ret->chain.reset(sk_CRYPTO_BUFFER_deep_copy(
-        cert->chain.get(), buffer_up_ref, CRYPTO_BUFFER_free));
-    if (!ret->chain) {
+  // TODO(crbug.com/boringssl/431): This should just be |CopyFrom|.
+  for (const auto &cred : cert->credentials) {
+    if (!ret->credentials.Push(UpRef(cred))) {
       return nullptr;
     }
   }
 
-  ret->privatekey = UpRef(cert->privatekey);
-  ret->key_method = cert->key_method;
-
-  if (!ret->sigalgs.CopyFrom(cert->sigalgs)) {
+  // |default_credential| is mutable, so it must be copied. We cannot simply
+  // bump the reference count.
+  ret->default_credential = cert->default_credential->Dup();
+  if (ret->default_credential == nullptr) {
     return nullptr;
   }
 
@@ -171,22 +165,9 @@
 
   ret->x509_method->cert_dup(ret.get(), cert);
 
-  ret->signed_cert_timestamp_list = UpRef(cert->signed_cert_timestamp_list);
-  ret->ocsp_response = UpRef(cert->ocsp_response);
-
   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;
 }
 
@@ -196,51 +177,6 @@
   cert->cert_cb_arg = arg;
 }
 
-enum leaf_cert_and_privkey_result_t {
-  leaf_cert_and_privkey_error,
-  leaf_cert_and_privkey_ok,
-  leaf_cert_and_privkey_mismatch,
-};
-
-// check_leaf_cert_and_privkey checks whether the certificate in |leaf_buffer|
-// and the private key in |privkey| are suitable and coherent. It returns
-// |leaf_cert_and_privkey_error| and pushes to the error queue if a problem is
-// found. If the certificate and private key are valid, but incoherent, it
-// returns |leaf_cert_and_privkey_mismatch|. Otherwise it returns
-// |leaf_cert_and_privkey_ok|.
-static enum leaf_cert_and_privkey_result_t check_leaf_cert_and_privkey(
-    CRYPTO_BUFFER *leaf_buffer, EVP_PKEY *privkey) {
-  CBS cert_cbs;
-  CRYPTO_BUFFER_init_CBS(leaf_buffer, &cert_cbs);
-  UniquePtr<EVP_PKEY> pubkey = ssl_cert_parse_pubkey(&cert_cbs);
-  if (!pubkey) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    return leaf_cert_and_privkey_error;
-  }
-
-  if (!ssl_is_key_type_supported(EVP_PKEY_id(pubkey.get()))) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-    return leaf_cert_and_privkey_error;
-  }
-
-  // An ECC certificate may be usable for ECDH or ECDSA. We only support ECDSA
-  // certificates, so sanity-check the key usage extension.
-  if (EVP_PKEY_id(pubkey.get()) == EVP_PKEY_EC &&
-      !ssl_cert_check_key_usage(&cert_cbs, key_usage_digital_signature)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-    return leaf_cert_and_privkey_error;
-  }
-
-  if (privkey != NULL &&
-      // Sanity-check that the private key and the certificate match.
-      !ssl_compare_public_and_private_key(pubkey.get(), privkey)) {
-    ERR_clear_error();
-    return leaf_cert_and_privkey_mismatch;
-  }
-
-  return leaf_cert_and_privkey_ok;
-}
-
 static int cert_set_chain_and_key(
     CERT *cert, CRYPTO_BUFFER *const *certs, size_t num_certs,
     EVP_PKEY *privkey, const SSL_PRIVATE_KEY_METHOD *privkey_method) {
@@ -255,75 +191,34 @@
     return 0;
   }
 
-  switch (check_leaf_cert_and_privkey(certs[0], privkey)) {
-    case leaf_cert_and_privkey_error:
-      return 0;
-    case leaf_cert_and_privkey_mismatch:
-      OPENSSL_PUT_ERROR(SSL, SSL_R_CERTIFICATE_AND_PRIVATE_KEY_MISMATCH);
-      return 0;
-    case leaf_cert_and_privkey_ok:
-      break;
-  }
-
-  UniquePtr<STACK_OF(CRYPTO_BUFFER)> certs_sk(sk_CRYPTO_BUFFER_new_null());
-  if (!certs_sk) {
+  if (!SSL_CREDENTIAL_set1_cert_chain(cert->default_credential.get(), certs,
+                                      num_certs)) {
     return 0;
   }
 
-  for (size_t i = 0; i < num_certs; i++) {
-    if (!PushToStack(certs_sk.get(), UpRef(certs[i]))) {
-      return 0;
-    }
-  }
+  cert->x509_method->cert_flush_cached_leaf(cert);
+  cert->x509_method->cert_flush_cached_chain(cert);
 
-  cert->privatekey = UpRef(privkey);
-  cert->key_method = privkey_method;
-
-  cert->chain = std::move(certs_sk);
-  return 1;
+  return privkey != nullptr
+             ? SSL_CREDENTIAL_set1_private_key(cert->default_credential.get(),
+                                               privkey)
+             : SSL_CREDENTIAL_set_private_key_method(
+                   cert->default_credential.get(), privkey_method);
 }
 
 bool ssl_set_cert(CERT *cert, UniquePtr<CRYPTO_BUFFER> buffer) {
-  switch (check_leaf_cert_and_privkey(buffer.get(), cert->privatekey.get())) {
-    case leaf_cert_and_privkey_error:
-      return false;
-    case leaf_cert_and_privkey_mismatch:
-      // don't fail for a cert/key mismatch, just free current private key
-      // (when switching to a different cert & key, first this function should
-      // be used, then |ssl_set_pkey|.
-      cert->privatekey.reset();
-      break;
-    case leaf_cert_and_privkey_ok:
-      break;
+  // Don't fail for a cert/key mismatch, just free the current private key.
+  // (When switching to a different keypair, the caller should switch the
+  // certificate, then the key.)
+  if (!cert->default_credential->SetLeafCert(
+          std::move(buffer), /*discard_key_on_mismatch=*/true)) {
+    return false;
   }
 
   cert->x509_method->cert_flush_cached_leaf(cert);
-
-  if (cert->chain != nullptr) {
-    CRYPTO_BUFFER_free(sk_CRYPTO_BUFFER_value(cert->chain.get(), 0));
-    sk_CRYPTO_BUFFER_set(cert->chain.get(), 0, buffer.release());
-    return true;
-  }
-
-  cert->chain.reset(sk_CRYPTO_BUFFER_new_null());
-  if (cert->chain == nullptr) {
-    return false;
-  }
-
-  if (!PushToStack(cert->chain.get(), std::move(buffer))) {
-    cert->chain.reset();
-    return false;
-  }
-
   return true;
 }
 
-bool ssl_has_certificate(const SSL_HANDSHAKE *hs) {
-  return hs->config->cert->chain != nullptr &&
-         sk_CRYPTO_BUFFER_value(hs->config->cert->chain.get(), 0) != nullptr &&
-         ssl_has_private_key(hs);
-}
-
 bool ssl_parse_cert_chain(uint8_t *out_alert,
                           UniquePtr<STACK_OF(CRYPTO_BUFFER)> *out_chain,
                           UniquePtr<EVP_PKEY> *out_pubkey,
@@ -653,167 +548,6 @@
   return true;
 }
 
-bool ssl_on_certificate_selected(SSL_HANDSHAKE *hs) {
-  SSL *const ssl = hs->ssl;
-  if (!ssl_has_certificate(hs)) {
-    // Nothing to do.
-    return true;
-  }
-
-  if (!ssl->ctx->x509_method->ssl_auto_chain_if_needed(hs)) {
-    return false;
-  }
-
-  CBS leaf;
-  CRYPTO_BUFFER_init_CBS(
-      sk_CRYPTO_BUFFER_value(hs->config->cert->chain.get(), 0), &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->dc_cert_verify_algorithm = dc_cert_verify_algorithm;
-  ret->algorithm = algorithm;
-  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;
-  CRYPTO_BUFFER_init_CBS(dc->raw.get(), &deleg);
-  if (!CBS_get_u32(&deleg, &valid_time) ||
-      !CBS_get_u16(&deleg, &dc->dc_cert_verify_algorithm) ||
-      !CBS_get_u24_length_prefixed(&deleg, &pubkey) ||
-      !CBS_get_u16(&deleg, &dc->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 || CBS_len(&pubkey) != 0) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
-    *out_alert = SSL_AD_DECODE_ERROR;
-    return nullptr;
-  }
-
-  // RFC 9345 forbids algorithms that use the rsaEncryption OID. As the
-  // RSASSA-PSS OID is unusably complicated, this effectively means we will not
-  // support RSA delegated credentials.
-  if (SSL_get_signature_algorithm_key_type(dc->dc_cert_verify_algorithm) ==
-      EVP_PKEY_RSA) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
-    *out_alert = SSL_AD_ILLEGAL_PARAMETER;
-    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 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 1.3 or higher has been negotiated.
-  const DC *dc = cert->dc.get();
-  assert(hs->ssl->s3->have_version);
-  if (ssl_protocol_version(hs->ssl) < TLS1_3_VERSION) {
-    return false;
-  }
-
-  // Check that the peer supports the signature over the delegated credential.
-  if (std::find(hs->peer_sigalgs.begin(), hs->peer_sigalgs.end(),
-                dc->algorithm) == hs->peer_sigalgs.end()) {
-    return false;
-  }
-
-  // Check that the peer supports the CertificateVerify signature algorithm.
-  if (std::find(hs->peer_delegated_credential_sigalgs.begin(),
-                hs->peer_delegated_credential_sigalgs.end(),
-                dc->dc_cert_verify_algorithm) ==
-      hs->peer_delegated_credential_sigalgs.end()) {
-    return false;
-  }
-
-  return true;
-}
-
-bool ssl_signing_with_dc(const SSL_HANDSHAKE *hs) {
-  // We only support delegated credentials as a server.
-  return hs->ssl->server && 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;
@@ -842,25 +576,19 @@
 
   CERT *cert = ssl->config->cert.get();
   cert->x509_method->cert_clear(cert);
-
-  cert->chain.reset();
-  cert->privatekey.reset();
-  cert->key_method = nullptr;
-
-  cert->dc.reset();
-  cert->dc_privatekey.reset();
-  cert->dc_key_method = nullptr;
+  cert->credentials.clear();
+  cert->default_credential->ClearCertAndKey();
 }
 
 const STACK_OF(CRYPTO_BUFFER) *SSL_CTX_get0_chain(const SSL_CTX *ctx) {
-  return ctx->cert->chain.get();
+  return ctx->cert->default_credential->chain.get();
 }
 
 const STACK_OF(CRYPTO_BUFFER) *SSL_get0_chain(const SSL *ssl) {
   if (!ssl->config) {
     return nullptr;
   }
-  return ssl->config->cert->chain.get();
+  return ssl->config->cert->default_credential->chain.get();
 }
 
 int SSL_CTX_use_certificate_ASN1(SSL_CTX *ctx, size_t der_len,
@@ -910,23 +638,11 @@
   return ssl->s3->hs->ca_names.get();
 }
 
-static int set_signed_cert_timestamp_list(CERT *cert, const uint8_t *list,
-                                          size_t list_len) {
-  CBS sct_list;
-  CBS_init(&sct_list, list, list_len);
-  if (!ssl_is_sct_list_valid(&sct_list)) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SCT_LIST);
-    return 0;
-  }
-
-  cert->signed_cert_timestamp_list.reset(
-      CRYPTO_BUFFER_new(CBS_data(&sct_list), CBS_len(&sct_list), nullptr));
-  return cert->signed_cert_timestamp_list != nullptr;
-}
-
 int SSL_CTX_set_signed_cert_timestamp_list(SSL_CTX *ctx, const uint8_t *list,
                                            size_t list_len) {
-  return set_signed_cert_timestamp_list(ctx->cert.get(), list, list_len);
+  UniquePtr<CRYPTO_BUFFER> buf(CRYPTO_BUFFER_new(list, list_len, nullptr));
+  return buf != nullptr && SSL_CREDENTIAL_set1_signed_cert_timestamp_list(
+                               ctx->cert->default_credential.get(), buf.get());
 }
 
 int SSL_set_signed_cert_timestamp_list(SSL *ssl, const uint8_t *list,
@@ -934,15 +650,18 @@
   if (!ssl->config) {
     return 0;
   }
-  return set_signed_cert_timestamp_list(ssl->config->cert.get(), list,
-                                        list_len);
+  UniquePtr<CRYPTO_BUFFER> buf(CRYPTO_BUFFER_new(list, list_len, nullptr));
+  return buf != nullptr &&
+         SSL_CREDENTIAL_set1_signed_cert_timestamp_list(
+             ssl->config->cert->default_credential.get(), buf.get());
 }
 
 int SSL_CTX_set_ocsp_response(SSL_CTX *ctx, const uint8_t *response,
                               size_t response_len) {
-  ctx->cert->ocsp_response.reset(
+  UniquePtr<CRYPTO_BUFFER> buf(
       CRYPTO_BUFFER_new(response, response_len, nullptr));
-  return ctx->cert->ocsp_response != nullptr;
+  return buf != nullptr && SSL_CREDENTIAL_set1_ocsp_response(
+                               ctx->cert->default_credential.get(), buf.get());
 }
 
 int SSL_set_ocsp_response(SSL *ssl, const uint8_t *response,
@@ -950,9 +669,11 @@
   if (!ssl->config) {
     return 0;
   }
-  ssl->config->cert->ocsp_response.reset(
+  UniquePtr<CRYPTO_BUFFER> buf(
       CRYPTO_BUFFER_new(response, response_len, nullptr));
-  return ssl->config->cert->ocsp_response != nullptr;
+  return buf != nullptr &&
+         SSL_CREDENTIAL_set1_ocsp_response(
+             ssl->config->cert->default_credential.get(), buf.get());
 }
 
 void SSL_CTX_set0_client_CAs(SSL_CTX *ctx, STACK_OF(CRYPTO_BUFFER) *name_list) {
@@ -967,16 +688,3 @@
   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);
-}
-
-int SSL_delegated_credential_used(const SSL *ssl) {
-  return ssl->s3->delegated_credential_used;
-}
diff --git a/ssl/ssl_credential.cc b/ssl/ssl_credential.cc
new file mode 100644
index 0000000..f787098
--- /dev/null
+++ b/ssl/ssl_credential.cc
@@ -0,0 +1,410 @@
+/* Copyright (c) 2024, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+#include <openssl/ssl.h>
+
+#include <assert.h>
+
+#include <openssl/span.h>
+
+#include "internal.h"
+#include "../crypto/internal.h"
+
+
+BSSL_NAMESPACE_BEGIN
+
+// new_leafless_chain returns a fresh stack of buffers set to {nullptr}.
+static UniquePtr<STACK_OF(CRYPTO_BUFFER)> new_leafless_chain(void) {
+  UniquePtr<STACK_OF(CRYPTO_BUFFER)> chain(sk_CRYPTO_BUFFER_new_null());
+  if (!chain ||
+      !sk_CRYPTO_BUFFER_push(chain.get(), nullptr)) {
+    return nullptr;
+  }
+
+  return chain;
+}
+
+bool ssl_get_credential_list(SSL_HANDSHAKE *hs, Array<SSL_CREDENTIAL *> *out) {
+  CERT *cert = hs->config->cert.get();
+  // Finish filling in the default credential if needed.
+  if (!cert->x509_method->ssl_auto_chain_if_needed(hs)) {
+    return false;
+  }
+
+  size_t num_creds = cert->credentials.size();
+  bool include_default = cert->default_credential->IsComplete();
+  if (include_default) {
+    num_creds++;
+  }
+
+  if (!out->Init(num_creds)) {
+    return false;
+  }
+
+  for (size_t i = 0; i < cert->credentials.size(); i++) {
+    (*out)[i] = cert->credentials[i].get();
+  }
+  if (include_default) {
+    (*out)[num_creds - 1] = cert->default_credential.get();
+  }
+  return true;
+}
+
+BSSL_NAMESPACE_END
+
+using namespace bssl;
+
+static CRYPTO_EX_DATA_CLASS g_ex_data_class = CRYPTO_EX_DATA_CLASS_INIT;
+
+ssl_credential_st::ssl_credential_st(SSLCredentialType type_arg)
+    : RefCounted(CheckSubClass()), type(type_arg) {
+  CRYPTO_new_ex_data(&ex_data);
+}
+
+ssl_credential_st::~ssl_credential_st() {
+  CRYPTO_free_ex_data(&g_ex_data_class, this, &ex_data);
+}
+
+static CRYPTO_BUFFER *buffer_up_ref(const CRYPTO_BUFFER *buffer) {
+  CRYPTO_BUFFER_up_ref(const_cast<CRYPTO_BUFFER *>(buffer));
+  return const_cast<CRYPTO_BUFFER *>(buffer);
+}
+
+UniquePtr<SSL_CREDENTIAL> ssl_credential_st::Dup() const {
+  assert(type == SSLCredentialType::kX509);
+  UniquePtr<SSL_CREDENTIAL> ret = MakeUnique<SSL_CREDENTIAL>(type);
+  if (ret == nullptr) {
+    return nullptr;
+  }
+
+  ret->pubkey = UpRef(pubkey);
+  ret->privkey = UpRef(privkey);
+  ret->key_method = key_method;
+  if (!ret->sigalgs.CopyFrom(sigalgs)) {
+    return nullptr;
+  }
+
+  if (chain) {
+    ret->chain.reset(sk_CRYPTO_BUFFER_deep_copy(chain.get(), buffer_up_ref,
+                                                CRYPTO_BUFFER_free));
+    if (!ret->chain) {
+      return nullptr;
+    }
+  }
+
+  ret->dc = UpRef(dc);
+  ret->signed_cert_timestamp_list = UpRef(signed_cert_timestamp_list);
+  ret->ocsp_response = UpRef(ocsp_response);
+  ret->dc_algorithm = dc_algorithm;
+  return ret;
+}
+
+void ssl_credential_st::ClearCertAndKey() {
+  pubkey = nullptr;
+  privkey = nullptr;
+  key_method = nullptr;
+  chain = nullptr;
+}
+
+bool ssl_credential_st::UsesX509() const {
+  // Currently, all credential types use X.509. However, we may add other
+  // certificate types in the future. Add the checks in the setters now, so we
+  // don't forget.
+  return true;
+}
+
+bool ssl_credential_st::UsesPrivateKey() const {
+  // Currently, all credential types use private keys. However, we may add PSK
+  return true;
+}
+
+bool ssl_credential_st::IsComplete() const {
+  // APIs like |SSL_use_certificate| and |SSL_set1_chain| configure the leaf and
+  // other certificates separately. It is possible for |chain| have a null leaf.
+  if (UsesX509() && (sk_CRYPTO_BUFFER_num(chain.get()) == 0 ||
+                     sk_CRYPTO_BUFFER_value(chain.get(), 0) == nullptr)) {
+    return false;
+  }
+  // We must have successfully extracted a public key from the certificate,
+  // delegated credential, etc.
+  if (UsesPrivateKey() && pubkey == nullptr) {
+    return false;
+  }
+  if (UsesPrivateKey() && privkey == nullptr && key_method == nullptr) {
+    return false;
+  }
+  if (type == SSLCredentialType::kDelegated && dc == nullptr) {
+    return false;
+  }
+  return true;
+}
+
+bool ssl_credential_st::SetLeafCert(UniquePtr<CRYPTO_BUFFER> leaf,
+                                    bool discard_key_on_mismatch) {
+  if (!UsesX509()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return false;
+  }
+
+  const bool private_key_matches_leaf = type != SSLCredentialType::kDelegated;
+
+  CBS cbs;
+  CRYPTO_BUFFER_init_CBS(leaf.get(), &cbs);
+  UniquePtr<EVP_PKEY> new_pubkey = ssl_cert_parse_pubkey(&cbs);
+  if (new_pubkey == nullptr) {
+    return false;
+  }
+
+  if (!ssl_is_key_type_supported(EVP_PKEY_id(new_pubkey.get()))) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+    return false;
+  }
+
+  // An ECC certificate may be usable for ECDH or ECDSA. We only support ECDSA
+  // certificates, so sanity-check the key usage extension.
+  if (EVP_PKEY_id(new_pubkey.get()) == EVP_PKEY_EC &&
+      !ssl_cert_check_key_usage(&cbs, key_usage_digital_signature)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+    return false;
+  }
+
+  if (private_key_matches_leaf && privkey != nullptr &&
+      !ssl_compare_public_and_private_key(new_pubkey.get(), privkey.get())) {
+    if (!discard_key_on_mismatch) {
+      return false;
+    }
+    ERR_clear_error();
+    privkey = nullptr;
+  }
+
+  if (chain == nullptr) {
+    chain = new_leafless_chain();
+    if (chain == nullptr) {
+      return false;
+    }
+  }
+
+  CRYPTO_BUFFER_free(sk_CRYPTO_BUFFER_value(chain.get(), 0));
+  sk_CRYPTO_BUFFER_set(chain.get(), 0, leaf.release());
+  if (private_key_matches_leaf) {
+    pubkey = std::move(new_pubkey);
+  }
+  return true;
+}
+
+bool ssl_credential_st::AppendIntermediateCert(UniquePtr<CRYPTO_BUFFER> cert) {
+  if (!UsesX509()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return false;
+  }
+
+  if (chain == nullptr) {
+    chain = new_leafless_chain();
+    if (chain == nullptr) {
+      return false;
+    }
+  }
+
+  return PushToStack(chain.get(), std::move(cert));
+}
+
+SSL_CREDENTIAL *SSL_CREDENTIAL_new_x509(void) {
+  return New<SSL_CREDENTIAL>(SSLCredentialType::kX509);
+}
+
+SSL_CREDENTIAL *SSL_CREDENTIAL_new_delegated(void) {
+  return New<SSL_CREDENTIAL>(SSLCredentialType::kDelegated);
+}
+
+void SSL_CREDENTIAL_up_ref(SSL_CREDENTIAL *cred) { cred->UpRefInternal(); }
+
+void SSL_CREDENTIAL_free(SSL_CREDENTIAL *cred) {
+  if (cred != nullptr) {
+    cred->DecRefInternal();
+  }
+}
+
+int SSL_CREDENTIAL_set1_private_key(SSL_CREDENTIAL *cred, EVP_PKEY *key) {
+  if (!cred->UsesPrivateKey()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  // If the public half has been configured, check |key| matches. |pubkey| will
+  // have been extracted from the certificate, delegated credential, etc.
+  if (cred->pubkey != nullptr &&
+      !ssl_compare_public_and_private_key(cred->pubkey.get(), key)) {
+    return false;
+  }
+
+  cred->privkey = UpRef(key);
+  return 1;
+}
+
+int SSL_CREDENTIAL_set_private_key_method(
+    SSL_CREDENTIAL *cred, const SSL_PRIVATE_KEY_METHOD *key_method) {
+  if (!cred->UsesPrivateKey()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  cred->key_method = key_method;
+  return 1;
+}
+
+int SSL_CREDENTIAL_set1_cert_chain(SSL_CREDENTIAL *cred,
+                                   CRYPTO_BUFFER *const *certs,
+                                   size_t num_certs) {
+  if (!cred->UsesX509() || num_certs == 0) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  if (!cred->SetLeafCert(UpRef(certs[0]), /*discard_key_on_mismatch=*/false)) {
+    return 0;
+  }
+
+  for (size_t i = 1; i < num_certs; i++) {
+    if (!cred->AppendIntermediateCert(UpRef(certs[i]))) {
+      return 0;
+    }
+  }
+
+  return 1;
+}
+
+int SSL_CREDENTIAL_set1_delegated_credential(
+    SSL_CREDENTIAL *cred, CRYPTO_BUFFER *dc) {
+  if (cred->type != SSLCredentialType::kDelegated) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  // Parse the delegated credential to check for validity, and extract a few
+  // fields from it. See RFC 9345, section 4.
+  CBS cbs, spki, sig;
+  uint32_t valid_time;
+  uint16_t dc_cert_verify_algorithm, algorithm;
+  CRYPTO_BUFFER_init_CBS(dc, &cbs);
+  if (!CBS_get_u32(&cbs, &valid_time) ||
+      !CBS_get_u16(&cbs, &dc_cert_verify_algorithm) ||
+      !CBS_get_u24_length_prefixed(&cbs, &spki) ||
+      !CBS_get_u16(&cbs, &algorithm) ||
+      !CBS_get_u16_length_prefixed(&cbs, &sig) ||  //
+      CBS_len(&sig) == 0 ||                        //
+      CBS_len(&cbs) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    return 0;
+  }
+
+  // RFC 9345 forbids algorithms that use the rsaEncryption OID. As the
+  // RSASSA-PSS OID is unusably complicated, this effectively means we will not
+  // support RSA delegated credentials.
+  if (SSL_get_signature_algorithm_key_type(dc_cert_verify_algorithm) ==
+      EVP_PKEY_RSA) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SIGNATURE_ALGORITHM);
+    return 0;
+  }
+
+  UniquePtr<EVP_PKEY> pubkey(EVP_parse_public_key(&spki));
+  if (pubkey == nullptr || CBS_len(&spki) != 0) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_DECODE_ERROR);
+    return 0;
+  }
+
+  if (!cred->sigalgs.CopyFrom(MakeConstSpan(&dc_cert_verify_algorithm, 1))) {
+    return 0;
+  }
+
+  if (cred->privkey != nullptr &&
+      !ssl_compare_public_and_private_key(pubkey.get(), cred->privkey.get())) {
+    return 0;
+  }
+
+  cred->dc = UpRef(dc);
+  cred->pubkey = std::move(pubkey);
+  cred->dc_algorithm = algorithm;
+  return 1;
+}
+
+int SSL_CREDENTIAL_set1_ocsp_response(SSL_CREDENTIAL *cred,
+                                      CRYPTO_BUFFER *ocsp) {
+  if (!cred->UsesX509()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  cred->ocsp_response = UpRef(ocsp);
+  return 1;
+}
+
+int SSL_CREDENTIAL_set1_signed_cert_timestamp_list(SSL_CREDENTIAL *cred,
+                                                   CRYPTO_BUFFER *sct_list) {
+  if (!cred->UsesX509()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  CBS cbs;
+  CRYPTO_BUFFER_init_CBS(sct_list, &cbs);
+  if (!ssl_is_sct_list_valid(&cbs)) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_INVALID_SCT_LIST);
+    return 0;
+  }
+
+  cred->signed_cert_timestamp_list = UpRef(sct_list);
+  return 1;
+}
+
+int SSL_CTX_add1_credential(SSL_CTX *ctx, SSL_CREDENTIAL *cred) {
+  if (!cred->IsComplete()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+  return ctx->cert->credentials.Push(UpRef(cred));
+}
+
+int SSL_add1_credential(SSL *ssl, SSL_CREDENTIAL *cred) {
+  if (ssl->config == nullptr) {
+    return 0;
+  }
+
+  if (!cred->IsComplete()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+  return ssl->config->cert->credentials.Push(UpRef(cred));
+}
+
+const SSL_CREDENTIAL *SSL_get0_selected_credential(const SSL *ssl) {
+  if (ssl->s3->hs == nullptr) {
+    return nullptr;
+  }
+  return ssl->s3->hs->credential.get();
+}
+
+int SSL_CREDENTIAL_get_ex_new_index(long argl, void *argp,
+                                    CRYPTO_EX_unused *unused,
+                                    CRYPTO_EX_dup *dup_unused,
+                                    CRYPTO_EX_free *free_func) {
+  return CRYPTO_get_ex_new_index_ex(&g_ex_data_class, argl, argp, free_func);
+}
+
+int SSL_CREDENTIAL_set_ex_data(SSL_CREDENTIAL *cred, int idx, void *arg) {
+  return CRYPTO_set_ex_data(&cred->ex_data, idx, arg);
+}
+
+void *SSL_CREDENTIAL_get_ex_data(const SSL_CREDENTIAL *cred, int idx) {
+  return CRYPTO_get_ex_data(&cred->ex_data, idx);
+}
diff --git a/ssl/ssl_lib.cc b/ssl/ssl_lib.cc
index 3531624..98f97eb 100644
--- a/ssl/ssl_lib.cc
+++ b/ssl/ssl_lib.cc
@@ -570,9 +570,10 @@
   ret->cert = MakeUnique<CERT>(method->x509_method);
   ret->sessions = lh_SSL_SESSION_new(ssl_session_hash, ssl_session_cmp);
   ret->client_CA.reset(sk_CRYPTO_BUFFER_new_null());
-  if (ret->cert == nullptr ||
-      ret->sessions == nullptr ||
-      ret->client_CA == nullptr ||
+  if (ret->cert == nullptr ||       //
+      !ret->cert->is_valid() ||     //
+      ret->sessions == nullptr ||   //
+      ret->client_CA == nullptr ||  //
       !ret->x509_method->ssl_ctx_new(ret.get())) {
     return nullptr;
   }
@@ -1726,15 +1727,15 @@
   return SSL_pending(ssl) != 0 || !ssl->s3->read_buffer.empty();
 }
 
-static bool has_cert_and_key(const CERT *cert) {
-  // TODO(davidben): If |cert->key_method| is set, that should be fine too.
-  if (cert->privatekey == nullptr) {
+static bool has_cert_and_key(const SSL_CREDENTIAL *cred) {
+  // TODO(davidben): If |cred->key_method| is set, that should be fine too.
+  if (cred->privkey == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_PRIVATE_KEY_ASSIGNED);
     return false;
   }
 
-  if (cert->chain == nullptr ||
-      sk_CRYPTO_BUFFER_value(cert->chain.get(), 0) == nullptr) {
+  if (cred->chain == nullptr ||
+      sk_CRYPTO_BUFFER_value(cred->chain.get(), 0) == nullptr) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_ASSIGNED);
     return false;
   }
@@ -1745,7 +1746,7 @@
 int SSL_CTX_check_private_key(const SSL_CTX *ctx) {
   // There is no need to actually check consistency because inconsistent values
   // can never be configured.
-  return has_cert_and_key(ctx->cert.get());
+  return has_cert_and_key(ctx->cert->default_credential.get());
 }
 
 int SSL_check_private_key(const SSL *ssl) {
@@ -1755,7 +1756,7 @@
 
   // There is no need to actually check consistency because inconsistent values
   // can never be configured.
-  return has_cert_and_key(ssl->config->cert.get());
+  return has_cert_and_key(ssl->config->cert->default_credential.get());
 }
 
 long SSL_get_default_timeout(const SSL *ssl) {
@@ -2532,11 +2533,11 @@
     assert(ssl->config);
     return nullptr;
   }
-  return ssl->config->cert->privatekey.get();
+  return ssl->config->cert->default_credential->privkey.get();
 }
 
 EVP_PKEY *SSL_CTX_get0_privatekey(const SSL_CTX *ctx) {
-  return ctx->cert->privatekey.get();
+  return ctx->cert->default_credential->privkey.get();
 }
 
 const SSL_CIPHER *SSL_get_current_cipher(const SSL *ssl) {
diff --git a/ssl/ssl_privkey.cc b/ssl/ssl_privkey.cc
index b3bb2c8..9cf2c8f 100644
--- a/ssl/ssl_privkey.cc
+++ b/ssl/ssl_privkey.cc
@@ -59,6 +59,8 @@
 #include <assert.h>
 #include <limits.h>
 
+#include <algorithm>
+
 #include <openssl/ec.h>
 #include <openssl/ec_key.h>
 #include <openssl/err.h>
@@ -77,33 +79,6 @@
          key_type == EVP_PKEY_ED25519;
 }
 
-static bool ssl_set_pkey(CERT *cert, EVP_PKEY *pkey) {
-  if (!ssl_is_key_type_supported(EVP_PKEY_id(pkey))) {
-    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
-    return false;
-  }
-
-  // If the leaf certificate has been configured, check it matches.
-  const CRYPTO_BUFFER *leaf = cert->chain != nullptr
-                                  ? sk_CRYPTO_BUFFER_value(cert->chain.get(), 0)
-                                  : nullptr;
-  if (leaf != nullptr) {
-    CBS cert_cbs;
-    CRYPTO_BUFFER_init_CBS(leaf, &cert_cbs);
-    UniquePtr<EVP_PKEY> pubkey = ssl_cert_parse_pubkey(&cert_cbs);
-    if (!pubkey) {
-      OPENSSL_PUT_ERROR(X509, X509_R_UNKNOWN_KEY_TYPE);
-      return false;
-    }
-    if (!ssl_compare_public_and_private_key(pubkey.get(), pkey)) {
-      return false;
-    }
-  }
-
-  cert->privatekey = UpRef(pkey);
-  return true;
-}
-
 typedef struct {
   uint16_t sigalg;
   int pkey_type;
@@ -144,21 +119,21 @@
   return NULL;
 }
 
-bool ssl_has_private_key(const SSL_HANDSHAKE *hs) {
-  if (hs->config->cert->privatekey != nullptr ||
-      hs->config->cert->key_method != nullptr ||
-      ssl_signing_with_dc(hs)) {
-    return true;
+bool ssl_pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
+                                 uint16_t sigalg) {
+  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
+  if (alg == NULL || EVP_PKEY_id(pkey) != alg->pkey_type) {
+    return false;
   }
 
-  return false;
-}
-
-static bool pkey_supports_algorithm(const SSL *ssl, EVP_PKEY *pkey,
-                                    uint16_t sigalg) {
-  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
-  if (alg == NULL ||
-      EVP_PKEY_id(pkey) != alg->pkey_type) {
+  // Ensure the RSA key is large enough for the hash. RSASSA-PSS requires that
+  // emLen be at least hLen + sLen + 2. Both hLen and sLen are the size of the
+  // hash in TLS. Reasonable RSA key sizes are large enough for the largest
+  // defined RSASSA-PSS algorithm, but 1024-bit RSA is slightly too small for
+  // SHA-512. 1024-bit RSA is sometimes used for test credentials, so check the
+  // size so that we can fall back to another algorithm in that case.
+  if (alg->is_rsa_pss &&
+      (size_t)EVP_PKEY_size(pkey) < 2 * EVP_MD_size(alg->digest_func()) + 2) {
     return false;
   }
 
@@ -196,7 +171,7 @@
 
 static bool setup_ctx(SSL *ssl, EVP_MD_CTX *ctx, EVP_PKEY *pkey,
                       uint16_t sigalg, bool is_verify) {
-  if (!pkey_supports_algorithm(ssl, pkey, sigalg)) {
+  if (!ssl_pkey_supports_algorithm(ssl, pkey, sigalg)) {
     OPENSSL_PUT_ERROR(SSL, SSL_R_WRONG_SIGNATURE_TYPE);
     return false;
   }
@@ -226,12 +201,13 @@
     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;
+  const SSL_CREDENTIAL *const cred = hs->credential.get();
   SSL_HANDSHAKE_HINTS *const hints = hs->hints.get();
   Array<uint8_t> spki;
   if (hints) {
     ScopedCBB spki_cbb;
     if (!CBB_init(spki_cbb.get(), 64) ||
-        !EVP_marshal_public_key(spki_cbb.get(), hs->local_pubkey.get()) ||
+        !EVP_marshal_public_key(spki_cbb.get(), cred->pubkey.get()) ||
         !CBBFinishArray(spki_cbb.get(), &spki)) {
       ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
       return ssl_private_key_failure;
@@ -251,13 +227,9 @@
     return ssl_private_key_success;
   }
 
-  const SSL_PRIVATE_KEY_METHOD *key_method = hs->config->cert->key_method;
-  EVP_PKEY *privatekey = hs->config->cert->privatekey.get();
+  const SSL_PRIVATE_KEY_METHOD *key_method = cred->key_method;
+  EVP_PKEY *privkey = cred->privkey.get();
   assert(!hs->can_release_private_key);
-  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;
@@ -277,7 +249,7 @@
   } else {
     *out_len = max_out;
     ScopedEVP_MD_CTX ctx;
-    if (!setup_ctx(ssl, ctx.get(), privatekey, sigalg, false /* sign */) ||
+    if (!setup_ctx(ssl, ctx.get(), privkey, sigalg, false /* sign */) ||
         !EVP_DigestSign(ctx.get(), out, out_len, in.data(), in.size())) {
       return ssl_private_key_failure;
     }
@@ -317,14 +289,15 @@
                                                       size_t max_out,
                                                       Span<const uint8_t> in) {
   SSL *const ssl = hs->ssl;
+  const SSL_CREDENTIAL *const cred = hs->credential.get();
   assert(!hs->can_release_private_key);
-  if (hs->config->cert->key_method != NULL) {
+  if (cred->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 = cred->key_method->complete(ssl, out, out_len, max_out);
     } else {
-      ret = hs->config->cert->key_method->decrypt(ssl, out, out_len, max_out,
-                                                  in.data(), in.size());
+      ret = cred->key_method->decrypt(ssl, out, out_len, max_out, in.data(),
+                                      in.size());
     }
     if (ret == ssl_private_key_failure) {
       OPENSSL_PUT_ERROR(SSL, SSL_R_PRIVATE_KEY_OPERATION_FAILED);
@@ -333,7 +306,7 @@
     return ret;
   }
 
-  RSA *rsa = EVP_PKEY_get0_RSA(hs->config->cert->privatekey.get());
+  RSA *rsa = EVP_PKEY_get0_RSA(cred->privkey.get());
   if (rsa == NULL) {
     // Decrypt operations are only supported for RSA keys.
     OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
@@ -349,28 +322,6 @@
   return ssl_private_key_success;
 }
 
-bool ssl_private_key_supports_signature_algorithm(SSL_HANDSHAKE *hs,
-                                                  uint16_t sigalg) {
-  SSL *const ssl = hs->ssl;
-  if (!pkey_supports_algorithm(ssl, hs->local_pubkey.get(), sigalg)) {
-    return false;
-  }
-
-  // Ensure the RSA key is large enough for the hash. RSASSA-PSS requires that
-  // emLen be at least hLen + sLen + 2. Both hLen and sLen are the size of the
-  // hash in TLS. Reasonable RSA key sizes are large enough for the largest
-  // defined RSASSA-PSS algorithm, but 1024-bit RSA is slightly too small for
-  // SHA-512. 1024-bit RSA is sometimes used for test credentials, so check the
-  // size so that we can fall back to another algorithm in that case.
-  const SSL_SIGNATURE_ALGORITHM *alg = get_signature_algorithm(sigalg);
-  if (alg->is_rsa_pss && (size_t)EVP_PKEY_size(hs->local_pubkey.get()) <
-                             2 * EVP_MD_size(alg->digest_func()) + 2) {
-    return false;
-  }
-
-  return true;
-}
-
 BSSL_NAMESPACE_END
 
 using namespace bssl;
@@ -388,7 +339,7 @@
     return 0;
   }
 
-  return ssl_set_pkey(ssl->config->cert.get(), pkey.get());
+  return SSL_use_PrivateKey(ssl, pkey.get());
 }
 
 int SSL_use_RSAPrivateKey_ASN1(SSL *ssl, const uint8_t *der, size_t der_len) {
@@ -407,7 +358,8 @@
     return 0;
   }
 
-  return ssl_set_pkey(ssl->config->cert.get(), pkey);
+  return SSL_CREDENTIAL_set1_private_key(
+      ssl->config->cert->default_credential.get(), pkey);
 }
 
 int SSL_use_PrivateKey_ASN1(int type, SSL *ssl, const uint8_t *der,
@@ -440,7 +392,7 @@
     return 0;
   }
 
-  return ssl_set_pkey(ctx->cert.get(), pkey.get());
+  return SSL_CTX_use_PrivateKey(ctx, pkey.get());
 }
 
 int SSL_CTX_use_RSAPrivateKey_ASN1(SSL_CTX *ctx, const uint8_t *der,
@@ -460,7 +412,8 @@
     return 0;
   }
 
-  return ssl_set_pkey(ctx->cert.get(), pkey);
+  return SSL_CREDENTIAL_set1_private_key(ctx->cert->default_credential.get(),
+                                         pkey);
 }
 
 int SSL_CTX_use_PrivateKey_ASN1(int type, SSL_CTX *ctx, const uint8_t *der,
@@ -485,12 +438,14 @@
   if (!ssl->config) {
     return;
   }
-  ssl->config->cert->key_method = key_method;
+  BSSL_CHECK(SSL_CREDENTIAL_set_private_key_method(
+      ssl->config->cert->default_credential.get(), key_method));
 }
 
 void SSL_CTX_set_private_key_method(SSL_CTX *ctx,
                                     const SSL_PRIVATE_KEY_METHOD *key_method) {
-  ctx->cert->key_method = key_method;
+  BSSL_CHECK(SSL_CREDENTIAL_set_private_key_method(
+      ctx->cert->default_credential.get(), key_method));
 }
 
 static constexpr size_t kMaxSignatureAlgorithmNameLen = 23;
@@ -642,9 +597,28 @@
   return true;
 }
 
+int SSL_CREDENTIAL_set1_signing_algorithm_prefs(SSL_CREDENTIAL *cred,
+                                                const uint16_t *prefs,
+                                                size_t num_prefs) {
+  if (!cred->UsesPrivateKey()) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  // Delegated credentials are constrained to a single algorithm, so there is no
+  // need to configure this.
+  if (cred->type == SSLCredentialType::kDelegated) {
+    OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
+    return 0;
+  }
+
+  return set_sigalg_prefs(&cred->sigalgs, MakeConstSpan(prefs, num_prefs));
+}
+
 int SSL_CTX_set_signing_algorithm_prefs(SSL_CTX *ctx, const uint16_t *prefs,
                                         size_t num_prefs) {
-  return set_sigalg_prefs(&ctx->cert->sigalgs, MakeConstSpan(prefs, num_prefs));
+  return SSL_CREDENTIAL_set1_signing_algorithm_prefs(
+      ctx->cert->default_credential.get(), prefs, num_prefs);
 }
 
 int SSL_set_signing_algorithm_prefs(SSL *ssl, const uint16_t *prefs,
@@ -652,8 +626,8 @@
   if (!ssl->config) {
     return 0;
   }
-  return set_sigalg_prefs(&ssl->config->cert->sigalgs,
-                          MakeConstSpan(prefs, num_prefs));
+  return SSL_CREDENTIAL_set1_signing_algorithm_prefs(
+      ssl->config->cert->default_credential.get(), prefs, num_prefs);
 }
 
 static constexpr struct {
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc
index bc0c842..9247dc3 100644
--- a/ssl/ssl_test.cc
+++ b/ssl/ssl_test.cc
@@ -5652,7 +5652,7 @@
       continue;
     }
 
-    ExpectSigAlgsEqual(test.expected, ctx->cert->sigalgs);
+    ExpectSigAlgsEqual(test.expected, ctx->cert->default_credential->sigalgs);
   }
 }
 
@@ -5710,7 +5710,7 @@
       continue;
     }
 
-    ExpectSigAlgsEqual(test.expected, ctx->cert->sigalgs);
+    ExpectSigAlgsEqual(test.expected, ctx->cert->default_credential->sigalgs);
   }
 }
 
diff --git a/ssl/ssl_x509.cc b/ssl/ssl_x509.cc
index 06baf88..d7f1083 100644
--- a/ssl/ssl_x509.cc
+++ b/ssl/ssl_x509.cc
@@ -183,17 +183,6 @@
   return buffer;
 }
 
-// new_leafless_chain returns a fresh stack of buffers set to {NULL}.
-static UniquePtr<STACK_OF(CRYPTO_BUFFER)> new_leafless_chain(void) {
-  UniquePtr<STACK_OF(CRYPTO_BUFFER)> chain(sk_CRYPTO_BUFFER_new_null());
-  if (!chain ||
-      !sk_CRYPTO_BUFFER_push(chain.get(), nullptr)) {
-    return nullptr;
-  }
-
-  return chain;
-}
-
 static void ssl_crypto_x509_cert_flush_cached_leaf(CERT *cert) {
   X509_free(cert->x509_leaf);
   cert->x509_leaf = nullptr;
@@ -209,37 +198,14 @@
 // which case no change to |cert->chain| is made. It preverses the existing
 // leaf from |cert->chain|, if any.
 static bool ssl_cert_set1_chain(CERT *cert, STACK_OF(X509) *chain) {
-  UniquePtr<STACK_OF(CRYPTO_BUFFER)> new_chain;
-
-  if (cert->chain != nullptr) {
-    new_chain.reset(sk_CRYPTO_BUFFER_new_null());
-    if (!new_chain) {
-      return false;
-    }
-
-    // |leaf| might be NULL if it's a “leafless” chain.
-    CRYPTO_BUFFER *leaf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
-    if (!PushToStack(new_chain.get(), UpRef(leaf))) {
-      return false;
-    }
-  }
-
   for (X509 *x509 : chain) {
-    if (!new_chain) {
-      new_chain = new_leafless_chain();
-      if (!new_chain) {
-        return false;
-      }
-    }
-
     UniquePtr<CRYPTO_BUFFER> buffer = x509_to_buffer(x509);
     if (!buffer ||
-        !PushToStack(new_chain.get(), std::move(buffer))) {
+        !cert->default_credential->AppendIntermediateCert(std::move(buffer))) {
       return false;
     }
   }
 
-  cert->chain = std::move(new_chain);
   ssl_crypto_x509_cert_flush_cached_chain(cert);
   return true;
 }
@@ -445,17 +411,17 @@
 }
 
 static bool ssl_crypto_x509_ssl_auto_chain_if_needed(SSL_HANDSHAKE *hs) {
-  // Only build a chain if there are no intermediates configured and the feature
-  // isn't disabled.
-  SSL *const ssl = hs->ssl;
-  if ((ssl->mode & SSL_MODE_NO_AUTO_CHAIN) || !ssl_has_certificate(hs) ||
-      hs->config->cert->chain == NULL ||
-      sk_CRYPTO_BUFFER_num(hs->config->cert->chain.get()) > 1) {
+  // Only build a chain if the feature isn't disabled, the default credential
+  // exists but has no intermediates configured.
+  SSL *ssl = hs->ssl;
+  SSL_CREDENTIAL *cred = hs->config->cert->default_credential.get();
+  if ((ssl->mode & SSL_MODE_NO_AUTO_CHAIN) || !cred->IsComplete() ||
+      sk_CRYPTO_BUFFER_num(cred->chain.get()) != 1) {
     return true;
   }
 
-  UniquePtr<X509> leaf(X509_parse_from_buffer(
-      sk_CRYPTO_BUFFER_value(hs->config->cert->chain.get(), 0)));
+  UniquePtr<X509> leaf(
+      X509_parse_from_buffer(sk_CRYPTO_BUFFER_value(cred->chain.get(), 0)));
   if (!leaf) {
     OPENSSL_PUT_ERROR(SSL, ERR_R_X509_LIB);
     return false;
@@ -753,12 +719,13 @@
 static int ssl_cert_cache_leaf_cert(CERT *cert) {
   assert(cert->x509_method);
 
+  const SSL_CREDENTIAL *cred = cert->default_credential.get();
   if (cert->x509_leaf != NULL ||
-      cert->chain == NULL) {
+      cred->chain == NULL) {
     return 1;
   }
 
-  CRYPTO_BUFFER *leaf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
+  CRYPTO_BUFFER *leaf = sk_CRYPTO_BUFFER_value(cred->chain.get(), 0);
   if (!leaf) {
     return 1;
   }
@@ -795,18 +762,8 @@
   assert(cert->x509_method);
 
   UniquePtr<CRYPTO_BUFFER> buffer = x509_to_buffer(x509);
-  if (!buffer) {
-    return 0;
-  }
-
-  if (cert->chain == nullptr) {
-    cert->chain = new_leafless_chain();
-    if (cert->chain == nullptr) {
-      return 0;
-    }
-  }
-
-  if (!PushToStack(cert->chain.get(), std::move(buffer))) {
+  if (!buffer ||
+      !cert->default_credential->AppendIntermediateCert(std::move(buffer))) {
     return 0;
   }
 
@@ -909,9 +866,10 @@
 static int ssl_cert_cache_chain_certs(CERT *cert) {
   assert(cert->x509_method);
 
+  const SSL_CREDENTIAL *cred = cert->default_credential.get();
   if (cert->x509_chain != nullptr ||
-      cert->chain == nullptr ||
-      sk_CRYPTO_BUFFER_num(cert->chain.get()) < 2) {
+      cred->chain == nullptr ||
+      sk_CRYPTO_BUFFER_num(cred->chain.get()) < 2) {
     return 1;
   }
 
@@ -920,8 +878,8 @@
     return 0;
   }
 
-  for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(cert->chain.get()); i++) {
-    CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(cert->chain.get(), i);
+  for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(cred->chain.get()); i++) {
+    CRYPTO_BUFFER *buffer = sk_CRYPTO_BUFFER_value(cred->chain.get(), i);
     UniquePtr<X509> x509(X509_parse_from_buffer(buffer));
     if (!x509 ||
         !PushToStack(chain.get(), std::move(x509))) {
@@ -1194,13 +1152,10 @@
 
 static int do_client_cert_cb(SSL *ssl, void *arg) {
   // Should only be called during handshake, but check to be sure.
-  if (!ssl->config) {
-    assert(ssl->config);
-    return -1;
-  }
+  BSSL_CHECK(ssl->config);
 
-  if (ssl_has_certificate(ssl->s3->hs.get()) ||
-      ssl->ctx->client_cert_cb == NULL) {
+  if (ssl->config->cert->default_credential->IsComplete() ||
+      ssl->ctx->client_cert_cb == nullptr) {
     return 1;
   }
 
diff --git a/ssl/test/bssl_shim.cc b/ssl/test/bssl_shim.cc
index 508b192..38ab01c 100644
--- a/ssl/test/bssl_shim.cc
+++ b/ssl/test/bssl_shim.cc
@@ -448,6 +448,7 @@
 // consistent with the test configuration and invariants.
 static bool CheckHandshakeProperties(SSL *ssl, bool is_resume,
                                      const TestConfig *config) {
+  TestState *state = GetTestState(ssl);
   if (!CheckAuthProperties(ssl, is_resume, config)) {
     return false;
   }
@@ -474,9 +475,9 @@
 
   bool expect_handshake_done =
       (is_resume || !config->false_start) && !SSL_in_early_data(ssl);
-  if (expect_handshake_done != GetTestState(ssl)->handshake_done) {
+  if (expect_handshake_done != state->handshake_done) {
     fprintf(stderr, "handshake was%s completed\n",
-            GetTestState(ssl)->handshake_done ? "" : " not");
+            state->handshake_done ? "" : " not");
     return false;
   }
 
@@ -486,20 +487,20 @@
         (!SSL_session_reused(ssl) || config->expect_ticket_renewal) &&
         // Session tickets are sent post-handshake in TLS 1.3.
         GetProtocolVersion(ssl) < TLS1_3_VERSION;
-    if (expect_new_session != GetTestState(ssl)->got_new_session) {
+    if (expect_new_session != state->got_new_session) {
       fprintf(stderr,
               "new session was%s cached, but we expected the opposite\n",
-              GetTestState(ssl)->got_new_session ? "" : " not");
+              state->got_new_session ? "" : " not");
       return false;
     }
   }
 
   if (!is_resume) {
-    if (config->expect_session_id && !GetTestState(ssl)->got_new_session) {
+    if (config->expect_session_id && !state->got_new_session) {
       fprintf(stderr, "session was not cached on the server.\n");
       return false;
     }
-    if (config->expect_no_session_id && GetTestState(ssl)->got_new_session) {
+    if (config->expect_no_session_id && state->got_new_session) {
       fprintf(stderr, "session was unexpectedly cached on the server.\n");
       return false;
     }
@@ -507,8 +508,7 @@
 
   // early_callback_called is updated in the handshaker, so we don't see it
   // here.
-  if (!config->handoff && config->is_server &&
-      !GetTestState(ssl)->early_callback_called) {
+  if (!config->handoff && config->is_server && !state->early_callback_called) {
     fprintf(stderr, "early callback not called\n");
     return false;
   }
@@ -694,11 +694,9 @@
     return false;
   }
 
-  if (config->expect_delegated_credential_used !=
-      !!SSL_delegated_credential_used(ssl)) {
-    fprintf(stderr,
-            "Got %s delegated credential usage, but wanted opposite. \n",
-            SSL_delegated_credential_used(ssl) ? "" : "no");
+  if (config->expect_selected_credential != state->selected_credential) {
+    fprintf(stderr, "Credential %d was used, wanted %d\n",
+            state->selected_credential, config->expect_selected_credential);
     return false;
   }
 
@@ -747,7 +745,6 @@
 
   // Test that handshake hints correctly skipped the expected operations.
   if (config->handshake_hints && !config->allow_hint_mismatch) {
-    const TestState *state = GetTestState(ssl);
     // If the private key operation is performed in the first roundtrip, a hint
     // match should have skipped it. This is ECDHE-based cipher suites in TLS
     // 1.2 and non-HRR handshakes in TLS 1.3.
diff --git a/ssl/test/runner/common.go b/ssl/test/runner/common.go
index 71f9c86..f85a81a 100644
--- a/ssl/test/runner/common.go
+++ b/ssl/test/runner/common.go
@@ -268,6 +268,7 @@
 	NegotiatedProtocolFromALPN bool                  // protocol negotiated with ALPN
 	ServerName                 string                // server name requested by client, if any (server side only)
 	PeerCertificates           []*x509.Certificate   // certificate chain presented by remote peer
+	PeerDelegatedCredential    []byte                // delegated credential presented by remote peer
 	VerifiedChains             [][]*x509.Certificate // verified chains built from PeerCertificates
 	OCSPResponse               []byte                // stapled OCSP response from the peer, if any
 	ChannelID                  *ecdsa.PublicKey      // the channel ID for this connection
@@ -308,7 +309,8 @@
 	secret                      []byte              // Secret associated with the session
 	handshakeHash               []byte              // Handshake hash for Channel ID purposes.
 	serverCertificates          []*x509.Certificate // Certificate chain presented by the server
-	extendedMasterSecret        bool                // Whether an extended master secret was used to generate the session
+	serverDelegatedCredential   []byte
+	extendedMasterSecret        bool // Whether an extended master secret was used to generate the session
 	sctList                     []byte
 	ocspResponse                []byte
 	earlyALPN                   string
@@ -1945,14 +1947,6 @@
 	// 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
-
 	// CompatModeWithQUIC, if true, enables TLS 1.3 compatibility mode
 	// when running over QUIC.
 	CompatModeWithQUIC bool
@@ -2128,9 +2122,17 @@
 	return supportedSignatureAlgorithms
 }
 
+type CredentialType int
+
+const (
+	CredentialTypeX509 CredentialType = iota
+	CredentialTypeDelegated
+)
+
 // A Credential is a certificate chain and private key that a TLS endpoint may
 // use to authenticate.
 type Credential struct {
+	Type CredentialType
 	// Certificate is a chain of one or more certificates, leaf first.
 	Certificate [][]byte
 	PrivateKey  crypto.PrivateKey // supported types: *rsa.PrivateKey, *ecdsa.PrivateKey
@@ -2149,6 +2151,9 @@
 	// processing for TLS clients doing client authentication. If nil, the
 	// leaf certificate will be parsed as needed.
 	Leaf *x509.Certificate
+	// DelegatedCredential is the delegated credential to use
+	// with the certificate.
+	DelegatedCredential []byte
 	// ChainPath is the path to the temporary on disk copy of the certificate
 	// chain.
 	ChainPath string
@@ -2158,6 +2163,9 @@
 	// certificate chain. If the chain only contains one certificate ChainPath
 	// and RootPath will be the same.
 	RootPath string
+	// SignSignatureAlgorithms, if not nil, overrides the default set of
+	// supported signature algorithms to sign with.
+	SignSignatureAlgorithms []signatureAlgorithm
 }
 
 func (c *Credential) WithSignatureAlgorithms(sigAlgs ...signatureAlgorithm) *Credential {
diff --git a/ssl/test/runner/conn.go b/ssl/test/runner/conn.go
index a3251dc..bda9b92 100644
--- a/ssl/test/runner/conn.go
+++ b/ssl/test/runner/conn.go
@@ -30,20 +30,21 @@
 	isClient bool
 
 	// constant after handshake; protected by handshakeMutex
-	handshakeMutex       sync.Mutex // handshakeMutex < in.Mutex, out.Mutex, errMutex
-	handshakeErr         error      // error resulting from handshake
-	wireVersion          uint16     // TLS wire version
-	vers                 uint16     // TLS version
-	haveVers             bool       // version has been negotiated
-	config               *Config    // configuration passed to constructor
-	handshakeComplete    bool
-	skipEarlyData        bool // On a server, indicates that the client is sending early data that must be skipped over.
-	didResume            bool // whether this connection was a session resumption
-	extendedMasterSecret bool // whether this session used an extended master secret
-	cipherSuite          *cipherSuite
-	ocspResponse         []byte // stapled OCSP response
-	sctList              []byte // signed certificate timestamp list
-	peerCertificates     []*x509.Certificate
+	handshakeMutex          sync.Mutex // handshakeMutex < in.Mutex, out.Mutex, errMutex
+	handshakeErr            error      // error resulting from handshake
+	wireVersion             uint16     // TLS wire version
+	vers                    uint16     // TLS version
+	haveVers                bool       // version has been negotiated
+	config                  *Config    // configuration passed to constructor
+	handshakeComplete       bool
+	skipEarlyData           bool // On a server, indicates that the client is sending early data that must be skipped over.
+	didResume               bool // whether this connection was a session resumption
+	extendedMasterSecret    bool // whether this session used an extended master secret
+	cipherSuite             *cipherSuite
+	ocspResponse            []byte // stapled OCSP response
+	sctList                 []byte // signed certificate timestamp list
+	peerCertificates        []*x509.Certificate
+	peerDelegatedCredential []byte
 	// verifiedChains contains the certificate chains that we built, as
 	// opposed to the ones presented by the server.
 	verifiedChains [][]*x509.Certificate
@@ -1850,6 +1851,7 @@
 		state.NegotiatedProtocolFromALPN = c.usedALPN
 		state.CipherSuite = c.cipherSuite.id
 		state.PeerCertificates = c.peerCertificates
+		state.PeerDelegatedCredential = c.peerDelegatedCredential
 		state.VerifiedChains = c.verifiedChains
 		state.OCSPResponse = c.ocspResponse
 		state.ServerName = c.serverName
diff --git a/ssl/test/runner/handshake_client.go b/ssl/test/runner/handshake_client.go
index bba721e..eecb114 100644
--- a/ssl/test/runner/handshake_client.go
+++ b/ssl/test/runner/handshake_client.go
@@ -39,7 +39,6 @@
 	session        *ClientSessionState
 	finishedBytes  []byte
 	peerPublicKey  crypto.PublicKey
-	peerIsDC       bool
 }
 
 func mapClientHelloVersion(vers uint16, isDTLS bool) uint16 {
@@ -1300,7 +1299,7 @@
 
 		c.peerSignatureAlgorithm = certVerifyMsg.signatureAlgorithm
 		input := hs.finishedHash.certificateVerifyInput(serverCertificateVerifyContextTLS13)
-		if hs.peerIsDC {
+		if c.peerDelegatedCredential != nil {
 			err = verifyMessageDC(c.vers, hs.peerPublicKey, c.config, certVerifyMsg.signatureAlgorithm, input, certVerifyMsg.signature)
 		} else {
 			err = verifyMessage(c.vers, hs.peerPublicKey, c.config, certVerifyMsg.signatureAlgorithm, input, certVerifyMsg.signature)
@@ -1831,10 +1830,6 @@
 		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")
@@ -1894,12 +1889,8 @@
 			c.sendAlert(alertBadCertificate)
 			return errors.New("tls: failed to verify delegated credential: " + err.Error())
 		}
-		hs.peerIsDC = true
+		c.peerDelegatedCredential = dc.raw
 	} else {
-		if c.config.Bugs.ExpectDelegatedCredentials {
-			c.sendAlert(alertInternalError)
-			return errors.New("tls: delegated credentials missing")
-		}
 		hs.peerPublicKey = leafPublicKey
 	}
 
@@ -2168,6 +2159,7 @@
 		// Restore masterSecret and peerCerts from previous state
 		hs.masterSecret = hs.session.secret
 		c.peerCertificates = hs.session.serverCertificates
+		c.peerDelegatedCredential = hs.session.serverDelegatedCredential
 		c.extendedMasterSecret = hs.session.extendedMasterSecret
 		c.sctList = hs.session.sctList
 		c.ocspResponse = hs.session.ocspResponse
@@ -2220,15 +2212,16 @@
 	// Create a session with no server identifier. Either a
 	// session ID or session ticket will be attached.
 	session := &ClientSessionState{
-		vers:               c.vers,
-		wireVersion:        c.wireVersion,
-		cipherSuite:        hs.suite,
-		secret:             hs.masterSecret,
-		handshakeHash:      hs.finishedHash.Sum(),
-		serverCertificates: c.peerCertificates,
-		sctList:            c.sctList,
-		ocspResponse:       c.ocspResponse,
-		ticketExpiration:   c.config.time().Add(time.Duration(7 * 24 * time.Hour)),
+		vers:                      c.vers,
+		wireVersion:               c.wireVersion,
+		cipherSuite:               hs.suite,
+		secret:                    hs.masterSecret,
+		handshakeHash:             hs.finishedHash.Sum(),
+		serverCertificates:        c.peerCertificates,
+		serverDelegatedCredential: c.peerDelegatedCredential,
+		sctList:                   c.sctList,
+		ocspResponse:              c.ocspResponse,
+		ticketExpiration:          c.config.time().Add(time.Duration(7 * 24 * time.Hour)),
 	}
 
 	if !hs.serverHello.extensions.ticketSupported {
diff --git a/ssl/test/runner/handshake_messages.go b/ssl/test/runner/handshake_messages.go
index c3d9dd3..6d2f902 100644
--- a/ssl/test/runner/handshake_messages.go
+++ b/ssl/test/runner/handshake_messages.go
@@ -1923,6 +1923,7 @@
 
 type delegatedCredential struct {
 	// https://www.rfc-editor.org/rfc/rfc9345.html#section-4
+	raw              []byte
 	signedBytes      []byte
 	lifetimeSecs     uint32
 	dcCertVerifyAlgo signatureAlgorithm
@@ -2049,6 +2050,7 @@
 
 					dc.dcCertVerifyAlgo = signatureAlgorithm(dcCertVerifyAlgo)
 					dc.algorithm = signatureAlgorithm(algorithm)
+					dc.raw = origBody
 					dc.signedBytes = []byte(origBody)[:4+2+3+len(dc.pkixPublicKey)]
 					cert.delegatedCredential = dc
 				default:
diff --git a/ssl/test/runner/handshake_server.go b/ssl/test/runner/handshake_server.go
index d059d72..ceaf029 100644
--- a/ssl/test/runner/handshake_server.go
+++ b/ssl/test/runner/handshake_server.go
@@ -1969,10 +1969,7 @@
 			certificateTypes: config.ClientCertificateTypes,
 		}
 		if certReq.certificateTypes == nil {
-			certReq.certificateTypes = []byte{
-				byte(CertTypeRSASign),
-				byte(CertTypeECDSASign),
-			}
+			certReq.certificateTypes = []byte{CertTypeRSASign, CertTypeECDSASign}
 		}
 		if c.vers >= VersionTLS12 {
 			certReq.hasSignatureAlgorithm = true
diff --git a/ssl/test/runner/runner.go b/ssl/test/runner/runner.go
index 24e4ac7..cd7fbca 100644
--- a/ssl/test/runner/runner.go
+++ b/ssl/test/runner/runner.go
@@ -315,21 +315,20 @@
 	return k, nil
 }
 
-func createDelegatedCredential(config delegatedCredentialConfig, parentDER []byte, parentPriv crypto.PrivateKey) (dc, privPKCS8 []uint8, err error) {
+func createDelegatedCredential(parent *Credential, config delegatedCredentialConfig) *Credential {
+	if parent.Type != CredentialTypeX509 {
+		panic("delegated credentials must be issued by X.509 credentials")
+	}
+
 	dcAlgo := config.dcAlgo
-	if dcAlgo == signatureAlgorithm(0) {
+	if dcAlgo == 0 {
 		dcAlgo = signatureECDSAWithP256AndSHA256
 	}
 
-	var pub crypto.PublicKey
-
+	var dcPriv crypto.Signer
 	switch dcAlgo {
 	case signatureRSAPKCS1WithMD5, signatureRSAPKCS1WithSHA1, signatureRSAPKCS1WithSHA256, signatureRSAPKCS1WithSHA384, signatureRSAPKCS1WithSHA512, signatureRSAPSSWithSHA256, signatureRSAPSSWithSHA384, signatureRSAPSSWithSHA512:
-		pub = &rsa2048Key.PublicKey
-		privPKCS8, err = x509.MarshalPKCS8PrivateKey(&rsa2048Key)
-		if err != nil {
-			return nil, nil, err
-		}
+		dcPriv = &rsa2048Key
 
 	case signatureECDSAWithSHA1, signatureECDSAWithP256AndSHA256, signatureECDSAWithP384AndSHA384, signatureECDSAWithP521AndSHA512:
 		var curve elliptic.Curve
@@ -346,17 +345,12 @@
 
 		priv, err := ecdsa.GenerateKey(curve, rand.Reader)
 		if err != nil {
-			return nil, nil, err
+			panic(err)
 		}
-
-		if privPKCS8, err = x509.MarshalPKCS8PrivateKey(priv); err != nil {
-			return nil, nil, err
-		}
-
-		pub = &priv.PublicKey
+		dcPriv = priv
 
 	default:
-		return nil, nil, fmt.Errorf("unsupported DC signature algorithm: %x", dcAlgo)
+		panic(fmt.Errorf("unsupported DC signature algorithm: %x", dcAlgo))
 	}
 
 	lifetime := config.lifetime
@@ -364,33 +358,36 @@
 		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)
+	if lifetimeSecs < 0 || lifetimeSecs > 1<<32 {
+		panic(fmt.Errorf("lifetime %s is too long to be expressed", lifetime))
 	}
 
 	// https://www.rfc-editor.org/rfc/rfc9345.html#section-4
-	dc = append(dc, byte(lifetimeSecs>>24), byte(lifetimeSecs>>16), byte(lifetimeSecs>>8), byte(lifetimeSecs))
-	dc = append(dc, byte(dcAlgo>>8), byte(dcAlgo))
+	dc := cryptobyte.NewBuilder(nil)
+	dc.AddUint32(uint32(lifetimeSecs))
+	dc.AddUint16(uint16(dcAlgo))
 
-	pubBytes, err := x509.MarshalPKIXPublicKey(pub)
+	pubBytes, err := x509.MarshalPKIXPublicKey(dcPriv.Public())
 	if err != nil {
-		return nil, nil, err
+		panic(err)
 	}
-
-	dc = append(dc, byte(len(pubBytes)>>16), byte(len(pubBytes)>>8), byte(len(pubBytes)))
-	dc = append(dc, pubBytes...)
+	addUint24LengthPrefixedBytes(dc, pubBytes)
 
 	var dummyConfig Config
-	parentSignature, err := signMessage(VersionTLS13, parentPriv, &dummyConfig, config.algo, delegatedCredentialSignedMessage(dc, config.algo, parentDER))
+	parentSignature, err := signMessage(VersionTLS13, parent.PrivateKey, &dummyConfig, config.algo, delegatedCredentialSignedMessage(dc.BytesOrPanic(), config.algo, parent.Leaf.Raw))
 	if err != nil {
-		return nil, nil, err
+		panic(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...)
+	dc.AddUint16(uint16(config.algo))
+	addUint16LengthPrefixedBytes(dc, parentSignature)
 
-	return dc, privPKCS8, nil
+	dcCred := *parent
+	dcCred.Type = CredentialTypeDelegated
+	dcCred.DelegatedCredential = dc.BytesOrPanic()
+	dcCred.PrivateKey = dcPriv
+	dcCred.KeyPath = writeTempKeyFile(dcPriv)
+	return &dcCred
 }
 
 // recordVersionToWire maps a record-layer protocol version to its wire
@@ -487,17 +484,13 @@
 	// srtpProtectionProfile is the DTLS-SRTP profile that should be negotiated.
 	// If zero, none should be negotiated.
 	srtpProtectionProfile uint16
-	// ocspResponse, if not nil, is the OCSP response to be received.
-	ocspResponse []uint8
-	// sctList, if not nil, is the SCT list to be received.
-	sctList []uint8
 	// peerSignatureAlgorithm, if not zero, is the signature algorithm that the
 	// peer should have used in the handshake.
 	peerSignatureAlgorithm signatureAlgorithm
 	// curveID, if not zero, is the curve that the handshake should have used.
 	curveID CurveID
-	// peerCertificate, if not nil, is the certificate chain the peer is
-	// expected to send.
+	// peerCertificate, if not nil, is the credential the peer is expected to
+	// send.
 	peerCertificate *Credential
 	// quicTransportParams contains the QUIC transport parameters that are to be
 	// sent by the peer using codepoint 57.
@@ -655,15 +648,22 @@
 	// skipSplitHandshake, if true, will skip the generation of a split
 	// handshake copy of the test.
 	skipSplitHandshake bool
+	// skipHints, if true, will skip the generation of a handshake hints copy of
+	// the test.
+	skipHints bool
 	// skipVersionNameCheck, if true, will skip the consistency check between
 	// test name and the versions.
 	skipVersionNameCheck bool
-	// shimCertificate, if populated, is the credential which should be send by
-	// the shim.
+	// shimCertificate, if not nil, is the default credential which should be
+	// configured at the shim. If set, it must be an X.509 credential.
 	shimCertificate *Credential
-	// handshakerCertificate, if populated, overrides the credential that is
-	// configured on the handshaker.
+	// handshakerCertificate, if not nil, overrides the default credential which
+	// on the handshaker.
 	handshakerCertificate *Credential
+	// shimCredentials is a list of credentials which should be configured at
+	// the shim. It differs from shimCertificate only in whether the old or
+	// new APIs are used.
+	shimCredentials []*Credential
 }
 
 var testCases []testCase
@@ -910,14 +910,6 @@
 		return fmt.Errorf("SRTP profile mismatch: got %d, wanted %d", p, expectations.srtpProtectionProfile)
 	}
 
-	if expectations.ocspResponse != nil && !bytes.Equal(expectations.ocspResponse, connState.OCSPResponse) {
-		return fmt.Errorf("OCSP Response mismatch: got %x, wanted %x", connState.OCSPResponse, expectations.ocspResponse)
-	}
-
-	if expectations.sctList != nil && !bytes.Equal(expectations.sctList, connState.SCTList) {
-		return fmt.Errorf("SCT list mismatch")
-	}
-
 	if expected := expectations.peerSignatureAlgorithm; expected != 0 && expected != connState.PeerSignatureAlgorithm {
 		return fmt.Errorf("expected peer to use signature algorithm %04x, but got %04x", expected, connState.PeerSignatureAlgorithm)
 	}
@@ -926,15 +918,34 @@
 		return fmt.Errorf("expected peer to use curve %04x, but got %04x", expected, connState.CurveID)
 	}
 
-	if expectations.peerCertificate != nil {
-		if len(connState.PeerCertificates) != len(expectations.peerCertificate.Certificate) {
-			return fmt.Errorf("expected peer to send %d certificates, but got %d", len(connState.PeerCertificates), len(expectations.peerCertificate.Certificate))
+	if expected := expectations.peerCertificate; expected != nil {
+		if len(connState.PeerCertificates) != len(expected.Certificate) {
+			return fmt.Errorf("expected peer to send %d certificates, but got %d", len(connState.PeerCertificates), len(expected.Certificate))
 		}
 		for i, cert := range connState.PeerCertificates {
-			if !bytes.Equal(cert.Raw, expectations.peerCertificate.Certificate[i]) {
+			if !bytes.Equal(cert.Raw, expected.Certificate[i]) {
 				return fmt.Errorf("peer certificate %d did not match", i+1)
 			}
 		}
+
+		if !bytes.Equal(connState.OCSPResponse, expected.OCSPStaple) {
+			return fmt.Errorf("peer OCSP response did not match")
+		}
+
+		if !bytes.Equal(connState.SCTList, expected.SignedCertificateTimestampList) {
+			return fmt.Errorf("peer SCT list did not match")
+		}
+
+		if expected.Type == CredentialTypeDelegated {
+			if connState.PeerDelegatedCredential == nil {
+				return fmt.Errorf("peer unexpectedly did not use delegated credentials")
+			}
+			if !bytes.Equal(expected.DelegatedCredential, connState.PeerDelegatedCredential) {
+				return fmt.Errorf("peer delegated credential did not match")
+			}
+		} else if connState.PeerDelegatedCredential != nil {
+			return fmt.Errorf("peer unexpectedly used delegated credentials")
+		}
 	}
 
 	if len(expectations.quicTransportParams) > 0 {
@@ -1388,7 +1399,20 @@
 // -shim-writes-first flag is used.
 const shimInitialWrite = "hello"
 
-func appendCredentialFlags(flags []string, cred *Credential, prefix string) []string {
+func appendCredentialFlags(flags []string, cred *Credential, prefix string, newCredential bool) []string {
+	if newCredential {
+		switch cred.Type {
+		case CredentialTypeX509:
+			flags = append(flags, prefix+"-new-x509-credential")
+		case CredentialTypeDelegated:
+			flags = append(flags, prefix+"-new-delegated-credential")
+		default:
+			panic(fmt.Errorf("unknown credential type %d", cred.Type))
+		}
+	} else if cred.Type != CredentialTypeX509 {
+		panic("default credential must be X.509")
+	}
+
 	if len(cred.ChainPath) != 0 {
 		flags = append(flags, prefix+"-cert-file", cred.ChainPath)
 	}
@@ -1404,6 +1428,9 @@
 	for _, sigAlg := range cred.SignatureAlgorithms {
 		flags = append(flags, prefix+"-signing-prefs", strconv.Itoa(int(sigAlg)))
 	}
+	if len(cred.DelegatedCredential) != 0 {
+		flags = append(flags, prefix+"-delegated-credential", base64FlagValue(cred.DelegatedCredential))
+	}
 	return flags
 }
 
@@ -1424,8 +1451,9 @@
 		flags = append(flags, "-server")
 	}
 
+	// Configure the default credential.
 	shimCertificate := test.shimCertificate
-	if shimCertificate == nil && test.testType == serverTest {
+	if shimCertificate == nil && len(test.shimCredentials) == 0 && test.testType == serverTest {
 		shimCertificate = &rsaCertificate
 	}
 	if shimCertificate != nil {
@@ -1433,11 +1461,15 @@
 		if test.handshakerCertificate != nil {
 			shimPrefix = "-on-shim"
 		}
-		flags = appendCredentialFlags(flags, shimCertificate, shimPrefix)
+		flags = appendCredentialFlags(flags, shimCertificate, shimPrefix, false)
+	}
+	if test.handshakerCertificate != nil {
+		flags = appendCredentialFlags(flags, test.handshakerCertificate, "-on-handshaker", false)
 	}
 
-	if test.handshakerCertificate != nil {
-		flags = appendCredentialFlags(flags, test.handshakerCertificate, "-on-handshaker")
+	// Configure any additional credentials.
+	for _, cred := range test.shimCredentials {
+		flags = appendCredentialFlags(flags, cred, "", true)
 	}
 
 	if test.protocol == dtls {
@@ -1745,6 +1777,10 @@
 	versionWire uint16
 }
 
+func (vers tlsVersion) String() string {
+	return vers.name
+}
+
 func (vers tlsVersion) shimFlag(protocol protocol) string {
 	// The shim uses the protocol version in its public API, but uses the
 	// DTLS-specific version if it exists.
@@ -1903,7 +1939,7 @@
 	for _, test := range tests {
 		if test.protocol != tls ||
 			test.testType != serverTest ||
-			strings.Contains(test.name, "DelegatedCredentials") ||
+			len(test.shimCredentials) != 0 ||
 			strings.Contains(test.name, "ECH-Server") ||
 			test.skipSplitHandshake {
 			continue
@@ -1926,7 +1962,8 @@
 
 	for _, test := range tests {
 		if test.protocol == dtls ||
-			test.testType != serverTest {
+			test.testType != serverTest ||
+			test.skipHints {
 			continue
 		}
 
@@ -5238,7 +5275,7 @@
 				MaxVersion: vers.version,
 			},
 			expectations: connectionExpectations{
-				ocspResponse: testOCSPResponse,
+				peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
 			},
 			shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
 			resumeSession:   true,
@@ -5309,7 +5346,7 @@
 			},
 			shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
 			expectations: connectionExpectations{
-				ocspResponse: testOCSPResponse,
+				peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
 			},
 			flags: []string{
 				"-use-ocsp-callback",
@@ -5329,7 +5366,8 @@
 			},
 			shimCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
 			expectations: connectionExpectations{
-				ocspResponse: []byte{},
+				// There should be no OCSP response from the peer.
+				peerCertificate: &rsaCertificate,
 			},
 			flags: []string{
 				"-use-ocsp-callback",
@@ -8419,7 +8457,7 @@
 				},
 				shimCertificate: rsaCertificate.WithSCTList(testSCTList),
 				expectations: connectionExpectations{
-					sctList: testSCTList,
+					peerCertificate: rsaCertificate.WithSCTList(testSCTList),
 				},
 				resumeSession: true,
 			})
@@ -16491,13 +16529,10 @@
 }
 
 func addDelegatedCredentialTests() {
-	ecdsaDC, ecdsaPKCS8, err := createDelegatedCredential(delegatedCredentialConfig{
-		algo: signatureRSAPSSWithSHA256,
-	}, rsaCertificate.Leaf.Raw, rsaCertificate.PrivateKey)
-	if err != nil {
-		panic(err)
-	}
-	ecdsaFlagValue := fmt.Sprintf("%x,%x", ecdsaDC, ecdsaPKCS8)
+	p256DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
+		dcAlgo: signatureECDSAWithP256AndSHA256,
+		algo:   signatureRSAPSSWithSHA256,
+	})
 
 	testCases = append(testCases, testCase{
 		testType: serverTest,
@@ -16506,8 +16541,10 @@
 			MinVersion: VersionTLS13,
 			MaxVersion: VersionTLS13,
 		},
-		flags: []string{
-			"-delegated-credential", ecdsaFlagValue,
+		shimCredentials: []*Credential{p256DC, &rsaCertificate},
+		flags:           []string{"-expect-selected-credential", "1"},
+		expectations: connectionExpectations{
+			peerCertificate: &rsaCertificate,
 		},
 	})
 
@@ -16518,13 +16555,11 @@
 			MinVersion:                    VersionTLS13,
 			MaxVersion:                    VersionTLS13,
 			DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
-			Bugs: ProtocolBugs{
-				ExpectDelegatedCredentials: true,
-			},
 		},
-		flags: []string{
-			"-delegated-credential", ecdsaFlagValue,
-			"-expect-delegated-credential-used",
+		shimCredentials: []*Credential{p256DC, &rsaCertificate},
+		flags:           []string{"-expect-selected-credential", "0"},
+		expectations: connectionExpectations{
+			peerCertificate: p256DC,
 		},
 	})
 
@@ -16541,13 +16576,11 @@
 			// against the signature made by the delegated credential.
 			VerifySignatureAlgorithms:     []signatureAlgorithm{signatureRSAPSSWithSHA256},
 			DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
-			Bugs: ProtocolBugs{
-				ExpectDelegatedCredentials: true,
-			},
 		},
-		flags: []string{
-			"-delegated-credential", ecdsaFlagValue,
-			"-expect-delegated-credential-used",
+		shimCredentials: []*Credential{p256DC, &rsaCertificate},
+		flags:           []string{"-expect-selected-credential", "0"},
+		expectations: connectionExpectations{
+			peerCertificate: p256DC,
 		},
 	})
 
@@ -16561,12 +16594,11 @@
 			// the server should not use delegated credentials.
 			VerifySignatureAlgorithms:     []signatureAlgorithm{signatureRSAPSSWithSHA384},
 			DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
-			Bugs: ProtocolBugs{
-				FailIfDelegatedCredentials: true,
-			},
 		},
-		flags: []string{
-			"-delegated-credential", ecdsaFlagValue,
+		shimCredentials: []*Credential{p256DC, &rsaCertificate},
+		flags:           []string{"-expect-selected-credential", "1"},
+		expectations: connectionExpectations{
+			peerCertificate: &rsaCertificate,
 		},
 	})
 
@@ -16581,50 +16613,78 @@
 			// credentials.
 			VerifySignatureAlgorithms:     []signatureAlgorithm{signatureRSAPSSWithSHA256},
 			DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
-			Bugs: ProtocolBugs{
-				FailIfDelegatedCredentials: true,
-			},
 		},
-		flags: []string{
-			"-delegated-credential", ecdsaFlagValue,
+		shimCredentials: []*Credential{p256DC, &rsaCertificate},
+		flags:           []string{"-expect-selected-credential", "1"},
+		expectations: connectionExpectations{
+			peerCertificate: &rsaCertificate,
+		},
+	})
+
+	// Delegated credentials are not supported at TLS 1.2, even if the client
+	// sends the extension.
+	testCases = append(testCases, testCase{
+		testType: serverTest,
+		name:     "DelegatedCredentials-TLS12-Forbidden",
+		config: Config{
+			MinVersion:                    VersionTLS12,
+			MaxVersion:                    VersionTLS12,
+			DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+		},
+		shimCredentials: []*Credential{p256DC, &rsaCertificate},
+		flags:           []string{"-expect-selected-credential", "1"},
+		expectations: connectionExpectations{
+			peerCertificate: &rsaCertificate,
 		},
 	})
 
 	// Generate another delegated credential, so we can get the keys out of sync.
-	_, ecdsaPKCS8Wrong, err := createDelegatedCredential(delegatedCredentialConfig{
+	dcWrongKey := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
 		algo: signatureRSAPSSWithSHA256,
-	}, rsaCertificate.Leaf.Raw, rsaCertificate.PrivateKey)
-	if err != nil {
-		panic(err)
-	}
-	mismatchFlagValue := fmt.Sprintf("%x,%x", ecdsaDC, ecdsaPKCS8Wrong)
+	})
+	dcWrongKey.DelegatedCredential = p256DC.DelegatedCredential
 	testCases = append(testCases, testCase{
 		testType: serverTest,
 		name:     "DelegatedCredentials-KeyMismatch",
-		flags: []string{
-			"-delegated-credential", mismatchFlagValue,
-		},
-		shouldFail:    true,
-		expectedError: ":KEY_VALUES_MISMATCH:",
+		// The handshake hints version of the test will, as a side effect, use a
+		// custom private key. Custom private keys can't be checked for key
+		// mismatches.
+		skipHints:       true,
+		shimCredentials: []*Credential{dcWrongKey},
+		shouldFail:      true,
+		expectedError:   ":KEY_VALUES_MISMATCH:",
 	})
 
 	// RSA delegated credentials should be rejected at configuration time.
-	rsaDC, rsaPKCS8, err := createDelegatedCredential(delegatedCredentialConfig{
+	rsaDC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
 		algo:   signatureRSAPSSWithSHA256,
 		dcAlgo: signatureRSAPSSWithSHA256,
-	}, rsaCertificate.Leaf.Raw, rsaCertificate.PrivateKey)
-	if err != nil {
-		panic(err)
-	}
-	rsaFlagValue := fmt.Sprintf("%x,%x", rsaDC, rsaPKCS8)
+	})
+	testCases = append(testCases, testCase{
+		testType:        serverTest,
+		name:            "DelegatedCredentials-NoRSA",
+		shimCredentials: []*Credential{rsaDC},
+		shouldFail:      true,
+		expectedError:   ":INVALID_SIGNATURE_ALGORITHM:",
+	})
+
+	// If configured with multiple delegated credentials, the server can cleanly
+	// select the first one that works.
+	p384DC := createDelegatedCredential(&rsaCertificate, delegatedCredentialConfig{
+		dcAlgo: signatureECDSAWithP384AndSHA384,
+		algo:   signatureRSAPSSWithSHA256,
+	})
 	testCases = append(testCases, testCase{
 		testType: serverTest,
-		name:     "DelegatedCredentials-NoRSA",
-		flags: []string{
-			"-delegated-credential", rsaFlagValue,
+		name:     "DelegatedCredentials-Multiple",
+		config: Config{
+			DelegatedCredentialAlgorithms: []signatureAlgorithm{signatureECDSAWithP384AndSHA384},
 		},
-		shouldFail:    true,
-		expectedError: ":INVALID_SIGNATURE_ALGORITHM:",
+		shimCredentials: []*Credential{p256DC, p384DC},
+		flags:           []string{"-expect-selected-credential", "1"},
+		expectations: connectionExpectations{
+			peerCertificate: p384DC,
+		},
 	})
 }
 
@@ -19296,7 +19356,7 @@
 			},
 			expectations: connectionExpectations{
 				// The shim's configuration should take precendence.
-				ocspResponse: testOCSPResponse,
+				peerCertificate: rsaCertificate.WithOCSP(testOCSPResponse),
 			},
 		})
 
@@ -19590,6 +19650,571 @@
 	}
 }
 
+func addCertificateSelectionTests() {
+	// Combinatorially test each selection criteria at different versions,
+	// protocols, and with the matching certificate before and after the
+	// mismatching one.
+	type certSelectTest struct {
+		name          string
+		testType      testType
+		minVersion    uint16
+		maxVersion    uint16
+		config        Config
+		match         *Credential
+		mismatch      *Credential
+		flags         []string
+		expectedError string
+	}
+	certSelectTests := []certSelectTest{
+		// TLS 1.0 through TLS 1.2 servers should incorporate TLS cipher suites
+		// into certificate selection.
+		{
+			name:       "Server-CipherSuite-ECDHE_ECDSA",
+			testType:   serverTest,
+			maxVersion: VersionTLS12,
+			config: Config{
+				CipherSuites: []uint16{
+					TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+				},
+			},
+			match:         &ecdsaP256Certificate,
+			mismatch:      &rsaCertificate,
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+		{
+			name:       "Server-CipherSuite-ECDHE_RSA",
+			testType:   serverTest,
+			maxVersion: VersionTLS12,
+			config: Config{
+				CipherSuites: []uint16{
+					TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+				},
+			},
+			match:         &rsaCertificate,
+			mismatch:      &ecdsaP256Certificate,
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+		{
+			name:       "Server-CipherSuite-RSA",
+			testType:   serverTest,
+			maxVersion: VersionTLS12,
+			config: Config{
+				CipherSuites: []uint16{
+					TLS_RSA_WITH_AES_128_CBC_SHA,
+				},
+			},
+			match:         &rsaCertificate,
+			mismatch:      &ecdsaP256Certificate,
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+
+		// Ed25519 counts as ECDSA for purposes of cipher suite matching.
+		{
+			name:       "Server-CipherSuite-ECDHE_ECDSA-Ed25519",
+			testType:   serverTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				CipherSuites: []uint16{
+					TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+				},
+			},
+			match:         &ed25519Certificate,
+			mismatch:      &rsaCertificate,
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+		{
+			name:       "Server-CipherSuite-ECDHE_RSA-Ed25519",
+			testType:   serverTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				CipherSuites: []uint16{
+					TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+				},
+			},
+			match:         &rsaCertificate,
+			mismatch:      &ed25519Certificate,
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+
+		// If there is no ECDHE curve match, ECDHE cipher suites are
+		// disqualified in TLS 1.2 and below. This, in turn, impacts the
+		// available cipher suites for each credential.
+		{
+			name:       "Server-CipherSuite-NoECDHE",
+			testType:   serverTest,
+			maxVersion: VersionTLS12,
+			config: Config{
+				CurvePreferences: []CurveID{CurveP256},
+			},
+			flags:         []string{"-curves", strconv.Itoa(int(CurveX25519))},
+			match:         &rsaCertificate,
+			mismatch:      &ecdsaP256Certificate,
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+
+		// If the client offered a cipher that would allow a certificate, but it
+		// wasn't one of the ones we configured, the certificate should be
+		// skipped in favor of another one.
+		{
+			name:       "Server-CipherSuite-Prefs",
+			testType:   serverTest,
+			maxVersion: VersionTLS12,
+			config: Config{
+				CipherSuites: []uint16{
+					TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+					TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+				},
+			},
+			flags:         []string{"-cipher", "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA:TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"},
+			match:         &rsaCertificate,
+			mismatch:      &ecdsaP256Certificate,
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+
+		// TLS 1.0 through 1.2 servers should incorporate the curve list into
+		// ECDSA certificate selection.
+		{
+			name:       "Server-Curve",
+			testType:   serverTest,
+			maxVersion: VersionTLS12,
+			config: Config{
+				CurvePreferences: []CurveID{CurveP256},
+			},
+			match:         &ecdsaP256Certificate,
+			mismatch:      &ecdsaP384Certificate,
+			expectedError: ":WRONG_CURVE:",
+		},
+
+		// TLS 1.3 servers ignore the curve list. ECDSA certificate selection is
+		// solely determined by the signature algorithm list.
+		{
+			name:       "Server-IgnoreCurve",
+			testType:   serverTest,
+			minVersion: VersionTLS13,
+			config: Config{
+				CurvePreferences: []CurveID{CurveP256},
+			},
+			match: &ecdsaP384Certificate,
+		},
+
+		// TLS 1.2 servers also ignore the curve list for Ed25519. The signature
+		// algorithm list is sufficient for Ed25519.
+		{
+			name:       "Server-IgnoreCurveEd25519",
+			testType:   serverTest,
+			minVersion: VersionTLS12,
+			config: Config{
+				CurvePreferences: []CurveID{CurveP256},
+			},
+			match: &ed25519Certificate,
+		},
+
+		// Without signature algorithm negotiation, Ed25519 is not usable in TLS
+		// 1.1 and below.
+		{
+			name:       "Server-NoEd25519",
+			testType:   serverTest,
+			maxVersion: VersionTLS11,
+			match:      &rsaCertificate,
+			mismatch:   &ed25519Certificate,
+		},
+
+		// TLS 1.2 and up should incorporate the signature algorithm list into
+		// certificate selection.
+		{
+			name:       "Server-SignatureAlgorithm",
+			testType:   serverTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+				CipherSuites: []uint16{
+					TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
+					TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+				},
+			},
+			match:         &ecdsaP256Certificate,
+			mismatch:      &rsaCertificate,
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+		{
+			name:       "Server-SignatureAlgorithm",
+			testType:   serverTest,
+			minVersion: VersionTLS13,
+			config: Config{
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+			},
+			match:         &ecdsaP256Certificate,
+			mismatch:      &rsaCertificate,
+			expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+		},
+
+		// TLS 1.2's use of the signature algorithm list only disables the
+		// signing-based algorithms. If an RSA key exchange cipher suite is
+		// eligible, that is fine. (This is not a realistic configuration,
+		// however. No one would configure RSA before ECDSA.)
+		{
+			name:       "Server-SignatureAlgorithmImpactsECDHEOnly",
+			testType:   serverTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+				CipherSuites: []uint16{
+					TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+					TLS_RSA_WITH_AES_128_CBC_SHA,
+				},
+			},
+			match: &rsaCertificate,
+		},
+
+		// TLS 1.3's use of the signature algorithm looks at the ECDSA curve
+		// embedded in the signature algorithm.
+		{
+			name:       "Server-SignatureAlgorithmECDSACurve",
+			testType:   serverTest,
+			minVersion: VersionTLS13,
+			config: Config{
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+			},
+			match:         &ecdsaP256Certificate,
+			mismatch:      &ecdsaP384Certificate,
+			expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+		},
+
+		// TLS 1.2's use does not.
+		{
+			name:       "Server-SignatureAlgorithmECDSACurve",
+			testType:   serverTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+			},
+			match: &ecdsaP384Certificate,
+		},
+
+		// TLS 1.0 and 1.1 do not look at the signature algorithm.
+		{
+			name:       "Server-IgnoreSignatureAlgorithm",
+			testType:   serverTest,
+			maxVersion: VersionTLS11,
+			config: Config{
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+			},
+			match: &rsaCertificate,
+		},
+
+		// Signature algorithm matches take preferences on the keys into
+		// consideration.
+		{
+			name:       "Server-SignatureAlgorithmKeyPrefs",
+			testType:   serverTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+				CipherSuites:              []uint16{TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
+			},
+			match:         rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+			mismatch:      rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
+			expectedError: ":NO_SHARED_CIPHER:",
+		},
+		{
+			name:       "Server-SignatureAlgorithmKeyPrefs",
+			testType:   serverTest,
+			minVersion: VersionTLS13,
+			config: Config{
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+			},
+			match:         rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+			mismatch:      rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
+			expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+		},
+
+		// TLS 1.2 clients and below check the certificate against the old
+		// client certificate types field.
+		{
+			name:       "Client-ClientCertificateTypes-RSA",
+			testType:   clientTest,
+			maxVersion: VersionTLS12,
+			config: Config{
+				ClientAuth:             RequestClientCert,
+				ClientCertificateTypes: []uint8{CertTypeRSASign},
+			},
+			match:         &rsaCertificate,
+			mismatch:      &ecdsaP256Certificate,
+			expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+		},
+		{
+			name:       "Client-ClientCertificateTypes-ECDSA",
+			testType:   clientTest,
+			maxVersion: VersionTLS12,
+			config: Config{
+				ClientAuth:             RequestClientCert,
+				ClientCertificateTypes: []uint8{CertTypeECDSASign},
+			},
+			match:         &ecdsaP256Certificate,
+			mismatch:      &rsaCertificate,
+			expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+		},
+
+		// Ed25519 is considered ECDSA for purposes of client certificate types.
+		{
+			name:       "Client-ClientCertificateTypes-RSA-Ed25519",
+			testType:   clientTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				ClientAuth:             RequestClientCert,
+				ClientCertificateTypes: []uint8{CertTypeRSASign},
+			},
+			match:         &rsaCertificate,
+			mismatch:      &ed25519Certificate,
+			expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+		},
+		{
+			name:       "Client-ClientCertificateTypes-ECDSA-Ed25519",
+			testType:   clientTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				ClientAuth:             RequestClientCert,
+				ClientCertificateTypes: []uint8{CertTypeECDSASign},
+			},
+			match:         &ed25519Certificate,
+			mismatch:      &rsaCertificate,
+			expectedError: ":UNKNOWN_CERTIFICATE_TYPE:",
+		},
+
+		// TLS 1.2 and up should incorporate the signature algorithm list into
+		// certificate selection. (There is no signature algorithm list to look
+		// at in TLS 1.0 and 1.1.)
+		{
+			name:       "Client-SignatureAlgorithm",
+			testType:   clientTest,
+			minVersion: VersionTLS12,
+			config: Config{
+				ClientAuth:                RequestClientCert,
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+			},
+			match:         &ecdsaP256Certificate,
+			mismatch:      &rsaCertificate,
+			expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+		},
+
+		// TLS 1.3's use of the signature algorithm looks at the ECDSA curve
+		// embedded in the signature algorithm.
+		{
+			name:       "Client-SignatureAlgorithmECDSACurve",
+			testType:   clientTest,
+			minVersion: VersionTLS13,
+			config: Config{
+				ClientAuth:                RequestClientCert,
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+			},
+			match:         &ecdsaP256Certificate,
+			mismatch:      &ecdsaP384Certificate,
+			expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+		},
+
+		// TLS 1.2's use does not. It is not possible to determine what ECDSA
+		// curves are allowed by the server.
+		{
+			name:       "Client-SignatureAlgorithmECDSACurve",
+			testType:   clientTest,
+			minVersion: VersionTLS12,
+			maxVersion: VersionTLS12,
+			config: Config{
+				ClientAuth:                RequestClientCert,
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureECDSAWithP256AndSHA256},
+			},
+			match: &ecdsaP384Certificate,
+		},
+
+		// Signature algorithm matches take preferences on the keys into
+		// consideration.
+		{
+			name:       "Client-SignatureAlgorithmKeyPrefs",
+			testType:   clientTest,
+			minVersion: VersionTLS12,
+			config: Config{
+				ClientAuth:                RequestClientCert,
+				VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+			},
+			match:         rsaChainCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA256),
+			mismatch:      rsaCertificate.WithSignatureAlgorithms(signatureRSAPSSWithSHA384),
+			expectedError: ":NO_COMMON_SIGNATURE_ALGORITHMS:",
+		},
+	}
+
+	for _, protocol := range []protocol{tls, dtls} {
+		for _, vers := range allVersions(protocol) {
+			suffix := fmt.Sprintf("%s-%s", protocol, vers)
+
+			// Test that the credential list is interpreted in preference order,
+			// with the default credential, if any, at the end.
+			testCases = append(testCases, testCase{
+				name:     fmt.Sprintf("CertificateSelection-Client-PreferenceOrder-%s", suffix),
+				testType: clientTest,
+				protocol: protocol,
+				config: Config{
+					MinVersion: vers.version,
+					MaxVersion: vers.version,
+					ClientAuth: RequestClientCert,
+				},
+				shimCredentials: []*Credential{&ecdsaP256Certificate, &ecdsaP384Certificate},
+				shimCertificate: &rsaCertificate,
+				flags:           []string{"-expect-selected-credential", "0"},
+				expectations:    connectionExpectations{peerCertificate: &ecdsaP256Certificate},
+			})
+			testCases = append(testCases, testCase{
+				name:     fmt.Sprintf("CertificateSelection-Server-PreferenceOrder-%s", suffix),
+				testType: serverTest,
+				protocol: protocol,
+				config: Config{
+					MinVersion: vers.version,
+					MaxVersion: vers.version,
+				},
+				shimCredentials: []*Credential{&ecdsaP256Certificate, &ecdsaP384Certificate},
+				shimCertificate: &rsaCertificate,
+				flags:           []string{"-expect-selected-credential", "0"},
+				expectations:    connectionExpectations{peerCertificate: &ecdsaP256Certificate},
+			})
+
+			// Test that the selected credential contributes the certificate chain, OCSP response,
+			// and SCT list.
+			testCases = append(testCases, testCase{
+				name:     fmt.Sprintf("CertificateSelection-Server-OCSP-SCT-%s", suffix),
+				testType: serverTest,
+				protocol: protocol,
+				config: Config{
+					MinVersion: vers.version,
+					MaxVersion: vers.version,
+					// Configure enough options so that, at all TLS versions, only an RSA
+					// certificate will be accepted.
+					CipherSuites: []uint16{
+						TLS_AES_128_GCM_SHA256,
+						TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
+						TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
+					},
+					VerifySignatureAlgorithms: []signatureAlgorithm{signatureRSAPSSWithSHA256},
+				},
+				shimCredentials: []*Credential{
+					ecdsaP256Certificate.WithOCSP(testOCSPResponse2).WithSCTList(testSCTList2),
+					rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+				},
+				shimCertificate: ecdsaP384Certificate.WithOCSP(testOCSPResponse2).WithSCTList(testSCTList2),
+				flags:           []string{"-expect-selected-credential", "1"},
+				expectations: connectionExpectations{
+					peerCertificate: rsaChainCertificate.WithOCSP(testOCSPResponse).WithSCTList(testSCTList),
+				},
+			})
+
+			// Test that the credentials API works asynchronously. This tests both deferring the
+			// configuration to the certificate callback, and using a custom, async private key.
+			testCases = append(testCases, testCase{
+				name:     fmt.Sprintf("CertificateSelection-Client-Async-%s", suffix),
+				testType: clientTest,
+				protocol: protocol,
+				config: Config{
+					MinVersion: vers.version,
+					MaxVersion: vers.version,
+					ClientAuth: RequestClientCert,
+				},
+				shimCredentials: []*Credential{&ecdsaP256Certificate},
+				shimCertificate: &rsaCertificate,
+				flags:           []string{"-async", "-expect-selected-credential", "0"},
+				expectations:    connectionExpectations{peerCertificate: &ecdsaP256Certificate},
+			})
+			testCases = append(testCases, testCase{
+				name:     fmt.Sprintf("CertificateSelection-Server-Async-%s", suffix),
+				testType: serverTest,
+				protocol: protocol,
+				config: Config{
+					MinVersion: vers.version,
+					MaxVersion: vers.version,
+				},
+				shimCredentials: []*Credential{&ecdsaP256Certificate},
+				shimCertificate: &rsaCertificate,
+				flags:           []string{"-async", "-expect-selected-credential", "0"},
+				expectations:    connectionExpectations{peerCertificate: &ecdsaP256Certificate},
+			})
+
+			for _, test := range certSelectTests {
+				if test.minVersion != 0 && vers.version < test.minVersion {
+					continue
+				}
+				if test.maxVersion != 0 && vers.version > test.maxVersion {
+					continue
+				}
+
+				config := test.config
+				config.MinVersion = vers.version
+				config.MaxVersion = vers.version
+
+				// If the mismatch field is omitted, this is a positive test,
+				// just to confirm that the selection logic does not block a
+				// particular certificate.
+				if test.mismatch == nil {
+					testCases = append(testCases, testCase{
+						name:            fmt.Sprintf("CertificateSelection-%s-%s", test.name, suffix),
+						protocol:        protocol,
+						testType:        test.testType,
+						config:          config,
+						shimCredentials: []*Credential{test.match},
+						flags:           append([]string{"-expect-selected-credential", "0"}, test.flags...),
+						expectations:    connectionExpectations{peerCertificate: test.match},
+					})
+					continue
+				}
+
+				testCases = append(testCases, testCase{
+					name:            fmt.Sprintf("CertificateSelection-%s-MatchFirst-%s", test.name, suffix),
+					protocol:        protocol,
+					testType:        test.testType,
+					config:          config,
+					shimCredentials: []*Credential{test.match, test.mismatch},
+					flags:           append([]string{"-expect-selected-credential", "0"}, test.flags...),
+					expectations:    connectionExpectations{peerCertificate: test.match},
+				})
+				testCases = append(testCases, testCase{
+					name:            fmt.Sprintf("CertificateSelection-%s-MatchSecond-%s", test.name, suffix),
+					protocol:        protocol,
+					testType:        test.testType,
+					config:          config,
+					shimCredentials: []*Credential{test.mismatch, test.match},
+					flags:           append([]string{"-expect-selected-credential", "1"}, test.flags...),
+					expectations:    connectionExpectations{peerCertificate: test.match},
+				})
+				testCases = append(testCases, testCase{
+					name:            fmt.Sprintf("CertificateSelection-%s-MatchDefault-%s", test.name, suffix),
+					protocol:        protocol,
+					testType:        test.testType,
+					config:          config,
+					shimCredentials: []*Credential{test.mismatch},
+					shimCertificate: test.match,
+					flags:           append([]string{"-expect-selected-credential", "-1"}, test.flags...),
+					expectations:    connectionExpectations{peerCertificate: test.match},
+				})
+				testCases = append(testCases, testCase{
+					name:               fmt.Sprintf("CertificateSelection-%s-MatchNone-%s", test.name, suffix),
+					protocol:           protocol,
+					testType:           test.testType,
+					config:             config,
+					shimCredentials:    []*Credential{test.mismatch, test.mismatch, test.mismatch},
+					flags:              test.flags,
+					shouldFail:         true,
+					expectedLocalError: "remote error: handshake failure",
+					expectedError:      test.expectedError,
+				})
+			}
+		}
+	}
+}
+
 func worker(dispatcher *shimDispatcher, statusChan chan statusMsg, c chan *testCase, shimPath string, wg *sync.WaitGroup) {
 	defer wg.Done()
 
@@ -19839,6 +20464,7 @@
 	addEncryptedClientHelloTests()
 	addHintMismatchTests()
 	addCompliancePolicyTests()
+	addCertificateSelectionTests()
 
 	toAppend, err := convertToSplitHandshakeTests(testCases)
 	if err != nil {
diff --git a/ssl/test/test_config.cc b/ssl/test/test_config.cc
index c0b435e..c48d81e 100644
--- a/ssl/test/test_config.cc
+++ b/ssl/test/test_config.cc
@@ -43,6 +43,7 @@
 
 namespace {
 
+template <typename Config>
 struct Flag {
   const char *name;
   bool has_param;
@@ -51,16 +52,17 @@
   // that only impact connecting to the runner.
   bool skip_handshaker;
   // If |has_param| is false, |param| will be nullptr.
-  std::function<bool(TestConfig *config, const char *param)> set_param;
+  std::function<bool(Config *config, const char *param)> set_param;
 };
 
-Flag BoolFlag(const char *name, bool TestConfig::*field,
-              bool skip_handshaker = false) {
-  return Flag{name, false, skip_handshaker,
-              [=](TestConfig *config, const char *) -> bool {
-                config->*field = true;
-                return true;
-              }};
+template <typename Config>
+Flag<Config> BoolFlag(const char *name, bool Config::*field,
+                      bool skip_handshaker = false) {
+  return Flag<Config>{name, false, skip_handshaker,
+                      [=](Config *config, const char *) -> bool {
+                        config->*field = true;
+                        return true;
+                      }};
 }
 
 template <typename T>
@@ -100,48 +102,50 @@
   return errno != ERANGE && *end == '\0';
 }
 
-template <typename T>
-Flag IntFlag(const char *name, T TestConfig::*field,
-             bool skip_handshaker = false) {
-  return Flag{name, true, skip_handshaker,
-              [=](TestConfig *config, const char *param) -> bool {
-                return StringToInt(&(config->*field), param);
-              }};
+template <typename Config, typename T>
+Flag<Config> IntFlag(const char *name, T Config::*field,
+                     bool skip_handshaker = false) {
+  return Flag<Config>{name, true, skip_handshaker,
+                      [=](Config *config, const char *param) -> bool {
+                        return StringToInt(&(config->*field), param);
+                      }};
 }
 
-template <typename T>
-Flag IntVectorFlag(const char *name, std::vector<T> TestConfig::*field,
-                   bool skip_handshaker = false) {
-  return Flag{name, true, skip_handshaker,
-              [=](TestConfig *config, const char *param) -> bool {
-                T value;
-                if (!StringToInt(&value, param)) {
-                  return false;
-                }
-                (config->*field).push_back(value);
-                return true;
-              }};
+template <typename Config, typename T>
+Flag<Config> IntVectorFlag(const char *name, std::vector<T> Config::*field,
+                           bool skip_handshaker = false) {
+  return Flag<Config>{name, true, skip_handshaker,
+                      [=](Config *config, const char *param) -> bool {
+                        T value;
+                        if (!StringToInt(&value, param)) {
+                          return false;
+                        }
+                        (config->*field).push_back(value);
+                        return true;
+                      }};
 }
 
-Flag StringFlag(const char *name, std::string TestConfig::*field,
-                bool skip_handshaker = false) {
-  return Flag{name, true, skip_handshaker,
-              [=](TestConfig *config, const char *param) -> bool {
-                config->*field = param;
-                return true;
-              }};
+template <typename Config>
+Flag<Config> StringFlag(const char *name, std::string Config::*field,
+                        bool skip_handshaker = false) {
+  return Flag<Config>{name, true, skip_handshaker,
+                      [=](Config *config, const char *param) -> bool {
+                        config->*field = param;
+                        return true;
+                      }};
 }
 
 // TODO(davidben): When we can depend on C++17 or Abseil, switch this to
 // std::optional or absl::optional.
-Flag OptionalStringFlag(const char *name,
-                        std::unique_ptr<std::string> TestConfig::*field,
-                        bool skip_handshaker = false) {
-  return Flag{name, true, skip_handshaker,
-              [=](TestConfig *config, const char *param) -> bool {
-                (config->*field) = std::make_unique<std::string>(param);
-                return true;
-              }};
+template <typename Config>
+Flag<Config> OptionalStringFlag(const char *name,
+                                std::unique_ptr<std::string> Config::*field,
+                                bool skip_handshaker = false) {
+  return Flag<Config>{name, true, skip_handshaker,
+                      [=](Config *config, const char *param) -> bool {
+                        (config->*field) = std::make_unique<std::string>(param);
+                        return true;
+                      }};
 }
 
 bool DecodeBase64(std::string *out, const std::string &in) {
@@ -161,281 +165,351 @@
   return true;
 }
 
-Flag Base64Flag(const char *name, std::string TestConfig::*field,
-                bool skip_handshaker = false) {
-  return Flag{name, true, skip_handshaker,
-              [=](TestConfig *config, const char *param) -> bool {
-                return DecodeBase64(&(config->*field), param);
-              }};
+template <typename Config>
+Flag<Config> Base64Flag(const char *name, std::string Config::*field,
+                        bool skip_handshaker = false) {
+  return Flag<Config>{name, true, skip_handshaker,
+                      [=](Config *config, const char *param) -> bool {
+                        return DecodeBase64(&(config->*field), param);
+                      }};
 }
 
-Flag Base64VectorFlag(const char *name,
-                      std::vector<std::string> TestConfig::*field,
-                      bool skip_handshaker = false) {
-  return Flag{name, true, skip_handshaker,
-              [=](TestConfig *config, const char *param) -> bool {
-                std::string value;
-                if (!DecodeBase64(&value, param)) {
-                  return false;
-                }
-                (config->*field).push_back(std::move(value));
-                return true;
-              }};
+template <typename Config>
+Flag<Config> Base64VectorFlag(const char *name,
+                              std::vector<std::string> Config::*field,
+                              bool skip_handshaker = false) {
+  return Flag<Config>{name, true, skip_handshaker,
+                      [=](Config *config, const char *param) -> bool {
+                        std::string value;
+                        if (!DecodeBase64(&value, param)) {
+                          return false;
+                        }
+                        (config->*field).push_back(std::move(value));
+                        return true;
+                      }};
 }
 
-Flag StringPairVectorFlag(
+template <typename Config>
+Flag<Config> StringPairVectorFlag(
     const char *name,
-    std::vector<std::pair<std::string, std::string>> TestConfig::*field,
+    std::vector<std::pair<std::string, std::string>> Config::*field,
     bool skip_handshaker = false) {
-  return Flag{name, true, skip_handshaker,
-              [=](TestConfig *config, const char *param) -> bool {
-                const char *comma = strchr(param, ',');
-                if (!comma) {
-                  return false;
-                }
-                (config->*field)
-                    .push_back(std::make_pair(std::string(param, comma - param),
-                                              std::string(comma + 1)));
-                return true;
-              }};
+  return Flag<Config>{
+      name, true, skip_handshaker,
+      [=](Config *config, const char *param) -> bool {
+        const char *comma = strchr(param, ',');
+        if (!comma) {
+          return false;
+        }
+        (config->*field)
+            .push_back(std::make_pair(std::string(param, comma - param),
+                                      std::string(comma + 1)));
+        return true;
+      }};
 }
 
-std::vector<Flag> SortedFlags() {
-  std::vector<Flag> flags = {
-      IntFlag("-port", &TestConfig::port, /*skip_handshaker=*/true),
-      BoolFlag("-ipv6", &TestConfig::ipv6, /*skip_handshaker=*/true),
-      IntFlag("-shim-id", &TestConfig::shim_id, /*skip_handshaker=*/true),
-      BoolFlag("-server", &TestConfig::is_server),
-      BoolFlag("-dtls", &TestConfig::is_dtls),
-      BoolFlag("-quic", &TestConfig::is_quic),
-      IntFlag("-resume-count", &TestConfig::resume_count),
-      StringFlag("-write-settings", &TestConfig::write_settings),
-      BoolFlag("-fallback-scsv", &TestConfig::fallback_scsv),
-      IntVectorFlag("-signing-prefs", &TestConfig::signing_prefs),
-      IntVectorFlag("-verify-prefs", &TestConfig::verify_prefs),
-      IntVectorFlag("-expect-peer-verify-pref",
-                    &TestConfig::expect_peer_verify_prefs),
-      IntVectorFlag("-curves", &TestConfig::curves),
-      StringFlag("-key-file", &TestConfig::key_file),
-      StringFlag("-cert-file", &TestConfig::cert_file),
-      StringFlag("-trust-cert", &TestConfig::trust_cert),
-      StringFlag("-expect-server-name", &TestConfig::expect_server_name),
-      BoolFlag("-enable-ech-grease", &TestConfig::enable_ech_grease),
-      Base64VectorFlag("-ech-server-config", &TestConfig::ech_server_configs),
-      Base64VectorFlag("-ech-server-key", &TestConfig::ech_server_keys),
-      IntVectorFlag("-ech-is-retry-config", &TestConfig::ech_is_retry_config),
-      BoolFlag("-expect-ech-accept", &TestConfig::expect_ech_accept),
-      StringFlag("-expect-ech-name-override",
-                 &TestConfig::expect_ech_name_override),
-      BoolFlag("-expect-no-ech-name-override",
-               &TestConfig::expect_no_ech_name_override),
-      Base64Flag("-expect-ech-retry-configs",
-                 &TestConfig::expect_ech_retry_configs),
-      BoolFlag("-expect-no-ech-retry-configs",
-               &TestConfig::expect_no_ech_retry_configs),
-      Base64Flag("-ech-config-list", &TestConfig::ech_config_list),
-      Base64Flag("-expect-certificate-types",
-                 &TestConfig::expect_certificate_types),
-      BoolFlag("-require-any-client-certificate",
-               &TestConfig::require_any_client_certificate),
-      StringFlag("-advertise-npn", &TestConfig::advertise_npn),
-      StringFlag("-expect-next-proto", &TestConfig::expect_next_proto),
-      BoolFlag("-false-start", &TestConfig::false_start),
-      StringFlag("-select-next-proto", &TestConfig::select_next_proto),
-      BoolFlag("-async", &TestConfig::async),
-      BoolFlag("-write-different-record-sizes",
-               &TestConfig::write_different_record_sizes),
-      BoolFlag("-cbc-record-splitting", &TestConfig::cbc_record_splitting),
-      BoolFlag("-partial-write", &TestConfig::partial_write),
-      BoolFlag("-no-tls13", &TestConfig::no_tls13),
-      BoolFlag("-no-tls12", &TestConfig::no_tls12),
-      BoolFlag("-no-tls11", &TestConfig::no_tls11),
-      BoolFlag("-no-tls1", &TestConfig::no_tls1),
-      BoolFlag("-no-ticket", &TestConfig::no_ticket),
-      Base64Flag("-expect-channel-id", &TestConfig::expect_channel_id),
-      BoolFlag("-enable-channel-id", &TestConfig::enable_channel_id),
-      StringFlag("-send-channel-id", &TestConfig::send_channel_id),
-      BoolFlag("-shim-writes-first", &TestConfig::shim_writes_first),
-      StringFlag("-host-name", &TestConfig::host_name),
-      StringFlag("-advertise-alpn", &TestConfig::advertise_alpn),
-      StringFlag("-expect-alpn", &TestConfig::expect_alpn),
-      StringFlag("-expect-late-alpn", &TestConfig::expect_late_alpn),
-      StringFlag("-expect-advertised-alpn",
-                 &TestConfig::expect_advertised_alpn),
-      StringFlag("-select-alpn", &TestConfig::select_alpn),
-      BoolFlag("-decline-alpn", &TestConfig::decline_alpn),
-      BoolFlag("-reject-alpn", &TestConfig::reject_alpn),
-      BoolFlag("-select-empty-alpn", &TestConfig::select_empty_alpn),
-      BoolFlag("-defer-alps", &TestConfig::defer_alps),
-      StringPairVectorFlag("-application-settings",
-                           &TestConfig::application_settings),
-      OptionalStringFlag("-expect-peer-application-settings",
-                         &TestConfig::expect_peer_application_settings),
-      BoolFlag("-alps-use-new-codepoint", &TestConfig::alps_use_new_codepoint),
-      Base64Flag("-quic-transport-params", &TestConfig::quic_transport_params),
-      Base64Flag("-expect-quic-transport-params",
-                 &TestConfig::expect_quic_transport_params),
-      IntFlag("-quic-use-legacy-codepoint",
-              &TestConfig::quic_use_legacy_codepoint),
-      BoolFlag("-expect-session-miss", &TestConfig::expect_session_miss),
-      BoolFlag("-expect-extended-master-secret",
-               &TestConfig::expect_extended_master_secret),
-      StringFlag("-psk", &TestConfig::psk),
-      StringFlag("-psk-identity", &TestConfig::psk_identity),
-      StringFlag("-srtp-profiles", &TestConfig::srtp_profiles),
-      BoolFlag("-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling),
-      BoolFlag("-enable-signed-cert-timestamps",
-               &TestConfig::enable_signed_cert_timestamps),
-      Base64Flag("-expect-signed-cert-timestamps",
-                 &TestConfig::expect_signed_cert_timestamps),
-      IntFlag("-min-version", &TestConfig::min_version),
-      IntFlag("-max-version", &TestConfig::max_version),
-      IntFlag("-expect-version", &TestConfig::expect_version),
-      IntFlag("-mtu", &TestConfig::mtu),
-      BoolFlag("-implicit-handshake", &TestConfig::implicit_handshake),
-      BoolFlag("-use-early-callback", &TestConfig::use_early_callback),
-      BoolFlag("-fail-early-callback", &TestConfig::fail_early_callback),
-      BoolFlag("-install-ddos-callback", &TestConfig::install_ddos_callback),
-      BoolFlag("-fail-ddos-callback", &TestConfig::fail_ddos_callback),
-      BoolFlag("-fail-cert-callback", &TestConfig::fail_cert_callback),
-      StringFlag("-cipher", &TestConfig::cipher),
-      BoolFlag("-handshake-never-done", &TestConfig::handshake_never_done),
-      IntFlag("-export-keying-material", &TestConfig::export_keying_material),
-      StringFlag("-export-label", &TestConfig::export_label),
-      StringFlag("-export-context", &TestConfig::export_context),
-      BoolFlag("-use-export-context", &TestConfig::use_export_context),
-      BoolFlag("-tls-unique", &TestConfig::tls_unique),
-      BoolFlag("-expect-ticket-renewal", &TestConfig::expect_ticket_renewal),
-      BoolFlag("-expect-no-session", &TestConfig::expect_no_session),
-      BoolFlag("-expect-ticket-supports-early-data",
-               &TestConfig::expect_ticket_supports_early_data),
-      BoolFlag("-expect-accept-early-data",
-               &TestConfig::expect_accept_early_data),
-      BoolFlag("-expect-reject-early-data",
-               &TestConfig::expect_reject_early_data),
-      BoolFlag("-expect-no-offer-early-data",
-               &TestConfig::expect_no_offer_early_data),
-      BoolFlag("-use-ticket-callback", &TestConfig::use_ticket_callback),
-      BoolFlag("-renew-ticket", &TestConfig::renew_ticket),
-      BoolFlag("-enable-early-data", &TestConfig::enable_early_data),
-      Base64Flag("-ocsp-response", &TestConfig::ocsp_response),
-      Base64Flag("-expect-ocsp-response", &TestConfig::expect_ocsp_response),
-      BoolFlag("-check-close-notify", &TestConfig::check_close_notify),
-      BoolFlag("-shim-shuts-down", &TestConfig::shim_shuts_down),
-      BoolFlag("-verify-fail", &TestConfig::verify_fail),
-      BoolFlag("-verify-peer", &TestConfig::verify_peer),
-      BoolFlag("-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc),
-      BoolFlag("-expect-verify-result", &TestConfig::expect_verify_result),
-      Base64Flag("-signed-cert-timestamps",
-                 &TestConfig::signed_cert_timestamps),
-      IntFlag("-expect-total-renegotiations",
-              &TestConfig::expect_total_renegotiations),
-      BoolFlag("-renegotiate-once", &TestConfig::renegotiate_once),
-      BoolFlag("-renegotiate-freely", &TestConfig::renegotiate_freely),
-      BoolFlag("-renegotiate-ignore", &TestConfig::renegotiate_ignore),
-      BoolFlag("-renegotiate-explicit", &TestConfig::renegotiate_explicit),
-      BoolFlag("-forbid-renegotiation-after-handshake",
-               &TestConfig::forbid_renegotiation_after_handshake),
-      IntFlag("-expect-peer-signature-algorithm",
-              &TestConfig::expect_peer_signature_algorithm),
-      IntFlag("-expect-curve-id", &TestConfig::expect_curve_id),
-      BoolFlag("-use-old-client-cert-callback",
-               &TestConfig::use_old_client_cert_callback),
-      IntFlag("-initial-timeout-duration-ms",
-              &TestConfig::initial_timeout_duration_ms),
-      StringFlag("-use-client-ca-list", &TestConfig::use_client_ca_list),
-      StringFlag("-expect-client-ca-list", &TestConfig::expect_client_ca_list),
-      BoolFlag("-send-alert", &TestConfig::send_alert),
-      BoolFlag("-peek-then-read", &TestConfig::peek_then_read),
-      BoolFlag("-enable-grease", &TestConfig::enable_grease),
-      BoolFlag("-permute-extensions", &TestConfig::permute_extensions),
-      IntFlag("-max-cert-list", &TestConfig::max_cert_list),
-      Base64Flag("-ticket-key", &TestConfig::ticket_key),
-      BoolFlag("-use-exporter-between-reads",
-               &TestConfig::use_exporter_between_reads),
-      IntFlag("-expect-cipher-aes", &TestConfig::expect_cipher_aes),
-      IntFlag("-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes),
-      IntFlag("-expect-cipher", &TestConfig::expect_cipher),
-      StringFlag("-expect-peer-cert-file", &TestConfig::expect_peer_cert_file),
-      IntFlag("-resumption-delay", &TestConfig::resumption_delay),
-      BoolFlag("-retain-only-sha256-client-cert",
-               &TestConfig::retain_only_sha256_client_cert),
-      BoolFlag("-expect-sha256-client-cert",
-               &TestConfig::expect_sha256_client_cert),
-      BoolFlag("-read-with-unfinished-write",
-               &TestConfig::read_with_unfinished_write),
-      BoolFlag("-expect-secure-renegotiation",
-               &TestConfig::expect_secure_renegotiation),
-      BoolFlag("-expect-no-secure-renegotiation",
-               &TestConfig::expect_no_secure_renegotiation),
-      IntFlag("-max-send-fragment", &TestConfig::max_send_fragment),
-      IntFlag("-read-size", &TestConfig::read_size),
-      BoolFlag("-expect-session-id", &TestConfig::expect_session_id),
-      BoolFlag("-expect-no-session-id", &TestConfig::expect_no_session_id),
-      IntFlag("-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew),
-      BoolFlag("-no-op-extra-handshake", &TestConfig::no_op_extra_handshake),
-      BoolFlag("-handshake-twice", &TestConfig::handshake_twice),
-      BoolFlag("-allow-unknown-alpn-protos",
-               &TestConfig::allow_unknown_alpn_protos),
-      BoolFlag("-use-custom-verify-callback",
-               &TestConfig::use_custom_verify_callback),
-      StringFlag("-expect-msg-callback", &TestConfig::expect_msg_callback),
-      BoolFlag("-allow-false-start-without-alpn",
-               &TestConfig::allow_false_start_without_alpn),
-      BoolFlag("-handoff", &TestConfig::handoff),
-      BoolFlag("-handshake-hints", &TestConfig::handshake_hints),
-      BoolFlag("-allow-hint-mismatch", &TestConfig::allow_hint_mismatch),
-      BoolFlag("-use-ocsp-callback", &TestConfig::use_ocsp_callback),
-      BoolFlag("-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback),
-      BoolFlag("-decline-ocsp-callback", &TestConfig::decline_ocsp_callback),
-      BoolFlag("-fail-ocsp-callback", &TestConfig::fail_ocsp_callback),
-      BoolFlag("-install-cert-compression-algs",
-               &TestConfig::install_cert_compression_algs),
-      IntFlag("-install-one-cert-compression-alg",
-              &TestConfig::install_one_cert_compression_alg),
-      BoolFlag("-reverify-on-resume", &TestConfig::reverify_on_resume),
-      BoolFlag("-ignore-rsa-key-usage", &TestConfig::ignore_rsa_key_usage),
-      BoolFlag("-expect-key-usage-invalid",
-               &TestConfig::expect_key_usage_invalid),
-      BoolFlag("-is-handshaker-supported",
-               &TestConfig::is_handshaker_supported),
-      BoolFlag("-handshaker-resume", &TestConfig::handshaker_resume),
-      StringFlag("-handshaker-path", &TestConfig::handshaker_path),
-      BoolFlag("-jdk11-workaround", &TestConfig::jdk11_workaround),
-      BoolFlag("-server-preference", &TestConfig::server_preference),
-      BoolFlag("-export-traffic-secrets", &TestConfig::export_traffic_secrets),
-      BoolFlag("-key-update", &TestConfig::key_update),
-      BoolFlag("-expect-delegated-credential-used",
-               &TestConfig::expect_delegated_credential_used),
-      StringFlag("-delegated-credential", &TestConfig::delegated_credential),
-      StringFlag("-expect-early-data-reason",
-                 &TestConfig::expect_early_data_reason),
-      BoolFlag("-expect-hrr", &TestConfig::expect_hrr),
-      BoolFlag("-expect-no-hrr", &TestConfig::expect_no_hrr),
-      BoolFlag("-wait-for-debugger", &TestConfig::wait_for_debugger),
-      StringFlag("-quic-early-data-context",
-                 &TestConfig::quic_early_data_context),
-      IntFlag("-early-write-after-message",
-              &TestConfig::early_write_after_message),
-      BoolFlag("-fips-202205", &TestConfig::fips_202205),
-      BoolFlag("-wpa-202304", &TestConfig::wpa_202304),
-      BoolFlag("-no-check-client-certificate-type",
-               &TestConfig::no_check_client_certificate_type),
-      BoolFlag("-no-check-ecdsa-curve", &TestConfig::no_check_ecdsa_curve),
-  };
-  std::sort(flags.begin(), flags.end(), [](const Flag &a, const Flag &b) {
-    return strcmp(a.name, b.name) < 0;
-  });
-  return flags;
+Flag<TestConfig> NewCredentialFlag(const char *name,
+                                   CredentialConfigType type) {
+  return Flag<TestConfig>{name, /*has_param=*/false, /*skip_handshaker=*/false,
+                          [=](TestConfig *config, const char *param) -> bool {
+                            config->credentials.emplace_back();
+                            config->credentials.back().type = type;
+                            return true;
+                          }};
 }
 
-const Flag *FindFlag(const char *name) {
-  static const std::vector<Flag> kSortedFlags = SortedFlags();
-  auto iter = std::lower_bound(kSortedFlags.begin(), kSortedFlags.end(), name,
-                               [](const Flag &flag, const char *key) {
-                                 return strcmp(flag.name, key) < 0;
-                               });
-  if (iter == kSortedFlags.end() || strcmp(iter->name, name) != 0) {
+Flag<TestConfig> CredentialFlagWithDefault(Flag<TestConfig> default_flag,
+                                           Flag<CredentialConfig> flag) {
+  BSSL_CHECK(strcmp(default_flag.name, flag.name) == 0);
+  BSSL_CHECK(default_flag.has_param == flag.has_param);
+  return Flag<TestConfig>{flag.name, flag.has_param, /*skip_handshaker=*/false,
+                          [=](TestConfig *config, const char *param) -> bool {
+                            if (config->credentials.empty()) {
+                              return default_flag.set_param(config, param);
+                            }
+                            return flag.set_param(&config->credentials.back(),
+                                                  param);
+                          }};
+}
+
+Flag<TestConfig> CredentialFlag(Flag<CredentialConfig> flag) {
+  return Flag<TestConfig>{flag.name, flag.has_param, /*skip_handshaker=*/false,
+                          [=](TestConfig *config, const char *param) -> bool {
+                            if (config->credentials.empty()) {
+                              fprintf(stderr, "No credentials configured.\n");
+                              return false;
+                            }
+                            return flag.set_param(&config->credentials.back(),
+                                                  param);
+                          }};
+}
+
+struct FlagNameComparator {
+  template <typename Config>
+  bool operator()(const Flag<Config> &flag1, const Flag<Config> &flag2) const {
+    return strcmp(flag1.name, flag2.name) < 0;
+  }
+
+  template <typename Config>
+  bool operator()(const Flag<Config> &flag, const char *name) const {
+    return strcmp(flag.name, name) < 0;
+  }
+};
+
+const Flag<TestConfig> *FindFlag(const char *name) {
+  static const std::vector<Flag<TestConfig>> flags = [] {
+    std::vector<Flag<TestConfig>> ret = {
+        IntFlag("-port", &TestConfig::port, /*skip_handshaker=*/true),
+        BoolFlag("-ipv6", &TestConfig::ipv6, /*skip_handshaker=*/true),
+        IntFlag("-shim-id", &TestConfig::shim_id, /*skip_handshaker=*/true),
+        BoolFlag("-server", &TestConfig::is_server),
+        BoolFlag("-dtls", &TestConfig::is_dtls),
+        BoolFlag("-quic", &TestConfig::is_quic),
+        IntFlag("-resume-count", &TestConfig::resume_count),
+        StringFlag("-write-settings", &TestConfig::write_settings),
+        BoolFlag("-fallback-scsv", &TestConfig::fallback_scsv),
+        IntVectorFlag("-verify-prefs", &TestConfig::verify_prefs),
+        IntVectorFlag("-expect-peer-verify-pref",
+                      &TestConfig::expect_peer_verify_prefs),
+        IntVectorFlag("-curves", &TestConfig::curves),
+        StringFlag("-trust-cert", &TestConfig::trust_cert),
+        StringFlag("-expect-server-name", &TestConfig::expect_server_name),
+        BoolFlag("-enable-ech-grease", &TestConfig::enable_ech_grease),
+        Base64VectorFlag("-ech-server-config", &TestConfig::ech_server_configs),
+        Base64VectorFlag("-ech-server-key", &TestConfig::ech_server_keys),
+        IntVectorFlag("-ech-is-retry-config", &TestConfig::ech_is_retry_config),
+        BoolFlag("-expect-ech-accept", &TestConfig::expect_ech_accept),
+        StringFlag("-expect-ech-name-override",
+                   &TestConfig::expect_ech_name_override),
+        BoolFlag("-expect-no-ech-name-override",
+                 &TestConfig::expect_no_ech_name_override),
+        Base64Flag("-expect-ech-retry-configs",
+                   &TestConfig::expect_ech_retry_configs),
+        BoolFlag("-expect-no-ech-retry-configs",
+                 &TestConfig::expect_no_ech_retry_configs),
+        Base64Flag("-ech-config-list", &TestConfig::ech_config_list),
+        Base64Flag("-expect-certificate-types",
+                   &TestConfig::expect_certificate_types),
+        BoolFlag("-require-any-client-certificate",
+                 &TestConfig::require_any_client_certificate),
+        StringFlag("-advertise-npn", &TestConfig::advertise_npn),
+        StringFlag("-expect-next-proto", &TestConfig::expect_next_proto),
+        BoolFlag("-false-start", &TestConfig::false_start),
+        StringFlag("-select-next-proto", &TestConfig::select_next_proto),
+        BoolFlag("-async", &TestConfig::async),
+        BoolFlag("-write-different-record-sizes",
+                 &TestConfig::write_different_record_sizes),
+        BoolFlag("-cbc-record-splitting", &TestConfig::cbc_record_splitting),
+        BoolFlag("-partial-write", &TestConfig::partial_write),
+        BoolFlag("-no-tls13", &TestConfig::no_tls13),
+        BoolFlag("-no-tls12", &TestConfig::no_tls12),
+        BoolFlag("-no-tls11", &TestConfig::no_tls11),
+        BoolFlag("-no-tls1", &TestConfig::no_tls1),
+        BoolFlag("-no-ticket", &TestConfig::no_ticket),
+        Base64Flag("-expect-channel-id", &TestConfig::expect_channel_id),
+        BoolFlag("-enable-channel-id", &TestConfig::enable_channel_id),
+        StringFlag("-send-channel-id", &TestConfig::send_channel_id),
+        BoolFlag("-shim-writes-first", &TestConfig::shim_writes_first),
+        StringFlag("-host-name", &TestConfig::host_name),
+        StringFlag("-advertise-alpn", &TestConfig::advertise_alpn),
+        StringFlag("-expect-alpn", &TestConfig::expect_alpn),
+        StringFlag("-expect-late-alpn", &TestConfig::expect_late_alpn),
+        StringFlag("-expect-advertised-alpn",
+                   &TestConfig::expect_advertised_alpn),
+        StringFlag("-select-alpn", &TestConfig::select_alpn),
+        BoolFlag("-decline-alpn", &TestConfig::decline_alpn),
+        BoolFlag("-reject-alpn", &TestConfig::reject_alpn),
+        BoolFlag("-select-empty-alpn", &TestConfig::select_empty_alpn),
+        BoolFlag("-defer-alps", &TestConfig::defer_alps),
+        StringPairVectorFlag("-application-settings",
+                             &TestConfig::application_settings),
+        OptionalStringFlag("-expect-peer-application-settings",
+                           &TestConfig::expect_peer_application_settings),
+        BoolFlag("-alps-use-new-codepoint",
+                 &TestConfig::alps_use_new_codepoint),
+        Base64Flag("-quic-transport-params",
+                   &TestConfig::quic_transport_params),
+        Base64Flag("-expect-quic-transport-params",
+                   &TestConfig::expect_quic_transport_params),
+        IntFlag("-quic-use-legacy-codepoint",
+                &TestConfig::quic_use_legacy_codepoint),
+        BoolFlag("-expect-session-miss", &TestConfig::expect_session_miss),
+        BoolFlag("-expect-extended-master-secret",
+                 &TestConfig::expect_extended_master_secret),
+        StringFlag("-psk", &TestConfig::psk),
+        StringFlag("-psk-identity", &TestConfig::psk_identity),
+        StringFlag("-srtp-profiles", &TestConfig::srtp_profiles),
+        BoolFlag("-enable-ocsp-stapling", &TestConfig::enable_ocsp_stapling),
+        BoolFlag("-enable-signed-cert-timestamps",
+                 &TestConfig::enable_signed_cert_timestamps),
+        Base64Flag("-expect-signed-cert-timestamps",
+                   &TestConfig::expect_signed_cert_timestamps),
+        IntFlag("-min-version", &TestConfig::min_version),
+        IntFlag("-max-version", &TestConfig::max_version),
+        IntFlag("-expect-version", &TestConfig::expect_version),
+        IntFlag("-mtu", &TestConfig::mtu),
+        BoolFlag("-implicit-handshake", &TestConfig::implicit_handshake),
+        BoolFlag("-use-early-callback", &TestConfig::use_early_callback),
+        BoolFlag("-fail-early-callback", &TestConfig::fail_early_callback),
+        BoolFlag("-install-ddos-callback", &TestConfig::install_ddos_callback),
+        BoolFlag("-fail-ddos-callback", &TestConfig::fail_ddos_callback),
+        BoolFlag("-fail-cert-callback", &TestConfig::fail_cert_callback),
+        StringFlag("-cipher", &TestConfig::cipher),
+        BoolFlag("-handshake-never-done", &TestConfig::handshake_never_done),
+        IntFlag("-export-keying-material", &TestConfig::export_keying_material),
+        StringFlag("-export-label", &TestConfig::export_label),
+        StringFlag("-export-context", &TestConfig::export_context),
+        BoolFlag("-use-export-context", &TestConfig::use_export_context),
+        BoolFlag("-tls-unique", &TestConfig::tls_unique),
+        BoolFlag("-expect-ticket-renewal", &TestConfig::expect_ticket_renewal),
+        BoolFlag("-expect-no-session", &TestConfig::expect_no_session),
+        BoolFlag("-expect-ticket-supports-early-data",
+                 &TestConfig::expect_ticket_supports_early_data),
+        BoolFlag("-expect-accept-early-data",
+                 &TestConfig::expect_accept_early_data),
+        BoolFlag("-expect-reject-early-data",
+                 &TestConfig::expect_reject_early_data),
+        BoolFlag("-expect-no-offer-early-data",
+                 &TestConfig::expect_no_offer_early_data),
+        BoolFlag("-use-ticket-callback", &TestConfig::use_ticket_callback),
+        BoolFlag("-renew-ticket", &TestConfig::renew_ticket),
+        BoolFlag("-enable-early-data", &TestConfig::enable_early_data),
+        Base64Flag("-expect-ocsp-response", &TestConfig::expect_ocsp_response),
+        BoolFlag("-check-close-notify", &TestConfig::check_close_notify),
+        BoolFlag("-shim-shuts-down", &TestConfig::shim_shuts_down),
+        BoolFlag("-verify-fail", &TestConfig::verify_fail),
+        BoolFlag("-verify-peer", &TestConfig::verify_peer),
+        BoolFlag("-verify-peer-if-no-obc", &TestConfig::verify_peer_if_no_obc),
+        BoolFlag("-expect-verify-result", &TestConfig::expect_verify_result),
+        IntFlag("-expect-total-renegotiations",
+                &TestConfig::expect_total_renegotiations),
+        BoolFlag("-renegotiate-once", &TestConfig::renegotiate_once),
+        BoolFlag("-renegotiate-freely", &TestConfig::renegotiate_freely),
+        BoolFlag("-renegotiate-ignore", &TestConfig::renegotiate_ignore),
+        BoolFlag("-renegotiate-explicit", &TestConfig::renegotiate_explicit),
+        BoolFlag("-forbid-renegotiation-after-handshake",
+                 &TestConfig::forbid_renegotiation_after_handshake),
+        IntFlag("-expect-peer-signature-algorithm",
+                &TestConfig::expect_peer_signature_algorithm),
+        IntFlag("-expect-curve-id", &TestConfig::expect_curve_id),
+        BoolFlag("-use-old-client-cert-callback",
+                 &TestConfig::use_old_client_cert_callback),
+        IntFlag("-initial-timeout-duration-ms",
+                &TestConfig::initial_timeout_duration_ms),
+        StringFlag("-use-client-ca-list", &TestConfig::use_client_ca_list),
+        StringFlag("-expect-client-ca-list",
+                   &TestConfig::expect_client_ca_list),
+        BoolFlag("-send-alert", &TestConfig::send_alert),
+        BoolFlag("-peek-then-read", &TestConfig::peek_then_read),
+        BoolFlag("-enable-grease", &TestConfig::enable_grease),
+        BoolFlag("-permute-extensions", &TestConfig::permute_extensions),
+        IntFlag("-max-cert-list", &TestConfig::max_cert_list),
+        Base64Flag("-ticket-key", &TestConfig::ticket_key),
+        BoolFlag("-use-exporter-between-reads",
+                 &TestConfig::use_exporter_between_reads),
+        IntFlag("-expect-cipher-aes", &TestConfig::expect_cipher_aes),
+        IntFlag("-expect-cipher-no-aes", &TestConfig::expect_cipher_no_aes),
+        IntFlag("-expect-cipher", &TestConfig::expect_cipher),
+        StringFlag("-expect-peer-cert-file",
+                   &TestConfig::expect_peer_cert_file),
+        IntFlag("-resumption-delay", &TestConfig::resumption_delay),
+        BoolFlag("-retain-only-sha256-client-cert",
+                 &TestConfig::retain_only_sha256_client_cert),
+        BoolFlag("-expect-sha256-client-cert",
+                 &TestConfig::expect_sha256_client_cert),
+        BoolFlag("-read-with-unfinished-write",
+                 &TestConfig::read_with_unfinished_write),
+        BoolFlag("-expect-secure-renegotiation",
+                 &TestConfig::expect_secure_renegotiation),
+        BoolFlag("-expect-no-secure-renegotiation",
+                 &TestConfig::expect_no_secure_renegotiation),
+        IntFlag("-max-send-fragment", &TestConfig::max_send_fragment),
+        IntFlag("-read-size", &TestConfig::read_size),
+        BoolFlag("-expect-session-id", &TestConfig::expect_session_id),
+        BoolFlag("-expect-no-session-id", &TestConfig::expect_no_session_id),
+        IntFlag("-expect-ticket-age-skew", &TestConfig::expect_ticket_age_skew),
+        BoolFlag("-no-op-extra-handshake", &TestConfig::no_op_extra_handshake),
+        BoolFlag("-handshake-twice", &TestConfig::handshake_twice),
+        BoolFlag("-allow-unknown-alpn-protos",
+                 &TestConfig::allow_unknown_alpn_protos),
+        BoolFlag("-use-custom-verify-callback",
+                 &TestConfig::use_custom_verify_callback),
+        StringFlag("-expect-msg-callback", &TestConfig::expect_msg_callback),
+        BoolFlag("-allow-false-start-without-alpn",
+                 &TestConfig::allow_false_start_without_alpn),
+        BoolFlag("-handoff", &TestConfig::handoff),
+        BoolFlag("-handshake-hints", &TestConfig::handshake_hints),
+        BoolFlag("-allow-hint-mismatch", &TestConfig::allow_hint_mismatch),
+        BoolFlag("-use-ocsp-callback", &TestConfig::use_ocsp_callback),
+        BoolFlag("-set-ocsp-in-callback", &TestConfig::set_ocsp_in_callback),
+        BoolFlag("-decline-ocsp-callback", &TestConfig::decline_ocsp_callback),
+        BoolFlag("-fail-ocsp-callback", &TestConfig::fail_ocsp_callback),
+        BoolFlag("-install-cert-compression-algs",
+                 &TestConfig::install_cert_compression_algs),
+        IntFlag("-install-one-cert-compression-alg",
+                &TestConfig::install_one_cert_compression_alg),
+        BoolFlag("-reverify-on-resume", &TestConfig::reverify_on_resume),
+        BoolFlag("-ignore-rsa-key-usage", &TestConfig::ignore_rsa_key_usage),
+        BoolFlag("-expect-key-usage-invalid",
+                 &TestConfig::expect_key_usage_invalid),
+        BoolFlag("-is-handshaker-supported",
+                 &TestConfig::is_handshaker_supported),
+        BoolFlag("-handshaker-resume", &TestConfig::handshaker_resume),
+        StringFlag("-handshaker-path", &TestConfig::handshaker_path),
+        BoolFlag("-jdk11-workaround", &TestConfig::jdk11_workaround),
+        BoolFlag("-server-preference", &TestConfig::server_preference),
+        BoolFlag("-export-traffic-secrets",
+                 &TestConfig::export_traffic_secrets),
+        BoolFlag("-key-update", &TestConfig::key_update),
+        StringFlag("-expect-early-data-reason",
+                   &TestConfig::expect_early_data_reason),
+        BoolFlag("-expect-hrr", &TestConfig::expect_hrr),
+        BoolFlag("-expect-no-hrr", &TestConfig::expect_no_hrr),
+        BoolFlag("-wait-for-debugger", &TestConfig::wait_for_debugger),
+        StringFlag("-quic-early-data-context",
+                   &TestConfig::quic_early_data_context),
+        IntFlag("-early-write-after-message",
+                &TestConfig::early_write_after_message),
+        BoolFlag("-fips-202205", &TestConfig::fips_202205),
+        BoolFlag("-wpa-202304", &TestConfig::wpa_202304),
+        BoolFlag("-no-check-client-certificate-type",
+                 &TestConfig::no_check_client_certificate_type),
+        BoolFlag("-no-check-ecdsa-curve", &TestConfig::no_check_ecdsa_curve),
+        IntFlag("-expect-selected-credential",
+                &TestConfig::expect_selected_credential),
+        // Credential flags are stateful. First, use one of the
+        // -new-*-credential flags to introduce a new credential. Then the flags
+        // below switch from acting on the default credential to the newly-added
+        // one. Repeat this process to continue adding them.
+        NewCredentialFlag("-new-x509-credential", CredentialConfigType::kX509),
+        NewCredentialFlag("-new-delegated-credential",
+                          CredentialConfigType::kDelegated),
+        CredentialFlagWithDefault(
+            StringFlag("-cert-file", &TestConfig::cert_file),
+            StringFlag("-cert-file", &CredentialConfig::cert_file)),
+        CredentialFlagWithDefault(
+            StringFlag("-key-file", &TestConfig::key_file),
+            StringFlag("-key-file", &CredentialConfig::key_file)),
+        CredentialFlagWithDefault(
+            IntVectorFlag("-signing-prefs", &TestConfig::signing_prefs),
+            IntVectorFlag("-signing-prefs", &CredentialConfig::signing_prefs)),
+        CredentialFlag(Base64Flag("-delegated-credential",
+                                  &CredentialConfig::delegated_credential)),
+        CredentialFlagWithDefault(
+            Base64Flag("-ocsp-response", &TestConfig::ocsp_response),
+            Base64Flag("-ocsp-response", &CredentialConfig::ocsp_response)),
+        CredentialFlagWithDefault(
+            Base64Flag("-signed-cert-timestamps",
+                       &TestConfig::signed_cert_timestamps),
+            Base64Flag("-signed-cert-timestamps",
+                       &CredentialConfig::signed_cert_timestamps)),
+    };
+    std::sort(ret.begin(), ret.end(), FlagNameComparator{});
+    return ret;
+  }();
+  auto iter =
+      std::lower_bound(flags.begin(), flags.end(), name, FlagNameComparator{});
+  if (iter == flags.end() || strcmp(iter->name, name) != 0) {
     return nullptr;
   }
   return &*iter;
@@ -487,7 +561,7 @@
       out = out_retry;
     }
 
-    const Flag *flag = FindFlag(name);
+    const Flag<TestConfig> *flag = FindFlag(name);
     if (flag == nullptr) {
       fprintf(stderr, "Unrecognized flag: %s\n", name);
       return false;
@@ -564,6 +638,44 @@
       SSL_get_ex_data(ssl, TestConfigExDataIndex()));
 }
 
+struct CredentialInfo {
+  int number = -1;
+  bssl::UniquePtr<EVP_PKEY> private_key;
+};
+
+static void CredentialInfoExDataFree(void *parent, void *ptr,
+                                     CRYPTO_EX_DATA *ad, int index, long argl,
+                                     void *argp) {
+  delete static_cast<CredentialInfo*>(ptr);
+}
+
+static int CredentialInfoExDataIndex() {
+  static int index = [&] {
+    OPENSSL_disable_malloc_failures_for_testing();
+    int ret = SSL_CREDENTIAL_get_ex_new_index(0, nullptr, nullptr, nullptr,
+                                              CredentialInfoExDataFree);
+    BSSL_CHECK(ret >= 0);
+    OPENSSL_enable_malloc_failures_for_testing();
+    return ret;
+  }();
+  return index;
+}
+
+static const CredentialInfo *GetCredentialInfo(const SSL_CREDENTIAL *cred) {
+  return static_cast<const CredentialInfo *>(
+      SSL_CREDENTIAL_get_ex_data(cred, CredentialInfoExDataIndex()));
+}
+
+static bool SetCredentialInfo(SSL_CREDENTIAL *cred,
+                              std::unique_ptr<CredentialInfo> info) {
+  if (!SSL_CREDENTIAL_set_ex_data(cred, CredentialInfoExDataIndex(),
+                                  info.get())) {
+    return false;
+  }
+  info.release();  // |cred| takes ownership on success.
+  return true;
+}
+
 static int LegacyOCSPCallback(SSL *ssl, void *arg) {
   const TestConfig *config = GetTestConfig(ssl);
   if (!SSL_is_server(ssl)) {
@@ -775,13 +887,23 @@
       // test fails.
       abort();
     }
+
     // This callback is called when the handshake completes. |SSL_get_session|
     // must continue to work and |SSL_in_init| must return false.
     if (SSL_in_init(ssl) || SSL_get_session(ssl) == nullptr) {
       fprintf(stderr, "Invalid state for SSL_CB_HANDSHAKE_DONE.\n");
       abort();
     }
-    GetTestState(ssl)->handshake_done = true;
+
+    TestState *test_state = GetTestState(ssl);
+    test_state->handshake_done = true;
+
+    // Save the selected credential for the tests to assert on.
+    const SSL_CREDENTIAL *cred = SSL_get0_selected_credential(ssl);
+    const CredentialInfo *cred_info =
+        cred != nullptr ? GetCredentialInfo(cred) : nullptr;
+    test_state->selected_credential =
+        cred_info != nullptr ? cred_info->number : -1;
   }
 }
 
@@ -947,6 +1069,254 @@
       PEM_read_bio_PrivateKey(bio.get(), NULL, NULL, NULL));
 }
 
+static bssl::UniquePtr<CRYPTO_BUFFER> X509ToBuffer(X509 *x509) {
+  uint8_t *der = nullptr;
+  int der_len = i2d_X509(x509, &der);
+  if (der_len < 0) {
+    return nullptr;
+  }
+  bssl::UniquePtr<uint8_t> free_der(der);
+  return bssl::UniquePtr<CRYPTO_BUFFER>(
+      CRYPTO_BUFFER_new(der, der_len, nullptr));
+}
+
+
+static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
+                                                        size_t *out_len,
+                                                        size_t max_out);
+
+static EVP_PKEY *GetPrivateKey(SSL *ssl) {
+  const CredentialInfo *cred_info =
+      GetCredentialInfo(SSL_get0_selected_credential(ssl));
+  if (cred_info != nullptr) {
+    return cred_info->private_key.get();
+  }
+
+  return GetTestState(ssl)->private_key.get();
+}
+
+static ssl_private_key_result_t AsyncPrivateKeySign(
+    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
+    uint16_t signature_algorithm, const uint8_t *in, size_t in_len) {
+  TestState *test_state = GetTestState(ssl);
+  test_state->used_private_key = true;
+  if (!test_state->private_key_result.empty()) {
+    fprintf(stderr, "AsyncPrivateKeySign called with operation pending.\n");
+    abort();
+  }
+
+  EVP_PKEY *private_key = GetPrivateKey(ssl);
+  if (EVP_PKEY_id(private_key) !=
+      SSL_get_signature_algorithm_key_type(signature_algorithm)) {
+    fprintf(stderr, "Key type does not match signature algorithm.\n");
+    abort();
+  }
+
+  // Determine the hash.
+  const EVP_MD *md = SSL_get_signature_algorithm_digest(signature_algorithm);
+  bssl::ScopedEVP_MD_CTX ctx;
+  EVP_PKEY_CTX *pctx;
+  if (!EVP_DigestSignInit(ctx.get(), &pctx, md, nullptr, private_key)) {
+    return ssl_private_key_failure;
+  }
+
+  // Configure additional signature parameters.
+  if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) {
+    if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) ||
+        !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1 /* salt len = hash len */)) {
+      return ssl_private_key_failure;
+    }
+  }
+
+  // Write the signature into |test_state|.
+  size_t len = 0;
+  if (!EVP_DigestSign(ctx.get(), nullptr, &len, in, in_len)) {
+    return ssl_private_key_failure;
+  }
+  test_state->private_key_result.resize(len);
+  if (!EVP_DigestSign(ctx.get(), test_state->private_key_result.data(), &len,
+                      in, in_len)) {
+    return ssl_private_key_failure;
+  }
+  test_state->private_key_result.resize(len);
+
+  return AsyncPrivateKeyComplete(ssl, out, out_len, max_out);
+}
+
+static ssl_private_key_result_t AsyncPrivateKeyDecrypt(SSL *ssl, uint8_t *out,
+                                                       size_t *out_len,
+                                                       size_t max_out,
+                                                       const uint8_t *in,
+                                                       size_t in_len) {
+  TestState *test_state = GetTestState(ssl);
+  test_state->used_private_key = true;
+  if (!test_state->private_key_result.empty()) {
+    fprintf(stderr, "AsyncPrivateKeyDecrypt called with operation pending.\n");
+    abort();
+  }
+
+  EVP_PKEY *private_key = GetPrivateKey(ssl);
+  RSA *rsa = EVP_PKEY_get0_RSA(private_key);
+  if (rsa == NULL) {
+    fprintf(stderr, "AsyncPrivateKeyDecrypt called with incorrect key type.\n");
+    abort();
+  }
+  test_state->private_key_result.resize(RSA_size(rsa));
+  if (!RSA_decrypt(rsa, out_len, test_state->private_key_result.data(),
+                   RSA_size(rsa), in, in_len, RSA_NO_PADDING)) {
+    return ssl_private_key_failure;
+  }
+
+  test_state->private_key_result.resize(*out_len);
+
+  return AsyncPrivateKeyComplete(ssl, out, out_len, max_out);
+}
+
+static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
+                                                        size_t *out_len,
+                                                        size_t max_out) {
+  TestState *test_state = GetTestState(ssl);
+  if (test_state->private_key_result.empty()) {
+    fprintf(stderr,
+            "AsyncPrivateKeyComplete called without operation pending.\n");
+    abort();
+  }
+
+  if (GetTestConfig(ssl)->async && test_state->private_key_retries < 2) {
+    // Only return the decryption on the second attempt, to test both incomplete
+    // |sign|/|decrypt| and |complete|.
+    return ssl_private_key_retry;
+  }
+
+  if (max_out < test_state->private_key_result.size()) {
+    fprintf(stderr, "Output buffer too small.\n");
+    return ssl_private_key_failure;
+  }
+  OPENSSL_memcpy(out, test_state->private_key_result.data(),
+                 test_state->private_key_result.size());
+  *out_len = test_state->private_key_result.size();
+
+  test_state->private_key_result.clear();
+  test_state->private_key_retries = 0;
+  return ssl_private_key_success;
+}
+
+static const SSL_PRIVATE_KEY_METHOD g_async_private_key_method = {
+    AsyncPrivateKeySign,
+    AsyncPrivateKeyDecrypt,
+    AsyncPrivateKeyComplete,
+};
+
+static bssl::UniquePtr<SSL_CREDENTIAL> CredentialFromConfig(
+    const TestConfig &config, const CredentialConfig &cred_config, int number) {
+  bssl::UniquePtr<SSL_CREDENTIAL> cred;
+  switch (cred_config.type) {
+    case CredentialConfigType::kX509:
+      cred.reset(SSL_CREDENTIAL_new_x509());
+      break;
+    case CredentialConfigType::kDelegated:
+      cred.reset(SSL_CREDENTIAL_new_delegated());
+      break;
+  }
+  if (cred == nullptr) {
+    return nullptr;
+  }
+
+  auto info = std::make_unique<CredentialInfo>();
+  info->number = number;
+
+  if (!cred_config.cert_file.empty()) {
+    bssl::UniquePtr<X509> x509;
+    bssl::UniquePtr<STACK_OF(X509)> chain;
+    if (!LoadCertificate(&x509, &chain, cred_config.cert_file.c_str())) {
+      return nullptr;
+    }
+    std::vector<bssl::UniquePtr<CRYPTO_BUFFER>> buffers;
+    buffers.push_back(X509ToBuffer(x509.get()));
+    if (buffers.back() == nullptr) {
+      return nullptr;
+    }
+    for (X509 *cert : chain.get()) {
+      buffers.push_back(X509ToBuffer(cert));
+      if (buffers.back() == nullptr) {
+        return nullptr;
+      }
+    }
+    std::vector<CRYPTO_BUFFER *> buffers_raw;
+    for (const auto &buffer : buffers) {
+      buffers_raw.push_back(buffer.get());
+    }
+    if (!SSL_CREDENTIAL_set1_cert_chain(cred.get(), buffers_raw.data(),
+                                        buffers_raw.size())) {
+      return nullptr;
+    }
+  }
+
+  if (!cred_config.key_file.empty()) {
+    bssl::UniquePtr<EVP_PKEY> pkey =
+        LoadPrivateKey(cred_config.key_file.c_str());
+    if (pkey == nullptr) {
+      return nullptr;
+    }
+    if (config.async || config.handshake_hints) {
+      info->private_key = std::move(pkey);
+      if (!SSL_CREDENTIAL_set_private_key_method(cred.get(),
+                                                 &g_async_private_key_method)) {
+        return nullptr;
+      }
+    } else {
+      if (!SSL_CREDENTIAL_set1_private_key(cred.get(), pkey.get())) {
+        return nullptr;
+      }
+    }
+  }
+
+  if (!cred_config.signing_prefs.empty() &&
+      !SSL_CREDENTIAL_set1_signing_algorithm_prefs(
+          cred.get(), cred_config.signing_prefs.data(),
+          cred_config.signing_prefs.size())) {
+    return nullptr;
+  }
+
+  if (!cred_config.delegated_credential.empty()) {
+    bssl::UniquePtr<CRYPTO_BUFFER> buf(
+        CRYPTO_BUFFER_new(reinterpret_cast<const uint8_t *>(
+                              cred_config.delegated_credential.data()),
+                          cred_config.delegated_credential.size(), nullptr));
+    if (buf == nullptr ||
+        !SSL_CREDENTIAL_set1_delegated_credential(cred.get(), buf.get())) {
+      return nullptr;
+    }
+  }
+
+  if (!cred_config.ocsp_response.empty()) {
+    bssl::UniquePtr<CRYPTO_BUFFER> buf(CRYPTO_BUFFER_new(
+        reinterpret_cast<const uint8_t *>(cred_config.ocsp_response.data()),
+        cred_config.ocsp_response.size(), nullptr));
+    if (buf == nullptr ||
+        !SSL_CREDENTIAL_set1_ocsp_response(cred.get(), buf.get())) {
+      return nullptr;
+    }
+  }
+
+  if (!cred_config.signed_cert_timestamps.empty()) {
+    bssl::UniquePtr<CRYPTO_BUFFER> buf(
+        CRYPTO_BUFFER_new(reinterpret_cast<const uint8_t *>(
+                              cred_config.signed_cert_timestamps.data()),
+                          cred_config.signed_cert_timestamps.size(), nullptr));
+    if (buf == nullptr || !SSL_CREDENTIAL_set1_signed_cert_timestamp_list(
+                              cred.get(), buf.get())) {
+      return nullptr;
+    }
+  }
+
+  if (!SetCredentialInfo(cred.get(), std::move(info))) {
+    return nullptr;
+  }
+
+  return cred;
+}
+
 static bool GetCertificate(SSL *ssl, bssl::UniquePtr<X509> *out_x509,
                            bssl::UniquePtr<STACK_OF(X509)> *out_chain,
                            bssl::UniquePtr<EVP_PKEY> *out_pkey) {
@@ -974,6 +1344,15 @@
                              config->ocsp_response.size())) {
     return false;
   }
+
+  for (size_t i = 0; i < config->credentials.size(); i++) {
+    bssl::UniquePtr<SSL_CREDENTIAL> cred = CredentialFromConfig(
+        *config, config->credentials[i], static_cast<int>(i));
+    if (cred == nullptr || !SSL_add1_credential(ssl, cred.get())) {
+      return false;
+    }
+  }
+
   return true;
 }
 
@@ -1163,121 +1542,6 @@
   return 1;
 }
 
-static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
-                                                        size_t *out_len,
-                                                        size_t max_out);
-
-static ssl_private_key_result_t AsyncPrivateKeySign(
-    SSL *ssl, uint8_t *out, size_t *out_len, size_t max_out,
-    uint16_t signature_algorithm, const uint8_t *in, size_t in_len) {
-  TestState *test_state = GetTestState(ssl);
-  test_state->used_private_key = true;
-  if (!test_state->private_key_result.empty()) {
-    fprintf(stderr, "AsyncPrivateKeySign called with operation pending.\n");
-    abort();
-  }
-
-  if (EVP_PKEY_id(test_state->private_key.get()) !=
-      SSL_get_signature_algorithm_key_type(signature_algorithm)) {
-    fprintf(stderr, "Key type does not match signature algorithm.\n");
-    abort();
-  }
-
-  // Determine the hash.
-  const EVP_MD *md = SSL_get_signature_algorithm_digest(signature_algorithm);
-  bssl::ScopedEVP_MD_CTX ctx;
-  EVP_PKEY_CTX *pctx;
-  if (!EVP_DigestSignInit(ctx.get(), &pctx, md, nullptr,
-                          test_state->private_key.get())) {
-    return ssl_private_key_failure;
-  }
-
-  // Configure additional signature parameters.
-  if (SSL_is_signature_algorithm_rsa_pss(signature_algorithm)) {
-    if (!EVP_PKEY_CTX_set_rsa_padding(pctx, RSA_PKCS1_PSS_PADDING) ||
-        !EVP_PKEY_CTX_set_rsa_pss_saltlen(pctx, -1 /* salt len = hash len */)) {
-      return ssl_private_key_failure;
-    }
-  }
-
-  // Write the signature into |test_state|.
-  size_t len = 0;
-  if (!EVP_DigestSign(ctx.get(), nullptr, &len, in, in_len)) {
-    return ssl_private_key_failure;
-  }
-  test_state->private_key_result.resize(len);
-  if (!EVP_DigestSign(ctx.get(), test_state->private_key_result.data(), &len,
-                      in, in_len)) {
-    return ssl_private_key_failure;
-  }
-  test_state->private_key_result.resize(len);
-
-  return AsyncPrivateKeyComplete(ssl, out, out_len, max_out);
-}
-
-static ssl_private_key_result_t AsyncPrivateKeyDecrypt(SSL *ssl, uint8_t *out,
-                                                       size_t *out_len,
-                                                       size_t max_out,
-                                                       const uint8_t *in,
-                                                       size_t in_len) {
-  TestState *test_state = GetTestState(ssl);
-  test_state->used_private_key = true;
-  if (!test_state->private_key_result.empty()) {
-    fprintf(stderr, "AsyncPrivateKeyDecrypt called with operation pending.\n");
-    abort();
-  }
-
-  RSA *rsa = EVP_PKEY_get0_RSA(test_state->private_key.get());
-  if (rsa == NULL) {
-    fprintf(stderr, "AsyncPrivateKeyDecrypt called with incorrect key type.\n");
-    abort();
-  }
-  test_state->private_key_result.resize(RSA_size(rsa));
-  if (!RSA_decrypt(rsa, out_len, test_state->private_key_result.data(),
-                   RSA_size(rsa), in, in_len, RSA_NO_PADDING)) {
-    return ssl_private_key_failure;
-  }
-
-  test_state->private_key_result.resize(*out_len);
-
-  return AsyncPrivateKeyComplete(ssl, out, out_len, max_out);
-}
-
-static ssl_private_key_result_t AsyncPrivateKeyComplete(SSL *ssl, uint8_t *out,
-                                                        size_t *out_len,
-                                                        size_t max_out) {
-  TestState *test_state = GetTestState(ssl);
-  if (test_state->private_key_result.empty()) {
-    fprintf(stderr,
-            "AsyncPrivateKeyComplete called without operation pending.\n");
-    abort();
-  }
-
-  if (GetTestConfig(ssl)->async && test_state->private_key_retries < 2) {
-    // Only return the decryption on the second attempt, to test both incomplete
-    // |sign|/|decrypt| and |complete|.
-    return ssl_private_key_retry;
-  }
-
-  if (max_out < test_state->private_key_result.size()) {
-    fprintf(stderr, "Output buffer too small.\n");
-    return ssl_private_key_failure;
-  }
-  OPENSSL_memcpy(out, test_state->private_key_result.data(),
-                 test_state->private_key_result.size());
-  *out_len = test_state->private_key_result.size();
-
-  test_state->private_key_result.clear();
-  test_state->private_key_retries = 0;
-  return ssl_private_key_success;
-}
-
-static const SSL_PRIVATE_KEY_METHOD g_async_private_key_method = {
-    AsyncPrivateKeySign,
-    AsyncPrivateKeyDecrypt,
-    AsyncPrivateKeyComplete,
-};
-
 static bool InstallCertificate(SSL *ssl) {
   bssl::UniquePtr<X509> x509;
   bssl::UniquePtr<STACK_OF(X509)> chain;
@@ -1988,42 +2252,6 @@
     }
   }
 
-  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.\n");
-      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.\n");
-      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.\n");
-      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;
-    }
-  }
-
   if (!quic_early_data_context.empty() &&
       !SSL_set_quic_early_data_context(
           ssl.get(),
diff --git a/ssl/test/test_config.h b/ssl/test/test_config.h
index 26a11bc..e4de861 100644
--- a/ssl/test/test_config.h
+++ b/ssl/test/test_config.h
@@ -24,6 +24,18 @@
 
 #include "test_state.h"
 
+enum class CredentialConfigType { kX509, kDelegated };
+
+struct CredentialConfig {
+  CredentialConfigType type;
+  std::string cert_file;
+  std::string key_file;
+  std::vector<uint16_t> signing_prefs;
+  std::string delegated_credential;
+  std::string ocsp_response;
+  std::string signed_cert_timestamps;
+};
+
 struct TestConfig {
   int port = 0;
   bool ipv6 = false;
@@ -190,8 +202,6 @@
   bool server_preference = false;
   bool export_traffic_secrets = false;
   bool key_update = false;
-  bool expect_delegated_credential_used = false;
-  std::string delegated_credential;
   std::string expect_early_data_reason;
   bool expect_hrr = false;
   bool expect_no_hrr = false;
@@ -202,6 +212,8 @@
   bool wpa_202304 = false;
   bool no_check_client_certificate_type = false;
   bool no_check_ecdsa_curve = false;
+  int expect_selected_credential = -1;
+  std::vector<CredentialConfig> credentials;
 
   std::vector<const char*> handshaker_args;
 
diff --git a/ssl/test/test_state.h b/ssl/test/test_state.h
index 4199d4a..14b4e05 100644
--- a/ssl/test/test_state.h
+++ b/ssl/test/test_state.h
@@ -68,6 +68,7 @@
   int explicit_renegotiates = 0;
   std::function<bool(const SSL_CLIENT_HELLO*)> get_handshake_hints_cb;
   int last_message_received = -1;
+  int selected_credential = -1;
 };
 
 bool SetTestState(SSL *ssl, std::unique_ptr<TestState> state);
diff --git a/ssl/tls13_both.cc b/ssl/tls13_both.cc
index 5ab5a1c..8058057 100644
--- a/ssl/tls13_both.cc
+++ b/ssl/tls13_both.cc
@@ -391,8 +391,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();
+  const SSL_CREDENTIAL *cred = hs->credential.get();
 
   ScopedCBB cbb;
   CBB *body, body_storage, certificate_list;
@@ -416,11 +415,12 @@
     return false;
   }
 
-  if (!ssl_has_certificate(hs)) {
+  if (hs->credential == nullptr) {
     return ssl_add_message_cbb(ssl, cbb.get());
   }
 
-  CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cert->chain.get(), 0);
+  assert(hs->credential->UsesX509());
+  CRYPTO_BUFFER *leaf_buf = sk_CRYPTO_BUFFER_value(cred->chain.get(), 0);
   CBB leaf, extensions;
   if (!CBB_add_u24_length_prefixed(&certificate_list, &leaf) ||
       !CBB_add_bytes(&leaf, CRYPTO_BUFFER_data(leaf_buf),
@@ -430,51 +430,49 @@
     return false;
   }
 
-  if (hs->scts_requested && cert->signed_cert_timestamp_list != nullptr) {
+  if (hs->scts_requested && cred->signed_cert_timestamp_list != nullptr) {
     CBB contents;
     if (!CBB_add_u16(&extensions, TLSEXT_TYPE_certificate_timestamp) ||
         !CBB_add_u16_length_prefixed(&extensions, &contents) ||
         !CBB_add_bytes(
             &contents,
-            CRYPTO_BUFFER_data(cert->signed_cert_timestamp_list.get()),
-            CRYPTO_BUFFER_len(cert->signed_cert_timestamp_list.get())) ||
+            CRYPTO_BUFFER_data(cred->signed_cert_timestamp_list.get()),
+            CRYPTO_BUFFER_len(cred->signed_cert_timestamp_list.get())) ||
         !CBB_flush(&extensions)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       return false;
     }
   }
 
-  if (hs->ocsp_stapling_requested && cert->ocsp_response != NULL) {
+  if (hs->ocsp_stapling_requested && cred->ocsp_response != NULL) {
     CBB contents, ocsp_response;
     if (!CBB_add_u16(&extensions, TLSEXT_TYPE_status_request) ||
         !CBB_add_u16_length_prefixed(&extensions, &contents) ||
         !CBB_add_u8(&contents, TLSEXT_STATUSTYPE_ocsp) ||
         !CBB_add_u24_length_prefixed(&contents, &ocsp_response) ||
         !CBB_add_bytes(&ocsp_response,
-                       CRYPTO_BUFFER_data(cert->ocsp_response.get()),
-                       CRYPTO_BUFFER_len(cert->ocsp_response.get())) ||
+                       CRYPTO_BUFFER_data(cred->ocsp_response.get()),
+                       CRYPTO_BUFFER_len(cred->ocsp_response.get())) ||
         !CBB_flush(&extensions)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       return false;
     }
   }
 
-  if (ssl_signing_with_dc(hs)) {
-    const CRYPTO_BUFFER *raw = dc->raw.get();
+  if (cred->type == SSLCredentialType::kDelegated) {
     CBB child;
     if (!CBB_add_u16(&extensions, TLSEXT_TYPE_delegated_credential) ||
         !CBB_add_u16_length_prefixed(&extensions, &child) ||
-        !CBB_add_bytes(&child, CRYPTO_BUFFER_data(raw),
-                       CRYPTO_BUFFER_len(raw)) ||
+        !CBB_add_bytes(&child, CRYPTO_BUFFER_data(cred->dc.get()),
+                       CRYPTO_BUFFER_len(cred->dc.get())) ||
         !CBB_flush(&extensions)) {
       OPENSSL_PUT_ERROR(SSL, ERR_R_INTERNAL_ERROR);
       return false;
     }
-    ssl->s3->delegated_credential_used = true;
   }
 
-  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);
+  for (size_t i = 1; i < sk_CRYPTO_BUFFER_num(cred->chain.get()); i++) {
+    CRYPTO_BUFFER *cert_buf = sk_CRYPTO_BUFFER_value(cred->chain.get(), i);
     CBB child;
     if (!CBB_add_u24_length_prefixed(&certificate_list, &child) ||
         !CBB_add_bytes(&child, CRYPTO_BUFFER_data(cert_buf),
@@ -556,7 +554,8 @@
 enum ssl_private_key_result_t tls13_add_certificate_verify(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
   uint16_t signature_algorithm;
-  if (!tls1_choose_signature_algorithm(hs, &signature_algorithm)) {
+  if (!tls1_choose_signature_algorithm(hs, hs->credential.get(),
+                                       &signature_algorithm)) {
     ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
     return ssl_private_key_failure;
   }
@@ -571,7 +570,7 @@
   }
 
   CBB child;
-  const size_t max_sig_len = EVP_PKEY_size(hs->local_pubkey.get());
+  const size_t max_sig_len = EVP_PKEY_size(hs->credential->pubkey.get());
   uint8_t *sig;
   size_t sig_len;
   if (!CBB_add_u16_length_prefixed(&body, &child) ||
diff --git a/ssl/tls13_client.cc b/ssl/tls13_client.cc
index 82ed7a8..cdb46c6 100644
--- a/ssl/tls13_client.cc
+++ b/ssl/tls13_client.cc
@@ -832,6 +832,17 @@
   return ssl_hs_ok;
 }
 
+static bool check_credential(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred) {
+  if (cred->type != SSLCredentialType::kX509) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_UNKNOWN_CERTIFICATE_TYPE);
+    return false;
+  }
+
+  // Check that we will be able to generate a signature.
+  uint16_t unused;
+  return tls1_choose_signature_algorithm(hs, cred, &unused);
+}
+
 static enum ssl_hs_wait_t do_send_client_certificate(SSL_HANDSHAKE *hs) {
   SSL *const ssl = hs->ssl;
 
@@ -859,8 +870,31 @@
     }
   }
 
-  if (!ssl_on_certificate_selected(hs) ||
-      !tls13_add_certificate(hs)) {
+  Array<SSL_CREDENTIAL *> creds;
+  if (!ssl_get_credential_list(hs, &creds)) {
+    return ssl_hs_error;
+  }
+
+  if (!creds.empty()) {
+    // Select the credential to use.
+    //
+    // TODO(davidben): In doing so, we pick the signature algorithm. Save that
+    // decision to avoid redoing it later.
+    for (SSL_CREDENTIAL *cred : creds) {
+      ERR_clear_error();
+      if (check_credential(hs, cred)) {
+        hs->credential = UpRef(cred);
+        break;
+      }
+    }
+    if (hs->credential == nullptr) {
+      // The error from the last attempt is in the error queue.
+      ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
+      return ssl_hs_error;
+    }
+  }
+
+  if (!tls13_add_certificate(hs)) {
     return ssl_hs_error;
   }
 
@@ -870,7 +904,7 @@
 
 static enum ssl_hs_wait_t do_send_client_certificate_verify(SSL_HANDSHAKE *hs) {
   // Don't send CertificateVerify if there is no certificate.
-  if (!ssl_has_certificate(hs)) {
+  if (hs->credential == nullptr) {
     hs->tls13_state = state_complete_second_flight;
     return ssl_hs_ok;
   }
diff --git a/ssl/tls13_server.cc b/ssl/tls13_server.cc
index 588d09d..ebe0cb4 100644
--- a/ssl/tls13_server.cc
+++ b/ssl/tls13_server.cc
@@ -17,6 +17,7 @@
 #include <assert.h>
 #include <string.h>
 
+#include <algorithm>
 #include <tuple>
 
 #include <openssl/aead.h>
@@ -206,6 +207,28 @@
   return true;
 }
 
+static bool check_credential(SSL_HANDSHAKE *hs, const SSL_CREDENTIAL *cred) {
+  switch (cred->type) {
+    case SSLCredentialType::kX509:
+      break;
+    case SSLCredentialType::kDelegated:
+      // Check that the peer supports the signature over the delegated
+      // credential.
+      if (std::find(hs->peer_sigalgs.begin(), hs->peer_sigalgs.end(),
+                    cred->dc_algorithm) == hs->peer_sigalgs.end()) {
+        OPENSSL_PUT_ERROR(SSL, SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS);
+        return false;
+      }
+      break;
+  }
+
+  // Check that we will be able to generate a signature. If |cred| is a
+  // delegated credential, this also checks that the peer supports delegated
+  // credentials and matched |dc_cert_verify_algorithm|.
+  uint16_t unused;
+  return tls1_choose_signature_algorithm(hs, cred, &unused);
+}
+
 static enum ssl_hs_wait_t do_select_parameters(SSL_HANDSHAKE *hs) {
   // At this point, most ClientHello extensions have already been processed by
   // the common handshake logic. Resolve the remaining non-PSK parameters.
@@ -225,6 +248,33 @@
                  client_hello.session_id_len);
   hs->session_id_len = client_hello.session_id_len;
 
+  Array<SSL_CREDENTIAL *> creds;
+  if (!ssl_get_credential_list(hs, &creds)) {
+    return ssl_hs_error;
+  }
+  if (creds.empty()) {
+    OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_INTERNAL_ERROR);
+    return ssl_hs_error;
+  }
+
+  // Select the credential to use.
+  //
+  // TODO(davidben): In doing so, we pick the signature algorithm. Save that
+  // decision to avoid redoing it later.
+  for (SSL_CREDENTIAL *cred : creds) {
+    ERR_clear_error();
+    if (check_credential(hs, cred)) {
+      hs->credential = UpRef(cred);
+      break;
+    }
+  }
+  if (hs->credential == nullptr) {
+    // The error from the last attempt is in the error queue.
+    ssl_send_alert(ssl, SSL3_AL_FATAL, SSL_AD_HANDSHAKE_FAILURE);
+    return ssl_hs_error;
+  }
+
   // Negotiate the cipher suite.
   hs->new_cipher = choose_tls13_cipher(ssl, &client_hello);
   if (hs->new_cipher == NULL) {
@@ -851,11 +901,6 @@
 
   // Send the server Certificate message, if necessary.
   if (!ssl->s3->session_reused) {
-    if (!ssl_has_certificate(hs)) {
-      OPENSSL_PUT_ERROR(SSL, SSL_R_NO_CERTIFICATE_SET);
-      return ssl_hs_error;
-    }
-
     if (!tls13_add_certificate(hs)) {
       return ssl_hs_error;
     }