Bug 1293231 - Certificate Transparency - basic telemetry reports; r=keeler, r=Cykesiopka draft
authorSergei Chernov <sergei.cv@ndivi.com>
Thu, 11 Aug 2016 13:41:50 +0300
changeset 417945 5c7e92e4bc4a708321258abaabe2f66228b02707
parent 417914 66a77b9bfe5dcacd50eccf85de7c0e7e15ce0ffd
child 532210 9b357d3b890d3387897e79c236873ac402b64275
push id30534
push usersergei.cv@ndivi.com
push dateTue, 27 Sep 2016 11:22:38 +0000
reviewerskeeler, Cykesiopka
bugs1293231
milestone52.0a1
Bug 1293231 - Certificate Transparency - basic telemetry reports; r=keeler, r=Cykesiopka MozReview-Commit-ID: EGvuZADObJo
config/external/nss/nss.symbols
netwerk/base/security-prefs.js
security/certverifier/CTKnownLogs.h
security/certverifier/CTSerialization.cpp
security/certverifier/CTVerifyResult.cpp
security/certverifier/CTVerifyResult.h
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/MultiLogCTVerifier.cpp
security/certverifier/MultiLogCTVerifier.h
security/certverifier/NSSCertDBTrustDomain.cpp
security/certverifier/NSSCertDBTrustDomain.h
security/certverifier/SignedCertificateTimestamp.h
security/certverifier/moz.build
security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/SharedCertVerifier.h
security/manager/ssl/SharedSSLState.h
security/manager/ssl/nsNSSCertificate.cpp
security/manager/ssl/nsNSSCertificateDB.cpp
security/manager/ssl/nsNSSComponent.cpp
security/manager/ssl/nsNSSIOLayer.cpp
security/manager/ssl/nsSiteSecurityService.cpp
toolkit/components/telemetry/Histograms.json
--- a/config/external/nss/nss.symbols
+++ b/config/external/nss/nss.symbols
@@ -674,16 +674,17 @@ SSL_HandshakeNegotiatedExtension
 SSL_ImplementedCiphers @DATA@
 SSL_ImportFD
 SSL_NamedGroupConfig
 SSL_NumImplementedCiphers @DATA@
 SSL_OptionSet
 SSL_OptionSetDefault
 SSL_PeerCertificate
 SSL_PeerCertificateChain
+SSL_PeerSignedCertTimestamps
 SSL_PeerStapledOCSPResponses
 SSL_ResetHandshake
 SSL_SendAdditionalKeyShares
 SSL_SetCanFalseStartCallback
 SSL_SetDowngradeCheckVersion
 SSL_SetNextProtoNego
 SSL_SetPKCS11PinArg
 SSL_SetSockPeerID
