Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/security/certverifier/CTPolicyEnforcer.cpp
Line
Count
Source (jump to first uncovered line)
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
GetRequiredEmbeddedSctsCount(size_t certLifetimeInFullCalendarMonths)
27
0
{
28
0
  // "there are Embedded SCTs from AT LEAST N+1 once or currently qualified
29
0
  // logs, where N is the lifetime of the certificate in years (normally
30
0
  // rounding up, but rounding down when up to 3 months over), and must be
31
0
  // at least 1"
32
0
  return 1 + (certLifetimeInFullCalendarMonths + 9) / 12;
33
0
}
34
35
// Whether a valid embedded SCT is present in the list.
36
static bool
37
HasValidEmbeddedSct(const VerifiedSCTList& verifiedScts)
38
0
{
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
0
    }
44
0
  }
45
0
  return false;
46
0
}
47
48
// Whether a valid non-embedded SCT is present in the list.
49
static bool
50
HasValidNonEmbeddedSct(const VerifiedSCTList& verifiedScts)
51
0
{
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
0
    }
58
0
  }
59
0
  return false;
60
0
}
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
CountIndependentLogOperatorsForSelectedScts(const VerifiedSCTList& verifiedScts,
68
  const CTLogOperatorList& dependentOperators,
69
  size_t& count,
70
  SelectFunc selected)
71
0
{
72
0
  CTLogOperatorList operatorIds;
73
0
  for (const VerifiedSCT& verifiedSct : verifiedScts) {
74
0
    CTLogOperatorId sctLogOperatorId = verifiedSct.logOperatorId;
75
0
    // 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
0
      }
82
0
    }
83
0
    if (isDependentOperator || !selected(verifiedSct)) {
84
0
      continue;
85
0
    }
86
0
    // 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
0
      }
93
0
    }
94
0
    // ...and if not, add it.
95
0
    if (!alreadyAdded) {
96
0
      if (!operatorIds.append(sctLogOperatorId)) {
97
0
        return Result::FATAL_ERROR_NO_MEMORY;
98
0
      }
99
0
    }
100
0
  }
101
0
  count = operatorIds.length();
102
0
  return Success;
103
0
}
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
CountLogsForSelectedScts(const VerifiedSCTList& verifiedScts,
110
                         size_t& count,
111
                         SelectFunc selected)
112
0
{
113
0
  // Keep pointers to log ids (of type Buffer) from |verifiedScts| to save on
114
0
  // 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
0
    }
120
0
121
0
    const Buffer* sctLogId = &verifiedSct.sct.logId;
122
0
    // 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
0
      }
129
0
    }
130
0
    // ...and if not, add it.
131
0
    if (!alreadyAdded) {
132
0
      if (!logIds.append(sctLogId)) {
133
0
        return Result::FATAL_ERROR_NO_MEMORY;
134
0
      }
135
0
    }
136
0
  }
137
0
  count = logIds.length();
138
0
  return Success;
139
0
}
Unexecuted instantiation: Unified_cpp_certverifier0.cpp:mozilla::pkix::Result mozilla::ct::CountLogsForSelectedScts<mozilla::ct::CheckNonEmbeddedCompliance(mozilla::Vector<mozilla::ct::VerifiedSCT, 0ul, mozilla::MallocAllocPolicy> const&, bool&)::$_2>(mozilla::Vector<mozilla::ct::VerifiedSCT, 0ul, mozilla::MallocAllocPolicy> const&, unsigned long&, mozilla::ct::CheckNonEmbeddedCompliance(mozilla::Vector<mozilla::ct::VerifiedSCT, 0ul, mozilla::MallocAllocPolicy> const&, bool&)::$_2)
Unexecuted instantiation: Unified_cpp_certverifier0.cpp:mozilla::pkix::Result mozilla::ct::CountLogsForSelectedScts<mozilla::ct::CheckEmbeddedCompliance(mozilla::Vector<mozilla::ct::VerifiedSCT, 0ul, mozilla::MallocAllocPolicy> const&, unsigned long, unsigned long, bool&)::$_3>(mozilla::Vector<mozilla::ct::VerifiedSCT, 0ul, mozilla::MallocAllocPolicy> const&, unsigned long&, mozilla::ct::CheckEmbeddedCompliance(mozilla::Vector<mozilla::ct::VerifiedSCT, 0ul, mozilla::MallocAllocPolicy> const&, unsigned long, unsigned long, bool&)::$_3)
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
GetEffectiveCertIssuanceTime(const VerifiedSCTList& verifiedScts)
155
0
{
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
0
    }
161
0
  }
162
0
  return result;
163
0
}
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
LogWasQualifiedForSct(const VerifiedSCT& verifiedSct, uint64_t certIssuanceTime)
170
0
{
171
0
  if (verifiedSct.status == VerifiedSCT::Status::Valid) {
172
0
    return true;
173
0
  }
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
0
  }
179
0
  return false;
180
0
}
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
CheckOperatorDiversityCompliance(const VerifiedSCTList& verifiedScts,
191
                                 uint64_t certIssuanceTime,
192
                                 const CTLogOperatorList& dependentOperators,
193
                                 bool& compliant)
