Rewrite X.509 name-matching tests.

This replaces v3name_test.cc which is rather difficult to follow.
v3name_test.cc ran all pairs of names against each other, used a
default case-insensitivity rule, and then had a list of string
exceptions to that rule. This is hopefully easier for us to adjust
later. It also fixes a testing bug we wouldn't notice if an expected
"exception" didn't fire.

Sadly, we cannot use designated initializers in C++ yet. MSVC does not
support them until MSVC 2019.

Change-Id: Ia8e3bf5f57d33a9bf1fc929ba1e8cd2a270a8a24
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/50725
Reviewed-by: Adam Langley <agl@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/crypto/x509/x509_test.cc b/crypto/x509/x509_test.cc
index 77c383a..01f0bc7 100644
--- a/crypto/x509/x509_test.cc
+++ b/crypto/x509/x509_test.cc
@@ -3533,3 +3533,324 @@
   // Padding bits in BIT STRINGs must be zero in BER.
   EXPECT_FALSE(CertFromPEM(kNonZeroPadding));
 }
+
+TEST(X509Test, Names) {
+  bssl::UniquePtr<EVP_PKEY> key = PrivateKeyFromPEM(kP256Key);
+  ASSERT_TRUE(key);
+  bssl::UniquePtr<X509> root =
+      MakeTestCert("Root", "Root", key.get(), /*is_ca=*/true);
+  ASSERT_TRUE(root);
+  ASSERT_TRUE(X509_sign(root.get(), key.get(), EVP_sha256()));
+
+  struct {
+    std::vector<std::pair<int, std::string>> cert_subject;
+    std::vector<std::string> cert_dns_names;
+    std::vector<std::string> cert_emails;
+    std::vector<std::string> valid_dns_names;
+    std::vector<std::string> invalid_dns_names;
+    std::vector<std::string> valid_emails;
+    std::vector<std::string> invalid_emails;
+    unsigned flags;
+  } kTests[] = {
+      // DNS names only match DNS names and do so case-insensitively.
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/{"example.com", "WWW.EXAMPLE.COM"},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/
+          {"example.com", "EXAMPLE.COM", "www.example.com", "WWW.EXAMPLE.COM"},
+          /*invalid_dns_names=*/{"test.example.com", "example.org"},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{"test@example.com", "example.com"},
+          /*flags=*/0,
+      },
+
+      // DNS wildcards match exactly one component.
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/{"*.example.com", "*.EXAMPLE.ORG"},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/
+          {"www.example.com", "WWW.EXAMPLE.COM", "www.example.org",
+           "WWW.EXAMPLE.ORG"},
+          /*invalid_dns_names=*/{"example.com", "test.www.example.com"},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{"test@example.com", "www.example.com"},
+          /*flags=*/0,
+      },
+
+      // DNS wildcards can be disabled.
+      // TODO(davidben): Can we remove this feature? Does anyone use it?
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/{"example.com", "*.example.com"},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/{"example.com"},
+          /*invalid_dns_names=*/{"www.example.com"},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{},
+          /*flags=*/X509_CHECK_FLAG_NO_WILDCARDS,
+      },
+
+      // Invalid DNS wildcards do not match.
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/
+          {"a.*", "**.b.example", "*c.example", "d*.example", "e*e.example",
+           "*", ".", "..", "*."},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/{},
+          /*invalid_dns_names=*/
+          {"a.example", "test.b.example", "cc.example", "dd.example",
+           "eee.example", "f", "g."},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{},
+          /*flags=*/0,
+      },
+
+      // IDNs match like any other DNS labels.
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/
+          {"xn--rger-koa.a.example", "*.xn--rger-koa.b.example",
+           "www.xn--rger-koa.c.example"},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/
+          {"xn--rger-koa.a.example", "www.xn--rger-koa.b.example",
+           "www.xn--rger-koa.c.example"},
+          /*invalid_dns_names=*/
+          {"www.xn--rger-koa.a.example", "xn--rger-koa.b.example",
+           "www.xn--rger-koa.d.example"},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{},
+          /*flags=*/0,
+      },
+
+      // For now, DNS names are also extracted out of the common name, but only
+      // there is no SAN list.
+      // TODO(https://crbug.com/boringssl/464): Remove this.
+      {
+          /*cert_subject=*/{{NID_commonName, "a.example"},
+                            {NID_commonName, "*.b.example"}},
+          /*cert_dns_names=*/{},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/
+          {"a.example", "A.EXAMPLE", "test.b.example", "TEST.B.EXAMPLE"},
+          /*invalid_dns_names=*/{},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{},
+          /*flags=*/0,
+      },
+      {
+          /*cert_subject=*/{{NID_commonName, "a.example"},
+                            {NID_commonName, "*.b.example"}},
+          /*cert_dns_names=*/{"example.com"},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/{},
+          /*invalid_dns_names=*/
+          {"a.example", "A.EXAMPLE", "test.b.example", "TEST.B.EXAMPLE"},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{},
+          /*flags=*/0,
+      },
+
+      // Other subject RDNs do not provide DNS names.
+      {
+          /*cert_subject=*/{{NID_organizationName, "example.com"}},
+          /*cert_dns_names=*/{},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/{},
+          /*invalid_dns_names=*/{"example.com"},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{},
+          /*flags=*/0,
+      },
+
+      // Input DNS names cannot have wildcards.
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/{"www.example.com"},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/{},
+          /*invalid_dns_names=*/{"*.example.com"},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{},
+          /*flags=*/0,
+      },
+
+      // However, OpenSSL has some non-standard behavior that implements this
+      // with a different syntax.
+      // TODO(https://crbug.com/boringssl/463): Remove this.
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/{"www.a.example", "*.b.test"},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/{".a.example", ".b.test", ".example", ".test"},
+          /*invalid_dns_names=*/{".www.a.example", ".www.b.test"},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/{},
+          /*flags=*/0,
+      },
+
+      // Emails match case-sensitively before the '@' and case-insensitively
+      // after. They do not match DNS names.
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/{},
+          /*cert_emails=*/{"test@a.example", "TEST@B.EXAMPLE"},
+          /*valid_dns_names=*/{},
+          /*invalid_dns_names=*/{"a.example", "b.example"},
+          /*valid_emails=*/
+          {"test@a.example", "test@A.EXAMPLE", "TEST@b.example",
+           "TEST@B.EXAMPLE"},
+          /*invalid_emails=*/
+          {"TEST@a.example", "test@B.EXAMPLE", "another-test@a.example",
+           "est@a.example"},
+          /*flags=*/0,
+      },
+
+      // Emails may also be found in the subject.
+      {
+          /*cert_subject=*/{{NID_pkcs9_emailAddress, "test@a.example"},
+                            {NID_pkcs9_emailAddress, "TEST@B.EXAMPLE"}},
+          /*cert_dns_names=*/{},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/{},
+          /*invalid_dns_names=*/{"a.example", "b.example"},
+          /*valid_emails=*/
+          {"test@a.example", "test@A.EXAMPLE", "TEST@b.example",
+           "TEST@B.EXAMPLE"},
+          /*invalid_emails=*/
+          {"TEST@a.example", "test@B.EXAMPLE", "another-test@a.example",
+           "est@a.example"},
+          /*flags=*/0,
+      },
+
+      // There are no email wildcard names.
+      {
+          /*cert_subject=*/{},
+          /*cert_dns_names=*/{},
+          /*cert_emails=*/{"test@*.a.example", "@b.example", "*@c.example"},
+          /*valid_dns_names=*/{},
+          /*invalid_dns_names=*/{},
+          /*valid_emails=*/{},
+          /*invalid_emails=*/
+          {"test@test.a.example", "test@b.example", "test@c.example"},
+          /*flags=*/0,
+      },
+
+      // Unrelated RDNs can be skipped when looking in the subject.
+      {
+          /*cert_subject=*/{{NID_organizationName, "Acme Corporation"},
+                            {NID_commonName, "a.example"},
+                            {NID_pkcs9_emailAddress, "test@b.example"},
+                            {NID_countryName, "US"}},
+          /*cert_dns_names=*/{},
+          /*cert_emails=*/{},
+          /*valid_dns_names=*/{"a.example"},
+          /*invalid_dns_names=*/{},
+          /*valid_emails=*/{"test@b.example"},
+          /*invalid_emails=*/{},
+          /*flags=*/0,
+      },
+  };
+
+  size_t i = 0;
+  for (const auto &t : kTests) {
+    SCOPED_TRACE(i++);
+
+    // Issue a test certificate.
+    bssl::UniquePtr<X509> cert =
+        MakeTestCert("Root", "Leaf", key.get(), /*is_ca=*/false);
+    ASSERT_TRUE(cert);
+    if (!t.cert_subject.empty()) {
+      bssl::UniquePtr<X509_NAME> subject(X509_NAME_new());
+      ASSERT_TRUE(subject);
+      for (const auto &entry : t.cert_subject) {
+        ASSERT_TRUE(X509_NAME_add_entry_by_NID(
+            subject.get(), entry.first, MBSTRING_ASC,
+            reinterpret_cast<const unsigned char *>(entry.second.data()),
+            entry.second.size(), /*loc=*/-1, /*set=*/0));
+      }
+      ASSERT_TRUE(X509_set_subject_name(cert.get(), subject.get()));
+    }
+    bssl::UniquePtr<GENERAL_NAMES> sans(sk_GENERAL_NAME_new_null());
+    ASSERT_TRUE(sans);
+    for (const auto &dns : t.cert_dns_names) {
+      bssl::UniquePtr<GENERAL_NAME> name(GENERAL_NAME_new());
+      ASSERT_TRUE(name);
+      name->type = GEN_DNS;
+      name->d.dNSName = ASN1_IA5STRING_new();
+      ASSERT_TRUE(name->d.dNSName);
+      ASSERT_TRUE(ASN1_STRING_set(name->d.dNSName, dns.data(), dns.size()));
+      ASSERT_TRUE(bssl::PushToStack(sans.get(), std::move(name)));
+    }
+    for (const auto &email : t.cert_emails) {
+      bssl::UniquePtr<GENERAL_NAME> name(GENERAL_NAME_new());
+      ASSERT_TRUE(name);
+      name->type = GEN_EMAIL;
+      name->d.rfc822Name = ASN1_IA5STRING_new();
+      ASSERT_TRUE(name->d.rfc822Name);
+      ASSERT_TRUE(
+          ASN1_STRING_set(name->d.rfc822Name, email.data(), email.size()));
+      ASSERT_TRUE(bssl::PushToStack(sans.get(), std::move(name)));
+    }
+    if (sk_GENERAL_NAME_num(sans.get()) != 0) {
+      ASSERT_TRUE(X509_add1_ext_i2d(cert.get(), NID_subject_alt_name,
+                                    sans.get(), /*crit=*/0, /*flags=*/0));
+    }
+    ASSERT_TRUE(X509_sign(cert.get(), key.get(), EVP_sha256()));
+
+    for (const auto &dns : t.valid_dns_names) {
+      SCOPED_TRACE(dns);
+      EXPECT_EQ(1, X509_check_host(cert.get(), dns.data(), dns.size(), t.flags,
+                                   /*peername=*/nullptr));
+      EXPECT_EQ(X509_V_OK,
+                Verify(cert.get(), {root.get()}, /*intermediates=*/{},
+                       /*crls=*/{}, /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+                         ASSERT_TRUE(X509_VERIFY_PARAM_set1_host(
+                             param, dns.data(), dns.size()));
+                         X509_VERIFY_PARAM_set_hostflags(param, t.flags);
+                       }));
+    }
+
+    for (const auto &dns : t.invalid_dns_names) {
+      SCOPED_TRACE(dns);
+      EXPECT_EQ(0, X509_check_host(cert.get(), dns.data(), dns.size(), t.flags,
+                                   /*peername=*/nullptr));
+      EXPECT_EQ(X509_V_ERR_HOSTNAME_MISMATCH,
+                Verify(cert.get(), {root.get()}, /*intermediates=*/{},
+                       /*crls=*/{}, /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+                         ASSERT_TRUE(X509_VERIFY_PARAM_set1_host(
+                             param, dns.data(), dns.size()));
+                         X509_VERIFY_PARAM_set_hostflags(param, t.flags);
+                       }));
+    }
+
+    for (const auto &email : t.valid_emails) {
+      SCOPED_TRACE(email);
+      EXPECT_EQ(
+          1, X509_check_email(cert.get(), email.data(), email.size(), t.flags));
+      EXPECT_EQ(X509_V_OK,
+                Verify(cert.get(), {root.get()}, /*intermediates=*/{},
+                       /*crls=*/{}, /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+                         ASSERT_TRUE(X509_VERIFY_PARAM_set1_email(
+                             param, email.data(), email.size()));
+                         X509_VERIFY_PARAM_set_hostflags(param, t.flags);
+                       }));
+    }
+
+    for (const auto &email : t.invalid_emails) {
+      SCOPED_TRACE(email);
+      EXPECT_EQ(
+          0, X509_check_email(cert.get(), email.data(), email.size(), t.flags));
+      EXPECT_EQ(X509_V_ERR_EMAIL_MISMATCH,
+                Verify(cert.get(), {root.get()}, /*intermediates=*/{},
+                       /*crls=*/{}, /*flags=*/0, [&](X509_VERIFY_PARAM *param) {
+                         ASSERT_TRUE(X509_VERIFY_PARAM_set1_email(
+                             param, email.data(), email.size()));
+                         X509_VERIFY_PARAM_set_hostflags(param, t.flags);
+                       }));
+    }
+  }
+}