OLD | NEW |
| (Empty) |
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 "net/cert/cert_policy_enforcer.h" | |
6 | |
7 #include <algorithm> | |
8 | |
9 #include "base/bind.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" | |
24 | |
25 namespace net { | |
26 | |
27 namespace { | |
28 | |
29 bool IsEmbeddedSCT(const scoped_refptr<ct::SignedCertificateTimestamp>& sct) { | |
30 return sct->origin == ct::SignedCertificateTimestamp::SCT_EMBEDDED; | |
31 } | |
32 | |
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) | |
38 return true; | |
39 #else | |
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 */; | |
43 #endif | |
44 } | |
45 | |
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); | |
54 | |
55 // Add any remainder as a full month. | |
56 if (exploded_expiry.day_of_month > exploded_start.day_of_month) | |
57 ++month_diff; | |
58 | |
59 return month_diff; | |
60 } | |
61 | |
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); | |
70 | |
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) | |
76 return true; | |
77 | |
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. | |
81 return false; | |
82 } | |
83 | |
84 uint32_t expiry_in_months_approx = | |
85 ApproximateMonthDifference(cert.valid_start(), cert.valid_expiry()); | |
86 | |
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 | |
89 // it qualifies. | |
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; | |
97 } else { | |
98 num_required_embedded_scts = 2; | |
99 } | |
100 | |
101 return num_embedded_scts >= num_required_embedded_scts; | |
102 } | |
103 | |
104 enum CTComplianceStatus { | |
105 CT_NOT_COMPLIANT = 0, | |
106 CT_IN_WHITELIST = 1, | |
107 CT_ENOUGH_SCTS = 2, | |
108 CT_COMPLIANCE_MAX, | |
109 }; | |
110 | |
111 const char* ComplianceStatusToString(CTComplianceStatus status) { | |
112 switch (status) { | |
113 case CT_NOT_COMPLIANT: | |
114 return "NOT_COMPLIANT"; | |
115 break; | |
116 case CT_IN_WHITELIST: | |
117 return "WHITELISTED"; | |
118 break; | |
119 case CT_ENOUGH_SCTS: | |
120 return "ENOUGH_SCTS"; | |
121 break; | |
122 case CT_COMPLIANCE_MAX: | |
123 break; | |
124 } | |
125 | |
126 return "unknown"; | |
127 } | |
128 | |
129 void LogCTComplianceStatusToUMA(CTComplianceStatus status) { | |
130 UMA_HISTOGRAM_ENUMERATION("Net.SSL_EVCertificateCTCompliance", status, | |
131 CT_COMPLIANCE_MAX); | |
132 } | |
133 | |
134 struct ComplianceDetails { | |
135 ComplianceDetails() | |
136 : ct_presence_required(false), | |
137 build_timely(false), | |
138 status(CT_NOT_COMPLIANT) {} | |
139 | |
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. | |
144 bool build_timely; | |
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; | |
150 }; | |
151 | |
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()); | |
167 } | |
168 } | |
169 return dict; | |
170 } | |
171 | |
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())); | |
178 | |
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); | |
182 | |
183 UMA_HISTOGRAM_BOOLEAN("Net.SSL_EVCertificateInWhitelist", | |
184 cert_in_ev_whitelist); | |
185 } | |
186 return cert_in_ev_whitelist; | |
187 } | |
188 | |
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; | |
194 | |
195 if (!IsBuildTimely()) | |
196 return; | |
197 result->build_timely = true; | |
198 | |
199 if (ev_whitelist && ev_whitelist->IsValid()) | |
200 result->whitelist_version = ev_whitelist->Version(); | |
201 | |
202 if (IsCertificateInWhitelist(*cert, ev_whitelist)) { | |
203 result->status = CT_IN_WHITELIST; | |
204 return; | |
205 } | |
206 | |
207 if (HasRequiredNumberOfSCTs(*cert, ct_result)) { | |
208 result->status = CT_ENOUGH_SCTS; | |
209 return; | |
210 } | |
211 | |
212 result->status = CT_NOT_COMPLIANT; | |
213 } | |
214 | |
215 } // namespace | |
216 | |
217 CertPolicyEnforcer::CertPolicyEnforcer(bool require_ct_for_ev) | |
218 : require_ct_for_ev_(require_ct_for_ev) { | |
219 } | |
220 | |
221 CertPolicyEnforcer::~CertPolicyEnforcer() { | |
222 } | |
223 | |
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; | |
230 | |
231 if (require_ct_for_ev_) | |
232 CheckCTEVPolicyCompliance(cert, ev_whitelist, ct_result, &details); | |
233 | |
234 NetLog::ParametersCallback net_log_callback = | |
235 base::Bind(&NetLogComplianceCheckResultCallback, base::Unretained(cert), | |
236 base::Unretained(&details)); | |
237 | |
238 net_log.AddEvent(NetLog::TYPE_EV_CERT_CT_COMPLIANCE_CHECKED, | |
239 net_log_callback); | |
240 | |
241 if (!details.ct_presence_required) | |
242 return true; | |
243 | |
244 if (!details.build_timely) | |
245 return false; | |
246 | |
247 LogCTComplianceStatusToUMA(details.status); | |
248 | |
249 if (details.status == CT_IN_WHITELIST || details.status == CT_ENOUGH_SCTS) | |
250 return true; | |
251 | |
252 return false; | |
253 } | |
254 | |
255 } // namespace net | |
OLD | NEW |