Bug 1317951 - Certificate Transparency - basic support for disqualified logs. r=keeler draft
authorSergei Chernov <sergei.cv@ndivi.com>
Sun, 20 Nov 2016 15:40:23 +0200
changeset 441639 51f1551533bc9fefbe04fd3c9b8e24d60113cb07
parent 439707 2598a93e2e1a27f363af9fdd290cf4184cc51d48
child 537600 198a55105a60c9f3d0037e20649bd9f68440b38c
push id36478
push usersergei.cv@ndivi.com
push dateSun, 20 Nov 2016 13:56:04 +0000
reviewerskeeler
bugs1317951
milestone53.0a1
Bug 1317951 - Certificate Transparency - basic support for disqualified logs. r=keeler MozReview-Commit-ID: Lv5wIdmcRPN
security/certverifier/CTKnownLogs.h
security/certverifier/CTLogVerifier.cpp
security/certverifier/CTLogVerifier.h
security/certverifier/CTSerialization.cpp
security/certverifier/CTVerifyResult.cpp
security/certverifier/CTVerifyResult.h
security/certverifier/CertVerifier.cpp
security/certverifier/MultiLogCTVerifier.cpp
security/certverifier/MultiLogCTVerifier.h
security/certverifier/SignedCertificateTimestamp.cpp
security/certverifier/SignedCertificateTimestamp.h
security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/nsSSLStatus.cpp
security/manager/tools/getCTKnownLogs.py
toolkit/components/telemetry/Histograms.json
--- a/security/certverifier/CTKnownLogs.h
+++ b/security/certverifier/CTKnownLogs.h
@@ -1,94 +1,126 @@
 /* -*- 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 was automatically generated by getCTKnownLogs.py. */
+/* Note: manually updated the disqualified logs using the info from */
+/* https://cs.chromium.org/chromium/src/net/cert/ct_known_logs_static-inc.h */
 
 #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;
