OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "net/base/transport_security_state.h" | 5 #include "net/base/transport_security_state.h" |
6 | 6 |
7 #if defined(USE_OPENSSL) | 7 #if defined(USE_OPENSSL) |
8 #include <openssl/ecdsa.h> | 8 #include <openssl/ecdsa.h> |
9 #include <openssl/ssl.h> | 9 #include <openssl/ssl.h> |
10 #else // !defined(USE_OPENSSL) | 10 #else // !defined(USE_OPENSSL) |
11 #include <cryptohi.h> | 11 #include <cryptohi.h> |
12 #include <hasht.h> | 12 #include <hasht.h> |
13 #include <keyhi.h> | 13 #include <keyhi.h> |
14 #include <pk11pub.h> | 14 #include <pk11pub.h> |
15 #include <nspr.h> | 15 #include <nspr.h> |
16 #endif | 16 #endif |
17 | 17 |
18 #include <algorithm> | 18 #include <algorithm> |
19 | 19 |
20 #include "base/base64.h" | 20 #include "base/base64.h" |
21 #include "base/build_time.h" | 21 #include "base/build_time.h" |
22 #include "base/logging.h" | 22 #include "base/logging.h" |
23 #include "base/memory/scoped_ptr.h" | 23 #include "base/memory/scoped_ptr.h" |
24 #include "base/metrics/histogram.h" | 24 #include "base/metrics/histogram.h" |
25 #include "base/sha1.h" | 25 #include "base/sha1.h" |
26 #include "base/string_number_conversions.h" | |
27 #include "base/string_util.h" | 26 #include "base/string_util.h" |
28 #include "base/time.h" | 27 #include "base/time.h" |
29 #include "base/utf_string_conversions.h" | |
30 #include "base/values.h" | |
31 #include "crypto/sha2.h" | 28 #include "crypto/sha2.h" |
32 #include "googleurl/src/gurl.h" | 29 #include "googleurl/src/gurl.h" |
33 #include "net/base/dns_util.h" | 30 #include "net/base/dns_util.h" |
34 #include "net/base/ssl_info.h" | 31 #include "net/base/ssl_info.h" |
35 #include "net/base/x509_cert_types.h" | 32 #include "net/base/transport_security_state_preload.h" |
36 #include "net/base/x509_certificate.h" | 33 #include "net/base/x509_certificate.h" |
37 #include "net/http/http_security_headers.h" | 34 #include "net/http/http_security_headers.h" |
38 | 35 |
39 #if defined(USE_OPENSSL) | 36 #if defined(USE_OPENSSL) |
40 #include "crypto/openssl_util.h" | 37 #include "crypto/openssl_util.h" |
41 #endif | 38 #endif |
42 | 39 |
43 namespace net { | 40 namespace net { |
44 | 41 |
45 namespace { | 42 namespace { |
46 | 43 |
47 std::string HashesToBase64String(const HashValueVector& hashes) { | 44 std::string HashesToBase64String(const HashValueVector& hashes) { |
48 std::string str; | 45 std::string str; |
49 for (size_t i = 0; i != hashes.size(); ++i) { | 46 for (size_t i = 0; i != hashes.size(); ++i) { |
50 if (i != 0) | 47 if (i != 0) |
51 str += ","; | 48 str += ","; |
52 str += hashes[i].ToString(); | 49 str += hashes[i].ToString(); |
53 } | 50 } |
54 return str; | 51 return str; |
55 } | 52 } |
56 | 53 |
57 std::string HashHost(const std::string& canonicalized_host) { | |
58 char hashed[crypto::kSHA256Length]; | |
59 crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); | |
60 return std::string(hashed, sizeof(hashed)); | |
61 } | |
62 | |
63 // Returns true if the intersection of |a| and |b| is not empty. If either | 54 // Returns true if the intersection of |a| and |b| is not empty. If either |
64 // |a| or |b| is empty, returns false. | 55 // |a| or |b| is empty, returns false. |
65 bool HashesIntersect(const HashValueVector& a, | 56 bool HashesIntersect(const HashValueVector& a, |
66 const HashValueVector& b) { | 57 const HashValueVector& b) { |
67 for (HashValueVector::const_iterator i = a.begin(); i != a.end(); ++i) { | 58 for (HashValueVector::const_iterator i = a.begin(); i != a.end(); ++i) { |
68 HashValueVector::const_iterator j = | 59 HashValueVector::const_iterator j = |
69 std::find_if(b.begin(), b.end(), HashValuesEqual(*i)); | 60 std::find_if(b.begin(), b.end(), HashValuesEqual(*i)); |
70 if (j != b.end()) | 61 if (j != b.end()) |
71 return true; | 62 return true; |
72 } | 63 } |
73 return false; | 64 return false; |
74 } | 65 } |
75 | 66 |
76 bool AddHash(const char* sha1_hash, | 67 std::string HashHost(const std::string& host) { |
77 HashValueVector* out) { | 68 std::string lowercase = StringToLowerASCII(host); |
78 HashValue hash(HASH_VALUE_SHA1); | 69 std::string old_style_canonicalized_name; |
79 memcpy(hash.data(), sha1_hash, hash.size()); | 70 if (!DNSDomainFromDot(lowercase, &old_style_canonicalized_name)) |
80 out->push_back(hash); | 71 return std::string(); |
72 | |
73 char hashed[crypto::kSHA256Length]; | |
74 crypto::SHA256HashString(old_style_canonicalized_name, hashed, | |
75 sizeof(hashed)); | |
76 return std::string(hashed, sizeof(hashed)); | |
77 } | |
Ryan Sleevi
2013/04/29 22:06:14
Seems like moving this function back to its origin
unsafe
2013/04/30 10:42:57
Done.
| |
78 | |
79 // Iterate over ("www.example.com", "example.com", "com") | |
80 struct DomainNameIterator { | |
81 explicit DomainNameIterator(const std::string& host) { | |
82 name_ = StringToLowerASCII(host); | |
83 index_ = 0; | |
84 } | |
85 | |
86 bool AtEnd() { | |
87 return index_ == name_.length(); | |
88 } | |
89 | |
90 // Advance to NUL char, or after the next '.' | |
91 void Advance() { | |
92 if (AtEnd()) | |
93 return; | |
94 for (index_++; name_[index_] != '.' && name_[index_] != 0; ++index_); | |
95 if (name_[index_] == '.') | |
96 index_++; | |
97 } | |
98 | |
99 std::string GetName() { | |
100 return name_.substr(index_); | |
101 } | |
102 | |
103 bool IsFullHostname() { | |
104 return index_ == 0; | |
105 } | |
106 | |
107 std::string name_; // The full hostname, canonicalized to lowercase | |
108 size_t index_; // Index into name_ | |
109 }; | |
110 | |
111 // Template functions for maps of DynamicEntries (or subclasses) | |
112 | |
113 #define DynamicEntryMapConstIter \ | |
114 typename std::map<std::string, T>::const_iterator | |
115 #define DynamicEntryMapIter \ | |
Ryan Sleevi
2013/04/29 22:06:14
I still really dislike these #defines, even in the
unsafe
2013/04/30 10:42:57
Done.
| |
116 typename std::map<std::string, T>::iterator | |
117 | |
118 template <typename T> | |
119 bool GetDynamicEntry(const std::map<std::string, T>& entries, | |
120 const base::Time& now, const std::string& hashed_host, | |
121 bool is_full_hostname, T* result_entry) { | |
Ryan Sleevi
2013/04/29 22:06:14
style nit: one param per line for multi-line funct
unsafe
2013/04/30 10:42:57
Done.
| |
122 // Find the entry, and return if relevant and nonexpired | |
123 DynamicEntryMapConstIter find_iter = entries.find(hashed_host); | |
124 if (find_iter != entries.end()) { | |
Ryan Sleevi
2013/04/29 22:06:14
Prefer early returns rather than nested conditiona
unsafe
2013/04/30 10:42:57
Done.
| |
125 const T& found_entry = find_iter->second; | |
126 if ((is_full_hostname || found_entry.include_subdomains_) && | |
127 found_entry.expiry_ > now) { | |
128 *result_entry = found_entry; | |
129 return true; | |
130 } | |
131 } | |
132 return false; | |
133 } | |
134 | |
135 template<typename T> | |
136 bool AddDynamicEntry(std::map<std::string, T>& entries, | |
137 const std::string& hashed_host, const T& new_entry, | |
138 TransportSecurityState* state) { | |
Ryan Sleevi
2013/04/29 22:06:14
design: Rather than passing |state|, which is quit
unsafe
2013/04/30 10:42:57
I tried that. It could be done, but it makes the
| |
139 bool dirty = false; | |
Ryan Sleevi
2013/04/29 22:06:14
seems like |dirty| is entirely unnecessary - you a
unsafe
2013/04/30 10:42:57
Done.
| |
140 DynamicEntryMapIter find_iter = entries.find(hashed_host); | |
141 if (find_iter != entries.end()) { | |
142 // Leave 'created' unchanged | |
143 T& found_entry = find_iter->second; | |
144 found_entry.expiry_ = new_entry.expiry_; | |
145 found_entry.include_subdomains_ = new_entry.include_subdomains_; | |
146 dirty = true; | |
147 } else { | |
Ryan Sleevi
2013/04/29 22:06:14
style nit: Prefer error handling up front, followe
unsafe
2013/04/30 10:42:57
It's not really an error, just 2 cases? Does it m
| |
148 entries[hashed_host] = new_entry; | |
149 dirty = true; | |
150 } | |
151 if (dirty) | |
152 state->StateIsDirty(); | |
81 return true; | 153 return true; |
82 } | 154 } |
83 | 155 |
156 template<typename T> | |
157 bool DeleteDynamicEntry(std::map<std::string, T>& entries, | |
158 const std::string& hashed_host, | |
159 TransportSecurityState* state) { | |
160 DynamicEntryMapIter find_iter = entries.find(hashed_host); | |
161 if (find_iter != entries.end()) { | |
162 entries.erase(find_iter); | |
163 state->StateIsDirty(); | |
164 return true; | |
165 } | |
166 return false; | |
167 } | |
168 | |
169 template<typename T> | |
170 void DeleteDynamicEntriesSince(std::map<std::string, T>& entries, | |
171 const base::Time& time, | |
172 TransportSecurityState* state) { | |
173 bool dirty = false; | |
174 DynamicEntryMapIter iter = entries.begin(); | |
175 while (iter != entries.end()) { | |
176 if (iter->second.created_ >= time) { | |
177 entries.erase(iter++); | |
178 dirty = true; | |
179 } else { | |
180 iter++; | |
181 } | |
182 } | |
183 if (dirty) | |
184 state->StateIsDirty(); | |
185 } | |
186 | |
84 } // namespace | 187 } // namespace |
85 | 188 |
189 | |
86 TransportSecurityState::TransportSecurityState() | 190 TransportSecurityState::TransportSecurityState() |
87 : delegate_(NULL) { | 191 : delegate_(NULL) { |
88 } | 192 } |
89 | 193 |
90 TransportSecurityState::Iterator::Iterator(const TransportSecurityState& state) | 194 TransportSecurityState::~TransportSecurityState() {} |
91 : iterator_(state.enabled_hosts_.begin()), | |
92 end_(state.enabled_hosts_.end()) { | |
93 } | |
94 | |
95 TransportSecurityState::Iterator::~Iterator() {} | |
96 | 195 |
97 void TransportSecurityState::SetDelegate( | 196 void TransportSecurityState::SetDelegate( |
98 TransportSecurityState::Delegate* delegate) { | 197 TransportSecurityState::Delegate* delegate) { |
99 delegate_ = delegate; | 198 delegate_ = delegate; |
100 } | 199 } |
101 | 200 |
102 void TransportSecurityState::EnableHost(const std::string& host, | 201 void TransportSecurityState::ClearDynamicData() { |
103 const DomainState& state) { | 202 hsts_entries_.clear(); |
203 hpkp_entries_.clear(); | |
204 } | |
205 | |
206 void TransportSecurityState::DeleteAllDynamicDataSince(const base::Time& time) { | |
104 DCHECK(CalledOnValidThread()); | 207 DCHECK(CalledOnValidThread()); |
105 | 208 DeleteDynamicEntriesSince(hsts_entries_, time, this); |
106 const std::string canonicalized_host = CanonicalizeHost(host); | 209 DeleteDynamicEntriesSince(hpkp_entries_, time, this); |
Ryan Sleevi
2013/04/29 22:06:14
For example, why the "bool* dirty" is nice, is thi
unsafe
2013/04/30 10:42:57
I think the persister doesn't kick in right away,
Ryan Sleevi
2013/04/30 19:55:09
That's a wrong coupling of layers though. This cod
| |
107 if (canonicalized_host.empty()) | |
108 return; | |
109 | |
110 DomainState existing_state; | |
111 | |
112 // Use the original creation date if we already have this host. (But note | |
113 // that statically-defined states have no |created| date. Therefore, we do | |
114 // not bother to search the SNI-only static states.) | |
115 DomainState state_copy(state); | |
116 if (GetDomainState(host, false /* sni_enabled */, &existing_state) && | |
117 !existing_state.created.is_null()) { | |
118 state_copy.created = existing_state.created; | |
119 } | |
120 | |
121 // No need to store this value since it is redundant. (|canonicalized_host| | |
122 // is the map key.) | |
123 state_copy.domain.clear(); | |
124 | |
125 enabled_hosts_[HashHost(canonicalized_host)] = state_copy; | |
126 DirtyNotify(); | |
127 } | 210 } |
128 | 211 |
129 bool TransportSecurityState::DeleteDynamicDataForHost(const std::string& host) { | 212 bool TransportSecurityState::DeleteDynamicDataForHost(const std::string& host) { |
130 DCHECK(CalledOnValidThread()); | 213 DCHECK(CalledOnValidThread()); |
131 | 214 bool deleted_hsts = DeleteHSTS(host); |
132 const std::string canonicalized_host = CanonicalizeHost(host); | 215 bool deleted_hpkp = DeleteHPKP(host); |
133 if (canonicalized_host.empty()) | 216 return deleted_hsts || deleted_hpkp; |
134 return false; | |
135 | |
136 DomainStateMap::iterator i = enabled_hosts_.find( | |
137 HashHost(canonicalized_host)); | |
138 if (i != enabled_hosts_.end()) { | |
139 enabled_hosts_.erase(i); | |
140 DirtyNotify(); | |
141 return true; | |
142 } | |
143 return false; | |
144 } | 217 } |
145 | 218 |
146 bool TransportSecurityState::GetDomainState(const std::string& host, | 219 bool TransportSecurityState::GetDomainState(const std::string& host, |
147 bool sni_enabled, | 220 bool sni_enabled, |
148 DomainState* result) { | 221 DomainState* result) const { |
149 DCHECK(CalledOnValidThread()); | 222 DCHECK(CalledOnValidThread()); |
150 | 223 bool found = false; |
151 DomainState state; | 224 const base::Time now = base::Time::Now(); |
152 const std::string canonicalized_host = CanonicalizeHost(host); | 225 DomainState dynamic_state; |
153 if (canonicalized_host.empty()) | 226 |
227 found = GetPreloadDomainState(sni_enabled, now, host, result); | |
228 found = GetDynamicDomainState(now, host, &dynamic_state) || found; | |
229 | |
230 // Merge dynamic state into preload state | |
231 // Currently, HSTS and HPKP are set if either state has them set. | |
232 // However, if both states have HPKP set, the preload pins take precedence. | |
233 // This behavior may change (e.g. for the most-recent to take priority). | |
234 if (!result->should_upgrade_ && dynamic_state.should_upgrade_) | |
235 result->should_upgrade_ = true; | |
236 if (!result->has_public_key_pins_ && dynamic_state.has_public_key_pins_) { | |
237 result->has_public_key_pins_ = true; | |
238 result->public_key_pins_good_hashes_ = | |
239 dynamic_state.public_key_pins_good_hashes_; | |
240 } | |
241 return found; | |
242 } | |
243 | |
244 bool TransportSecurityState::GetDynamicDomainState(const base::Time& now, | |
245 const std::string& host, | |
246 DomainState* result) const { | |
247 DynamicEntry hsts_entry; | |
248 HPKPEntry hpkp_entry; | |
249 // Iterate over 'www.example.com", 'example.com", "com" | |
250 for (DomainNameIterator iter(host); !iter.AtEnd(); iter.Advance()) { | |
251 std::string hashed_host = HashHost(iter.GetName()); | |
252 bool is_full_hostname = iter.IsFullHostname(); | |
253 | |
254 // Get HSTS data from map | |
255 if (!result->should_upgrade_ && | |
256 GetDynamicEntry(hsts_entries_, now, hashed_host, is_full_hostname, | |
257 &hsts_entry)) { | |
258 result->should_upgrade_ = true; | |
259 } | |
260 | |
261 // Get HPKP data from map | |
262 if (!result->has_public_key_pins_ && | |
263 GetDynamicEntry(hpkp_entries_, now, hashed_host, is_full_hostname, | |
264 &hpkp_entry)) { | |
265 result->has_public_key_pins_ = true; | |
266 result->public_key_pins_good_hashes_ = hpkp_entry.good_hashes_; | |
267 } | |
268 | |
269 // If we've got all possible data, exit early | |
270 if (result->should_upgrade_ && result->has_public_key_pins_) | |
271 return true; | |
272 } | |
273 return result->should_upgrade_ || result->has_public_key_pins_; | |
274 } | |
275 | |
276 bool TransportSecurityState::GetPreloadDomainState(bool sni_enabled, | |
277 const base::Time& now, | |
278 const std::string& host, | |
279 DomainState* result) const { | |
280 #if defined(PRELOADS_PRESENT) | |
281 const PreloadEntry* entries = kPreloadedEntries; | |
282 size_t num_entries = kNumPreloaded; | |
283 | |
284 if (!IsBuildTimely()) | |
154 return false; | 285 return false; |
155 | 286 |
156 bool has_preload = GetStaticDomainState(canonicalized_host, sni_enabled, | 287 for (int count = 0; count < 2; count++) { |
157 &state); | 288 // If sni_enabled, then scan through SNI entries (if necessary) |
158 std::string canonicalized_preload = CanonicalizeHost(state.domain); | 289 if (count == 1) { |
159 | 290 if (!sni_enabled) |
160 base::Time current_time(base::Time::Now()); | 291 break; |
161 | 292 entries = kPreloadedEntriesSNI; |
162 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | 293 num_entries = kNumPreloadedSNI; |
163 std::string host_sub_chunk(&canonicalized_host[i], | 294 } |
164 canonicalized_host.size() - i); | 295 |
165 // Exact match of a preload always wins. | 296 for (DomainNameIterator iter(host); !iter.AtEnd(); iter.Advance()) { |
166 if (has_preload && host_sub_chunk == canonicalized_preload) { | 297 std::string name = iter.GetName(); |
167 *result = state; | 298 for (size_t index = 0; index < num_entries; index++) { |
168 return true; | 299 const PreloadEntry& entry = entries[index]; |
169 } | 300 |
170 | 301 // If we find a relevant preload entry, populate the |
171 DomainStateMap::iterator j = | 302 // entire DomainState from it and return |
172 enabled_hosts_.find(HashHost(host_sub_chunk)); | 303 if (entry.length == name.size() && |
173 if (j == enabled_hosts_.end()) | 304 memcmp(entry.dns_name, name.data(), entry.length) == 0 && |
Ryan Sleevi
2013/04/29 22:06:14
seems like this memcmp should be moved to the last
unsafe
2013/04/30 10:42:57
Done.
| |
174 continue; | 305 (iter.IsFullHostname() || entry.include_subdomains)) { |
175 | 306 if (entry.https_required) |
176 if (current_time > j->second.upgrade_expiry && | 307 result->should_upgrade_ = true; |
177 current_time > j->second.dynamic_spki_hashes_expiry) { | 308 |
178 enabled_hosts_.erase(j); | 309 if (entry.pins.required_hashes || entry.pins.excluded_hashes) |
179 DirtyNotify(); | 310 result->has_public_key_pins_ = true; |
180 continue; | 311 HashValue hash(HASH_VALUE_SHA1); |
181 } | 312 if (entry.pins.required_hashes) { |
182 | 313 const char* const* sha1_hashes = entry.pins.required_hashes; |
183 state = j->second; | 314 while (*sha1_hashes) { |
184 state.domain = DNSDomainToString(host_sub_chunk); | 315 memcpy(hash.data(), *sha1_hashes, hash.size()); |
185 | 316 result->public_key_pins_good_hashes_.push_back(hash); |
186 // Succeed if we matched the domain exactly or if subdomain matches are | 317 sha1_hashes++; |
187 // allowed. | 318 } |
188 if (i == 0 || j->second.include_subdomains) { | |
189 *result = state; | |
190 return true; | |
191 } | |
192 | |
193 return false; | |
194 } | |
195 | |
196 return false; | |
197 } | |
198 | |
199 void TransportSecurityState::ClearDynamicData() { | |
200 enabled_hosts_.clear(); | |
201 } | |
202 | |
203 void TransportSecurityState::DeleteAllDynamicDataSince(const base::Time& time) { | |
204 DCHECK(CalledOnValidThread()); | |
205 | |
206 bool dirtied = false; | |
207 | |
208 DomainStateMap::iterator i = enabled_hosts_.begin(); | |
209 while (i != enabled_hosts_.end()) { | |
210 if (i->second.created >= time) { | |
211 dirtied = true; | |
212 enabled_hosts_.erase(i++); | |
213 } else { | |
214 i++; | |
215 } | |
216 } | |
217 | |
218 if (dirtied) | |
219 DirtyNotify(); | |
220 } | |
221 | |
222 TransportSecurityState::~TransportSecurityState() {} | |
223 | |
224 void TransportSecurityState::DirtyNotify() { | |
225 DCHECK(CalledOnValidThread()); | |
226 | |
227 if (delegate_) | |
228 delegate_->StateIsDirty(this); | |
229 } | |
230 | |
231 // static | |
232 std::string TransportSecurityState::CanonicalizeHost(const std::string& host) { | |
233 // We cannot perform the operations as detailed in the spec here as |host| | |
234 // has already undergone IDN processing before it reached us. Thus, we check | |
235 // that there are no invalid characters in the host and lowercase the result. | |
236 | |
237 std::string new_host; | |
238 if (!DNSDomainFromDot(host, &new_host)) { | |
239 // DNSDomainFromDot can fail if any label is > 63 bytes or if the whole | |
240 // name is >255 bytes. However, search terms can have those properties. | |
241 return std::string(); | |
242 } | |
243 | |
244 for (size_t i = 0; new_host[i]; i += new_host[i] + 1) { | |
245 const unsigned label_length = static_cast<unsigned>(new_host[i]); | |
246 if (!label_length) | |
247 break; | |
248 | |
249 for (size_t j = 0; j < label_length; ++j) { | |
250 // RFC 3490, 4.1, step 3 | |
251 if (!IsSTD3ASCIIValidCharacter(new_host[i + 1 + j])) | |
252 return std::string(); | |
253 | |
254 new_host[i + 1 + j] = tolower(new_host[i + 1 + j]); | |
255 } | |
256 | |
257 // step 3(b) | |
258 if (new_host[i + 1] == '-' || | |
259 new_host[i + label_length] == '-') { | |
260 return std::string(); | |
261 } | |
262 } | |
263 | |
264 return new_host; | |
265 } | |
266 | |
267 // |ReportUMAOnPinFailure| uses these to report which domain was associated | |
268 // with the public key pinning failure. | |
269 // | |
270 // DO NOT CHANGE THE ORDERING OF THESE NAMES OR REMOVE ANY OF THEM. Add new | |
271 // domains at the END of the listing (but before DOMAIN_NUM_EVENTS). | |
272 enum SecondLevelDomainName { | |
273 DOMAIN_NOT_PINNED, | |
274 | |
275 DOMAIN_GOOGLE_COM, | |
276 DOMAIN_ANDROID_COM, | |
277 DOMAIN_GOOGLE_ANALYTICS_COM, | |
278 DOMAIN_GOOGLEPLEX_COM, | |
279 DOMAIN_YTIMG_COM, | |
280 DOMAIN_GOOGLEUSERCONTENT_COM, | |
281 DOMAIN_YOUTUBE_COM, | |
282 DOMAIN_GOOGLEAPIS_COM, | |
283 DOMAIN_GOOGLEADSERVICES_COM, | |
284 DOMAIN_GOOGLECODE_COM, | |
285 DOMAIN_APPSPOT_COM, | |
286 DOMAIN_GOOGLESYNDICATION_COM, | |
287 DOMAIN_DOUBLECLICK_NET, | |
288 DOMAIN_GSTATIC_COM, | |
289 DOMAIN_GMAIL_COM, | |
290 DOMAIN_GOOGLEMAIL_COM, | |
291 DOMAIN_GOOGLEGROUPS_COM, | |
292 | |
293 DOMAIN_TORPROJECT_ORG, | |
294 | |
295 DOMAIN_TWITTER_COM, | |
296 DOMAIN_TWIMG_COM, | |
297 | |
298 DOMAIN_AKAMAIHD_NET, | |
299 | |
300 DOMAIN_TOR2WEB_ORG, | |
301 | |
302 DOMAIN_YOUTU_BE, | |
303 DOMAIN_GOOGLECOMMERCE_COM, | |
304 DOMAIN_URCHIN_COM, | |
305 DOMAIN_GOO_GL, | |
306 DOMAIN_G_CO, | |
307 DOMAIN_GOOGLE_AC, | |
308 DOMAIN_GOOGLE_AD, | |
309 DOMAIN_GOOGLE_AE, | |
310 DOMAIN_GOOGLE_AF, | |
311 DOMAIN_GOOGLE_AG, | |
312 DOMAIN_GOOGLE_AM, | |
313 DOMAIN_GOOGLE_AS, | |
314 DOMAIN_GOOGLE_AT, | |
315 DOMAIN_GOOGLE_AZ, | |
316 DOMAIN_GOOGLE_BA, | |
317 DOMAIN_GOOGLE_BE, | |
318 DOMAIN_GOOGLE_BF, | |
319 DOMAIN_GOOGLE_BG, | |
320 DOMAIN_GOOGLE_BI, | |
321 DOMAIN_GOOGLE_BJ, | |
322 DOMAIN_GOOGLE_BS, | |
323 DOMAIN_GOOGLE_BY, | |
324 DOMAIN_GOOGLE_CA, | |
325 DOMAIN_GOOGLE_CAT, | |
326 DOMAIN_GOOGLE_CC, | |
327 DOMAIN_GOOGLE_CD, | |
328 DOMAIN_GOOGLE_CF, | |
329 DOMAIN_GOOGLE_CG, | |
330 DOMAIN_GOOGLE_CH, | |
331 DOMAIN_GOOGLE_CI, | |
332 DOMAIN_GOOGLE_CL, | |
333 DOMAIN_GOOGLE_CM, | |
334 DOMAIN_GOOGLE_CN, | |
335 DOMAIN_CO_AO, | |
336 DOMAIN_CO_BW, | |
337 DOMAIN_CO_CK, | |
338 DOMAIN_CO_CR, | |
339 DOMAIN_CO_HU, | |
340 DOMAIN_CO_ID, | |
341 DOMAIN_CO_IL, | |
342 DOMAIN_CO_IM, | |
343 DOMAIN_CO_IN, | |
344 DOMAIN_CO_JE, | |
345 DOMAIN_CO_JP, | |
346 DOMAIN_CO_KE, | |
347 DOMAIN_CO_KR, | |
348 DOMAIN_CO_LS, | |
349 DOMAIN_CO_MA, | |
350 DOMAIN_CO_MZ, | |
351 DOMAIN_CO_NZ, | |
352 DOMAIN_CO_TH, | |
353 DOMAIN_CO_TZ, | |
354 DOMAIN_CO_UG, | |
355 DOMAIN_CO_UK, | |
356 DOMAIN_CO_UZ, | |
357 DOMAIN_CO_VE, | |
358 DOMAIN_CO_VI, | |
359 DOMAIN_CO_ZA, | |
360 DOMAIN_CO_ZM, | |
361 DOMAIN_CO_ZW, | |
362 DOMAIN_COM_AF, | |
363 DOMAIN_COM_AG, | |
364 DOMAIN_COM_AI, | |
365 DOMAIN_COM_AR, | |
366 DOMAIN_COM_AU, | |
367 DOMAIN_COM_BD, | |
368 DOMAIN_COM_BH, | |
369 DOMAIN_COM_BN, | |
370 DOMAIN_COM_BO, | |
371 DOMAIN_COM_BR, | |
372 DOMAIN_COM_BY, | |
373 DOMAIN_COM_BZ, | |
374 DOMAIN_COM_CN, | |
375 DOMAIN_COM_CO, | |
376 DOMAIN_COM_CU, | |
377 DOMAIN_COM_CY, | |
378 DOMAIN_COM_DO, | |
379 DOMAIN_COM_EC, | |
380 DOMAIN_COM_EG, | |
381 DOMAIN_COM_ET, | |
382 DOMAIN_COM_FJ, | |
383 DOMAIN_COM_GE, | |
384 DOMAIN_COM_GH, | |
385 DOMAIN_COM_GI, | |
386 DOMAIN_COM_GR, | |
387 DOMAIN_COM_GT, | |
388 DOMAIN_COM_HK, | |
389 DOMAIN_COM_IQ, | |
390 DOMAIN_COM_JM, | |
391 DOMAIN_COM_JO, | |
392 DOMAIN_COM_KH, | |
393 DOMAIN_COM_KW, | |
394 DOMAIN_COM_LB, | |
395 DOMAIN_COM_LY, | |
396 DOMAIN_COM_MT, | |
397 DOMAIN_COM_MX, | |
398 DOMAIN_COM_MY, | |
399 DOMAIN_COM_NA, | |
400 DOMAIN_COM_NF, | |
401 DOMAIN_COM_NG, | |
402 DOMAIN_COM_NI, | |
403 DOMAIN_COM_NP, | |
404 DOMAIN_COM_NR, | |
405 DOMAIN_COM_OM, | |
406 DOMAIN_COM_PA, | |
407 DOMAIN_COM_PE, | |
408 DOMAIN_COM_PH, | |
409 DOMAIN_COM_PK, | |
410 DOMAIN_COM_PL, | |
411 DOMAIN_COM_PR, | |
412 DOMAIN_COM_PY, | |
413 DOMAIN_COM_QA, | |
414 DOMAIN_COM_RU, | |
415 DOMAIN_COM_SA, | |
416 DOMAIN_COM_SB, | |
417 DOMAIN_COM_SG, | |
418 DOMAIN_COM_SL, | |
419 DOMAIN_COM_SV, | |
420 DOMAIN_COM_TJ, | |
421 DOMAIN_COM_TN, | |
422 DOMAIN_COM_TR, | |
423 DOMAIN_COM_TW, | |
424 DOMAIN_COM_UA, | |
425 DOMAIN_COM_UY, | |
426 DOMAIN_COM_VC, | |
427 DOMAIN_COM_VE, | |
428 DOMAIN_COM_VN, | |
429 DOMAIN_GOOGLE_CV, | |
430 DOMAIN_GOOGLE_CZ, | |
431 DOMAIN_GOOGLE_DE, | |
432 DOMAIN_GOOGLE_DJ, | |
433 DOMAIN_GOOGLE_DK, | |
434 DOMAIN_GOOGLE_DM, | |
435 DOMAIN_GOOGLE_DZ, | |
436 DOMAIN_GOOGLE_EE, | |
437 DOMAIN_GOOGLE_ES, | |
438 DOMAIN_GOOGLE_FI, | |
439 DOMAIN_GOOGLE_FM, | |
440 DOMAIN_GOOGLE_FR, | |
441 DOMAIN_GOOGLE_GA, | |
442 DOMAIN_GOOGLE_GE, | |
443 DOMAIN_GOOGLE_GG, | |
444 DOMAIN_GOOGLE_GL, | |
445 DOMAIN_GOOGLE_GM, | |
446 DOMAIN_GOOGLE_GP, | |
447 DOMAIN_GOOGLE_GR, | |
448 DOMAIN_GOOGLE_GY, | |
449 DOMAIN_GOOGLE_HK, | |
450 DOMAIN_GOOGLE_HN, | |
451 DOMAIN_GOOGLE_HR, | |
452 DOMAIN_GOOGLE_HT, | |
453 DOMAIN_GOOGLE_HU, | |
454 DOMAIN_GOOGLE_IE, | |
455 DOMAIN_GOOGLE_IM, | |
456 DOMAIN_GOOGLE_INFO, | |
457 DOMAIN_GOOGLE_IQ, | |
458 DOMAIN_GOOGLE_IS, | |
459 DOMAIN_GOOGLE_IT, | |
460 DOMAIN_IT_AO, | |
461 DOMAIN_GOOGLE_JE, | |
462 DOMAIN_GOOGLE_JO, | |
463 DOMAIN_GOOGLE_JOBS, | |
464 DOMAIN_GOOGLE_JP, | |
465 DOMAIN_GOOGLE_KG, | |
466 DOMAIN_GOOGLE_KI, | |
467 DOMAIN_GOOGLE_KZ, | |
468 DOMAIN_GOOGLE_LA, | |
469 DOMAIN_GOOGLE_LI, | |
470 DOMAIN_GOOGLE_LK, | |
471 DOMAIN_GOOGLE_LT, | |
472 DOMAIN_GOOGLE_LU, | |
473 DOMAIN_GOOGLE_LV, | |
474 DOMAIN_GOOGLE_MD, | |
475 DOMAIN_GOOGLE_ME, | |
476 DOMAIN_GOOGLE_MG, | |
477 DOMAIN_GOOGLE_MK, | |
478 DOMAIN_GOOGLE_ML, | |
479 DOMAIN_GOOGLE_MN, | |
480 DOMAIN_GOOGLE_MS, | |
481 DOMAIN_GOOGLE_MU, | |
482 DOMAIN_GOOGLE_MV, | |
483 DOMAIN_GOOGLE_MW, | |
484 DOMAIN_GOOGLE_NE, | |
485 DOMAIN_NE_JP, | |
486 DOMAIN_GOOGLE_NET, | |
487 DOMAIN_GOOGLE_NL, | |
488 DOMAIN_GOOGLE_NO, | |
489 DOMAIN_GOOGLE_NR, | |
490 DOMAIN_GOOGLE_NU, | |
491 DOMAIN_OFF_AI, | |
492 DOMAIN_GOOGLE_PK, | |
493 DOMAIN_GOOGLE_PL, | |
494 DOMAIN_GOOGLE_PN, | |
495 DOMAIN_GOOGLE_PS, | |
496 DOMAIN_GOOGLE_PT, | |
497 DOMAIN_GOOGLE_RO, | |
498 DOMAIN_GOOGLE_RS, | |
499 DOMAIN_GOOGLE_RU, | |
500 DOMAIN_GOOGLE_RW, | |
501 DOMAIN_GOOGLE_SC, | |
502 DOMAIN_GOOGLE_SE, | |
503 DOMAIN_GOOGLE_SH, | |
504 DOMAIN_GOOGLE_SI, | |
505 DOMAIN_GOOGLE_SK, | |
506 DOMAIN_GOOGLE_SM, | |
507 DOMAIN_GOOGLE_SN, | |
508 DOMAIN_GOOGLE_SO, | |
509 DOMAIN_GOOGLE_ST, | |
510 DOMAIN_GOOGLE_TD, | |
511 DOMAIN_GOOGLE_TG, | |
512 DOMAIN_GOOGLE_TK, | |
513 DOMAIN_GOOGLE_TL, | |
514 DOMAIN_GOOGLE_TM, | |
515 DOMAIN_GOOGLE_TN, | |
516 DOMAIN_GOOGLE_TO, | |
517 DOMAIN_GOOGLE_TP, | |
518 DOMAIN_GOOGLE_TT, | |
519 DOMAIN_GOOGLE_US, | |
520 DOMAIN_GOOGLE_UZ, | |
521 DOMAIN_GOOGLE_VG, | |
522 DOMAIN_GOOGLE_VU, | |
523 DOMAIN_GOOGLE_WS, | |
524 | |
525 DOMAIN_CHROMIUM_ORG, | |
526 | |
527 DOMAIN_CRYPTO_CAT, | |
528 | |
529 // Boundary value for UMA_HISTOGRAM_ENUMERATION: | |
530 DOMAIN_NUM_EVENTS | |
531 }; | |
532 | |
533 // PublicKeyPins contains a number of SubjectPublicKeyInfo hashes for a site. | |
534 // The validated certificate chain for the site must not include any of | |
535 // |excluded_hashes| and must include one or more of |required_hashes|. | |
536 struct PublicKeyPins { | |
537 const char* const* required_hashes; | |
538 const char* const* excluded_hashes; | |
539 }; | |
540 | |
541 struct HSTSPreload { | |
542 uint8 length; | |
543 bool include_subdomains; | |
544 char dns_name[34]; | |
545 bool https_required; | |
546 PublicKeyPins pins; | |
547 SecondLevelDomainName second_level_domain_name; | |
548 }; | |
549 | |
550 static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, | |
551 const std::string& canonicalized_host, size_t i, | |
552 TransportSecurityState::DomainState* out, bool* ret) { | |
553 for (size_t j = 0; j < num_entries; j++) { | |
554 if (entries[j].length == canonicalized_host.size() - i && | |
555 memcmp(entries[j].dns_name, &canonicalized_host[i], | |
556 entries[j].length) == 0) { | |
557 if (!entries[j].include_subdomains && i != 0) { | |
558 *ret = false; | |
559 } else { | |
560 out->include_subdomains = entries[j].include_subdomains; | |
561 *ret = true; | |
562 if (!entries[j].https_required) | |
563 out->upgrade_mode = TransportSecurityState::DomainState::MODE_DEFAULT; | |
564 if (entries[j].pins.required_hashes) { | |
565 const char* const* sha1_hash = entries[j].pins.required_hashes; | |
566 while (*sha1_hash) { | |
567 AddHash(*sha1_hash, &out->static_spki_hashes); | |
568 sha1_hash++; | |
569 } | 319 } |
570 } | 320 if (entry.pins.excluded_hashes) { |
571 if (entries[j].pins.excluded_hashes) { | 321 const char* const* sha1_hashes = entry.pins.excluded_hashes; |
572 const char* const* sha1_hash = entries[j].pins.excluded_hashes; | 322 while (*sha1_hashes) { |
573 while (*sha1_hash) { | 323 memcpy(hash.data(), *sha1_hashes, hash.size()); |
574 AddHash(*sha1_hash, &out->bad_static_spki_hashes); | 324 result->public_key_pins_bad_hashes_.push_back(hash); |
575 sha1_hash++; | 325 sha1_hashes++; |
326 } | |
576 } | 327 } |
328 | |
329 if (entry.pins.required_hashes == kGoogleAcceptableCerts) | |
330 result->is_google_pinned_property_ = true; | |
331 | |
332 if (entry.second_level_domain_name != DOMAIN_NOT_PINNED) { | |
333 result->report_uma_on_pin_failure_ = true; | |
334 result->second_level_domain_name_ = entry.second_level_domain_name; | |
335 } | |
336 return true; | |
577 } | 337 } |
578 } | 338 } |
579 return true; | 339 } |
580 } | 340 } |
581 } | 341 #endif |
582 return false; | 342 return false; |
583 } | 343 } |
584 | 344 |
585 #include "net/base/transport_security_state_static.h" | 345 void TransportSecurityState::AddHSTSHeader(const std::string& host, |
586 | |
587 // Returns the HSTSPreload entry for the |canonicalized_host| in |entries|, | |
588 // or NULL if there is none. Prefers exact hostname matches to those that | |
589 // match only because HSTSPreload.include_subdomains is true. | |
590 // | |
591 // |canonicalized_host| should be the hostname as canonicalized by | |
592 // CanonicalizeHost. | |
593 static const struct HSTSPreload* GetHSTSPreload( | |
594 const std::string& canonicalized_host, | |
595 const struct HSTSPreload* entries, | |
596 size_t num_entries) { | |
597 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | |
598 for (size_t j = 0; j < num_entries; j++) { | |
599 const struct HSTSPreload* entry = entries + j; | |
600 | |
601 if (i != 0 && !entry->include_subdomains) | |
602 continue; | |
603 | |
604 if (entry->length == canonicalized_host.size() - i && | |
605 memcmp(entry->dns_name, &canonicalized_host[i], entry->length) == 0) { | |
606 return entry; | |
607 } | |
608 } | |
609 } | |
610 | |
611 return NULL; | |
612 } | |
613 | |
614 bool TransportSecurityState::AddHSTSHeader(const std::string& host, | |
615 const std::string& value) { | 346 const std::string& value) { |
616 base::Time now = base::Time::Now(); | 347 const base::Time now = base::Time::Now(); |
617 TransportSecurityState::DomainState domain_state; | 348 base::TimeDelta max_age; |
618 if (ParseHSTSHeader(now, value, &domain_state.upgrade_expiry, | 349 bool include_subdomains = false; |
619 &domain_state.include_subdomains)) { | 350 if (ParseHSTSHeader(value, &max_age, &include_subdomains)) { |
620 // Handle max-age == 0 | 351 if (max_age.InSeconds() == 0) |
621 if (now == domain_state.upgrade_expiry) | 352 DeleteHSTS(host); |
622 domain_state.upgrade_mode = DomainState::MODE_DEFAULT; | |
623 else | 353 else |
624 domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS; | 354 AddHSTS(host, now, now + max_age, include_subdomains); |
625 domain_state.created = now; | 355 } |
626 EnableHost(host, domain_state); | 356 } |
627 return true; | 357 |
628 } | 358 void TransportSecurityState::AddHPKPHeader(const std::string& host, |
629 return false; | |
630 } | |
631 | |
632 bool TransportSecurityState::AddHPKPHeader(const std::string& host, | |
633 const std::string& value, | 359 const std::string& value, |
634 const SSLInfo& ssl_info) { | 360 const SSLInfo& ssl_info) { |
635 base::Time now = base::Time::Now(); | 361 const base::Time now = base::Time::Now(); |
636 TransportSecurityState::DomainState domain_state; | 362 base::TimeDelta max_age; |
637 if (ParseHPKPHeader(now, value, ssl_info.public_key_hashes, | 363 HashValueVector public_key_pin_hashes; |
638 &domain_state.dynamic_spki_hashes_expiry, | 364 bool include_subdomains = false; // TODO(trevp) PARSE FROM HEADER |
Ryan Sleevi
2013/04/29 22:06:14
Seems like entirely the wrong place to put this TO
unsafe
2013/04/30 10:42:57
It's not an implementation detail - ParseHPKP will
| |
639 &domain_state.dynamic_spki_hashes)) { | 365 if (ParseHPKPHeader(value, ssl_info.public_key_hashes, &max_age, |
640 domain_state.upgrade_mode = DomainState::MODE_DEFAULT; | 366 &public_key_pin_hashes)) { |
641 domain_state.created = now; | 367 if (max_age.InSeconds() == 0) { |
642 EnableHost(host, domain_state); | 368 DeleteHPKP(host); |
643 return true; | 369 } else { |
644 } | 370 AddHPKP(host, now, now + max_age, include_subdomains, |
645 return false; | 371 public_key_pin_hashes); |
372 } | |
373 } | |
646 } | 374 } |
647 | 375 |
648 bool TransportSecurityState::AddHSTS(const std::string& host, | 376 bool TransportSecurityState::AddHSTS(const std::string& host, |
377 const base::Time& created, | |
649 const base::Time& expiry, | 378 const base::Time& expiry, |
650 bool include_subdomains) { | 379 bool include_subdomains) { |
651 // Copy-and-modify the existing DomainState for this host (if any). | 380 return AddHSTSHashedHost(HashHost(host), created, expiry, |
652 TransportSecurityState::DomainState domain_state; | 381 include_subdomains); |
653 const std::string canonicalized_host = CanonicalizeHost(host); | |
654 const std::string hashed_host = HashHost(canonicalized_host); | |
655 DomainStateMap::const_iterator i = enabled_hosts_.find( | |
656 hashed_host); | |
657 if (i != enabled_hosts_.end()) | |
658 domain_state = i->second; | |
659 | |
660 domain_state.created = base::Time::Now(); | |
661 domain_state.include_subdomains = include_subdomains; | |
662 domain_state.upgrade_expiry = expiry; | |
663 domain_state.upgrade_mode = DomainState::MODE_FORCE_HTTPS; | |
664 EnableHost(host, domain_state); | |
665 return true; | |
666 } | 382 } |
667 | 383 |
668 bool TransportSecurityState::AddHPKP(const std::string& host, | 384 bool TransportSecurityState::AddHPKP(const std::string& host, |
385 const base::Time& created, | |
669 const base::Time& expiry, | 386 const base::Time& expiry, |
670 bool include_subdomains, | 387 bool include_subdomains, |
671 const HashValueVector& hashes) { | 388 const HashValueVector& hashes) { |
672 // Copy-and-modify the existing DomainState for this host (if any). | 389 return AddHPKPHashedHost(HashHost(host), created, expiry, |
673 TransportSecurityState::DomainState domain_state; | 390 include_subdomains, hashes); |
674 const std::string canonicalized_host = CanonicalizeHost(host); | 391 } |
675 const std::string hashed_host = HashHost(canonicalized_host); | 392 |
676 DomainStateMap::const_iterator i = enabled_hosts_.find( | 393 bool TransportSecurityState::AddHSTSHashedHost(const std::string& hashed_host, |
677 hashed_host); | 394 const base::Time& created, |
678 if (i != enabled_hosts_.end()) | 395 const base::Time& expiry, |
679 domain_state = i->second; | 396 bool include_subdomains) { |
680 | 397 |
681 domain_state.created = base::Time::Now(); | 398 DynamicEntry entry(include_subdomains, created, expiry); |
682 domain_state.include_subdomains = include_subdomains; | 399 return AddDynamicEntry(hsts_entries_, hashed_host, entry, this); |
683 domain_state.dynamic_spki_hashes_expiry = expiry; | 400 } |
684 domain_state.dynamic_spki_hashes = hashes; | 401 |
685 EnableHost(host, domain_state); | 402 bool TransportSecurityState::AddHPKPHashedHost(const std::string& hashed_host, |
686 return true; | 403 const base::Time& created, |
687 } | 404 const base::Time& expiry, |
688 | 405 bool include_subdomains, |
689 // static | 406 const HashValueVector& hashes) { |
690 bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host, | 407 if (hashes.empty()) |
691 bool sni_enabled) { | 408 return false; |
692 std::string canonicalized_host = CanonicalizeHost(host); | 409 HPKPEntry entry(include_subdomains, created, expiry, hashes); |
693 const struct HSTSPreload* entry = | 410 return AddDynamicEntry(hpkp_entries_, hashed_host, entry, this); |
694 GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS); | 411 } |
695 | 412 |
696 if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts) | 413 bool TransportSecurityState::DeleteHSTS(const std::string& host) { |
697 return true; | 414 return DeleteDynamicEntry(hsts_entries_, HashHost(host), this); |
698 | 415 } |
699 if (sni_enabled) { | 416 |
700 entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS, | 417 bool TransportSecurityState::DeleteHPKP(const std::string& host) { |
701 kNumPreloadedSNISTS); | 418 return DeleteDynamicEntry(hpkp_entries_, HashHost(host), this); |
702 if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts) | 419 } |
703 return true; | 420 |
704 } | 421 const std::map<std::string, TransportSecurityState::DynamicEntry>& |
705 | 422 TransportSecurityState::GetHSTSEntries() const { |
706 return false; | 423 return hsts_entries_; |
707 } | 424 } |
708 | 425 |
709 // static | 426 const std::map<std::string, TransportSecurityState::HPKPEntry>& |
710 void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) { | 427 TransportSecurityState::GetHPKPEntries() const { |
711 std::string canonicalized_host = CanonicalizeHost(host); | 428 return hpkp_entries_; |
712 | 429 } |
713 const struct HSTSPreload* entry = | 430 |
714 GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS); | 431 void TransportSecurityState::StateIsDirty() { |
715 | 432 if (delegate_) |
716 if (!entry) { | 433 delegate_->StateIsDirty(this); |
717 entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS, | 434 } |
718 kNumPreloadedSNISTS); | 435 |
719 } | |
720 | |
721 if (!entry) { | |
722 // We don't care to report pin failures for dynamic pins. | |
723 return; | |
724 } | |
725 | |
726 DCHECK(entry); | |
727 DCHECK(entry->pins.required_hashes); | |
728 DCHECK(entry->second_level_domain_name != DOMAIN_NOT_PINNED); | |
729 | |
730 UMA_HISTOGRAM_ENUMERATION("Net.PublicKeyPinFailureDomain", | |
731 entry->second_level_domain_name, DOMAIN_NUM_EVENTS); | |
732 } | |
733 | |
734 // static | |
735 bool TransportSecurityState::IsBuildTimely() { | 436 bool TransportSecurityState::IsBuildTimely() { |
736 const base::Time build_time = base::GetBuildTime(); | 437 const base::Time build_time = base::GetBuildTime(); |
737 // We consider built-in information to be timely for 10 weeks. | 438 // We consider built-in information to be timely for 10 weeks. |
738 return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */; | 439 return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */; |
739 } | 440 } |
740 | 441 |
741 bool TransportSecurityState::GetStaticDomainState( | 442 // DynamicEntry and subclasses (e.g. HPKPEntry) |
742 const std::string& canonicalized_host, | 443 |
743 bool sni_enabled, | 444 TransportSecurityState::DynamicEntry::DynamicEntry() |
744 DomainState* out) { | 445 : include_subdomains_(false), |
745 DCHECK(CalledOnValidThread()); | 446 created_(), |
746 | 447 expiry_() { |
747 out->upgrade_mode = DomainState::MODE_FORCE_HTTPS; | 448 } |
748 out->include_subdomains = false; | 449 |
749 | 450 TransportSecurityState::DynamicEntry::~DynamicEntry() { |
750 const bool is_build_timely = IsBuildTimely(); | 451 } |
751 | 452 |
752 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | 453 TransportSecurityState::DynamicEntry::DynamicEntry(bool include_subdomains, |
753 std::string host_sub_chunk(&canonicalized_host[i], | 454 const base::Time& created, |
754 canonicalized_host.size() - i); | 455 const base::Time& expiry) |
755 out->domain = DNSDomainToString(host_sub_chunk); | 456 : include_subdomains_(include_subdomains), |
756 std::string hashed_host(HashHost(host_sub_chunk)); | 457 created_(created), |
757 if (forced_hosts_.find(hashed_host) != forced_hosts_.end()) { | 458 expiry_(expiry) { |
758 *out = forced_hosts_[hashed_host]; | 459 } |
759 out->domain = DNSDomainToString(host_sub_chunk); | 460 |
760 return true; | 461 TransportSecurityState::HPKPEntry::HPKPEntry() |
761 } | 462 : DynamicEntry(), good_hashes_() { |
762 bool ret; | 463 } |
763 if (is_build_timely && | 464 |
764 HasPreload(kPreloadedSTS, kNumPreloadedSTS, canonicalized_host, i, out, | 465 TransportSecurityState::HPKPEntry::~HPKPEntry() { |
765 &ret)) { | 466 } |
766 return ret; | 467 |
767 } | 468 TransportSecurityState::HPKPEntry::HPKPEntry( |
768 if (sni_enabled && | 469 bool include_subdomains, |
769 is_build_timely && | 470 const base::Time& created, |
770 HasPreload(kPreloadedSNISTS, kNumPreloadedSNISTS, canonicalized_host, i, | 471 const base::Time& expiry, |
771 out, &ret)) { | 472 const HashValueVector& good_hashes): |
772 return ret; | 473 DynamicEntry(include_subdomains, created, expiry), |
773 } | 474 good_hashes_(good_hashes) { |
774 } | 475 } |
775 | 476 |
776 return false; | 477 // DomainState |
777 } | |
778 | |
779 void TransportSecurityState::AddOrUpdateEnabledHosts( | |
780 const std::string& hashed_host, const DomainState& state) { | |
781 enabled_hosts_[hashed_host] = state; | |
782 } | |
783 | |
784 void TransportSecurityState::AddOrUpdateForcedHosts( | |
785 const std::string& hashed_host, const DomainState& state) { | |
786 forced_hosts_[hashed_host] = state; | |
787 } | |
788 | 478 |
789 TransportSecurityState::DomainState::DomainState() | 479 TransportSecurityState::DomainState::DomainState() |
790 : upgrade_mode(MODE_FORCE_HTTPS), | 480 : should_upgrade_(false), |
791 created(base::Time::Now()), | 481 has_public_key_pins_(false), |
792 include_subdomains(false) { | 482 is_google_pinned_property_(false), |
483 report_uma_on_pin_failure_(false), | |
484 public_key_pins_good_hashes_(), | |
485 public_key_pins_bad_hashes_(), | |
486 second_level_domain_name_(DOMAIN_NOT_PINNED) { | |
793 } | 487 } |
794 | 488 |
795 TransportSecurityState::DomainState::~DomainState() { | 489 TransportSecurityState::DomainState::~DomainState() { |
796 } | 490 } |
797 | 491 |
492 bool TransportSecurityState::DomainState::HasPublicKeyPins() const { | |
493 return has_public_key_pins_; | |
494 } | |
495 | |
798 bool TransportSecurityState::DomainState::CheckPublicKeyPins( | 496 bool TransportSecurityState::DomainState::CheckPublicKeyPins( |
799 const HashValueVector& hashes) const { | 497 const HashValueVector& hashes) const { |
800 // Validate that hashes is not empty. By the time this code is called (in | 498 if (HashesIntersect(public_key_pins_bad_hashes_, hashes)) { |
801 // production), that should never happen, but it's good to be defensive. | 499 LOG(ERROR) << "Rejecting public key chain. Validated chain: " |
802 // And, hashes *can* be empty in some test scenarios. | 500 << HashesToBase64String(hashes) |
803 if (hashes.empty()) { | 501 << ", matches one or more bad hashes: " |
804 LOG(ERROR) << "Rejecting empty public key chain for public-key-pinned " | 502 << HashesToBase64String(public_key_pins_bad_hashes_); |
805 "domain " << domain; | |
806 return false; | 503 return false; |
807 } | 504 } |
808 | 505 |
809 if (HashesIntersect(bad_static_spki_hashes, hashes)) { | 506 // If there are no good pins, then any valid chain is acceptable. |
810 LOG(ERROR) << "Rejecting public key chain for domain " << domain | 507 // Otherwise, there has to be a match. |
811 << ". Validated chain: " << HashesToBase64String(hashes) | 508 if (public_key_pins_good_hashes_.empty() || |
812 << ", matches one or more bad hashes: " | 509 HashesIntersect(public_key_pins_good_hashes_, hashes)) { |
813 << HashesToBase64String(bad_static_spki_hashes); | |
814 return false; | |
815 } | |
816 | |
817 // If there are no pins, then any valid chain is acceptable. | |
818 if (dynamic_spki_hashes.empty() && static_spki_hashes.empty()) | |
819 return true; | 510 return true; |
820 | 511 } |
821 if (HashesIntersect(dynamic_spki_hashes, hashes) || | 512 |
822 HashesIntersect(static_spki_hashes, hashes)) { | 513 LOG(ERROR) << "Rejecting public key chain. Validated chain: " |
823 return true; | 514 << HashesToBase64String(hashes) |
824 } | 515 << ", expected: " |
825 | 516 << HashesToBase64String(public_key_pins_good_hashes_); |
826 LOG(ERROR) << "Rejecting public key chain for domain " << domain | |
827 << ". Validated chain: " << HashesToBase64String(hashes) | |
828 << ", expected: " << HashesToBase64String(dynamic_spki_hashes) | |
829 << " or: " << HashesToBase64String(static_spki_hashes); | |
830 return false; | 517 return false; |
831 } | 518 } |
832 | 519 |
833 bool TransportSecurityState::DomainState::ShouldUpgradeToSSL() const { | 520 bool TransportSecurityState::DomainState::ShouldUpgradeToSSL() const { |
834 return upgrade_mode == MODE_FORCE_HTTPS; | 521 return should_upgrade_; |
835 } | 522 } |
836 | 523 |
837 bool TransportSecurityState::DomainState::ShouldSSLErrorsBeFatal() const { | 524 bool TransportSecurityState::DomainState::ShouldSSLErrorsBeFatal() const { |
838 return true; | 525 return should_upgrade_; |
839 } | 526 } |
840 | 527 |
841 bool TransportSecurityState::DomainState::Equals( | 528 bool TransportSecurityState::DomainState::IsGooglePinnedProperty() const { |
842 const DomainState& other) const { | 529 return is_google_pinned_property_; |
843 // TODO(palmer): Implement this | 530 } |
844 (void) other; | 531 |
845 return true; | 532 void TransportSecurityState::DomainState::ReportUMAOnPinFailure() const { |
846 } | 533 if (report_uma_on_pin_failure_) { |
847 | 534 UMA_HISTOGRAM_ENUMERATION("Net.PublicKeyPinFailureDomain", |
848 bool TransportSecurityState::DomainState::HasPublicKeyPins() const { | 535 second_level_domain_name_, DOMAIN_NUM_EVENTS); |
849 return static_spki_hashes.size() > 0 || | 536 } |
850 bad_static_spki_hashes.size() > 0 || | 537 } |
851 dynamic_spki_hashes.size() > 0; | 538 |
539 const HashValueVector& | |
540 TransportSecurityState::DomainState::GetPublicKeyPinsGoodHashes() const { | |
541 return public_key_pins_good_hashes_; | |
542 } | |
543 | |
544 const HashValueVector& | |
545 TransportSecurityState::DomainState::GetPublicKeyPinsBadHashes() const { | |
546 return public_key_pins_bad_hashes_; | |
852 } | 547 } |
853 | 548 |
854 } // namespace | 549 } // namespace |
OLD | NEW |