Line data Source code
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/. */
6 :
7 : #include "CTPolicyEnforcer.h"
8 :
9 : #include <algorithm>
10 : #include <stdint.h>
11 :
12 : #include "mozilla/ArrayUtils.h"
13 : #include "mozilla/Assertions.h"
14 : #include "mozilla/Logging.h"
15 : #include "mozilla/Vector.h"
16 :
17 : extern mozilla::LazyLogModule gCertVerifierLog;
18 :
19 : namespace mozilla { namespace ct {
20 :
21 : using namespace mozilla::pkix;
22 :
23 : // Returns the number of embedded SCTs required to be present on a
24 : // certificate for Qualification Case #2 (embedded SCTs).
25 : static size_t
26 0 : GetRequiredEmbeddedSctsCount(size_t certLifetimeInFullCalendarMonths)
27 : {
28 : // "there are Embedded SCTs from AT LEAST N+1 once or currently qualified
29 : // logs, where N is the lifetime of the certificate in years (normally
30 : // rounding up, but rounding down when up to 3 months over), and must be
31 : // at least 1"
32 0 : return 1 + (certLifetimeInFullCalendarMonths + 9) / 12;
33 : }
34 :
35 : // Whether a valid embedded SCT is present in the list.
36 : static bool
37 0 : HasValidEmbeddedSct(const VerifiedSCTList& verifiedScts)
38 : {
39 0 : for (const VerifiedSCT& verifiedSct : verifiedScts) {
40 0 : if (verifiedSct.status == VerifiedSCT::Status::Valid &&
41 0 : verifiedSct.origin == VerifiedSCT::Origin::Embedded) {
42 0 : return true;
43 : }
44 : }
45 0 : return false;
46 : }
47 :
48 : // Whether a valid non-embedded SCT is present in the list.
49 : static bool
50 0 : HasValidNonEmbeddedSct(const VerifiedSCTList& verifiedScts)
51 : {
52 0 : for (const VerifiedSCT& verifiedSct : verifiedScts) {
53 0 : if (verifiedSct.status == VerifiedSCT::Status::Valid &&
54 0 : (verifiedSct.origin == VerifiedSCT::Origin::TLSExtension ||
55 0 : verifiedSct.origin == VerifiedSCT::Origin::OCSPResponse)) {
56 0 : return true;
57 : }
58 : }
59 0 : return false;
60 : }
61 :
62 : // Given a list of verified SCTs, counts the number of distinct CA-independent
63 : // log operators running the CT logs that issued the SCTs which satisfy
64 : // the provided boolean predicate.
65 : template <typename SelectFunc>
66 : static Result
67 0 : CountIndependentLogOperatorsForSelectedScts(const VerifiedSCTList& verifiedScts,
68 : const CTLogOperatorList& dependentOperators,
69 : size_t& count,
70 : SelectFunc selected)
71 : {
72 0 : CTLogOperatorList operatorIds;
73 0 : for (const VerifiedSCT& verifiedSct : verifiedScts) {
74 0 : CTLogOperatorId sctLogOperatorId = verifiedSct.logOperatorId;
75 : // Check if |sctLogOperatorId| is CA-dependent.
76 0 : bool isDependentOperator = false;
77 0 : for (CTLogOperatorId dependentOperator : dependentOperators) {
78 0 : if (sctLogOperatorId == dependentOperator) {
79 0 : isDependentOperator = true;
80 0 : break;
81 : }
82 : }
83 0 : if (isDependentOperator || !selected(verifiedSct)) {
84 0 : continue;
85 : }
86 : // Check if |sctLogOperatorId| is in |operatorIds|...
87 0 : bool alreadyAdded = false;
88 0 : for (CTLogOperatorId id : operatorIds) {
89 0 : if (id == sctLogOperatorId) {
90 0 : alreadyAdded = true;
91 0 : break;
92 : }
93 : }
94 : // ...and if not, add it.
95 0 : if (!alreadyAdded) {
96 0 : if (!operatorIds.append(sctLogOperatorId)) {
97 0 : return Result::FATAL_ERROR_NO_MEMORY;
98 : }
99 : }
100 : }
101 0 : count = operatorIds.length();
102 0 : return Success;
103 : }
104 :
105 : // Given a list of verified SCTs, counts the number of distinct CT logs
106 : // that issued the SCTs that satisfy the |selected| predicate.
107 : template <typename SelectFunc>
108 : static Result
109 0 : CountLogsForSelectedScts(const VerifiedSCTList& verifiedScts,
110 : size_t& count,
111 : SelectFunc selected)
112 : {
113 : // Keep pointers to log ids (of type Buffer) from |verifiedScts| to save on
114 : // memory allocations.
115 0 : Vector<const Buffer*, 8> logIds;
116 0 : for (const VerifiedSCT& verifiedSct : verifiedScts) {
117 0 : if (!selected(verifiedSct)) {
118 0 : continue;
119 : }
120 :
121 0 : const Buffer* sctLogId = &verifiedSct.sct.logId;
122 : // Check if |sctLogId| points to data already in |logIds|...
123 0 : bool alreadyAdded = false;
124 0 : for (const Buffer* logId : logIds) {
125 0 : if (*logId == *sctLogId) {
126 0 : alreadyAdded = true;
127 0 : break;
128 : }
129 : }
130 : // ...and if not, add it.
131 0 : if (!alreadyAdded) {
132 0 : if (!logIds.append(sctLogId)) {
133 0 : return Result::FATAL_ERROR_NO_MEMORY;
134 : }
135 : }
136 : }
137 0 : count = logIds.length();
138 0 : return Success;
139 : }
140 :
141 : // Calculates the effective issuance time of connection's certificate using
142 : // the SCTs present on the connection (we can't rely on notBefore validity
143 : // field of the certificate since it can be backdated).
144 : // Used to determine whether to accept SCTs issued by past qualified logs.
145 : // The effective issuance time is defined as the earliest of all SCTs,
146 : // rather than the latest of embedded SCTs, in order to give CAs the benefit
147 : // of the doubt in the event a log is revoked in the midst of processing
148 : // a precertificate and issuing the certificate.
149 : // It is acceptable to ignore the origin of the SCTs because SCTs
150 : // delivered via OCSP/TLS extension will cover the full certificate,
151 : // which necessarily will exist only after the precertificate
152 : // has been logged and the actual certificate issued.
153 : static uint64_t
154 0 : GetEffectiveCertIssuanceTime(const VerifiedSCTList& verifiedScts)
155 : {
156 0 : uint64_t result = UINT64_MAX;
157 0 : for (const VerifiedSCT& verifiedSct : verifiedScts) {
158 0 : if (verifiedSct.status == VerifiedSCT::Status::Valid) {
159 0 : result = std::min(result, verifiedSct.sct.timestamp);
160 : }
161 : }
162 0 : return result;
163 : }
164 :
165 : // Checks if the log that issued the given SCT is "once or currently qualified"
166 : // (i.e. was qualified at the time of the certificate issuance). In addition,
167 : // makes sure the SCT is before the disqualification.
168 : static bool
169 0 : LogWasQualifiedForSct(const VerifiedSCT& verifiedSct, uint64_t certIssuanceTime)
170 : {
171 0 : if (verifiedSct.status == VerifiedSCT::Status::Valid) {
172 0 : return true;
173 : }
174 0 : if (verifiedSct.status == VerifiedSCT::Status::ValidFromDisqualifiedLog) {
175 0 : uint64_t logDisqualificationTime = verifiedSct.logDisqualificationTime;
176 0 : return certIssuanceTime < logDisqualificationTime &&
177 0 : verifiedSct.sct.timestamp < logDisqualificationTime;
178 : }
179 0 : return false;
180 : }
181 :
182 : // "A certificate is CT Qualified if it is presented with at least two SCTs
183 : // from once or currently qualified logs run by a minimum of two entities
184 : // independent of the CA and of each other."
185 : // By the grandfathering provision (not currently implemented), certificates
186 : // "are CT Qualified if they are presented with SCTs from once or
187 : // currently qualified logs run by a minimum of one entity independent
188 : // of the CA."
189 : static Result
190 0 : CheckOperatorDiversityCompliance(const VerifiedSCTList& verifiedScts,
191 : uint64_t certIssuanceTime,
192 : const CTLogOperatorList& dependentOperators,
193 : bool& compliant)
194 : {
195 : size_t independentOperatorsCount;
196 0 : Result rv = CountIndependentLogOperatorsForSelectedScts(verifiedScts,
197 : dependentOperators, independentOperatorsCount,
198 0 : [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
199 0 : return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
200 0 : });
201 0 : if (rv != Success) {
202 0 : return rv;
203 : }
204 : // Having at least 2 operators implies we have at least 2 SCTs.
205 : // For the grandfathering provision (1 operator) we will need to include
206 : // an additional SCTs count check using
207 : // rv = CountLogsForSelectedScts(verifiedScts, sctsCount,
208 : // [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
209 : // return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
210 : // });
211 0 : compliant = independentOperatorsCount >= 2;
212 0 : return Success;
213 : }
214 :
215 : // Qualification Case #1 (non-embedded SCTs) - the following must hold:
216 : // a. An SCT from a log qualified at the time of check is presented via the
217 : // TLS extension OR is embedded within a stapled OCSP response;
218 : // AND
219 : // b. There are at least two SCTs from logs qualified at the time of check,
220 : // presented via any method.
221 : static Result
222 0 : CheckNonEmbeddedCompliance(const VerifiedSCTList& verifiedScts, bool& compliant)
223 : {
224 0 : if (!HasValidNonEmbeddedSct(verifiedScts)) {
225 0 : compliant = false;
226 0 : return Success;
227 : }
228 :
229 : size_t validSctsCount;
230 0 : Result rv = CountLogsForSelectedScts(verifiedScts, validSctsCount,
231 0 : [](const VerifiedSCT& verifiedSct)->bool {
232 0 : return verifiedSct.status == VerifiedSCT::Status::Valid;
233 0 : });
234 0 : if (rv != Success) {
235 0 : return rv;
236 : }
237 :
238 0 : MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
239 : ("CT Policy non-embedded case status: validSCTs=%zu\n",
240 : validSctsCount));
241 0 : compliant = validSctsCount >= 2;
242 0 : return Success;
243 : }
244 :
245 : // Qualification Case #2 (embedded SCTs) - the following must hold:
246 : // a. An Embedded SCT from a log qualified at the time of check is presented;
247 : // AND
248 : // b. There are Embedded SCTs from AT LEAST N + 1 once or currently qualified
249 : // logs, where N is the lifetime of the certificate in years (normally
250 : // rounding up, but rounding down when up to 3 months over), and must be
251 : // at least 1.
252 : static Result
253 0 : CheckEmbeddedCompliance(const VerifiedSCTList& verifiedScts,
254 : size_t certLifetimeInCalendarMonths,
255 : uint64_t certIssuanceTime,
256 : bool& compliant)
257 : {
258 0 : if (!HasValidEmbeddedSct(verifiedScts)) {
259 0 : compliant = false;
260 0 : return Success;
261 : }
262 :
263 : // Count the compliant embedded SCTs. Only a single SCT from each log
264 : // is accepted. Note that a given log might return several different SCTs
265 : // for the same precertificate (it is permitted, but advised against).
266 : size_t embeddedSctsCount;
267 0 : Result rv = CountLogsForSelectedScts(verifiedScts, embeddedSctsCount,
268 0 : [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
269 0 : return verifiedSct.origin == VerifiedSCT::Origin::Embedded &&
270 0 : LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
271 0 : });
272 0 : if (rv != Success) {
273 0 : return rv;
274 : }
275 :
276 : size_t requiredSctsCount =
277 0 : GetRequiredEmbeddedSctsCount(certLifetimeInCalendarMonths);
278 :
279 0 : MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
280 : ("CT Policy embedded case status: "
281 : "requiredSCTs=%zu embeddedSCTs=%zu\n",
282 : requiredSctsCount, embeddedSctsCount));
283 :
284 0 : compliant = embeddedSctsCount >= requiredSctsCount;
285 0 : return Success;
286 : }
287 :
288 : Result
289 0 : CTPolicyEnforcer::CheckCompliance(const VerifiedSCTList& verifiedScts,
290 : size_t certLifetimeInCalendarMonths,
291 : const CTLogOperatorList& dependentOperators,
292 : CTPolicyCompliance& compliance)
293 : {
294 0 : uint64_t certIssuanceTime = GetEffectiveCertIssuanceTime(verifiedScts);
295 :
296 : bool diversityOK;
297 : Result rv = CheckOperatorDiversityCompliance(verifiedScts, certIssuanceTime,
298 : dependentOperators,
299 0 : diversityOK);
300 0 : if (rv != Success) {
301 0 : return rv;
302 : }
303 0 : if (diversityOK) {
304 0 : MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
305 : ("CT Policy: diversity satisfied\n"));
306 : }
307 :
308 : bool nonEmbeddedCaseOK;
309 0 : rv = CheckNonEmbeddedCompliance(verifiedScts, nonEmbeddedCaseOK);
310 0 : if (rv != Success) {
311 0 : return rv;
312 : }
313 0 : if (nonEmbeddedCaseOK) {
314 0 : MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
315 : ("CT Policy: non-embedded case satisfied)\n"));
316 : }
317 :
318 : bool embeddedCaseOK;
319 : rv = CheckEmbeddedCompliance(verifiedScts, certLifetimeInCalendarMonths,
320 0 : certIssuanceTime, embeddedCaseOK);
321 0 : if (rv != Success) {
322 0 : return rv;
323 : }
324 0 : if (embeddedCaseOK) {
325 0 : MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
326 : ("CT Policy: embedded case satisfied\n"));
327 : }
328 :
329 0 : if (nonEmbeddedCaseOK || embeddedCaseOK) {
330 0 : compliance = diversityOK ? CTPolicyCompliance::Compliant
331 0 : : CTPolicyCompliance::NotDiverseScts;
332 : } else {
333 : // Not enough SCTs are present to satisfy either case of the policy.
334 0 : compliance = CTPolicyCompliance::NotEnoughScts;
335 : }
336 :
337 0 : switch (compliance) {
338 : case CTPolicyCompliance::Compliant:
339 0 : MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
340 : ("CT Policy compliance: Compliant\n"));
341 0 : break;
342 : case CTPolicyCompliance::NotEnoughScts:
343 0 : MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
344 : ("CT Policy compliance: NotEnoughScts\n"));
345 0 : break;
346 : case CTPolicyCompliance::NotDiverseScts:
347 0 : MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
348 : ("CT Policy compliance: NotDiverseScts\n"));
349 0 : break;
350 : case CTPolicyCompliance::Unknown:
351 : default:
352 0 : MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
353 : }
354 0 : return Success;
355 : }
356 :
357 : } } // namespace mozilla::ct
|