Update SSL error handling code to account for Subject CN deprecation

In Issue 308330, Chrome deprecated the use of the Subject CN field in
certificate hostname validation. However, the certificate error
interstitial and error classification logic were left unchanged,
leading to misleading error messages and doomed error recovery attempts
in the event that a certificate lacked SubjectAltNames.

In this change, Chrome's Certificate Error interstitial and error
recovery will no longer fallback to the certificate's Subject CN field
when evaluating the certificate's valid dns names.

BUG=703614

Review-Url: https://codereview.chromium.org/2777383002
Cr-Commit-Position: refs/heads/master@{#462230}
(cherry picked from commit c7484f52b8ceb68e4334cad505e894aeef6cba83)

Review-Url: https://codereview.chromium.org/2804883005 .
Cr-Commit-Position: refs/branch-heads/3029@{#612}
Cr-Branched-From: 939b32ee5ba05c396eef3fd992822fcca9a2e262-refs/heads/master@{#454471}
diff --git a/chrome/browser/ssl/ssl_error_handler.cc b/chrome/browser/ssl/ssl_error_handler.cc
index 22c1ba6..4d0674d 100644
--- a/chrome/browser/ssl/ssl_error_handler.cc
+++ b/chrome/browser/ssl/ssl_error_handler.cc
@@ -569,37 +569,38 @@
   }
 #endif
 
-  std::vector<std::string> dns_names;
-  ssl_info_.cert->GetDNSNames(&dns_names);
-  DCHECK(!dns_names.empty());
-  GURL suggested_url;
   if (IsSSLCommonNameMismatchHandlingEnabled() &&
       cert_error_ == net::ERR_CERT_COMMON_NAME_INVALID &&
-      delegate_->IsErrorOverridable() &&
-      delegate_->GetSuggestedUrl(dns_names, &suggested_url)) {
-    RecordUMA(WWW_MISMATCH_FOUND);
+      delegate_->IsErrorOverridable()) {
+    std::vector<std::string> dns_names;
+    ssl_info_.cert->GetSubjectAltName(&dns_names, nullptr);
+    GURL suggested_url;
+    if (!dns_names.empty() &&
+        delegate_->GetSuggestedUrl(dns_names, &suggested_url)) {
+      RecordUMA(WWW_MISMATCH_FOUND_IN_SAN);
 
-    // Show the SSL interstitial if |CERT_STATUS_COMMON_NAME_INVALID| is not
-    // the only error. Need not check for captive portal in this case.
-    // (See the comment below).
-    if (!only_error_is_name_mismatch) {
-      ShowSSLInterstitial();
+      // Show the SSL interstitial if |CERT_STATUS_COMMON_NAME_INVALID| is not
+      // the only error. Need not check for captive portal in this case.
+      // (See the comment below).
+      if (!only_error_is_name_mismatch) {
+        ShowSSLInterstitial();
+        return;
+      }
+      delegate_->CheckSuggestedUrl(
+          suggested_url,
+          base::Bind(&SSLErrorHandler::CommonNameMismatchHandlerCallback,
+                     weak_ptr_factory_.GetWeakPtr()));
+      timer_.Start(FROM_HERE, g_config.Pointer()->interstitial_delay(), this,
+                   &SSLErrorHandler::ShowSSLInterstitial);
+
+      if (g_config.Pointer()->timer_started_callback())
+        g_config.Pointer()->timer_started_callback()->Run(web_contents_);
+
+      // Do not check for a captive portal in this case, because a captive
+      // portal most likely cannot serve a valid certificate which passes the
+      // similarity check.
       return;
     }
-    delegate_->CheckSuggestedUrl(
-        suggested_url,
-        base::Bind(&SSLErrorHandler::CommonNameMismatchHandlerCallback,
-                   weak_ptr_factory_.GetWeakPtr()));
-    timer_.Start(FROM_HERE, g_config.Pointer()->interstitial_delay(), this,
-                 &SSLErrorHandler::ShowSSLInterstitial);
-
-    if (g_config.Pointer()->timer_started_callback())
-      g_config.Pointer()->timer_started_callback()->Run(web_contents_);
-
-    // Do not check for a captive portal in this case, because a captive
-    // portal most likely cannot serve a valid certificate which passes the
-    // similarity check.
-    return;
   }
 
   // Always listen to captive portal notifications, otherwise build fails
diff --git a/chrome/browser/ssl/ssl_error_handler.h b/chrome/browser/ssl/ssl_error_handler.h
index f0dc06b..319eb61 100644
--- a/chrome/browser/ssl/ssl_error_handler.h
+++ b/chrome/browser/ssl/ssl_error_handler.h
@@ -66,15 +66,16 @@
   // Public for testing.
   enum UMAEvent {
     HANDLE_ALL = 0,
-    SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE,
-    SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE,
-    SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE,
-    SHOW_SSL_INTERSTITIAL_OVERRIDABLE,
-    WWW_MISMATCH_FOUND,
-    WWW_MISMATCH_URL_AVAILABLE,
-    WWW_MISMATCH_URL_NOT_AVAILABLE,
-    SHOW_BAD_CLOCK,
-    CAPTIVE_PORTAL_CERT_FOUND,
+    SHOW_CAPTIVE_PORTAL_INTERSTITIAL_NONOVERRIDABLE = 1,
+    SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE = 2,
+    SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE = 3,
+    SHOW_SSL_INTERSTITIAL_OVERRIDABLE = 4,
+    WWW_MISMATCH_FOUND = 5,  // Deprecated in M59 by WWW_MISMATCH_FOUND_IN_SAN.
+    WWW_MISMATCH_URL_AVAILABLE = 6,
+    WWW_MISMATCH_URL_NOT_AVAILABLE = 7,
+    SHOW_BAD_CLOCK = 8,
+    CAPTIVE_PORTAL_CERT_FOUND = 9,
+    WWW_MISMATCH_FOUND_IN_SAN = 10,
     SSL_ERROR_HANDLER_EVENT_COUNT
   };
 
diff --git a/chrome/browser/ssl/ssl_error_handler_unittest.cc b/chrome/browser/ssl/ssl_error_handler_unittest.cc
index 19471ae..43a34b4 100644
--- a/chrome/browser/ssl/ssl_error_handler_unittest.cc
+++ b/chrome/browser/ssl/ssl_error_handler_unittest.cc
@@ -196,13 +196,13 @@
 class SSLErrorHandlerNameMismatchTest : public ChromeRenderViewHostTestHarness {
  public:
   SSLErrorHandlerNameMismatchTest() : field_trial_list_(nullptr) {}
+  ~SSLErrorHandlerNameMismatchTest() override {}
 
   void SetUp() override {
     ChromeRenderViewHostTestHarness::SetUp();
     SSLErrorHandler::ResetConfigForTesting();
     SSLErrorHandler::SetInterstitialDelayForTesting(base::TimeDelta());
-    ssl_info_.cert =
-        net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
+    ssl_info_.cert = GetCertificate();
     ssl_info_.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID;
     ssl_info_.public_key_hashes.push_back(
         net::HashValue(kCertPublicKeyHashValue));
@@ -230,6 +230,13 @@
   const net::SSLInfo& ssl_info() { return ssl_info_; }
 
  private:
+  // Returns a certificate for the test. Virtual to allow derived fixtures to
+  // use a certificate with different characteristics.
+  virtual scoped_refptr<net::X509Certificate> GetCertificate() {
+    return net::ImportCertFromFile(net::GetTestCertsDirectory(),
+                                   "subjectAltName_www_example_com.pem");
+  }
+
   net::SSLInfo ssl_info_;
   std::unique_ptr<TestSSLErrorHandler> error_handler_;
   TestSSLErrorHandlerDelegate* delegate_;
@@ -238,6 +245,22 @@
   DISALLOW_COPY_AND_ASSIGN(SSLErrorHandlerNameMismatchTest);
 };
 
+// A class to test name mismatch errors, where the certificate lacks a
+// SubjectAltName. Creates an error handler with a name mismatch error.
+class SSLErrorHandlerNameMismatchNoSANTest
+    : public SSLErrorHandlerNameMismatchTest {
+ public:
+  SSLErrorHandlerNameMismatchNoSANTest() {}
+
+ private:
+  // Return a certificate that contains no SubjectAltName field.
+  scoped_refptr<net::X509Certificate> GetCertificate() override {
+    return net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
+  }
+
+  DISALLOW_COPY_AND_ASSIGN(SSLErrorHandlerNameMismatchNoSANTest);
+};
+
 // A class to test the captive portal certificate list feature. Creates an error
 // handler with a name mismatch error by default. The error handler can be
 // recreated by calling ResetErrorHandler() with an appropriate cert status.
@@ -575,7 +598,7 @@
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                SSLErrorHandler::HANDLE_ALL, 1);
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
-                               SSLErrorHandler::WWW_MISMATCH_FOUND, 1);
+                               SSLErrorHandler::WWW_MISMATCH_FOUND_IN_SAN, 1);
   histograms.ExpectBucketCount(
       SSLErrorHandler::GetHistogramNameForTesting(),
       SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
@@ -649,7 +672,7 @@
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                SSLErrorHandler::HANDLE_ALL, 1);
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
-                               SSLErrorHandler::WWW_MISMATCH_FOUND, 1);
+                               SSLErrorHandler::WWW_MISMATCH_FOUND_IN_SAN, 1);
   histograms.ExpectBucketCount(
       SSLErrorHandler::GetHistogramNameForTesting(),
       SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
@@ -681,11 +704,35 @@
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                SSLErrorHandler::HANDLE_ALL, 1);
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
-                               SSLErrorHandler::WWW_MISMATCH_FOUND, 1);
+                               SSLErrorHandler::WWW_MISMATCH_FOUND_IN_SAN, 1);
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                SSLErrorHandler::WWW_MISMATCH_URL_AVAILABLE, 1);
 }
 