--- a/netwerk/base/security-prefs.js
+++ b/netwerk/base/security-prefs.js
@@ -81,16 +81,21 @@ pref("security.pki.name_matching_mode", 
 // 2: similarly, but for 23 August 2015
 // 3: it is never considered equivalent
 #ifdef RELEASE_BUILD
 pref("security.pki.netscape_step_up_policy", 1);
 #else
 pref("security.pki.netscape_step_up_policy", 2);
 #endif
 
+// Configures Certificate Transparency support mode:
+// 0: Fully disabled.
+// 1: Only collect telemetry. CT qualification checks are not performed.
+pref("security.pki.certificate_transparency.mode", 1);
+
 pref("security.webauth.u2f", false);
 pref("security.webauth.u2f_enable_softtoken", false);
 pref("security.webauth.u2f_enable_usbtoken", false);
 
 pref("security.ssl.errorReporting.enabled", true);
 pref("security.ssl.errorReporting.url", "https://incoming.telemetry.mozilla.org/submit/sslreports/");
 pref("security.ssl.errorReporting.automatic", false);
 
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTKnownLogs.h
@@ -0,0 +1,155 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* This file is generated by print_log_list.py from
+ * https://github.com/google/certificate-transparency/ */
+
+#ifndef CTKnownLogs_h
+#define CTKnownLogs_h
+
+#include <stddef.h>
+
+struct CTLogInfo {
+  const char* const logName;
+  const char* const logUrl;
+  const char* const logKey;
+  const size_t logKeyLength;
+};
+
+const CTLogInfo kCTLogList[] = {
+  { "Google 'Pilot' log",
+    "https://ct.googleapis.com/pilot/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x7d\xa8\x4b\x12\x29\x80\xa3\x3d\xad"
+    "\xd3\x5a\x77\xb8\xcc\xe2\x88\xb3\xa5\xfd\xf1\xd3\x0c\xcd\x18\x0c\xe8\x41"
+    "\x46\xe8\x81\x01\x1b\x15\xe1\x4b\xf1\x1b\x62\xdd\x36\x0a\x08\x18\xba\xed"
+    "\x0b\x35\x84\xd0\x9e\x40\x3c\x2d\x9e\x9b\x82\x65\xbd\x1f\x04\x10\x41\x4c"
+    "\xa0",
+    91 },
+  { "Google 'Aviator' log",
+    "https://ct.googleapis.com/aviator/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd7\xf4\xcc\x69\xb2\xe4\x0e\x90\xa3"
+    "\x8a\xea\x5a\x70\x09\x4f\xef\x13\x62\xd0\x8d\x49\x60\xff\x1b\x40\x50\x07"
+    "\x0c\x6d\x71\x86\xda\x25\x49\x8d\x65\xe1\x08\x0d\x47\x34\x6b\xbd\x27\xbc"
+    "\x96\x21\x3e\x34\xf5\x87\x76\x31\xb1\x7f\x1d\xc9\x85\x3b\x0d\xf7\x1f\x3f"
+    "\xe9",
+    91 },
+  { "DigiCert Log Server",
+    "https://ct1.digicert-ct.com/log/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x02\x46\xc5\xbe\x1b\xbb\x82\x40\x16"
+    "\xe8\xc1\xd2\xac\x19\x69\x13\x59\xf8\xf8\x70\x85\x46\x40\xb9\x38\xb0\x23"
+    "\x82\xa8\x64\x4c\x7f\xbf\xbb\x34\x9f\x4a\x5f\x28\x8a\xcf\x19\xc4\x00\xf6"
+    "\x36\x06\x93\x65\xed\x4c\xf5\xa9\x21\x62\x5a\xd8\x91\xeb\x38\x24\x40\xac"
+    "\xe8",
+    91 },
+  { "Google 'Rocketeer' log",
+    "https://ct.googleapis.com/rocketeer/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x20\x5b\x18\xc8\x3c\xc1\x8b\xb3\x31"
+    "\x08\x00\xbf\xa0\x90\x57\x2b\xb7\x47\x8c\x6f\xb5\x68\xb0\x8e\x90\x78\xe9"
+    "\xa0\x73\xea\x4f\x28\x21\x2e\x9c\xc0\xf4\x16\x1b\xaa\xf9\xd5\xd7\xa9\x80"
+    "\xc3\x4e\x2f\x52\x3c\x98\x01\x25\x46\x24\x25\x28\x23\x77\x2d\x05\xc2\x40"
+    "\x7a",
+    91 },
+  { "Certly.IO log",
+    "https://log.certly.io/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x0b\x23\xcb\x85\x62\x98\x61\x48\x04"
+    "\x73\xeb\x54\x5d\xf3\xd0\x07\x8c\x2d\x19\x2d\x8c\x36\xf5\xeb\x8f\x01\x42"
+    "\x0a\x7c\x98\x26\x27\xc1\xb5\xdd\x92\x93\xb0\xae\xf8\x9b\x3d\x0c\xd8\x4c"
+    "\x4e\x1d\xf9\x15\xfb\x47\x68\x7b\xba\x66\xb7\x25\x9c\xd0\x4a\xc2\x66\xdb"
+    "\x48",
+    91 },
+  { "Izenpe log",
+    "https://ct.izenpe.com/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x27\x64\x39\x0c\x2d\xdc\x50\x18\xf8"
+    "\x21\x00\xa2\x0e\xed\x2c\xea\x3e\x75\xba\x9f\x93\x64\x09\x00\x11\xc4\x11"
+    "\x17\xab\x5c\xcf\x0f\x74\xac\xb5\x97\x90\x93\x00\x5b\xb8\xeb\xf7\x27\x3d"
+    "\xd9\xb2\x0a\x81\x5f\x2f\x0d\x75\x38\x94\x37\x99\x1e\xf6\x07\x76\xe0\xee"
+    "\xbe",
+    91 },
+  { "Symantec log",
+    "https://ct.ws.symantec.com/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x96\xea\xac\x1c\x46\x0c\x1b\x55\xdc"
+    "\x0d\xfc\xb5\x94\x27\x46\x57\x42\x70\x3a\x69\x18\xe2\xbf\x3b\xc4\xdb\xab"
+    "\xa0\xf4\xb6\x6c\xc0\x53\x3f\x4d\x42\x10\x33\xf0\x58\x97\x8f\x6b\xbe\x72"
+    "\xf4\x2a\xec\x1c\x42\xaa\x03\x2f\x1a\x7e\x28\x35\x76\x99\x08\x3d\x21\x14"
+    "\x86",
+    91 },
+  { "Venafi log",
+    "https://ctlog.api.venafi.com/",
+    "\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05"
+    "\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xa2\x5a\x48"
+    "\x1f\x17\x52\x95\x35\xcb\xa3\x5b\x3a\x1f\x53\x82\x76\x94\xa3\xff\x80\xf2"
+    "\x1c\x37\x3c\xc0\xb1\xbd\xc1\x59\x8b\xab\x2d\x65\x93\xd7\xf3\xe0\x04\xd5"
+    "\x9a\x6f\xbf\xd6\x23\x76\x36\x4f\x23\x99\xcb\x54\x28\xad\x8c\x15\x4b\x65"
+    "\x59\x76\x41\x4a\x9c\xa6\xf7\xb3\x3b\x7e\xb1\xa5\x49\xa4\x17\x51\x6c\x80"
+    "\xdc\x2a\x90\x50\x4b\x88\x24\xe9\xa5\x12\x32\x93\x04\x48\x90\x02\xfa\x5f"
+    "\x0e\x30\x87\x8e\x55\x76\x05\xee\x2a\x4c\xce\xa3\x6a\x69\x09\x6e\x25\xad"
+    "\x82\x76\x0f\x84\x92\xfa\x38\xd6\x86\x4e\x24\x8f\x9b\xb0\x72\xcb\x9e\xe2"
+    "\x6b\x3f\xe1\x6d\xc9\x25\x75\x23\x88\xa1\x18\x58\x06\x23\x33\x78\xda\x00"
+    "\xd0\x38\x91\x67\xd2\xa6\x7d\x27\x97\x67\x5a\xc1\xf3\x2f\x17\xe6\xea\xd2"
+    "\x5b\xe8\x81\xcd\xfd\x92\x68\xe7\xf3\x06\xf0\xe9\x72\x84\xee\x01\xa5\xb1"
+    "\xd8\x33\xda\xce\x83\xa5\xdb\xc7\xcf\xd6\x16\x7e\x90\x75\x18\xbf\x16\xdc"
+    "\x32\x3b\x6d\x8d\xab\x82\x17\x1f\x89\x20\x8d\x1d\x9a\xe6\x4d\x23\x08\xdf"
+    "\x78\x6f\xc6\x05\xbf\x5f\xae\x94\x97\xdb\x5f\x64\xd4\xee\x16\x8b\xa3\x84"
+    "\x6c\x71\x2b\xf1\xab\x7f\x5d\x0d\x32\xee\x04\xe2\x90\xec\x41\x9f\xfb\x39"
+    "\xc1\x02\x03\x01\x00\x01",
+    294 },
+  { "Symantec 'Vega' log",
+    "https://vega.ws.symantec.com/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xea\x95\x9e\x02\xff\xee\xf1\x33\x6d"
+    "\x4b\x87\xbc\xcd\xfd\x19\x17\x62\xff\x94\xd3\xd0\x59\x07\x3f\x02\x2d\x1c"
+    "\x90\xfe\xc8\x47\x30\x3b\xf1\xdd\x0d\xb8\x11\x0c\x5d\x1d\x86\xdd\xab\xd3"
+    "\x2b\x46\x66\xfb\x6e\x65\xb7\x3b\xfd\x59\x68\xac\xdf\xa6\xf8\xce\xd2\x18"
+    "\x4d",
+    91 },
+  { "CNNIC CT log",
+    "https://ctserver.cnnic.cn/",
+    "\x30\x82\x01\x22\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05"
+    "\x00\x03\x82\x01\x0f\x00\x30\x82\x01\x0a\x02\x82\x01\x01\x00\xbf\xb5\x08"
+    "\x61\x9a\x29\x32\x04\xd3\x25\x63\xe9\xd8\x85\xe1\x86\xe0\x1f\xd6\x5e\x9a"
+    "\xf7\x33\x3b\x80\x1b\xe7\xb6\x3e\x5f\x2d\xa1\x66\xf6\x95\x4a\x84\xa6\x21"
+    "\x56\x79\xe8\xf7\x85\xee\x5d\xe3\x7c\x12\xc0\xe0\x89\x22\x09\x22\x3e\xba"
+    "\x16\x95\x06\xbd\xa8\xb9\xb1\xa9\xb2\x7a\xd6\x61\x2e\x87\x11\xb9\x78\x40"
+    "\x89\x75\xdb\x0c\xdc\x90\xe0\xa4\x79\xd6\xd5\x5e\x6e\xd1\x2a\xdb\x34\xf4"
+    "\x99\x3f\x65\x89\x3b\x46\xc2\x29\x2c\x15\x07\x1c\xc9\x4b\x1a\x54\xf8\x6c"
+    "\x1e\xaf\x60\x27\x62\x0a\x65\xd5\x9a\xb9\x50\x36\x16\x6e\x71\xf6\x1f\x01"
+    "\xf7\x12\xa7\xfc\xbf\xf6\x21\xa3\x29\x90\x86\x2d\x77\xde\xbb\x4c\xd4\xcf"
+    "\xfd\xd2\xcf\x82\x2c\x4d\xd4\xf2\xc2\x2d\xac\xa9\xbe\xea\xc3\x19\x25\x43"
+    "\xb2\xe5\x9a\x6c\x0d\xc5\x1c\xa5\x8b\xf7\x3f\x30\xaf\xb9\x01\x91\xb7\x69"
+    "\x12\x12\xe5\x83\x61\xfe\x34\x00\xbe\xf6\x71\x8a\xc7\xeb\x50\x92\xe8\x59"
+    "\xfe\x15\x91\xeb\x96\x97\xf8\x23\x54\x3f\x2d\x8e\x07\xdf\xee\xda\xb3\x4f"
+    "\xc8\x3c\x9d\x6f\xdf\x3c\x2c\x43\x57\xa1\x47\x0c\x91\x04\xf4\x75\x4d\xda"
+    "\x89\x81\xa4\x14\x06\x34\xb9\x98\xc3\xda\xf1\xfd\xed\x33\x36\xd3\x16\x2d"
+    "\x35\x02\x03\x01\x00\x01",
+    294 },
+  { "WoSign log",
+    "https://ctlog.wosign.com/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xcc\x11\x88\x7b\x2d\x66\xcb\xae\x8f"
+    "\x4d\x30\x66\x27\x19\x25\x22\x93\x21\x46\xb4\x2f\x01\xd3\xc6\xf9\x2b\xd5"
+    "\xc8\xba\x73\x9b\x06\xa2\xf0\x8a\x02\x9c\xd0\x6b\x46\x18\x30\x85\xba\xe9"
+    "\x24\x8b\x0e\xd1\x5b\x70\x28\x0c\x7e\xf1\x3a\x45\x7f\x5a\xf3\x82\x42\x60"
+    "\x31",
+    91 },
+  { "StartCom log",
+    "https://ct.startssl.com/",
+    "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48"
+    "\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x48\xf3\x59\xf3\xf6\x05\x18\xd3\xdb"
+    "\xb2\xed\x46\x7e\xcf\xc8\x11\xb5\x57\xb1\xa8\xd6\x4c\xe6\x9f\xb7\x4a\x1a"
+    "\x14\x86\x43\xa9\x48\xb0\xcb\x5a\x3f\x3c\x4a\xca\xdf\xc4\x82\x14\x55\x9a"
+    "\xf8\xf7\x8e\x40\x55\xdc\xf4\xd2\xaf\xea\x75\x74\xfb\x4e\x7f\x60\x86\x2e"
+    "\x51",
+    91 }
+};
+
+#endif // CTKnownLogs_h
--- a/security/certverifier/CTSerialization.cpp
+++ b/security/certverifier/CTSerialization.cpp
@@ -504,16 +504,20 @@ DecodeSignedCertificateTimestamp(Reader&
     return rv;
   }
   rv = InputToBuffer(extensions, result.extensions);
   if (rv != Success) {
     return rv;
   }
   result.timestamp = timestamp;
 
+  result.origin = SignedCertificateTimestamp::Origin::Unknown;
+  result.verificationStatus =
+    SignedCertificateTimestamp::VerificationStatus::None;
+
   output = Move(result);
   return Success;
 }
 
 Result
 EncodeSCTList(const Vector<pkix::Input>& scts, Buffer& output)
 {
   // Find out the total size of the SCT list to be written so we can
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTVerifyResult.cpp
@@ -0,0 +1,18 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CTVerifyResult.h"
+
+namespace mozilla { namespace ct {
+
+void
+CTVerifyResult::Reset()
+{
+  scts.clear();
+  decodingErrors = 0;
+}
+
+} } // namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTVerifyResult.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef CTVerifyResult_h
+#define CTVerifyResult_h
+
+#include "mozilla/Vector.h"
+#include "SignedCertificateTimestamp.h"
+
+namespace mozilla { namespace ct {
+
+typedef Vector<SignedCertificateTimestamp> SCTList;
+
+// Holds Signed Certificate Timestamps verification results.
+class CTVerifyResult
+{
+public:
+  // SCTs that were processed during the verification. For each SCT,
+  // the verification result is stored in its |verificationStatus| field.
+  SCTList scts;
+
+  // The verifier makes the best effort to extract the available SCTs
+  // from the binary sources provided to it.
+  // If some SCT cannot be extracted due to encoding errors, the verifier
+  // proceeds to the next available one. In other words, decoding errors are
+  // effectively ignored.
+  // Note that a serialized SCT may fail to decode for a "legitimate" reason,
+  // e.g. if the SCT is from a future version of the Certificate Transparency
+  // standard.
+  // |decodingErrors| field counts the errors of the above kind.
+  size_t decodingErrors;
+
+  void Reset();
+};
+
+} } // namespace mozilla::ct
+
+#endif  // CTVerifyResult_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -4,31 +4,35 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CertVerifier.h"
 
 #include <stdint.h>
 
 #include "BRNameMatchingPolicy.h"
+#include "CTKnownLogs.h"
 #include "ExtendedValidation.h"
+#include "MultiLogCTVerifier.h"
 #include "NSSCertDBTrustDomain.h"
 #include "NSSErrorsService.h"
 #include "cert.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "nsNSSComponent.h"
 #include "nsServiceManagerUtils.h"
 #include "pk11pub.h"
 #include "pkix/pkix.h"
 #include "pkix/pkixnss.h"
 #include "prerror.h"
 #include "secerr.h"
 #include "secmod.h"
 #include "sslerr.h"
 
+using namespace mozilla::ct;
 using namespace mozilla::pkix;
 using namespace mozilla::psm;
 
 mozilla::LazyLogModule gCertVerifierLog("certverifier");
 
 namespace mozilla { namespace psm {
 
 const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
@@ -37,26 +41,29 @@ const CertVerifier::Flags CertVerifier::
 
 CertVerifier::CertVerifier(OcspDownloadConfig odc,
                            OcspStrictConfig osc,
                            OcspGetConfig ogc,
                            uint32_t certShortLifetimeInDays,
                            PinningMode pinningMode,
                            SHA1Mode sha1Mode,
                            BRNameMatchingPolicy::Mode nameMatchingMode,
-                           NetscapeStepUpPolicy netscapeStepUpPolicy)
+                           NetscapeStepUpPolicy netscapeStepUpPolicy,
+                           CertificateTransparencyMode ctMode)
   : mOCSPDownloadConfig(odc)
   , mOCSPStrict(osc == ocspStrict)
   , mOCSPGETEnabled(ogc == ocspGetEnabled)
   , mCertShortLifetimeInDays(certShortLifetimeInDays)
   , mPinningMode(pinningMode)
   , mSHA1Mode(sha1Mode)
   , mNameMatchingMode(nameMatchingMode)
   , mNetscapeStepUpPolicy(netscapeStepUpPolicy)
+  , mCTMode(ctMode)
 {
+  LoadKnownCTLogs();
 }
 
 CertVerifier::~CertVerifier()
 {
 }
 
 Result
 IsCertChainRootBuiltInRoot(const UniqueCERTCertList& chain, bool& result)
@@ -116,41 +123,187 @@ static Result
 BuildCertChainForOneKeyUsage(NSSCertDBTrustDomain& trustDomain, Input certDER,
                              Time time, KeyUsage ku1, KeyUsage ku2,
                              KeyUsage ku3, KeyPurposeId eku,
                              const CertPolicyId& requiredPolicy,
                              const Input* stapledOCSPResponse,
                              /*optional out*/ CertVerifier::OCSPStaplingStatus*
                                                 ocspStaplingStatus)
 {
-  trustDomain.ResetOCSPStaplingStatus();
+  trustDomain.ResetAccumulatedState();
   Result rv = BuildCertChain(trustDomain, certDER, time,
                              EndEntityOrCA::MustBeEndEntity, ku1,
                              eku, requiredPolicy, stapledOCSPResponse);
   if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
-    trustDomain.ResetOCSPStaplingStatus();
+    trustDomain.ResetAccumulatedState();
     rv = BuildCertChain(trustDomain, certDER, time,
                         EndEntityOrCA::MustBeEndEntity, ku2,
                         eku, requiredPolicy, stapledOCSPResponse);
     if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
-      trustDomain.ResetOCSPStaplingStatus();
+      trustDomain.ResetAccumulatedState();
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity, ku3,
                           eku, requiredPolicy, stapledOCSPResponse);
       if (rv != Success) {
         rv = Result::ERROR_INADEQUATE_KEY_USAGE;
       }
     }
   }
   if (ocspStaplingStatus) {
     *ocspStaplingStatus = trustDomain.GetOCSPStaplingStatus();
   }
   return rv;
 }
 