+struct CTLogInfo
+{
+  const char* const name;
+  // Index within kCTLogOperatorList.
+  const size_t operatorIndex;
+  const bool disqualified;
+  // 0 for qualified logs, disqualification time for disqualified logs
+  // (in milliseconds, measured since the epoch, ignoring leap seconds).
+  const uint64_t disqualificationTime;
+  const char* const key;
+  const size_t keyLength;
+};
+
+struct CTLogOperatorInfo
+{
+  const char* const name;
+  const uint16_t id;
 };
 
 const CTLogInfo kCTLogList[] = {
   { "Google 'Pilot' log",
-    "https://ct.googleapis.com/pilot/",
+    0, // operated by Google
+    false, // qualified log
+    0, // no disqualification time
     "\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/",
+    0, // operated by Google
+    false, // qualified log
+    0, // no disqualification time
     "\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/",
+    1, // operated by DigiCert
+    false, // qualified log
+    0, // no disqualification time
     "\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/",
+    0, // operated by Google
+    false, // qualified log
+    0, // no disqualification time
     "\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/",
+    2, // operated by Certly
+    // *** NOTE - DISQUALIFICATION STATUS MANUALLY UPDATED ***
+    true, // disqualified log
+    1460678400000, // Date.parse("2016-04-15T00:00:00Z")
     "\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/",
+    3, // operated by Izenpe
+    // *** NOTE - DISQUALIFICATION STATUS MANUALLY UPDATED ***
+    true, // disqualified log
+    1464566400000, // Date.parse("2016-05-30T00:00:00Z")
     "\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/",
+    4, // operated by Symantec
+    false, // qualified log
+    0, // no disqualification time
     "\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/",
+    5, // operated by Venafi
+    false, // qualified log
+    0, // no disqualification time
     "\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"
@@ -98,26 +130,30 @@ const CTLogInfo kCTLogList[] = {
     "\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/",
+    4, // operated by Symantec
+    false, // qualified log
+    0, // no disqualification time
     "\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/",
+    6, // operated by CNNIC
+    false, // qualified log
+    0, // no disqualification time
     "\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"
@@ -127,28 +163,44 @@ const CTLogInfo kCTLogList[] = {
     "\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/",
+    7, // operated by WoSign
+    false, // qualified log
+    0, // no disqualification time
     "\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/",
+    8, // operated by StartCom
+    false, // qualified log
+    0, // no disqualification time
     "\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 }
 };
 
+const CTLogOperatorInfo kCTLogOperatorList[] = {
+  { "Google", 0 },
+  { "DigiCert", 1 },
+  { "Certly", 2 },
+  { "Izenpe", 3 },
+  { "Symantec", 4 },
+  { "Venafi", 5 },
+  { "CNNIC", 7 },
+  { "WoSign", 8 },
+  { "StartCom", 9 }
+};
+
 #endif // CTKnownLogs_h
--- a/security/certverifier/CTLogVerifier.cpp
+++ b/security/certverifier/CTLogVerifier.cpp
@@ -1,16 +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 "CTLogVerifier.h"
 
+#include <stdint.h>
+
 #include "CTSerialization.h"
 #include "hasht.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
 #include "pkix/pkixnss.h"
 #include "pkixutil.h"
 
 namespace mozilla { namespace ct {
@@ -115,21 +117,27 @@ public:
   }
 
   DigitallySigned::SignatureAlgorithm mSignatureAlgorithm;
 };
 
 
 CTLogVerifier::CTLogVerifier()
   : mSignatureAlgorithm(DigitallySigned::SignatureAlgorithm::Anonymous)
+  , mOperatorId(-1)
+  , mDisqualified(false)
+  , mDisqualificationTime(UINT64_MAX)
 {
 }
 
 Result
-CTLogVerifier::Init(Input subjectPublicKeyInfo)
+CTLogVerifier::Init(Input subjectPublicKeyInfo,
+                    CTLogOperatorId operatorId,
+                    bool disqualified,
+                    uint64_t disqualificationTime)
 {
   SignatureParamsTrustDomain trustDomain;
   Result rv = CheckSubjectPublicKeyInfo(subjectPublicKeyInfo, trustDomain,
                                         EndEntityOrCA::MustBeEndEntity);
   if (rv != Success) {
     return rv;
   }
   mSignatureAlgorithm = trustDomain.mSignatureAlgorithm;
@@ -143,16 +151,19 @@ CTLogVerifier::Init(Input subjectPublicK
     return Result::FATAL_ERROR_NO_MEMORY;
   }
   rv = DigestBufNSS(subjectPublicKeyInfo, DigestAlgorithm::sha256,
                     mKeyId.begin(), mKeyId.length());
   if (rv != Success) {
     return rv;
   }
 
+  mOperatorId = operatorId;
+  mDisqualified = disqualified;
+  mDisqualificationTime = disqualified ? disqualificationTime : UINT64_MAX;
   return Success;
 }
 
 Result
 CTLogVerifier::Verify(const LogEntry& entry,
                       const SignedCertificateTimestamp& sct)
 {
   if (mKeyId.empty() || sct.logId != mKeyId) {
--- a/security/certverifier/CTLogVerifier.h
+++ b/security/certverifier/CTLogVerifier.h
@@ -21,26 +21,39 @@ namespace mozilla { namespace ct {
 // The verification functions return Success if the provided SCT has passed
 // verification, ERROR_BAD_SIGNATURE if failed verification, or other result
 // on error.
 class CTLogVerifier
 {
 public:
   CTLogVerifier();
 
-  // Initializes the verifier with log-specific information.
+  // Initializes the verifier with log-specific information. Only the public
+  // key is used for verification, other parameters are purely informational.
   // |subjectPublicKeyInfo| is a DER-encoded SubjectPublicKeyInfo.
+  // |operatorId| The numeric ID of the log operator as assigned at
+  // https://www.certificate-transparency.org/known-logs .
+  // |disqualified| Whether the log is disqualified.
+  // |disqualificationTime| Disqualification timestamp (for disqualified logs).
   // An error is returned if |subjectPublicKeyInfo| refers to an unsupported
   // public key.
-  pkix::Result Init(pkix::Input subjectPublicKeyInfo);
+  pkix::Result Init(pkix::Input subjectPublicKeyInfo,
+                    CTLogOperatorId operatorId = -1,
+                    bool disqualified = false,
+                    uint64_t disqualificationTime = 0);
 
   // Returns the log's key ID, which is a SHA256 hash of its public key.
   // See RFC 6962, Section 3.2.
   const Buffer& keyId() const { return mKeyId; }
 
+  // Informational attributes passed to the constructor.
+  CTLogOperatorId operatorId() const { return mOperatorId; }
+  bool isDisqualified() const { return mDisqualified; }
+  uint64_t disqualificationTime() const { return mDisqualificationTime; }
+
   // Verifies that |sct| contains a valid signature for |entry|.
   // |sct| must be signed by the verifier's log.
   pkix::Result Verify(const LogEntry& entry,
                       const SignedCertificateTimestamp& sct);
 
   // Verifies the signature in |sth|.
   // |sth| must be signed by the verifier's log.
   pkix::Result VerifySignedTreeHead(const SignedTreeHead& sth);
@@ -56,13 +69,16 @@ private:
   // Returns Success if passed verification, ERROR_BAD_SIGNATURE if failed
   // verification, or other result on error.
   pkix::Result VerifySignature(pkix::Input data, pkix::Input signature);
   pkix::Result VerifySignature(const Buffer& data, const Buffer& signature);
 
   Buffer mSubjectPublicKeyInfo;
   Buffer mKeyId;
   DigitallySigned::SignatureAlgorithm mSignatureAlgorithm;
+  CTLogOperatorId mOperatorId;
+  bool mDisqualified;
+  uint64_t mDisqualificationTime;
 };
 
 } } // namespace mozilla::ct
 
 #endif // CTLogVerifier_h
--- a/security/certverifier/CTSerialization.cpp
+++ b/security/certverifier/CTSerialization.cpp
@@ -506,20 +506,16 @@ 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
--- a/security/certverifier/CTVerifyResult.cpp
+++ b/security/certverifier/CTVerifyResult.cpp
@@ -1,18 +1,28 @@
 /* -*- 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"
 
+#include <stdint.h>
+
 namespace mozilla { namespace ct {
 
+VerifiedSCT::VerifiedSCT()
+  : status(Status::None)
+  , origin(Origin::Unknown)
+  , logOperatorId(-1)
+  , logDisqualificationTime(UINT64_MAX)
+{
+}
+
 void
 CTVerifyResult::Reset()
 {
-  scts.clear();
+  verifiedScts.clear();
   decodingErrors = 0;
 }
 
 } } // namespace mozilla::ct
--- a/security/certverifier/CTVerifyResult.h
+++ b/security/certverifier/CTVerifyResult.h
@@ -7,25 +7,64 @@
 #ifndef CTVerifyResult_h
 #define CTVerifyResult_h
 
 #include "mozilla/Vector.h"
 #include "SignedCertificateTimestamp.h"
 
 namespace mozilla { namespace ct {
 
-typedef Vector<SignedCertificateTimestamp> SCTList;
+// Holds a verified Signed Certificate Timestamp along with the verification
+// status (e.g. valid/invalid) and additional information related to the
+// verification.
+struct VerifiedSCT
+{
+  VerifiedSCT();
+
+  // The original SCT.
+  SignedCertificateTimestamp sct;
+
+  enum class Status {
+    None,
+    // The SCT is from a known log, and the signature is valid.
+    Valid,
+    // The SCT is from a known disqualified log, and the signature is valid.
+    // For the disqualification time of the log see |logDisqualificationTime|.
+    DisqualifiedLog,
+    // 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 SCTs are considered invalid (see RFC 6962, Section 5.2).
+    InvalidTimestamp,
+  };
+
+  enum class Origin {
+    Unknown,
+    Embedded,
+    TLSExtension,
+    OCSPResponse,
+  };
+
+  Status status;
+  Origin origin;
+  CTLogOperatorId logOperatorId;
+  uint64_t logDisqualificationTime;
+};
+
+typedef Vector<VerifiedSCT> VerifiedSCTList;
 
 // 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;
+  // SCTs that were processed during the verification along with their
+  // verification results.
+  VerifiedSCTList verifiedScts;
 
   // 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
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -4,16 +4,17 @@
  * 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 "CTKnownLogs.h"
+#include "CTLogVerifier.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"
@@ -154,26 +155,37 @@ BuildCertChainForOneKeyUsage(NSSCertDBTr
 
 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);
+      BitwiseCast<const uint8_t*, const char*>(log.key), log.keyLength);
     if (rv != Success) {
       MOZ_ASSERT_UNREACHABLE("Failed reading a log key for a known CT Log");
       continue;
     }
-    rv = mCTVerifier->AddLog(publicKey);
+
+    CTLogVerifier logVerifier;
+    const CTLogOperatorInfo& logOperator =
+      kCTLogOperatorList[log.operatorIndex];
+    rv = logVerifier.Init(publicKey, logOperator.id,
+                          log.disqualified, log.disqualificationTime);
     if (rv != Success) {
       MOZ_ASSERT_UNREACHABLE("Failed initializing a known CT Log");
       continue;
     }
+
+    rv = mCTVerifier->AddLog(Move(logVerifier));
+    if (rv != Success) {
+      MOZ_ASSERT_UNREACHABLE("Failed activating a known CT Log");
+      continue;
+    }
   }
 }
 
 Result
 CertVerifier::VerifySignedCertificateTimestamps(
   NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& builtChain,
   Input sctsFromTLS, Time time,
   /*optional out*/ CertificateTransparencyInfo* ctInfo)
@@ -253,45 +265,49 @@ CertVerifier::VerifySignedCertificateTim
                            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 validCount = 0;
     size_t unknownLogCount = 0;
+    size_t disqualifiedLogCount = 0;
     size_t invalidSignatureCount = 0;
     size_t invalidTimestampCount = 0;
-    for (const SignedCertificateTimestamp& sct : result.scts) {
-      switch (sct.verificationStatus) {
-        case SignedCertificateTimestamp::VerificationStatus::OK:
-          verifiedCount++;
+    for (const VerifiedSCT& verifiedSct : result.verifiedScts) {
+      switch (verifiedSct.status) {
+        case VerifiedSCT::Status::Valid:
+          validCount++;
           break;
-        case SignedCertificateTimestamp::VerificationStatus::UnknownLog:
+        case VerifiedSCT::Status::DisqualifiedLog:
+          disqualifiedLogCount++;
+          break;
+        case VerifiedSCT::Status::UnknownLog:
           unknownLogCount++;
           break;
-        case SignedCertificateTimestamp::VerificationStatus::InvalidSignature:
+        case VerifiedSCT::Status::InvalidSignature:
           invalidSignatureCount++;
           break;
-        case SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp:
+        case VerifiedSCT::Status::InvalidTimestamp:
           invalidTimestampCount++;
           break;
-        case SignedCertificateTimestamp::VerificationStatus::None:
+        case VerifiedSCT::Status::None:
         default:
-          MOZ_ASSERT_UNREACHABLE("Unexpected SCT verificationStatus");
+          MOZ_ASSERT_UNREACHABLE("Unexpected SCT verification status");
       }
     }
     MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
             ("SCT verification result: "
-             "verified=%zu unknownLog=%zu "
+             "valid=%zu unknownLog=%zu disqualifiedLog=%zu "
              "invalidSignature=%zu invalidTimestamp=%zu "
              "decodingErrors=%zu\n",
-             verifiedCount, unknownLogCount,
+             validCount, unknownLogCount, disqualifiedLogCount,
              invalidSignatureCount, invalidTimestampCount,
              result.decodingErrors));
   }
 
   if (ctInfo) {
     ctInfo->processedSCTs = true;
     ctInfo->verifyResult = Move(result);
   }
--- a/security/certverifier/MultiLogCTVerifier.cpp
+++ b/security/certverifier/MultiLogCTVerifier.cpp
@@ -10,37 +10,32 @@
 #include "CTSerialization.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Move.h"
 
 namespace mozilla { namespace ct {
 
 using namespace mozilla::pkix;
 
-// Note: this moves |sct| to the target list in |result|, invalidating |sct|.
+// Note: this moves |verifiedSct| to the target list in |result|.
 static Result
 StoreVerifiedSct(CTVerifyResult& result,
-                 SignedCertificateTimestamp&& sct,
-                 SignedCertificateTimestamp::VerificationStatus status)
+                 VerifiedSCT&& verifiedSct,
+                 VerifiedSCT::Status status)
 {
-  sct.verificationStatus = status;
-  if (!result.scts.append(Move(sct))) {
+  verifiedSct.status = status;
+  if (!result.verifiedScts.append(Move(verifiedSct))) {
     return Result::FATAL_ERROR_NO_MEMORY;
   }
   return Success;
 }
 
 Result
-MultiLogCTVerifier::AddLog(Input publicKey)
+MultiLogCTVerifier::AddLog(CTLogVerifier&& log)
 {
-  CTLogVerifier log;
-  Result rv = log.Init(publicKey);
-  if (rv != Success) {
-    return rv;
-  }
   if (!mLogs.append(Move(log))) {
     return Result::FATAL_ERROR_NO_MEMORY;
   }
   return Success;
 }
 
 Result
 MultiLogCTVerifier::Verify(Input cert,
@@ -60,55 +55,52 @@ MultiLogCTVerifier::Verify(Input cert,
   if (issuerSubjectPublicKeyInfo.GetLength() > 0 &&
       sctListFromCert.GetLength() > 0) {
     LogEntry precertEntry;
     rv = GetPrecertLogEntry(cert, issuerSubjectPublicKeyInfo, precertEntry);
     if (rv != Success) {
       return rv;
     }
     rv = VerifySCTs(sctListFromCert, precertEntry,
-                    SignedCertificateTimestamp::Origin::Embedded, time,
-                    result);
+                    VerifiedSCT::Origin::Embedded, time, result);
     if (rv != Success) {
       return rv;
     }
   }
 
   LogEntry x509Entry;
   rv = GetX509LogEntry(cert, x509Entry);
   if (rv != Success) {
     return rv;
   }
 
   // Verify SCTs from a stapled OCSP response
   if (sctListFromOCSPResponse.GetLength() > 0) {
     rv = VerifySCTs(sctListFromOCSPResponse, x509Entry,
-                    SignedCertificateTimestamp::Origin::OCSPResponse, time,
-                    result);
+                    VerifiedSCT::Origin::OCSPResponse, time, result);
     if (rv != Success) {
       return rv;
     }
   }
 
   // Verify SCTs from a TLS extension
   if (sctListFromTLSExtension.GetLength() > 0) {
     rv = VerifySCTs(sctListFromTLSExtension, x509Entry,
-                    SignedCertificateTimestamp::Origin::TLSExtension, time,
-                    result);
+                    VerifiedSCT::Origin::TLSExtension, time, result);
     if (rv != Success) {
       return rv;
     }
   }
   return Success;
 }
 
 Result
 MultiLogCTVerifier::VerifySCTs(Input encodedSctList,
                                const LogEntry& expectedEntry,
-                               SignedCertificateTimestamp::Origin origin,
+                               VerifiedSCT::Origin origin,
                                Time time,
                                CTVerifyResult& result)
 {
   Reader listReader;
   Result rv = DecodeSCTList(encodedSctList, listReader);
   if (rv != Success) {
     result.decodingErrors++;
     return Success;
@@ -124,70 +116,84 @@ MultiLogCTVerifier::VerifySCTs(Input enc
 
     Reader encodedSctReader(encodedSct);
     SignedCertificateTimestamp sct;
     rv = DecodeSignedCertificateTimestamp(encodedSctReader, sct);
     if (rv != Success) {
       result.decodingErrors++;
       continue;
     }
-    sct.origin = origin;
 
-    rv = VerifySingleSCT(Move(sct), expectedEntry, time, result);
+    rv = VerifySingleSCT(Move(sct), expectedEntry, origin, time, result);
     if (rv != Success) {
       return rv;
     }
   }
   return Success;
 }
 
 Result
 MultiLogCTVerifier::VerifySingleSCT(SignedCertificateTimestamp&& sct,
                                     const LogEntry& expectedEntry,
+                                    VerifiedSCT::Origin origin,
                                     Time time,
                                     CTVerifyResult& result)
 {
+  VerifiedSCT verifiedSct;
+  verifiedSct.origin = origin;
+
   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),
-      SignedCertificateTimestamp::VerificationStatus::UnknownLog);
+    return StoreVerifiedSct(result,
+      Move(verifiedSct), VerifiedSCT::Status::UnknownLog);
   }
 
+  verifiedSct.logOperatorId = matchingLog->operatorId();
+
   if (!matchingLog->SignatureParametersMatch(sct.signature)) {
     // SCT signature parameters do not match the log's.
-    return StoreVerifiedSct(result, Move(sct),
-      SignedCertificateTimestamp::VerificationStatus::InvalidSignature);
+    return StoreVerifiedSct(result,
+      Move(verifiedSct), VerifiedSCT::Status::InvalidSignature);
   }
 
   Result rv = matchingLog->Verify(expectedEntry, sct);
   if (rv != Success) {
     if (rv == Result::ERROR_BAD_SIGNATURE) {
-      return StoreVerifiedSct(result, Move(sct),
-        SignedCertificateTimestamp::VerificationStatus::InvalidSignature);
+      return StoreVerifiedSct(result,
+        Move(verifiedSct), VerifiedSCT::Status::InvalidSignature);
     }
     return rv;
   }
 
+  // Make sure the timestamp is legitimate (not in the future).
   // |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.
+  // (towards the future) 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 (sctTime > time) {
-    return StoreVerifiedSct(result, Move(sct),
-      SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp);
+    return StoreVerifiedSct(result,
+      Move(verifiedSct), VerifiedSCT::Status::InvalidTimestamp);
   }
 
-  return StoreVerifiedSct(result, Move(sct),
-    SignedCertificateTimestamp::VerificationStatus::OK);
+  // SCT verified ok, see if the log is qualified. Since SCTs from
+  // disqualified logs are treated as valid under certain circumstances (see
+  // the CT Policy), the log qualification check must be the last one we do.
+  if (matchingLog->isDisqualified()) {
+    verifiedSct.logDisqualificationTime = matchingLog->disqualificationTime();
+    return StoreVerifiedSct(result,
+      Move(verifiedSct), VerifiedSCT::Status::DisqualifiedLog);
+  }
+
+  return StoreVerifiedSct(result,
+    Move(verifiedSct), VerifiedSCT::Status::Valid);
 }
 
 } } // namespace mozilla::ct
