1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/certificate_transparency/chrome_ct_policy_enforcer.h"
6
7#include <stdint.h>
8
9#include <algorithm>
10#include <memory>
11#include <utility>
12
13#include "base/bind.h"
14#include "base/build_time.h"
15#include "base/callback_helpers.h"
16#include "base/metrics/field_trial.h"
17#include "base/metrics/histogram_macros.h"
18#include "base/numerics/safe_conversions.h"
19#include "base/strings/string_number_conversions.h"
20#include "base/time/time.h"
21#include "base/values.h"
22#include "base/version.h"
23#include "components/certificate_transparency/ct_known_logs.h"
24#include "net/cert/ct_policy_status.h"
25#include "net/cert/signed_certificate_timestamp.h"
26#include "net/cert/x509_certificate.h"
27#include "net/cert/x509_certificate_net_log_param.h"
28#include "net/log/net_log_capture_mode.h"
29#include "net/log/net_log_event_type.h"
30#include "net/log/net_log_parameters_callback.h"
31#include "net/log/net_log_with_source.h"
32
33using net::ct::CTPolicyCompliance;
34
35namespace certificate_transparency {
36
37namespace {
38
39// Returns true if the current build is recent enough to ensure that
40// built-in security information (e.g. CT Logs) is fresh enough.
41// TODO(eranm): Move to base or net/base
42bool IsBuildTimely() {
43 const base::Time build_time = base::GetBuildTime();
44 // We consider built-in information to be timely for 10 weeks.
45 return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */;
46}
47
48// Returns a rounded-down months difference of |start| and |end|,
49// together with an indication of whether the last month was
50// a full month, because the range starts specified in the policy
51// are not consistent in terms of including the range start value.
52void RoundedDownMonthDifference(const base::Time& start,
53 const base::Time& end,
54 size_t* rounded_months_difference,
55 bool* has_partial_month) {
56 DCHECK(rounded_months_difference);
57 DCHECK(has_partial_month);
58 base::Time::Exploded exploded_start;
59 base::Time::Exploded exploded_expiry;
60 start.UTCExplode(&exploded_start);
61 end.UTCExplode(&exploded_expiry);
62 if (end < start) {
63 *rounded_months_difference = 0;
64 *has_partial_month = false;
65 return;
66 }
67
68 *has_partial_month = true;
69 uint32_t month_diff = (exploded_expiry.year - exploded_start.year) * 12 +
70 (exploded_expiry.month - exploded_start.month);
71 if (exploded_expiry.day_of_month < exploded_start.day_of_month)
72 --month_diff;
73 else if (exploded_expiry.day_of_month == exploded_start.day_of_month)
74 *has_partial_month = false;
75
76 *rounded_months_difference = month_diff;
77}
78
79const char* CTPolicyComplianceToString(CTPolicyCompliance status) {
80 switch (status) {
81 case CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS:
82 return "COMPLIES_VIA_SCTS";
83 case CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS:
84 return "NOT_ENOUGH_SCTS";
85 case CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS:
86 return "NOT_DIVERSE_SCTS";
87 case CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY:
88 return "BUILD_NOT_TIMELY";
89 case CTPolicyCompliance::CT_POLICY_COMPLIANCE_DETAILS_NOT_AVAILABLE:
90 case CTPolicyCompliance::CT_POLICY_COUNT:
91 NOTREACHED();
92 return "unknown";
93 }
94
95 NOTREACHED();
96 return "unknown";
97}
98
99std::unique_ptr<base::Value> NetLogCertComplianceCheckResultCallback(
100 net::X509Certificate* cert,
101 bool build_timely,
102 CTPolicyCompliance compliance,
103 net::NetLogCaptureMode capture_mode) {
104 std::unique_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
105 dict->Set("certificate",
106 net::NetLogX509CertificateCallback(cert, capture_mode));
107 dict->SetBoolean("build_timely", build_timely);
108 dict->SetString("ct_compliance_status",
109 CTPolicyComplianceToString(compliance));
110 return std::move(dict);
111}
112
113// Evaluates against the policy specified at
114// https://sites.google.com/a/chromium.org/dev/Home/chromium-security/root-ca-policy/EVCTPlanMay2015edition.pdf?attredirects=0
115CTPolicyCompliance CheckCTPolicyCompliance(
116 const net::X509Certificate& cert,
117 const net::ct::SCTList& verified_scts) {
118 // Cert is outside the bounds of parsable; reject it.
119 if (cert.valid_start().is_null() || cert.valid_expiry().is_null() ||
120 cert.valid_start().is_max() || cert.valid_expiry().is_max()) {
121 return CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS;
122 }
123
124 // Scan for the earliest SCT. This is used to determine whether to enforce
125 // log diversity requirements, as well as whether to enforce whether or not
126 // a log was qualified or pending qualification at time of issuance (in the
127 // case of embedded SCTs). It's acceptable to ignore the origin of the SCT,
128 // because SCTs delivered via OCSP/TLS extension will cover the full
129 // certificate, which necessarily will exist only after the precertificate
130 // has been logged and the actual certificate issued.
131 // Note: Here, issuance date is defined as the earliest of all SCTs, rather
132 // than the latest of embedded SCTs, in order to give CAs the benefit of
133 // the doubt in the event a log is revoked in the midst of processing
134 // a precertificate and issuing the certificate.
135 base::Time issuance_date = base::Time::Max();
136 for (const auto& sct : verified_scts) {
137 base::Time unused;
138 if (IsLogDisqualified(sct->log_id, &unused))
139 continue;
140 issuance_date = std::min(sct->timestamp, issuance_date);
141 }
142
143 bool has_valid_google_sct = false;
144 bool has_valid_nongoogle_sct = false;
145 bool has_valid_embedded_sct = false;
146 bool has_valid_nonembedded_sct = false;
147 bool has_embedded_google_sct = false;
148 bool has_embedded_nongoogle_sct = false;
149 std::vector<base::StringPiece> embedded_log_ids;
150 for (const auto& sct : verified_scts) {
151 base::Time disqualification_date;
152 bool is_disqualified =
153 IsLogDisqualified(sct->log_id, &disqualification_date);
154 if (is_disqualified &&
155 sct->origin != net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) {
156 // For OCSP and TLS delivered SCTs, only SCTs that are valid at the
157 // time of check are accepted.
158 continue;
159 }
160
161 if (IsLogOperatedByGoogle(sct->log_id)) {
162 has_valid_google_sct |= !is_disqualified;
163 if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED)
164 has_embedded_google_sct = true;
165 } else {
166 has_valid_nongoogle_sct |= !is_disqualified;
167 if (sct->origin == net::ct::SignedCertificateTimestamp::SCT_EMBEDDED)
168 has_embedded_nongoogle_sct = true;
169 }
170 if (sct->origin != net::ct::SignedCertificateTimestamp::SCT_EMBEDDED) {
171 has_valid_nonembedded_sct = true;
172 } else {
173 has_valid_embedded_sct |= !is_disqualified;
174 // If the log is disqualified, it only counts towards quorum if
175 // the certificate was issued before the log was disqualified, and the
176 // SCT was obtained before the log was disqualified.
177 if (!is_disqualified || (issuance_date < disqualification_date &&
178 sct->timestamp < disqualification_date)) {
179 embedded_log_ids.push_back(sct->log_id);
180 }
181 }
182 }
183
184 // Option 1:
185 // An SCT presented via the TLS extension OR embedded within a stapled OCSP
186 // response is from a log qualified at time of check;
187 // AND there is at least one SCT from a Google Log that is qualified at
188 // time of check, presented via any method;
189 // AND there is at least one SCT from a non-Google Log that is qualified
190 // at the time of check, presented via any method.
191 //
192 // Note: Because SCTs embedded via TLS or OCSP can be updated on the fly,
193 // the issuance date is irrelevant, as any policy changes can be
194 // accomodated.
195 if (has_valid_nonembedded_sct && has_valid_google_sct &&
196 has_valid_nongoogle_sct) {
197 return CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS;
198 }
199 // Note: If has_valid_nonembedded_sct was true, but Option 2 isn't met,
200 // then the result will be that there weren't diverse enough SCTs, as that
201 // the only other way for the conditional above to fail). Because Option 1
202 // has the diversity requirement, it's implicitly a minimum number of SCTs
203 // (specifically, 2), but that's not explicitly specified in the policy.
204
205 // Option 2:
206 // There is at least one embedded SCT from a log qualified at the time of
207 // check ...
208 if (!has_valid_embedded_sct) {
209 // Under Option 2, there weren't enough SCTs, and potentially under
210 // Option 1, there weren't diverse enough SCTs. Try to signal the error
211 // that is most easily fixed.
212 return has_valid_nonembedded_sct
213 ? CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS
214 : CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS;
215 }
216
217 // ... AND there is at least one embedded SCT from a Google Log once or
218 // currently qualified;
219 // AND there is at least one embedded SCT from a non-Google Log once or
220 // currently qualified;
221 // ...
222 //
223 // Note: This policy language is only enforced after the below issuance
224 // date, as that's when the diversity policy first came into effect for
225 // SCTs embedded in certificates.
226 // The date when diverse SCTs requirement is effective from.
227 // 2015-07-01 00:00:00 UTC.
228 const base::Time kDiverseSCTRequirementStartDate =
229 base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(1435708800);
230 if (issuance_date >= kDiverseSCTRequirementStartDate &&
231 !(has_embedded_google_sct && has_embedded_nongoogle_sct)) {
232 // Note: This also covers the case for non-embedded SCTs, as it's only
233 // possible to reach here if both sets are not diverse enough.
234 return CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS;
235 }
236
237 size_t lifetime_in_months = 0;
238 bool has_partial_month = false;
239 RoundedDownMonthDifference(cert.valid_start(), cert.valid_expiry(),
240 &lifetime_in_months, &has_partial_month);
241
242 // ... AND the certificate embeds SCTs from AT LEAST the number of logs
243 // once or currently qualified shown in Table 1 of the CT Policy.
244 size_t num_required_embedded_scts = 5;
245 if (lifetime_in_months > 39 ||
246 (lifetime_in_months == 39 && has_partial_month)) {
247 num_required_embedded_scts = 5;
248 } else if (lifetime_in_months > 27 ||
249 (lifetime_in_months == 27 && has_partial_month)) {
250 num_required_embedded_scts = 4;
251 } else if (lifetime_in_months >= 15) {
252 num_required_embedded_scts = 3;
253 } else {
254 num_required_embedded_scts = 2;
255 }
256
257 // Sort the embedded log IDs and remove duplicates, so that only a single
258 // SCT from each log is accepted. This is to handle the case where a given
259 // log returns different SCTs for the same precertificate (which is
260 // permitted, but advised against).
261 std::sort(embedded_log_ids.begin(), embedded_log_ids.end());
262 auto sorted_end =
263 std::unique(embedded_log_ids.begin(), embedded_log_ids.end());
264 size_t num_embedded_scts =
265 std::distance(embedded_log_ids.begin(), sorted_end);
266
267 if (num_embedded_scts >= num_required_embedded_scts)
268 return CTPolicyCompliance::CT_POLICY_COMPLIES_VIA_SCTS;
269
270 // Under Option 2, there weren't enough SCTs, and potentially under Option
271 // 1, there weren't diverse enough SCTs. Try to signal the error that is
272 // most easily fixed.
273 return has_valid_nonembedded_sct
274 ? CTPolicyCompliance::CT_POLICY_NOT_DIVERSE_SCTS
275 : CTPolicyCompliance::CT_POLICY_NOT_ENOUGH_SCTS;
276}
277
278} // namespace
279
280CTPolicyCompliance ChromeCTPolicyEnforcer::CheckCompliance(
281 net::X509Certificate* cert,
282 const net::ct::SCTList& verified_scts,
283 const net::NetLogWithSource& net_log) {
284 // If the build is not timely, no certificate is considered compliant
285 // with CT policy. The reasoning is that, for example, a log might
286 // have been pulled and is no longer considered valid; thus, a client
287 // needs up-to-date information about logs to consider certificates to
288 // be compliant with policy.
289 bool build_timely = IsBuildTimely();
290 CTPolicyCompliance compliance;
291 if (!build_timely) {
292 compliance = CTPolicyCompliance::CT_POLICY_BUILD_NOT_TIMELY;
293 } else {
294 compliance = CheckCTPolicyCompliance(*cert, verified_scts);
295 }
296
297 net::NetLogParametersCallback net_log_callback =
298 base::BindRepeating(&NetLogCertComplianceCheckResultCallback,
299 base::Unretained(cert), build_timely, compliance);
300
301 net_log.AddEvent(net::NetLogEventType::CERT_CT_COMPLIANCE_CHECKED,
302 net_log_callback);
303
304 return compliance;
305}
306
307} // namespace certificate_transparency
308