Bug 1320566 - Certificate Transparency - implement CT Policy. r=keeler draft
authorSergei Chernov <sergei.cv@ndivi.com>
Mon, 09 Jan 2017 08:22:28 +0200
changeset 466694 5229bb0c183642f234d778a776a715c2723d2c29
parent 466606 fbdfcecf0c774d2221f11aed5a504d5591c774e0
child 543480 d4689b33c8f59e4f7e9bf83b47438bb9b1a652a1
push id42957
push usersergei.cv@ndivi.com
push dateThu, 26 Jan 2017 09:11:25 +0000
reviewerskeeler
bugs1320566
milestone54.0a1
Bug 1320566 - Certificate Transparency - implement CT Policy. r=keeler MozReview-Commit-ID: LcMdKcgBStG
browser/base/content/pageinfo/security.js
security/certverifier/CTDiversityPolicy.cpp
security/certverifier/CTDiversityPolicy.h
security/certverifier/CTLog.h
security/certverifier/CTLogVerifier.h
security/certverifier/CTPolicyEnforcer.cpp
security/certverifier/CTPolicyEnforcer.h
security/certverifier/CertVerifier.cpp
security/certverifier/CertVerifier.h
security/certverifier/moz.build
security/certverifier/tests/gtest/CTDiversityPolicyTest.cpp
security/certverifier/tests/gtest/CTPolicyEnforcerTest.cpp
security/certverifier/tests/gtest/moz.build
security/manager/locales/en-US/chrome/pippki/pippki.properties
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/nsISSLStatus.idl
security/manager/ssl/nsSSLStatus.cpp
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -87,34 +87,28 @@ var security = {
         case nsISSLStatus.TLS_VERSION_1_2:
           retval.version = "TLS 1.2"
           break;
         case nsISSLStatus.TLS_VERSION_1_3:
           retval.version = "TLS 1.3"
           break;
       }
 