+// No suggestions should be requested if certificate lacks a SubjectAltName.
+TEST_F(SSLErrorHandlerNameMismatchNoSANTest,
+       SSLCommonNameMismatchHandlingRequiresSubjectAltName) {
+  base::HistogramTester histograms;
+  EXPECT_FALSE(error_handler()->IsTimerRunningForTesting());
+  delegate()->set_suggested_url_exists();
+  error_handler()->StartHandlingError();
+
+  EXPECT_FALSE(delegate()->suggested_url_checked());
+  base::RunLoop().RunUntilIdle();
+
+  EXPECT_TRUE(delegate()->ssl_interstitial_shown());
+  EXPECT_FALSE(delegate()->redirected_to_suggested_url());
+
+  histograms.ExpectTotalCount(SSLErrorHandler::GetHistogramNameForTesting(), 2);
+  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
+                               SSLErrorHandler::HANDLE_ALL, 1);
+  histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
+                               SSLErrorHandler::WWW_MISMATCH_FOUND_IN_SAN, 0);
+  histograms.ExpectBucketCount(
+      SSLErrorHandler::GetHistogramNameForTesting(),
+      SSLErrorHandler::SHOW_SSL_INTERSTITIAL_OVERRIDABLE, 1);
+}
+
 TEST_F(SSLErrorHandlerNameMismatchTest,
        ShouldShowSSLInterstitialOnInvalidUrlCheckResult) {
   base::HistogramTester histograms;
@@ -710,7 +757,7 @@
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                SSLErrorHandler::HANDLE_ALL, 1);
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
-                               SSLErrorHandler::WWW_MISMATCH_FOUND, 1);
+                               SSLErrorHandler::WWW_MISMATCH_FOUND_IN_SAN, 1);
   histograms.ExpectBucketCount(SSLErrorHandler::GetHistogramNameForTesting(),
                                SSLErrorHandler::WWW_MISMATCH_URL_NOT_AVAILABLE,
                                1);
diff --git a/components/ssl_errors/error_classification.cc b/components/ssl_errors/error_classification.cc
index 572b572..52827fe 100644
--- a/components/ssl_errors/error_classification.cc
+++ b/components/ssl_errors/error_classification.cc
@@ -38,32 +38,13 @@
 namespace ssl_errors {
 namespace {
 
-// Events for UMA. Do not reorder or change!
-enum SSLInterstitialCause {
-  CLOCK_PAST,
-  CLOCK_FUTURE,
-  WWW_SUBDOMAIN_MATCH,
-  SUBDOMAIN_MATCH,
-  SUBDOMAIN_INVERSE_MATCH,
-  SUBDOMAIN_OUTSIDE_WILDCARD,
-  HOST_NAME_NOT_KNOWN_TLD,
-  LIKELY_MULTI_TENANT_HOSTING,
-  LOCALHOST,
-  PRIVATE_URL,
-  AUTHORITY_ERROR_CAPTIVE_PORTAL,  // Deprecated in M47.
-  SELF_SIGNED,
-  EXPIRED_RECENTLY,
-  LIKELY_SAME_DOMAIN,
-  UNUSED_INTERSTITIAL_CAUSE_ENTRY,
-};
-
 void RecordSSLInterstitialCause(bool overridable, SSLInterstitialCause event) {
   if (overridable) {
     UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.overridable", event,
-                              UNUSED_INTERSTITIAL_CAUSE_ENTRY);
+                              SSL_INTERSTITIAL_CAUSE_MAX);
   } else {
     UMA_HISTOGRAM_ENUMERATION("interstitial.ssl.cause.nonoverridable", event,
-                              UNUSED_INTERSTITIAL_CAUSE_ENTRY);
+                              SSL_INTERSTITIAL_CAUSE_MAX);
   }
 }
 