194
0
{
195
0
  size_t independentOperatorsCount;
196
0
  Result rv = CountIndependentLogOperatorsForSelectedScts(verifiedScts,
197
0
    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
0
  }
204
0
  // Having at least 2 operators implies we have at least 2 SCTs.
205
0
  // For the grandfathering provision (1 operator) we will need to include
206
0
  // an additional SCTs count check using
207
0
  // rv = CountLogsForSelectedScts(verifiedScts, sctsCount,
208
0
  //   [certIssuanceTime](const VerifiedSCT& verifiedSct)->bool {
209
0
  //     return LogWasQualifiedForSct(verifiedSct, certIssuanceTime);
210
0
  // });
211
0
  compliant = independentOperatorsCount >= 2;
212
0
  return Success;
213
0
}
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
CheckNonEmbeddedCompliance(const VerifiedSCTList& verifiedScts, bool& compliant)
223
0
{
224
0
  if (!HasValidNonEmbeddedSct(verifiedScts)) {
225
0
    compliant = false;
226
0
    return Success;
227
0
  }
228
0
229
0
  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
0
  }
237
0
238
0
  MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
239
0
          ("CT Policy non-embedded case status: validSCTs=%zu\n",
240
0
           validSctsCount));
241
0
  compliant = validSctsCount >= 2;
242
0
  return Success;
243
0
}
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
CheckEmbeddedCompliance(const VerifiedSCTList& verifiedScts,
254
                        size_t certLifetimeInCalendarMonths,
255
                        uint64_t certIssuanceTime,
256
                        bool& compliant)
257
0
{
258
0
  if (!HasValidEmbeddedSct(verifiedScts)) {
259
0
    compliant = false;
260
0
    return Success;
261
0
  }
262
0
263
0
  // Count the compliant embedded SCTs. Only a single SCT from each log
264
0
  // is accepted. Note that a given log might return several different SCTs
265
0
  // for the same precertificate (it is permitted, but advised against).
266
0
  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
0
  }
275
0
276
0
  size_t requiredSctsCount =
277
0
    GetRequiredEmbeddedSctsCount(certLifetimeInCalendarMonths);
278
0
279
0
  MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
280
0
          ("CT Policy embedded case status: "
281
0
           "requiredSCTs=%zu embeddedSCTs=%zu\n",
282
0
           requiredSctsCount, embeddedSctsCount));
283
0
284
0
  compliant = embeddedSctsCount >= requiredSctsCount;
285
0
  return Success;
286
0
}
287
288
Result
289
CTPolicyEnforcer::CheckCompliance(const VerifiedSCTList& verifiedScts,
290
                                  size_t certLifetimeInCalendarMonths,
291
                                  const CTLogOperatorList& dependentOperators,
292
                                  CTPolicyCompliance& compliance)
293
0
{
294
0
  uint64_t certIssuanceTime = GetEffectiveCertIssuanceTime(verifiedScts);
295
0
296
0
  bool diversityOK;
297
0
  Result rv = CheckOperatorDiversityCompliance(verifiedScts, certIssuanceTime,
298
0
                                               dependentOperators,
299
0
                                               diversityOK);
300
0
  if (rv != Success) {
301
0
    return rv;
302
0
  }
303
0
  if (diversityOK) {
304
0
    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
305
0
            ("CT Policy: diversity satisfied\n"));
306
0
  }
307
0
308
0
  bool nonEmbeddedCaseOK;
309
0
  rv = CheckNonEmbeddedCompliance(verifiedScts, nonEmbeddedCaseOK);
310
0
  if (rv != Success) {
311
0
    return rv;
312
0
  }
313
0
  if (nonEmbeddedCaseOK) {
314
0
    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
315
0
            ("CT Policy: non-embedded case satisfied)\n"));
316
0
  }
317
0
318
0
  bool embeddedCaseOK;
319
0
  rv = CheckEmbeddedCompliance(verifiedScts, certLifetimeInCalendarMonths,
320
0
                               certIssuanceTime, embeddedCaseOK);
321
0
  if (rv != Success) {
322
0
    return rv;
323
0
  }
324
0
  if (embeddedCaseOK) {
325
0
    MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
326
0
            ("CT Policy: embedded case satisfied\n"));
327
0
  }
328
0
329
0
  if (nonEmbeddedCaseOK || embeddedCaseOK) {
330
0
    compliance = diversityOK ? CTPolicyCompliance::Compliant
331
0
                             : CTPolicyCompliance::NotDiverseScts;
332
0
  } else {
333
0
    // Not enough SCTs are present to satisfy either case of the policy.
334
0
    compliance = CTPolicyCompliance::NotEnoughScts;
335
0
  }
336
0
337
0
  switch (compliance) {
338
0
    case CTPolicyCompliance::Compliant:
339
0
      MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
340
0
                ("CT Policy compliance: Compliant\n"));
341
0
      break;
342
0
    case CTPolicyCompliance::NotEnoughScts:
343
0
      MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
344
0
                ("CT Policy compliance: NotEnoughScts\n"));
345
0
      break;
346
0
    case CTPolicyCompliance::NotDiverseScts:
347
0
      MOZ_LOG(gCertVerifierLog, LogLevel::Debug,
348
0
                ("CT Policy compliance: NotDiverseScts\n"));
349
0
      break;
350
0
    case CTPolicyCompliance::Unknown:
351
0
    default:
352
0
      MOZ_ASSERT_UNREACHABLE("Unexpected CTPolicyCompliance type");
353
0
  }
354
0
  return Success;
355
0
}
356
357
} } // namespace mozilla::ct