Add ChainContainsIssuer to SSL_CREDENTIAL This walks the chain of an X.509 credential to tell you if the chain contains a certifiate whose issuer is a byte for byte match for a DER encoded DN. Change-Id: I913f4786b922563dfae48ca8631281558396863c Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/71607 Commit-Queue: Bob Beck <bbe@google.com> Reviewed-by: Adam Langley <agl@google.com>
diff --git a/ssl/internal.h b/ssl/internal.h index 0fa35bd..fac6260 100644 --- a/ssl/internal.h +++ b/ssl/internal.h
@@ -1605,6 +1605,16 @@ OPENSSL_EXPORT bool ssl_cert_check_key_usage(const CBS *in, enum ssl_key_usage_t bit); +// ssl_cert_extract_issuer parses the DER-encoded, X.509 certificate in |in| +// and extracts the issuer. On success it returns true and the DER encoded +// issuer is in |out_dn|, otherwise it returns false. +OPENSSL_EXPORT bool ssl_cert_extract_issuer(const CBS *in, CBS *out_dn); + +// ssl_cert_matches_issuer parses the DER-encoded, X.509 certificate in |in| +// and returns true if its issuer is an exact match for the DER encoded +// distinguished name in |dn| +bool ssl_cert_matches_issuer(const CBS *in, const CBS *dn); + // ssl_cert_parse_pubkey extracts the public key from the DER-encoded, X.509 // certificate in |in|. It returns an allocated |EVP_PKEY| or else returns // nullptr and pushes to the error queue. @@ -1891,6 +1901,10 @@ // returns one on success and zero on error. bool AppendIntermediateCert(bssl::UniquePtr<CRYPTO_BUFFER> cert); + // ChainContainsIssuer returns true if |dn| is a byte for byte match with the + // issuer of any certificate in |chain|, false otherwise. + bool ChainContainsIssuer(bssl::Span<const uint8_t> dn) const; + // type is the credential type and determines which other fields apply. bssl::SSLCredentialType type;
diff --git a/ssl/ssl_cert.cc b/ssl/ssl_cert.cc index bd0143e..b6d2cb7 100644 --- a/ssl/ssl_cert.cc +++ b/ssl/ssl_cert.cc
@@ -325,6 +325,38 @@ return true; } +bool ssl_cert_extract_issuer(const CBS *in, CBS *out_dn) { + CBS buf = *in; + + CBS toplevel; + CBS cert; + if (!CBS_get_asn1(&buf, &toplevel, CBS_ASN1_SEQUENCE) || // + CBS_len(&buf) != 0 || // + !CBS_get_asn1(&toplevel, &cert, CBS_ASN1_SEQUENCE) || // + // version + !CBS_get_optional_asn1( + &cert, NULL, NULL, + CBS_ASN1_CONSTRUCTED | CBS_ASN1_CONTEXT_SPECIFIC | 0) || + // serialNumber + !CBS_get_asn1(&cert, NULL, CBS_ASN1_INTEGER) || + // signature algorithm + !CBS_get_asn1(&cert, NULL, CBS_ASN1_SEQUENCE) || + // issuer + !CBS_get_asn1_element(&cert, out_dn, CBS_ASN1_SEQUENCE)) { + return false; + } + return true; +} + +bool ssl_cert_matches_issuer(const CBS *in, const CBS *dn) { + CBS issuer; + + if (!ssl_cert_extract_issuer(in, &issuer)) { + return false; + } + return CBS_mem_equal(&issuer, CBS_data(dn), CBS_len(dn)); +} + UniquePtr<EVP_PKEY> ssl_cert_parse_pubkey(const CBS *in) { CBS buf = *in, tbs_cert; if (!ssl_cert_skip_to_spki(&buf, &tbs_cert)) {
diff --git a/ssl/ssl_credential.cc b/ssl/ssl_credential.cc index 39c14de..357d8f8 100644 --- a/ssl/ssl_credential.cc +++ b/ssl/ssl_credential.cc
@@ -213,6 +213,28 @@ } } +bool ssl_credential_st::ChainContainsIssuer( + bssl::Span<const uint8_t> dn) const { + if (UsesX509()) { + // TODO(bbe) This is used for matching a chain by CA name for the CA extension. + // If we require a chain to be present, we could remove any remaining parts + // of the chain after the found issuer, on the assumption that the peer + // sending the CA extension has the issuer in their trust store and does not + // need us to waste bytes on the wire. + CBS dn_cbs; + CBS_init(&dn_cbs, dn.data(), dn.size()); + for (size_t i = 0; i < sk_CRYPTO_BUFFER_num(chain.get()); i++) { + const CRYPTO_BUFFER *cert = sk_CRYPTO_BUFFER_value(chain.get(), i); + CBS cert_cbs; + CRYPTO_BUFFER_init_CBS(cert, &cert_cbs); + if (ssl_cert_matches_issuer(&cert_cbs, &dn_cbs)) { + return true; + } + } + } + return false; +} + bool ssl_credential_st::AppendIntermediateCert(UniquePtr<CRYPTO_BUFFER> cert) { if (!UsesX509()) { OPENSSL_PUT_ERROR(SSL, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED);
diff --git a/ssl/ssl_test.cc b/ssl/ssl_test.cc index 4d74bfa..0056779 100644 --- a/ssl/ssl_test.cc +++ b/ssl/ssl_test.cc
@@ -1824,6 +1824,22 @@ return BufferFromPEM(kCertPEM); } +static bssl::UniquePtr<CRYPTO_BUFFER> GetChainTestIntermediateIssuerBuffer() { + static const char kSubjectPEM[] = + "-----BEGIN SUBJECT-----\n" + "MBQxEjAQBgNVBAMMCUMgUm9vdCBDQQ==\n" + "-----END SUBJECT-----\n"; + return BufferFromPEM(kSubjectPEM); +} + +static bssl::UniquePtr<CRYPTO_BUFFER> GetChainTestUnmatchingIssuerBuffer() { + static const char kSubjectPEM[] = + "-----BEGIN SUBJECT-----\n" + "MBYxFDASBgNVBAMMC0RpZ2lOb3RBRm94\n" + "-----END SUBJECT-----\n"; + return BufferFromPEM(kSubjectPEM); +} + static bssl::UniquePtr<X509> GetChainTestIntermediate() { return X509FromBuffer(GetChainTestIntermediateBuffer()); } @@ -5159,13 +5175,19 @@ BuffersEqual(SSL_get0_peer_certificates(client.get()), {leaf2.get()})); } -TEST(SSLTest, OverrideCredentialChain) { +TEST(SSLTest, CredentialChains) { bssl::UniquePtr<EVP_PKEY> key = GetChainTestKey(); ASSERT_TRUE(key); bssl::UniquePtr<CRYPTO_BUFFER> leaf = GetChainTestCertificateBuffer(); ASSERT_TRUE(leaf); bssl::UniquePtr<CRYPTO_BUFFER> ca = GetChainTestIntermediateBuffer(); ASSERT_TRUE(ca); + bssl::UniquePtr<CRYPTO_BUFFER> ca_subject = + GetChainTestIntermediateIssuerBuffer(); + ASSERT_TRUE(ca_subject); + bssl::UniquePtr<CRYPTO_BUFFER> bogus_subject = + GetChainTestUnmatchingIssuerBuffer(); + ASSERT_TRUE(bogus_subject); std::vector<CRYPTO_BUFFER *> chain = {leaf.get(), ca.get()}; std::vector<CRYPTO_BUFFER *> wrong_chain = {leaf.get(), leaf.get(), @@ -5179,9 +5201,30 @@ // Configure one chain (including the leaf), then replace it with another. ASSERT_TRUE(SSL_CREDENTIAL_set1_cert_chain(cred.get(), wrong_chain.data(), wrong_chain.size())); + CBS ca_subject_cbs, ca_cbs; + CRYPTO_BUFFER_init_CBS(ca.get(), &ca_cbs); + ASSERT_TRUE(ssl_cert_extract_issuer(&ca_cbs, &ca_subject_cbs)); + bssl::UniquePtr<CRYPTO_BUFFER> subject_buf( + CRYPTO_BUFFER_new_from_CBS(&ca_subject_cbs, nullptr)); + EXPECT_EQ(Bytes(CRYPTO_BUFFER_data(ca_subject.get()), + CRYPTO_BUFFER_len(ca_subject.get())), + Bytes(CRYPTO_BUFFER_data(subject_buf.get()), + CRYPTO_BUFFER_len(subject_buf.get()))); +#if !defined(BORINGSSL_SHARED_LIBRARY) + ASSERT_FALSE(cred->ChainContainsIssuer( + MakeConstSpan(CRYPTO_BUFFER_data(subject_buf.get()), + CRYPTO_BUFFER_len(subject_buf.get())))); +#endif + ASSERT_TRUE( SSL_CREDENTIAL_set1_cert_chain(cred.get(), chain.data(), chain.size())); +#if !defined(BORINGSSL_SHARED_LIBRARY) + ASSERT_TRUE(cred->ChainContainsIssuer( + MakeConstSpan(CRYPTO_BUFFER_data(subject_buf.get()), + CRYPTO_BUFFER_len(subject_buf.get())))); +#endif + ASSERT_TRUE(SSL_CREDENTIAL_set1_private_key(cred.get(), key.get())); ASSERT_TRUE(SSL_CTX_add1_credential(ctx.get(), cred.get()));