-      // Select status text to display for Certificate Transparency.
+      // Select the status text to display for Certificate Transparency.
+      // Since we do not yet enforce the CT Policy on secure connections,
+      // we must not complain on policy discompliance (it might be viewed
+      // as a security issue by the user).
       switch (status.certificateTransparencyStatus) {
         case nsISSLStatus.CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE:
-          // CT compliance checks were not performed,
-          // do not display any status text.
+        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS:
+        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS:
           retval.certificateTransparency = null;
           break;
-        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_NONE:
-          retval.certificateTransparency = "None";
-          break;
-        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_OK:
-          retval.certificateTransparency = "OK";
-          break;
-        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG:
-          retval.certificateTransparency = "UnknownLog";
-          break;
-        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_INVALID:
-          retval.certificateTransparency = "Invalid";
+        case nsISSLStatus.CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT:
+          retval.certificateTransparency = "Compliant";
           break;
       }
 
       return retval;
     }
     return {
       hostName,
       cAName : "",
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTDiversityPolicy.cpp
@@ -0,0 +1,44 @@
+/* -*- 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 "CTDiversityPolicy.h"
+
+namespace mozilla { namespace ct {
+
+typedef mozilla::pkix::Result Result;
+
+Result
+GetCTLogOperatorsFromVerifiedSCTList(const VerifiedSCTList& list,
+                                     CTLogOperatorList& operators)
+{
+  operators.clear();
+  for (const VerifiedSCT& verifiedSct : list) {
+    CTLogOperatorId sctLogOperatorId = verifiedSct.logOperatorId;
+    bool alreadyAdded = false;
+    for (CTLogOperatorId id : operators) {
+      if (id == sctLogOperatorId) {
+        alreadyAdded = true;
+        break;
+      }
+    }
+    if (!alreadyAdded) {
+      if (!operators.append(sctLogOperatorId)) {
+        return Result::FATAL_ERROR_NO_MEMORY;
+      }
+    }
+  }
+  return Success;
+}
+
+Result
+CTDiversityPolicy::GetDependentOperators(const UniqueCERTCertList& builtChain,
+                                         const CTLogOperatorList& operators,
+                                         CTLogOperatorList& dependentOperators)
+{
+  return Success;
+}
+
+} } // namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTDiversityPolicy.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 CTDiversityPolicy_h
+#define CTDiversityPolicy_h
+
+#include "CTLog.h"
+#include "CTVerifyResult.h"
+#include "mozilla/Vector.h"
+#include "pkix/Result.h"
+#include "ScopedNSSTypes.h"
+
+namespace mozilla { namespace ct {
+
+// Retuns the list of unique CT log operator IDs appearing in the provided
+// list of verified SCTs.
+pkix::Result GetCTLogOperatorsFromVerifiedSCTList(const VerifiedSCTList& list,
+                                                  CTLogOperatorList& operators);
+
+// Helper class used by CTPolicyEnforcer to check the CT log operators
+// diversity requirements of the CT Policy.
+// See CTPolicyEnforcer.h for more details.
+class CTDiversityPolicy
+{
+public:
+  // Given a certificate chain and a set of CT log operators,
+  // returns the subset of log operators that are dependent on the CA
+  // issuing the certificate (as defined by the CT Policy).
+  //
+  // NOTE: TBD, PENDING FINALIZATION OF MOZILLA CT POLICY.
+  pkix::Result GetDependentOperators(const UniqueCERTCertList& builtChain,
+                                     const CTLogOperatorList& operators,
+                                     CTLogOperatorList& dependentOperators);
+};
+
+} } // namespace mozilla::ct
+
+#endif // CTDiversityPolicy_h
--- a/security/certverifier/CTLog.h
+++ b/security/certverifier/CTLog.h
@@ -4,24 +4,28 @@
  * 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 CTLog_h
 #define CTLog_h
 
 #include <stdint.h>
 
+#include "mozilla/Vector.h"
+
 namespace mozilla { namespace ct {
 
 // 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, so you can use special
 // values (such as -1) to indicate a "null" or unknown log ID.
 typedef int16_t CTLogOperatorId;
 
+typedef Vector<CTLogOperatorId, 8> CTLogOperatorList;
+
 // Current status of a CT log in regard to its inclusion in the
 // Known Logs List such as https://www.certificate-transparency.org/known-logs
 enum class CTLogStatus
 {
   // Status unknown or unavailable.
   Unknown,
   // Included in the list of known logs.
   Included,
--- a/security/certverifier/CTLogVerifier.h
+++ b/security/certverifier/CTLogVerifier.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef CTLogVerifier_h
 #define CTLogVerifier_h
 
 #include "CTLog.h"
 #include "pkix/Input.h"
 #include "pkix/pkix.h"
+#include "pkix/Result.h"
 #include "SignedCertificateTimestamp.h"
 #include "SignedTreeHead.h"
 
 namespace mozilla { namespace ct {
 
 // Verifies Signed Certificate Timestamps (SCTs) provided by a specific log
 // using the public key of that log. Assumes the SCT being verified
 // matches the log by log key ID and signature parameters (an error is returned
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTPolicyEnforcer.cpp
@@ -0,0 +1,357 @@
+/* -*- 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 "CTPolicyEnforcer.h"
+
+#include <algorithm>
+#include <stdint.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Vector.h"
+
+extern mozilla::LazyLogModule gCertVerifierLog;
+
+namespace mozilla { namespace ct {
+
+using namespace mozilla::pkix;
+
+// Returns the number of embedded SCTs required to be present on a
+// certificate for Qualification Case #2 (embedded SCTs).
+static size_t
+GetRequiredEmbeddedSctsCount(size_t certLifetimeInFullCalendarMonths)
+{
+  // "there are Embedded SCTs from AT LEAST N+1 once or currently qualified
+  // logs, where N is the lifetime of the certificate in years (normally
+  // rounding up, but rounding down when up to 3 months over), and must be
+  // at least 1"
+  return 1 + (certLifetimeInFullCalendarMonths + 9) / 12;
+}
+
+// Whether a valid embedded SCT is present in the list.
+static bool
+HasValidEmbeddedSct(const VerifiedSCTList& verifiedScts)
+{
+  for (const VerifiedSCT& verifiedSct : verifiedScts) {
+    if (verifiedSct.status == VerifiedSCT::Status::Valid &&
+        verifiedSct.origin == VerifiedSCT::Origin::Embedded) {
+      return true;
+    }
+  }
+  return false;
+}
+
+// Whether a valid non-embedded SCT is present in the list.
+static bool
+HasValidNonEmbeddedSct(const VerifiedSCTList& verifiedScts)
+{
+  for (const VerifiedSCT& verifiedSct : verifiedScts) {
+    if (verifiedSct.status == VerifiedSCT::Status::Valid &&
+         (verifiedSct.origin == VerifiedSCT::Origin::TLSExtension ||
+          verifiedSct.origin == VerifiedSCT::Origin::OCSPResponse)) {
+      return true;
+    }
+  }
+  return false;
+}
+
+// Given a list of verified SCTs, counts the number of distinct CA-independent
+// log operators running the CT logs that issued the SCTs which satisfy
+// the provided boolean predicate.
+template <typename SelectFunc>
+static Result
+CountIndependentLogOperatorsForSelectedScts(const VerifiedSCTList& verifiedScts,
+  const CTLogOperatorList& dependentOperators,
+  size_t& count,
+  SelectFunc selected)
+{
+  CTLogOperatorList operatorIds;
+  for (const VerifiedSCT& verifiedSct : verifiedScts) {
+    CTLogOperatorId sctLogOperatorId = verifiedSct.logOperatorId;
+    // Check if |sctLogOperatorId| is CA-dependent.
+    bool isDependentOperator = false;
+    for (CTLogOperatorId dependentOperator : dependentOperators) {
+      if (sctLogOperatorId == dependentOperator) {
+        isDependentOperator = true;
+        break;
+      }
+    }
+    if (isDependentOperator || !selected(verifiedSct)) {
+      continue;
+    }
+    // Check if |sctLogOperatorId| is in |operatorIds|...
+    bool alreadyAdded = false;
+    for (CTLogOperatorId id : operatorIds) {
+      if (id == sctLogOperatorId) {
+        alreadyAdded = true;
+        break;
+      }
+    }
+    // ...and if not, add it.
+    if (!alreadyAdded) {
+      if (!operatorIds.append(sctLogOperatorId)) {
+        return Result::FATAL_ERROR_NO_MEMORY;
+      }
+    }
+  }
+  count = operatorIds.length();
+  return Success;
+}
+
+// Given a list of verified SCTs, counts the number of distinct CT logs
+// that issued the SCTs that satisfy the |selected| predicate.
+template <typename SelectFunc>
+static Result
+CountLogsForSelectedScts(const VerifiedSCTList& verifiedScts,
+                         size_t& count,
+                         SelectFunc selected)
+{
+  // Keep pointers to log ids (of type Buffer) from |verifiedScts| to save on
+  // memory allocations.
+  Vector<const Buffer*, 8> logIds;
+  for (const VerifiedSCT& verifiedSct : verifiedScts) {
+    if (!selected(verifiedSct)) {
+      continue;
+    }
+
+    const Buffer* sctLogId = &verifiedSct.sct.logId;
+    // Check if |sctLogId| points to data already in |logIds|...
+    bool alreadyAdded = false;
+    for (const Buffer* logId : logIds) {
+      if (*logId == *sctLogId) {
+        alreadyAdded = true;
+        break;
+      }
+    }
+    // ...and if not, add it.
+    if (!alreadyAdded) {
+      if (!logIds.append(sctLogId)) {
+        return Result::FATAL_ERROR_NO_MEMORY;
+      }
+    }
+  }
+  count = logIds.length();
+  return Success;
+}
+
+// Calculates the effective issuance time of connection's certificate using
+// the SCTs present on the connection (we can't rely on notBefore validity
+// field of the certificate since it can be backdated).
+// Used to determine whether to accept SCTs issued by past qualified logs.
+// The effective issuance time is defined as the earliest of all SCTs,
+// rather than the latest of embedded SCTs, in order to give CAs the benefit
+// of the doubt in the event a log is revoked in the midst of processing
+// a precertificate and issuing the certificate.
+// It is acceptable to ignore the origin of the SCTs because SCTs
+// delivered via OCSP/TLS extension will cover the full certificate,
+// which necessarily will exist only after the precertificate
+// has been logged and the actual certificate issued.
+static uint64_t
+GetEffectiveCertIssuanceTime(const VerifiedSCTList& verifiedScts)
+{
+  uint64_t result = UINT64_MAX;
+  for (const VerifiedSCT& verifiedSct : verifiedScts) {
+    if (verifiedSct.status == VerifiedSCT::Status::Valid) {
+      result = std::min(result, verifiedSct.sct.timestamp);
+    }
+  }
+  return result;
+}
+
+// Checks if the log that issued the given SCT is "once or currently qualified"
+// (i.e. was qualified at the time of the certificate issuance). In addition,
+// makes sure the SCT is before the disqualification.
+static bool
+LogWasQualifiedForSct(const VerifiedSCT& verifiedSct, uint64_t certIssuanceTime)
+{
+  if (verifiedSct.status == VerifiedSCT::Status::Valid) {
+    return true;
+  }
+  if (verifiedSct.status == VerifiedSCT::Status::ValidFromDisqualifiedLog) {
+    uint64_t logDisqualificationTime = verifiedSct.logDisqualificationTime;
+    return certIssuanceTime < logDisqualificationTime &&
+      verifiedSct.sct.timestamp < logDisqualificationTime;
+  }
+  return false;
+}
+
+// "A certificate is CT Qualified if it is presented with at least two SCTs
+// from once or currently qualified logs run by a minimum of two entities
+// independent of the CA and of each other."
+// By the grandfathering provision (not currently implemented), certificates
+// "are CT Qualified if they are presented with SCTs from once or
+// currently qualified logs run by a minimum of one entity independent
+// of the CA."
+static Result
+CheckOperatorDiversityCompliance(const VerifiedSCTList& verifiedScts,
+                                 uint64_t certIssuanceTime,
+                                 const CTLogOperatorList& dependentOperators,
+                                 bool& compliant)
+{
+  size_t independentOperatorsCount;
+  Result rv = CountIndependentLogOperatorsForSelectedScts(verifiedScts,
+    dependentOperators, independentOperatorsCount,
+    [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
+      return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
+    });
+  if (rv != Success) {
+    return rv;
+  }
+  // Having at least 2 operators implies we have at least 2 SCTs.
+  // For the grandfathering provision (1 operator) we will need to include
+  // an additional SCTs count check using
+  // rv = CountLogsForSelectedScts(verifiedScts, sctsCount,
+  //   [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
+  //     return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
+  // });
+  compliant = independentOperatorsCount >= 2;
+  return Success;
+}
+
+// Qualification Case #1 (non-embedded SCTs) - the following must hold:
+// a. An SCT from a log qualified at the time of check is presented via the
+// TLS extension OR is embedded within a stapled OCSP response;
+// AND
+// b. There are at least two SCTs from logs qualified at the time of check,
+// presented via any method.
+static Result
+CheckNonEmbeddedCompliance(const VerifiedSCTList& verifiedScts, bool& compliant)
+{
+  if (!HasValidNonEmbeddedSct(verifiedScts)) {
+    compliant = false;
+    return Success;
+  }
+
+  size_t validSctsCount;
+  Result rv = CountLogsForSelectedScts(verifiedScts, validSctsCount,
+    [](const VerifiedSCT& verifiedSct)->bool {
+      return verifiedSct.status == VerifiedSCT::Status::Valid;
+    });
+  if (rv != Success) {
+    return rv;
+  }
+
+  MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+          ("CT Policy non-embedded case status: validSCTs=%zu\n",
+           validSctsCount));
+  compliant = validSctsCount >= 2;
+  return Success;
+}
+
+// Qualification Case #2 (embedded SCTs) - the following must hold:
+// a. An Embedded SCT from a log qualified at the time of check is presented;
+// AND
+// b. There are Embedded SCTs from AT LEAST N + 1 once or currently qualified
+// logs, where N is the lifetime of the certificate in years (normally
+// rounding up, but rounding down when up to 3 months over), and must be
+// at least 1.
+static Result
+CheckEmbeddedCompliance(const VerifiedSCTList& verifiedScts,
+                        size_t certLifetimeInCalendarMonths,
+                        uint64_t certIssuanceTime,
+                        bool& compliant)
+{
+  if (!HasValidEmbeddedSct(verifiedScts)) {
+    compliant = false;
+    return Success;
+  }
+
+  // Count the compliant embedded SCTs. Only a single SCT from each log
+  // is accepted. Note that a given log might return several different SCTs
+  // for the same precertificate (it is permitted, but advised against).
+  size_t embeddedSctsCount;
+  Result rv = CountLogsForSelectedScts(verifiedScts, embeddedSctsCount,
+    [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
+      return verifiedSct.origin == VerifiedSCT::Origin::Embedded &&
+        LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
+  });
+  if (rv != Success) {
+    return rv;
+  }
+
+  size_t requiredSctsCount =
+    GetRequiredEmbeddedSctsCount(certLifetimeInCalendarMonths);
+
+  MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+          ("CT Policy embedded case status: "
+           "requiredSCTs=%zu embeddedSCTs=%zu\n",
+           requiredSctsCount, embeddedSctsCount));
+
+  compliant = embeddedSctsCount >= requiredSctsCount;
+  return Success;
+}
+
+Result
+CTPolicyEnforcer::CheckCompliance(const VerifiedSCTList& verifiedScts,
+                                  size_t certLifetimeInCalendarMonths,
+                                  const CTLogOperatorList& dependentOperators,
+                                  CTPolicyCompliance& compliance)
+{
+  uint64_t certIssuanceTime = GetEffectiveCertIssuanceTime(verifiedScts);
+
+  bool diversityOK;
+  Result rv = CheckOperatorDiversityCompliance(verifiedScts, certIssuanceTime,
+                                               dependentOperators,
+                                               diversityOK);
+  if (rv != Success) {
+    return rv;
+  }
+  if (diversityOK) {
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("CT Policy: diversity satisfied\n"));
+  }
+
+  bool nonEmbeddedCaseOK;
+  rv = CheckNonEmbeddedCompliance(verifiedScts, nonEmbeddedCaseOK);
+  if (rv != Success) {
+    return rv;
+  }
+  if (nonEmbeddedCaseOK) {
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("CT Policy: non-embedded case satisfied)\n"));
+  }
+
+  bool embeddedCaseOK;
+  rv = CheckEmbeddedCompliance(verifiedScts, certLifetimeInCalendarMonths,
+                               certIssuanceTime, embeddedCaseOK);
+  if (rv != Success) {
+    return rv;
+  }
+  if (embeddedCaseOK) {
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("CT Policy: embedded case satisfied\n"));
+  }
+
+  if (nonEmbeddedCaseOK || embeddedCaseOK) {
+    compliance = diversityOK ? CTPolicyCompliance::Compliant
+                             : CTPolicyCompliance::NotDiverseScts;
+  } else {
+    // Not enough SCTs are present to satisfy either case of the policy.
+    compliance = CTPolicyCompliance::NotEnoughScts;
+  }
+
+  switch (compliance) {
+    case CTPolicyCompliance::Compliant:
+      MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+                ("CT Policy compliance: Compliant\n"));
+      break;
+    case CTPolicyCompliance::NotEnoughScts:
+      MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+                ("CT Policy compliance: NotEnoughScts\n"));
+      break;
+    case CTPolicyCompliance::NotDiverseScts:
+      MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+                ("CT Policy compliance: NotDiverseScts\n"));
+      break;
+    case CTPolicyCompliance::Unknown:
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
+  }
+  return Success;
+}
+
+} } // namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/CTPolicyEnforcer.h
@@ -0,0 +1,65 @@
+/* -*- 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 CTPolicyEnforcer_h
+#define CTPolicyEnforcer_h
+
+#include "CTLog.h"
+#include "CTVerifyResult.h"
+#include "pkix/Result.h"
+
+namespace mozilla { namespace ct {
+
+// Information about the compliance of the TLS connection with the
+// Certificate Transparency policy.
+enum class CTPolicyCompliance
+{
+  // Compliance not checked or not applicable.
+  Unknown,
+  // The connection complied with the certificate policy
+  // by including SCTs that satisfy the policy.
+  Compliant,
+  // The connection did not have enough valid SCTs to comply.
+  NotEnoughScts,
+  // The connection had enough valid SCTs, but the diversity requirement
+  // was not met (the number of CT log operators independent of the CA
+  // and of each other is too low).
+  NotDiverseScts,
+};
+
+// Checks whether a TLS connection complies with the current CT policy.
+// The implemented policy is based on the Mozilla CT Policy draft 0.1.0
+// (see https://docs.google.com/document/d/
+// 1rnqYYwscAx8WhS-MCdTiNzYQus9e37HuVyafQvEeNro/edit).
+//
+// NOTE: CT DIVERSITY REQUIREMENT IS TBD, PENDING FINALIZATION
+// OF MOZILLA CT POLICY. Specifically:
+// 1. CT log operators being CA-dependent is not currently taken into account
+// (see CTDiversityPolicy.h).
+// 2. The grandfathering provision of the operator diversity requirement
+// is not implemented (see "CT Qualified" section of the policy and
+// CheckOperatorDiversityCompliance in CTPolicyEnforcer.cpp).
+class CTPolicyEnforcer
+{
+public:
+  // |verifiedSct| - SCTs present on the connection along with their
+  // verification status.
+  // |certLifetimeInCalendarMonths| - certificate lifetime in full calendar
+  // months (i.e. rounded down), based on the notBefore/notAfter fields.
+  // |dependentOperators| - which CT log operators are dependent on the CA
+  // that issued the certificate. SCTs issued by logs associated with such
+  // operators are treated differenly when evaluating the policy.
+  // See CTDiversityPolicy class.
+  // |compliance| - the result of the compliance check.
+  pkix::Result CheckCompliance(const VerifiedSCTList& verifiedScts,
+                               size_t certLifetimeInCalendarMonths,
+                               const CTLogOperatorList& dependentOperators,
+                               CTPolicyCompliance& compliance);
+};
+
+} } // namespace mozilla::ct
+
+#endif // CTPolicyEnforcer_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -3,16 +3,17 @@
 /* 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 "CertVerifier.h"
 
 #include <stdint.h>
 
+#include "CTDiversityPolicy.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"
@@ -25,22 +26,66 @@
 #include "secmod.h"
 
 using namespace mozilla::ct;
 using namespace mozilla::pkix;
 using namespace mozilla::psm;
 
 mozilla::LazyLogModule gCertVerifierLog("certverifier");
 
+// Returns the certificate validity period in calendar months (rounded down).
+// "extern" to allow unit tests in CTPolicyEnforcerTest.cpp.
+extern mozilla::pkix::Result
+GetCertLifetimeInFullMonths(PRTime certNotBefore,
+                            PRTime certNotAfter,
+                            size_t& months)
+{
+  if (certNotBefore >= certNotAfter) {
+    MOZ_ASSERT_UNREACHABLE("Expected notBefore < notAfter");
+    return mozilla::pkix::Result::FATAL_ERROR_INVALID_ARGS;
+  }
+
+  PRExplodedTime explodedNotBefore;
+  PRExplodedTime explodedNotAfter;
+
+  PR_ExplodeTime(certNotBefore, PR_LocalTimeParameters, &explodedNotBefore);
+  PR_ExplodeTime(certNotAfter, PR_LocalTimeParameters, &explodedNotAfter);
+
+  PRInt32 signedMonths =
+    (explodedNotAfter.tm_year - explodedNotBefore.tm_year) * 12 +
+    (explodedNotAfter.tm_month - explodedNotBefore.tm_month);
+  if (explodedNotAfter.tm_mday < explodedNotBefore.tm_mday) {
+    --signedMonths;
+  }
+
+  // Can't use `mozilla::AssertedCast<size_t>(signedMonths)` below
+  // since it currently generates a warning on Win x64 debug.
+  if (signedMonths < 0) {
+    MOZ_ASSERT_UNREACHABLE("Expected explodedNotBefore < explodedNotAfter");
+    return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  months = static_cast<size_t>(signedMonths);
+
+  return Success;
+}
+
 namespace mozilla { namespace psm {
 
 const CertVerifier::Flags CertVerifier::FLAG_LOCAL_ONLY = 1;
 const CertVerifier::Flags CertVerifier::FLAG_MUST_BE_EV = 2;
 const CertVerifier::Flags CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST = 4;
 
+void
+CertificateTransparencyInfo::Reset()
+{
+  enabled = false;
+  verifyResult.Reset();
+  policyCompliance = CTPolicyCompliance::Unknown;
+}
+
 CertVerifier::CertVerifier(OcspDownloadConfig odc,
                            OcspStrictConfig osc,
                            OcspGetConfig ogc,
                            uint32_t certShortLifetimeInDays,
                            PinningMode pinningMode,
                            SHA1Mode sha1Mode,
                            BRNameMatchingPolicy::Mode nameMatchingMode,
                            NetscapeStepUpPolicy netscapeStepUpPolicy,
@@ -174,20 +219,23 @@ CertVerifier::LoadKnownCTLogs()
     }
 
     rv = mCTVerifier->AddLog(Move(logVerifier));
     if (rv != Success) {
       MOZ_ASSERT_UNREACHABLE("Failed activating a known CT Log");
       continue;
     }
   }
+  // TBD: Initialize mCTDiversityPolicy with the CA dependency map
+  // of the known CT logs operators.
+  mCTDiversityPolicy = MakeUnique<CTDiversityPolicy>();
 }
 
 Result
-CertVerifier::VerifySignedCertificateTimestamps(
+CertVerifier::VerifyCertificateTransparencyPolicy(
   NSSCertDBTrustDomain& trustDomain, const UniqueCERTCertList& builtChain,
   Input sctsFromTLS, Time time,
   /*optional out*/ CertificateTransparencyInfo* ctInfo)
 {
   if (ctInfo) {
     ctInfo->Reset();
   }
   if (mCTMode == CertificateTransparencyMode::Disabled) {
@@ -196,40 +244,33 @@ CertVerifier::VerifySignedCertificateTim
   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.
@@ -237,16 +278,22 @@ CertVerifier::VerifySignedCertificateTim
   }
 
   CERTCertificate* endEntity = endEntityNode->cert;
   CERTCertificate* issuer = issuerNode->cert;
   if (!endEntity || !issuer) {
     return Result::FATAL_ERROR_INVALID_ARGS;
   }
 
+  if (endEntity->subjectName) {
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("Verifying CT Policy compliance of subject %s\n",
+             endEntity->subjectName));
+  }
+
   Input endEntityDER;
   Result rv = endEntityDER.Init(endEntity->derCert.data,
                                 endEntity->derCert.len);
   if (rv != Success) {
     return rv;
   }
 
   Input issuerPublicKeyDER;
