Bug 1869043 allow a device to be specified with MediaTrackGraph::NotifyWhenDeviceStar...
[gecko.git] / security / ct / CTPolicyEnforcer.cpp
blob0cacb25da66d291cca6550948d541d5d9ccb5b66
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "CTPolicyEnforcer.h"
9 #include <algorithm>
10 #include <stdint.h>
11 #include <vector>
13 namespace mozilla {
14 namespace ct {
16 using namespace mozilla::pkix;
18 // Returns the number of embedded SCTs required to be present on a
19 // certificate for Qualification Case #2 (embedded SCTs).
20 static size_t GetRequiredEmbeddedSctsCount(
21 size_t certLifetimeInFullCalendarMonths) {
22 // "there are Embedded SCTs from AT LEAST N+1 once or currently qualified
23 // logs, where N is the lifetime of the certificate in years (normally
24 // rounding up, but rounding down when up to 3 months over), and must be
25 // at least 1"
26 return 1 + (certLifetimeInFullCalendarMonths + 9) / 12;
29 // Whether a valid embedded SCT is present in the list.
30 static bool HasValidEmbeddedSct(const VerifiedSCTList& verifiedScts) {
31 for (const VerifiedSCT& verifiedSct : verifiedScts) {
32 if (verifiedSct.status == VerifiedSCT::Status::Valid &&
33 verifiedSct.origin == VerifiedSCT::Origin::Embedded) {
34 return true;
37 return false;
40 // Whether a valid non-embedded SCT is present in the list.
41 static bool HasValidNonEmbeddedSct(const VerifiedSCTList& verifiedScts) {
42 for (const VerifiedSCT& verifiedSct : verifiedScts) {
43 if (verifiedSct.status == VerifiedSCT::Status::Valid &&
44 (verifiedSct.origin == VerifiedSCT::Origin::TLSExtension ||
45 verifiedSct.origin == VerifiedSCT::Origin::OCSPResponse)) {
46 return true;
49 return false;
52 // Given a list of verified SCTs, counts the number of distinct CA-independent
53 // log operators running the CT logs that issued the SCTs which satisfy
54 // the provided boolean predicate.
55 template <typename SelectFunc>
56 void CountIndependentLogOperatorsForSelectedScts(
57 const VerifiedSCTList& verifiedScts,
58 const CTLogOperatorList& dependentOperators, size_t& count,
59 SelectFunc selected) {
60 CTLogOperatorList operatorIds;
61 for (const VerifiedSCT& verifiedSct : verifiedScts) {
62 CTLogOperatorId sctLogOperatorId = verifiedSct.logOperatorId;
63 // Check if |sctLogOperatorId| is CA-dependent.
64 bool isDependentOperator = false;
65 for (CTLogOperatorId dependentOperator : dependentOperators) {
66 if (sctLogOperatorId == dependentOperator) {
67 isDependentOperator = true;
68 break;
71 if (isDependentOperator || !selected(verifiedSct)) {
72 continue;
74 // Check if |sctLogOperatorId| is in |operatorIds|...
75 bool alreadyAdded = false;
76 for (CTLogOperatorId id : operatorIds) {
77 if (id == sctLogOperatorId) {
78 alreadyAdded = true;
79 break;
82 // ...and if not, add it.
83 if (!alreadyAdded) {
84 operatorIds.push_back(sctLogOperatorId);
87 count = operatorIds.size();
90 // Given a list of verified SCTs, counts the number of distinct CT logs
91 // that issued the SCTs that satisfy the |selected| predicate.
92 template <typename SelectFunc>
93 static void CountLogsForSelectedScts(const VerifiedSCTList& verifiedScts,
94 size_t& count, SelectFunc selected) {
95 // Keep pointers to log ids (of type Buffer) from |verifiedScts| to save on
96 // memory allocations.
97 std::vector<const Buffer*> logIds;
98 for (const VerifiedSCT& verifiedSct : verifiedScts) {
99 if (!selected(verifiedSct)) {
100 continue;
103 const Buffer* sctLogId = &verifiedSct.sct.logId;
104 // Check if |sctLogId| points to data already in |logIds|...
105 bool alreadyAdded = false;
106 for (const Buffer* logId : logIds) {
107 if (*logId == *sctLogId) {
108 alreadyAdded = true;
109 break;
112 // ...and if not, add it.
113 if (!alreadyAdded) {
114 logIds.push_back(sctLogId);
117 count = logIds.size();
120 // Calculates the effective issuance time of connection's certificate using
121 // the SCTs present on the connection (we can't rely on notBefore validity
122 // field of the certificate since it can be backdated).
123 // Used to determine whether to accept SCTs issued by past qualified logs.
124 // The effective issuance time is defined as the earliest of all SCTs,
125 // rather than the latest of embedded SCTs, in order to give CAs the benefit
126 // of the doubt in the event a log is revoked in the midst of processing
127 // a precertificate and issuing the certificate.
128 // It is acceptable to ignore the origin of the SCTs because SCTs
129 // delivered via OCSP/TLS extension will cover the full certificate,
130 // which necessarily will exist only after the precertificate
131 // has been logged and the actual certificate issued.
132 static uint64_t GetEffectiveCertIssuanceTime(
133 const VerifiedSCTList& verifiedScts) {
134 uint64_t result = UINT64_MAX;
135 for (const VerifiedSCT& verifiedSct : verifiedScts) {
136 if (verifiedSct.status == VerifiedSCT::Status::Valid) {
137 result = std::min(result, verifiedSct.sct.timestamp);
140 return result;
143 // Checks if the log that issued the given SCT is "once or currently qualified"
144 // (i.e. was qualified at the time of the certificate issuance). In addition,
145 // makes sure the SCT is before the disqualification.
146 static bool LogWasQualifiedForSct(const VerifiedSCT& verifiedSct,
147 uint64_t certIssuanceTime) {
148 if (verifiedSct.status == VerifiedSCT::Status::Valid) {
149 return true;
151 if (verifiedSct.status == VerifiedSCT::Status::ValidFromDisqualifiedLog) {
152 uint64_t logDisqualificationTime = verifiedSct.logDisqualificationTime;
153 return certIssuanceTime < logDisqualificationTime &&
154 verifiedSct.sct.timestamp < logDisqualificationTime;
156 return false;
159 // "A certificate is CT Qualified if it is presented with at least two SCTs
160 // from once or currently qualified logs run by a minimum of two entities
161 // independent of the CA and of each other."
162 // By the preexisting certificate exception provision (not currently
163 // implemented), certificates "are CT Qualified if they are presented with SCTs
164 // from once or currently qualified logs run by a minimum of one entity
165 // independent of the CA."
166 static void CheckOperatorDiversityCompliance(
167 const VerifiedSCTList& verifiedScts, uint64_t certIssuanceTime,
168 const CTLogOperatorList& dependentOperators, bool& compliant) {
169 size_t independentOperatorsCount;
170 CountIndependentLogOperatorsForSelectedScts(
171 verifiedScts, dependentOperators, independentOperatorsCount,
172 [certIssuanceTime](const VerifiedSCT& verifiedSct) -> bool {
173 return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
175 // Having at least 2 operators implies we have at least 2 SCTs.
176 // For the preexisting certificate exception provision (1 operator) we will
177 // need to include an additional SCTs count check using
178 // CountLogsForSelectedScts(verifiedScts, sctsCount,
179 // [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
180 // return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
181 // });
182 compliant = independentOperatorsCount >= 2;
185 // Qualification Case #1 (non-embedded SCTs) - the following must hold:
186 // a. An SCT from a log qualified at the time of check is presented via the
187 // TLS extension OR is embedded within a stapled OCSP response;
188 // AND
189 // b. There are at least two SCTs from logs qualified at the time of check,
190 // presented via any method.
191 static void CheckNonEmbeddedCompliance(const VerifiedSCTList& verifiedScts,
192 bool& compliant) {
193 if (!HasValidNonEmbeddedSct(verifiedScts)) {
194 compliant = false;
195 return;
198 size_t validSctsCount;
199 CountLogsForSelectedScts(
200 verifiedScts, validSctsCount, [](const VerifiedSCT& verifiedSct) -> bool {
201 return verifiedSct.status == VerifiedSCT::Status::Valid;
204 compliant = validSctsCount >= 2;
207 // Qualification Case #2 (embedded SCTs) - the following must hold:
208 // a. An Embedded SCT from a log qualified at the time of check is presented;
209 // AND
210 // b. There are Embedded SCTs from AT LEAST N + 1 once or currently qualified
211 // logs, where N is the lifetime of the certificate in years (normally
212 // rounding up, but rounding down when up to 3 months over), and must be
213 // at least 1.
214 static void CheckEmbeddedCompliance(const VerifiedSCTList& verifiedScts,
215 size_t certLifetimeInCalendarMonths,
216 uint64_t certIssuanceTime,
217 bool& compliant) {
218 if (!HasValidEmbeddedSct(verifiedScts)) {
219 compliant = false;
220 return;
223 // Count the compliant embedded SCTs. Only a single SCT from each log
224 // is accepted. Note that a given log might return several different SCTs
225 // for the same precertificate (it is permitted, but advised against).
226 size_t embeddedSctsCount;
227 CountLogsForSelectedScts(
228 verifiedScts, embeddedSctsCount,
229 [certIssuanceTime](const VerifiedSCT& verifiedSct) -> bool {
230 return verifiedSct.origin == VerifiedSCT::Origin::Embedded &&
231 LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
234 size_t requiredSctsCount =
235 GetRequiredEmbeddedSctsCount(certLifetimeInCalendarMonths);
237 compliant = embeddedSctsCount >= requiredSctsCount;
240 void CTPolicyEnforcer::CheckCompliance(
241 const VerifiedSCTList& verifiedScts, size_t certLifetimeInCalendarMonths,
242 const CTLogOperatorList& dependentOperators,
243 CTPolicyCompliance& compliance) {
244 uint64_t certIssuanceTime = GetEffectiveCertIssuanceTime(verifiedScts);
246 bool diversityOK;
247 CheckOperatorDiversityCompliance(verifiedScts, certIssuanceTime,
248 dependentOperators, diversityOK);
250 bool nonEmbeddedCaseOK;
251 CheckNonEmbeddedCompliance(verifiedScts, nonEmbeddedCaseOK);
253 bool embeddedCaseOK;
254 CheckEmbeddedCompliance(verifiedScts, certLifetimeInCalendarMonths,
255 certIssuanceTime, embeddedCaseOK);
257 if (nonEmbeddedCaseOK || embeddedCaseOK) {
258 compliance = diversityOK ? CTPolicyCompliance::Compliant
259 : CTPolicyCompliance::NotDiverseScts;
260 } else {
261 // Not enough SCTs are present to satisfy either case of the policy.
262 compliance = CTPolicyCompliance::NotEnoughScts;
265 switch (compliance) {
266 case CTPolicyCompliance::Compliant:
267 case CTPolicyCompliance::NotEnoughScts:
268 case CTPolicyCompliance::NotDiverseScts:
269 break;
270 case CTPolicyCompliance::Unknown:
271 default:
272 assert(false);
276 } // namespace ct
277 } // namespace mozilla