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 3// found in the LICENSE file. 4 5#include "net/base/transport_security_state.h" 6 7#include "base/base64.h" 8#include "base/command_line.h" 9#include "base/json/json_reader.h" 10#include "base/json/json_writer.h" 11#include "base/logging.h" 12#include "base/memory/scoped_ptr.h" 13#include "base/sha1.h" 14#include "base/string_number_conversions.h" 15#include "base/string_split.h" 16#include "base/string_tokenizer.h" 17#include "base/string_util.h" 18#include "base/utf_string_conversions.h" 19#include "base/values.h" 20#include "crypto/sha2.h" 21#include "googleurl/src/gurl.h" 22#include "net/base/dns_util.h" 23#include "net/base/net_switches.h" 24 25namespace net { 26 27const long int TransportSecurityState::kMaxHSTSAgeSecs = 86400 * 365; // 1 year 28 29TransportSecurityState::TransportSecurityState() 30 : delegate_(NULL) { 31} 32 33static 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 39void TransportSecurityState::EnableHost(const std::string& host, 40 const DomainState& state) { 41 const std::string canonicalized_host = CanonicalizeHost(host); 42 if (canonicalized_host.empty()) 43 return; 44 45 // TODO(cevans) -- we likely want to permit a host to override a built-in, 46 // for at least the case where the override is stricter (i.e. includes 47 // subdomains, or includes certificate pinning). 48 DomainState temp; 49 if (IsPreloadedSTS(canonicalized_host, true, &temp)) 50 return; 51 52 // Use the original creation date if we already have this host. 53 DomainState state_copy(state); 54 DomainState existing_state; 55 if (IsEnabledForHost(&existing_state, host, true)) 56 state_copy.created = existing_state.created; 57 58 // We don't store these values. 59 state_copy.preloaded = false; 60 state_copy.domain.clear(); 61 62 enabled_hosts_[HashHost(canonicalized_host)] = state_copy; 63 DirtyNotify(); 64} 65 66bool TransportSecurityState::DeleteHost(const std::string& host) { 67 const std::string canonicalized_host = CanonicalizeHost(host); 68 if (canonicalized_host.empty()) 69 return false; 70 71 std::map<std::string, DomainState>::iterator i = enabled_hosts_.find( 72 HashHost(canonicalized_host)); 73 if (i != enabled_hosts_.end()) { 74 enabled_hosts_.erase(i); 75 DirtyNotify(); 76 return true; 77 } 78 return false; 79} 80 81// IncludeNUL converts a char* to a std::string and includes the terminating 82// NUL in the result. 83static std::string IncludeNUL(const char* in) { 84 return std::string(in, strlen(in) + 1); 85} 86 87bool TransportSecurityState::IsEnabledForHost(DomainState* result, 88 const std::string& host, 89 bool sni_available) { 90 const std::string canonicalized_host = CanonicalizeHost(host); 91 if (canonicalized_host.empty()) 92 return false; 93 94 if (IsPreloadedSTS(canonicalized_host, sni_available, result)) 95 return result->mode != DomainState::MODE_NONE; 96 97 *result = DomainState(); 98 99 base::Time current_time(base::Time::Now()); 100 101 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { 102 std::string hashed_domain(HashHost(IncludeNUL(&canonicalized_host[i]))); 103 104 std::map<std::string, DomainState>::iterator j = 105 enabled_hosts_.find(hashed_domain); 106 if (j == enabled_hosts_.end()) 107 continue; 108 109 if (current_time > j->second.expiry) { 110 enabled_hosts_.erase(j); 111 DirtyNotify(); 112 continue; 113 } 114 115 *result = j->second; 116 result->domain = DNSDomainToString( 117 canonicalized_host.substr(i, canonicalized_host.size() - i)); 118 119 // If we matched the domain exactly, it doesn't matter what the value of 120 // include_subdomains is. 121 if (i == 0) 122 return true; 123 124 return j->second.include_subdomains; 125 } 126 127 return false; 128} 129 130void TransportSecurityState::DeleteSince(const base::Time& time) { 131 bool dirtied = false; 132 133 std::map<std::string, DomainState>::iterator i = enabled_hosts_.begin(); 134 while (i != enabled_hosts_.end()) { 135 if (i->second.created >= time) { 136 dirtied = true; 137 enabled_hosts_.erase(i++); 138 } else { 139 i++; 140 } 141 } 142 143 if (dirtied) 144 DirtyNotify(); 145} 146 147// MaxAgeToInt converts a string representation of a number of seconds into a 148// int. We use strtol in order to handle overflow correctly. The string may 149// contain an arbitary number which we should truncate correctly rather than 150// throwing a parse failure. 151static bool MaxAgeToInt(std::string::const_iterator begin, 152 std::string::const_iterator end, 153 int* result) { 154 const std::string s(begin, end); 155 char* endptr; 156 long int i = strtol(s.data(), &endptr, 10 /* base */); 157 if (*endptr || i < 0) 158 return false; 159 if (i > TransportSecurityState::kMaxHSTSAgeSecs) 160 i = TransportSecurityState::kMaxHSTSAgeSecs; 161 *result = i; 162 return true; 163} 164 165// "Strict-Transport-Security" ":" 166// "max-age" "=" delta-seconds [ ";" "includeSubDomains" ] 167bool TransportSecurityState::ParseHeader(const std::string& value, 168 int* max_age, 169 bool* include_subdomains) { 170 DCHECK(max_age); 171 DCHECK(include_subdomains); 172 173 int max_age_candidate = 0; 174 175 enum ParserState { 176 START, 177 AFTER_MAX_AGE_LABEL, 178 AFTER_MAX_AGE_EQUALS, 179 AFTER_MAX_AGE, 180 AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER, 181 AFTER_INCLUDE_SUBDOMAINS, 182 } state = START; 183 184 StringTokenizer tokenizer(value, " \t=;"); 185 tokenizer.set_options(StringTokenizer::RETURN_DELIMS); 186 while (tokenizer.GetNext()) { 187 DCHECK(!tokenizer.token_is_delim() || tokenizer.token().length() == 1); 188 switch (state) { 189 case START: 190 if (IsAsciiWhitespace(*tokenizer.token_begin())) 191 continue; 192 if (!LowerCaseEqualsASCII(tokenizer.token(), "max-age")) 193 return false; 194 state = AFTER_MAX_AGE_LABEL; 195 break; 196 197 case AFTER_MAX_AGE_LABEL: 198 if (IsAsciiWhitespace(*tokenizer.token_begin())) 199 continue; 200 if (*tokenizer.token_begin() != '=') 201 return false; 202 DCHECK(tokenizer.token().length() == 1); 203 state = AFTER_MAX_AGE_EQUALS; 204 break; 205 206 case AFTER_MAX_AGE_EQUALS: 207 if (IsAsciiWhitespace(*tokenizer.token_begin())) 208 continue; 209 if (!MaxAgeToInt(tokenizer.token_begin(), 210 tokenizer.token_end(), 211 &max_age_candidate)) 212 return false; 213 state = AFTER_MAX_AGE; 214 break; 215 216 case AFTER_MAX_AGE: 217 if (IsAsciiWhitespace(*tokenizer.token_begin())) 218 continue; 219 if (*tokenizer.token_begin() != ';') 220 return false; 221 state = AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER; 222 break; 223 224 case AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER: 225 if (IsAsciiWhitespace(*tokenizer.token_begin())) 226 continue; 227 if (!LowerCaseEqualsASCII(tokenizer.token(), "includesubdomains")) 228 return false; 229 state = AFTER_INCLUDE_SUBDOMAINS; 230 break; 231 232 case AFTER_INCLUDE_SUBDOMAINS: 233 if (!IsAsciiWhitespace(*tokenizer.token_begin())) 234 return false; 235 break; 236 237 default: 238 NOTREACHED(); 239 } 240 } 241 242 // We've consumed all the input. Let's see what state we ended up in. 243 switch (state) { 244 case START: 245 case AFTER_MAX_AGE_LABEL: 246 case AFTER_MAX_AGE_EQUALS: 247 return false; 248 case AFTER_MAX_AGE: 249 *max_age = max_age_candidate; 250 *include_subdomains = false; 251 return true; 252 case AFTER_MAX_AGE_INCLUDE_SUB_DOMAINS_DELIMITER: 253 return false; 254 case AFTER_INCLUDE_SUBDOMAINS: 255 *max_age = max_age_candidate; 256 *include_subdomains = true; 257 return true; 258 default: 259 NOTREACHED(); 260 return false; 261 } 262} 263 264void TransportSecurityState::SetDelegate( 265 TransportSecurityState::Delegate* delegate) { 266 delegate_ = delegate; 267} 268 269// This function converts the binary hashes, which we store in 270// |enabled_hosts_|, to a base64 string which we can include in a JSON file. 271static std::string HashedDomainToExternalString(const std::string& hashed) { 272 std::string out; 273 CHECK(base::Base64Encode(hashed, &out)); 274 return out; 275} 276 277// This inverts |HashedDomainToExternalString|, above. It turns an external 278// string (from a JSON file) into an internal (binary) string. 279static std::string ExternalStringToHashedDomain(const std::string& external) { 280 std::string out; 281 if (!base::Base64Decode(external, &out) || 282 out.size() != crypto::SHA256_LENGTH) { 283 return std::string(); 284 } 285 286 return out; 287} 288 289bool TransportSecurityState::Serialise(std::string* output) { 290 DictionaryValue toplevel; 291 for (std::map<std::string, DomainState>::const_iterator 292 i = enabled_hosts_.begin(); i != enabled_hosts_.end(); ++i) { 293 DictionaryValue* state = new DictionaryValue; 294 state->SetBoolean("include_subdomains", i->second.include_subdomains); 295 state->SetDouble("created", i->second.created.ToDoubleT()); 296 state->SetDouble("expiry", i->second.expiry.ToDoubleT()); 297 298 switch (i->second.mode) { 299 case DomainState::MODE_STRICT: 300 state->SetString("mode", "strict"); 301 break; 302 case DomainState::MODE_OPPORTUNISTIC: 303 state->SetString("mode", "opportunistic"); 304 break; 305 case DomainState::MODE_SPDY_ONLY: 306 state->SetString("mode", "spdy-only"); 307 break; 308 default: 309 NOTREACHED() << "DomainState with unknown mode"; 310 delete state; 311 continue; 312 } 313 314 ListValue* pins = new ListValue; 315 for (std::vector<SHA1Fingerprint>::const_iterator 316 j = i->second.public_key_hashes.begin(); 317 j != i->second.public_key_hashes.end(); ++j) { 318 std::string hash_str(reinterpret_cast<const char*>(j->data), 319 sizeof(j->data)); 320 std::string b64; 321 base::Base64Encode(hash_str, &b64); 322 pins->Append(new StringValue("sha1/" + b64)); 323 } 324 state->Set("public_key_hashes", pins); 325 326 toplevel.Set(HashedDomainToExternalString(i->first), state); 327 } 328 329 base::JSONWriter::Write(&toplevel, true /* pretty print */, output); 330 return true; 331} 332 333bool TransportSecurityState::LoadEntries(const std::string& input, 334 bool* dirty) { 335 enabled_hosts_.clear(); 336 return Deserialise(input, dirty, &enabled_hosts_); 337} 338 339// static 340bool TransportSecurityState::Deserialise( 341 const std::string& input, 342 bool* dirty, 343 std::map<std::string, DomainState>* out) { 344 scoped_ptr<Value> value( 345 base::JSONReader::Read(input, false /* do not allow trailing commas */)); 346 if (!value.get() || !value->IsType(Value::TYPE_DICTIONARY)) 347 return false; 348 349 DictionaryValue* dict_value = reinterpret_cast<DictionaryValue*>(value.get()); 350 const base::Time current_time(base::Time::Now()); 351 bool dirtied = false; 352 353 for (DictionaryValue::key_iterator i = dict_value->begin_keys(); 354 i != dict_value->end_keys(); ++i) { 355 DictionaryValue* state; 356 if (!dict_value->GetDictionaryWithoutPathExpansion(*i, &state)) 357 continue; 358 359 bool include_subdomains; 360 std::string mode_string; 361 double created; 362 double expiry; 363 364 if (!state->GetBoolean("include_subdomains", &include_subdomains) || 365 !state->GetString("mode", &mode_string) || 366 !state->GetDouble("expiry", &expiry)) { 367 continue; 368 } 369 370 ListValue* pins_list = NULL; 371 std::vector<SHA1Fingerprint> public_key_hashes; 372 if (state->GetList("public_key_hashes", &pins_list)) { 373 size_t num_pins = pins_list->GetSize(); 374 for (size_t i = 0; i < num_pins; ++i) { 375 std::string type_and_base64; 376 std::string hash_str; 377 SHA1Fingerprint hash; 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 } 388 } 389 390 DomainState::Mode mode; 391 if (mode_string == "strict") { 392 mode = DomainState::MODE_STRICT; 393 } else if (mode_string == "opportunistic") { 394 mode = DomainState::MODE_OPPORTUNISTIC; 395 } else if (mode_string == "spdy-only") { 396 mode = DomainState::MODE_SPDY_ONLY; 397 } else if (mode_string == "none") { 398 mode = DomainState::MODE_NONE; 399 } else { 400 LOG(WARNING) << "Unknown TransportSecurityState mode string found: " 401 << mode_string; 402 continue; 403 } 404 405 base::Time expiry_time = base::Time::FromDoubleT(expiry); 406 base::Time created_time; 407 if (state->GetDouble("created", &created)) { 408 created_time = base::Time::FromDoubleT(created); 409 } else { 410 // We're migrating an old entry with no creation date. Make sure we 411 // write the new date back in a reasonable time frame. 412 dirtied = true; 413 created_time = base::Time::Now(); 414 } 415 416 if (expiry_time <= current_time) { 417 // Make sure we dirty the state if we drop an entry. 418 dirtied = true; 419 continue; 420 } 421 422 std::string hashed = ExternalStringToHashedDomain(*i); 423 if (hashed.empty()) { 424 dirtied = true; 425 continue; 426 } 427 428 DomainState new_state; 429 new_state.mode = mode; 430 new_state.created = created_time; 431 new_state.expiry = expiry_time; 432 new_state.include_subdomains = include_subdomains; 433 new_state.public_key_hashes = public_key_hashes; 434 (*out)[hashed] = new_state; 435 } 436 437 *dirty = dirtied; 438 return true; 439} 440 441TransportSecurityState::~TransportSecurityState() { 442} 443 444void TransportSecurityState::DirtyNotify() { 445 if (delegate_) 446 delegate_->StateIsDirty(this); 447} 448 449// static 450std::string TransportSecurityState::CanonicalizeHost(const std::string& host) { 451 // We cannot perform the operations as detailed in the spec here as |host| 452 // has already undergone IDN processing before it reached us. Thus, we check 453 // that there are no invalid characters in the host and lowercase the result. 454 455 std::string new_host; 456 if (!DNSDomainFromDot(host, &new_host)) { 457 // DNSDomainFromDot can fail if any label is > 63 bytes or if the whole 458 // name is >255 bytes. However, search terms can have those properties. 459 return std::string(); 460 } 461 462 for (size_t i = 0; new_host[i]; i += new_host[i] + 1) { 463 const unsigned label_length = static_cast<unsigned>(new_host[i]); 464 if (!label_length) 465 break; 466 467 for (size_t j = 0; j < label_length; ++j) { 468 // RFC 3490, 4.1, step 3 469 if (!IsSTD3ASCIIValidCharacter(new_host[i + 1 + j])) 470 return std::string(); 471 472 new_host[i + 1 + j] = tolower(new_host[i + 1 + j]); 473 } 474 475 // step 3(b) 476 if (new_host[i + 1] == '-' || 477 new_host[i + label_length] == '-') { 478 return std::string(); 479 } 480 } 481 482 return new_host; 483} 484 485// IsPreloadedSTS returns true if the canonicalized hostname should always be 486// considered to have STS enabled. 487// static 488bool TransportSecurityState::IsPreloadedSTS( 489 const std::string& canonicalized_host, 490 bool sni_available, 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#ifdef ANDROID 501 ; 502#else 503 = CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 504 switches::kHstsHosts); 505#endif 506 if (!cmd_line_hsts.empty()) { 507 bool dirty; 508 Deserialise(cmd_line_hsts, &dirty, &hosts); 509 } 510 511 // In the medium term this list is likely to just be hardcoded here. This, 512 // slightly odd, form removes the need for additional relocations records. 513 static const struct { 514 uint8 length; 515 bool include_subdomains; 516 char dns_name[30]; 517 } kPreloadedSTS[] = { 518 {16, false, "\003www\006paypal\003com"}, 519 {16, false, "\003www\006elanex\003biz"}, 520 {12, true, "\006jottit\003com"}, 521 {19, true, "\015sunshinepress\003org"}, 522 {21, false, "\003www\013noisebridge\003net"}, 523 {10, false, "\004neg9\003org"}, 524 {12, true, "\006riseup\003net"}, 525 {11, false, "\006factor\002cc"}, 526 {22, false, "\007members\010mayfirst\003org"}, 527 {22, false, "\007support\010mayfirst\003org"}, 528 {17, false, "\002id\010mayfirst\003org"}, 529 {20, false, "\005lists\010mayfirst\003org"}, 530 {19, true, "\015splendidbacon\003com"}, 531 {19, true, "\006health\006google\003com"}, 532 {21, true, "\010checkout\006google\003com"}, 533 {19, true, "\006chrome\006google\003com"}, 534 {26, false, "\006latest\006chrome\006google\003com"}, 535 {28, false, "\016aladdinschools\007appspot\003com"}, 536 {14, true, "\011ottospora\002nl"}, 537 {17, true, "\004docs\006google\003com"}, 538 {18, true, "\005sites\006google\003com"}, 539 {25, true, "\014spreadsheets\006google\003com"}, 540 {22, false, "\011appengine\006google\003com"}, 541 {25, false, "\003www\017paycheckrecords\003com"}, 542 {20, true, "\006market\007android\003com"}, 543 {14, false, "\010lastpass\003com"}, 544 {18, false, "\003www\010lastpass\003com"}, 545 {14, true, "\010keyerror\003com"}, 546 {22, true, "\011encrypted\006google\003com"}, 547 {13, false, "\010entropia\002de"}, 548 {17, false, "\003www\010entropia\002de"}, 549 {21, true, "\010accounts\006google\003com"}, 550#if defined(OS_CHROMEOS) 551 {17, true, "\004mail\006google\003com"}, 552 {13, false, "\007twitter\003com"}, 553 {17, false, "\003www\007twitter\003com"}, 554 {17, false, "\003api\007twitter\003com"}, 555 {17, false, "\003dev\007twitter\003com"}, 556 {22, false, "\010business\007twitter\003com"}, 557#endif 558 }; 559 static const size_t kNumPreloadedSTS = ARRAYSIZE_UNSAFE(kPreloadedSTS); 560 561 static const struct { 562 uint8 length; 563 bool include_subdomains; 564 char dns_name[30]; 565 } kPreloadedSNISTS[] = { 566 {11, false, "\005gmail\003com"}, 567 {16, false, "\012googlemail\003com"}, 568 {15, false, "\003www\005gmail\003com"}, 569 {20, false, "\003www\012googlemail\003com"}, 570 }; 571 static const size_t kNumPreloadedSNISTS = ARRAYSIZE_UNSAFE(kPreloadedSNISTS); 572 573 for (size_t i = 0; canonicalized_host[i]; i += canonicalized_host[i] + 1) { 574 std::string host_sub_chunk(&canonicalized_host[i], 575 canonicalized_host.size() - i); 576 out->domain = DNSDomainToString(host_sub_chunk); 577 std::string hashed_host(HashHost(host_sub_chunk)); 578 if (hosts.find(hashed_host) != hosts.end()) { 579 *out = hosts[hashed_host]; 580 out->domain = DNSDomainToString(host_sub_chunk); 581 out->preloaded = true; 582 return true; 583 } 584 for (size_t j = 0; j < kNumPreloadedSTS; j++) { 585 if (kPreloadedSTS[j].length == canonicalized_host.size() - i && 586 memcmp(kPreloadedSTS[j].dns_name, &canonicalized_host[i], 587 kPreloadedSTS[j].length) == 0) { 588 if (!kPreloadedSTS[j].include_subdomains && i != 0) 589 return false; 590 out->include_subdomains = kPreloadedSTS[j].include_subdomains; 591 return true; 592 } 593 } 594 if (sni_available) { 595 for (size_t j = 0; j < kNumPreloadedSNISTS; j++) { 596 if (kPreloadedSNISTS[j].length == canonicalized_host.size() - i && 597 memcmp(kPreloadedSNISTS[j].dns_name, &canonicalized_host[i], 598 kPreloadedSNISTS[j].length) == 0) { 599 if (!kPreloadedSNISTS[j].include_subdomains && i != 0) 600 return false; 601 out->include_subdomains = kPreloadedSNISTS[j].include_subdomains; 602 return true; 603 } 604 } 605 } 606 } 607 608 return false; 609} 610 611static std::string HashesToBase64String( 612 const std::vector<net::SHA1Fingerprint>& hashes) { 613 std::vector<std::string> hashes_strs; 614 for (std::vector<net::SHA1Fingerprint>::const_iterator 615 i = hashes.begin(); i != hashes.end(); i++) { 616 std::string s; 617 const std::string hash_str(reinterpret_cast<const char*>(i->data), 618 sizeof(i->data)); 619 base::Base64Encode(hash_str, &s); 620 hashes_strs.push_back(s); 621 } 622 623 return JoinString(hashes_strs, ','); 624} 625 626TransportSecurityState::DomainState::DomainState() 627 : mode(MODE_STRICT), 628 created(base::Time::Now()), 629 include_subdomains(false), 630 preloaded(false) { 631} 632 633TransportSecurityState::DomainState::~DomainState() { 634} 635 636bool TransportSecurityState::DomainState::IsChainOfPublicKeysPermitted( 637 const std::vector<net::SHA1Fingerprint>& hashes) { 638 if (public_key_hashes.empty()) 639 return true; 640 641 for (std::vector<net::SHA1Fingerprint>::const_iterator 642 i = hashes.begin(); i != hashes.end(); ++i) { 643 for (std::vector<net::SHA1Fingerprint>::const_iterator 644 j = public_key_hashes.begin(); j != public_key_hashes.end(); ++j) { 645 if (i->Equals(*j)) 646 return true; 647 } 648 } 649 650 LOG(ERROR) << "Rejecting public key chain for domain " << domain 651 << ". Validated chain: " << HashesToBase64String(hashes) 652 << ", expected: " << HashesToBase64String(public_key_hashes); 653 654 return false; 655} 656 657} // namespace 658