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