1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
|
/* -*- 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 <set>
#include <stdint.h>
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<CTLogOperatorId> logOperators;
std::set<Buffer> logIds;
for (const auto& verifiedSct : verifiedScts) {
if (verifiedSct.origin != SCTOrigin::Embedded) {
continue;
}
if (verifiedSct.logState != CTLogState::Admissible &&
!LogWasQualifiedForSct(verifiedSct, certIssuanceTime)) {
continue;
}
// SCTs from tiled logs "MUST" have a valid leaf index extension.
if (verifiedSct.logFormat == CTLogFormat::Tiled &&
verifiedSct.sct.leafIndex.isNothing()) {
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<CTLogOperatorId> logOperators;
std::set<Buffer> logIds;
for (const auto& verifiedSct : verifiedScts) {
if (verifiedSct.origin == SCTOrigin::Embedded) {
continue;
}
if (verifiedSct.logState != CTLogState::Admissible) {
continue;
}
// SCTs from tiled logs "MUST" have a valid leaf index extension.
if (verifiedSct.logFormat == CTLogFormat::Tiled &&
verifiedSct.sct.leafIndex.isNothing()) {
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
|