--- a/security/certverifier/MultiLogCTVerifier.h
+++ b/security/certverifier/MultiLogCTVerifier.h
@@ -18,17 +18,17 @@
 namespace mozilla { namespace ct {
 
 // 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);
+  pkix::Result AddLog(CTLogVerifier&& log);
 
   // Verifies SCTs embedded in the certificate itself, SCTs embedded in a
   // stapled OCSP response, and SCTs obtained via the
   // signed_certificate_timestamp TLS extension on the given |cert|.
   //
   // A certificate is permitted but not required to use multiple sources for
   // SCTs. It is expected that most certificates will use only one source
   // (embedding, TLS extension or OCSP stapling).
@@ -61,24 +61,25 @@ public:
                       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,
+                          VerifiedSCT::Origin origin,
                           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,
+                               VerifiedSCT::Origin origin,
                                pkix::Time time,
                                CTVerifyResult& result);
 
   // The list of known logs.
   Vector<CTLogVerifier> mLogs;
 };
 
 } } // namespace mozilla::ct
--- a/security/certverifier/SignedCertificateTimestamp.cpp
+++ b/security/certverifier/SignedCertificateTimestamp.cpp
@@ -33,13 +33,14 @@ namespace mozilla {
 bool
 operator==(const ct::Buffer& a, const ct::Buffer& b)
 {
   return (a.empty() && b.empty()) ||
     (a.length() == b.length() && memcmp(a.begin(), b.begin(), a.length()) == 0);
 }
 
 bool
-operator!=(const ct::Buffer& a, const ct::Buffer& b) {
+operator!=(const ct::Buffer& a, const ct::Buffer& b)
+{
   return !(a == b);
 }
 
 } // namespace mozilla
--- a/security/certverifier/SignedCertificateTimestamp.h
+++ b/security/certverifier/SignedCertificateTimestamp.h
@@ -80,44 +80,24 @@ struct SignedCertificateTimestamp
 
   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;
 };
 
