Disallow wildcards from matching top-level registry controlled domains during cert validation.

This only disallows wildcards for "ICANN" TLDs/registry controlled domains, and
excludes domains in the "private" registry (such as appspot.com or
s3.amazonaws.com)

BUG=100442
TEST=net_unittests:X509CertificateNameVerifyTest.*, as well as visiting sites
such as https://www.appspot.com continues to work without issue.

Review URL: https://chromiumcodereview.appspot.com/14741019

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@200771 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/net/cert/x509_certificate.cc b/net/cert/x509_certificate.cc
index 1b431ce..d583876c 100644
--- a/net/cert/x509_certificate.cc
+++ b/net/cert/x509_certificate.cc
@@ -24,6 +24,7 @@
 #include "base/time.h"
 #include "googleurl/src/url_canon.h"
 #include "net/base/net_util.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
 #include "net/cert/pem_tokenizer.h"
 
 namespace net {
@@ -552,10 +553,35 @@
   bool allow_wildcards = false;
   if (!reference_domain.empty()) {
     DCHECK(reference_domain.starts_with("."));
-    // We required at least 3 components (i.e. 2 dots) as a basic protection
-    // against too-broad wild-carding.
-    // Also we don't attempt wildcard matching on a purely numerical hostname.
-    allow_wildcards = reference_domain.rfind('.') != 0 &&
+
+    // Do not allow wildcards for public/ICANN registry controlled domains -
+    // that is, prevent *.com or *.co.uk as valid presented names, but do not
+    // prevent *.appspot.com (a private registry controlled domain).
+    // In addition, unknown top-level domains (such as 'intranet' domains or
+    // new TLDs/gTLDs not yet added to the registry controlled domain dataset)
+    // are also implicitly prevented.
+    // Because |reference_domain| must contain at least one name component that
+    // is not registry controlled, this ensures that all reference domains
+    // contain at least three domain components when using wildcards.
+    size_t registry_length =
+        registry_controlled_domains::GetRegistryLength(
+            reference_name,
+            registry_controlled_domains::INCLUDE_UNKNOWN_REGISTRIES,
+            registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES);
+
+    // Because |reference_name| was already canonicalized, the following
+    // should never happen.
+    CHECK_NE(std::string::npos, registry_length);
+
+    // Account for the leading dot in |reference_domain|.
+    bool is_registry_controlled =
+        registry_length != 0 &&
+        registry_length == (reference_domain.size() - 1);
+
+    // Additionally, do not attempt wildcard matching for purely numeric
+    // hostnames.
+    allow_wildcards =
+        !is_registry_controlled &&
         reference_name.find_first_not_of("0123456789.") != std::string::npos;
   }
 
@@ -622,13 +648,11 @@
   return false;
 }
 
-#if !defined(USE_NSS)
 bool X509Certificate::VerifyNameMatch(const std::string& hostname) const {
   std::vector<std::string> dns_names, ip_addrs;
   GetSubjectAltName(&dns_names, &ip_addrs);
   return VerifyHostname(hostname, subject_.common_name, dns_names, ip_addrs);
 }
-#endif
 
 // static
 bool X509Certificate::GetPEMEncoded(OSCertHandle cert_handle,
diff --git a/net/cert/x509_certificate_nss.cc b/net/cert/x509_certificate_nss.cc
index f6fdd94..ea2c350 100644
--- a/net/cert/x509_certificate_nss.cc
+++ b/net/cert/x509_certificate_nss.cc
@@ -152,10 +152,6 @@
   x509_util::GetSubjectAltName(cert_handle_, dns_names, ip_addrs);
 }
 
-bool X509Certificate::VerifyNameMatch(const std::string& hostname) const {
-  return CERT_VerifyCertName(cert_handle_, hostname.c_str()) == SECSuccess;
-}
-
 bool X509Certificate::IsIssuedByEncoded(
     const std::vector<std::string>& valid_issuers) {
   // Get certificate chain as scoped list of CERTCertificate objects.
diff --git a/net/cert/x509_certificate_unittest.cc b/net/cert/x509_certificate_unittest.cc
index 6e10439..735de89 100644
--- a/net/cert/x509_certificate_unittest.cc
+++ b/net/cert/x509_certificate_unittest.cc
@@ -1023,7 +1023,6 @@
                                             "xn--poema-*.com.br,"
                                             "xn--*-9qae5a.com.br,"
                                             "*--poema-9qae5a.com.br" },
-    { true, "xn--poema-9qae5a.com.br", "*.com.br" },
     // The following are adapted from the  examples quoted from
     // http://tools.ietf.org/html/rfc6125#section-6.4.3
     //  (e.g., *.example.com would match foo.example.com but
@@ -1037,12 +1036,25 @@
     { true, "baz1.example.net", "baz*.example.net" },
     { true, "foobaz.example.net", "*baz.example.net" },
     { true, "buzz.example.net", "b*z.example.net" },
-    // Wildcards should not be valid unless there are at least three name
-    // components.
-    { true,  "h.co.uk", "*.co.uk" },
+    // Wildcards should not be valid for public registry controlled domains,
+    // and unknown/unrecognized domains, at least three domain components must
+    // be present.
+    { true, "www.test.example", "*.test.example" },
+    { true, "test.example.co.uk", "*.example.co.uk" },
+    { false, "test.example", "*.exmaple" },
+    { false, "example.co.uk", "*.co.uk" },
     { false, "foo.com", "*.com" },
     { false, "foo.us", "*.us" },
     { false, "foo", "*" },
+    // IDN variants of wildcards and registry controlled domains.
+    { true, "www.xn--poema-9qae5a.com.br", "*.xn--poema-9qae5a.com.br" },
+    { true, "test.example.xn--mgbaam7a8h", "*.example.xn--mgbaam7a8h" },
+    { false, "xn--poema-9qae5a.com.br", "*.com.br" },
+    { false, "example.xn--mgbaam7a8h", "*.xn--mgbaam7a8h" },
+    // Wildcards should be permissible for 'private' registry controlled
+    // domains.
+    { true, "www.appspot.com", "*.appspot.com" },
+    { true, "foo.s3.amazonaws.com", "*.s3.amazonaws.com" },
     // Multiple wildcards are not valid.
     { false, "foo.example.com", "*.*.com" },
     { false, "foo.bar.example.com", "*.bar.*.com" },
@@ -1063,6 +1075,9 @@
     { false, "example.com.", "*.com" },
     { false, "example.com.", "*.com." },
     { false, "foo.", "*." },
+    { false, "foo", "*." },
+    { false, "foo.co.uk", "*.co.uk." },
+    { false, "foo.co.uk.", "*.co.uk." },
     // IP addresses in common name; IPv4 only.
     { true, "127.0.0.1", "127.0.0.1" },
     { true, "192.168.1.1", "192.168.1.1" },