+void
+CertVerifier::LoadKnownCTLogs()
+{
+  mCTVerifier = MakeUnique<MultiLogCTVerifier>();
+  for (const CTLogInfo& log : kCTLogList) {
+    Input publicKey;
+    Result rv = publicKey.Init(
+      BitwiseCast<const uint8_t*, const char*>(log.logKey), log.logKeyLength);
+    if (rv != Success) {
+      MOZ_ASSERT_UNREACHABLE("Failed reading a log key for a known CT Log");
+      continue;
+    }
+    rv = mCTVerifier->AddLog(publicKey);
+    if (rv != Success) {
+      MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log");
+      continue;
+    }
+  }
+}
+
+Result
+CertVerifier::VerifySignedCertificateTimestamps(
+  NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& builtChain,
+  Input sctsFromTLS, Time time,
+  /*optional out*/ CertificateTransparencyInfo* ctInfo)
+{
+  if (ctInfo) {
+    ctInfo->Reset();
+  }
+  if (mCTMode == CertificateTransparencyMode::Disabled) {
+    return Success;
+  }
+  if (ctInfo) {
+    ctInfo->enabled = true;
+  }
+
+  if (!builtChain || CERT_LIST_EMPTY(builtChain)) {
+    return Result::FATAL_ERROR_INVALID_ARGS;
+  }
+
+  bool gotScts = false;
+  Input embeddedSCTs = trustDomain.GetSCTListFromCertificate();
+  if (embeddedSCTs.GetLength() > 0) {
+    gotScts = true;
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("Got embedded SCT data of length %zu\n",
+              static_cast<size_t>(embeddedSCTs.GetLength())));
+  }
+  Input sctsFromOCSP = trustDomain.GetSCTListFromOCSPStapling();
+  if (sctsFromOCSP.GetLength() > 0) {
+    gotScts = true;
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("Got OCSP SCT data of length %zu\n",
+              static_cast<size_t>(sctsFromOCSP.GetLength())));
+  }
+  if (sctsFromTLS.GetLength() > 0) {
+    gotScts = true;
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("Got TLS SCT data of length %zu\n",
+              static_cast<size_t>(sctsFromTLS.GetLength())));
+  }
+  if (!gotScts) {
+    return Success;
+  }
+
+  CERTCertListNode* endEntityNode = CERT_LIST_HEAD(builtChain);
+  if (!endEntityNode) {
+    return Result::FATAL_ERROR_INVALID_ARGS;
+  }
+  CERTCertListNode* issuerNode = CERT_LIST_NEXT(endEntityNode);
+  if (!issuerNode) {
+    // Issuer certificate is required for SCT verification.
+    return Success;
+  }
+
+  CERTCertificate* endEntity = endEntityNode->cert;
+  CERTCertificate* issuer = issuerNode->cert;
+  if (!endEntity || !issuer) {
+    return Result::FATAL_ERROR_INVALID_ARGS;
+  }
+
+  Input endEntityDER;
+  Result rv = endEntityDER.Init(endEntity->derCert.data,
+                                endEntity->derCert.len);
+  if (rv != Success) {
+    return rv;
+  }
+
+  Input issuerPublicKeyDER;
+  rv = issuerPublicKeyDER.Init(issuer->derPublicKey.data,
+                               issuer->derPublicKey.len);
+  if (rv != Success) {
+    return rv;
+  }
+
+  CTVerifyResult result;
+  rv = mCTVerifier->Verify(endEntityDER, issuerPublicKeyDER,
+                           embeddedSCTs, sctsFromOCSP, sctsFromTLS, time,
+                           result);
+  if (rv != Success) {
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("SCT verification failed with fatal error %i\n", rv));
+    return rv;
+  }
+
+  if (MOZ_LOG_TEST(gCertVerifierLog, LogLevel::Debug)) {
+    size_t verifiedCount = 0;
+    size_t unknownLogCount = 0;
+    size_t invalidSignatureCount = 0;
+    size_t invalidTimestampCount = 0;
+    for (const SignedCertificateTimestamp& sct : result.scts) {
+      switch (sct.verificationStatus) {
+        case SignedCertificateTimestamp::VerificationStatus::OK:
+          verifiedCount++;
+          break;
+        case SignedCertificateTimestamp::VerificationStatus::UnknownLog:
+          unknownLogCount++;
+          break;
+        case SignedCertificateTimestamp::VerificationStatus::InvalidSignature:
+          invalidSignatureCount++;
+          break;
+        case SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp:
+          invalidTimestampCount++;
+          break;
+        case SignedCertificateTimestamp::VerificationStatus::None:
+        default:
+          MOZ_ASSERT_UNREACHABLE("Unexpected SCT verificationStatus");
+      }
+    }
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("SCT verification result: "
+             "verified=%zu unknownLog=%zu "
+             "invalidSignature=%zu invalidTimestamp=%zu "
+             "decodingErrors=%zu\n",
+             verifiedCount, unknownLogCount,
+             invalidSignatureCount, invalidTimestampCount,
+             result.decodingErrors));
+  }
+
+  if (ctInfo) {
+    ctInfo->processedSCTs = true;
+    ctInfo->verifyResult = Move(result);
+  }
+  return Success;
+}
+
 bool
 CertVerifier::SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode)
 {
   switch (mSHA1Mode) {
     case SHA1Mode::Forbidden:
       return mode != SHA1Mode::Forbidden;
     case SHA1Mode::ImportedRoot:
       return mode != SHA1Mode::Forbidden && mode != SHA1Mode::ImportedRoot;
@@ -170,21 +323,23 @@ static const unsigned int MIN_RSA_BITS =
 static const unsigned int MIN_RSA_BITS_WEAK = 1024;
 
 SECStatus
 CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
                          Time time, void* pinArg, const char* hostname,
                  /*out*/ UniqueCERTCertList& builtChain,
             /*optional*/ const Flags flags,
             /*optional*/ const SECItem* stapledOCSPResponseSECItem,
+            /*optional*/ const SECItem* sctsFromTLSSECItem,
         /*optional out*/ SECOidTag* evOidPolicy,
         /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
         /*optional out*/ KeySizeStatus* keySizeStatus,
         /*optional out*/ SHA1ModeResult* sha1ModeResult,
-        /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
+        /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
+        /*optional out*/ CertificateTransparencyInfo* ctInfo)
 {
   MOZ_LOG(gCertVerifierLog, LogLevel::Debug, ("Top of VerifyCert\n"));
 
   PR_ASSERT(cert);
   PR_ASSERT(usage == certificateUsageSSLServer || !(flags & FLAG_MUST_BE_EV));
   PR_ASSERT(usage == certificateUsageSSLServer || !keySizeStatus);
   PR_ASSERT(usage == certificateUsageSSLServer || !sha1ModeResult);
 
@@ -250,16 +405,25 @@ CertVerifier::VerifyCert(CERTCertificate
     if (rv != Success) {
       // The stapled OCSP response was too big.
       PR_SetError(SEC_ERROR_OCSP_MALFORMED_RESPONSE, 0);
       return SECFailure;
     }
     stapledOCSPResponse = &stapledOCSPResponseInput;
   }
 
+  Input sctsFromTLSInput;
+  if (sctsFromTLSSECItem) {
+    rv = sctsFromTLSInput.Init(sctsFromTLSSECItem->data,
+                               sctsFromTLSSECItem->len);
+    // Silently discard the error of the extension being too big,
+    // do not fail the verification.
+    MOZ_ASSERT(rv == Success);
+  }
+
   switch (usage) {
     case certificateUsageSSLClient: {
       // XXX: We don't really have a trust bit for SSL client authentication so
       // just use trustEmail as it is the closest alternative.
       NSSCertDBTrustDomain trustDomain(trustEmail, defaultOCSPFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        mCertShortLifetimeInDays,
                                        pinningDisabled, MIN_RSA_BITS_WEAK,
@@ -363,16 +527,22 @@ CertVerifier::VerifyCert(CERTCertificate
           MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
                   ("cert is EV with status %i\n", sha1ModeResults[i]));
           if (evOidPolicy) {
             *evOidPolicy = evPolicyOidTag;
           }
           if (sha1ModeResult) {
             *sha1ModeResult = sha1ModeResults[i];
           }
+          rv = VerifySignedCertificateTimestamps(trustDomain, builtChain,
+                                                 sctsFromTLSInput, time,
+                                                 ctInfo);
+          if (rv != Success) {
+            break;
+          }
         }
       }
       if (rv == Success) {
         break;
       }
 #endif
 
       if (flags & FLAG_MUST_BE_EV) {
@@ -444,16 +614,22 @@ CertVerifier::VerifyCert(CERTCertificate
           }
           if (rv == Success) {
             if (keySizeStatus) {
               *keySizeStatus = keySizeStatuses[i];
             }
             if (sha1ModeResult) {
               *sha1ModeResult = sha1ModeResults[j];
             }
+            rv = VerifySignedCertificateTimestamps(trustDomain, builtChain,
+                                                   sctsFromTLSInput, time,
+                                                   ctInfo);
+            if (rv != Success) {
+              break;
+            }
           }
         }
       }
 
       if (rv == Success) {
         break;
       }
 
@@ -626,27 +802,29 @@ CertVerifier::VerifyCert(CERTCertificate
   }
 
   return SECSuccess;
 }
 
 SECStatus
 CertVerifier::VerifySSLServerCert(const UniqueCERTCertificate& peerCert,
                      /*optional*/ const SECItem* stapledOCSPResponse,
+                     /*optional*/ const SECItem* sctsFromTLS,
                                   Time time,
                      /*optional*/ void* pinarg,
                                   const char* hostname,
                           /*out*/ UniqueCERTCertList& builtChain,
                      /*optional*/ bool saveIntermediatesInPermanentDatabase,
                      /*optional*/ Flags flags,
                  /*optional out*/ SECOidTag* evOidPolicy,
                  /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus,
                  /*optional out*/ KeySizeStatus* keySizeStatus,
                  /*optional out*/ SHA1ModeResult* sha1ModeResult,
-                 /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
+                 /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo,
+                 /*optional out*/ CertificateTransparencyInfo* ctInfo)
 {
   PR_ASSERT(peerCert);
   // XXX: PR_ASSERT(pinarg)
   PR_ASSERT(hostname);
   PR_ASSERT(hostname[0]);
 
   if (evOidPolicy) {
     *evOidPolicy = SEC_OID_UNKNOWN;
@@ -656,19 +834,20 @@ CertVerifier::VerifySSLServerCert(const 
     PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0);
     return SECFailure;
   }
 
   // CreateCertErrorRunnable assumes that CheckCertHostname is only called
   // if VerifyCert succeeded.
   SECStatus rv = VerifyCert(peerCert.get(), certificateUsageSSLServer, time,
                             pinarg, hostname, builtChain, flags,
-                            stapledOCSPResponse, evOidPolicy,
-                            ocspStaplingStatus, keySizeStatus,
-                            sha1ModeResult, pinningTelemetryInfo);
+                            stapledOCSPResponse, sctsFromTLS,
+                            evOidPolicy, ocspStaplingStatus, keySizeStatus,
+                            sha1ModeResult, pinningTelemetryInfo,
+                            ctInfo);
   if (rv != SECSuccess) {
     return rv;
   }
 
   Input peerCertInput;
   Result result = peerCertInput.Init(peerCert->derCert.data,
                                      peerCert->derCert.len);
   if (result != Success) {
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -3,21 +3,32 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CertVerifier_h
 #define CertVerifier_h
 
 #include "BRNameMatchingPolicy.h"
+#include "CTVerifyResult.h"
 #include "OCSPCache.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtr.h"
 #include "pkix/pkixtypes.h"
 
+namespace mozilla { namespace ct {
+
+// Including MultiLogCTVerifier.h would bring along all of its dependent
+// headers and force us to export them in moz.build. Just forward-declare
+// the class here instead.
+class MultiLogCTVerifier;
+
+} } // namespace mozilla::ct
+
 namespace mozilla { namespace psm {
 
 // These values correspond to the CERT_CHAIN_KEY_SIZE_STATUS telemetry.
 enum class KeySizeStatus {
   NeverChecked = 0,
   LargeMinimumSucceeded = 1,
   CompatibilityRisk = 2,
   AlreadyBad = 3,
@@ -44,16 +55,31 @@ public:
   int32_t certPinningResultBucket;
   // Should we accumulate telemetry for the root?
   bool accumulateForRoot;
   int32_t rootBucket;
 
   void Reset() { accumulateForRoot = false; accumulateResult = false; }
 };
 
+class CertificateTransparencyInfo
+{
+public:
+  // Was CT enabled?
+  bool enabled;
+  // Did we receive and process any binary SCT data from the supported sources?
+  bool processedSCTs;
+  // Verification result of the processed SCTs.
+  mozilla::ct::CTVerifyResult verifyResult;
+
+  void Reset() { enabled = false; processedSCTs = false; verifyResult.Reset(); }
+};
+
+class NSSCertDBTrustDomain;
+
 class CertVerifier
 {
 public:
   typedef unsigned int Flags;
   // XXX: FLAG_LOCAL_ONLY is ignored in the classic verification case
   static const Flags FLAG_LOCAL_ONLY;
   // Don't perform fallback DV validation on EV validation failure.
   static const Flags FLAG_MUST_BE_EV;
@@ -74,36 +100,40 @@ public:
   SECStatus VerifyCert(CERTCertificate* cert,
                        SECCertificateUsage usage,
                        mozilla::pkix::Time time,
                        void* pinArg,
                        const char* hostname,
                /*out*/ UniqueCERTCertList& builtChain,
                        Flags flags = 0,
        /*optional in*/ const SECItem* stapledOCSPResponse = nullptr,
+       /*optional in*/ const SECItem* sctsFromTLS = nullptr,
       /*optional out*/ SECOidTag* evOidPolicy = nullptr,
       /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
       /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
       /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
-      /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr);
+      /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
+      /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);
 
   SECStatus VerifySSLServerCert(
                     const UniqueCERTCertificate& peerCert,
        /*optional*/ const SECItem* stapledOCSPResponse,
+       /*optional*/ const SECItem* sctsFromTLS,
                     mozilla::pkix::Time time,
        /*optional*/ void* pinarg,
                     const char* hostname,
             /*out*/ UniqueCERTCertList& builtChain,
        /*optional*/ bool saveIntermediatesInPermanentDatabase = false,
        /*optional*/ Flags flags = 0,
    /*optional out*/ SECOidTag* evOidPolicy = nullptr,
    /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus = nullptr,
    /*optional out*/ KeySizeStatus* keySizeStatus = nullptr,
    /*optional out*/ SHA1ModeResult* sha1ModeResult = nullptr,
-   /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr);
+   /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo = nullptr,
+   /*optional out*/ CertificateTransparencyInfo* ctInfo = nullptr);
 
   enum PinningMode {
     pinningDisabled = 0,
     pinningAllowUserCAMITM = 1,
     pinningStrict = 2,
     pinningEnforceTestMode = 3
   };
 
@@ -121,37 +151,56 @@ public:
   enum OcspDownloadConfig {
     ocspOff = 0,
     ocspOn = 1,
     ocspEVOnly = 2
   };
   enum OcspStrictConfig { ocspRelaxed = 0, ocspStrict };
   enum OcspGetConfig { ocspGetDisabled = 0, ocspGetEnabled = 1 };
 
+  enum class CertificateTransparencyMode {
+    Disabled = 0,
+    TelemetryOnly = 1,
+  };
+
   CertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
                PinningMode pinningMode, SHA1Mode sha1Mode,
                BRNameMatchingPolicy::Mode nameMatchingMode,
-               NetscapeStepUpPolicy netscapeStepUpPolicy);
+               NetscapeStepUpPolicy netscapeStepUpPolicy,
+               CertificateTransparencyMode ctMode);
   ~CertVerifier();
 
   void ClearOCSPCache() { mOCSPCache.Clear(); }
 
   const OcspDownloadConfig mOCSPDownloadConfig;
   const bool mOCSPStrict;
   const bool mOCSPGETEnabled;
   const uint32_t mCertShortLifetimeInDays;
   const PinningMode mPinningMode;
   const SHA1Mode mSHA1Mode;
   const BRNameMatchingPolicy::Mode mNameMatchingMode;
   const NetscapeStepUpPolicy mNetscapeStepUpPolicy;
+  const CertificateTransparencyMode mCTMode;
 
 private:
   OCSPCache mOCSPCache;
 
+  // We only have a forward declaration of MultiLogCTVerifier (see above),
+  // so we keep a pointer to it and allocate dynamically.
+  UniquePtr<mozilla::ct::MultiLogCTVerifier> mCTVerifier;
+
+  void LoadKnownCTLogs();
+  mozilla::pkix::Result VerifySignedCertificateTimestamps(
+                     NSSCertDBTrustDomain& trustDomain,
+                     const UniqueCERTCertList& builtChain,
+                     mozilla::pkix::Input sctsFromTLS,
+                     mozilla::pkix::Time time,
+    /*optional out*/ CertificateTransparencyInfo* ctInfo);
+
   // Returns true if the configured SHA1 mode is more restrictive than the given
   // mode. SHA1Mode::Forbidden is more restrictive than any other mode except
   // Forbidden. Next is ImportedRoot, then ImportedRootOrBefore2016, then
   // Allowed. (A mode is never more restrictive than itself.)
   bool SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode);
 };
 
 mozilla::pkix::Result IsCertBuiltInRoot(CERTCertificate* cert, bool& result);
