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