+// Signed integer sufficient to store the numeric ID of CT log operators
+// as assigned at https://www.certificate-transparency.org/known-logs .
+// The assigned IDs are 0-based positive integers.
+// While not directly related to SCTs, the log operator ID is used when
+// enforcing the CT Policy on connections.
+typedef int32_t CTLogOperatorId;
 
 inline pkix::Result BufferToInput(const Buffer& buffer, pkix::Input& input)
 {
   return input.Init(buffer.begin(), buffer.length());
 }
 
 inline pkix::Result InputToBuffer(pkix::Input input, Buffer& buffer)
 {
--- a/security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
+++ b/security/certverifier/tests/gtest/MultiLogCTVerifierTest.cpp
@@ -21,44 +21,48 @@ namespace mozilla { namespace ct {
 
 using namespace mozilla::pkix;
 
 class MultiLogCTVerifierTest : public ::testing::Test
 {
 public:
   MultiLogCTVerifierTest()
     : mNow(Time::uninitialized)
+    , mLogOperatorID(123)
   {}
 
   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())));
+    CTLogVerifier log;
+    ASSERT_EQ(Success, log.Init(InputForBuffer(GetTestPublicKey()),
+                                mLogOperatorID));
+    ASSERT_EQ(Success, mVerifier.AddLog(Move(log)));
 
     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 = TimeFromEpochInSeconds(1451606400u); // Date.parse("2016-01-01")/1000
   }
 