--- a/security/certverifier/MultiLogCTVerifier.cpp
+++ b/security/certverifier/MultiLogCTVerifier.cpp
@@ -3,65 +3,36 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MultiLogCTVerifier.h"
 
 #include "CTObjectsExtractor.h"
 #include "CTSerialization.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/Move.h"
 
 namespace mozilla { namespace ct {
 
 using namespace mozilla::pkix;
 
-// The possible verification statuses for a Signed Certificate Timestamp.
-enum class SCTVerifyStatus {
-  UnknownLog, // The SCT is from an unknown log and can not be verified.
-  Invalid, // The SCT is from a known log, but the signature is invalid.
-  OK // The SCT is from a known log, and the signature is valid.
-};
-
 // Note: this moves |sct| to the target list in |result|, invalidating |sct|.
 static Result
 StoreVerifiedSct(CTVerifyResult& result,
                  SignedCertificateTimestamp&& sct,
-                 SCTVerifyStatus status)
+                 SignedCertificateTimestamp::VerificationStatus status)
 {
-  SCTList* target;
-  switch (status) {
-    case SCTVerifyStatus::UnknownLog:
-      target = &result.unknownLogsScts;
-      break;
-    case SCTVerifyStatus::Invalid:
-      target = &result.invalidScts;
-      break;
-    case SCTVerifyStatus::OK:
-      target = &result.verifiedScts;
-      break;
-    default:
-      MOZ_ASSERT_UNREACHABLE("Unexpected SCTVerifyStatus type");
-      return Result::FATAL_ERROR_LIBRARY_FAILURE;
-  }
-  if (!target->append(Move(sct))) {
+  sct.verificationStatus = status;
+  if (!result.scts.append(Move(sct))) {
     return Result::FATAL_ERROR_NO_MEMORY;
   }
   return Success;
 }
 
-void
-CTVerifyResult::Reset()
-{
-  verifiedScts.clear();
-  invalidScts.clear();
-  unknownLogsScts.clear();
-  decodingErrors = 0;
-}
-
 Result
 MultiLogCTVerifier::AddLog(Input publicKey)
 {
   CTLogVerifier log;
   Result rv = log.Init(publicKey);
   if (rv != Success) {
     return rv;
   }
@@ -72,17 +43,17 @@ MultiLogCTVerifier::AddLog(Input publicK
 }
 
 Result
 MultiLogCTVerifier::Verify(Input cert,
                            Input issuerSubjectPublicKeyInfo,
                            Input sctListFromCert,
                            Input sctListFromOCSPResponse,
                            Input sctListFromTLSExtension,
-                           uint64_t time,
+                           Time time,
                            CTVerifyResult& result)
 {
   MOZ_ASSERT(cert.GetLength() > 0);
   result.Reset();
 
   Result rv;
 
   // Verify embedded SCTs
@@ -128,17 +99,17 @@ MultiLogCTVerifier::Verify(Input cert,
   }
   return Success;
 }
 
 Result
 MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
                                const LogEntry& expectedEntry,
                                SignedCertificateTimestamp::Origin origin,
-                               uint64_t time,
+                               Time time,
                                CTVerifyResult& result)
 {
   Reader listReader;
   Result rv = DecodeSCTList(encodedSctList, listReader);
   if (rv != Success) {
     result.decodingErrors++;
     return Success;
   }
@@ -166,46 +137,57 @@ MultiLogCTVerifier::VerifySCTs(Input enc
     }
   }
   return Success;
 }
 
 Result
 MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
                                     const LogEntry& expectedEntry,
-                                    uint64_t time,
+                                    Time time,
                                     CTVerifyResult& result)
 {
   CTLogVerifier* matchingLog = nullptr;
   for (auto& log : mLogs) {
     if (log.keyId() == sct.logId) {
       matchingLog = &log;
       break;
     }
   }
 
   if (!matchingLog) {
     // SCT does not match any known log.
-    return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::UnknownLog);
+    return StoreVerifiedSct(result, Move(sct),
+      SignedCertificateTimestamp::VerificationStatus::UnknownLog);
   }
 
   if (!matchingLog->SignatureParametersMatch(sct.signature)) {
     // SCT signature parameters do not match the log's.
-    return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
+    return StoreVerifiedSct(result, Move(sct),
+      SignedCertificateTimestamp::VerificationStatus::InvalidSignature);
   }
 
   Result rv = matchingLog->Verify(expectedEntry, sct);
   if (rv != Success) {
     if (rv == Result::ERROR_BAD_SIGNATURE) {
-      return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
+      return StoreVerifiedSct(result, Move(sct),
+        SignedCertificateTimestamp::VerificationStatus::InvalidSignature);
     }
     return rv;
   }
 
+  // |sct.timestamp| is measured in milliseconds since the epoch,
+  // ignoring leap seconds. When converting it to a second-level precision
+  // pkix::Time, we need to round it either up or down. In our case, rounding up
+  // is more "secure", although practically it does not matter.
+  Time sctTime = TimeFromEpochInSeconds((sct.timestamp + 999u) / 1000u);
+
   // SCT verified ok, just make sure the timestamp is legitimate.
-  if (sct.timestamp > time) {
-    return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::Invalid);
+  if (sctTime > time) {
+    return StoreVerifiedSct(result, Move(sct),
+      SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp);
   }
 
-  return StoreVerifiedSct(result, Move(sct), SCTVerifyStatus::OK);
+  return StoreVerifiedSct(result, Move(sct),
+    SignedCertificateTimestamp::VerificationStatus::OK);
 }
 
 } } // namespace mozilla::ct
--- a/security/certverifier/MultiLogCTVerifier.h
+++ b/security/certverifier/MultiLogCTVerifier.h
@@ -3,55 +3,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MultiLogCTVerifier_h
 #define MultiLogCTVerifier_h
 
 #include "CTLogVerifier.h"