@@ -109,7 +90,7 @@
                          const net::X509Certificate& cert) {
   std::string www_host;
   std::vector<std::string> dns_names;
-  cert.GetDNSNames(&dns_names);
+  cert.GetSubjectAltName(&dns_names, nullptr);
   return GetWWWSubDomainMatch(request_url, dns_names, &www_host);
 }
 
@@ -148,24 +129,28 @@
     }
     case ssl_errors::ErrorInfo::CERT_COMMON_NAME_INVALID: {
       std::string host_name = request_url.host();
+      std::vector<std::string> dns_names;
+      cert.GetSubjectAltName(&dns_names, nullptr);
+      std::vector<HostnameTokens> dns_name_tokens =
+          GetTokenizedDNSNames(dns_names);
+
+      if (dns_names.empty())
+        RecordSSLInterstitialCause(overridable, NO_SUBJECT_ALT_NAME);
+
       if (HostNameHasKnownTLD(host_name)) {
         HostnameTokens host_name_tokens = Tokenize(host_name);
         if (IsWWWSubDomainMatch(request_url, cert))
-          RecordSSLInterstitialCause(overridable, WWW_SUBDOMAIN_MATCH);
+          RecordSSLInterstitialCause(overridable, WWW_SUBDOMAIN_MATCH2);
         if (IsSubDomainOutsideWildcard(request_url, cert))
-          RecordSSLInterstitialCause(overridable, SUBDOMAIN_OUTSIDE_WILDCARD);
-        std::vector<std::string> dns_names;
-        cert.GetDNSNames(&dns_names);
-        std::vector<HostnameTokens> dns_name_tokens =
-            GetTokenizedDNSNames(dns_names);
+          RecordSSLInterstitialCause(overridable, SUBDOMAIN_OUTSIDE_WILDCARD2);
         if (NameUnderAnyNames(host_name_tokens, dns_name_tokens))
-          RecordSSLInterstitialCause(overridable, SUBDOMAIN_MATCH);
+          RecordSSLInterstitialCause(overridable, SUBDOMAIN_MATCH2);
         if (AnyNamesUnderName(dns_name_tokens, host_name_tokens))
-          RecordSSLInterstitialCause(overridable, SUBDOMAIN_INVERSE_MATCH);
+          RecordSSLInterstitialCause(overridable, SUBDOMAIN_INVERSE_MATCH2);
         if (IsCertLikelyFromMultiTenantHosting(request_url, cert))
-          RecordSSLInterstitialCause(overridable, LIKELY_MULTI_TENANT_HOSTING);
+          RecordSSLInterstitialCause(overridable, LIKELY_MULTI_TENANT_HOSTING2);
         if (IsCertLikelyFromSameDomain(request_url, cert))
-          RecordSSLInterstitialCause(overridable, LIKELY_SAME_DOMAIN);
+          RecordSSLInterstitialCause(overridable, LIKELY_SAME_DOMAIN2);
       } else {
         RecordSSLInterstitialCause(overridable, HOST_NAME_NOT_KNOWN_TLD);
       }
@@ -382,7 +367,7 @@
   std::string host_name = request_url.host();
   HostnameTokens host_name_tokens = Tokenize(host_name);
   std::vector<std::string> dns_names;
-  cert.GetDNSNames(&dns_names);
+  cert.GetSubjectAltName(&dns_names, nullptr);
   bool result = false;
 
   // This method requires that the host name be longer than the dns name on
@@ -410,7 +395,7 @@
   std::string host_name = request_url.host();
   std::vector<std::string> dns_names;
   std::vector<std::string> dns_names_domain;
-  cert.GetDNSNames(&dns_names);
+  cert.GetSubjectAltName(&dns_names, nullptr);
   size_t dns_names_size = dns_names.size();
 
   // If there is only 1 DNS name then it is definitely not a shared certificate.
@@ -457,7 +442,9 @@
                                 const net::X509Certificate& cert) {
   std::string host_name = request_url.host();
   std::vector<std::string> dns_names;
-  cert.GetDNSNames(&dns_names);
+  cert.GetSubjectAltName(&dns_names, nullptr);
+  if (dns_names.empty())
+    return false;
 
   dns_names.push_back(host_name);
   std::vector<std::string> dns_names_domain;
diff --git a/components/ssl_errors/error_classification.h b/components/ssl_errors/error_classification.h
index b8c472df..0dea465 100644
--- a/components/ssl_errors/error_classification.h
+++ b/components/ssl_errors/error_classification.h
@@ -28,6 +28,33 @@
 
 // Methods for identifying specific error causes. ------------------------------
 
+// These values are written to logs. New enum values can be added, but existing
+// enums must never be renumbered or deleted and reused.
+enum SSLInterstitialCause {
+  CLOCK_PAST = 0,
+  CLOCK_FUTURE = 1,
+  WWW_SUBDOMAIN_MATCH = 2,         // Deprecated in M59.
+  SUBDOMAIN_MATCH = 3,             // Deprecated in M59.
+  SUBDOMAIN_INVERSE_MATCH = 4,     // Deprecated in M59.
+  SUBDOMAIN_OUTSIDE_WILDCARD = 5,  // Deprecated in M59.
+  HOST_NAME_NOT_KNOWN_TLD = 6,
+  LIKELY_MULTI_TENANT_HOSTING = 7,  // Deprecated in M59.
+  LOCALHOST = 8,
+  PRIVATE_URL = 9,
+  AUTHORITY_ERROR_CAPTIVE_PORTAL = 10,  // Deprecated in M47.
+  SELF_SIGNED = 11,
+  EXPIRED_RECENTLY = 12,
+  LIKELY_SAME_DOMAIN = 13,  // Deprecated in M59.
+  NO_SUBJECT_ALT_NAME = 14,
+  WWW_SUBDOMAIN_MATCH2 = 15,
+  SUBDOMAIN_MATCH2 = 16,
+  SUBDOMAIN_INVERSE_MATCH2 = 17,
+  SUBDOMAIN_OUTSIDE_WILDCARD2 = 18,
+  LIKELY_MULTI_TENANT_HOSTING2 = 19,
+  LIKELY_SAME_DOMAIN2 = 20,
+  SSL_INTERSTITIAL_CAUSE_MAX
+};
+
 // What is known about the accuracy of system clock.  Do not change or
 // reorder; these values are used in an UMA histogram.
 enum ClockState {
@@ -110,14 +137,9 @@
 bool IsCertLikelyFromSameDomain(const GURL& request_url,
                                 const net::X509Certificate& cert);
 
-// Returns true if the site's hostname differs from one of the DNS
-// names in the certificate (CN or SANs) only by the presence or
-// absence of the single-label prefix "www". E.g.: (The first domain
-// is hostname and the second domain is a DNS name in the certificate)
-//     www.example.com ~ example.com -> true
-//     example.com ~ www.example.com -> true
-//     www.food.example.com ~ example.com -> false
-//     mail.example.com ~ example.com -> false
+// Returns true if the site's hostname differs from one of the DNS names in
+// |dns_names| only by the presence or absence of the single-label prefix "www".
+// The matching name from the certificate is returned in |www_match_host_name|.
 bool GetWWWSubDomainMatch(const GURL& request_url,
                           const std::vector<std::string>& dns_names,
                           std::string* www_match_host_name);
diff --git a/components/ssl_errors/error_classification_unittest.cc b/components/ssl_errors/error_classification_unittest.cc
index 1099181a..c4c2c45 100644
--- a/components/ssl_errors/error_classification_unittest.cc
+++ b/components/ssl_errors/error_classification_unittest.cc
@@ -26,11 +26,15 @@
 #include "net/test/test_certificate_data.h"
 #include "net/test/test_data_directory.h"
 #include "net/url_request/url_request_test_util.h"
+#include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 #include "url/gurl.h"
 
+using testing::ElementsAre;
+
 namespace {
 const char kNetworkTimeHistogram[] = "interstitial.ssl.clockstate.network3";
+const char kSslErrorCauseHistogram[] = "interstitial.ssl.cause.overridable";
 
 static std::unique_ptr<net::test_server::HttpResponse>
 NetworkErrorResponseHandler(const net::test_server::HttpRequest& request) {
@@ -53,112 +57,132 @@
 };
 
 TEST_F(SSLErrorClassificationTest, TestNameMismatch) {
-  scoped_refptr<net::X509Certificate> google_cert(
-      net::X509Certificate::CreateFromBytes(
-          reinterpret_cast<const char*>(google_der), sizeof(google_der)));
-  ASSERT_TRUE(google_cert.get());
-  std::vector<std::string> dns_names_google;
-  google_cert->GetDNSNames(&dns_names_google);
-  ASSERT_EQ(1u, dns_names_google.size());  // ["www.google.com"]
-  std::vector<std::string> hostname_tokens_google =
-      ssl_errors::Tokenize(dns_names_google[0]);
-  ASSERT_EQ(3u, hostname_tokens_google.size());  // ["www","google","com"]
-  std::vector<std::vector<std::string>> dns_name_tokens_google;
-  dns_name_tokens_google.push_back(hostname_tokens_google);
-  ASSERT_EQ(1u, dns_name_tokens_google.size());  // [["www","google","com"]]
+  scoped_refptr<net::X509Certificate> example_cert = net::ImportCertFromFile(
+      net::GetTestCertsDirectory(), "subjectAltName_www_example_com.pem");
+  ASSERT_TRUE(example_cert);
+  std::vector<std::string> dns_names_example;
+  example_cert->GetSubjectAltName(&dns_names_example, nullptr);
+  ASSERT_THAT(dns_names_example, ElementsAre("www.example.com"));
+  std::vector<std::string> hostname_tokens_example =
+      ssl_errors::Tokenize(dns_names_example[0]);
+  ASSERT_THAT(hostname_tokens_example, ElementsAre("www", "example", "com"));
+  std::vector<std::vector<std::string>> dns_name_tokens_example;
+  dns_name_tokens_example.push_back(hostname_tokens_example);
+  ASSERT_EQ(1u, dns_name_tokens_example.size());  // [["www","example","com"]]
+  ASSERT_THAT(dns_name_tokens_example[0], ElementsAre("www", "example", "com"));
 
   {
-    GURL origin("https://google.com");
+    GURL origin("https://example.com");
     std::string www_host;
     std::vector<std::string> host_name_tokens = base::SplitString(
         origin.host(), ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
     EXPECT_TRUE(
-        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_google, &www_host));
-    EXPECT_EQ("www.google.com", www_host);
+        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_example, &www_host));
+    EXPECT_EQ("www.example.com", www_host);
     EXPECT_FALSE(ssl_errors::NameUnderAnyNames(host_name_tokens,
-                                               dns_name_tokens_google));
-    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_google,
+                                               dns_name_tokens_example));
+    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_example,
                                                host_name_tokens));
