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