+#include "CTVerifyResult.h"
 #include "mozilla/Vector.h"
 #include "pkix/Input.h"
 #include "pkix/Result.h"
+#include "pkix/Time.h"
 #include "SignedCertificateTimestamp.h"
 
 namespace mozilla { namespace ct {
 
-typedef Vector<SignedCertificateTimestamp> SCTList;
-
-// Holds Signed Certificate Timestamps, arranged by their verification results.
-class CTVerifyResult
-{
-public:
-  // SCTs from known logs where the signature verified correctly.
-  SCTList verifiedScts;
-
-  // SCTs from known logs where the signature failed to verify.
-  SCTList invalidScts;
-
-  // SCTs from unknown logs and as such are unverifiable.
-  SCTList unknownLogsScts;
-
-  // For a certificate to pass Certificate Transparency verification, at least
-  // one of the provided SCTs must validate. The verifier makes the best effort
-  // to extract the available SCTs from the binary sources provided to it.
-  // If some SCT cannot be extracted due to encoding errors, the verifier
-  // proceeds to the next available one. In other words, decoding errors are
-  // effectively ignored.
-  // Note that a serialized SCT may fail to decode for a "legitimate" reason,
-  // e.g. if the SCT is from a future version of the Certificate Transparency
-  // standard.
-  // |decodingErrors| field counts the errors of the above kind.
-  // This field is purely informational; there is probably nothing to do with it
-  // in release builds, but it is useful in unit tests.
-  size_t decodingErrors;
-
-  void Reset();
-};
-
 // A Certificate Transparency verifier that can verify Signed Certificate
 // Timestamps from multiple logs.
 class MultiLogCTVerifier
 {
 public:
   // Adds a new log to the list of known logs to verify against.
   pkix::Result AddLog(pkix::Input publicKey);
 
@@ -75,44 +45,41 @@ public:
   // |issuerSubjectPublicKeyInfo|  SPKI of |cert|'s issuer. Can be empty,
   //                               in which case the embedded SCT list
   //                               won't be verified.
   // |sctListFromOCSPResponse|  SCT list included in a stapled OCSP response
   //                            for |cert|. Empty if not available.
   // |sctListFromTLSExtension|  is the SCT list from the TLS extension. Empty
   //                            if no extension was present.
   // |time|  the current time. Used to make sure SCTs are not in the future.
-  //         Measured in milliseconds since the epoch, ignoring leap seconds
-  //         (same format as used by the "timestamp" field of
-  //         SignedCertificateTimestamp).
   // |result|  will be filled with the SCTs present, divided into categories
   //           based on the verification result.
   pkix::Result Verify(pkix::Input cert,
                       pkix::Input issuerSubjectPublicKeyInfo,
                       pkix::Input sctListFromCert,
                       pkix::Input sctListFromOCSPResponse,
                       pkix::Input sctListFromTLSExtension,
-                      uint64_t time,
+                      pkix::Time time,
                       CTVerifyResult& result);
 
 private:
   // Verifies a list of SCTs from |encodedSctList| over |expectedEntry|,
   // placing the verification results in |result|. The SCTs in the list
   // come from |origin| (as will be reflected in the origin field of each SCT).
   pkix::Result VerifySCTs(pkix::Input encodedSctList,
                           const LogEntry& expectedEntry,
                           SignedCertificateTimestamp::Origin origin,
-                          uint64_t time,
+                          pkix::Time time,
                           CTVerifyResult& result);
 
   // Verifies a single, parsed SCT against all known logs.
   // Note: moves |sct| to the target list in |result|, invalidating |sct|.
   pkix::Result VerifySingleSCT(SignedCertificateTimestamp&& sct,
                                const ct::LogEntry& expectedEntry,
-                               uint64_t time,
+                               pkix::Time time,
                                CTVerifyResult& result);
 
   // The list of known logs.
   Vector<CTLogVerifier> mLogs;
 };
 
 } } // namespace mozilla::ct
 
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -10,16 +10,17 @@
 
 #include "ExtendedValidation.h"
 #include "NSSErrorsService.h"
 #include "OCSPRequestor.h"
 #include "OCSPVerificationTrustDomain.h"
 #include "PublicKeyPinningService.h"
 #include "cert.h"
 #include "certdb.h"
+#include "mozilla/Assertions.h"
 #include "mozilla/Casting.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Unused.h"
 #include "nsNSSCertificate.h"
 #include "nsServiceManagerUtils.h"
 #include "nss.h"
 #include "pk11pub.h"
 #include "pkix/Result.h"
@@ -68,16 +69,18 @@ NSSCertDBTrustDomain::NSSCertDBTrustDoma
   , mValidityCheckingMode(validityCheckingMode)
   , mSHA1Mode(sha1Mode)
   , mNetscapeStepUpPolicy(netscapeStepUpPolicy)
   , mBuiltChain(builtChain)
   , mPinningTelemetryInfo(pinningTelemetryInfo)
   , mHostname(hostname)
   , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
   , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
+  , mSCTListFromCertificate()
+  , mSCTListFromOCSPStapling()
 {
 }
 
 // If useRoots is true, we only use root certificates in the candidate list.
 // If useRoots is false, we only use non-root certificates in the list.
 static Result
 FindIssuerInner(const UniqueCERTCertList& candidates, bool useRoots,
                 Input encodedIssuerName, TrustDomain::IssuerChecker& checker,
@@ -958,19 +961,69 @@ NSSCertDBTrustDomain::NetscapeStepUpMatc
       return Success;
     default:
       MOZ_ASSERT_UNREACHABLE("unhandled NetscapeStepUpPolicy type");
   }
   return Result::FATAL_ERROR_LIBRARY_FAILURE;
 }
 
 void
-NSSCertDBTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension /*extension*/,
-                                             Input /*extensionData*/)
+NSSCertDBTrustDomain::ResetAccumulatedState()
+{
+  mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
+  mSCTListFromOCSPStapling = nullptr;
+  mSCTListFromCertificate = nullptr;
+}
+
+static Input
+SECItemToInput(const UniqueSECItem& item)
+{
+  Input result;
+  if (item) {
+    MOZ_ASSERT(item->type == siBuffer);
+    Result rv = result.Init(item->data, item->len);
+    // As used here, |item| originally comes from an Input,
+    // so there should be no issues converting it back.
+    MOZ_ASSERT(rv == Success);
+    Unused << rv; // suppresses warnings in release builds
+  }
+  return result;
+}
+
+Input
+NSSCertDBTrustDomain::GetSCTListFromCertificate() const
 {
+  return SECItemToInput(mSCTListFromCertificate);
+}
+
+Input
+NSSCertDBTrustDomain::GetSCTListFromOCSPStapling() const
+{
+  return SECItemToInput(mSCTListFromOCSPStapling);
+}
+
+void
+NSSCertDBTrustDomain::NoteAuxiliaryExtension(AuxiliaryExtension extension,
+                                             Input extensionData)
+{
+  UniqueSECItem* out = nullptr;
+  switch (extension) {
+    case AuxiliaryExtension::EmbeddedSCTList:
+      out = &mSCTListFromCertificate;
+      break;
+    case AuxiliaryExtension::SCTListFromOCSPResponse:
+      out = &mSCTListFromOCSPStapling;
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("unhandled AuxiliaryExtension");
+  }
+  if (out) {
+    SECItem extensionDataItem = UnsafeMapInputToSECItem(extensionData);
+    out->reset(SECITEM_DupItem(&extensionDataItem));
+  }
 }
 
 SECStatus
 InitializeNSS(const char* dir, bool readOnly, bool loadPKCS11Modules)
 {
   // The NSS_INIT_NOROOTINIT flag turns off the loading of the root certs
   // module by NSS_Initialize because we will load it in InstallLoadableRoots
   // later.  It also allows us to work around a bug in the system NSS in
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -140,24 +140,32 @@ public:
 
   virtual Result IsChainValid(const mozilla::pkix::DERArray& certChain,
                               mozilla::pkix::Time time) override;
 
   virtual void NoteAuxiliaryExtension(
                    mozilla::pkix::AuxiliaryExtension extension,
                    mozilla::pkix::Input extensionData) override;
 
+  // Resets the OCSP stapling status and SCT lists accumulated during
+  // the chain building.
+  void ResetAccumulatedState();
+
   CertVerifier::OCSPStaplingStatus GetOCSPStaplingStatus() const
   {
     return mOCSPStaplingStatus;
   }
-  void ResetOCSPStaplingStatus()
-  {
-    mOCSPStaplingStatus = CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
-  }
+
+  // SCT lists (see Certificate Transparency) extracted during
+  // certificate verification. Note that the returned Inputs are invalidated
+  // the next time a chain is built and by ResetAccumulatedState method
+  // (and when the TrustDomain object is destroyed).
+
+  mozilla::pkix::Input GetSCTListFromCertificate() const;
+  mozilla::pkix::Input GetSCTListFromOCSPStapling() const;
 
 private:
   enum EncodedResponseSource {
     ResponseIsFromNetwork = 1,
     ResponseWasStapled = 2
   };
   Result VerifyAndMaybeCacheEncodedOCSPResponse(
     const mozilla::pkix::CertID& certID, mozilla::pkix::Time time,
@@ -175,13 +183,16 @@ private:
   ValidityCheckingMode mValidityCheckingMode;
   CertVerifier::SHA1Mode mSHA1Mode;
   NetscapeStepUpPolicy mNetscapeStepUpPolicy;
   UniqueCERTCertList& mBuiltChain; // non-owning
   PinningTelemetryInfo* mPinningTelemetryInfo;
   const char* mHostname; // non-owning - only used for pinning checks
   nsCOMPtr<nsICertBlocklist> mCertBlocklist;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
+  // Certificate Transparency data extracted during certificate verification
+  UniqueSECItem mSCTListFromCertificate;
+  UniqueSECItem mSCTListFromOCSPStapling;
 };
 
 } } // namespace mozilla::psm
 
 #endif // NSSCertDBTrustDomain_h
--- a/security/certverifier/SignedCertificateTimestamp.h
+++ b/security/certverifier/SignedCertificateTimestamp.h
@@ -73,33 +73,49 @@ struct DigitallySigned
 // SignedCertificateTimestamp struct in RFC 6962, Section 3.2.
 struct SignedCertificateTimestamp
 {
   // Version enum in RFC 6962, Section 3.2.
   enum class Version {
     V1 = 0,
   };
 
-  // Source of the SCT - supplementary, not defined in CT RFC.
-  // Note: The numeric values are used within histograms and should not change
-  // or be re-assigned.
-  enum class Origin {
-    Embedded = 0,
-    TLSExtension = 1,
-    OCSPResponse = 2,
-  };
-
   Version version;
   Buffer logId;
   // "timestamp" is the current time in milliseconds, measured since the epoch,
   // ignoring leap seconds. See RFC 6962, Section 3.2.
   uint64_t timestamp;
   Buffer extensions;
   DigitallySigned signature;
+
+  // Supplementary fields, not defined in CT RFC. Set during the various
+  // stages of processing the received SCTs.
+
+  enum class Origin {
+    Unknown,
+    Embedded,
+    TLSExtension,
+    OCSPResponse
+  };
+
+  enum class VerificationStatus {
+    None,
+    // The SCT is from a known log, and the signature is valid.
+    OK,
+    // The SCT is from an unknown log and can not be verified.
+    UnknownLog,
+    // The SCT is from a known log, but the signature is invalid.
+    InvalidSignature,
+    // The SCT signature is valid, but the timestamp is in the future.
+    // Such SCT are considered invalid (see RFC 6962, Section 5.2).
+    InvalidTimestamp
+  };
+
   Origin origin;
+  VerificationStatus verificationStatus;
 };
 
 
 inline pkix::Result BufferToInput(const Buffer& buffer, pkix::Input& input)
 {
   return input.Init(buffer.begin(), buffer.length());
 }
 
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -2,25 +2,29 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'BRNameMatchingPolicy.h',
     'CertVerifier.h',
