Chromium Code Reviews| 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" |
| 14 #include "base/string_tokenizer.h" | 15 #include "base/string_tokenizer.h" |
| 16 #include "base/string_split.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" |
| 21 | 23 |
| 22 namespace net { | 24 namespace net { |
| 23 | 25 |
| 24 const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year | 26 const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year |
| 25 | 27 |
| 26 TransportSecurityState::TransportSecurityState() | 28 TransportSecurityState::TransportSecurityState() |
| 27 : delegate_(NULL) { | 29 : delegate_(NULL) { |
| 28 } | 30 } |
| 29 | 31 |
| 30 void TransportSecurityState::EnableHost(const std::string& host, | 32 void TransportSecurityState::EnableHost(const std::string& host, |
| 31 const DomainState& state) { | 33 const DomainState& state) { |
| 32 const std::string canonicalized_host = CanonicalizeHost(host); | 34 const std::string canonicalized_host = CanonicalizeHost(host); |
| 33 if (canonicalized_host.empty()) | 35 if (canonicalized_host.empty()) |
| 34 return; | 36 return; |
| 35 | 37 |
| 36 // TODO(cevans) -- we likely want to permit a host to override a built-in, | 38 // 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 | 39 // for at least the case where the override is stricter (i.e. includes |
| 38 // subdomains, or includes certificate pinning). | 40 // subdomains, or includes certificate pinning). |
| 39 bool temp; | 41 DomainState temp; |
| 40 if (IsPreloadedSTS(canonicalized_host, true, &temp)) | 42 if (IsPreloadedSTS(canonicalized_host, true, &temp)) |
| 41 return; | 43 return; |
| 42 | 44 |
| 43 char hashed[crypto::SHA256_LENGTH]; | 45 char hashed[crypto::SHA256_LENGTH]; |
| 44 crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); | 46 crypto::SHA256HashString(canonicalized_host, hashed, sizeof(hashed)); |
| 45 | 47 |
| 46 // Use the original creation date if we already have this host. | 48 // Use the original creation date if we already have this host. |
| 47 DomainState state_copy(state); | 49 DomainState state_copy(state); |
| 48 DomainState existing_state; | 50 DomainState existing_state; |
| 49 if (IsEnabledForHost(&existing_state, host, true)) | 51 if (IsEnabledForHost(&existing_state, host, true)) |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 83 | 85 |
| 84 bool TransportSecurityState::IsEnabledForHost(DomainState* result, | 86 bool TransportSecurityState::IsEnabledForHost(DomainState* result, |
| 85 const std::string& host, | 87 const std::string& host, |
| 86 bool sni_available) { | 88 bool sni_available) { |
| 87 *result = DomainState(); | 89 *result = DomainState(); |
| 88 | 90 |
| 89 const std::string canonicalized_host = CanonicalizeHost(host); | 91 const std::string canonicalized_host = CanonicalizeHost(host); |
| 90 if (canonicalized_host.empty()) | 92 if (canonicalized_host.empty()) |
| 91 return false; | 93 return false; |
| 92 | 94 |
| 93 bool include_subdomains; | 95 if (IsPreloadedSTS(canonicalized_host, sni_available, result)) { |
| 94 if (IsPreloadedSTS(canonicalized_host, sni_available, &include_subdomains)) { | 96 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 } | 97 } |
| 101 | 98 |
| 102 result->preloaded = false; | 99 result->preloaded = false; |
| 103 base::Time current_time(base::Time::Now()); | 100 base::Time current_time(base::Time::Now()); |
| 104 | 101 |
| 105 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | 102 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { |
| 106 char hashed_domain[crypto::SHA256_LENGTH]; | 103 char hashed_domain[crypto::SHA256_LENGTH]; |
| 107 | 104 |
| 108 crypto::SHA256HashString(IncludeNUL(&canonicalized_host[i]), &hashed_domain, | 105 crypto::SHA256HashString(IncludeNUL(&canonicalized_host[i]), &hashed_domain, |
| 109 sizeof(hashed_domain)); | 106 sizeof(hashed_domain)); |
| (...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 329 } | 326 } |
| 330 state->Set("public_key_hashes", pins); | 327 state->Set("public_key_hashes", pins); |
| 331 | 328 |
| 332 toplevel.Set(HashedDomainToExternalString(i->first), state); | 329 toplevel.Set(HashedDomainToExternalString(i->first), state); |
| 333 } | 330 } |
| 334 | 331 |
| 335 base::JSONWriter::Write(&toplevel, true /* pretty print */, output); | 332 base::JSONWriter::Write(&toplevel, true /* pretty print */, output); |
| 336 return true; | 333 return true; |
| 337 } | 334 } |
| 338 | 335 |
| 336 static void AddHash(const std::string& hash, | |
| 337 std::vector<SHA1Fingerprint>* out) { | |
| 338 std::string hash_str; | |
| 339 SHA1Fingerprint raw_hash; | |
| 340 if (hash.find("sha1/") == 0 && | |
| 341 base::Base64Decode(hash.substr(5, hash.size() - 5), &hash_str) && | |
|
abarth-chromium
2011/04/17 05:39:23
It would be nicer if 5 wasn't a magic number here.
| |
| 342 hash_str.size() == base::SHA1_LENGTH) { | |
| 343 memcpy(raw_hash.data, hash_str.data(), sizeof(raw_hash.data)); | |
| 344 out->push_back(raw_hash); | |
| 345 } | |
| 346 } | |
| 347 | |
| 339 bool TransportSecurityState::Deserialise(const std::string& input, | 348 bool TransportSecurityState::Deserialise(const std::string& input, |
| 340 bool* dirty) { | 349 bool* dirty) { |
| 341 enabled_hosts_.clear(); | 350 enabled_hosts_.clear(); |
| 342 | 351 |
| 343 scoped_ptr<Value> value( | 352 scoped_ptr<Value> value( |
| 344 base::JSONReader::Read(input, false /* do not allow trailing commas */)); | 353 base::JSONReader::Read(input, false /* do not allow trailing commas */)); |
| 345 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) | 354 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) |
| 346 return false; | 355 return false; |
| 347 | 356 |
| 348 DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get()); | 357 DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get()); |
| (...skipping 16 matching lines...) Expand all Loading... | |
| 365 !state->GetDouble("expiry", &expiry)) { | 374 !state->GetDouble("expiry", &expiry)) { |
| 366 continue; | 375 continue; |
| 367 } | 376 } |
| 368 | 377 |
| 369 ListValue* pins_list = NULL; | 378 ListValue* pins_list = NULL; |
| 370 std::vector<SHA1Fingerprint> public_key_hashes; | 379 std::vector<SHA1Fingerprint> public_key_hashes; |
| 371 if (state->GetList("public_key_hashes", &pins_list)) { | 380 if (state->GetList("public_key_hashes", &pins_list)) { |
| 372 size_t num_pins = pins_list->GetSize(); | 381 size_t num_pins = pins_list->GetSize(); |
| 373 for (size_t i = 0; i < num_pins; ++i) { | 382 for (size_t i = 0; i < num_pins; ++i) { |
| 374 std::string type_and_base64; | 383 std::string type_and_base64; |
| 375 std::string hash_str; | 384 if (pins_list->GetString(i, &type_and_base64)) |
| 376 SHA1Fingerprint hash; | 385 AddHash(type_and_base64, &public_key_hashes); |
| 377 if (pins_list->GetString(i, &type_and_base64) && | |
| 378 type_and_base64.find("sha1/") == 0 && | |
| 379 base::Base64Decode( | |
| 380 type_and_base64.substr(5, type_and_base64.size() - 5), | |
| 381 &hash_str) && | |
| 382 hash_str.size() == base::SHA1_LENGTH) { | |
| 383 memcpy(hash.data, hash_str.data(), sizeof(hash.data)); | |
| 384 public_key_hashes.push_back(hash); | |
| 385 } | |
| 386 } | 386 } |
| 387 } | 387 } |
| 388 | 388 |
| 389 DomainState::Mode mode; | 389 DomainState::Mode mode; |
| 390 if (mode_string == "strict") { | 390 if (mode_string == "strict") { |
| 391 mode = DomainState::MODE_STRICT; | 391 mode = DomainState::MODE_STRICT; |
| 392 } else if (mode_string == "opportunistic") { | 392 } else if (mode_string == "opportunistic") { |
| 393 mode = DomainState::MODE_OPPORTUNISTIC; | 393 mode = DomainState::MODE_OPPORTUNISTIC; |
| 394 } else if (mode_string == "spdy-only") { | 394 } else if (mode_string == "spdy-only") { |
| 395 mode = DomainState::MODE_SPDY_ONLY; | 395 mode = DomainState::MODE_SPDY_ONLY; |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 478 | 478 |
| 479 return new_host; | 479 return new_host; |
| 480 } | 480 } |
| 481 | 481 |
| 482 // IsPreloadedSTS returns true if the canonicalized hostname should always be | 482 // IsPreloadedSTS returns true if the canonicalized hostname should always be |
| 483 // considered to have STS enabled. | 483 // considered to have STS enabled. |
| 484 // static | 484 // static |
| 485 bool TransportSecurityState::IsPreloadedSTS( | 485 bool TransportSecurityState::IsPreloadedSTS( |
| 486 const std::string& canonicalized_host, | 486 const std::string& canonicalized_host, |
| 487 bool sni_available, | 487 bool sni_available, |
| 488 bool *include_subdomains) { | 488 DomainState* out) { |
| 489 out->preloaded = true; | |
| 490 out->mode = DomainState::MODE_STRICT; | |
| 491 out->created = out->expiry = base::Time::FromTimeT(0); | |
|
abarth-chromium
2011/04/17 05:39:23
I'm not sure this is allowed by the style guide.
| |
| 492 out->include_subdomains = false; | |
| 493 | |
| 494 std::string cmd_line_hsts = | |
| 495 CommandLine::ForCurrentProcess()->GetSwitchValueASCII("hsts-hosts"); | |
|
abarth-chromium
2011/04/17 05:39:23
Command line constants should be in a switches.cc
| |
| 496 if (!cmd_line_hsts.empty()) { | |
| 497 std::vector<std::string> hosts; | |
| 498 base::SplitString(cmd_line_hsts, ',', &hosts); | |
| 499 for (std::vector<std::string>::const_iterator i = hosts.begin(); | |
| 500 i != hosts.end(); | |
| 501 ++i) { | |
|
abarth-chromium
2011/04/17 05:39:23
I would merge these last two lines.
| |
| 502 std::string host_spec = *i; | |
| 503 std::vector<std::string> parts; | |
| 504 base::SplitString(host_spec, ':', &parts); | |
| 505 if (parts.size() != 4) | |
| 506 continue; | |
| 507 std::string host = CanonicalizeHost(parts[0]); | |
| 508 if (host != canonicalized_host) | |
| 509 continue; | |
| 510 if (parts[1] == "none") | |
| 511 out->mode = DomainState::MODE_NONE; | |
| 512 else if (parts[1] != "strict") | |
| 513 continue; | |
| 514 if (parts[2] == "true") | |
|
abarth-chromium
2011/04/17 05:39:23
True? Why not "includeSubdomains" ?
| |
| 515 out->include_subdomains = true; | |
| 516 if (!parts[3].empty()) { | |
| 517 std::vector<SHA1Fingerprint> fingerprints; | |
| 518 std::vector<std::string> str_fingerprints; | |
| 519 base::SplitString(parts[3], '|', &str_fingerprints); | |
|
abarth-chromium
2011/04/17 05:39:23
This is getting complicated.
| |
| 520 std::vector<std::string>::const_iterator j; | |
| 521 for (j = str_fingerprints.begin(); j != str_fingerprints.end(); ++j) { | |
| 522 AddHash(*j, &fingerprints); | |
| 523 } | |
| 524 out->public_key_hashes = fingerprints; | |
| 525 } | |
| 526 return true; | |
| 527 } | |
| 528 } | |
| 529 | |
| 489 // In the medium term this list is likely to just be hardcoded here. This, | 530 // 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. | 531 // slightly odd, form removes the need for additional relocations records. |
| 491 static const struct { | 532 static const struct { |
| 492 uint8 length; | 533 uint8 length; |
| 493 bool include_subdomains; | 534 bool include_subdomains; |
| 494 char dns_name[30]; | 535 char dns_name[30]; |
| 495 } kPreloadedSTS[] = { | 536 } kPreloadedSTS[] = { |
| 496 {16, false, "\003www\006paypal\003com"}, | 537 {16, false, "\003www\006paypal\003com"}, |
| 497 {16, false, "\003www\006elanex\003biz"}, | 538 {16, false, "\003www\006elanex\003biz"}, |
| 498 {12, true, "\006jottit\003com"}, | 539 {12, true, "\006jottit\003com"}, |
| (...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 546 }; | 587 }; |
| 547 static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); | 588 static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); |
| 548 | 589 |
| 549 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | 590 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { |
| 550 for (size_t j = 0; j < kNumPreloadedSTS; j++) { | 591 for (size_t j = 0; j < kNumPreloadedSTS; j++) { |
| 551 if (kPreloadedSTS[j].length == canonicalized_host.size() - i && | 592 if (kPreloadedSTS[j].length == canonicalized_host.size() - i && |
| 552 memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i], | 593 memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i], |
| 553 kPreloadedSTS[j].length) == 0) { | 594 kPreloadedSTS[j].length) == 0) { |
| 554 if (!kPreloadedSTS[j].include_subdomains && i != 0) | 595 if (!kPreloadedSTS[j].include_subdomains && i != 0) |
| 555 return false; | 596 return false; |
| 556 *include_subdomains = kPreloadedSTS[j].include_subdomains; | 597 out->include_subdomains = kPreloadedSTS[j].include_subdomains; |
| 557 return true; | 598 return true; |
| 558 } | 599 } |
| 559 } | 600 } |
| 560 if (sni_available) { | 601 if (sni_available) { |
| 561 for (size_t j = 0; j < kNumPreloadedSNISTS; j++) { | 602 for (size_t j = 0; j < kNumPreloadedSNISTS; j++) { |
| 562 if (kPreloadedSNISTS[j].length == canonicalized_host.size() - i && | 603 if (kPreloadedSNISTS[j].length == canonicalized_host.size() - i && |
| 563 memcmp(kPreloadedSNISTS[j].dns_name, &canonicalized_host[i], | 604 memcmp(kPreloadedSNISTS[j].dns_name, &canonicalized_host[i], |
| 564 kPreloadedSNISTS[j].length) == 0) { | 605 kPreloadedSNISTS[j].length) == 0) { |
| 565 if (!kPreloadedSNISTS[j].include_subdomains && i != 0) | 606 if (!kPreloadedSNISTS[j].include_subdomains && i != 0) |
| 566 return false; | 607 return false; |
| 567 *include_subdomains = kPreloadedSNISTS[j].include_subdomains; | 608 out->include_subdomains = kPreloadedSNISTS[j].include_subdomains; |
| 568 return true; | 609 return true; |
| 569 } | 610 } |
| 570 } | 611 } |
| 571 } | 612 } |
| 572 } | 613 } |
| 573 | 614 |
| 574 return false; | 615 return false; |
| 575 } | 616 } |
| 576 | 617 |
| 577 static std::string HashesToBase64String( | 618 static std::string HashesToBase64String( |
| (...skipping 28 matching lines...) Expand all Loading... | |
| 606 | 647 |
| 607 for (std::vector<net::SHA1Fingerprint>::const_iterator | 648 for (std::vector<net::SHA1Fingerprint>::const_iterator |
| 608 i = hashes.begin(); i != hashes.end(); ++i) { | 649 i = hashes.begin(); i != hashes.end(); ++i) { |
| 609 for (std::vector<net::SHA1Fingerprint>::const_iterator | 650 for (std::vector<net::SHA1Fingerprint>::const_iterator |
| 610 j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) { | 651 j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) { |
| 611 if (i->Equals(*j)) | 652 if (i->Equals(*j)) |
| 612 return true; | 653 return true; |
| 613 } | 654 } |
| 614 } | 655 } |
| 615 | 656 |
| 616 | |
| 617 LOG(ERROR) << "Rejecting public key chain for domain " << domain | 657 LOG(ERROR) << "Rejecting public key chain for domain " << domain |
| 618 << ". Validated chain: " << HashesToBase64String(hashes) | 658 << ". Validated chain: " << HashesToBase64String(hashes) |
| 619 << ", expected: " << HashesToBase64String(public_key_hashes); | 659 << ", expected: " << HashesToBase64String(public_key_hashes); |
| 620 | 660 |
| 621 return false; | 661 return false; |
| 622 } | 662 } |
| 623 | 663 |
| 624 } // namespace | 664 } // namespace |
| OLD | NEW |