-  void CheckForSingleVerifiedSCTInResult(const CTVerifyResult& result,
-    SignedCertificateTimestamp::Origin origin)
+  void CheckForSingleValidSCTInResult(const CTVerifyResult& result,
+    VerifiedSCT::Origin origin)
   {
     EXPECT_EQ(0U, result.decodingErrors);
-    ASSERT_EQ(1U, result.scts.length());
-    EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::OK,
-              result.scts[0].verificationStatus);
-    EXPECT_EQ(origin, result.scts[0].origin);
+    ASSERT_EQ(1U, result.verifiedScts.length());
+    EXPECT_EQ(VerifiedSCT::Status::Valid, result.verifiedScts[0].status);
+    EXPECT_EQ(origin, result.verifiedScts[0].origin);
+    EXPECT_EQ(mLogOperatorID, result.verifiedScts[0].logOperatorId);
   }
 
   // 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));
@@ -80,29 +84,29 @@ public:
     ExtractEmbeddedSCTList(cert, sctList);
     ASSERT_FALSE(sctList.empty());
 
     CTVerifyResult result;
     ASSERT_EQ(Success,
               mVerifier.Verify(InputForBuffer(cert), InputForBuffer(issuerSPKI),
                                InputForBuffer(sctList), Input(), Input(),
                                mNow, result));
-    CheckForSingleVerifiedSCTInResult(result,
-      SignedCertificateTimestamp::Origin::Embedded);
+    CheckForSingleValidSCTInResult(result, VerifiedSCT::Origin::Embedded);
   }
 
 protected:
   MultiLogCTVerifier mVerifier;
   Buffer mTestCert;
   Buffer mEmbeddedCert;
   Buffer mCaCert;
   Buffer mCaCertSPKI;
   Buffer mIntermediateCert;
   Buffer mIntermediateCertSPKI;
   Time mNow;
