/* -*- 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 "mozilla/Assertions.h" #include "mozpkix/Time.h" #include #include namespace mozilla { namespace ct { using namespace mozilla::pkix; // Returns the number of embedded SCTs required to be present in a certificate. // For certificates with a lifetime of less than or equal to 180 days, only 2 // embedded SCTs are required. Otherwise 3 are required. MOZ_RUNINIT const Duration ONE_HUNDRED_AND_EIGHTY_DAYS = Duration(180 * Time::ONE_DAY_IN_SECONDS); size_t GetRequiredEmbeddedSctsCount(Duration certLifetime) { // pkix::Duration doesn't define operator<=, hence phrasing this comparison // in an awkward way return ONE_HUNDRED_AND_EIGHTY_DAYS < certLifetime ? 3 : 2; } // 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. uint64_t GetEffectiveCertIssuanceTime(const VerifiedSCTList& verifiedScts) { uint64_t result = UINT64_MAX; for (const VerifiedSCT& verifiedSct : verifiedScts) { if (verifiedSct.logState == CTLogState::Admissible) { 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 retirement timestamp. bool LogWasQualifiedForSct(const VerifiedSCT& verifiedSct, uint64_t certIssuanceTime) { switch (verifiedSct.logState) { case CTLogState::Admissible: return true; case CTLogState::Retired: { uint64_t logRetirementTime = verifiedSct.logTimestamp; return certIssuanceTime < logRetirementTime && verifiedSct.sct.timestamp < logRetirementTime; } } MOZ_ASSERT_UNREACHABLE("verifiedSct.logState must be Admissible or Retired"); return false; } // Qualification for embedded SCTs: // There must be at least one embedded SCT from a log that was Admissible (i.e. // Qualified, Usable, or ReadOnly) at the time of the check. // There must be at least N embedded SCTs from distinct logs that were // Admissible or Retired at the time of the check, where N depends on the // lifetime of the certificate. If the certificate lifetime is less than or // equal to 180 days, N is 2. Otherwise, N is 3. // Among these SCTs, at least two must be issued from distinct log operators. CTPolicyCompliance EmbeddedSCTsCompliant(const VerifiedSCTList& verifiedScts, uint64_t certIssuanceTime, Duration certLifetime) { size_t admissibleCount = 0; size_t admissibleOrRetiredCount = 0; std::set logOperators; std::set logIds; for (const auto& verifiedSct : verifiedScts) { if (verifiedSct.origin != SCTOrigin::Embedded) { continue; } if (verifiedSct.logState != CTLogState::Admissible && !LogWasQualifiedForSct(verifiedSct, certIssuanceTime)) { continue; } // Note that a single SCT can count for both the "from a log that was // admissible" case and the "from a log that was admissible or retired" // case. if (verifiedSct.logState == CTLogState::Admissible) { admissibleCount++; } if (LogWasQualifiedForSct(verifiedSct, certIssuanceTime)) { admissibleOrRetiredCount++; logIds.insert(verifiedSct.sct.logId); } logOperators.insert(verifiedSct.logOperatorId); } size_t requiredEmbeddedScts = GetRequiredEmbeddedSctsCount(certLifetime); if (admissibleCount < 1 || admissibleOrRetiredCount < requiredEmbeddedScts) { return CTPolicyCompliance::NotEnoughScts; } if (logIds.size() < requiredEmbeddedScts || logOperators.size() < 2) { return CTPolicyCompliance::NotDiverseScts; } return CTPolicyCompliance::Compliant; } // Qualification for non-embedded SCTs (i.e. SCTs delivered via TLS handshake // or OCSP response): // There must be at least two SCTs from logs that were Admissible (i.e. // Qualified, Usable, or ReadOnly) at the time of the check. Among these SCTs, // at least two must be issued from distinct log operators. CTPolicyCompliance NonEmbeddedSCTsCompliant( const VerifiedSCTList& verifiedScts) { size_t admissibleCount = 0; std::set logOperators; std::set logIds; for (const auto& verifiedSct : verifiedScts) { if (verifiedSct.origin == SCTOrigin::Embedded) { continue; } if (verifiedSct.logState != CTLogState::Admissible) { continue; } admissibleCount++; logIds.insert(verifiedSct.sct.logId); logOperators.insert(verifiedSct.logOperatorId); } if (admissibleCount < 2) { return CTPolicyCompliance::NotEnoughScts; } if (logIds.size() < 2 || logOperators.size() < 2) { return CTPolicyCompliance::NotDiverseScts; } return CTPolicyCompliance::Compliant; } CTPolicyCompliance CheckCTPolicyCompliance(const VerifiedSCTList& verifiedScts, Duration certLifetime) { if (NonEmbeddedSCTsCompliant(verifiedScts) == CTPolicyCompliance::Compliant) { return CTPolicyCompliance::Compliant; } uint64_t certIssuanceTime = GetEffectiveCertIssuanceTime(verifiedScts); return EmbeddedSCTsCompliant(verifiedScts, certIssuanceTime, certLifetime); } } // namespace ct } // namespace mozilla