-    EXPECT_FALSE(ssl_errors::IsSubDomainOutsideWildcard(origin, *google_cert));
+    EXPECT_FALSE(ssl_errors::IsSubDomainOutsideWildcard(origin, *example_cert));
     EXPECT_FALSE(
-        ssl_errors::IsCertLikelyFromMultiTenantHosting(origin, *google_cert));
-    EXPECT_TRUE(ssl_errors::IsCertLikelyFromSameDomain(origin, *google_cert));
+        ssl_errors::IsCertLikelyFromMultiTenantHosting(origin, *example_cert));
+    EXPECT_TRUE(ssl_errors::IsCertLikelyFromSameDomain(origin, *example_cert));
   }
 
   {
-    GURL origin("https://foo.blah.google.com");
+    GURL origin("https://foo.blah.example.com");
     std::string www_host;
     std::vector<std::string> host_name_tokens = base::SplitString(
         origin.host(), ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
     EXPECT_FALSE(
-        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_google, &www_host));
+        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_example, &www_host));
     EXPECT_FALSE(ssl_errors::NameUnderAnyNames(host_name_tokens,
-                                               dns_name_tokens_google));
-    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_google,
+                                               dns_name_tokens_example));
+    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_example,
                                                host_name_tokens));
-    EXPECT_TRUE(ssl_errors::IsCertLikelyFromSameDomain(origin, *google_cert));
+    EXPECT_TRUE(ssl_errors::IsCertLikelyFromSameDomain(origin, *example_cert));
   }
 
   {
-    GURL origin("https://foo.www.google.com");
+    GURL origin("https://foo.www.example.com");
     std::string www_host;
     std::vector<std::string> host_name_tokens = base::SplitString(
         origin.host(), ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
     EXPECT_FALSE(
-        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_google, &www_host));
+        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_example, &www_host));
     EXPECT_TRUE(ssl_errors::NameUnderAnyNames(host_name_tokens,
-                                              dns_name_tokens_google));
-    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_google,
+                                              dns_name_tokens_example));
+    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_example,
                                                host_name_tokens));
-    EXPECT_TRUE(ssl_errors::IsCertLikelyFromSameDomain(origin, *google_cert));
+    EXPECT_TRUE(ssl_errors::IsCertLikelyFromSameDomain(origin, *example_cert));
   }
 
   {
-    GURL origin("https://www.google.com.foo");
+    GURL origin("https://www.example.com.foo");
     std::string www_host;
     std::vector<std::string> host_name_tokens = base::SplitString(
         origin.host(), ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
     EXPECT_FALSE(
-        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_google, &www_host));
+        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_example, &www_host));
     EXPECT_FALSE(ssl_errors::NameUnderAnyNames(host_name_tokens,
-                                               dns_name_tokens_google));
-    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_google,
+                                               dns_name_tokens_example));
+    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_example,
                                                host_name_tokens));