+    'CTVerifyResult.h',
     'OCSPCache.h',
+    'SignedCertificateTimestamp.h',
+    'SignedTreeHead.h',
 ]
 
 UNIFIED_SOURCES += [
     'BRNameMatchingPolicy.cpp',
     'CertVerifier.cpp',
     'CTLogVerifier.cpp',
     'CTObjectsExtractor.cpp',
     'CTSerialization.cpp',
+    'CTVerifyResult.cpp',
     'MultiLogCTVerifier.cpp',
     'NSSCertDBTrustDomain.cpp',
     'OCSPCache.cpp',
     'OCSPRequestor.cpp',
     'OCSPVerificationTrustDomain.cpp',
     'SignedCertificateTimestamp.cpp',
 ]
 
--- a/security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
+++ b/security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
@@ -19,42 +19,46 @@
 
 namespace mozilla { namespace ct {
 
 using namespace mozilla::pkix;
 
 class MultiLogCTVerifierTest : public ::testing::Test
 {
 public:
+  MultiLogCTVerifierTest()
+    : mNow(Time::uninitialized)
+  {}
+
   void SetUp() override
   {
     // Does nothing if NSS is already initialized.
     MOZ_RELEASE_ASSERT(NSS_NoDB_Init(nullptr) == SECSuccess);
 
     ASSERT_EQ(Success, mVerifier.AddLog(InputForBuffer(GetTestPublicKey())));
 
     mTestCert = GetDEREncodedX509Cert();
     mEmbeddedCert = GetDEREncodedTestEmbeddedCert();
     mCaCert = GetDEREncodedCACert();
     mCaCertSPKI = ExtractCertSPKI(mCaCert);
     mIntermediateCert = GetDEREncodedIntermediateCert();
     mIntermediateCertSPKI = ExtractCertSPKI(mIntermediateCert);
 
     // Set the current time making sure all test timestamps are in the past.
-    mNow = UINT64_MAX;
+    mNow = TimeFromEpochInSeconds(1451606400u); // Date.parse("2016-01-01")/1000
   }
 
   void CheckForSingleVerifiedSCTInResult(const CTVerifyResult& result,
     SignedCertificateTimestamp::Origin origin)
   {
     EXPECT_EQ(0U, result.decodingErrors);
-    EXPECT_TRUE(result.invalidScts.empty());
-    EXPECT_TRUE(result.unknownLogsScts.empty());
-    ASSERT_EQ(1U, result.verifiedScts.length());
-    EXPECT_EQ(origin, result.verifiedScts[0].origin);
+    ASSERT_EQ(1U, result.scts.length());
+    EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::OK,
+              result.scts[0].verificationStatus);
+    EXPECT_EQ(origin, result.scts[0].origin);
   }
 
   // Writes an SCTList containing a single |sct| into |output|.
   void EncodeSCTListForTesting(Input sct, Buffer& output)
   {
     Vector<Input> list;
     ASSERT_TRUE(list.append(Move(sct)));
     ASSERT_EQ(Success, EncodeSCTList(list, output));
@@ -88,17 +92,17 @@ public:
 protected:
   MultiLogCTVerifier mVerifier;
   Buffer mTestCert;
   Buffer mEmbeddedCert;
   Buffer mCaCert;
   Buffer mCaCertSPKI;
   Buffer mIntermediateCert;
   Buffer mIntermediateCertSPKI;
-  uint64_t mNow;
+  Time mNow;
 };
 
 // Test that an embedded SCT can be extracted and the extracted SCT contains
 // the expected data. This tests the ExtractEmbeddedSCTList function from
 // CTTestUtils.h that other tests here rely upon.
 TEST_F(MultiLogCTVerifierTest, ExtractEmbeddedSCT)
 {
   SignedCertificateTimestamp sct;
@@ -191,17 +195,19 @@ TEST_F(MultiLogCTVerifierTest, VerifiesS
   CTVerifyResult result;
   ASSERT_EQ(Success,
             mVerifier.Verify(InputForBuffer(mTestCert), Input(), Input(),
                              InputForBuffer(sctList), InputForBuffer(sctList),
                              mNow, result));
 
   // The result should contain verified SCTs from TLS and OCSP origins.
   EnumSet<SignedCertificateTimestamp::Origin> origins;
-  for (auto& sct : result.verifiedScts) {
+  for (const SignedCertificateTimestamp& sct : result.scts) {
+    EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::OK,
+              sct.verificationStatus);
     origins += sct.origin;
   }
   EXPECT_FALSE(
     origins.contains(SignedCertificateTimestamp::Origin::Embedded));
   EXPECT_TRUE(
     origins.contains(SignedCertificateTimestamp::Origin::OCSPResponse));
   EXPECT_TRUE(
     origins.contains(SignedCertificateTimestamp::Origin::TLSExtension));
@@ -213,13 +219,15 @@ TEST_F(MultiLogCTVerifierTest, Identifie
   GetSCTListWithInvalidLogID(sctList);
 
   CTVerifyResult result;
   ASSERT_EQ(Success,
             mVerifier.Verify(InputForBuffer(mTestCert), Input(),
                              Input(), Input(), InputForBuffer(sctList),
                              mNow, result));
 
-  EXPECT_EQ(1U, result.unknownLogsScts.length());
   EXPECT_EQ(0U, result.decodingErrors);
+  ASSERT_EQ(1U, result.scts.length());
+  EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::UnknownLog,
+            result.scts[0].verificationStatus);
 }
 
 } } // namespace mozilla::ct
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -734,60 +734,65 @@ class SSLServerCertVerificationJob : pub
 {
 public:
   // Must be called only on the socket transport thread
   static SECStatus Dispatch(const RefPtr<SharedCertVerifier>& certVerifier,
                             const void* fdForLogging,
                             nsNSSSocketInfo* infoObject,
                             const UniqueCERTCertificate& serverCert,
                             const UniqueCERTCertList& peerCertChain,
-                            SECItem* stapledOCSPResponse,
+                            const SECItem* stapledOCSPResponse,
+                            const SECItem* sctsFromTLSExtension,
                             uint32_t providerFlags,
                             Time time,
                             PRTime prtime);
 private:
   NS_DECL_NSIRUNNABLE
 
   // Must be called only on the socket transport thread
   SSLServerCertVerificationJob(const RefPtr<SharedCertVerifier>& certVerifier,
                                const void* fdForLogging,
                                nsNSSSocketInfo* infoObject,
                                const UniqueCERTCertificate& cert,
                                UniqueCERTCertList peerCertChain,
-                               SECItem* stapledOCSPResponse,
+                               const SECItem* stapledOCSPResponse,
+                               const SECItem* sctsFromTLSExtension,
                                uint32_t providerFlags,
                                Time time,
                                PRTime prtime);
   const RefPtr<SharedCertVerifier> mCertVerifier;
   const void* const mFdForLogging;
   const RefPtr<nsNSSSocketInfo> mInfoObject;
   const UniqueCERTCertificate mCert;
   UniqueCERTCertList mPeerCertChain;
   const uint32_t mProviderFlags;
   const Time mTime;
   const PRTime mPRTime;
   const TimeStamp mJobStartTime;
   const UniqueSECItem mStapledOCSPResponse;
+  const UniqueSECItem mSCTsFromTLSExtension;
 };
 
 SSLServerCertVerificationJob::SSLServerCertVerificationJob(
     const RefPtr<SharedCertVerifier>& certVerifier, const void* fdForLogging,
     nsNSSSocketInfo* infoObject, const UniqueCERTCertificate& cert,
-    UniqueCERTCertList peerCertChain, SECItem* stapledOCSPResponse,
+    UniqueCERTCertList peerCertChain, const SECItem* stapledOCSPResponse,
+    const SECItem* sctsFromTLSExtension,
     uint32_t providerFlags, Time time, PRTime prtime)
   : mCertVerifier(certVerifier)
   , mFdForLogging(fdForLogging)
   , mInfoObject(infoObject)
   , mCert(CERT_DupCertificate(cert.get()))
   , mPeerCertChain(Move(peerCertChain))
   , mProviderFlags(providerFlags)
   , mTime(time)
   , mPRTime(prtime)
   , mJobStartTime(TimeStamp::Now())
   , mStapledOCSPResponse(SECITEM_DupItem(stapledOCSPResponse))
+  , mSCTsFromTLSExtension(SECITEM_DupItem(sctsFromTLSExtension))
 {
 }
 
 // This function assumes that we will only use the SPDY connection coalescing
 // feature on connections where we have negotiated SPDY using NPN. If we ever
 // talk SPDY without having negotiated it with SPDY, this code will give wrong
 // and perhaps unsafe results.
 //
@@ -1209,23 +1214,98 @@ void
 GatherSuccessfulValidationTelemetry(const UniqueCERTCertList& certList)
 {
   GatherBaselineRequirementsTelemetry(certList);
   GatherEKUTelemetry(certList);
   GatherRootCATelemetry(certList);
   GatherEndEntityTelemetry(certList);
 }
 
+void
+GatherTelemetryForSingleSCT(const ct::SignedCertificateTimestamp& sct)
+{
+  // See SSL_SCTS_ORIGIN in Histograms.json.
+  uint32_t origin = 0;
+  switch (sct.origin) {
+    case ct::SignedCertificateTimestamp::Origin::Embedded:
+      origin = 1;
+      break;
+    case ct::SignedCertificateTimestamp::Origin::TLSExtension:
+      origin = 2;
+      break;
+    case ct::SignedCertificateTimestamp::Origin::OCSPResponse:
+      origin = 3;
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unexpected SCT::Origin type");
+  }
+  Telemetry::Accumulate(Telemetry::SSL_SCTS_ORIGIN, origin);
+
+  // See SSL_SCTS_VERIFICATION_STATUS in Histograms.json.
+  uint32_t verificationStatus = 0;
+  switch (sct.verificationStatus) {
+    case ct::SignedCertificateTimestamp::VerificationStatus::OK:
+      verificationStatus = 1;
+      break;
+    case ct::SignedCertificateTimestamp::VerificationStatus::UnknownLog:
+      verificationStatus = 2;
+      break;
+    case ct::SignedCertificateTimestamp::VerificationStatus::InvalidSignature:
+      verificationStatus = 3;
+      break;
+    case ct::SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp:
+      verificationStatus = 4;
+      break;
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unexpected SCT::VerificationStatus type");
+  }
+  Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS,
+                        verificationStatus);
+}
+
+void
+GatherCertificateTransparencyTelemetry(const UniqueCERTCertList& certList,
+                                       const CertificateTransparencyInfo& info)
+{
+  if (!info.enabled) {
+    // No telemetry is gathered when CT is disabled.
+    return;
+  }
+
+  if (!info.processedSCTs) {
+    // We didn't receive any SCT data for this connection.
+    Telemetry::Accumulate(Telemetry::SSL_SCTS_PER_CONNECTION, 0);
+    return;
+  }
+
+  for (const ct::SignedCertificateTimestamp& sct : info.verifyResult.scts) {
+    GatherTelemetryForSingleSCT(sct);
+  }
+
+  // Decoding errors are reported to the 0th bucket
+  // of the SSL_SCTS_VERIFICATION_STATUS enumerated probe.
+  for (size_t i = 0; i < info.verifyResult.decodingErrors; ++i) {
+    Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS, 0);
+  }
+
+  // Handle the histogram of SCTs counts.
+  uint32_t sctsCount = static_cast<uint32_t>(info.verifyResult.scts.length());
+  // Note that sctsCount can be 0 in case we've received SCT binary data,
+  // but it failed to parse (e.g. due to unsupported CT protocol version).
+  Telemetry::Accumulate(Telemetry::SSL_SCTS_PER_CONNECTION, sctsCount);
+}
+
 // Note: Takes ownership of |peerCertChain| if SECSuccess is not returned.
 SECStatus
 AuthCertificate(CertVerifier& certVerifier,
                 nsNSSSocketInfo* infoObject,
                 const UniqueCERTCertificate& cert,
                 UniqueCERTCertList& peerCertChain,
-                SECItem* stapledOCSPResponse,
+                const SECItem* stapledOCSPResponse,
+                const SECItem* sctsFromTLSExtension,
                 uint32_t providerFlags,
                 Time time)
 {
   MOZ_ASSERT(infoObject);
   MOZ_ASSERT(cert);
 
   SECStatus rv;
 
@@ -1236,30 +1316,32 @@ AuthCertificate(CertVerifier& certVerifi
 
   SECOidTag evOidPolicy;
   UniqueCERTCertList certList;
   CertVerifier::OCSPStaplingStatus ocspStaplingStatus =
     CertVerifier::OCSP_STAPLING_NEVER_CHECKED;
   KeySizeStatus keySizeStatus = KeySizeStatus::NeverChecked;
   SHA1ModeResult sha1ModeResult = SHA1ModeResult::NeverChecked;
   PinningTelemetryInfo pinningTelemetryInfo;
+  CertificateTransparencyInfo certificateTransparencyInfo;
 
   int flags = 0;
   if (!infoObject->SharedState().IsOCSPStaplingEnabled() ||
       !infoObject->SharedState().IsOCSPMustStapleEnabled()) {
     flags |= CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
   }
 
   rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse,
-                                        time, infoObject,
+                                        sctsFromTLSExtension, time, infoObject,
                                         infoObject->GetHostNameRaw(),
                                         certList, saveIntermediates, flags,
                                         &evOidPolicy, &ocspStaplingStatus,
                                         &keySizeStatus, &sha1ModeResult,
-                                        &pinningTelemetryInfo);
+                                        &pinningTelemetryInfo,
+                                        &certificateTransparencyInfo);
   PRErrorCode savedErrorCode;
   if (rv != SECSuccess) {
     savedErrorCode = PR_GetError();
   }
 
   uint32_t evStatus = (rv != SECSuccess) ? 0                // 0 = Failure
                     : (evOidPolicy == SEC_OID_UNKNOWN) ? 1  // 1 = DV
                     : 2;                                    // 2 = EV
