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/command_line.h" |
| 9 #include "base/json/json_reader.h" | 9 #include "base/json/json_reader.h" |
| 10 #include "base/json/json_writer.h" | 10 #include "base/json/json_writer.h" |
| (...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 329 base::JSONWriter::Write(&toplevel, true /* pretty print */, output); | 329 base::JSONWriter::Write(&toplevel, true /* pretty print */, output); |
| 330 return true; | 330 return true; |
| 331 } | 331 } |
| 332 | 332 |
| 333 bool TransportSecurityState::LoadEntries(const std::string& input, | 333 bool TransportSecurityState::LoadEntries(const std::string& input, |
| 334 bool* dirty) { | 334 bool* dirty) { |
| 335 enabled_hosts_.clear(); | 335 enabled_hosts_.clear(); |
| 336 return Deserialise(input, dirty, &enabled_hosts_); | 336 return Deserialise(input, dirty, &enabled_hosts_); |
| 337 } | 337 } |
| 338 | 338 |
| 339 static bool AddHash(const std::string& type_and_base64, | |
| 340 std::vector<SHA1Fingerprint>* out) { | |
| 341 std::string hash_str; | |
| 342 if (type_and_base64.find("sha1/") == 0 && | |
| 343 base::Base64Decode(type_and_base64.substr(5, type_and_base64.size() - 5), | |
| 344 &hash_str) && | |
| 345 hash_str.size() == base::SHA1_LENGTH) { | |
| 346 SHA1Fingerprint hash; | |
| 347 memcpy(hash.data, hash_str.data(), sizeof(hash.data)); | |
| 348 out->push_back(hash); | |
| 349 return true; | |
| 350 } | |
| 351 return false; | |
| 352 } | |
| 353 | |
| 339 // static | 354 // static |
| 340 bool TransportSecurityState::Deserialise( | 355 bool TransportSecurityState::Deserialise( |
| 341 const std::string& input, | 356 const std::string& input, |
| 342 bool* dirty, | 357 bool* dirty, |
| 343 std::map<std::string, DomainState>* out) { | 358 std::map<std::string, DomainState>* out) { |
| 344 scoped_ptr<Value> value( | 359 scoped_ptr<Value> value( |
| 345 base::JSONReader::Read(input, false /* do not allow trailing commas */)); | 360 base::JSONReader::Read(input, false /* do not allow trailing commas */)); |
| 346 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) | 361 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) |
| 347 return false; | 362 return false; |
| 348 | 363 |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 366 !state->GetDouble("expiry", &expiry)) { | 381 !state->GetDouble("expiry", &expiry)) { |
| 367 continue; | 382 continue; |
| 368 } | 383 } |
| 369 | 384 |
| 370 ListValue* pins_list = NULL; | 385 ListValue* pins_list = NULL; |
| 371 std::vector<SHA1Fingerprint> public_key_hashes; | 386 std::vector<SHA1Fingerprint> public_key_hashes; |
| 372 if (state->GetList("public_key_hashes", &pins_list)) { | 387 if (state->GetList("public_key_hashes", &pins_list)) { |
| 373 size_t num_pins = pins_list->GetSize(); | 388 size_t num_pins = pins_list->GetSize(); |
| 374 for (size_t i = 0; i < num_pins; ++i) { | 389 for (size_t i = 0; i < num_pins; ++i) { |
| 375 std::string type_and_base64; | 390 std::string type_and_base64; |
| 376 std::string hash_str; | 391 if (pins_list->GetString(i, &type_and_base64)) |
| 377 SHA1Fingerprint hash; | 392 AddHash(type_and_base64, &public_key_hashes); |
| 378 if (pins_list->GetString(i, &type_and_base64) && | |
| 379 type_and_base64.find("sha1/") == 0 && | |
| 380 base::Base64Decode( | |
| 381 type_and_base64.substr(5, type_and_base64.size() - 5), | |
| 382 &hash_str) && | |
| 383 hash_str.size() == base::SHA1_LENGTH) { | |
| 384 memcpy(hash.data, hash_str.data(), sizeof(hash.data)); | |
| 385 public_key_hashes.push_back(hash); | |
| 386 } | |
| 387 } | 393 } |
| 388 } | 394 } |
| 389 | 395 |
| 390 DomainState::Mode mode; | 396 DomainState::Mode mode; |
| 391 if (mode_string == "strict") { | 397 if (mode_string == "strict") { |
| 392 mode = DomainState::MODE_STRICT; | 398 mode = DomainState::MODE_STRICT; |
| 393 } else if (mode_string == "opportunistic") { | 399 } else if (mode_string == "opportunistic") { |
| 394 mode = DomainState::MODE_OPPORTUNISTIC; | 400 mode = DomainState::MODE_OPPORTUNISTIC; |
| 395 } else if (mode_string == "spdy-only") { | 401 } else if (mode_string == "spdy-only") { |
| 396 mode = DomainState::MODE_SPDY_ONLY; | 402 mode = DomainState::MODE_SPDY_ONLY; |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 475 // step 3(b) | 481 // step 3(b) |
| 476 if (new_host[i + 1] == '-' || | 482 if (new_host[i + 1] == '-' || |
| 477 new_host[i + label_length] == '-') { | 483 new_host[i + label_length] == '-') { |
| 478 return std::string(); | 484 return std::string(); |
| 479 } | 485 } |
| 480 } | 486 } |
| 481 | 487 |
| 482 return new_host; | 488 return new_host; |
| 483 } | 489 } |
| 484 | 490 |
| 491 struct HSTSPreload { | |
| 492 uint8 length; | |
| 493 bool include_subdomains; | |
| 494 char dns_name[30]; | |
| 495 bool https_required; | |
| 496 const char** required_hashes; | |
| 497 }; | |
| 498 | |
| 499 static bool HasPreload(const struct HSTSPreload* entries, size_t num_entries, | |
| 500 const std::string& canonicalized_host, size_t i, | |
| 501 TransportSecurityState::DomainState* out, bool* ret) { | |
| 502 for (size_t j = 0; j < num_entries; j++) { | |
| 503 if (entries[j].length == canonicalized_host.size() - i && | |
| 504 memcmp(entries[j].dns_name, &canonicalized_host[i], | |
| 505 entries[j].length) == 0) { | |
| 506 if (!entries[j].include_subdomains && i != 0) { | |
| 507 *ret = false; | |
| 508 } else { | |
| 509 out->include_subdomains = entries[j].include_subdomains; | |
| 510 *ret = true; | |
| 511 if (!entries[j].https_required) | |
| 512 out->mode = TransportSecurityState::DomainState::MODE_NONE; | |
| 513 if (entries[j].required_hashes) { | |
| 514 const char** hash = entries[j].required_hashes; | |
| 515 while (*hash) { | |
| 516 bool ok = AddHash(*hash, &out->public_key_hashes); | |
| 517 DCHECK(ok); | |
| 518 hash++; | |
| 519 } | |
| 520 } | |
| 521 } | |
| 522 return true; | |
| 523 } | |
| 524 } | |
| 525 return false; | |
| 526 } | |
| 527 | |
| 485 // IsPreloadedSTS returns true if the canonicalized hostname should always be | 528 // IsPreloadedSTS returns true if the canonicalized hostname should always be |
| 486 // considered to have STS enabled. | 529 // considered to have STS enabled. |
| 487 // static | 530 // static |
| 488 bool TransportSecurityState::IsPreloadedSTS( | 531 bool TransportSecurityState::IsPreloadedSTS( |
| 489 const std::string& canonicalized_host, | 532 const std::string& canonicalized_host, |
| 490 bool sni_available, | 533 bool sni_available, |
| 491 DomainState* out) { | 534 DomainState* out) { |
| 492 out->preloaded = true; | 535 out->preloaded = true; |
| 493 out->mode = DomainState::MODE_STRICT; | 536 out->mode = DomainState::MODE_STRICT; |
| 494 out->created = base::Time::FromTimeT(0); | 537 out->created = base::Time::FromTimeT(0); |
| 495 out->expiry = out->created; | 538 out->expiry = out->created; |
| 496 out->include_subdomains = false; | 539 out->include_subdomains = false; |
| 497 | 540 |
| 498 std::map<std::string, DomainState> hosts; | 541 std::map<std::string, DomainState> hosts; |
| 499 std::string cmd_line_hsts = | 542 std::string cmd_line_hsts = |
| 500 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( | 543 CommandLine::ForCurrentProcess()->GetSwitchValueASCII( |
| 501 switches::kHstsHosts); | 544 switches::kHstsHosts); |
| 502 if (!cmd_line_hsts.empty()) { | 545 if (!cmd_line_hsts.empty()) { |
| 503 bool dirty; | 546 bool dirty; |
| 504 Deserialise(cmd_line_hsts, &dirty, &hosts); | 547 Deserialise(cmd_line_hsts, &dirty, &hosts); |
| 505 } | 548 } |
| 506 | 549 |
| 550 // These hashes are base64 encodings of SHA1 hashes for cert public keys. | |
| 551 static const char* kCertPKHashVerisignClass3 = | |
|
agl
2011/04/25 19:39:46
I'm conflicted about parsing these strings all the
agl
2011/04/25 19:39:46
Need to change all of these from "const char*" to
| |
| 552 "sha1/4n972HfV354KP560yw4uqe/baXc="; | |
| 553 static const char* kCertPKHashVerisignClass3G3 = | |
| 554 "sha1/IvGeLsbqzPxdI0b0wuj2xVTdXgc="; | |
| 555 static const char* kCertPKHashGoogle1024 = | |
| 556 "sha1/QMVAHW+MuvCLAO3vse6H0AWzuc0="; | |
| 557 static const char* kCertPKHashGoogle2048 = | |
| 558 "sha1/AbkhxY0L343gKf+cki7NVWp+ozk="; | |
| 559 static const char* kCertPKHashEquifaxSecureCA = | |
| 560 "sha1/SOZo+SvSspXXR9gjIBBPM5iQn9Q="; | |
| 561 static const char* kCertPKHashGeoTrustGlobalCA = | |
| 562 "sha1/wHqYaI2J+6sFZAwRfap9ZbjKzE4="; | |
| 563 static const char* kGoogleAcceptableCerts[] = { | |
| 564 kCertPKHashVerisignClass3, | |
| 565 kCertPKHashVerisignClass3G3, | |
| 566 kCertPKHashGoogle1024, | |
| 567 kCertPKHashGoogle2048, | |
| 568 kCertPKHashEquifaxSecureCA, | |
| 569 kCertPKHashGeoTrustGlobalCA, | |
| 570 0, | |
| 571 }; | |
| 572 | |
| 507 // In the medium term this list is likely to just be hardcoded here. This, | 573 // In the medium term this list is likely to just be hardcoded here. This, |
| 508 // slightly odd, form removes the need for additional relocations records. | 574 // slightly odd, form removes the need for additional relocations records. |
| 509 static const struct { | 575 static const struct HSTSPreload kPreloadedSTS[] = { |
| 510 uint8 length; | 576 {16, false, "\003www\006paypal\003com", true, 0 }, |
| 511 bool include_subdomains; | 577 {16, false, "\003www\006elanex\003biz", true, 0 }, |
| 512 char dns_name[30]; | 578 {12, true, "\006jottit\003com", true, 0 }, |
| 513 } kPreloadedSTS[] = { | 579 {19, true, "\015sunshinepress\003org", true, 0 }, |
| 514 {16, false, "\003www\006paypal\003com"}, | 580 {21, false, "\003www\013noisebridge\003net", true, 0 }, |
| 515 {16, false, "\003www\006elanex\003biz"}, | 581 {10, false, "\004neg9\003org", true, 0 }, |
| 516 {12, true, "\006jottit\003com"}, | 582 {12, true, "\006riseup\003net", true, 0 }, |
| 517 {19, true, "\015sunshinepress\003org"}, | 583 {11, false, "\006factor\002cc", true, 0 }, |
| 518 {21, false, "\003www\013noisebridge\003net"}, | 584 {22, false, "\007members\010mayfirst\003org", true, 0 }, |
| 519 {10, false, "\004neg9\003org"}, | 585 {22, false, "\007support\010mayfirst\003org", true, 0 }, |
| 520 {12, true, "\006riseup\003net"}, | 586 {17, false, "\002id\010mayfirst\003org", true, 0 }, |
| 521 {11, false, "\006factor\002cc"}, | 587 {20, false, "\005lists\010mayfirst\003org", true, 0 }, |
| 522 {22, false, "\007members\010mayfirst\003org"}, | 588 {19, true, "\015splendidbacon\003com", true, 0 }, |
| 523 {22, false, "\007support\010mayfirst\003org"}, | 589 {19, true, "\006health\006google\003com", true, 0 }, |
| 524 {17, false, "\002id\010mayfirst\003org"}, | 590 {21, true, "\010checkout\006google\003com", true, 0 }, |
| 525 {20, false, "\005lists\010mayfirst\003org"}, | 591 {19, true, "\006chrome\006google\003com", true, kGoogleAcceptableCerts }, |
| 526 {19, true, "\015splendidbacon\003com"}, | 592 {26, false, "\006latest\006chrome\006google\003com", true, 0 }, |
| 527 {19, true, "\006health\006google\003com"}, | 593 {28, false, "\016aladdinschools\007appspot\003com", true, 0 }, |
| 528 {21, true, "\010checkout\006google\003com"}, | 594 {14, true, "\011ottospora\002nl", true, 0 }, |
| 529 {19, true, "\006chrome\006google\003com"}, | 595 {17, true, "\004docs\006google\003com", true, 0 }, |
| 530 {26, false, "\006latest\006chrome\006google\003com"}, | 596 {18, true, "\005sites\006google\003com", true, 0 }, |
| 531 {28, false, "\016aladdinschools\007appspot\003com"}, | 597 {25, true, "\014spreadsheets\006google\003com", true, 0 }, |
| 532 {14, true, "\011ottospora\002nl"}, | 598 {22, false, "\011appengine\006google\003com", true, 0 }, |
| 533 {17, true, "\004docs\006google\003com"}, | 599 {25, false, "\003www\017paycheckrecords\003com", true, 0 }, |
| 534 {18, true, "\005sites\006google\003com"}, | 600 {20, true, "\006market\007android\003com", true, 0 }, |
| 535 {25, true, "\014spreadsheets\006google\003com"}, | 601 {14, false, "\010lastpass\003com", true, 0 }, |
| 536 {22, false, "\011appengine\006google\003com"}, | 602 {18, false, "\003www\010lastpass\003com", true, 0 }, |
| 537 {25, false, "\003www\017paycheckrecords\003com"}, | 603 {14, true, "\010keyerror\003com", true, 0 }, |
| 538 {20, true, "\006market\007android\003com"}, | 604 {22, true, "\011encrypted\006google\003com", true, 0 }, |
| 539 {14, false, "\010lastpass\003com"}, | 605 {13, false, "\010entropia\002de", true, 0 }, |
| 540 {18, false, "\003www\010lastpass\003com"}, | 606 {17, false, "\003www\010entropia\002de", true, 0 }, |
| 541 {14, true, "\010keyerror\003com"}, | 607 {21, true, "\010accounts\006google\003com", true, 0 }, |
| 542 {22, true, "\011encrypted\006google\003com"}, | |
| 543 {13, false, "\010entropia\002de"}, | |
| 544 {17, false, "\003www\010entropia\002de"}, | |
| 545 {21, true, "\010accounts\006google\003com"}, | |
| 546 #if defined(OS_CHROMEOS) | 608 #if defined(OS_CHROMEOS) |
| 547 {17, true, "\004mail\006google\003com"}, | 609 {17, true, "\004mail\006google\003com", true, 0 }, |
| 548 {13, false, "\007twitter\003com"}, | 610 {13, false, "\007twitter\003com", true, 0 }, |
| 549 {17, false, "\003www\007twitter\003com"}, | 611 {17, false, "\003www\007twitter\003com", true, 0 }, |
| 550 {17, false, "\003api\007twitter\003com"}, | 612 {17, false, "\003api\007twitter\003com", true, 0 }, |
| 551 {17, false, "\003dev\007twitter\003com"}, | 613 {17, false, "\003dev\007twitter\003com", true, 0}, |
| 552 {22, false, "\010business\007twitter\003com"}, | 614 {22, false, "\010business\007twitter\003com", true, 0 }, |
| 553 #endif | 615 #endif |
| 554 }; | 616 }; |
| 555 static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS); | 617 static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS); |
| 556 | 618 |
| 557 static const struct { | 619 static const struct HSTSPreload kPreloadedSNISTS[] = { |
| 558 uint8 length; | 620 {11, true, "\005gmail\003com", true, 0 }, |
| 559 bool include_subdomains; | 621 {16, true, "\012googlemail\003com", true, 0 }, |
| 560 char dns_name[30]; | |
| 561 } kPreloadedSNISTS[] = { | |
| 562 {11, true, "\005gmail\003com"}, | |
| 563 {16, true, "\012googlemail\003com"}, | |
| 564 }; | 622 }; |
| 565 static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); | 623 static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); |
| 566 | 624 |
| 567 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { | 625 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { |
| 568 std::string host_sub_chunk(&canonicalized_host[i], | 626 std::string host_sub_chunk(&canonicalized_host[i], |
| 569 canonicalized_host.size() - i); | 627 canonicalized_host.size() - i); |
| 570 out->domain = DNSDomainToString(host_sub_chunk); | 628 out->domain = DNSDomainToString(host_sub_chunk); |
| 571 std::string hashed_host(HashHost(host_sub_chunk)); | 629 std::string hashed_host(HashHost(host_sub_chunk)); |
| 572 if (hosts.find(hashed_host) != hosts.end()) { | 630 if (hosts.find(hashed_host) != hosts.end()) { |
| 573 *out = hosts[hashed_host]; | 631 *out = hosts[hashed_host]; |
| 574 out->domain = DNSDomainToString(host_sub_chunk); | 632 out->domain = DNSDomainToString(host_sub_chunk); |
| 575 out->preloaded = true; | 633 out->preloaded = true; |
| 576 return true; | 634 return true; |
| 577 } | 635 } |
| 578 for (size_t j = 0; j < kNumPreloadedSTS; j++) { | 636 bool ret; |
| 579 if (kPreloadedSTS[j].length == canonicalized_host.size() - i && | 637 if (HasPreload(kPreloadedSTS, kNumPreloadedSTS, canonicalized_host, i, out, |
| 580 memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i], | 638 &ret)) |
|
agl
2011/04/25 19:39:46
I think you should have { } around this if body be
| |
| 581 kPreloadedSTS[j].length) == 0) { | 639 return ret; |
| 582 if (!kPreloadedSTS[j].include_subdomains && i != 0) | 640 if (sni_available && |
| 583 return false; | 641 HasPreload(kPreloadedSNISTS, kNumPreloadedSNISTS, canonicalized_host, i, |
|
agl
2011/04/25 19:39:46
ditto.
| |
| 584 out->include_subdomains = kPreloadedSTS[j].include_subdomains; | 642 out, &ret)) |
| 585 return true; | 643 return ret; |
| 586 } | |
| 587 } | |
| 588 if (sni_available) { | |
| 589 for (size_t j = 0; j < kNumPreloadedSNISTS; j++) { | |
| 590 if (kPreloadedSNISTS[j].length == canonicalized_host.size() - i && | |
| 591 memcmp(kPreloadedSNISTS[j].dns_name, &canonicalized_host[i], | |
| 592 kPreloadedSNISTS[j].length) == 0) { | |
| 593 if (!kPreloadedSNISTS[j].include_subdomains && i != 0) | |
| 594 return false; | |
| 595 out->include_subdomains = kPreloadedSNISTS[j].include_subdomains; | |
| 596 return true; | |
| 597 } | |
| 598 } | |
| 599 } | |
| 600 } | 644 } |
| 601 | 645 |
| 602 return false; | 646 return false; |
| 603 } | 647 } |
| 604 | 648 |
| 605 static std::string HashesToBase64String( | 649 static std::string HashesToBase64String( |
| 606 const std::vector<net::SHA1Fingerprint>& hashes) { | 650 const std::vector<net::SHA1Fingerprint>& hashes) { |
| 607 std::vector<std::string> hashes_strs; | 651 std::vector<std::string> hashes_strs; |
| 608 for (std::vector<net::SHA1Fingerprint>::const_iterator | 652 for (std::vector<net::SHA1Fingerprint>::const_iterator |
| 609 i = hashes.begin(); i != hashes.end(); i++) { | 653 i = hashes.begin(); i != hashes.end(); i++) { |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 642 } | 686 } |
| 643 | 687 |
| 644 LOG(ERROR) << "Rejecting public key chain for domain " << domain | 688 LOG(ERROR) << "Rejecting public key chain for domain " << domain |
| 645 << ". Validated chain: " << HashesToBase64String(hashes) | 689 << ". Validated chain: " << HashesToBase64String(hashes) |
| 646 << ", expected: " << HashesToBase64String(public_key_hashes); | 690 << ", expected: " << HashesToBase64String(public_key_hashes); |
| 647 | 691 |
| 648 return false; | 692 return false; |
| 649 } | 693 } |
| 650 | 694 |
| 651 } // namespace | 695 } // namespace |
| OLD | NEW |