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.
5 #include "net/cert/cert_policy_enforcer.h"
10 #include "base/build_time.h"
11 #include "base/callback_helpers.h"
12 #include "base/metrics/field_trial.h"
13 #include "base/metrics/histogram.h"
14 #include "base/numerics/safe_conversions.h"
15 #include "base/strings/string_number_conversions.h"
16 #include "base/values.h"
17 #include "base/version.h"
18 #include "net/cert/ct_ev_whitelist.h"
19 #include "net/cert/ct_verify_result.h"
20 #include "net/cert/signed_certificate_timestamp.h"
21 #include "net/cert/x509_certificate.h"
22 #include "net/cert/x509_certificate_net_log_param.h"
23 #include "net/log/net_log.h"
29 bool IsEmbeddedSCT(const scoped_refptr
<ct::SignedCertificateTimestamp
>& sct
) {
30 return sct
->origin
== ct::SignedCertificateTimestamp::SCT_EMBEDDED
;
33 // Returns true if the current build is recent enough to ensure that
34 // built-in security information (e.g. CT Logs) is fresh enough.
35 // TODO(eranm): Move to base or net/base
36 bool IsBuildTimely() {
37 #if defined(DONT_EMBED_BUILD_METADATA) && !defined(OFFICIAL_BUILD)
40 const base::Time build_time
= base::GetBuildTime();
41 // We consider built-in information to be timely for 10 weeks.
42 return (base::Time::Now() - build_time
).InDays() < 70 /* 10 weeks */;
46 // Returns a rounded-down months difference of |start| and |end|,
47 // together with an indication of whether the last month was
48 // a full month, because the range starts specified in the policy
49 // are not consistent in terms of including the range start value.
50 void RoundedDownMonthDifference(const base::Time
& start
,
51 const base::Time
& end
,
52 size_t* rounded_months_difference
,
53 bool* has_partial_month
) {
54 DCHECK(rounded_months_difference
);
55 DCHECK(has_partial_month
);
56 base::Time::Exploded exploded_start
;
57 base::Time::Exploded exploded_expiry
;
58 start
.UTCExplode(&exploded_start
);
59 end
.UTCExplode(&exploded_expiry
);
61 *rounded_months_difference
= 0;
62 *has_partial_month
= false;
65 *has_partial_month
= true;
66 uint32_t month_diff
= (exploded_expiry
.year
- exploded_start
.year
) * 12 +
67 (exploded_expiry
.month
- exploded_start
.month
);
68 if (exploded_expiry
.day_of_month
< exploded_start
.day_of_month
)
70 else if (exploded_expiry
.day_of_month
== exploded_start
.day_of_month
)
71 *has_partial_month
= false;
73 *rounded_months_difference
= month_diff
;
76 bool HasRequiredNumberOfSCTs(const X509Certificate
& cert
,
77 const ct::CTVerifyResult
& ct_result
) {
78 // TODO(eranm): Count the number of *independent* SCTs once the information
79 // about log operators is available, crbug.com/425174
80 size_t num_valid_scts
= ct_result
.verified_scts
.size();
81 size_t num_embedded_scts
=
82 std::count_if(ct_result
.verified_scts
.begin(),
83 ct_result
.verified_scts
.end(), IsEmbeddedSCT
);
85 size_t num_non_embedded_scts
= num_valid_scts
- num_embedded_scts
;
86 // If at least two valid SCTs were delivered by means other than embedding
87 // (i.e. in a TLS extension or OCSP), then the certificate conforms to bullet
88 // number 3 of the "Qualifying Certificate" section of the CT/EV policy.
89 if (num_non_embedded_scts
>= 2)
92 if (cert
.valid_start().is_null() || cert
.valid_expiry().is_null() ||
93 cert
.valid_start().is_max() || cert
.valid_expiry().is_max()) {
94 // Will not be able to calculate the certificate's validity period.
99 bool has_partial_month
;
100 RoundedDownMonthDifference(cert
.valid_start(), cert
.valid_expiry(), &lifetime
,
103 // For embedded SCTs, if the certificate has the number of SCTs specified in
104 // table 1 of the "Qualifying Certificate" section of the CT/EV policy, then
106 size_t num_required_embedded_scts
;
107 if (lifetime
> 39 || (lifetime
== 39 && has_partial_month
)) {
108 num_required_embedded_scts
= 5;
109 } else if (lifetime
> 27 || (lifetime
== 27 && has_partial_month
)) {
110 num_required_embedded_scts
= 4;
111 } else if (lifetime
>= 15) {
112 num_required_embedded_scts
= 3;
114 num_required_embedded_scts
= 2;
117 return num_embedded_scts
>= num_required_embedded_scts
;
120 enum CTComplianceStatus
{
121 CT_NOT_COMPLIANT
= 0,
127 const char* ComplianceStatusToString(CTComplianceStatus status
) {
129 case CT_NOT_COMPLIANT
:
130 return "NOT_COMPLIANT";
132 case CT_IN_WHITELIST
:
133 return "WHITELISTED";
136 return "ENOUGH_SCTS";
138 case CT_COMPLIANCE_MAX
:
145 enum EVWhitelistStatus
{
146 EV_WHITELIST_NOT_PRESENT
= 0,
147 EV_WHITELIST_INVALID
= 1,
148 EV_WHITELIST_VALID
= 2,
152 void LogCTComplianceStatusToUMA(CTComplianceStatus status
,
153 const ct::EVCertsWhitelist
* ev_whitelist
) {
154 UMA_HISTOGRAM_ENUMERATION("Net.SSL_EVCertificateCTCompliance", status
,
156 if (status
== CT_NOT_COMPLIANT
) {
157 EVWhitelistStatus ev_whitelist_status
= EV_WHITELIST_NOT_PRESENT
;
158 if (ev_whitelist
!= NULL
) {
159 if (ev_whitelist
->IsValid())
160 ev_whitelist_status
= EV_WHITELIST_VALID
;
162 ev_whitelist_status
= EV_WHITELIST_INVALID
;
165 UMA_HISTOGRAM_ENUMERATION("Net.SSL_EVWhitelistValidityForNonCompliantCert",
166 ev_whitelist_status
, EV_WHITELIST_MAX
);
170 struct ComplianceDetails
{
172 : ct_presence_required(false),
174 status(CT_NOT_COMPLIANT
) {}
176 // Whether enforcement of the policy was required or not.
177 bool ct_presence_required
;
178 // Whether the build is not older than 10 weeks. The value is meaningful only
179 // if |ct_presence_required| is true.
181 // Compliance status - meaningful only if |ct_presence_required| and
182 // |build_timely| are true.
183 CTComplianceStatus status
;
184 // EV whitelist version.
185 base::Version whitelist_version
;
188 scoped_ptr
<base::Value
> NetLogComplianceCheckResultCallback(
189 X509Certificate
* cert
,
190 ComplianceDetails
* details
,
191 NetLogCaptureMode capture_mode
) {
192 scoped_ptr
<base::DictionaryValue
> dict(new base::DictionaryValue());
193 dict
->Set("certificate", NetLogX509CertificateCallback(cert
, capture_mode
));
194 dict
->SetBoolean("policy_enforcement_required",
195 details
->ct_presence_required
);
196 if (details
->ct_presence_required
) {
197 dict
->SetBoolean("build_timely", details
->build_timely
);
198 if (details
->build_timely
) {
199 dict
->SetString("ct_compliance_status",
200 ComplianceStatusToString(details
->status
));
201 if (details
->whitelist_version
.IsValid())
202 dict
->SetString("ev_whitelist_version",
203 details
->whitelist_version
.GetString());
209 bool IsCertificateInWhitelist(const X509Certificate
& cert
,
210 const ct::EVCertsWhitelist
* ev_whitelist
) {
211 bool cert_in_ev_whitelist
= false;
212 if (ev_whitelist
&& ev_whitelist
->IsValid()) {
213 const SHA256HashValue
fingerprint(
214 X509Certificate::CalculateFingerprint256(cert
.os_cert_handle()));
216 std::string truncated_fp
=
217 std::string(reinterpret_cast<const char*>(fingerprint
.data
), 8);
218 cert_in_ev_whitelist
= ev_whitelist
->ContainsCertificateHash(truncated_fp
);
220 UMA_HISTOGRAM_BOOLEAN("Net.SSL_EVCertificateInWhitelist",
221 cert_in_ev_whitelist
);
223 return cert_in_ev_whitelist
;
226 void CheckCTEVPolicyCompliance(X509Certificate
* cert
,
227 const ct::EVCertsWhitelist
* ev_whitelist
,
228 const ct::CTVerifyResult
& ct_result
,
229 ComplianceDetails
* result
) {
230 result
->ct_presence_required
= true;
232 if (!IsBuildTimely())
234 result
->build_timely
= true;
236 if (ev_whitelist
&& ev_whitelist
->IsValid())
237 result
->whitelist_version
= ev_whitelist
->Version();
239 if (IsCertificateInWhitelist(*cert
, ev_whitelist
)) {
240 result
->status
= CT_IN_WHITELIST
;
244 if (HasRequiredNumberOfSCTs(*cert
, ct_result
)) {
245 result
->status
= CT_ENOUGH_SCTS
;
249 result
->status
= CT_NOT_COMPLIANT
;
254 bool CertPolicyEnforcer::DoesConformToCTEVPolicy(
255 X509Certificate
* cert
,
256 const ct::EVCertsWhitelist
* ev_whitelist
,
257 const ct::CTVerifyResult
& ct_result
,
258 const BoundNetLog
& net_log
) {
259 ComplianceDetails details
;
261 CheckCTEVPolicyCompliance(cert
, ev_whitelist
, ct_result
, &details
);
263 NetLog::ParametersCallback net_log_callback
=
264 base::Bind(&NetLogComplianceCheckResultCallback
, base::Unretained(cert
),
265 base::Unretained(&details
));
267 net_log
.AddEvent(NetLog::TYPE_EV_CERT_CT_COMPLIANCE_CHECKED
,
270 if (!details
.ct_presence_required
)
273 if (!details
.build_timely
)
276 LogCTComplianceStatusToUMA(details
.status
, ev_whitelist
);
278 if (details
.status
== CT_IN_WHITELIST
|| details
.status
== CT_ENOUGH_SCTS
)