| 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 |