-    EXPECT_FALSE(ssl_errors::IsCertLikelyFromSameDomain(origin, *google_cert));
+    EXPECT_FALSE(ssl_errors::IsCertLikelyFromSameDomain(origin, *example_cert));
   }
 
   {
-    GURL origin("https://www.foogoogle.com.");
+    GURL origin("https://www.fooexample.com.");
     std::string www_host;
     std::vector<std::string> host_name_tokens = base::SplitString(
         origin.host(), ".", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
     EXPECT_FALSE(
-        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_google, &www_host));
+        ssl_errors::GetWWWSubDomainMatch(origin, dns_names_example, &www_host));
     EXPECT_FALSE(ssl_errors::NameUnderAnyNames(host_name_tokens,
-                                               dns_name_tokens_google));
-    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_google,
+                                               dns_name_tokens_example));
+    EXPECT_FALSE(ssl_errors::AnyNamesUnderName(dns_name_tokens_example,
                                                host_name_tokens));
-    EXPECT_FALSE(ssl_errors::IsCertLikelyFromSameDomain(origin, *google_cert));
+    EXPECT_FALSE(ssl_errors::IsCertLikelyFromSameDomain(origin, *example_cert));
   }
 
-  scoped_refptr<net::X509Certificate> webkit_cert(
-      net::X509Certificate::CreateFromBytes(
-          reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der)));
-  ASSERT_TRUE(webkit_cert.get());
-  std::vector<std::string> dns_names_webkit;
-  webkit_cert->GetDNSNames(&dns_names_webkit);
-  ASSERT_EQ(2u, dns_names_webkit.size());  // ["*.webkit.org", "webkit.org"]
-  std::vector<std::string> hostname_tokens_webkit_0 =
-      ssl_errors::Tokenize(dns_names_webkit[0]);
-  ASSERT_EQ(3u, hostname_tokens_webkit_0.size());  // ["*", "webkit","org"]
-  std::vector<std::string> hostname_tokens_webkit_1 =
-      ssl_errors::Tokenize(dns_names_webkit[1]);
-  ASSERT_EQ(2u, hostname_tokens_webkit_1.size());  // ["webkit","org"]
-  std::vector<std::vector<std::string>> dns_name_tokens_webkit;
-  dns_name_tokens_webkit.push_back(hostname_tokens_webkit_0);
-  dns_name_tokens_webkit.push_back(hostname_tokens_webkit_1);
-  ASSERT_EQ(2u, dns_name_tokens_webkit.size());
+  // Ensure that a certificate with no SubjectAltName does not fall back to
+  // the Subject CN when evaluating hostnames.
   {
+    scoped_refptr<net::X509Certificate> google_cert(
+        net::X509Certificate::CreateFromBytes(
+            reinterpret_cast<const char*>(google_der), sizeof(google_der)));
+    ASSERT_TRUE(google_cert);
+
+    GURL origin("https://google.com");
+
+    base::HistogramTester histograms;
+    ssl_errors::RecordUMAStatistics(true, base::Time::NowFromSystemTime(),
+                                    origin, net::ERR_CERT_COMMON_NAME_INVALID,
+                                    *google_cert);
+
+    // Verify that we recorded only NO_SUBJECT_ALT_NAME and no other causes.
+    histograms.ExpectUniqueSample(kSslErrorCauseHistogram,
+                                  ssl_errors::NO_SUBJECT_ALT_NAME, 1);
+  }
+
+  {
+    scoped_refptr<net::X509Certificate> webkit_cert(
+        net::X509Certificate::CreateFromBytes(
+            reinterpret_cast<const char*>(webkit_der), sizeof(webkit_der)));
+    ASSERT_TRUE(webkit_cert);
+    std::vector<std::string> dns_names_webkit;
+    webkit_cert->GetSubjectAltName(&dns_names_webkit, nullptr);
+    ASSERT_THAT(dns_names_webkit, ElementsAre("*.webkit.org", "webkit.org"));
+    std::vector<std::string> hostname_tokens_webkit_0 =
+        ssl_errors::Tokenize(dns_names_webkit[0]);
+    ASSERT_THAT(hostname_tokens_webkit_0, ElementsAre("*", "webkit", "org"));
+    std::vector<std::string> hostname_tokens_webkit_1 =
+        ssl_errors::Tokenize(dns_names_webkit[1]);
+    ASSERT_THAT(hostname_tokens_webkit_1, ElementsAre("webkit", "org"));
+    std::vector<std::vector<std::string>> dns_name_tokens_webkit;
+    dns_name_tokens_webkit.push_back(hostname_tokens_webkit_0);
+    dns_name_tokens_webkit.push_back(hostname_tokens_webkit_1);
+    ASSERT_EQ(2u, dns_name_tokens_webkit.size());
     GURL origin("https://a.b.webkit.org");
     std::string www_host;
     std::vector<std::string> host_name_tokens = base::SplitString(
diff --git a/components/ssl_errors/error_info.cc b/components/ssl_errors/error_info.cc
index 7ef291e..ab7afcc 100644
--- a/components/ssl_errors/error_info.cc
+++ b/components/ssl_errors/error_info.cc
@@ -32,22 +32,30 @@
   base::string16 details, short_description;
   switch (error_type) {
     case CERT_COMMON_NAME_INVALID: {
-      // If the certificate contains multiple DNS names, we choose the most
-      // representative one -- either the DNS name that's also in the subject
-      // field, or the first one.  If this heuristic turns out to be
-      // inadequate, we can consider choosing the DNS name that is the
-      // "closest match" to the host name in the request URL, or listing all
-      // the DNS names with an HTML <ul>.
       std::vector<std::string> dns_names;
-      cert->GetDNSNames(&dns_names);
-      DCHECK(!dns_names.empty());
+      cert->GetSubjectAltName(&dns_names, nullptr);
+
       size_t i = 0;
-      for (; i < dns_names.size(); ++i) {
-        if (dns_names[i] == cert->subject().common_name)
-          break;
+      if (dns_names.empty()) {
+        // The certificate had no DNS names, display an explanatory string.
+        // TODO(elawrence): Change the error messsage instead of just the
+        // placeholder string; see https://crbug.com/708268
+        dns_names.push_back("[missing_subjectAltName]");
+      } else {
+        // If the certificate contains multiple DNS names, we choose the most
+        // representative one -- either the DNS name that's also in the subject
+        // field, or the first one. If this heuristic turns out to be
+        // inadequate, we can consider choosing the DNS name that is the
+        // "closest match" to the host name in the request URL, or listing all
+        // the DNS names with an HTML <ul>.
+        for (; i < dns_names.size(); ++i) {
+          if (dns_names[i] == cert->subject().common_name)
+            break;
+        }
+        if (i == dns_names.size())
+          i = 0;
       }
-      if (i == dns_names.size())
-        i = 0;
+
       details = l10n_util::GetStringFUTF16(
           IDS_CERT_ERROR_COMMON_NAME_INVALID_DETAILS,
           UTF8ToUTF16(request_url.host()),
diff --git a/net/BUILD.gn b/net/BUILD.gn
index 12de000..af14d0f 100644
--- a/net/BUILD.gn
+++ b/net/BUILD.gn
@@ -2283,6 +2283,7 @@
     "data/ssl/certificates/spdy_pooling.pem",
     "data/ssl/certificates/start_after_expiry.pem",
     "data/ssl/certificates/subjectAltName_sanity_check.pem",
+    "data/ssl/certificates/subjectAltName_www_example_com.pem",
     "data/ssl/certificates/test_mail_google_com.pem",
     "data/ssl/certificates/thawte.single.pem",
     "data/ssl/certificates/tls_feature_extension.pem",
diff --git a/net/cert/x509_certificate.h b/net/cert/x509_certificate.h
index 2c6fb4d..e11f104 100644
--- a/net/cert/x509_certificate.h
+++ b/net/cert/x509_certificate.h
@@ -188,10 +188,13 @@
   const base::Time& valid_start() const { return valid_start_; }
   const base::Time& valid_expiry() const { return valid_expiry_; }
 
-  // Gets the DNS names in the certificate.  Pursuant to RFC 2818, Section 3.1
+  // Gets the DNS names in the certificate. Pursuant to RFC 2818, Section 3.1
   // Server Identity, if the certificate has a subjectAltName extension of
   // type dNSName, this method gets the DNS names in that extension.
   // Otherwise, it gets the common name in the subject field.
+  //
+  // Note: Chrome has deprecated fallback to the subject field, see
+  // https://crbug.com/308330; prefer GetSubjectAltName() instead.
   void GetDNSNames(std::vector<std::string>* dns_names) const;
 
   // Gets the subjectAltName extension field from the certificate, if any.
diff --git a/net/data/ssl/certificates/subjectAltName_www_example_com.pem b/net/data/ssl/certificates/subjectAltName_www_example_com.pem
new file mode 100644
index 0000000..88a338eee
--- /dev/null
+++ b/net/data/ssl/certificates/subjectAltName_www_example_com.pem
@@ -0,0 +1,75 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 15837232328995864193 (0xdbc926e4db217e81)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: C=US, ST=California, L=Mountain View, O=Test CA, CN=localhost
+        Validity
+            Not Before: Mar 28 22:42:07 2017 GMT
+            Not After : Mar 26 22:42:07 2027 GMT
+        Subject: C=US, ST=California, L=Mountain View, O=Test CA, CN=localhost
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:b5:12:3c:e3:76:25:c4:e5:9f:ac:69:3a:dd:40:
+                    78:95:ef:1e:f2:b6:c2:74:14:e7:ba:cc:f3:a6:46:
+                    6c:51:1f:2e:4e:9c:99:7a:82:1f:43:bc:28:8a:74:
+                    8e:52:73:59:24:ac:0a:71:c9:b5:30:a9:d3:85:f9:
+                    8c:76:d0:19:43:fe:cb:c0:d6:4b:9e:9b:76:63:84:
+                    ae:99:4d:14:a2:be:ae:81:5d:05:22:b9:46:39:66:
+                    4e:42:10:57:64:26:27:f2:88:7f:55:10:38:83:7a:
+                    dd:6c:09:ae:f1:09:33:04:84:ab:48:53:1c:9c:42:
+                    b4:f7:ee:c5:21:7a:65:56:91:64:99:9b:f4:2c:22:
+                    50:dc:36:35:ac:04:22:eb:5c:2d:4c:6c:4b:16:70:
+                    f7:13:36:f9:32:ec:6d:e8:f7:2e:f2:b9:b8:0c:e2:
+                    99:de:ed:61:7d:66:17:33:67:0e:6e:87:5d:05:65:
+                    e1:98:46:2c:e7:8e:6d:a7:20:ab:87:5b:f3:32:55:
+                    cb:2d:df:76:9b:cf:06:71:5c:06:77:3b:20:49:6b:
+                    47:5e:6e:ae:42:99:8b:8f:9e:c4:ba:4c:50:ec:14:
+                    a1:43:8f:24:e9:9a:c8:ca:ec:b3:e2:26:d4:a5:ba:
+                    c3:f8:90:b1:03:2a:05:72:a4:3f:17:bd:9c:af:68:
+                    e4:17
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: critical
+                CA:TRUE
+            X509v3 Subject Alternative Name: 
+                DNS:www.example.com
+    Signature Algorithm: sha256WithRSAEncryption
+         38:41:86:d5:01:6b:00:3d:61:1e:ad:f1:82:1d:01:fc:c1:98:
+         8e:d7:ea:46:2d:e3:7c:22:3c:ce:f2:64:f0:73:0c:e5:ea:21:
+         3a:8f:63:e1:c0:27:3d:84:ed:0f:aa:0e:d3:ca:76:67:ba:b9:
+         f4:da:fc:d5:ec:07:f4:97:08:2c:55:e4:be:5f:0c:e7:c9:7f:
+         be:81:b5:76:d2:47:6b:be:01:82:8c:75:27:df:78:99:62:55:
+         74:91:d9:c7:37:a3:62:1f:36:0b:ed:9f:54:01:be:87:9a:3d:
+         d5:90:1e:98:13:29:cd:23:9b:d4:a8:d0:c0:18:04:d5:05:55:
+         97:8d:6b:89:14:0a:1b:b4:2b:bd:7d:05:68:87:72:64:9e:b9:
+         66:a2:89:21:2a:ae:a1:b3:ef:22:0d:e1:80:46:52:11:20:3a:
+         a8:7e:18:1e:e1:19:2d:f5:de:cc:b9:f2:ff:6a:88:db:c1:31:
+         dc:34:25:e2:c0:4c:a7:6e:28:6e:b1:10:7b:04:ed:a4:82:3a:
+         81:55:44:1a:87:25:14:69:de:2d:c0:7d:3b:e3:21:c4:21:e5:
+         ca:f6:7c:11:12:d8:7f:c1:b7:f5:45:ca:6b:e5:41:91:5d:d8:
+         db:64:48:01:64:a5:df:85:51:4b:c4:ed:27:e3:2f:b4:71:f4:
+         c6:a1:a2:ad
+-----BEGIN CERTIFICATE-----
+MIIDcjCCAlqgAwIBAgIJANvJJuTbIX6BMA0GCSqGSIb3DQEBCwUAMGAxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1Nb3VudGFpbiBW
+aWV3MRAwDgYDVQQKDAdUZXN0IENBMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTcw
+MzI4MjI0MjA3WhcNMjcwMzI2MjI0MjA3WjBgMQswCQYDVQQGEwJVUzETMBEGA1UE
+CAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNTW91bnRhaW4gVmlldzEQMA4GA1UECgwH
+VGVzdCBDQTESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAtRI843YlxOWfrGk63UB4le8e8rbCdBTnuszzpkZsUR8uTpyZ
+eoIfQ7woinSOUnNZJKwKccm1MKnThfmMdtAZQ/7LwNZLnpt2Y4SumU0Uor6ugV0F
+IrlGOWZOQhBXZCYn8oh/VRA4g3rdbAmu8QkzBISrSFMcnEK09+7FIXplVpFkmZv0
+LCJQ3DY1rAQi61wtTGxLFnD3Ezb5Muxt6Pcu8rm4DOKZ3u1hfWYXM2cOboddBWXh
+mEYs545tpyCrh1vzMlXLLd92m88GcVwGdzsgSWtHXm6uQpmLj57EukxQ7BShQ48k
+6ZrIyuyz4ibUpbrD+JCxAyoFcqQ/F72cr2jkFwIDAQABoy8wLTAPBgNVHRMBAf8E
+BTADAQH/MBoGA1UdEQQTMBGCD3d3dy5leGFtcGxlLmNvbTANBgkqhkiG9w0BAQsF
+AAOCAQEAOEGG1QFrAD1hHq3xgh0B/MGYjtfqRi3jfCI8zvJk8HMM5eohOo9j4cAn
+PYTtD6oO08p2Z7q59Nr81ewH9JcILFXkvl8M58l/voG1dtJHa74Bgox1J994mWJV
+dJHZxzejYh82C+2fVAG+h5o91ZAemBMpzSOb1KjQwBgE1QVVl41riRQKG7QrvX0F
+aIdyZJ65ZqKJISquobPvIg3hgEZSESA6qH4YHuEZLfXezLny/2qI28Ex3DQl4sBM
+p24obrEQewTtpII6gVVEGoclFGneLcB9O+MhxCHlyvZ8ERLYf8G39UXKa+VBkV3Y
+22RIAWSl34VRS8TtJ+MvtHH0xqGirQ==
+-----END CERTIFICATE-----
diff --git a/net/data/ssl/scripts/ee.cnf b/net/data/ssl/scripts/ee.cnf
index 7d91d75c..3d42df1 100644
--- a/net/data/ssl/scripts/ee.cnf
+++ b/net/data/ssl/scripts/ee.cnf
@@ -81,6 +81,13 @@
 otherName = 1.2.3.4;UTF8:ignore me
 dirName = more_san_sanity
 
+[req_san_example]
+basicConstraints = critical, CA:true
+subjectAltName   = @san_example
+
+[san_example]
+DNS = www.example.com
+
 [req_spdy_pooling]
 subjectAltName = @spdy_pooling
 
diff --git a/net/data/ssl/scripts/generate-test-certs.sh b/net/data/ssl/scripts/generate-test-certs.sh
index f5395e4..abb9bd1 100755
--- a/net/data/ssl/scripts/generate-test-certs.sh
+++ b/net/data/ssl/scripts/generate-test-certs.sh
@@ -201,6 +201,11 @@
     -config ../scripts/ee.cnf -newkey rsa:2048 -text \
     -out ../certificates/subjectAltName_sanity_check.pem
 
+## SubjectAltName containing www.example.com
+try openssl req -x509 -days 3650 -extensions req_san_example \
+    -config ../scripts/ee.cnf -newkey rsa:2048 -text \
+    -out ../certificates/subjectAltName_www_example_com.pem
+
 ## Punycode handling
 SUBJECT_NAME="req_punycode_dn" \
   try openssl req -x509 -days 3650 -extensions req_punycode \
diff --git a/net/test/test_certificate_data.h b/net/test/test_certificate_data.h
index c8fa950..fbeaca7 100644
--- a/net/test/test_certificate_data.h
+++ b/net/test/test_certificate_data.h
@@ -33,7 +33,8 @@
 #define VARIABLE_IS_NOT_USED
 #endif
 
-// Google's cert.
+// Google's 2009 cert. Lacks a SubjectAltName, but contains www.google.com in
+// the Subject CN field.
 
 unsigned char VARIABLE_IS_NOT_USED google_der[] = {
   0x30, 0x82, 0x03, 0x21, 0x30, 0x82, 0x02, 0x8a, 0xa0, 0x03, 0x02, 0x01,
@@ -106,7 +107,8 @@
   0xdf
 };
 
-// webkit.org's cert.
+// webkit.org's 2008 cert. Contains a SubjectAltName field with *.webkit.org and
+// webkit.org. The Subject CN field contains *.webkit.org.
 
 unsigned char VARIABLE_IS_NOT_USED webkit_der[] = {
   0x30, 0x82, 0x05, 0x0d, 0x30, 0x82, 0x03, 0xf5, 0xa0, 0x03, 0x02, 0x01,
@@ -220,7 +222,9 @@
   0x8a
 };
 
-// thawte.com's cert (it's EV-licious!).
+// thawte.com 2008 Extended Validation cert. Lacks a SubjectAltName, but
+// contains www.thawte.com in the Subject CN field.
+
 unsigned char VARIABLE_IS_NOT_USED thawte_der[] = {
   0x30, 0x82, 0x04, 0xa5, 0x30, 0x82, 0x03, 0x8d, 0xa0, 0x03, 0x02, 0x01,
   0x02, 0x02, 0x10, 0x17, 0x76, 0x05, 0x88, 0x95, 0x58, 0xee, 0xbb, 0x00,
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index d8275c8..67040c41 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -110245,44 +110245,24 @@
     reason to believe that the system clock was behind.  Methods of detecting
     clock inaccuracy have changed over time.
   </int>
-  <int value="2"
-      label="WWW_SUBDOMAIN_MATCH: Difference between the URL and the DNS is
-             www">
-    This cause is recorded if the ssl error is CERT_COMMON_NAME_INVALID and the
-    hostname differs from one of the DNS names in the certificate (CN or SANs)
-    only by the presence or absence of the single-label prefix &quot;www&quot;.
-    This case is not recored if the host name is not a known TLD.
+  <int value="2" label="WWW_SUBDOMAIN_MATCH: (Deprecated)">
+    (Deprecated in favor of WWW_SUBDOMAIN_MATCH2)
   </int>
-  <int value="3" label="SUBDOMAIN_MATCH: The URL is a subdomain of the DNS">
-    This cause is recorded if the ssl error is CERT_COMMON_NAME_INVALID and the
-    difference between the URL and the DNS name is not &quot;www&quot;. This
-    case is not recorded if the host name is not a known TLD.
+  <int value="3" label="SUBDOMAIN_MATCH: (Deprecated)">
+    (Deprecated in favor of SUBDOMAIN_MATCH2)
   </int>
-  <int value="4"
-      label="SUBDOMAIN_INVERSE_MATCH: The DNS is a subdomain of the URL">
-    This cause is recorded if the ssl error is CERT_COMMON_NAME_INVALID and the
-    difference between the DNS name and the URL is not &quot;www&quot;. This
-    case is not recorded if the host name is not a known TLD.
+  <int value="4" label="SUBDOMAIN_INVERSE_MATCH: (Deprecated)">
+    (Deprecated in favor of SUBDOMAIN_INVERSE_MATCH2)
   </int>
-  <int value="5"
-      label="SUBDOMAIN_OUTSIDE_WILDCARD: The URL is outside the scope of the
-             wildcard certificate">
-    This cause is recorded only if the ssl error is CERT_COMMON_NAME_INVALID, we
-    have received a wildcard certificate and the scope of a wildcard certificate
-    is too narrow for the hostname. This case is not recorded if the host name
-    is not a known TLD.
+  <int value="5" label="SUBDOMAIN_OUTSIDE_WILDCARD: (Deprecated)">
+    (Deprecated in favor of SUBDOMAIN_OUTSIDE_WILDCARD2)
   </int>
   <int value="6"
       label="HOST_NAME_NOT_KNOWN_TLD: The host name is not a known TLD">
     This cause is recorded only for CERT_COMMON_NAME_INVALID errors.
   </int>
-  <int value="7"
-      label="LIKELY_MULTI_TENANT_HOSTING: The certificate is a shared
-             certificate">
-    This cause is recorded only for CERT_COMMON_NAME_INVALID errors. It is
-    possible that this error overlaps with others but it is not likely because
-    of the heuristics which decide as to what constitutes a shared certificate.
-    In cases of overlap, we emit to only one bucket.
+  <int value="7" label="LIKELY_MULTI_TENANT_HOSTING: (Deprecated)">
+    (Deprecated in favor of LIKELY_MULTI_TENANT_HOSTING2)
   </int>
   <int value="8" label="LOCALHOST: The user is trying to connect to local host">
     This cause is recorded only for CERT_AUTHORITY_INVALID errors.
@@ -110302,12 +110282,56 @@
   <int value="12" label="EXPIRED_RECENTLY: Cert expired within last 28 days.">
 
   </int>
-  <int value="13"
-      label="LIKELY_SAME_DOMAIN: Cert likely belongs to the same domain">
+  <int value="13" label="LIKELY_SAME_DOMAIN: (Deprecated)">
+    (Deprecated in favor of LIKELY_SAME_DOMAIN2)
+  </int>
+  <int value="14" label="NO_SUBJECT_ALT_NAME: Cert lacks SubjectAltName">
+    This case is recorded if the SSL error is CERT_COMMON_NAME_INVALID error and
+    the certificate does not specify any DNS names in a SubjectAltName
+    extension. (Chrome 58 deprecated matching hostnames to the SubjectCN Field.)
+  </int>
+  <int value="15"
+      label="WWW_SUBDOMAIN_MATCH2: Difference between the URL and the DNS is
+             www">
+    This cause is recorded if the SSL error is CERT_COMMON_NAME_INVALID and the
+    hostname differs from one of the DNS names in the certificate (SANs) only by
+    the presence or absence of the single-label prefix &quot;www&quot;. This
+    case is not recorded if the host name is not a known TLD.
+  </int>
+  <int value="16" label="SUBDOMAIN_MATCH2: The URL is a subdomain of the DNS">
+    This cause is recorded if the SSL error is CERT_COMMON_NAME_INVALID, the URL
+    hostname is a subdomain of a DNS name in the certificate, and the difference
+    between the URL and the DNS name is not &quot;www&quot;. This case is not
+    recorded if the host name is not a known TLD.
+  </int>
+  <int value="17"
+      label="SUBDOMAIN_INVERSE_MATCH2: The DNS is a subdomain of the URL">
+    This cause is recorded if the SSL error is CERT_COMMON_NAME_INVALID, a DNS
+    name in the certificate is a subdomain of the URL hostname, and the
+    difference between the DNS name and the URL is not &quot;www&quot;. This
+    case is not recorded if the host name is not a known TLD.
+  </int>
+  <int value="18"
+      label="SUBDOMAIN_OUTSIDE_WILDCARD2: The URL is outside the scope of the
+             wildcard certificate">
+    This cause is recorded only if the SSL error is CERT_COMMON_NAME_INVALID, we
+    have received a wildcard certificate and the scope of a wildcard certificate
+    is too narrow for the hostname. This case is not recorded if the host name
+    is not a known TLD.
+  </int>
+  <int value="19"
+      label="LIKELY_MULTI_TENANT_HOSTING2: The certificate is a shared
+             certificate">
+    This cause is recorded only for CERT_COMMON_NAME_INVALID errors where the
+    certificate contains numerous unrelated DNS names. This case is not recorded
+    if the host name is not a known TLD.
+  </int>
+  <int value="20"
+      label="LIKELY_SAME_DOMAIN2: Cert likely belongs to the same domain">
     This case is recorded if the SSL error is CERT_COMMON_NAME_INVALID error and
     the hostname in request URL has the same domain (effective TLD + 1 label) as
-    the common name or at least one of the subject alternative names in
-    certificate. This case is not recorded if the host name is not a known tld.
+    a SubjectAltName in the certificate. This case is not recorded if the host
+    name is not a known TLD.
   </int>
 </enum>
 
@@ -110317,11 +110341,12 @@
   <int value="2" label="SHOW_CAPTIVE_PORTAL_INTERSTITIAL_OVERRIDABLE"/>
   <int value="3" label="SHOW_SSL_INTERSTITIAL_NONOVERRIDABLE"/>
   <int value="4" label="SHOW_SSL_INTERSTITIAL_OVERRIDABLE"/>
-  <int value="5" label="WWW_MISMATCH_FOUND"/>
+  <int value="5" label="WWW_MISMATCH_FOUND (Deprecated)"/>
   <int value="6" label="WWW_MISMATCH_URL_AVAILABLE"/>
   <int value="7" label="WWW_MISMATCH_URL_NOT_AVAILABLE"/>
   <int value="8" label="SHOW_BAD_CLOCK"/>
   <int value="9" label="CAPTIVE_PORTAL_CERT_FOUND"/>
+  <int value="10" label="WWW_MISMATCH_FOUND_IN_SAN"/>
 </enum>
 
 <enum name="SSLErrorTypes" type="int">