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/base/net_log.h"
19 #include "net/cert/ct_ev_whitelist.h"
20 #include "net/cert/ct_verify_result.h"
21 #include "net/cert/signed_certificate_timestamp.h"
22 #include "net/cert/x509_certificate.h"
23 #include "net/cert/x509_certificate_net_log_param.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 uint32_t ApproximateMonthDifference(const base::Time
& start
,
47 const base::Time
& end
) {
48 base::Time::Exploded exploded_start
;
49 base::Time::Exploded exploded_expiry
;
50 start
.UTCExplode(&exploded_start
);
51 end
.UTCExplode(&exploded_expiry
);
52 uint32_t month_diff
= (exploded_expiry
.year
- exploded_start
.year
) * 12 +
53 (exploded_expiry
.month
- exploded_start
.month
);
55 // Add any remainder as a full month.
56 if (exploded_expiry
.day_of_month
> exploded_start
.day_of_month
)
62 bool HasRequiredNumberOfSCTs(const X509Certificate
& cert
,
63 const ct::CTVerifyResult
& ct_result
) {
64 // TODO(eranm): Count the number of *independent* SCTs once the information
65 // about log operators is available, crbug.com/425174
66 size_t num_valid_scts
= ct_result
.verified_scts
.size();
67 size_t num_embedded_scts
=
68 std::count_if(ct_result
.verified_scts
.begin(),
69 ct_result
.verified_scts
.end(), IsEmbeddedSCT
);
71 size_t num_non_embedded_scts
= num_valid_scts
- num_embedded_scts
;
72 // If at least two valid SCTs were delivered by means other than embedding
73 // (i.e. in a TLS extension or OCSP), then the certificate conforms to bullet
74 // number 3 of the "Qualifying Certificate" section of the CT/EV policy.
75 if (num_non_embedded_scts
>= 2)
78 if (cert
.valid_start().is_null() || cert
.valid_expiry().is_null() ||
79 cert
.valid_start().is_max() || cert
.valid_expiry().is_max()) {
80 // Will not be able to calculate the certificate's validity period.
84 uint32_t expiry_in_months_approx
=
85 ApproximateMonthDifference(cert
.valid_start(), cert
.valid_expiry());
87 // For embedded SCTs, if the certificate has the number of SCTs specified in
88 // table 1 of the "Qualifying Certificate" section of the CT/EV policy, then
90 size_t num_required_embedded_scts
;
91 if (expiry_in_months_approx
> 39) {
92 num_required_embedded_scts
= 5;
93 } else if (expiry_in_months_approx
> 27) {
94 num_required_embedded_scts
= 4;
95 } else if (expiry_in_months_approx
>= 15) {
96 num_required_embedded_scts
= 3;
98 num_required_embedded_scts
= 2;
101 return num_embedded_scts
>= num_required_embedded_scts
;
104 enum CTComplianceStatus
{
105 CT_NOT_COMPLIANT
= 0,
111 const char* ComplianceStatusToString(CTComplianceStatus status
) {
113 case CT_NOT_COMPLIANT
:
114 return "NOT_COMPLIANT";
116 case CT_IN_WHITELIST
:
117 return "WHITELISTED";
120 return "ENOUGH_SCTS";
122 case CT_COMPLIANCE_MAX
:
129 void LogCTComplianceStatusToUMA(CTComplianceStatus status
) {
130 UMA_HISTOGRAM_ENUMERATION("Net.SSL_EVCertificateCTCompliance", status
,
134 struct ComplianceDetails
{
136 : ct_presence_required(false),
138 status(CT_NOT_COMPLIANT
) {}
140 // Whether enforcement of the policy was required or not.
141 bool ct_presence_required
;
142 // Whether the build is not older than 10 weeks. The value is meaningful only
143 // if |ct_presence_required| is true.
145 // Compliance status - meaningful only if |ct_presence_required| and
146 // |build_timely| are true.
147 CTComplianceStatus status
;
148 // EV whitelist version.
149 base::Version whitelist_version
;
152 base::Value
* NetLogComplianceCheckResultCallback(X509Certificate
* cert
,
153 ComplianceDetails
* details
,
154 NetLog::LogLevel log_level
) {
155 base::DictionaryValue
* dict
= new base::DictionaryValue();
156 dict
->Set("certificate", NetLogX509CertificateCallback(cert
, log_level
));
157 dict
->SetBoolean("policy_enforcement_required",
158 details
->ct_presence_required
);
159 if (details
->ct_presence_required
) {
160 dict
->SetBoolean("build_timely", details
->build_timely
);
161 if (details
->build_timely
) {
162 dict
->SetString("ct_compliance_status",
163 ComplianceStatusToString(details
->status
));
164 if (details
->whitelist_version
.IsValid())
165 dict
->SetString("ev_whitelist_version",
166 details
->whitelist_version
.GetString());
172 bool IsCertificateInWhitelist(const X509Certificate
& cert
,
173 const ct::EVCertsWhitelist
* ev_whitelist
) {
174 bool cert_in_ev_whitelist
= false;
175 if (ev_whitelist
&& ev_whitelist
->IsValid()) {
176 const SHA256HashValue
fingerprint(
177 X509Certificate::CalculateFingerprint256(cert
.os_cert_handle()));
179 std::string truncated_fp
=
180 std::string(reinterpret_cast<const char*>(fingerprint
.data
), 8);
181 cert_in_ev_whitelist
= ev_whitelist
->ContainsCertificateHash(truncated_fp
);
183 UMA_HISTOGRAM_BOOLEAN("Net.SSL_EVCertificateInWhitelist",
184 cert_in_ev_whitelist
);
186 return cert_in_ev_whitelist
;
189 void CheckCTEVPolicyCompliance(X509Certificate
* cert
,
190 const ct::EVCertsWhitelist
* ev_whitelist
,
191 const ct::CTVerifyResult
& ct_result
,
192 ComplianceDetails
* result
) {
193 result
->ct_presence_required
= true;
195 if (!IsBuildTimely())
197 result
->build_timely
= true;
199 if (ev_whitelist
&& ev_whitelist
->IsValid())
200 result
->whitelist_version
= ev_whitelist
->Version();
202 if (IsCertificateInWhitelist(*cert
, ev_whitelist
)) {
203 result
->status
= CT_IN_WHITELIST
;
207 if (HasRequiredNumberOfSCTs(*cert
, ct_result
)) {
208 result
->status
= CT_ENOUGH_SCTS
;
212 result
->status
= CT_NOT_COMPLIANT
;
217 CertPolicyEnforcer::CertPolicyEnforcer(bool require_ct_for_ev
)
218 : require_ct_for_ev_(require_ct_for_ev
) {
221 CertPolicyEnforcer::~CertPolicyEnforcer() {
224 bool CertPolicyEnforcer::DoesConformToCTEVPolicy(
225 X509Certificate
* cert
,
226 const ct::EVCertsWhitelist
* ev_whitelist
,
227 const ct::CTVerifyResult
& ct_result
,
228 const BoundNetLog
& net_log
) {
229 ComplianceDetails details
;
231 if (require_ct_for_ev_
)
232 CheckCTEVPolicyCompliance(cert
, ev_whitelist
, ct_result
, &details
);
234 NetLog::ParametersCallback net_log_callback
=
235 base::Bind(&NetLogComplianceCheckResultCallback
, base::Unretained(cert
),
236 base::Unretained(&details
));
238 net_log
.AddEvent(NetLog::TYPE_EV_CERT_CT_COMPLIANCE_CHECKED
,
241 if (!details
.ct_presence_required
)
244 if (!details
.build_timely
)
247 LogCTComplianceStatusToUMA(details
.status
);
249 if (details
.status
== CT_IN_WHITELIST
|| details
.status
== CT_ENOUGH_SCTS
)