Chromium Code Reviews| 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 } | |
| 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 name_[index_] == 0; | |
|
palmer
2013/03/25 23:54:34
I am not sure that std::string guarantees that thi
unsafe
2013/03/26 01:42:22
gah, good catch!
| |
| 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_++); | |
|
palmer
2013/03/25 23:54:34
Chromium style is pre-increment.
unsafe
2013/03/26 01:42:22
Done.
| |
| 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 \ | |
|
palmer
2013/03/25 23:54:34
use typedef
unsafe
2013/03/26 01:42:22
Not possible! templates!
It's amazing how poorly
| |
| 114 typename std::map<std::string, T>::const_iterator | |
| 115 #define DynamicEntryMapIter \ | |
| 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) { | |
| 122 // Find the entry, and return if relevant and nonexpired | |
| 123 DynamicEntryMapConstIter find_iter = entries.find(hashed_host); | |
| 124 if (find_iter != entries.end()) { | |
| 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) { | |
| 139 bool dirty = false; | |
| 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 { | |
| 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); |
| 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 && |
| 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 |
| 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 } |
| 644 } | 370 else { |
| 645 return false; | 371 AddHPKP(host, now, now + max_age, include_subdomains, |
| 372 public_key_pin_hashes); | |
| 373 } | |
| 374 } | |
| 646 } | 375 } |
| 647 | 376 |
| 648 bool TransportSecurityState::AddHSTS(const std::string& host, | 377 bool TransportSecurityState::AddHSTS(const std::string& host, |
| 378 const base::Time& created, | |
| 649 const base::Time& expiry, | 379 const base::Time& expiry, |
| 650 bool include_subdomains) { | 380 bool include_subdomains) { |
| 651 // Copy-and-modify the existing DomainState for this host (if any). | 381 return AddHSTSHashedHost(HashHost(host), created, expiry, |
| 652 TransportSecurityState::DomainState domain_state; | 382 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 } | 383 } |
| 667 | 384 |
| 668 bool TransportSecurityState::AddHPKP(const std::string& host, | 385 bool TransportSecurityState::AddHPKP(const std::string& host, |
| 386 const base::Time& created, | |
| 669 const base::Time& expiry, | 387 const base::Time& expiry, |
| 670 bool include_subdomains, | 388 bool include_subdomains, |
| 671 const HashValueVector& hashes) { | 389 const HashValueVector& hashes) { |
| 672 // Copy-and-modify the existing DomainState for this host (if any). | 390 return AddHPKPHashedHost(HashHost(host), created, expiry, |
| 673 TransportSecurityState::DomainState domain_state; | 391 include_subdomains, hashes); |
| 674 const std::string canonicalized_host = CanonicalizeHost(host); | 392 } |
| 675 const std::string hashed_host = HashHost(canonicalized_host); | 393 |
| 676 DomainStateMap::const_iterator i = enabled_hosts_.find( | 394 bool TransportSecurityState::AddHSTSHashedHost(const std::string& hashed_host, |
| 677 hashed_host); | 395 const base::Time& created, |
| 678 if (i != enabled_hosts_.end()) | 396 const base::Time& expiry, |
| 679 domain_state = i->second; | 397 bool include_subdomains) { |
| 680 | 398 |
| 681 domain_state.created = base::Time::Now(); | 399 DynamicEntry entry(include_subdomains, created, expiry); |
| 682 domain_state.include_subdomains = include_subdomains; | 400 return AddDynamicEntry(hsts_entries_, hashed_host, entry, this); |
| 683 domain_state.dynamic_spki_hashes_expiry = expiry; | 401 } |
| 684 domain_state.dynamic_spki_hashes = hashes; | 402 |
| 685 EnableHost(host, domain_state); | 403 bool TransportSecurityState::AddHPKPHashedHost(const std::string& hashed_host, |
| 686 return true; | 404 const base::Time& created, |
| 687 } | 405 const base::Time& expiry, |
| 688 | 406 bool include_subdomains, |
| 689 // static | 407 const HashValueVector& hashes) { |
| 690 bool TransportSecurityState::IsGooglePinnedProperty(const std::string& host, | 408 if (hashes.empty()) |
| 691 bool sni_enabled) { | 409 return false; |
| 692 std::string canonicalized_host = CanonicalizeHost(host); | 410 HPKPEntry entry(include_subdomains, created, expiry, hashes); |
| 693 const struct HSTSPreload* entry = | 411 return AddDynamicEntry(hpkp_entries_, hashed_host, entry, this); |
| 694 GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS); | 412 } |
| 695 | 413 |
| 696 if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts) | 414 bool TransportSecurityState::DeleteHSTS(const std::string& host) { |
| 697 return true; | 415 return DeleteDynamicEntry(hsts_entries_, HashHost(host), this); |
| 698 | 416 } |
| 699 if (sni_enabled) { | 417 |
| 700 entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS, | 418 bool TransportSecurityState::DeleteHPKP(const std::string& host) { |
| 701 kNumPreloadedSNISTS); | 419 return DeleteDynamicEntry(hpkp_entries_, HashHost(host), this); |
| 702 if (entry && entry->pins.required_hashes == kGoogleAcceptableCerts) | 420 } |
| 703 return true; | 421 |
| 704 } | 422 const std::map<std::string, TransportSecurityState::DynamicEntry>& |
| 705 | 423 TransportSecurityState::GetHSTSEntries() const { |
| 706 return false; | 424 return hsts_entries_; |
| 707 } | 425 } |
| 708 | 426 |
| 709 // static | 427 const std::map<std::string, TransportSecurityState::HPKPEntry>& |
| 710 void TransportSecurityState::ReportUMAOnPinFailure(const std::string& host) { | 428 TransportSecurityState::GetHPKPEntries() const { |
| 711 std::string canonicalized_host = CanonicalizeHost(host); | 429 return hpkp_entries_; |
| 712 | 430 } |
| 713 const struct HSTSPreload* entry = | 431 |
| 714 GetHSTSPreload(canonicalized_host, kPreloadedSTS, kNumPreloadedSTS); | 432 void TransportSecurityState::StateIsDirty() { |
| 715 | 433 if (delegate_) |
| 716 if (!entry) { | 434 delegate_->StateIsDirty(this); |
| 717 entry = GetHSTSPreload(canonicalized_host, kPreloadedSNISTS, | 435 } |
| 718 kNumPreloadedSNISTS); | 436 |
| 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() { | 437 bool TransportSecurityState::IsBuildTimely() { |
| 736 const base::Time build_time = base::GetBuildTime(); | 438 const base::Time build_time = base::GetBuildTime(); |
| 737 // We consider built-in information to be timely for 10 weeks. | 439 // We consider built-in information to be timely for 10 weeks. |
| 738 return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */; | 440 return (base::Time::Now() - build_time).InDays() < 70 /* 10 weeks */; |
| 739 } | 441 } |
| 740 | 442 |
| 741 bool TransportSecurityState::GetStaticDomainState( | 443 // DynamicEntry and subclasses (e.g. HPKPEntry) |
| 742 const std::string& canonicalized_host, | 444 |
| 743 bool sni_enabled, | 445 TransportSecurityState::DynamicEntry::DynamicEntry() |
| 744 DomainState* out) { | 446 : include_subdomains_(false), |
| 745 DCHECK(CalledOnValidThread()); | 447 created_(), |
| 746 | 448 expiry_() { |
| 747 out->upgrade_mode = DomainState::MODE_FORCE_HTTPS; | 449 } |
| 748 out->include_subdomains = false; | 450 |
| 749 | 451 TransportSecurityState::DynamicEntry::~DynamicEntry() { |
| 750 const bool is_build_timely = IsBuildTimely(); | 452 } |
| 751 | 453 |
| 752 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | 454 TransportSecurityState::DynamicEntry::DynamicEntry(bool include_subdomains, |
| 753 std::string host_sub_chunk(&canonicalized_host[i], | 455 const base::Time& created, |
| 754 canonicalized_host.size() - i); | 456 const base::Time& expiry) |
| 755 out->domain = DNSDomainToString(host_sub_chunk); | 457 : include_subdomains_(include_subdomains), |
| 756 std::string hashed_host(HashHost(host_sub_chunk)); | 458 created_(created), |
| 757 if (forced_hosts_.find(hashed_host) != forced_hosts_.end()) { | 459 expiry_(expiry) { |
| 758 *out = forced_hosts_[hashed_host]; | 460 } |
| 759 out->domain = DNSDomainToString(host_sub_chunk); | 461 |
| 760 return true; | 462 TransportSecurityState::HPKPEntry::HPKPEntry() |
| 761 } | 463 : DynamicEntry(), good_hashes_() { |
| 762 bool ret; | 464 } |
| 763 if (is_build_timely && | 465 |
| 764 HasPreload(kPreloadedSTS, kNumPreloadedSTS, canonicalized_host, i, out, | 466 TransportSecurityState::HPKPEntry::~HPKPEntry() { |
| 765 &ret)) { | 467 } |
| 766 return ret; | 468 |
| 767 } | 469 TransportSecurityState::HPKPEntry::HPKPEntry( |
| 768 if (sni_enabled && | 470 bool include_subdomains, |
| 769 is_build_timely && | 471 const base::Time& created, |
| 770 HasPreload(kPreloadedSNISTS, kNumPreloadedSNISTS, canonicalized_host, i, | 472 const base::Time& expiry, |
| 771 out, &ret)) { | 473 const HashValueVector& good_hashes): |
| 772 return ret; | 474 DynamicEntry(include_subdomains, created, expiry), |
| 773 } | 475 good_hashes_(good_hashes) { |
| 774 } | 476 } |
| 775 | 477 |
| 776 return false; | 478 // 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 | 479 |
| 789 TransportSecurityState::DomainState::DomainState() | 480 TransportSecurityState::DomainState::DomainState() |
| 790 : upgrade_mode(MODE_FORCE_HTTPS), | 481 : should_upgrade_(false), |
| 791 created(base::Time::Now()), | 482 has_public_key_pins_(false), |
| 792 include_subdomains(false) { | 483 is_google_pinned_property_(false), |
| 484 report_uma_on_pin_failure_(false), | |
| 485 public_key_pins_good_hashes_(), | |
| 486 public_key_pins_bad_hashes_(), | |
| 487 second_level_domain_name_(DOMAIN_NOT_PINNED) { | |
| 793 } | 488 } |
| 794 | 489 |
| 795 TransportSecurityState::DomainState::~DomainState() { | 490 TransportSecurityState::DomainState::~DomainState() { |
| 796 } | 491 } |
| 797 | 492 |
| 493 bool TransportSecurityState::DomainState::HasPublicKeyPins() const { | |
| 494 return has_public_key_pins_; | |
| 495 } | |
| 496 | |
| 798 bool TransportSecurityState::DomainState::CheckPublicKeyPins( | 497 bool TransportSecurityState::DomainState::CheckPublicKeyPins( |
| 799 const HashValueVector& hashes) const { | 498 const HashValueVector& hashes) const { |
| 800 // Validate that hashes is not empty. By the time this code is called (in | 499 if (HashesIntersect(public_key_pins_bad_hashes_, hashes)) { |
| 801 // production), that should never happen, but it's good to be defensive. | 500 LOG(ERROR) << "Rejecting public key chain. Validated chain: " |
| 802 // And, hashes *can* be empty in some test scenarios. | 501 << HashesToBase64String(hashes) |
| 803 if (hashes.empty()) { | 502 << ", matches one or more bad hashes: " |
| 804 LOG(ERROR) << "Rejecting empty public key chain for public-key-pinned " | 503 << HashesToBase64String(public_key_pins_bad_hashes_); |
| 805 "domain " << domain; | |
| 806 return false; | 504 return false; |
| 807 } | 505 } |
| 808 | 506 |
| 809 if (HashesIntersect(bad_static_spki_hashes, hashes)) { | 507 // If there are no good pins, then any valid chain is acceptable. |
| 810 LOG(ERROR) << "Rejecting public key chain for domain " << domain | 508 // Otherwise, there has to be a match. |
| 811 << ". Validated chain: " << HashesToBase64String(hashes) | 509 if (public_key_pins_good_hashes_.empty() || |
| 812 << ", matches one or more bad hashes: " | 510 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; | 511 return true; |
| 820 | 512 } |
| 821 if (HashesIntersect(dynamic_spki_hashes, hashes) || | 513 |
| 822 HashesIntersect(static_spki_hashes, hashes)) { | 514 LOG(ERROR) << "Rejecting public key chain. Validated chain: " |
| 823 return true; | 515 << HashesToBase64String(hashes) |
| 824 } | 516 << ", expected: " |
| 825 | 517 << 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; | 518 return false; |
| 831 } | 519 } |
| 832 | 520 |
| 833 bool TransportSecurityState::DomainState::ShouldUpgradeToSSL() const { | 521 bool TransportSecurityState::DomainState::ShouldUpgradeToSSL() const { |
| 834 return upgrade_mode == MODE_FORCE_HTTPS; | 522 return should_upgrade_; |
| 835 } | 523 } |
| 836 | 524 |
| 837 bool TransportSecurityState::DomainState::ShouldSSLErrorsBeFatal() const { | 525 bool TransportSecurityState::DomainState::ShouldSSLErrorsBeFatal() const { |
| 838 return true; | 526 return should_upgrade_; |
| 839 } | 527 } |
| 840 | 528 |
| 841 bool TransportSecurityState::DomainState::Equals( | 529 bool TransportSecurityState::DomainState::IsGooglePinnedProperty() const { |
| 842 const DomainState& other) const { | 530 return is_google_pinned_property_; |
| 843 // TODO(palmer): Implement this | 531 } |
| 844 (void) other; | 532 |
| 845 return true; | 533 void TransportSecurityState::DomainState::ReportUMAOnPinFailure() const { |
| 846 } | 534 if (report_uma_on_pin_failure_) { |
| 847 | 535 UMA_HISTOGRAM_ENUMERATION("Net.PublicKeyPinFailureDomain", |
| 848 bool TransportSecurityState::DomainState::HasPublicKeyPins() const { | 536 second_level_domain_name_, DOMAIN_NUM_EVENTS); |
| 849 return static_spki_hashes.size() > 0 || | 537 } |
| 850 bad_static_spki_hashes.size() > 0 || | 538 } |
| 851 dynamic_spki_hashes.size() > 0; | 539 |
| 540 const HashValueVector& | |
| 541 TransportSecurityState::DomainState::GetPublicKeyPinsGoodHashes() const { | |
| 542 return public_key_pins_good_hashes_; | |
| 543 } | |
| 544 | |
| 545 const HashValueVector& | |
| 546 TransportSecurityState::DomainState::GetPublicKeyPinsBadHashes() const { | |
| 547 return public_key_pins_bad_hashes_; | |
| 852 } | 548 } |
| 853 | 549 |
| 854 } // namespace | 550 } // namespace |
| OLD | NEW |