@@ -299,19 +346,54 @@ CertVerifier::VerifySignedCertificateTim
              "valid=%zu unknownLog=%zu disqualifiedLog=%zu "
              "invalidSignature=%zu invalidTimestamp=%zu "
              "decodingErrors=%zu\n",
              validCount, unknownLogCount, disqualifiedLogCount,
              invalidSignatureCount, invalidTimestampCount,
              result.decodingErrors));
   }
 
+  PRTime notBefore;
+  PRTime notAfter;
+  if (CERT_GetCertTimes(endEntity, &notBefore, &notAfter) != SECSuccess) {
+    return Result::FATAL_ERROR_LIBRARY_FAILURE;
+  }
+  size_t lifetimeInMonths;
+  rv = GetCertLifetimeInFullMonths(notBefore, notAfter, lifetimeInMonths);
+  if (rv != Success) {
+    return rv;
+  }
+
+  CTLogOperatorList allOperators;
+  rv = GetCTLogOperatorsFromVerifiedSCTList(result.verifiedScts,
+                                            allOperators);
+  if (rv != Success) {
+    return rv;
+  }
+
+  CTLogOperatorList dependentOperators;
+  rv = mCTDiversityPolicy->GetDependentOperators(builtChain, allOperators,
+                                                 dependentOperators);
+  if (rv != Success) {
+    return rv;
+  }
+
+  CTPolicyEnforcer ctPolicyEnforcer;
+  CTPolicyCompliance ctPolicyCompliance;
+  rv = ctPolicyEnforcer.CheckCompliance(result.verifiedScts, lifetimeInMonths,
+                                        dependentOperators, ctPolicyCompliance);
+  if (rv != Success) {
+    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
+            ("CT policy check failed with fatal error %i\n", rv));
+    return rv;
+  }
+
   if (ctInfo) {
-    ctInfo->processedSCTs = true;
     ctInfo->verifyResult = Move(result);
+    ctInfo->policyCompliance = ctPolicyCompliance;
   }
   return Success;
 }
 
 bool
 CertVerifier::SHA1ModeMoreRestrictiveThanGivenMode(SHA1Mode mode)
 {
   switch (mSHA1Mode) {
@@ -533,19 +615,19 @@ 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);
+          rv = VerifyCertificateTransparencyPolicy(trustDomain, builtChain,
+                                                   sctsFromTLSInput, time,
+                                                   ctInfo);
           if (rv != Success) {
             break;
           }
         }
       }
       if (rv == Success) {
         break;
       }
