nsSiteSecurityService.cpp (33649B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 #include "nsSiteSecurityService.h" 6 7 #include "PublicKeyPinningService.h" 8 #include "mozilla/Assertions.h" 9 #include "mozilla/Base64.h" 10 #include "mozilla/LinkedList.h" 11 #include "mozilla/Logging.h" 12 #include "mozilla/StaticPrefs_network.h" 13 #include "mozilla/StaticPrefs_test.h" 14 #include "mozilla/Tokenizer.h" 15 #include "mozilla/dom/PContent.h" 16 #include "mozilla/dom/ToJSValue.h" 17 #include "nsCOMArray.h" 18 #include "nsIScriptSecurityManager.h" 19 #include "nsISocketProvider.h" 20 #include "nsIURI.h" 21 #include "nsNSSComponent.h" 22 #include "nsNetUtil.h" 23 #include "nsPromiseFlatString.h" 24 #include "nsReadableUtils.h" 25 #include "nsSecurityHeaderParser.h" 26 #include "nsURLHelper.h" 27 #include "nsVariant.h" 28 #include "nsXULAppAPI.h" 29 #include "prnetdb.h" 30 31 // A note about the preload list: 32 // When a site specifically disables HSTS by sending a header with 33 // 'max-age: 0', we keep a "knockout" value that means "we have no information 34 // regarding the HSTS state of this host" (any ancestor of "this host" can still 35 // influence its HSTS status via include subdomains, however). 36 // This prevents the preload list from overriding the site's current 37 // desired HSTS status. 38 #include "nsSTSPreloadListGenerated.inc" 39 40 using namespace mozilla; 41 using namespace mozilla::psm; 42 43 static LazyLogModule gSSSLog("nsSSService"); 44 45 #define SSSLOG(args) MOZ_LOG(gSSSLog, mozilla::LogLevel::Debug, args) 46 47 static const nsLiteralCString kHSTSKeySuffix = ":HSTS"_ns; 48 49 //////////////////////////////////////////////////////////////////////////////// 50 51 namespace { 52 53 class SSSTokenizer final : public Tokenizer { 54 public: 55 explicit SSSTokenizer(const nsACString& source) : Tokenizer(source) {} 56 57 [[nodiscard]] bool ReadBool(/*out*/ bool& value) { 58 uint8_t rawValue; 59 if (!ReadInteger(&rawValue)) { 60 return false; 61 } 62 63 if (rawValue != 0 && rawValue != 1) { 64 return false; 65 } 66 67 value = (rawValue == 1); 68 return true; 69 } 70 71 [[nodiscard]] bool ReadState(/*out*/ SecurityPropertyState& state) { 72 uint32_t rawValue; 73 if (!ReadInteger(&rawValue)) { 74 return false; 75 } 76 77 state = static_cast<SecurityPropertyState>(rawValue); 78 switch (state) { 79 case SecurityPropertyKnockout: 80 case SecurityPropertySet: 81 case SecurityPropertyUnset: 82 break; 83 default: 84 return false; 85 } 86 87 return true; 88 } 89 }; 90 91 // Parses a state string like "1500918564034,1,1" into its constituent parts. 92 bool ParseHSTSState(const nsCString& stateString, 93 /*out*/ PRTime& expireTime, 94 /*out*/ SecurityPropertyState& state, 95 /*out*/ bool& includeSubdomains) { 96 SSSTokenizer tokenizer(stateString); 97 SSSLOG(("Parsing state from %s", stateString.get())); 98 99 if (!tokenizer.ReadInteger(&expireTime)) { 100 return false; 101 } 102 103 if (!tokenizer.CheckChar(',')) { 104 return false; 105 } 106 107 if (!tokenizer.ReadState(state)) { 108 return false; 109 } 110 111 if (!tokenizer.CheckChar(',')) { 112 return false; 113 } 114 115 if (!tokenizer.ReadBool(includeSubdomains)) { 116 return false; 117 } 118 119 if (tokenizer.CheckChar(',')) { 120 // Read now-unused "source" field. 121 uint32_t unused; 122 if (!tokenizer.ReadInteger(&unused)) { 123 return false; 124 } 125 } 126 127 return tokenizer.CheckEOF(); 128 } 129 130 } // namespace 131 132 SiteHSTSState::SiteHSTSState(const nsCString& aHost, 133 const OriginAttributes& aOriginAttributes, 134 const nsCString& aStateString) 135 : mHostname(aHost), 136 mOriginAttributes(aOriginAttributes), 137 mHSTSExpireTime(0), 138 mHSTSState(SecurityPropertyUnset), 139 mHSTSIncludeSubdomains(false) { 140 bool valid = ParseHSTSState(aStateString, mHSTSExpireTime, mHSTSState, 141 mHSTSIncludeSubdomains); 142 if (!valid) { 143 SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get())); 144 mHSTSExpireTime = 0; 145 mHSTSState = SecurityPropertyUnset; 146 mHSTSIncludeSubdomains = false; 147 } 148 } 149 150 SiteHSTSState::SiteHSTSState(const nsCString& aHost, 151 const OriginAttributes& aOriginAttributes, 152 PRTime aHSTSExpireTime, 153 SecurityPropertyState aHSTSState, 154 bool aHSTSIncludeSubdomains) 155 156 : mHostname(aHost), 157 mOriginAttributes(aOriginAttributes), 158 mHSTSExpireTime(aHSTSExpireTime), 159 mHSTSState(aHSTSState), 160 mHSTSIncludeSubdomains(aHSTSIncludeSubdomains) {} 161 162 void SiteHSTSState::ToString(nsCString& aString) { 163 aString.Truncate(); 164 aString.AppendInt(mHSTSExpireTime); 165 aString.Append(','); 166 aString.AppendInt(mHSTSState); 167 aString.Append(','); 168 aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains)); 169 } 170 171 nsSiteSecurityService::nsSiteSecurityService() : mDafsa(kDafsa) {} 172 173 nsSiteSecurityService::~nsSiteSecurityService() = default; 174 175 NS_IMPL_ISUPPORTS(nsSiteSecurityService, nsISiteSecurityService) 176 177 nsresult nsSiteSecurityService::Init() { 178 nsCOMPtr<nsIDataStorageManager> dataStorageManager( 179 do_GetService("@mozilla.org/security/datastoragemanager;1")); 180 if (!dataStorageManager) { 181 return NS_ERROR_FAILURE; 182 } 183 nsresult rv = 184 dataStorageManager->Get(nsIDataStorageManager::SiteSecurityServiceState, 185 getter_AddRefs(mSiteStateStorage)); 186 if (NS_FAILED(rv)) { 187 return rv; 188 } 189 if (!mSiteStateStorage) { 190 return NS_ERROR_FAILURE; 191 } 192 193 return NS_OK; 194 } 195 196 nsresult nsSiteSecurityService::GetHost(nsIURI* aURI, nsACString& aResult) { 197 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI); 198 if (!innerURI) { 199 return NS_ERROR_FAILURE; 200 } 201 202 nsAutoCString host; 203 nsresult rv = innerURI->GetAsciiHost(host); 204 if (NS_FAILED(rv)) { 205 return rv; 206 } 207 208 aResult.Assign(PublicKeyPinningService::CanonicalizeHostname(host.get())); 209 if (aResult.IsEmpty()) { 210 return NS_ERROR_UNEXPECTED; 211 } 212 213 return NS_OK; 214 } 215 216 static void NormalizePartitionKey(nsString& partitionKey) { 217 // If present, the partitionKey will be of the form 218 // "(<scheme>,<domain>[,port>])" (where "<scheme>" will be "https" or "http" 219 // and "<port>", if present, will be a port number). This normalizes the 220 // scheme to "https" and strips the port so that a domain noted as HSTS will 221 // be HSTS regardless of scheme and port, as per the RFC. 222 Tokenizer16 tokenizer(partitionKey, nullptr, u".-_"); 223 if (!tokenizer.CheckChar(u'(')) { 224 return; 225 } 226 nsString scheme; 227 if (!(tokenizer.ReadWord(scheme))) { 228 return; 229 } 230 if (!tokenizer.CheckChar(u',')) { 231 return; 232 } 233 nsString host; 234 if (!tokenizer.ReadWord(host)) { 235 return; 236 } 237 partitionKey.Assign(u"(https,"); 238 partitionKey.Append(host); 239 partitionKey.Append(u")"); 240 } 241 242 // Uses the previous format of storage key. Only to be used for migrating old 243 // entries. 244 static void GetOldStorageKey(const nsACString& hostname, 245 const OriginAttributes& aOriginAttributes, 246 /*out*/ nsAutoCString& storageKey) { 247 storageKey = hostname; 248 249 // Don't isolate by userContextId. 250 OriginAttributes originAttributesNoUserContext = aOriginAttributes; 251 originAttributesNoUserContext.mUserContextId = 252 nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID; 253 nsAutoCString originAttributesSuffix; 254 originAttributesNoUserContext.CreateSuffix(originAttributesSuffix); 255 storageKey.Append(originAttributesSuffix); 256 storageKey.Append(kHSTSKeySuffix); 257 } 258 259 static void GetStorageKey(const nsACString& hostname, 260 const OriginAttributes& aOriginAttributes, 261 /*out*/ nsAutoCString& storageKey) { 262 storageKey = hostname; 263 264 // Don't isolate by userContextId. 265 OriginAttributes originAttributesNoUserContext = aOriginAttributes; 266 originAttributesNoUserContext.mUserContextId = 267 nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID; 268 NormalizePartitionKey(originAttributesNoUserContext.mPartitionKey); 269 nsAutoCString originAttributesSuffix; 270 originAttributesNoUserContext.CreateSuffix(originAttributesSuffix); 271 storageKey.Append(originAttributesSuffix); 272 } 273 274 // Expire times are in millis. Since Headers max-age is in seconds, and 275 // PR_Now() is in micros, normalize the units at milliseconds. 276 static int64_t ExpireTimeFromMaxAge(uint64_t maxAge) { 277 return (PR_Now() / PR_USEC_PER_MSEC) + ((int64_t)maxAge * PR_MSEC_PER_SEC); 278 } 279 280 inline uint64_t AbsoluteDifference(int64_t a, int64_t b) { 281 if (a <= b) { 282 return b - a; 283 } 284 return a - b; 285 } 286 287 const uint64_t sOneDayInMilliseconds = 24 * 60 * 60 * 1000; 288 289 nsresult nsSiteSecurityService::SetHSTSState( 290 const char* aHost, int64_t maxage, bool includeSubdomains, 291 SecurityPropertyState aHSTSState, 292 const OriginAttributes& aOriginAttributes) { 293 nsAutoCString hostname(aHost); 294 // If max-age is zero, the host is no longer considered HSTS. If the host was 295 // preloaded, we store an entry indicating that this host is not HSTS, causing 296 // the preloaded information to be ignored. 297 if (maxage == 0) { 298 return MarkHostAsNotHSTS(hostname, aOriginAttributes); 299 } 300 301 MOZ_ASSERT(aHSTSState == SecurityPropertySet, 302 "HSTS State must be SecurityPropertySet"); 303 304 int64_t expiretime = ExpireTimeFromMaxAge(maxage); 305 SiteHSTSState siteState(hostname, aOriginAttributes, expiretime, aHSTSState, 306 includeSubdomains); 307 nsAutoCString stateString; 308 siteState.ToString(stateString); 309 SSSLOG(("SSS: setting state for %s", hostname.get())); 310 bool isPrivate = aOriginAttributes.IsPrivateBrowsing(); 311 nsIDataStorage::DataType storageType = 312 isPrivate ? nsIDataStorage::DataType::Private 313 : nsIDataStorage::DataType::Persistent; 314 SSSLOG(("SSS: storing HSTS site entry for %s", hostname.get())); 315 nsAutoCString value; 316 nsresult rv = 317 GetWithMigration(hostname, aOriginAttributes, storageType, value); 318 // If this fails for a reason other than nothing by that key exists, 319 // propagate the failure. 320 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { 321 return rv; 322 } 323 // This is an entirely new entry. 324 if (rv == NS_ERROR_NOT_AVAILABLE) { 325 nsAutoCString storageKey; 326 GetStorageKey(hostname, aOriginAttributes, storageKey); 327 return mSiteStateStorage->Put(storageKey, stateString, storageType); 328 } 329 // Otherwise, only update the backing storage if the currently-stored state 330 // is different. In the case of expiration time, "different" means "is 331 // different by more than a day". 332 SiteHSTSState curSiteState(hostname, aOriginAttributes, value); 333 if (curSiteState.mHSTSState != siteState.mHSTSState || 334 curSiteState.mHSTSIncludeSubdomains != siteState.mHSTSIncludeSubdomains || 335 AbsoluteDifference(curSiteState.mHSTSExpireTime, 336 siteState.mHSTSExpireTime) > sOneDayInMilliseconds) { 337 rv = 338 PutWithMigration(hostname, aOriginAttributes, storageType, stateString); 339 if (NS_FAILED(rv)) { 340 return rv; 341 } 342 } 343 344 return NS_OK; 345 } 346 347 // Helper function to mark a host as not HSTS. In the general case, we can just 348 // remove the HSTS state. However, for preloaded entries, we have to store an 349 // entry that indicates this host is not HSTS to prevent the implementation 350 // using the preloaded information. 351 nsresult nsSiteSecurityService::MarkHostAsNotHSTS( 352 const nsAutoCString& aHost, const OriginAttributes& aOriginAttributes) { 353 bool isPrivate = aOriginAttributes.IsPrivateBrowsing(); 354 nsIDataStorage::DataType storageType = 355 isPrivate ? nsIDataStorage::DataType::Private 356 : nsIDataStorage::DataType::Persistent; 357 if (GetPreloadStatus(aHost)) { 358 SSSLOG(("SSS: storing knockout entry for %s", aHost.get())); 359 SiteHSTSState siteState(aHost, aOriginAttributes, 0, 360 SecurityPropertyKnockout, false); 361 nsAutoCString stateString; 362 siteState.ToString(stateString); 363 nsresult rv = 364 PutWithMigration(aHost, aOriginAttributes, storageType, stateString); 365 NS_ENSURE_SUCCESS(rv, rv); 366 } else { 367 SSSLOG(("SSS: removing entry for %s", aHost.get())); 368 RemoveWithMigration(aHost, aOriginAttributes, storageType); 369 } 370 371 return NS_OK; 372 } 373 374 NS_IMETHODIMP 375 nsSiteSecurityService::ResetState(nsIURI* aURI, 376 JS::Handle<JS::Value> aOriginAttributes, 377 nsISiteSecurityService::ResetStateBy aScope, 378 JSContext* aCx, uint8_t aArgc) { 379 if (!aURI) { 380 return NS_ERROR_INVALID_ARG; 381 } 382 383 OriginAttributes originAttributes; 384 if (aArgc > 0) { 385 // OriginAttributes were passed in. 386 if (!aOriginAttributes.isObject() || 387 !originAttributes.Init(aCx, aOriginAttributes)) { 388 return NS_ERROR_INVALID_ARG; 389 } 390 } 391 nsISiteSecurityService::ResetStateBy scope = 392 nsISiteSecurityService::ResetStateBy::ExactDomain; 393 if (aArgc > 1) { 394 // ResetStateBy scope was passed in 395 scope = aScope; 396 } 397 398 return ResetStateInternal(aURI, originAttributes, scope); 399 } 400 401 // Helper function to reset stored state of the given type for the host 402 // identified by the given URI. If there is preloaded information for the host, 403 // that information will be used for future queries. C.f. MarkHostAsNotHSTS, 404 // which will store a knockout entry for preloaded HSTS hosts that have sent a 405 // header with max-age=0 (meaning preloaded information will then not be used 406 // for that host). 407 nsresult nsSiteSecurityService::ResetStateInternal( 408 nsIURI* aURI, const OriginAttributes& aOriginAttributes, 409 nsISiteSecurityService::ResetStateBy aScope) { 410 if (!aURI) { 411 return NS_ERROR_INVALID_ARG; 412 } 413 nsAutoCString hostname; 414 nsresult rv = GetHost(aURI, hostname); 415 if (NS_FAILED(rv)) { 416 return rv; 417 } 418 419 OriginAttributes normalizedOriginAttributes(aOriginAttributes); 420 NormalizePartitionKey(normalizedOriginAttributes.mPartitionKey); 421 422 if (aScope == ResetStateBy::ExactDomain) { 423 ResetStateForExactDomain(hostname, normalizedOriginAttributes); 424 return NS_OK; 425 } 426 427 nsTArray<RefPtr<nsIDataStorageItem>> items; 428 rv = mSiteStateStorage->GetAll(items); 429 if (NS_FAILED(rv)) { 430 return rv; 431 } 432 for (const auto& item : items) { 433 static const nsLiteralCString kHPKPKeySuffix = ":HPKP"_ns; 434 nsAutoCString key; 435 rv = item->GetKey(key); 436 if (NS_FAILED(rv)) { 437 return rv; 438 } 439 nsAutoCString value; 440 rv = item->GetValue(value); 441 if (NS_FAILED(rv)) { 442 return rv; 443 } 444 if (StringEndsWith(key, kHPKPKeySuffix)) { 445 (void)mSiteStateStorage->Remove(key, 446 nsIDataStorage::DataType::Persistent); 447 continue; 448 } 449 size_t suffixLength = 450 StringEndsWith(key, kHSTSKeySuffix) ? kHSTSKeySuffix.Length() : 0; 451 nsCString origin(StringHead(key, key.Length() - suffixLength)); 452 nsAutoCString itemHostname; 453 OriginAttributes itemOriginAttributes; 454 if (!itemOriginAttributes.PopulateFromOrigin(origin, itemHostname)) { 455 continue; 456 } 457 bool hasRootDomain = false; 458 nsresult rv = net::HasRootDomain(itemHostname, hostname, &hasRootDomain); 459 if (NS_FAILED(rv)) { 460 continue; 461 } 462 if (hasRootDomain) { 463 ResetStateForExactDomain(itemHostname, itemOriginAttributes); 464 } else if (aScope == ResetStateBy::BaseDomain) { 465 mozilla::dom::PartitionKeyPatternDictionary partitionKeyPattern; 466 partitionKeyPattern.mBaseDomain.Construct( 467 NS_ConvertUTF8toUTF16(hostname)); 468 OriginAttributesPattern originAttributesPattern; 469 originAttributesPattern.mPartitionKeyPattern.Construct( 470 partitionKeyPattern); 471 if (originAttributesPattern.Matches(itemOriginAttributes)) { 472 ResetStateForExactDomain(itemHostname, itemOriginAttributes); 473 } 474 } 475 } 476 return NS_OK; 477 } 478 479 void nsSiteSecurityService::ResetStateForExactDomain( 480 const nsCString& aHostname, const OriginAttributes& aOriginAttributes) { 481 bool isPrivate = aOriginAttributes.IsPrivateBrowsing(); 482 nsIDataStorage::DataType storageType = 483 isPrivate ? nsIDataStorage::DataType::Private 484 : nsIDataStorage::DataType::Persistent; 485 RemoveWithMigration(aHostname, aOriginAttributes, storageType); 486 } 487 488 bool nsSiteSecurityService::HostIsIPAddress(const nsCString& hostname) { 489 PRNetAddr hostAddr; 490 PRErrorCode prv = PR_StringToNetAddr(hostname.get(), &hostAddr); 491 return (prv == PR_SUCCESS); 492 } 493 494 NS_IMETHODIMP 495 nsSiteSecurityService::ProcessHeaderScriptable( 496 nsIURI* aSourceURI, const nsACString& aHeader, 497 JS::Handle<JS::Value> aOriginAttributes, uint64_t* aMaxAge, 498 bool* aIncludeSubdomains, uint32_t* aFailureResult, JSContext* aCx, 499 uint8_t aArgc) { 500 OriginAttributes originAttributes; 501 if (aArgc > 0) { 502 if (!aOriginAttributes.isObject() || 503 !originAttributes.Init(aCx, aOriginAttributes)) { 504 return NS_ERROR_INVALID_ARG; 505 } 506 } 507 return ProcessHeader(aSourceURI, aHeader, originAttributes, aMaxAge, 508 aIncludeSubdomains, aFailureResult); 509 } 510 511 NS_IMETHODIMP 512 nsSiteSecurityService::ProcessHeader(nsIURI* aSourceURI, 513 const nsACString& aHeader, 514 const OriginAttributes& aOriginAttributes, 515 uint64_t* aMaxAge, 516 bool* aIncludeSubdomains, 517 uint32_t* aFailureResult) { 518 if (aFailureResult) { 519 *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN; 520 } 521 return ProcessHeaderInternal(aSourceURI, PromiseFlatCString(aHeader), 522 aOriginAttributes, aMaxAge, aIncludeSubdomains, 523 aFailureResult); 524 } 525 526 nsresult nsSiteSecurityService::ProcessHeaderInternal( 527 nsIURI* aSourceURI, const nsCString& aHeader, 528 const OriginAttributes& aOriginAttributes, uint64_t* aMaxAge, 529 bool* aIncludeSubdomains, uint32_t* aFailureResult) { 530 if (aFailureResult) { 531 *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN; 532 } 533 if (aMaxAge != nullptr) { 534 *aMaxAge = 0; 535 } 536 537 if (aIncludeSubdomains != nullptr) { 538 *aIncludeSubdomains = false; 539 } 540 541 nsAutoCString host; 542 nsresult rv = GetHost(aSourceURI, host); 543 NS_ENSURE_SUCCESS(rv, rv); 544 if (HostIsIPAddress(host)) { 545 /* Don't process headers if a site is accessed by IP address. */ 546 return NS_OK; 547 } 548 549 return ProcessSTSHeader(aSourceURI, aHeader, aOriginAttributes, aMaxAge, 550 aIncludeSubdomains, aFailureResult); 551 } 552 553 static uint32_t ParseSSSHeaders(const nsCString& aHeader, 554 bool& foundIncludeSubdomains, bool& foundMaxAge, 555 bool& foundUnrecognizedDirective, 556 uint64_t& maxAge) { 557 // "Strict-Transport-Security" ":" OWS 558 // STS-d *( OWS ";" OWS STS-d OWS) 559 // 560 // ; STS directive 561 // STS-d = maxAge / includeSubDomains 562 // 563 // maxAge = "max-age" "=" delta-seconds v-ext 564 // 565 // includeSubDomains = [ "includeSubDomains" ] 566 // 567 // The order of the directives is not significant. 568 // All directives must appear only once. 569 // Directive names are case-insensitive. 570 // The entire header is invalid if a directive not conforming to the 571 // syntax is encountered. 572 // Unrecognized directives (that are otherwise syntactically valid) are 573 // ignored, and the rest of the header is parsed as normal. 574 575 constexpr auto max_age_var = "max-age"_ns; 576 constexpr auto include_subd_var = "includesubdomains"_ns; 577 578 nsSecurityHeaderParser parser(aHeader); 579 nsresult rv = parser.Parse(); 580 if (NS_FAILED(rv)) { 581 SSSLOG(("SSS: could not parse header")); 582 return nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER; 583 } 584 mozilla::LinkedList<nsSecurityHeaderDirective>* directives = 585 parser.GetDirectives(); 586 587 for (nsSecurityHeaderDirective* directive = directives->getFirst(); 588 directive != nullptr; directive = directive->getNext()) { 589 SSSLOG(("SSS: found directive %s\n", directive->mName.get())); 590 if (directive->mName.EqualsIgnoreCase(max_age_var)) { 591 if (foundMaxAge) { 592 SSSLOG(("SSS: found two max-age directives")); 593 return nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES; 594 } 595 596 SSSLOG(("SSS: found max-age directive")); 597 foundMaxAge = true; 598 599 if (directive->mValue.isNothing()) { 600 SSSLOG(("SSS: max-age directive didn't include value")); 601 return nsISiteSecurityService::ERROR_INVALID_MAX_AGE; 602 } 603 604 Tokenizer tokenizer(*(directive->mValue)); 605 if (!tokenizer.ReadInteger(&maxAge)) { 606 SSSLOG(("SSS: could not parse delta-seconds")); 607 return nsISiteSecurityService::ERROR_INVALID_MAX_AGE; 608 } 609 610 if (!tokenizer.CheckEOF()) { 611 SSSLOG(("SSS: invalid value for max-age directive")); 612 return nsISiteSecurityService::ERROR_INVALID_MAX_AGE; 613 } 614 615 SSSLOG(("SSS: parsed delta-seconds: %" PRIu64, maxAge)); 616 } else if (directive->mName.EqualsIgnoreCase(include_subd_var)) { 617 if (foundIncludeSubdomains) { 618 SSSLOG(("SSS: found two includeSubdomains directives")); 619 return nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS; 620 } 621 622 SSSLOG(("SSS: found includeSubdomains directive")); 623 foundIncludeSubdomains = true; 624 625 if (directive->mValue.isSome()) { 626 SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'", 627 directive->mValue->get())); 628 return nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS; 629 } 630 } else { 631 SSSLOG(("SSS: ignoring unrecognized directive '%s'", 632 directive->mName.get())); 633 foundUnrecognizedDirective = true; 634 } 635 } 636 return nsISiteSecurityService::Success; 637 } 638 639 // 100 years is wildly longer than anyone will ever need. 640 const uint64_t sMaxMaxAgeInSeconds = UINT64_C(60 * 60 * 24 * 365 * 100); 641 642 nsresult nsSiteSecurityService::ProcessSTSHeader( 643 nsIURI* aSourceURI, const nsCString& aHeader, 644 const OriginAttributes& aOriginAttributes, uint64_t* aMaxAge, 645 bool* aIncludeSubdomains, uint32_t* aFailureResult) { 646 if (aFailureResult) { 647 *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN; 648 } 649 SSSLOG(("SSS: processing HSTS header '%s'", aHeader.get())); 650 651 bool foundMaxAge = false; 652 bool foundIncludeSubdomains = false; 653 bool foundUnrecognizedDirective = false; 654 uint64_t maxAge = 0; 655 656 uint32_t sssrv = ParseSSSHeaders(aHeader, foundIncludeSubdomains, foundMaxAge, 657 foundUnrecognizedDirective, maxAge); 658 if (sssrv != nsISiteSecurityService::Success) { 659 if (aFailureResult) { 660 *aFailureResult = sssrv; 661 } 662 return NS_ERROR_FAILURE; 663 } 664 665 // after processing all the directives, make sure we came across max-age 666 // somewhere. 667 if (!foundMaxAge) { 668 SSSLOG(("SSS: did not encounter required max-age directive")); 669 if (aFailureResult) { 670 *aFailureResult = nsISiteSecurityService::ERROR_NO_MAX_AGE; 671 } 672 return NS_ERROR_FAILURE; 673 } 674 675 // Cap the specified max-age. 676 if (maxAge > sMaxMaxAgeInSeconds) { 677 maxAge = sMaxMaxAgeInSeconds; 678 } 679 680 nsAutoCString hostname; 681 nsresult rv = GetHost(aSourceURI, hostname); 682 NS_ENSURE_SUCCESS(rv, rv); 683 684 // record the successfully parsed header data. 685 rv = SetHSTSState(hostname.get(), maxAge, foundIncludeSubdomains, 686 SecurityPropertySet, aOriginAttributes); 687 if (NS_FAILED(rv)) { 688 SSSLOG(("SSS: failed to set STS state")); 689 if (aFailureResult) { 690 *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE; 691 } 692 return rv; 693 } 694 695 if (aMaxAge != nullptr) { 696 *aMaxAge = maxAge; 697 } 698 699 if (aIncludeSubdomains != nullptr) { 700 *aIncludeSubdomains = foundIncludeSubdomains; 701 } 702 703 return foundUnrecognizedDirective ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA 704 : NS_OK; 705 } 706 707 NS_IMETHODIMP 708 nsSiteSecurityService::IsSecureURIScriptable( 709 nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx, 710 uint8_t aArgc, bool* aResult) { 711 OriginAttributes originAttributes; 712 if (aArgc > 0) { 713 if (!aOriginAttributes.isObject() || 714 !originAttributes.Init(aCx, aOriginAttributes)) { 715 return NS_ERROR_INVALID_ARG; 716 } 717 } 718 return IsSecureURI(aURI, originAttributes, aResult); 719 } 720 721 NS_IMETHODIMP 722 nsSiteSecurityService::IsSecureURI(nsIURI* aURI, 723 const OriginAttributes& aOriginAttributes, 724 bool* aResult) { 725 NS_ENSURE_ARG(aURI); 726 NS_ENSURE_ARG(aResult); 727 728 nsAutoCString hostname; 729 nsresult rv = GetHost(aURI, hostname); 730 NS_ENSURE_SUCCESS(rv, rv); 731 *aResult = false; 732 733 /* An IP address never qualifies as a secure URI. */ 734 const nsCString& flatHost = PromiseFlatCString(hostname); 735 if (HostIsIPAddress(flatHost)) { 736 return NS_OK; 737 } 738 739 /* Host name is invalid, can't be secure */ 740 if (!net_IsValidDNSHost(flatHost)) { 741 return NS_OK; 742 } 743 744 nsAutoCString host( 745 PublicKeyPinningService::CanonicalizeHostname(flatHost.get())); 746 747 // First check the exact host. 748 bool hostMatchesHSTSEntry = false; 749 rv = HostMatchesHSTSEntry(host, false, aOriginAttributes, 750 hostMatchesHSTSEntry); 751 if (NS_FAILED(rv)) { 752 return rv; 753 } 754 if (hostMatchesHSTSEntry) { 755 *aResult = true; 756 return NS_OK; 757 } 758 759 SSSLOG(("%s not congruent match for any known HSTS host", host.get())); 760 const char* superdomain; 761 762 uint32_t offset = 0; 763 for (offset = host.FindChar('.', offset) + 1; offset > 0; 764 offset = host.FindChar('.', offset) + 1) { 765 superdomain = host.get() + offset; 766 767 // If we get an empty string, don't continue. 768 if (strlen(superdomain) < 1) { 769 break; 770 } 771 772 // Do the same thing as with the exact host except now we're looking at 773 // ancestor domains of the original host and, therefore, we have to require 774 // that the entry asserts includeSubdomains. 775 nsAutoCString superdomainString(superdomain); 776 hostMatchesHSTSEntry = false; 777 rv = HostMatchesHSTSEntry(superdomainString, true, aOriginAttributes, 778 hostMatchesHSTSEntry); 779 if (NS_FAILED(rv)) { 780 return rv; 781 } 782 if (hostMatchesHSTSEntry) { 783 *aResult = true; 784 return NS_OK; 785 } 786 787 SSSLOG( 788 ("superdomain %s not known HSTS host (or includeSubdomains not set), " 789 "walking up domain", 790 superdomain)); 791 } 792 793 // If we get here, there was no congruent match, and no superdomain matched 794 // while asserting includeSubdomains, so this host is not HSTS. 795 *aResult = false; 796 return NS_OK; 797 } 798 799 // Checks if the given host is in the preload list. 800 // 801 // @param aHost The host to match. Only does exact host matching. 802 // @param aIncludeSubdomains Out, optional. Indicates whether or not to include 803 // subdomains. Only set if the host is matched and this function returns 804 // true. 805 // 806 // @return True if the host is matched, false otherwise. 807 bool nsSiteSecurityService::GetPreloadStatus(const nsACString& aHost, 808 bool* aIncludeSubdomains) const { 809 const int kIncludeSubdomains = 1; 810 bool found = false; 811 812 PRTime currentTime = 813 PR_Now() + 814 (StaticPrefs::test_currentTimeOffsetSeconds() * (PRTime)PR_USEC_PER_SEC); 815 if (StaticPrefs::network_stricttransportsecurity_preloadlist() && 816 currentTime < gPreloadListExpirationTime) { 817 int result = mDafsa.Lookup(aHost); 818 found = (result != mozilla::Dafsa::kKeyNotFound); 819 if (found && aIncludeSubdomains) { 820 *aIncludeSubdomains = (result == kIncludeSubdomains); 821 } 822 } 823 824 return found; 825 } 826 827 nsresult nsSiteSecurityService::GetWithMigration( 828 const nsACString& aHostname, const OriginAttributes& aOriginAttributes, 829 nsIDataStorage::DataType aDataStorageType, nsACString& aValue) { 830 // First see if this entry exists and has already been migrated. 831 nsAutoCString storageKey; 832 GetStorageKey(aHostname, aOriginAttributes, storageKey); 833 nsresult rv = mSiteStateStorage->Get(storageKey, aDataStorageType, aValue); 834 if (NS_SUCCEEDED(rv)) { 835 return NS_OK; 836 } 837 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { 838 return rv; 839 } 840 // Otherwise, it potentially needs to be migrated, if it's persistent data. 841 if (aDataStorageType != nsIDataStorage::DataType::Persistent) { 842 return NS_ERROR_NOT_AVAILABLE; 843 } 844 nsAutoCString oldStorageKey; 845 GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey); 846 rv = mSiteStateStorage->Get(oldStorageKey, 847 nsIDataStorage::DataType::Persistent, aValue); 848 if (NS_FAILED(rv)) { 849 return rv; 850 } 851 // If there was a value, remove the old entry, insert a new one with the new 852 // key, and return the value. 853 rv = mSiteStateStorage->Remove(oldStorageKey, 854 nsIDataStorage::DataType::Persistent); 855 if (NS_FAILED(rv)) { 856 return rv; 857 } 858 return mSiteStateStorage->Put(storageKey, aValue, 859 nsIDataStorage::DataType::Persistent); 860 } 861 862 nsresult nsSiteSecurityService::PutWithMigration( 863 const nsACString& aHostname, const OriginAttributes& aOriginAttributes, 864 nsIDataStorage::DataType aDataStorageType, const nsACString& aStateString) { 865 // Only persistent data needs migrating. 866 if (aDataStorageType == nsIDataStorage::DataType::Persistent) { 867 // Since the intention is to overwrite the previously-stored data anyway, 868 // the old entry can be removed. 869 nsAutoCString oldStorageKey; 870 GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey); 871 nsresult rv = mSiteStateStorage->Remove( 872 oldStorageKey, nsIDataStorage::DataType::Persistent); 873 if (NS_FAILED(rv)) { 874 return rv; 875 } 876 } 877 878 nsAutoCString storageKey; 879 GetStorageKey(aHostname, aOriginAttributes, storageKey); 880 return mSiteStateStorage->Put(storageKey, aStateString, aDataStorageType); 881 } 882 883 nsresult nsSiteSecurityService::RemoveWithMigration( 884 const nsACString& aHostname, const OriginAttributes& aOriginAttributes, 885 nsIDataStorage::DataType aDataStorageType) { 886 // Only persistent data needs migrating. 887 if (aDataStorageType == nsIDataStorage::DataType::Persistent) { 888 nsAutoCString oldStorageKey; 889 GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey); 890 nsresult rv = mSiteStateStorage->Remove( 891 oldStorageKey, nsIDataStorage::DataType::Persistent); 892 if (NS_FAILED(rv)) { 893 return rv; 894 } 895 } 896 897 nsAutoCString storageKey; 898 GetStorageKey(aHostname, aOriginAttributes, storageKey); 899 return mSiteStateStorage->Remove(storageKey, aDataStorageType); 900 } 901 902 // Determines whether or not there is a matching HSTS entry for the given host. 903 // If aRequireIncludeSubdomains is set, then for there to be a matching HSTS 904 // entry, it must assert includeSubdomains. 905 nsresult nsSiteSecurityService::HostMatchesHSTSEntry( 906 const nsAutoCString& aHost, bool aRequireIncludeSubdomains, 907 const OriginAttributes& aOriginAttributes, bool& aHostMatchesHSTSEntry) { 908 aHostMatchesHSTSEntry = false; 909 // First we check for an entry in site security storage. If that entry exists, 910 // we don't want to check in the preload lists. We only want to use the 911 // stored value if it is not a knockout entry, however. 912 // Additionally, if it is a knockout entry, we want to stop looking for data 913 // on the host, because the knockout entry indicates "we have no information 914 // regarding the security status of this host". 915 bool isPrivate = aOriginAttributes.IsPrivateBrowsing(); 916 nsIDataStorage::DataType storageType = 917 isPrivate ? nsIDataStorage::DataType::Private 918 : nsIDataStorage::DataType::Persistent; 919 SSSLOG(("Seeking HSTS entry for %s", aHost.get())); 920 nsAutoCString value; 921 nsresult rv = GetWithMigration(aHost, aOriginAttributes, storageType, value); 922 // If this fails for a reason other than nothing by that key exists, 923 // propagate the failure. 924 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { 925 return rv; 926 } 927 bool checkPreloadList = true; 928 // If something by that key does exist, decode and process that information. 929 if (NS_SUCCEEDED(rv)) { 930 SiteHSTSState siteState(aHost, aOriginAttributes, value); 931 if (siteState.mHSTSState != SecurityPropertyUnset) { 932 SSSLOG(("Found HSTS entry for %s", aHost.get())); 933 bool expired = siteState.IsExpired(); 934 if (!expired) { 935 SSSLOG(("Entry for %s is not expired", aHost.get())); 936 if (siteState.mHSTSState == SecurityPropertySet) { 937 aHostMatchesHSTSEntry = aRequireIncludeSubdomains 938 ? siteState.mHSTSIncludeSubdomains 939 : true; 940 return NS_OK; 941 } 942 } 943 944 if (expired) { 945 SSSLOG( 946 ("Entry %s is expired - checking for preload state", aHost.get())); 947 if (!GetPreloadStatus(aHost)) { 948 SSSLOG(("No static preload - removing expired entry")); 949 nsAutoCString storageKey; 950 GetStorageKey(aHost, aOriginAttributes, storageKey); 951 rv = mSiteStateStorage->Remove(storageKey, storageType); 952 if (NS_FAILED(rv)) { 953 return rv; 954 } 955 } 956 } 957 return NS_OK; 958 } 959 checkPreloadList = false; 960 } 961 962 bool includeSubdomains = false; 963 // Finally look in the static preload list. 964 if (checkPreloadList && GetPreloadStatus(aHost, &includeSubdomains)) { 965 SSSLOG(("%s is a preloaded HSTS host", aHost.get())); 966 aHostMatchesHSTSEntry = 967 aRequireIncludeSubdomains ? includeSubdomains : true; 968 } 969 970 return NS_OK; 971 } 972 973 NS_IMETHODIMP 974 nsSiteSecurityService::ClearAll() { return mSiteStateStorage->Clear(); }