Bug 1293231 - Certificate Transparency - basic telemetry reports; r=Cykesiopka,keeler
authorSergei Chernov <sergei.cv@ndivi.com>
Thu, 11 Aug 2016 13:41:50 +0300
changeset 315367 50143dbdcb47bf47c8827c8777b0e11e92e25418
parent 315366 c651f7174f37747de99eaafd27b2d78fb10bead3
child 315368 e709712a33e7bdfd8f8e8a6cb82b200f95b8b285
push id30748
push usercbook@mozilla.com
push dateWed, 28 Sep 2016 13:53:19 +0000
treeherdermozilla-central@8c84b7618840 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersCykesiopka, keeler
bugs1293231
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1293231 - Certificate Transparency - basic telemetry reports; r=Cykesiopka,keeler 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": {