+  CTLogOperatorId mLogOperatorID;
 };
 
 // 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;
@@ -161,73 +165,94 @@ TEST_F(MultiLogCTVerifierTest, VerifiesS
   EncodeSCTListForTesting(InputForBuffer(sct), sctList);
 
   CTVerifyResult result;
   ASSERT_EQ(Success,
             mVerifier.Verify(InputForBuffer(mTestCert), Input(),
                              Input(), InputForBuffer(sctList), Input(),
                              mNow, result));
 
-  CheckForSingleVerifiedSCTInResult(result,
-    SignedCertificateTimestamp::Origin::OCSPResponse);
+  CheckForSingleValidSCTInResult(result, VerifiedSCT::Origin::OCSPResponse);
 }
 
 TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromTLS)
 {
   Buffer sct(GetTestSignedCertificateTimestamp());
   Buffer sctList;
   EncodeSCTListForTesting(InputForBuffer(sct), sctList);
 
   CTVerifyResult result;
   ASSERT_EQ(Success,
             mVerifier.Verify(InputForBuffer(mTestCert), Input(),
                              Input(), Input(), InputForBuffer(sctList),
                              mNow, result));
 
-  CheckForSingleVerifiedSCTInResult(result,
-    SignedCertificateTimestamp::Origin::TLSExtension);
+  CheckForSingleValidSCTInResult(result, VerifiedSCT::Origin::TLSExtension);
 }
 
 TEST_F(MultiLogCTVerifierTest, VerifiesSCTFromMultipleSources)
 {
   Buffer sct(GetTestSignedCertificateTimestamp());
   Buffer sctList;
   EncodeSCTListForTesting(InputForBuffer(sct), sctList);
 
   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 (const SignedCertificateTimestamp& sct : result.scts) {
-    EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::OK,
-              sct.verificationStatus);
-    origins += sct.origin;
+  EnumSet<VerifiedSCT::Origin> origins;
+  for (const VerifiedSCT& verifiedSct : result.verifiedScts) {
+    EXPECT_EQ(VerifiedSCT::Status::Valid, verifiedSct.status);
+    origins += verifiedSct.origin;
   }
-  EXPECT_FALSE(
-    origins.contains(SignedCertificateTimestamp::Origin::Embedded));
-  EXPECT_TRUE(
-    origins.contains(SignedCertificateTimestamp::Origin::OCSPResponse));
-  EXPECT_TRUE(
-    origins.contains(SignedCertificateTimestamp::Origin::TLSExtension));
+  EXPECT_FALSE(origins.contains(VerifiedSCT::Origin::Embedded));
+  EXPECT_TRUE(origins.contains(VerifiedSCT::Origin::OCSPResponse));
+  EXPECT_TRUE(origins.contains(VerifiedSCT::Origin::TLSExtension));
 }
 
 TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromUnknownLog)
 {
   Buffer sctList;
   GetSCTListWithInvalidLogID(sctList);
 
   CTVerifyResult result;
   ASSERT_EQ(Success,
             mVerifier.Verify(InputForBuffer(mTestCert), Input(),
                              Input(), Input(), InputForBuffer(sctList),
                              mNow, result));
 
   EXPECT_EQ(0U, result.decodingErrors);
-  ASSERT_EQ(1U, result.scts.length());
-  EXPECT_EQ(SignedCertificateTimestamp::VerificationStatus::UnknownLog,
-            result.scts[0].verificationStatus);
+  ASSERT_EQ(1U, result.verifiedScts.length());
+  EXPECT_EQ(VerifiedSCT::Status::UnknownLog, result.verifiedScts[0].status);
+}
+
+TEST_F(MultiLogCTVerifierTest, IdentifiesSCTFromDisqualifiedLog)
+{
+  MultiLogCTVerifier verifier;
+  CTLogVerifier log;
+  const uint64_t disqualificationTime = 12345u;
+  ASSERT_EQ(Success, log.Init(InputForBuffer(GetTestPublicKey()),
+    mLogOperatorID, true /*disqualified log*/, disqualificationTime));
+  ASSERT_EQ(Success, verifier.AddLog(Move(log)));
+
+  Buffer sct(GetTestSignedCertificateTimestamp());
+  Buffer sctList;
+  EncodeSCTListForTesting(InputForBuffer(sct), sctList);
+
+  CTVerifyResult result;
+  ASSERT_EQ(Success,
+            verifier.Verify(InputForBuffer(mTestCert), Input(),
+                            Input(), Input(), InputForBuffer(sctList),
+                            mNow, result));
+
+  EXPECT_EQ(0U, result.decodingErrors);
+  ASSERT_EQ(1U, result.verifiedScts.length());
+  EXPECT_EQ(VerifiedSCT::Status::DisqualifiedLog,
+            result.verifiedScts[0].status);
+  EXPECT_EQ(disqualificationTime,
+            result.verifiedScts[0].logDisqualificationTime);
+  EXPECT_EQ(mLogOperatorID, result.verifiedScts[0].logOperatorId);
 }
 
 } } // namespace mozilla::ct
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -1222,52 +1222,55 @@ GatherSuccessfulValidationTelemetry(cons
 {
   GatherBaselineRequirementsTelemetry(certList);
   GatherEKUTelemetry(certList);
   GatherRootCATelemetry(certList);
   GatherEndEntityTelemetry(certList);
 }
 
 void
-GatherTelemetryForSingleSCT(const ct::SignedCertificateTimestamp& sct)
+GatherTelemetryForSingleSCT(const ct::VerifiedSCT& verifiedSct)
 {
   // See SSL_SCTS_ORIGIN in Histograms.json.
   uint32_t origin = 0;
-  switch (sct.origin) {
-    case ct::SignedCertificateTimestamp::Origin::Embedded:
+  switch (verifiedSct.origin) {
+    case ct::VerifiedSCT::Origin::Embedded:
       origin = 1;
       break;
-    case ct::SignedCertificateTimestamp::Origin::TLSExtension:
+    case ct::VerifiedSCT::Origin::TLSExtension:
       origin = 2;
       break;
-    case ct::SignedCertificateTimestamp::Origin::OCSPResponse:
+    case ct::VerifiedSCT::Origin::OCSPResponse:
       origin = 3;
       break;
     default:
-      MOZ_ASSERT_UNREACHABLE("Unexpected SCT::Origin type");
+      MOZ_ASSERT_UNREACHABLE("Unexpected VerifiedSCT::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:
+  switch (verifiedSct.status) {
+    case ct::VerifiedSCT::Status::Valid:
       verificationStatus = 1;
       break;
-    case ct::SignedCertificateTimestamp::VerificationStatus::UnknownLog:
+    case ct::VerifiedSCT::Status::UnknownLog:
       verificationStatus = 2;
       break;
-    case ct::SignedCertificateTimestamp::VerificationStatus::InvalidSignature:
+    case ct::VerifiedSCT::Status::InvalidSignature:
       verificationStatus = 3;
       break;
-    case ct::SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp:
+    case ct::VerifiedSCT::Status::InvalidTimestamp:
       verificationStatus = 4;
       break;
+    case ct::VerifiedSCT::Status::DisqualifiedLog:
+      verificationStatus = 5;
+      break;
     default:
-      MOZ_ASSERT_UNREACHABLE("Unexpected SCT::VerificationStatus type");
+      MOZ_ASSERT_UNREACHABLE("Unexpected VerifiedSCT::Status type");
   }
   Telemetry::Accumulate(Telemetry::SSL_SCTS_VERIFICATION_STATUS,
                         verificationStatus);
 }
 
 void
 GatherCertificateTransparencyTelemetry(const UniqueCERTCertList& certList,
                                        const CertificateTransparencyInfo& info)
@@ -1278,28 +1281,29 @@ GatherCertificateTransparencyTelemetry(c
   }
 
   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) {
+  for (const ct::VerifiedSCT& sct : info.verifyResult.verifiedScts) {
     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());
+  uint32_t sctsCount =
+    static_cast<uint32_t>(info.verifyResult.verifiedScts.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,
--- a/security/manager/ssl/nsSSLStatus.cpp
+++ b/security/manager/ssl/nsSSLStatus.cpp
@@ -1,21 +1,21 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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"
 #include "mozilla/Casting.h"
 #include "nsSSLStatus.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIObjectOutputStream.h"
 #include "nsIObjectInputStream.h"
 #include "nsNSSCertificate.h"
-#include "SignedCertificateTimestamp.h"
 #include "ssl.h"
 
 NS_IMETHODIMP
 nsSSLStatus::GetServerCert(nsIX509Cert** aServerCert)
 {
   NS_ENSURE_ARG_POINTER(aServerCert);
 
   nsCOMPtr<nsIX509Cert> cert = mServerCert;
@@ -325,53 +325,54 @@ nsSSLStatus::SetServerCert(nsNSSCertific
   mIsEV = (aEVStatus == EVStatus::EV);
   mHasIsEVStatus = true;
 }
 
 void
 nsSSLStatus::SetCertificateTransparencyInfo(
   const mozilla::psm::CertificateTransparencyInfo& info)
 {
-  using mozilla::ct::SignedCertificateTimestamp;
+  using mozilla::ct::VerifiedSCT;
 
   if (!info.enabled) {
     // CT disabled.
     mCertificateTransparencyStatus =
       nsISSLStatus::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
     return;
   }
 
   if (!info.processedSCTs) {
     // No SCTs processed on the connection.
     mCertificateTransparencyStatus =
       nsISSLStatus::CERTIFICATE_TRANSPARENCY_NONE;
     return;
   }
 
-  bool hasOKSCTs = false;
+  bool hasValidSCTs = false;
   bool hasUnknownLogSCTs = false;
   bool hasInvalidSCTs = false;
-  for (const SignedCertificateTimestamp& sct : info.verifyResult.scts) {
-    switch (sct.verificationStatus) {
-      case SignedCertificateTimestamp::VerificationStatus::OK:
-        hasOKSCTs = true;
+  for (const VerifiedSCT& verifiedSct : info.verifyResult.verifiedScts) {
+    switch (verifiedSct.status) {
+      case VerifiedSCT::Status::Valid:
+        hasValidSCTs = true;
         break;
-      case SignedCertificateTimestamp::VerificationStatus::UnknownLog:
+      case VerifiedSCT::Status::UnknownLog:
+      case VerifiedSCT::Status::DisqualifiedLog:
         hasUnknownLogSCTs = true;
         break;
-      case SignedCertificateTimestamp::VerificationStatus::InvalidSignature:
-      case SignedCertificateTimestamp::VerificationStatus::InvalidTimestamp:
+      case VerifiedSCT::Status::InvalidSignature:
+      case VerifiedSCT::Status::InvalidTimestamp:
         hasInvalidSCTs = true;
         break;
       default:
-        MOZ_ASSERT_UNREACHABLE("Unexpected SCT::VerificationStatus type");
+        MOZ_ASSERT_UNREACHABLE("Unexpected VerifiedSCT::Status type");
     }
   }
 
-  if (hasOKSCTs) {
+  if (hasValidSCTs) {
     mCertificateTransparencyStatus =
       nsISSLStatus::CERTIFICATE_TRANSPARENCY_OK;
   } else if (hasUnknownLogSCTs) {
     mCertificateTransparencyStatus =
       nsISSLStatus::CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG;
   } else if (hasInvalidSCTs) {
     mCertificateTransparencyStatus =
       nsISSLStatus::CERTIFICATE_TRANSPARENCY_INVALID;
--- a/security/manager/tools/getCTKnownLogs.py
+++ b/security/manager/tools/getCTKnownLogs.py
@@ -29,70 +29,124 @@ OUTPUT_TEMPLATE = """\
 
 /* This file was automatically generated by $prog. */
 
 #ifndef $include_guard
 #define $include_guard
 
 #include <stddef.h>
 
-struct CTLogInfo {
-  const char* const logName;
-  const char* const logUrl;
-  const char* const logKey;
-  const size_t logKeyLength;
+struct CTLogInfo
+{
+  const char* const name;
+  // Index within kCTLogOperatorList.
+  const size_t operatorIndex;
+  const bool isDisqualified;
+  // 0 for qualified logs, disqualification time for disqualified logs
+  // (in milliseconds, measured since the epoch, ignoring leap seconds).
+  const uint64_t disqualificationTime;
+  const char* const key;
+  const size_t keyLength;
+};
+
+struct CTLogOperatorInfo
+{
+  const char* const name;
+  const uint16_t id;
 };
 
 const CTLogInfo kCTLogList[] = {
 $logs
 };
 
+const CTLogOperatorInfo kCTLogOperatorList[] = {
+$operators
+};
+
 #endif // $include_guard
 """
 
 
 def get_hex_lines(blob, width):
     """ Convert a binary string to a multiline text of C escape sequences. """
     text = "".join(["\\x{:02x}".format(ord(c)) for c in blob])
     # When escaped, a single byte takes 4 chars (e.g. "\x00").
     # Make sure we don't break an escaped byte between the lines.
     return textwrap.wrap(text, width - width % 4)
 
 
+def get_operator_and_index(json_data, operator_id):
+    """ Return operator's entry from the JSON along with its array index. """
+    matches = [(operator, index) for (index, operator) in enumerate(
+        json_data["operators"]) if operator["id"] == operator_id]
+    assert len(matches) != 0, "No operators with id {0} defined.".format(
+        operator_id)
+    assert len(matches) == 1, "Found multiple operators with id {0}.".format(
+        operator_id)
+    return matches[0]
+
+
 def get_log_info_structs(json_data):
     """ Return array of CTLogInfo initializers for the known logs. """
     tmpl = Template(textwrap.dedent("""\
           { $description,
-            $url,
+            $operator_index, // $operator_comment
+            $disqualified, // $disqualified_comment
+            $disqualification_timestamp, // $disqualification_timestamp_comment
         $indented_log_key,
             $log_key_len }"""))
     initializers = []
     for log in json_data["logs"]:
         log_key = base64.decodestring(log["key"])
+        # "operated_by" is a list, we assume here it always contains one item.
+        operated_by = log["operated_by"]
+        assert len(operated_by) == 1, "operated_by must contain one item."
+        operator, operator_index = get_operator_and_index(json_data,
+                                                          operated_by[0])
         initializers.append(tmpl.substitute(
             # Use json.dumps for C-escaping strings.
             # Not perfect but close enough.
             description=json.dumps(log["description"]),
-            url=json.dumps("https://{0}/".format(log["url"])),
+            operator_index=operator_index,
+            operator_comment="operated by {0}".
+            # The comment must not contain "/".
+            format(operator["name"]).replace("/", "|"),
+            disqualified="false",
+            disqualified_comment="qualified log",
+            disqualification_timestamp=0,
+            disqualification_timestamp_comment="no disqualification time",
             # Maximum line width is 80.
             indented_log_key="\n".
             join(['    "{0}"'.format(l) for l in get_hex_lines(log_key, 74)]),
             log_key_len=len(log_key)))
     return initializers
 
 
+def get_log_operator_structs(json_data):
+    """ Return array of CTLogOperatorInfo initializers. """
+    tmpl = Template("  { $name, $id }")
+    initializers = []
+    for operator in json_data["operators"]:
+        initializers.append(tmpl.substitute(
+            name=json.dumps(operator["name"]),
+            id=operator["id"]))
+    return initializers
+
+
 def generate_cpp_header_file(json_data, out_file):
     """ Generate the C++ header file for the known logs. """
     filename = os.path.basename(out_file.name)
     include_guard = filename.replace(".", "_").replace("/", "_")
     log_info_initializers = get_log_info_structs(json_data)
+    operator_info_initializers = get_log_operator_structs(json_data)
     out_file.write(Template(OUTPUT_TEMPLATE).substitute(
         prog=os.path.basename(sys.argv[0]),
         include_guard=include_guard,
-        logs=",\n".join(log_info_initializers)))
+        logs=",\n".join(log_info_initializers),
+        operators=",\n".join(operator_info_initializers)))
 
 
 def run(args):
     """
     Load the input JSON file and generate the C++ header according to the
     command line arguments.
     """
     if args.file:
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -8558,17 +8558,17 @@
   },
   "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)"
+    "description": "Verification status of Signed Certificate Timestamps received (0=Decoding error, 1=Valid SCT, 2=SCT from unknown log, 3=Invalid SCT signature, 4=SCT timestamp is in the future, 5=Valid SCT from a disqualified log)"
   },
   "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)"
   },