OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 #include "base/base64.h" | 7 #include "base/base64.h" |
| 8 #include "base/command_line.h" |
8 #include "base/json/json_reader.h" | 9 #include "base/json/json_reader.h" |
9 #include "base/json/json_writer.h" | 10 #include "base/json/json_writer.h" |
10 #include "base/logging.h" | 11 #include "base/logging.h" |
11 #include "base/memory/scoped_ptr.h" | 12 #include "base/memory/scoped_ptr.h" |
12 #include "base/sha1.h" | 13 #include "base/sha1.h" |
13 #include "base/string_number_conversions.h" | 14 #include "base/string_number_conversions.h" |
| 15 #include "base/string_split.h" |
14 #include "base/string_tokenizer.h" | 16 #include "base/string_tokenizer.h" |
15 #include "base/string_util.h" | 17 #include "base/string_util.h" |
16 #include "base/utf_string_conversions.h" | 18 #include "base/utf_string_conversions.h" |
17 #include "base/values.h" | 19 #include "base/values.h" |
18 #include "crypto/sha2.h" | 20 #include "crypto/sha2.h" |
19 #include "googleurl/src/gurl.h" | 21 #include "googleurl/src/gurl.h" |
20 #include "net/base/dns_util.h" | 22 #include "net/base/dns_util.h" |
| 23 #include "net/base/net_switches.h" |
21 | 24 |
22 namespace net { | 25 namespace net { |
23 | 26 |
24 const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year | 27 const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year |
25 | 28 |
26 TransportSecurityState::TransportSecurityState() | 29 TransportSecurityState::TransportSecurityState() |
27 : delegate_(NULL) { | 30 : delegate_(NULL) { |
28 } | 31 } |
29 | 32 |
| 33 static std::string HashHost(const std::string& canonicalized_host) { |
| 34 char hashed[crypto::SHA256_LENGTH]; |
| 35 crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); |
| 36 return std::string(hashed, sizeof(hashed)); |
| 37 } |
| 38 |
30 void TransportSecurityState::EnableHost(const std::string& host, | 39 void TransportSecurityState::EnableHost(const std::string& host, |
31 const DomainState& state) { | 40 const DomainState& state) { |
32 const std::string canonicalized_host = CanonicalizeHost(host); | 41 const std::string canonicalized_host = CanonicalizeHost(host); |
33 if (canonicalized_host.empty()) | 42 if (canonicalized_host.empty()) |
34 return; | 43 return; |
35 | 44 |
36 // TODO(cevans) -- we likely want to permit a host to override a built-in, | 45 // TODO(cevans) -- we likely want to permit a host to override a built-in, |
37 // for at least the case where the override is stricter (i.e. includes | 46 // for at least the case where the override is stricter (i.e. includes |
38 // subdomains, or includes certificate pinning). | 47 // subdomains, or includes certificate pinning). |
39 bool temp; | 48 DomainState temp; |
40 if (IsPreloadedSTS(canonicalized_host, true, &temp)) | 49 if (IsPreloadedSTS(canonicalized_host, true, &temp)) |
41 return; | 50 return; |
42 | 51 |
43 char hashed[crypto::SHA256_LENGTH]; | |
44 crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); | |
45 | |
46 // Use the original creation date if we already have this host. | 52 // Use the original creation date if we already have this host. |
47 DomainState state_copy(state); | 53 DomainState state_copy(state); |
48 DomainState existing_state; | 54 DomainState existing_state; |
49 if (IsEnabledForHost(&existing_state, host, true)) | 55 if (IsEnabledForHost(&existing_state, host, true)) |
50 state_copy.created = existing_state.created; | 56 state_copy.created = existing_state.created; |
51 | 57 |
52 // We don't store these values. | 58 // We don't store these values. |
53 state_copy.preloaded = false; | 59 state_copy.preloaded = false; |
54 state_copy.domain.clear(); | 60 state_copy.domain.clear(); |
55 | 61 |
56 enabled_hosts_[std::string(hashed, sizeof(hashed))] = state_copy; | 62 enabled_hosts_[HashHost(canonicalized_host)] = state_copy; |
57 DirtyNotify(); | 63 DirtyNotify(); |
58 } | 64 } |
59 | 65 |
60 bool TransportSecurityState::DeleteHost(const std::string& host) { | 66 bool TransportSecurityState::DeleteHost(const std::string& host) { |
61 const std::string canonicalized_host = CanonicalizeHost(host); | 67 const std::string canonicalized_host = CanonicalizeHost(host); |
62 if (canonicalized_host.empty()) | 68 if (canonicalized_host.empty()) |
63 return false; | 69 return false; |
64 | 70 |
65 char hashed[crypto::SHA256_LENGTH]; | |
66 crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); | |
67 | |
68 std::map<std::string, DomainState>::iterator i = enabled_hosts_.find( | 71 std::map<std::string, DomainState>::iterator i = enabled_hosts_.find( |
69 std::string(hashed, sizeof(hashed))); | 72 HashHost(canonicalized_host)); |
70 if (i != enabled_hosts_.end()) { | 73 if (i != enabled_hosts_.end()) { |
71 enabled_hosts_.erase(i); | 74 enabled_hosts_.erase(i); |
72 DirtyNotify(); | 75 DirtyNotify(); |
73 return true; | 76 return true; |
74 } | 77 } |
75 return false; | 78 return false; |
76 } | 79 } |
77 | 80 |
78 // IncludeNUL converts a char* to a std::string and includes the terminating | 81 // IncludeNUL converts a char* to a std::string and includes the terminating |
79 // NUL in the result. | 82 // NUL in the result. |
80 static std::string IncludeNUL(const char* in) { | 83 static std::string IncludeNUL(const char* in) { |
81 return std::string(in, strlen(in) + 1); | 84 return std::string(in, strlen(in) + 1); |
82 } | 85 } |
83 | 86 |
84 bool TransportSecurityState::IsEnabledForHost(DomainState* result, | 87 bool TransportSecurityState::IsEnabledForHost(DomainState* result, |
85 const std::string& host, | 88 const std::string& host, |
86 bool sni_available) { | 89 bool sni_available) { |
87 *result = DomainState(); | |
88 | |
89 const std::string canonicalized_host = CanonicalizeHost(host); | 90 const std::string canonicalized_host = CanonicalizeHost(host); |
90 if (canonicalized_host.empty()) | 91 if (canonicalized_host.empty()) |
91 return false; | 92 return false; |
92 | 93 |
93 bool include_subdomains; | 94 if (IsPreloadedSTS(canonicalized_host, sni_available, result)) |
94 if (IsPreloadedSTS(canonicalized_host, sni_available, &include_subdomains)) { | 95 return result->mode != DomainState::MODE_NONE; |
95 result->created = result->expiry = base::Time::FromTimeT(0); | |
96 result->mode = DomainState::MODE_STRICT; | |
97 result->include_subdomains = include_subdomains; | |
98 result->preloaded = true; | |
99 return true; | |
100 } | |
101 | 96 |
102 result->preloaded = false; | 97 *result = DomainState(); |
| 98 |
103 base::Time current_time(base::Time::Now()); | 99 base::Time current_time(base::Time::Now()); |
104 | 100 |
105 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | 101 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { |
106 char hashed_domain[crypto::SHA256_LENGTH]; | 102 std::string hashed_domain(HashHost(IncludeNUL(&canonicalized_host[i]))); |
107 | 103 |
108 crypto::SHA256HashString(IncludeNUL(&canonicalized_host[i]), &hashed_domain, | |
109 sizeof(hashed_domain)); | |
110 std::map<std::string, DomainState>::iterator j = | 104 std::map<std::string, DomainState>::iterator j = |
111 enabled_hosts_.find(std::string(hashed_domain, sizeof(hashed_domain))); | 105 enabled_hosts_.find(hashed_domain); |
112 if (j == enabled_hosts_.end()) | 106 if (j == enabled_hosts_.end()) |
113 continue; | 107 continue; |
114 | 108 |
115 if (current_time > j->second.expiry) { | 109 if (current_time > j->second.expiry) { |
116 enabled_hosts_.erase(j); | 110 enabled_hosts_.erase(j); |
117 DirtyNotify(); | 111 DirtyNotify(); |
118 continue; | 112 continue; |
119 } | 113 } |
120 | 114 |
121 *result = j->second; | 115 *result = j->second; |
(...skipping 207 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
329 } | 323 } |
330 state->Set("public_key_hashes", pins); | 324 state->Set("public_key_hashes", pins); |
331 | 325 |
332 toplevel.Set(HashedDomainToExternalString(i->first), state); | 326 toplevel.Set(HashedDomainToExternalString(i->first), state); |
333 } | 327 } |
334 | 328 |
335 base::JSONWriter::Write(&toplevel, true /* pretty print */, output); | 329 base::JSONWriter::Write(&toplevel, true /* pretty print */, output); |
336 return true; | 330 return true; |
337 } | 331 } |
338 | 332 |
339 bool TransportSecurityState::Deserialise(const std::string& input, | 333 bool TransportSecurityState::LoadEntries(const std::string& input, |
340 bool* dirty) { | 334 bool* dirty) { |
341 enabled_hosts_.clear(); | 335 enabled_hosts_.clear(); |
| 336 return Deserialise(input, dirty, &enabled_hosts_); |
| 337 } |
342 | 338 |
| 339 // static |
| 340 bool TransportSecurityState::Deserialise( |
| 341 const std::string& input, |
| 342 bool* dirty, |
| 343 std::map<std::string, DomainState>* out) { |
343 scoped_ptr<Value> value( | 344 scoped_ptr<Value> value( |
344 base::JSONReader::Read(input, false /* do not allow trailing commas */)); | 345 base::JSONReader::Read(input, false /* do not allow trailing commas */)); |
345 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) | 346 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) |
346 return false; | 347 return false; |
347 | 348 |
348 DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get()); | 349 DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get()); |
349 const base::Time current_time(base::Time::Now()); | 350 const base::Time current_time(base::Time::Now()); |
350 bool dirtied = false; | 351 bool dirtied = false; |
351 | 352 |
352 for (DictionaryValue::key_iterator i = dict_value->begin_keys(); | 353 for (DictionaryValue::key_iterator i = dict_value->begin_keys(); |
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
386 } | 387 } |
387 } | 388 } |
388 | 389 |
389 DomainState::Mode mode; | 390 DomainState::Mode mode; |
390 if (mode_string == "strict") { | 391 if (mode_string == "strict") { |
391 mode = DomainState::MODE_STRICT; | 392 mode = DomainState::MODE_STRICT; |
392 } else if (mode_string == "opportunistic") { | 393 } else if (mode_string == "opportunistic") { |
393 mode = DomainState::MODE_OPPORTUNISTIC; | 394 mode = DomainState::MODE_OPPORTUNISTIC; |
394 } else if (mode_string == "spdy-only") { | 395 } else if (mode_string == "spdy-only") { |
395 mode = DomainState::MODE_SPDY_ONLY; | 396 mode = DomainState::MODE_SPDY_ONLY; |
| 397 } else if (mode_string == "none") { |
| 398 mode = DomainState::MODE_NONE; |
396 } else { | 399 } else { |
397 LOG(WARNING) << "Unknown TransportSecurityState mode string found: " | 400 LOG(WARNING) << "Unknown TransportSecurityState mode string found: " |
398 << mode_string; | 401 << mode_string; |
399 continue; | 402 continue; |
400 } | 403 } |
401 | 404 |
402 base::Time expiry_time = base::Time::FromDoubleT(expiry); | 405 base::Time expiry_time = base::Time::FromDoubleT(expiry); |
403 base::Time created_time; | 406 base::Time created_time; |
404 if (state->GetDouble("created", &created)) { | 407 if (state->GetDouble("created", &created)) { |
405 created_time = base::Time::FromDoubleT(created); | 408 created_time = base::Time::FromDoubleT(created); |
(...skipping 15 matching lines...) Expand all Loading... |
421 dirtied = true; | 424 dirtied = true; |
422 continue; | 425 continue; |
423 } | 426 } |
424 | 427 |
425 DomainState new_state; | 428 DomainState new_state; |
426 new_state.mode = mode; | 429 new_state.mode = mode; |
427 new_state.created = created_time; | 430 new_state.created = created_time; |
428 new_state.expiry = expiry_time; | 431 new_state.expiry = expiry_time; |
429 new_state.include_subdomains = include_subdomains; | 432 new_state.include_subdomains = include_subdomains; |
430 new_state.public_key_hashes = public_key_hashes; | 433 new_state.public_key_hashes = public_key_hashes; |
431 enabled_hosts_[hashed] = new_state; | 434 (*out)[hashed] = new_state; |
432 } | 435 } |
433 | 436 |
434 *dirty = dirtied; | 437 *dirty = dirtied; |
435 return true; | 438 return true; |
436 } | 439 } |
437 | 440 |
438 TransportSecurityState::~TransportSecurityState() { | 441 TransportSecurityState::~TransportSecurityState() { |
439 } | 442 } |
440 | 443 |
441 void TransportSecurityState::DirtyNotify() { | 444 void TransportSecurityState::DirtyNotify() { |
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
478 | 481 |
479 return new_host; | 482 return new_host; |
480 } | 483 } |
481 | 484 |
482 // IsPreloadedSTS returns true if the canonicalized hostname should always be | 485 // IsPreloadedSTS returns true if the canonicalized hostname should always be |
483 // considered to have STS enabled. | 486 // considered to have STS enabled. |
484 // static | 487 // static |
485 bool TransportSecurityState::IsPreloadedSTS( | 488 bool TransportSecurityState::IsPreloadedSTS( |
486 const std::string& canonicalized_host, | 489 const std::string& canonicalized_host, |
487 bool sni_available, | 490 bool sni_available, |
488 bool *include_subdomains) { | 491 DomainState* out) { |
| 492 out->preloaded = true; |
| 493 out->mode = DomainState::MODE_STRICT; |
| 494 out->created = base::Time::FromTimeT(0); |
| 495 out->expiry = out->created; |
| 496 out->include_subdomains = false; |
| 497 |
| 498 std::map<std::string, DomainState> hosts; |
| 499 std::string cmd_line_hsts = |
| 500 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| 501 switches::kHstsHosts); |
| 502 if (!cmd_line_hsts.empty()) { |
| 503 bool dirty; |
| 504 Deserialise(cmd_line_hsts, &dirty, &hosts); |
| 505 } |
| 506 |
489 // In the medium term this list is likely to just be hardcoded here. This, | 507 // In the medium term this list is likely to just be hardcoded here. This, |
490 // slightly odd, form removes the need for additional relocations records. | 508 // slightly odd, form removes the need for additional relocations records. |
491 static const struct { | 509 static const struct { |
492 uint8 length; | 510 uint8 length; |
493 bool include_subdomains; | 511 bool include_subdomains; |
494 char dns_name[30]; | 512 char dns_name[30]; |
495 } kPreloadedSTS[] = { | 513 } kPreloadedSTS[] = { |
496 {16, false, "\003www\006paypal\003com"}, | 514 {16, false, "\003www\006paypal\003com"}, |
497 {16, false, "\003www\006elanex\003biz"}, | 515 {16, false, "\003www\006elanex\003biz"}, |
498 {12, true, "\006jottit\003com"}, | 516 {12, true, "\006jottit\003com"}, |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
540 uint8 length; | 558 uint8 length; |
541 bool include_subdomains; | 559 bool include_subdomains; |
542 char dns_name[30]; | 560 char dns_name[30]; |
543 } kPreloadedSNISTS[] = { | 561 } kPreloadedSNISTS[] = { |
544 {11, true, "\005gmail\003com"}, | 562 {11, true, "\005gmail\003com"}, |
545 {16, true, "\012googlemail\003com"}, | 563 {16, true, "\012googlemail\003com"}, |
546 }; | 564 }; |
547 static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); | 565 static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); |
548 | 566 |
549 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | 567 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { |
| 568 std::string host_sub_chunk(&canonicalized_host[i], |
| 569 canonicalized_host.size() - i); |
| 570 out->domain = DNSDomainToString(host_sub_chunk); |
| 571 std::string hashed_host(HashHost(host_sub_chunk)); |
| 572 if (hosts.find(hashed_host) != hosts.end()) { |
| 573 *out = hosts[hashed_host]; |
| 574 out->domain = DNSDomainToString(host_sub_chunk); |
| 575 out->preloaded = true; |
| 576 return true; |
| 577 } |
550 for (size_t j = 0; j < kNumPreloadedSTS; j++) { | 578 for (size_t j = 0; j < kNumPreloadedSTS; j++) { |
551 if (kPreloadedSTS[j].length == canonicalized_host.size() - i && | 579 if (kPreloadedSTS[j].length == canonicalized_host.size() - i && |
552 memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i], | 580 memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i], |
553 kPreloadedSTS[j].length) == 0) { | 581 kPreloadedSTS[j].length) == 0) { |
554 if (!kPreloadedSTS[j].include_subdomains && i != 0) | 582 if (!kPreloadedSTS[j].include_subdomains && i != 0) |
555 return false; | 583 return false; |
556 *include_subdomains = kPreloadedSTS[j].include_subdomains; | 584 out->include_subdomains = kPreloadedSTS[j].include_subdomains; |
557 return true; | 585 return true; |
558 } | 586 } |
559 } | 587 } |
560 if (sni_available) { | 588 if (sni_available) { |
561 for (size_t j = 0; j < kNumPreloadedSNISTS; j++) { | 589 for (size_t j = 0; j < kNumPreloadedSNISTS; j++) { |
562 if (kPreloadedSNISTS[j].length == canonicalized_host.size() - i && | 590 if (kPreloadedSNISTS[j].length == canonicalized_host.size() - i && |
563 memcmp(kPreloadedSNISTS[j].dns_name, &canonicalized_host[i], | 591 memcmp(kPreloadedSNISTS[j].dns_name, &canonicalized_host[i], |
564 kPreloadedSNISTS[j].length) == 0) { | 592 kPreloadedSNISTS[j].length) == 0) { |
565 if (!kPreloadedSNISTS[j].include_subdomains && i != 0) | 593 if (!kPreloadedSNISTS[j].include_subdomains && i != 0) |
566 return false; | 594 return false; |
567 *include_subdomains = kPreloadedSNISTS[j].include_subdomains; | 595 out->include_subdomains = kPreloadedSNISTS[j].include_subdomains; |
568 return true; | 596 return true; |
569 } | 597 } |
570 } | 598 } |
571 } | 599 } |
572 } | 600 } |
573 | 601 |
574 return false; | 602 return false; |
575 } | 603 } |
576 | 604 |
577 static std::string HashesToBase64String( | 605 static std::string HashesToBase64String( |
(...skipping 28 matching lines...) Expand all Loading... |
606 | 634 |
607 for (std::vector<net::SHA1Fingerprint>::const_iterator | 635 for (std::vector<net::SHA1Fingerprint>::const_iterator |
608 i = hashes.begin(); i != hashes.end(); ++i) { | 636 i = hashes.begin(); i != hashes.end(); ++i) { |
609 for (std::vector<net::SHA1Fingerprint>::const_iterator | 637 for (std::vector<net::SHA1Fingerprint>::const_iterator |
610 j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) { | 638 j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) { |
611 if (i->Equals(*j)) | 639 if (i->Equals(*j)) |
612 return true; | 640 return true; |
613 } | 641 } |
614 } | 642 } |
615 | 643 |
616 | |
617 LOG(ERROR) << "Rejecting public key chain for domain " << domain | 644 LOG(ERROR) << "Rejecting public key chain for domain " << domain |
618 << ". Validated chain: " << HashesToBase64String(hashes) | 645 << ". Validated chain: " << HashesToBase64String(hashes) |
619 << ", expected: " << HashesToBase64String(public_key_hashes); | 646 << ", expected: " << HashesToBase64String(public_key_hashes); |
620 | 647 |
621 return false; | 648 return false; |
622 } | 649 } |
623 | 650 |
624 } // namespace | 651 } // namespace |
OLD | NEW |