@@ -1300,16 +1382,18 @@ AuthCertificate(CertVerifier& certVerifi
     }
     else {
       nsc = nsNSSCertificate::Create(cert.get());
     }
   }
 
   if (rv == SECSuccess) {
     GatherSuccessfulValidationTelemetry(certList);
+    GatherCertificateTransparencyTelemetry(certList,
+                                           certificateTransparencyInfo);
 
     // The connection may get terminated, for example, if the server requires
     // a client cert. Let's provide a minimal SSLStatus
     // to the caller that contains at least the cert and its status.
     if (!status) {
       status = new nsSSLStatus();
       infoObject->SetSSLStatus(status);
     }
@@ -1352,17 +1436,18 @@ AuthCertificate(CertVerifier& certVerifi
 
 /*static*/ SECStatus
 SSLServerCertVerificationJob::Dispatch(
   const RefPtr<SharedCertVerifier>& certVerifier,
   const void* fdForLogging,
   nsNSSSocketInfo* infoObject,
   const UniqueCERTCertificate& serverCert,
   const UniqueCERTCertList& peerCertChain,
-  SECItem* stapledOCSPResponse,
+  const SECItem* stapledOCSPResponse,
+  const SECItem* sctsFromTLSExtension,
   uint32_t providerFlags,
   Time time,
   PRTime prtime)
 {
   // Runs on the socket transport thread
   if (!certVerifier || !infoObject || !serverCert) {
     NS_ERROR("Invalid parameters for SSL server cert validation");
     PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
@@ -1379,18 +1464,18 @@ SSLServerCertVerificationJob::Dispatch(
   if (!peerCertChainCopy) {
     PR_SetError(SEC_ERROR_NO_MEMORY, 0);
     return SECFailure;
   }
 
   RefPtr<SSLServerCertVerificationJob> job(
     new SSLServerCertVerificationJob(certVerifier, fdForLogging, infoObject,
                                      serverCert, Move(peerCertChainCopy),
-                                     stapledOCSPResponse, providerFlags,
-                                     time, prtime));
+                                     stapledOCSPResponse, sctsFromTLSExtension,
+                                     providerFlags, time, prtime));
 
   nsresult nrv;
   if (!gCertVerificationThreadPool) {
     nrv = NS_ERROR_NOT_INITIALIZED;
   } else {
     nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL);
   }
   if (NS_FAILED(nrv)) {
@@ -1431,16 +1516,17 @@ SSLServerCertVerificationJob::Run()
     Telemetry::ID failureTelemetry
       = Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_MOZILLAPKIX;
 
     // Reset the error code here so we can detect if AuthCertificate fails to
     // set the error code if/when it fails.
     PR_SetError(0, 0);
     SECStatus rv = AuthCertificate(*mCertVerifier, mInfoObject, mCert,
                                    mPeerCertChain, mStapledOCSPResponse.get(),
+                                   mSCTsFromTLSExtension.get(),
                                    mProviderFlags, mTime);
     MOZ_ASSERT(mPeerCertChain || rv != SECSuccess,
                "AuthCertificate() should take ownership of chain on failure");
     if (rv == SECSuccess) {
       uint32_t interval = (uint32_t) ((TimeStamp::Now() - mJobStartTime).ToMilliseconds());
       RefPtr<SSLServerCertVerificationResult> restart(
         new SSLServerCertVerificationResult(mInfoObject, 0,
                                             successTelemetry, interval));
@@ -1581,41 +1667,49 @@ AuthCertificateHook(void* arg, PRFileDes
   // We don't own these pointers.
   const SECItemArray* csa = SSL_PeerStapledOCSPResponses(fd);
   SECItem* stapledOCSPResponse = nullptr;
   // we currently only support single stapled responses
   if (csa && csa->len == 1) {
     stapledOCSPResponse = &csa->items[0];
   }
 
+  const SECItem* sctsFromTLSExtension = SSL_PeerSignedCertTimestamps(fd);
+  if (sctsFromTLSExtension && sctsFromTLSExtension->len == 0) {
+    // SSL_PeerSignedCertTimestamps returns null on error and empty item
+    // when no extension was returned by the server. We always use null when
+    // no extension was received (for whatever reason), ignoring errors.
+    sctsFromTLSExtension = nullptr;
+  }
+
   uint32_t providerFlags = 0;
   socketInfo->GetProviderFlags(&providerFlags);
 
   if (onSTSThread) {
 
     // We *must* do certificate verification on a background thread because
     // we need the socket transport thread to be free for our OCSP requests,
     // and we *want* to do certificate verification on a background thread
     // because of the performance benefits of doing so.
     socketInfo->SetCertVerificationWaiting();
     SECStatus rv = SSLServerCertVerificationJob::Dispatch(
                      certVerifier, static_cast<const void*>(fd), socketInfo,
                      serverCert, peerCertChain, stapledOCSPResponse,
-                     providerFlags, now, prnow);
+                     sctsFromTLSExtension, providerFlags, now, prnow);
     return rv;
   }
 
   // We can't do certificate verification on a background thread, because the
   // thread doing the network I/O may not interrupt its network I/O on receipt
   // of our SSLServerCertVerificationResult event, and/or it might not even be
   // a non-blocking socket.
 
   SECStatus rv = AuthCertificate(*certVerifier, socketInfo, serverCert,
                                  peerCertChain, stapledOCSPResponse,
-                                 providerFlags, now);
+                                 sctsFromTLSExtension, providerFlags, now);
   MOZ_ASSERT(peerCertChain || rv != SECSuccess,
              "AuthCertificate() should take ownership of chain on failure");
   if (rv == SECSuccess) {
     Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1);
     return SECSuccess;
   }
 
   PRErrorCode error = PR_GetError();
--- a/security/manager/ssl/SharedCertVerifier.h
+++ b/security/manager/ssl/SharedCertVerifier.h
@@ -17,19 +17,20 @@ protected:
 
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SharedCertVerifier)
 
   SharedCertVerifier(OcspDownloadConfig odc, OcspStrictConfig osc,
                      OcspGetConfig ogc, uint32_t certShortLifetimeInDays,
                      PinningMode pinningMode, SHA1Mode sha1Mode,
                      BRNameMatchingPolicy::Mode nameMatchingMode,
-                     NetscapeStepUpPolicy netscapeStepUpPolicy)
+                     NetscapeStepUpPolicy netscapeStepUpPolicy,
+                     CertificateTransparencyMode ctMode)
     : mozilla::psm::CertVerifier(odc, osc, ogc, certShortLifetimeInDays,
                                  pinningMode, sha1Mode, nameMatchingMode,
-                                 netscapeStepUpPolicy)
+                                 netscapeStepUpPolicy, ctMode)
   {
   }
 };
 
 } } // namespace mozilla::psm
 
 #endif // SharedCertVerifier_h
--- a/security/manager/ssl/SharedSSLState.h
+++ b/security/manager/ssl/SharedSSLState.h
@@ -38,23 +38,31 @@ public:
   void SetOCSPStaplingEnabled(bool staplingEnabled)
   {
     mOCSPStaplingEnabled = staplingEnabled;
   }
   void SetOCSPMustStapleEnabled(bool mustStapleEnabled)
   {
     mOCSPMustStapleEnabled = mustStapleEnabled;
   }
+  void SetSignedCertTimestampsEnabled(bool signedCertTimestampsEnabled)
+  {
+    mSignedCertTimestampsEnabled = signedCertTimestampsEnabled;
+  }
 
   // The following methods may be called from any thread
   bool SocketCreated();
   void NoteSocketCreated();
   static void NoteCertOverrideServiceInstantiated();
   bool IsOCSPStaplingEnabled() const { return mOCSPStaplingEnabled; }
   bool IsOCSPMustStapleEnabled() const { return mOCSPMustStapleEnabled; }
+  bool IsSignedCertTimestampsEnabled() const
+  {
+    return mSignedCertTimestampsEnabled;
+  }
 
 private:
   ~SharedSSLState();
 
   void Cleanup();
 
   nsCOMPtr<nsIObserver> mObserver;
   RefPtr<nsClientAuthRememberService> mClientAuthRemember;
@@ -62,16 +70,17 @@ private:
 
   // True if any sockets have been created that use this shared data.
   // Requires synchronization between the socket and main threads for
   // reading/writing.
   Mutex mMutex;
   bool mSocketCreated;
   bool mOCSPStaplingEnabled;
   bool mOCSPMustStapleEnabled;
+  bool mSignedCertTimestampsEnabled;
 };
 
 SharedSSLState* PublicSSLState();
 SharedSSLState* PrivateSSLState();
 
 } // namespace psm
 } // namespace mozilla
 