@@ -620,19 +702,19 @@ CertVerifier::VerifyCert(CERTCertificate
           }
           if (rv == Success) {
             if (keySizeStatus) {
               *keySizeStatus = keySizeStatuses[i];
             }
             if (sha1ModeResult) {
               *sha1ModeResult = sha1ModeResults[j];
             }
-            rv = VerifySignedCertificateTimestamps(trustDomain, builtChain,
-                                                   sctsFromTLSInput, time,
-                                                   ctInfo);
+            rv = VerifyCertificateTransparencyPolicy(trustDomain, builtChain,
+                                                     sctsFromTLSInput, time,
+                                                     ctInfo);
             if (rv != Success) {
               break;
             }
           }
         }
       }
 
       if (rv == Success) {
--- a/security/certverifier/CertVerifier.h
+++ b/security/certverifier/CertVerifier.h
@@ -3,16 +3,17 @@
 /* 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 "CTPolicyEnforcer.h"
 #include "CTVerifyResult.h"
 #include "OCSPCache.h"
 #include "ScopedNSSTypes.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtr.h"
 #include "pkix/pkixtypes.h"
 
 #if defined(_MSC_VER)
@@ -23,20 +24,21 @@
 #endif /* defined(_MSC_VER) */
 #include "mozilla/BasePrincipal.h"
 #if defined(_MSC_VER)
 #pragma warning(pop) /* popping the pragma in this file */
 #endif /* defined(_MSC_VER) */
 
 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.
+// Including the headers of the classes below would bring along all of their
+// dependent headers and force us to export them in moz.build.
+// Just forward-declare the classes here instead.
 class MultiLogCTVerifier;
+class CTDiversityPolicy;
 
 } } // namespace mozilla::ct
 
 namespace mozilla { namespace psm {
 
 typedef mozilla::pkix::Result Result;
 
 // These values correspond to the CERT_CHAIN_KEY_SIZE_STATUS telemetry.
@@ -73,22 +75,22 @@ public:
   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;
+  // Connection compliance to the CT Policy.
+  mozilla::ct::CTPolicyCompliance policyCompliance;
 
-  void Reset() { enabled = false; processedSCTs = false; verifyResult.Reset(); }
+  void Reset();
 };
 
 class NSSCertDBTrustDomain;
 
 class CertVerifier
 {
 public:
   typedef unsigned int Flags;
@@ -197,22 +199,23 @@ public:
   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.
+  // We only have a forward declarations of these classes (see above)
+  // so we must allocate dynamically.
   UniquePtr<mozilla::ct::MultiLogCTVerifier> mCTVerifier;
+  UniquePtr<mozilla::ct::CTDiversityPolicy> mCTDiversityPolicy;
 
   void LoadKnownCTLogs();
-  mozilla::pkix::Result VerifySignedCertificateTimestamps(
+  mozilla::pkix::Result VerifyCertificateTransparencyPolicy(
                      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
--- a/security/certverifier/moz.build
+++ b/security/certverifier/moz.build
@@ -3,27 +3,30 @@
 # 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',
     'CTLog.h',
+    'CTPolicyEnforcer.h',
     'CTVerifyResult.h',
     'OCSPCache.h',
     'SignedCertificateTimestamp.h',
     'SignedTreeHead.h',
 ]
 
 UNIFIED_SOURCES += [
     'BRNameMatchingPolicy.cpp',
     'CertVerifier.cpp',
+    'CTDiversityPolicy.cpp',
     'CTLogVerifier.cpp',
     'CTObjectsExtractor.cpp',
+    'CTPolicyEnforcer.cpp',
     'CTSerialization.cpp',
     'CTVerifyResult.cpp',
     'MultiLogCTVerifier.cpp',
     'NSSCertDBTrustDomain.cpp',
     'OCSPCache.cpp',
     'OCSPRequestor.cpp',
     'OCSPVerificationTrustDomain.cpp',
     'SignedCertificateTimestamp.cpp',
new file mode 100644
--- /dev/null
+++ b/security/certverifier/tests/gtest/CTDiversityPolicyTest.cpp
@@ -0,0 +1,13 @@
+/* -*- 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 "CTDiversityPolicy.h"
+
+namespace mozilla { namespace ct {
+
+// TBD, PENDING FINALIZATION OF MOZILLA CT POLICY.
+
+} } // namespace mozilla::ct
new file mode 100644
--- /dev/null
+++ b/security/certverifier/tests/gtest/CTPolicyEnforcerTest.cpp
@@ -0,0 +1,427 @@
+/* -*- 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 "CTPolicyEnforcer.h"
+
+#include <algorithm>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "CTVerifyResult.h"
+#include "gtest/gtest.h"
+#include "hasht.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "prtime.h"
+#include "SignedCertificateTimestamp.h"
+
+// Implemented in CertVerifier.cpp.
+extern mozilla::pkix::Result
+GetCertLifetimeInFullMonths(PRTime certNotBefore,
+                            PRTime certNotAfter,
+                            size_t& months);
+
+namespace mozilla { namespace ct {
+
+using namespace mozilla::pkix;
+
+class CTPolicyEnforcerTest : public ::testing::Test
+{
+public:
+  void SetUp() override
+  {
+    MOZ_ALWAYS_TRUE(OPERATORS_1_AND_2.append(OPERATOR_1));
+    MOZ_ALWAYS_TRUE(OPERATORS_1_AND_2.append(OPERATOR_2));
+  }
+
+  void GetLogId(Buffer& logId, size_t logNo)
+  {
+    ASSERT_TRUE(logId.resize(SHA256_LENGTH));
+    std::fill(logId.begin(), logId.end(), 0);
+    // Just raw-copy |logId| into the output buffer.
+    MOZ_ASSERT(sizeof(logNo) <= logId.length());
+    memcpy(logId.begin(), &logNo, sizeof(logNo));
+  }
+
+  void AddSct(VerifiedSCTList& verifiedScts,
+              size_t logNo,
+              CTLogOperatorId operatorId,
+              VerifiedSCT::Origin origin,
+              uint64_t timestamp,
+              VerifiedSCT::Status status = VerifiedSCT::Status::Valid)
+  {
+    VerifiedSCT verifiedSct;
+    verifiedSct.status = status;
+    verifiedSct.origin = origin;
+    verifiedSct.logOperatorId = operatorId;
+    verifiedSct.logDisqualificationTime =
+      status == VerifiedSCT::Status::ValidFromDisqualifiedLog ?
+      DISQUALIFIED_AT : UINT64_MAX;
+    verifiedSct.sct.version = SignedCertificateTimestamp::Version::V1;
+    verifiedSct.sct.timestamp = timestamp;
+    Buffer logId;
+    GetLogId(logId, logNo);
+    verifiedSct.sct.logId = Move(logId);
+    ASSERT_TRUE(verifiedScts.append(Move(verifiedSct)));
+  }
+
+  void AddMultipleScts(VerifiedSCTList& verifiedScts,
+                       size_t logsCount,
+                       uint8_t operatorsCount,
+                       VerifiedSCT::Origin origin,
+                       uint64_t timestamp,
+                       VerifiedSCT::Status status = VerifiedSCT::Status::Valid)
+  {
+    for (size_t logNo = 0; logNo < logsCount; logNo++) {
+      CTLogOperatorId operatorId = logNo % operatorsCount;
+      AddSct(verifiedScts, logNo, operatorId, origin, timestamp, status);
+    }
+  }
+
+  void CheckCompliance(const VerifiedSCTList& verifiedSct,
+                       size_t certLifetimeInCalendarMonths,
+                       const CTLogOperatorList& dependentLogOperators,
+                       CTPolicyCompliance expectedCompliance)
+  {
+    CTPolicyCompliance compliance;
+    ASSERT_EQ(Success,
+              mPolicyEnforcer.CheckCompliance(verifiedSct,
+                                              certLifetimeInCalendarMonths,
+                                              dependentLogOperators,
+                                              compliance));
+    EXPECT_EQ(expectedCompliance, compliance);
+  }
+
+protected:
+  CTPolicyEnforcer mPolicyEnforcer;
+
+  const size_t LOG_1 = 1;
+  const size_t LOG_2 = 2;
+  const size_t LOG_3 = 3;
+  const size_t LOG_4 = 4;
+  const size_t LOG_5 = 5;
+
+  const CTLogOperatorId OPERATOR_1 = 1;
+  const CTLogOperatorId OPERATOR_2 = 2;
+  const CTLogOperatorId OPERATOR_3 = 3;
+
+  CTLogOperatorList NO_OPERATORS;
+  CTLogOperatorList OPERATORS_1_AND_2;
+
+  const VerifiedSCT::Origin ORIGIN_EMBEDDED = VerifiedSCT::Origin::Embedded;
+  const VerifiedSCT::Origin ORIGIN_TLS = VerifiedSCT::Origin::TLSExtension;
+  const VerifiedSCT::Origin ORIGIN_OCSP = VerifiedSCT::Origin::OCSPResponse;
+
+  // 4 years of cert lifetime requires 5 SCTs for the embedded case.
+  const size_t DEFAULT_MONTHS = 4 * 12L;
+
+  // Date.parse("2015-08-15T00:00:00Z")
+  const uint64_t TIMESTAMP_1 = 1439596800000L;
+
+  // Date.parse("2016-04-15T00:00:00Z")
+  const uint64_t DISQUALIFIED_AT = 1460678400000L;
+
+  // Date.parse("2016-04-01T00:00:00Z")
+  const uint64_t BEFORE_DISQUALIFIED = 1459468800000L;
+
+  // Date.parse("2016-04-16T00:00:00Z")
+  const uint64_t AFTER_DISQUALIFIED = 1460764800000L;
+};
+
+TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithNonEmbeddedSCTs)
+{
+  VerifiedSCTList scts;
+
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
+
+  CheckCompliance(scts,  DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest, DoesNotConformNotEnoughDiverseNonEmbeddedSCTs)
+{
+  VerifiedSCTList scts;
+
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
+
+  CheckCompliance(scts, DEFAULT_MONTHS, OPERATORS_1_AND_2,
+                  CTPolicyCompliance::NotDiverseScts);
+}
+
+TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithEmbeddedSCTs)
+{
+  VerifiedSCTList scts;
+
+  // 5 embedded SCTs required for DEFAULT_MONTHS.
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+  CheckCompliance(scts,  DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest, DoesNotConformNotEnoughDiverseEmbeddedSCTs)
+{
+  VerifiedSCTList scts;
+
+  // 5 embedded SCTs required for DEFAULT_MONTHS.
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+  CheckCompliance(scts,  DEFAULT_MONTHS, OPERATORS_1_AND_2,
+                  CTPolicyCompliance::NotDiverseScts);
+}
+
+TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledNonEmbeddedSCTs)
+{
+  VerifiedSCTList scts;
+
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_OCSP, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, TIMESTAMP_1);
+
+  CheckCompliance(scts,  DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest, ConformsToCTPolicyWithPooledEmbeddedSCTs)
+{
+  VerifiedSCTList scts;
+
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_OCSP, TIMESTAMP_1);
+
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughSCTs)
+{
+  VerifiedSCTList scts;
+
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest, DoesNotConformToCTPolicyNotEnoughFreshSCTs)
+{
+  VerifiedSCTList scts;
+
+  // The results should be the same before and after disqualification,
+  // regardless of the delivery method.
+
+  // SCT from before disqualification.
+  scts.clear();
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, BEFORE_DISQUALIFIED,
+         VerifiedSCT::Status::ValidFromDisqualifiedLog);
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::NotEnoughScts);
+  // SCT from after disqualification.
+  scts.clear();
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_TLS, AFTER_DISQUALIFIED,
+         VerifiedSCT::Status::ValidFromDisqualifiedLog);
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::NotEnoughScts);
+
+  // Embedded SCT from before disqualification.
+  scts.clear();
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_DISQUALIFIED,
+         VerifiedSCT::Status::ValidFromDisqualifiedLog);
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::NotEnoughScts);
+
+  // Embedded SCT from after disqualification.
+  scts.clear();
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_TLS, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
+         VerifiedSCT::Status::ValidFromDisqualifiedLog);
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+       ConformsWithDisqualifiedLogBeforeDisqualificationDate)
+{
+  VerifiedSCTList scts;
+
+  // 5 embedded SCTs required for DEFAULT_MONTHS.
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, BEFORE_DISQUALIFIED,
+         VerifiedSCT::Status::ValidFromDisqualifiedLog);
+
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::Compliant);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+       DoesNotConformWithDisqualifiedLogAfterDisqualificationDate)
+{
+  VerifiedSCTList scts;
+
+  // 5 embedded SCTs required for DEFAULT_MONTHS.
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
+         VerifiedSCT::Status::ValidFromDisqualifiedLog);
+
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+       DoesNotConformWithIssuanceDateAfterDisqualificationDate)
+{
+  VerifiedSCTList scts;
+
+  // 5 embedded SCTs required for DEFAULT_MONTHS.
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED,
+         VerifiedSCT::Status::ValidFromDisqualifiedLog);
+  AddSct(scts, LOG_2, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
+  AddSct(scts, LOG_3, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
+  AddSct(scts, LOG_4, OPERATOR_1, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
+  AddSct(scts, LOG_5, OPERATOR_2, ORIGIN_EMBEDDED, AFTER_DISQUALIFIED);
+
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+       DoesNotConformToCTPolicyNotEnoughUniqueEmbeddedLogs)
+{
+  VerifiedSCTList scts;
+
+  // Operator #1
+  AddSct(scts, LOG_1, OPERATOR_1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  // Operator #2, different logs
+  AddSct(scts, LOG_2, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_3, OPERATOR_2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  // Operator #3, same log
+  AddSct(scts, LOG_4, OPERATOR_3, ORIGIN_EMBEDDED, TIMESTAMP_1);
+  AddSct(scts, LOG_4, OPERATOR_3, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+  // 5 embedded SCTs required. However, only 4 are from distinct logs.
+  CheckCompliance(scts, DEFAULT_MONTHS, NO_OPERATORS,
+                  CTPolicyCompliance::NotEnoughScts);
+}
+
+TEST_F(CTPolicyEnforcerTest,
+       ConformsToPolicyExactNumberOfSCTsForValidityPeriod)
+{
+  // Test multiple validity periods.
+  const struct TestData {
+    size_t certLifetimeInCalendarMonths;
+    size_t sctsRequired;
+  } kTestData[] = {
+    { 3, 2 },
+    { 12 + 2, 2 },
+    { 12 + 3, 3 },
+    { 2*12 + 2, 3 },
+    { 2*12 + 3, 4 },
+    { 3*12 + 2, 4 },
+    { 3*12 + 4, 5 }
+  };
+
+  for (size_t i = 0; i < ArrayLength(kTestData); ++i) {
+    SCOPED_TRACE(i);
+
+    size_t months = kTestData[i].certLifetimeInCalendarMonths;
+    size_t sctsRequired = kTestData[i].sctsRequired;
+
+    // Less SCTs than required is not enough.
+    for (size_t sctsAvailable = 0; sctsAvailable < sctsRequired;
+         ++sctsAvailable) {
+      VerifiedSCTList scts;
+      AddMultipleScts(scts, sctsAvailable, 1, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+      CTPolicyCompliance compliance;
+      ASSERT_EQ(Success,
+                mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS,
+                                                compliance))
+        << "i=" << i
+        << " sctsRequired=" << sctsRequired
+        << " sctsAvailable=" << sctsAvailable;
+      EXPECT_EQ(CTPolicyCompliance::NotEnoughScts, compliance)
+        << "i=" << i
+        << " sctsRequired=" << sctsRequired
+        << " sctsAvailable=" << sctsAvailable;
+    }
+
+    // Add exactly the required number of SCTs (from 2 operators).
+    VerifiedSCTList scts;
+    AddMultipleScts(scts, sctsRequired, 2, ORIGIN_EMBEDDED, TIMESTAMP_1);
+
+    CTPolicyCompliance compliance;
+    ASSERT_EQ(Success,
+              mPolicyEnforcer.CheckCompliance(scts, months, NO_OPERATORS,
+                                              compliance))
+      << "i=" << i;
+    EXPECT_EQ(CTPolicyCompliance::Compliant, compliance)
+      << "i=" << i;
+  }
+}
+
+TEST_F(CTPolicyEnforcerTest, TestEdgeCasesOfGetCertLifetimeInFullMonths)
+{
+  const struct TestData {
+    PRTime notBefore;
+    PRTime notAfter;
+    size_t expectedMonths;
+  } kTestData[] = {
+    { // 1 second less than 1 month
+      1424863500000000, // Date.parse("2015-02-25T11:25:00Z") * 1000
+      1427196299000000, // Date.parse("2015-03-24T11:24:59Z") * 1000
+      0 },
+    { // exactly 1 month
+      1424863500000000, // Date.parse("2015-02-25T11:25:00Z") * 1000
+      1427282700000000, // Date.parse("2015-03-25T11:25:00Z") * 1000
+      1 },
+    { // 1 year, 1 month
+      1427282700000000, // Date.parse("2015-03-25T11:25:00Z") * 1000
+      1461583500000000, // Date.parse("2016-04-25T11:25:00Z") * 1000
+      13 },
+    { // 1 year, 1 month, first day of notBefore month, last of notAfter
+      1425209100000000, // Date.parse("2015-03-01T11:25:00Z") * 1000
+      1462015500000000, // Date.parse("2016-04-30T11:25:00Z") * 1000
+      13 },
+    { // 1 year, adjacent months, last day of notBefore month, first of notAfter
+      1427801100000000, // Date.parse("2015-03-31T11:25:00Z") * 1000
+      1459509900000000, // Date.parse("2016-04-01T11:25:00Z") * 1000
+      12 }
+  };
+
+  for (size_t i = 0; i < ArrayLength(kTestData); ++i) {
+    SCOPED_TRACE(i);
+
+    size_t months;
+    ASSERT_EQ(Success,
+              GetCertLifetimeInFullMonths(kTestData[i].notBefore,
+                                          kTestData[i].notAfter,
+                                          months))
+              << "i=" << i;
+    EXPECT_EQ(kTestData[i].expectedMonths, months)
+              << "i=" << i;
+  }
+}
+
+} } // namespace mozilla::ct
--- a/security/certverifier/tests/gtest/moz.build
+++ b/security/certverifier/tests/gtest/moz.build
@@ -1,17 +1,19 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # 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/.
 
 SOURCES += [
+    'CTDiversityPolicyTest.cpp',
     'CTLogVerifierTest.cpp',
     'CTObjectsExtractorTest.cpp',
+    'CTPolicyEnforcerTest.cpp',
     'CTSerializationTest.cpp',
     'CTTestUtils.cpp',
     'MultiLogCTVerifierTest.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/security/certverifier',
     '/security/pkix/include',
--- a/security/manager/locales/en-US/chrome/pippki/pippki.properties
+++ b/security/manager/locales/en-US/chrome/pippki/pippki.properties
@@ -106,20 +106,17 @@ pageInfo_Privacy_None4=The page you are 
 # %3$S is protocol version like "SSL 3" or "TLS 1.2"
 pageInfo_EncryptionWithBitsAndProtocol=Connection Encrypted (%1$S, %2$S bit keys, %3$S)
 pageInfo_BrokenEncryption=Broken Encryption (%1$S, %2$S bit keys, %3$S)
 pageInfo_Privacy_Encrypted1=The page you are viewing was encrypted before being transmitted over the Internet.
 pageInfo_Privacy_Encrypted2=Encryption makes it difficult for unauthorized people to view information traveling between computers. It is therefore unlikely that anyone read this page as it traveled across the network.
 pageInfo_MixedContent=Connection Partially Encrypted
 pageInfo_MixedContent2=Parts of the page you are viewing were not encrypted before being transmitted over the Internet.
 pageInfo_WeakCipher=Your connection to this website uses weak encryption and is not private. Other people can view your information or modify the website’s behavior.
-pageInfo_CertificateTransparency_None=This website does not supply Certificate Transparency audit records.
-pageInfo_CertificateTransparency_OK=This website supplies publicly auditable Certificate Transparency records.
-pageInfo_CertificateTransparency_UnknownLog=This website claims to have Certificate Transparency audit records, but the records were issued by an unknown party and cannot be verified.
-pageInfo_CertificateTransparency_Invalid=This website supplies Certificate Transparency audit records, but the records failed verification.
+pageInfo_CertificateTransparency_Compliant=This website complies with the Certificate Transparency policy.
 
 # Cert Viewer
 # LOCALIZATION NOTE(certViewerTitle): Title used for the Certificate Viewer.
 # %1$S is a string representative of the certificate being viewed.
 certViewerTitle=Certificate Viewer: “%1$S”
 notPresent=<Not Part Of Certificate>
 
 # Token Manager
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -1273,36 +1273,30 @@ 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::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.verifiedScts.length());
-  // Note that sctsCount can be 0 in case we've received SCT binary data,
+  // Note that sctsCount can also 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,
--- a/security/manager/ssl/nsISSLStatus.idl
+++ b/security/manager/ssl/nsISSLStatus.idl
@@ -18,21 +18,20 @@ interface nsISSLStatus : nsISupports {
 
   const short SSL_VERSION_3   = 0;
   const short TLS_VERSION_1   = 1;
   const short TLS_VERSION_1_1 = 2;
   const short TLS_VERSION_1_2 = 3;
   const short TLS_VERSION_1_3 = 4;
   readonly attribute unsigned short protocolVersion;
 
-  const short CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE = 0;
-  const short CERTIFICATE_TRANSPARENCY_NONE           = 1;
-  const short CERTIFICATE_TRANSPARENCY_OK             = 2;
-  const short CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG    = 3;
-  const short CERTIFICATE_TRANSPARENCY_INVALID        = 4;
+  const short CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE          = 0;
+  const short CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT        = 5;
+  const short CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS  = 6;
+  const short CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS = 7;
   readonly attribute unsigned short certificateTransparencyStatus;
 
   readonly attribute boolean isDomainMismatch;
   readonly attribute boolean isNotValidAtThisTime;
 
   /* Note: To distinguish between
    *         "unstrusted because missing or untrusted issuer"
    *       and
--- a/security/manager/ssl/nsSSLStatus.cpp
+++ b/security/manager/ssl/nsSSLStatus.cpp
@@ -325,56 +325,36 @@ nsSSLStatus::SetServerCert(nsNSSCertific
   mIsEV = (aEVStatus == EVStatus::EV);
   mHasIsEVStatus = true;
 }
 
 void
 nsSSLStatus::SetCertificateTransparencyInfo(
   const mozilla::psm::CertificateTransparencyInfo& info)
 {
-  using mozilla::ct::VerifiedSCT;
+  using mozilla::ct::CTPolicyCompliance;
+
+  mCertificateTransparencyStatus =
+    nsISSLStatus::CERTIFICATE_TRANSPARENCY_NOT_APPLICABLE;
 
   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 hasValidSCTs = false;
-  bool hasUnknownLogSCTs = false;
-  bool hasInvalidSCTs = false;
-  for (const VerifiedSCT& verifiedSct : info.verifyResult.verifiedScts) {
-    switch (verifiedSct.status) {
-      case VerifiedSCT::Status::Valid:
-        hasValidSCTs = true;
-        break;
-      case VerifiedSCT::Status::UnknownLog:
-      case VerifiedSCT::Status::ValidFromDisqualifiedLog:
-        hasUnknownLogSCTs = true;
-        break;
-      case VerifiedSCT::Status::InvalidSignature:
-      case VerifiedSCT::Status::InvalidTimestamp:
-        hasInvalidSCTs = true;
-        break;
-      default:
-        MOZ_ASSERT_UNREACHABLE("Unexpected VerifiedSCT::Status type");
-    }
-  }
-
-  if (hasValidSCTs) {
-    mCertificateTransparencyStatus =
-      nsISSLStatus::CERTIFICATE_TRANSPARENCY_OK;
-  } else if (hasUnknownLogSCTs) {
-    mCertificateTransparencyStatus =
-      nsISSLStatus::CERTIFICATE_TRANSPARENCY_UNKNOWN_LOG;
-  } else if (hasInvalidSCTs) {
-    mCertificateTransparencyStatus =
-      nsISSLStatus::CERTIFICATE_TRANSPARENCY_INVALID;
+  switch (info.policyCompliance) {
+    case CTPolicyCompliance::Compliant:
+      mCertificateTransparencyStatus =
+        nsISSLStatus::CERTIFICATE_TRANSPARENCY_POLICY_COMPLIANT;
+      break;
+    case CTPolicyCompliance::NotEnoughScts:
+      mCertificateTransparencyStatus =
+        nsISSLStatus::CERTIFICATE_TRANSPARENCY_POLICY_NOT_ENOUGH_SCTS;
+      break;
+    case CTPolicyCompliance::NotDiverseScts:
+      mCertificateTransparencyStatus =
+        nsISSLStatus::CERTIFICATE_TRANSPARENCY_POLICY_NOT_DIVERSE_SCTS;
+      break;
+    case CTPolicyCompliance::Unknown:
+    default:
+      MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
   }
 }