Implement a skeleton of the Superfish interstitial

This CL adds SuperfishBlockingPage and SuperfishErrorUI classes to display the
Superfish interstitial. SSLErrorHandler checks for the Superfish certificate
fingerprint and uses a SuperfishBlockingPage (a subclass of SSLBlockingPage)
when it's present.

The strings displayed by SuperfishErrorUI are stubs for now; those will be
filled in to match the mocks in a follow-up.

SSLBlockingPage is modified a bit to allow a subclass that uses its own subclass
of SSLErrorUI. And SSLErrorUI is modified a bit to allow a subclass that uses
special strings.

The certificate error report format is updated to report when the Superfish
interstitial is shown (similar to how clock and captive portal interstitials are
reported). Certificate reporting for the Superfish interstitial is missing a
test; see https://crbug.com/735803.

This is all gated behind the SuperfishInterstitial Finch feature.

BUG=734590

Review-Url: https://codereview.chromium.org/2949003003
Cr-Commit-Position: refs/heads/master@{#481781}
diff --git a/chrome/browser/ssl/ssl_error_handler.cc b/chrome/browser/ssl/ssl_error_handler.cc
index 5353a1c..a0a82506 100644
--- a/chrome/browser/ssl/ssl_error_handler.cc
+++ b/chrome/browser/ssl/ssl_error_handler.cc
@@ -59,6 +59,9 @@
 const base::Feature kSSLCommonNameMismatchHandling{
     "SSLCommonNameMismatchHandling", base::FEATURE_ENABLED_BY_DEFAULT};
 
+const base::Feature kSuperfishInterstitial{"SuperfishInterstitial",
+                                           base::FEATURE_DISABLED_BY_DEFAULT};
+
 // Default delay in milliseconds before displaying the SSL interstitial.
 // This can be changed in tests.
 // - If there is a name mismatch and a suggested URL available result arrives
@@ -70,26 +73,34 @@
 
 const char kHistogram[] = "interstitial.ssl_error_handler";
 
-// Records an UMA histogram for whether the Superfish SPKI was present in the
-// certificate chain.
-void RecordSuperfishUMA(const net::HashValueVector& public_key_hashes) {
-  // This is the SPKI hash of the well-known Superfish certificate at
-  // https://pastebin.com/WcXv8QcG.
-  const char kSuperfishSPKI[] =
-      "sha256/S7jzW6HhJvjd4bDEIGJe2G3OYae92tveqauleP8TFF4=";
-  net::HashValue superfish_spki_hash_value;
-  if (!superfish_spki_hash_value.FromString(kSuperfishSPKI)) {
-    NOTREACHED();
-  }
-  bool found_superfish = false;
-  for (const auto& hash : public_key_hashes) {
-    if (hash == superfish_spki_hash_value) {
-      found_superfish = true;
-      break;
+bool IsSuperfish(const scoped_refptr<net::X509Certificate>& cert) {
+  // This is the fingerprint of the well-known Superfish certificate at
+  // https://pastebin.com/WcXv8QcG. Superfish is identified by certificate
+  // fingerprint rather than SPKI because net::SSLInfo does not guarantee
+  // |public_key_hashes| (the SPKIs) to be populated if the certificate doesn't
+  // verify successfully. It so happens that Superfish uses the same certificate
+  // universally (not just the same public key), and calculating the fingerprint
+  // is more convenient here than calculating the SPKI.
+  const net::SHA256HashValue kSuperfishFingerprint{
+      {0xB6, 0xFE, 0x91, 0x51, 0x40, 0x2B, 0xAD, 0x1C, 0x06, 0xD7, 0xE6,
+       0x6D, 0xB6, 0x7A, 0x26, 0xAA, 0x73, 0x56, 0xF2, 0xE6, 0xC6, 0x44,
+       0xDB, 0xCF, 0x9F, 0x98, 0x96, 0x8F, 0xF6, 0x32, 0xE1, 0xB7}};
+  for (const net::X509Certificate::OSCertHandle& intermediate :
+       cert->GetIntermediateCertificates()) {
+    net::SHA256HashValue hash =
+        net::X509Certificate::CalculateFingerprint256(intermediate);
+    if (hash == kSuperfishFingerprint) {
+      return true;
     }
   }
+  return false;
+}
+
+// Records an UMA histogram for whether the Superfish certificate was present in
+// the certificate chain.
+void RecordSuperfishUMA(const scoped_refptr<net::X509Certificate>& cert) {
   UMA_HISTOGRAM_BOOLEAN("interstitial.ssl_error_handler.superfish",
-                        found_superfish);
+                        IsSuperfish(cert));
 }
 
 // Adds a message to console after navigation commits and then, deletes itself.
@@ -447,9 +458,12 @@
 
 void SSLErrorHandlerDelegateImpl::ShowSSLInterstitial() {
   // Show SSL blocking page. The interstitial owns the blocking page.
-  (SSLBlockingPage::Create(web_contents_, cert_error_, ssl_info_, request_url_,
-                           options_mask_, base::Time::NowFromSystemTime(),
-                           std::move(ssl_cert_reporter_), callback_))
+  (SSLBlockingPage::Create(
+       web_contents_, cert_error_, ssl_info_, request_url_, options_mask_,
+       base::Time::NowFromSystemTime(), std::move(ssl_cert_reporter_),
+       base::FeatureList::IsEnabled(kSuperfishInterstitial) &&
+           IsSuperfish(ssl_info_.cert),
+       callback_))
       ->Show();
 }
 
@@ -563,7 +577,7 @@
 void SSLErrorHandler::StartHandlingError() {
   RecordUMA(HANDLE_ALL);
 
-  RecordSuperfishUMA(ssl_info_.public_key_hashes);
+  RecordSuperfishUMA(ssl_info_.cert);
 
   if (ssl_errors::ErrorInfo::NetErrorToErrorType(cert_error_) ==
       ssl_errors::ErrorInfo::CERT_DATE_INVALID) {