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()));