--- a/security/manager/ssl/nsNSSCertificate.cpp
+++ b/security/manager/ssl/nsNSSCertificate.cpp
@@ -1312,17 +1312,20 @@ nsNSSCertificate::hasValidEVOidTag(SECOi
   uint32_t flags = mozilla::psm::CertVerifier::FLAG_LOCAL_ONLY |
     mozilla::psm::CertVerifier::FLAG_MUST_BE_EV;
   UniqueCERTCertList unusedBuiltChain;
   SECStatus rv = certVerifier->VerifyCert(mCert.get(),
     certificateUsageSSLServer, mozilla::pkix::Now(),
     nullptr /* XXX pinarg */,
     nullptr /* hostname */,
     unusedBuiltChain,
-    flags, nullptr /* stapledOCSPResponse */, &resultOidTag);
+    flags,
+    nullptr /* stapledOCSPResponse */,
+    nullptr /* sctsFromTLSExtension */,
+    &resultOidTag);
 
   if (rv != SECSuccess) {
     resultOidTag = SEC_OID_UNKNOWN;
   }
   if (resultOidTag != SEC_OID_UNKNOWN) {
     validEV = true;
   }
   return NS_OK;
--- a/security/manager/ssl/nsNSSCertificateDB.cpp
+++ b/security/manager/ssl/nsNSSCertificateDB.cpp
@@ -1488,30 +1488,32 @@ VerifyCertAtTime(nsIX509Cert* aCert,
 
   UniqueCERTCertList resultChain;
   SECOidTag evOidPolicy;
   SECStatus srv;
 
   if (aHostname && aUsage == certificateUsageSSLServer) {
     srv = certVerifier->VerifySSLServerCert(nssCert,
                                             nullptr, // stapledOCSPResponse
+                                            nullptr, // sctsFromTLSExtension
                                             aTime,
                                             nullptr, // Assume no context
                                             aHostname,
                                             resultChain,
                                             false, // don't save intermediates
                                             aFlags,
                                             &evOidPolicy);
   } else {
     srv = certVerifier->VerifyCert(nssCert.get(), aUsage, aTime,
                                    nullptr, // Assume no context
                                    aHostname,
                                    resultChain,
                                    aFlags,
                                    nullptr, // stapledOCSPResponse
+                                   nullptr, // sctsFromTLSExtension
                                    &evOidPolicy);
   }
 
   PRErrorCode error = PR_GetError();
 
   nsCOMPtr<nsIX509CertList> nssCertList;
   // This adopts the list
   nssCertList = new nsNSSCertList(Move(resultChain), locker);
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1497,16 +1497,35 @@ void nsNSSComponent::setValidationOption
   PublicSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
   PrivateSSLState()->SetOCSPStaplingEnabled(ocspStaplingEnabled);
 
   bool ocspMustStapleEnabled = Preferences::GetBool("security.ssl.enable_ocsp_must_staple",
                                                     true);
   PublicSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
   PrivateSSLState()->SetOCSPMustStapleEnabled(ocspMustStapleEnabled);
 
+  const CertVerifier::CertificateTransparencyMode defaultCTMode =
+    CertVerifier::CertificateTransparencyMode::TelemetryOnly;
+  CertVerifier::CertificateTransparencyMode ctMode =
+    static_cast<CertVerifier::CertificateTransparencyMode>
+      (Preferences::GetInt("security.pki.certificate_transparency.mode",
+                           static_cast<int32_t>(defaultCTMode)));
+  switch (ctMode) {
+    case CertVerifier::CertificateTransparencyMode::Disabled:
+    case CertVerifier::CertificateTransparencyMode::TelemetryOnly:
+      break;
+    default:
+      ctMode = defaultCTMode;
+      break;
+  }
+  bool sctsEnabled =
+    ctMode != CertVerifier::CertificateTransparencyMode::Disabled;
+  PublicSSLState()->SetSignedCertTimestampsEnabled(sctsEnabled);
+  PrivateSSLState()->SetSignedCertTimestampsEnabled(sctsEnabled);
+
   CertVerifier::PinningMode pinningMode =
     static_cast<CertVerifier::PinningMode>
       (Preferences::GetInt("security.cert_pinning.enforcement_level",
                            CertVerifier::pinningDisabled));
   if (pinningMode > CertVerifier::pinningEnforceTestMode) {
     pinningMode = CertVerifier::pinningDisabled;
   }
 
@@ -1566,17 +1585,18 @@ void nsNSSComponent::setValidationOption
   uint32_t certShortLifetimeInDays;
 
   GetRevocationBehaviorFromPrefs(&odc, &osc, &ogc, &certShortLifetimeInDays,
                                  lock);
   mDefaultCertVerifier = new SharedCertVerifier(odc, osc, ogc,
                                                 certShortLifetimeInDays,
                                                 pinningMode, sha1Mode,
                                                 nameMatchingMode,
-                                                netscapeStepUpPolicy);
+                                                netscapeStepUpPolicy,
+                                                ctMode);
 }
 
 // Enable the TLS versions given in the prefs, defaulting to TLS 1.0 (min) and
 // TLS 1.2 (max) when the prefs aren't set or set to invalid values.
 nsresult
 nsNSSComponent::setEnabledTLSVersions()
 {
   // keep these values in sync with security-prefs.js
@@ -1977,16 +1997,17 @@ nsNSSComponent::Observe(nsISupports* aSu
     } else if (prefName.Equals("security.ssl.disable_session_identifiers")) {
       ConfigureTLSSessionIdentifiers();
     } else if (prefName.EqualsLiteral("security.OCSP.enabled") ||
                prefName.EqualsLiteral("security.OCSP.require") ||
                prefName.EqualsLiteral("security.OCSP.GET.enabled") ||
                prefName.EqualsLiteral("security.pki.cert_short_lifetime_in_days") ||
                prefName.EqualsLiteral("security.ssl.enable_ocsp_stapling") ||
                prefName.EqualsLiteral("security.ssl.enable_ocsp_must_staple") ||
+               prefName.EqualsLiteral("security.pki.certificate_transparency.mode") ||
                prefName.EqualsLiteral("security.cert_pinning.enforcement_level") ||
                prefName.EqualsLiteral("security.pki.sha1_enforcement_level") ||
                prefName.EqualsLiteral("security.pki.name_matching_mode") ||
                prefName.EqualsLiteral("security.pki.netscape_step_up_policy")) {
       MutexAutoLock lock(mutex);
       setValidationOptions(false, lock);
 #ifdef DEBUG
     } else if (prefName.EqualsLiteral("security.test.built_in_root_hash")) {
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -438,20 +438,25 @@ nsNSSSocketInfo::IsAcceptableForHost(con
   // is trying to join onto this connection.
   RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
   if (!certVerifier) {
     return NS_OK;
   }
   nsAutoCString hostnameFlat(PromiseFlatCString(hostname));
   CertVerifier::Flags flags = CertVerifier::FLAG_LOCAL_ONLY;
   UniqueCERTCertList unusedBuiltChain;
-  SECStatus rv = certVerifier->VerifySSLServerCert(nssCert, nullptr,
+  SECStatus rv = certVerifier->VerifySSLServerCert(nssCert,
+                                                   nullptr, // stapledOCSPResponse
+                                                   nullptr, // sctsFromTLSExtension
                                                    mozilla::pkix::Now(),
-                                                   nullptr, hostnameFlat.get(),
-                                                   unusedBuiltChain, false, flags);
+                                                   nullptr, // pinarg
+                                                   hostnameFlat.get(),
+                                                   unusedBuiltChain,
+                                                   false, // save intermediates
+                                                   flags);
   if (rv != SECSuccess) {
     return NS_OK;
   }
 
   // All tests pass
   *_retval = true;
   return NS_OK;
 }
@@ -2507,16 +2512,22 @@ nsSSLIOLayerSetOptions(PRFileDesc* fd, b
     return NS_ERROR_FAILURE;
   }
 
   bool enabled = infoObject->SharedState().IsOCSPStaplingEnabled();
   if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_OCSP_STAPLING, enabled)) {
     return NS_ERROR_FAILURE;
   }
 
+  bool sctsEnabled = infoObject->SharedState().IsSignedCertTimestampsEnabled();
+  if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
+      sctsEnabled)) {
+    return NS_ERROR_FAILURE;
+  }
+
   if (SECSuccess != SSL_OptionSet(fd, SSL_HANDSHAKE_AS_CLIENT, true)) {
     return NS_ERROR_FAILURE;
   }
 
   // Set the Peer ID so that SSL proxy connections work properly and to
   // separate anonymous and/or private browsing connections.
   uint32_t flags = infoObject->GetProviderFlags();
   nsAutoCString peerId;
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -712,17 +712,19 @@ nsSiteSecurityService::ProcessPKPHeader(
   // We don't want this verification to cause any network traffic that would
   // block execution. Also, since we don't have access to the original stapled
   // OCSP response, we can't enforce this aspect of the TLS Feature extension.
   // This is ok, because it will have been enforced when we originally connected
   // to the site (or it's disabled, in which case we wouldn't want to enforce it
   // anyway).
   CertVerifier::Flags flags = CertVerifier::FLAG_LOCAL_ONLY |
                               CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
-  if (certVerifier->VerifySSLServerCert(nssCert, nullptr, // stapled ocsp
+  if (certVerifier->VerifySSLServerCert(nssCert,
+                                        nullptr, // stapledOCSPResponse
+                                        nullptr, // sctsFromTLSExtension
                                         now, nullptr, // pinarg
                                         host.get(), // hostname
                                         certList,
                                         false, // don't store intermediates
                                         flags)
       != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8065,16 +8065,43 @@
   "SSL_PERMANENT_CERT_ERROR_OVERRIDES": {
     "alert_emails": ["seceng@mozilla.org"],
     "expires_in_version": "default",
     "kind": "exponential",
     "high": 1024,
     "n_buckets": 10,
     "description": "How many permanent certificate overrides a user has stored."
   },
+  "SSL_SCTS_ORIGIN": {
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 10,
+    "bug_numbers": [1293231],
+    "releaseChannelCollection": "opt-out",
+    "description": "Origin of Signed Certificate Timestamps received (1=Embedded, 2=TLS handshake extension, 3=Stapled OCSP response)"
+  },
+  "SSL_SCTS_PER_CONNECTION": {
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 10,
+    "bug_numbers": [1293231],
+    "releaseChannelCollection": "opt-out",
+    "description": "Histogram of Signed Certificate Timestamps per SSL connection, from all sources (embedded / OCSP Stapling / TLS handshake). Bucket 0 counts the cases when no SCTs were received, or none were extracted due to parsing errors."
+  },
+  "SSL_SCTS_VERIFICATION_STATUS": {
+    "alert_emails": ["seceng-telemetry@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 10,
+    "bug_numbers": [1293231],
+    "releaseChannelCollection": "opt-out",
+    "description": "Verification status of Signed Certificate Timestamps received (0=Decoding error, 1=SCT verified, 2=SCT from unknown log, 3=Invalid SCT signature, 4=SCT timestamp is in the future)"
+  },
   "SSL_SERVER_AUTH_EKU": {
     "alert_emails": ["seceng-telemetry@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 10,
     "description": "Presence of of the Server Authenticaton EKU in accepted SSL server certificates (0=No EKU, 1=EKU present and has id_kp_serverAuth, 2=EKU present and has id_kp_serverAuth as well as some other EKU, 3=EKU present but does not contain id_kp_serverAuth)"
   },
   "TELEMETRY_TEST_EXPIRED": {