Bug 1935611 - Fix libyuv/libpng link failed for loongarch64. r=glandium,tnikkel,ng
[gecko.git] / security / manager / ssl / nsSiteSecurityService.cpp
blobda25630282699d6b26b5305aff7a0bd7d97f6807
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/. */
5 #include "nsSiteSecurityService.h"
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/Preferences.h"
13 #include "mozilla/Tokenizer.h"
14 #include "mozilla/dom/PContent.h"
15 #include "mozilla/dom/ToJSValue.h"
16 #include "nsCOMArray.h"
17 #include "nsIScriptSecurityManager.h"
18 #include "nsISocketProvider.h"
19 #include "nsIURI.h"
20 #include "nsNSSComponent.h"
21 #include "nsNetUtil.h"
22 #include "nsPromiseFlatString.h"
23 #include "nsReadableUtils.h"
24 #include "nsSecurityHeaderParser.h"
25 #include "nsThreadUtils.h"
26 #include "nsVariant.h"
27 #include "nsXULAppAPI.h"
28 #include "prnetdb.h"
30 // A note about the preload list:
31 // When a site specifically disables HSTS by sending a header with
32 // 'max-age: 0', we keep a "knockout" value that means "we have no information
33 // regarding the HSTS state of this host" (any ancestor of "this host" can still
34 // influence its HSTS status via include subdomains, however).
35 // This prevents the preload list from overriding the site's current
36 // desired HSTS status.
37 #include "nsSTSPreloadListGenerated.inc"
39 using namespace mozilla;
40 using namespace mozilla::psm;
42 static LazyLogModule gSSSLog("nsSSService");
44 #define SSSLOG(args) MOZ_LOG(gSSSLog, mozilla::LogLevel::Debug, args)
46 static const nsLiteralCString kHSTSKeySuffix = ":HSTS"_ns;
48 ////////////////////////////////////////////////////////////////////////////////
50 namespace {
52 class SSSTokenizer final : public Tokenizer {
53 public:
54 explicit SSSTokenizer(const nsACString& source) : Tokenizer(source) {}
56 [[nodiscard]] bool ReadBool(/*out*/ bool& value) {
57 uint8_t rawValue;
58 if (!ReadInteger(&rawValue)) {
59 return false;
62 if (rawValue != 0 && rawValue != 1) {
63 return false;
66 value = (rawValue == 1);
67 return true;
70 [[nodiscard]] bool ReadState(/*out*/ SecurityPropertyState& state) {
71 uint32_t rawValue;
72 if (!ReadInteger(&rawValue)) {
73 return false;
76 state = static_cast<SecurityPropertyState>(rawValue);
77 switch (state) {
78 case SecurityPropertyKnockout:
79 case SecurityPropertySet:
80 case SecurityPropertyUnset:
81 break;
82 default:
83 return false;
86 return true;
90 // Parses a state string like "1500918564034,1,1" into its constituent parts.
91 bool ParseHSTSState(const nsCString& stateString,
92 /*out*/ PRTime& expireTime,
93 /*out*/ SecurityPropertyState& state,
94 /*out*/ bool& includeSubdomains) {
95 SSSTokenizer tokenizer(stateString);
96 SSSLOG(("Parsing state from %s", stateString.get()));
98 if (!tokenizer.ReadInteger(&expireTime)) {
99 return false;
102 if (!tokenizer.CheckChar(',')) {
103 return false;
106 if (!tokenizer.ReadState(state)) {
107 return false;
110 if (!tokenizer.CheckChar(',')) {
111 return false;
114 if (!tokenizer.ReadBool(includeSubdomains)) {
115 return false;
118 if (tokenizer.CheckChar(',')) {
119 // Read now-unused "source" field.
120 uint32_t unused;
121 if (!tokenizer.ReadInteger(&unused)) {
122 return false;
126 return tokenizer.CheckEOF();
129 } // namespace
131 SiteHSTSState::SiteHSTSState(const nsCString& aHost,
132 const OriginAttributes& aOriginAttributes,
133 const nsCString& aStateString)
134 : mHostname(aHost),
135 mOriginAttributes(aOriginAttributes),
136 mHSTSExpireTime(0),
137 mHSTSState(SecurityPropertyUnset),
138 mHSTSIncludeSubdomains(false) {
139 bool valid = ParseHSTSState(aStateString, mHSTSExpireTime, mHSTSState,
140 mHSTSIncludeSubdomains);
141 if (!valid) {
142 SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
143 mHSTSExpireTime = 0;
144 mHSTSState = SecurityPropertyUnset;
145 mHSTSIncludeSubdomains = false;
149 SiteHSTSState::SiteHSTSState(const nsCString& aHost,
150 const OriginAttributes& aOriginAttributes,
151 PRTime aHSTSExpireTime,
152 SecurityPropertyState aHSTSState,
153 bool aHSTSIncludeSubdomains)
155 : mHostname(aHost),
156 mOriginAttributes(aOriginAttributes),
157 mHSTSExpireTime(aHSTSExpireTime),
158 mHSTSState(aHSTSState),
159 mHSTSIncludeSubdomains(aHSTSIncludeSubdomains) {}
161 void SiteHSTSState::ToString(nsCString& aString) {
162 aString.Truncate();
163 aString.AppendInt(mHSTSExpireTime);
164 aString.Append(',');
165 aString.AppendInt(mHSTSState);
166 aString.Append(',');
167 aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
170 nsSiteSecurityService::nsSiteSecurityService()
171 : mUsePreloadList(true), mPreloadListTimeOffset(0), mDafsa(kDafsa) {}
173 nsSiteSecurityService::~nsSiteSecurityService() = default;
175 NS_IMPL_ISUPPORTS(nsSiteSecurityService, nsIObserver, nsISiteSecurityService)
177 nsresult nsSiteSecurityService::Init() {
178 // Don't access Preferences off the main thread.
179 if (!NS_IsMainThread()) {
180 MOZ_ASSERT_UNREACHABLE("nsSiteSecurityService initialized off main thread");
181 return NS_ERROR_NOT_SAME_THREAD;
184 mUsePreloadList = mozilla::Preferences::GetBool(
185 "network.stricttransportsecurity.preloadlist", true);
186 mozilla::Preferences::AddStrongObserver(
187 this, "network.stricttransportsecurity.preloadlist");
188 mPreloadListTimeOffset =
189 mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", 0);
190 mozilla::Preferences::AddStrongObserver(this,
191 "test.currentTimeOffsetSeconds");
192 nsCOMPtr<nsIDataStorageManager> dataStorageManager(
193 do_GetService("@mozilla.org/security/datastoragemanager;1"));
194 if (!dataStorageManager) {
195 return NS_ERROR_FAILURE;
197 nsresult rv =
198 dataStorageManager->Get(nsIDataStorageManager::SiteSecurityServiceState,
199 getter_AddRefs(mSiteStateStorage));
200 if (NS_FAILED(rv)) {
201 return rv;
203 if (!mSiteStateStorage) {
204 return NS_ERROR_FAILURE;
207 return NS_OK;
210 nsresult nsSiteSecurityService::GetHost(nsIURI* aURI, nsACString& aResult) {
211 nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
212 if (!innerURI) {
213 return NS_ERROR_FAILURE;
216 nsAutoCString host;
217 nsresult rv = innerURI->GetAsciiHost(host);
218 if (NS_FAILED(rv)) {
219 return rv;
222 aResult.Assign(PublicKeyPinningService::CanonicalizeHostname(host.get()));
223 if (aResult.IsEmpty()) {
224 return NS_ERROR_UNEXPECTED;
227 return NS_OK;
230 static void NormalizePartitionKey(nsString& partitionKey) {
231 // If present, the partitionKey will be of the form
232 // "(<scheme>,<domain>[,port>])" (where "<scheme>" will be "https" or "http"
233 // and "<port>", if present, will be a port number). This normalizes the
234 // scheme to "https" and strips the port so that a domain noted as HSTS will
235 // be HSTS regardless of scheme and port, as per the RFC.
236 Tokenizer16 tokenizer(partitionKey, nullptr, u".-_");
237 if (!tokenizer.CheckChar(u'(')) {
238 return;
240 nsString scheme;
241 if (!(tokenizer.ReadWord(scheme))) {
242 return;
244 if (!tokenizer.CheckChar(u',')) {
245 return;
247 nsString host;
248 if (!tokenizer.ReadWord(host)) {
249 return;
251 partitionKey.Assign(u"(https,");
252 partitionKey.Append(host);
253 partitionKey.Append(u")");
256 // Uses the previous format of storage key. Only to be used for migrating old
257 // entries.
258 static void GetOldStorageKey(const nsACString& hostname,
259 const OriginAttributes& aOriginAttributes,
260 /*out*/ nsAutoCString& storageKey) {
261 storageKey = hostname;
263 // Don't isolate by userContextId.
264 OriginAttributes originAttributesNoUserContext = aOriginAttributes;
265 originAttributesNoUserContext.mUserContextId =
266 nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
267 nsAutoCString originAttributesSuffix;
268 originAttributesNoUserContext.CreateSuffix(originAttributesSuffix);
269 storageKey.Append(originAttributesSuffix);
270 storageKey.Append(kHSTSKeySuffix);
273 static void GetStorageKey(const nsACString& hostname,
274 const OriginAttributes& aOriginAttributes,
275 /*out*/ nsAutoCString& storageKey) {
276 storageKey = hostname;
278 // Don't isolate by userContextId.
279 OriginAttributes originAttributesNoUserContext = aOriginAttributes;
280 originAttributesNoUserContext.mUserContextId =
281 nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
282 NormalizePartitionKey(originAttributesNoUserContext.mPartitionKey);
283 nsAutoCString originAttributesSuffix;
284 originAttributesNoUserContext.CreateSuffix(originAttributesSuffix);
285 storageKey.Append(originAttributesSuffix);
288 // Expire times are in millis. Since Headers max-age is in seconds, and
289 // PR_Now() is in micros, normalize the units at milliseconds.
290 static int64_t ExpireTimeFromMaxAge(uint64_t maxAge) {
291 return (PR_Now() / PR_USEC_PER_MSEC) + ((int64_t)maxAge * PR_MSEC_PER_SEC);
294 inline uint64_t AbsoluteDifference(int64_t a, int64_t b) {
295 if (a <= b) {
296 return b - a;
298 return a - b;
301 const uint64_t sOneDayInMilliseconds = 24 * 60 * 60 * 1000;
303 nsresult nsSiteSecurityService::SetHSTSState(
304 const char* aHost, int64_t maxage, bool includeSubdomains,
305 SecurityPropertyState aHSTSState,
306 const OriginAttributes& aOriginAttributes) {
307 nsAutoCString hostname(aHost);
308 // If max-age is zero, the host is no longer considered HSTS. If the host was
309 // preloaded, we store an entry indicating that this host is not HSTS, causing
310 // the preloaded information to be ignored.
311 if (maxage == 0) {
312 return MarkHostAsNotHSTS(hostname, aOriginAttributes);
315 MOZ_ASSERT(aHSTSState == SecurityPropertySet,
316 "HSTS State must be SecurityPropertySet");
318 int64_t expiretime = ExpireTimeFromMaxAge(maxage);
319 SiteHSTSState siteState(hostname, aOriginAttributes, expiretime, aHSTSState,
320 includeSubdomains);
321 nsAutoCString stateString;
322 siteState.ToString(stateString);
323 SSSLOG(("SSS: setting state for %s", hostname.get()));
324 bool isPrivate = aOriginAttributes.IsPrivateBrowsing();
325 nsIDataStorage::DataType storageType =
326 isPrivate ? nsIDataStorage::DataType::Private
327 : nsIDataStorage::DataType::Persistent;
328 SSSLOG(("SSS: storing HSTS site entry for %s", hostname.get()));
329 nsAutoCString value;
330 nsresult rv =
331 GetWithMigration(hostname, aOriginAttributes, storageType, value);
332 // If this fails for a reason other than nothing by that key exists,
333 // propagate the failure.
334 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
335 return rv;
337 // This is an entirely new entry.
338 if (rv == NS_ERROR_NOT_AVAILABLE) {
339 nsAutoCString storageKey;
340 GetStorageKey(hostname, aOriginAttributes, storageKey);
341 return mSiteStateStorage->Put(storageKey, stateString, storageType);
343 // Otherwise, only update the backing storage if the currently-stored state
344 // is different. In the case of expiration time, "different" means "is
345 // different by more than a day".
346 SiteHSTSState curSiteState(hostname, aOriginAttributes, value);
347 if (curSiteState.mHSTSState != siteState.mHSTSState ||
348 curSiteState.mHSTSIncludeSubdomains != siteState.mHSTSIncludeSubdomains ||
349 AbsoluteDifference(curSiteState.mHSTSExpireTime,
350 siteState.mHSTSExpireTime) > sOneDayInMilliseconds) {
351 rv =
352 PutWithMigration(hostname, aOriginAttributes, storageType, stateString);
353 if (NS_FAILED(rv)) {
354 return rv;
358 return NS_OK;
361 // Helper function to mark a host as not HSTS. In the general case, we can just
362 // remove the HSTS state. However, for preloaded entries, we have to store an
363 // entry that indicates this host is not HSTS to prevent the implementation
364 // using the preloaded information.
365 nsresult nsSiteSecurityService::MarkHostAsNotHSTS(
366 const nsAutoCString& aHost, const OriginAttributes& aOriginAttributes) {
367 bool isPrivate = aOriginAttributes.IsPrivateBrowsing();
368 nsIDataStorage::DataType storageType =
369 isPrivate ? nsIDataStorage::DataType::Private
370 : nsIDataStorage::DataType::Persistent;
371 if (GetPreloadStatus(aHost)) {
372 SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
373 SiteHSTSState siteState(aHost, aOriginAttributes, 0,
374 SecurityPropertyKnockout, false);
375 nsAutoCString stateString;
376 siteState.ToString(stateString);
377 nsresult rv =
378 PutWithMigration(aHost, aOriginAttributes, storageType, stateString);
379 NS_ENSURE_SUCCESS(rv, rv);
380 } else {
381 SSSLOG(("SSS: removing entry for %s", aHost.get()));
382 RemoveWithMigration(aHost, aOriginAttributes, storageType);
385 return NS_OK;
388 NS_IMETHODIMP
389 nsSiteSecurityService::ResetState(nsIURI* aURI,
390 JS::Handle<JS::Value> aOriginAttributes,
391 nsISiteSecurityService::ResetStateBy aScope,
392 JSContext* aCx, uint8_t aArgc) {
393 if (!aURI) {
394 return NS_ERROR_INVALID_ARG;
397 OriginAttributes originAttributes;
398 if (aArgc > 0) {
399 // OriginAttributes were passed in.
400 if (!aOriginAttributes.isObject() ||
401 !originAttributes.Init(aCx, aOriginAttributes)) {
402 return NS_ERROR_INVALID_ARG;
405 nsISiteSecurityService::ResetStateBy scope =
406 nsISiteSecurityService::ResetStateBy::ExactDomain;
407 if (aArgc > 1) {
408 // ResetStateBy scope was passed in
409 scope = aScope;
412 return ResetStateInternal(aURI, originAttributes, scope);
415 // Helper function to reset stored state of the given type for the host
416 // identified by the given URI. If there is preloaded information for the host,
417 // that information will be used for future queries. C.f. MarkHostAsNotHSTS,
418 // which will store a knockout entry for preloaded HSTS hosts that have sent a
419 // header with max-age=0 (meaning preloaded information will then not be used
420 // for that host).
421 nsresult nsSiteSecurityService::ResetStateInternal(
422 nsIURI* aURI, const OriginAttributes& aOriginAttributes,
423 nsISiteSecurityService::ResetStateBy aScope) {
424 if (!aURI) {
425 return NS_ERROR_INVALID_ARG;
427 nsAutoCString hostname;
428 nsresult rv = GetHost(aURI, hostname);
429 if (NS_FAILED(rv)) {
430 return rv;
433 OriginAttributes normalizedOriginAttributes(aOriginAttributes);
434 NormalizePartitionKey(normalizedOriginAttributes.mPartitionKey);
436 if (aScope == ResetStateBy::ExactDomain) {
437 ResetStateForExactDomain(hostname, normalizedOriginAttributes);
438 return NS_OK;
441 nsTArray<RefPtr<nsIDataStorageItem>> items;
442 rv = mSiteStateStorage->GetAll(items);
443 if (NS_FAILED(rv)) {
444 return rv;
446 for (const auto& item : items) {
447 static const nsLiteralCString kHPKPKeySuffix = ":HPKP"_ns;
448 nsAutoCString key;
449 rv = item->GetKey(key);
450 if (NS_FAILED(rv)) {
451 return rv;
453 nsAutoCString value;
454 rv = item->GetValue(value);
455 if (NS_FAILED(rv)) {
456 return rv;
458 if (StringEndsWith(key, kHPKPKeySuffix)) {
459 (void)mSiteStateStorage->Remove(key,
460 nsIDataStorage::DataType::Persistent);
461 continue;
463 size_t suffixLength =
464 StringEndsWith(key, kHSTSKeySuffix) ? kHSTSKeySuffix.Length() : 0;
465 nsCString origin(StringHead(key, key.Length() - suffixLength));
466 nsAutoCString itemHostname;
467 OriginAttributes itemOriginAttributes;
468 if (!itemOriginAttributes.PopulateFromOrigin(origin, itemHostname)) {
469 continue;
471 bool hasRootDomain = false;
472 nsresult rv = net::HasRootDomain(itemHostname, hostname, &hasRootDomain);
473 if (NS_FAILED(rv)) {
474 continue;
476 if (hasRootDomain) {
477 ResetStateForExactDomain(itemHostname, itemOriginAttributes);
478 } else if (aScope == ResetStateBy::BaseDomain) {
479 mozilla::dom::PartitionKeyPatternDictionary partitionKeyPattern;
480 partitionKeyPattern.mBaseDomain.Construct(
481 NS_ConvertUTF8toUTF16(hostname));
482 OriginAttributesPattern originAttributesPattern;
483 originAttributesPattern.mPartitionKeyPattern.Construct(
484 partitionKeyPattern);
485 if (originAttributesPattern.Matches(itemOriginAttributes)) {
486 ResetStateForExactDomain(itemHostname, itemOriginAttributes);
490 return NS_OK;
493 void nsSiteSecurityService::ResetStateForExactDomain(
494 const nsCString& aHostname, const OriginAttributes& aOriginAttributes) {
495 bool isPrivate = aOriginAttributes.IsPrivateBrowsing();
496 nsIDataStorage::DataType storageType =
497 isPrivate ? nsIDataStorage::DataType::Private
498 : nsIDataStorage::DataType::Persistent;
499 RemoveWithMigration(aHostname, aOriginAttributes, storageType);
502 bool nsSiteSecurityService::HostIsIPAddress(const nsCString& hostname) {
503 PRNetAddr hostAddr;
504 PRErrorCode prv = PR_StringToNetAddr(hostname.get(), &hostAddr);
505 return (prv == PR_SUCCESS);
508 NS_IMETHODIMP
509 nsSiteSecurityService::ProcessHeaderScriptable(
510 nsIURI* aSourceURI, const nsACString& aHeader,
511 JS::Handle<JS::Value> aOriginAttributes, uint64_t* aMaxAge,
512 bool* aIncludeSubdomains, uint32_t* aFailureResult, JSContext* aCx,
513 uint8_t aArgc) {
514 OriginAttributes originAttributes;
515 if (aArgc > 0) {
516 if (!aOriginAttributes.isObject() ||
517 !originAttributes.Init(aCx, aOriginAttributes)) {
518 return NS_ERROR_INVALID_ARG;
521 return ProcessHeader(aSourceURI, aHeader, originAttributes, aMaxAge,
522 aIncludeSubdomains, aFailureResult);
525 NS_IMETHODIMP
526 nsSiteSecurityService::ProcessHeader(nsIURI* aSourceURI,
527 const nsACString& aHeader,
528 const OriginAttributes& aOriginAttributes,
529 uint64_t* aMaxAge,
530 bool* aIncludeSubdomains,
531 uint32_t* aFailureResult) {
532 if (aFailureResult) {
533 *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
535 return ProcessHeaderInternal(aSourceURI, PromiseFlatCString(aHeader),
536 aOriginAttributes, aMaxAge, aIncludeSubdomains,
537 aFailureResult);
540 nsresult nsSiteSecurityService::ProcessHeaderInternal(
541 nsIURI* aSourceURI, const nsCString& aHeader,
542 const OriginAttributes& aOriginAttributes, uint64_t* aMaxAge,
543 bool* aIncludeSubdomains, uint32_t* aFailureResult) {
544 if (aFailureResult) {
545 *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
547 if (aMaxAge != nullptr) {
548 *aMaxAge = 0;
551 if (aIncludeSubdomains != nullptr) {
552 *aIncludeSubdomains = false;
555 nsAutoCString host;
556 nsresult rv = GetHost(aSourceURI, host);
557 NS_ENSURE_SUCCESS(rv, rv);
558 if (HostIsIPAddress(host)) {
559 /* Don't process headers if a site is accessed by IP address. */
560 return NS_OK;
563 return ProcessSTSHeader(aSourceURI, aHeader, aOriginAttributes, aMaxAge,
564 aIncludeSubdomains, aFailureResult);
567 static uint32_t ParseSSSHeaders(const nsCString& aHeader,
568 bool& foundIncludeSubdomains, bool& foundMaxAge,
569 bool& foundUnrecognizedDirective,
570 uint64_t& maxAge) {
571 // "Strict-Transport-Security" ":" OWS
572 // STS-d *( OWS ";" OWS STS-d OWS)
574 // ; STS directive
575 // STS-d = maxAge / includeSubDomains
577 // maxAge = "max-age" "=" delta-seconds v-ext
579 // includeSubDomains = [ "includeSubDomains" ]
581 // The order of the directives is not significant.
582 // All directives must appear only once.
583 // Directive names are case-insensitive.
584 // The entire header is invalid if a directive not conforming to the
585 // syntax is encountered.
586 // Unrecognized directives (that are otherwise syntactically valid) are
587 // ignored, and the rest of the header is parsed as normal.
589 constexpr auto max_age_var = "max-age"_ns;
590 constexpr auto include_subd_var = "includesubdomains"_ns;
592 nsSecurityHeaderParser parser(aHeader);
593 nsresult rv = parser.Parse();
594 if (NS_FAILED(rv)) {
595 SSSLOG(("SSS: could not parse header"));
596 return nsISiteSecurityService::ERROR_COULD_NOT_PARSE_HEADER;
598 mozilla::LinkedList<nsSecurityHeaderDirective>* directives =
599 parser.GetDirectives();
601 for (nsSecurityHeaderDirective* directive = directives->getFirst();
602 directive != nullptr; directive = directive->getNext()) {
603 SSSLOG(("SSS: found directive %s\n", directive->mName.get()));
604 if (directive->mName.EqualsIgnoreCase(max_age_var)) {
605 if (foundMaxAge) {
606 SSSLOG(("SSS: found two max-age directives"));
607 return nsISiteSecurityService::ERROR_MULTIPLE_MAX_AGES;
610 SSSLOG(("SSS: found max-age directive"));
611 foundMaxAge = true;
613 if (directive->mValue.isNothing()) {
614 SSSLOG(("SSS: max-age directive didn't include value"));
615 return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
618 Tokenizer tokenizer(*(directive->mValue));
619 if (!tokenizer.ReadInteger(&maxAge)) {
620 SSSLOG(("SSS: could not parse delta-seconds"));
621 return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
624 if (!tokenizer.CheckEOF()) {
625 SSSLOG(("SSS: invalid value for max-age directive"));
626 return nsISiteSecurityService::ERROR_INVALID_MAX_AGE;
629 SSSLOG(("SSS: parsed delta-seconds: %" PRIu64, maxAge));
630 } else if (directive->mName.EqualsIgnoreCase(include_subd_var)) {
631 if (foundIncludeSubdomains) {
632 SSSLOG(("SSS: found two includeSubdomains directives"));
633 return nsISiteSecurityService::ERROR_MULTIPLE_INCLUDE_SUBDOMAINS;
636 SSSLOG(("SSS: found includeSubdomains directive"));
637 foundIncludeSubdomains = true;
639 if (directive->mValue.isSome()) {
640 SSSLOG(("SSS: includeSubdomains directive unexpectedly had value '%s'",
641 directive->mValue->get()));
642 return nsISiteSecurityService::ERROR_INVALID_INCLUDE_SUBDOMAINS;
644 } else {
645 SSSLOG(("SSS: ignoring unrecognized directive '%s'",
646 directive->mName.get()));
647 foundUnrecognizedDirective = true;
650 return nsISiteSecurityService::Success;
653 // 100 years is wildly longer than anyone will ever need.
654 const uint64_t sMaxMaxAgeInSeconds = UINT64_C(60 * 60 * 24 * 365 * 100);
656 nsresult nsSiteSecurityService::ProcessSTSHeader(
657 nsIURI* aSourceURI, const nsCString& aHeader,
658 const OriginAttributes& aOriginAttributes, uint64_t* aMaxAge,
659 bool* aIncludeSubdomains, uint32_t* aFailureResult) {
660 if (aFailureResult) {
661 *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
663 SSSLOG(("SSS: processing HSTS header '%s'", aHeader.get()));
665 bool foundMaxAge = false;
666 bool foundIncludeSubdomains = false;
667 bool foundUnrecognizedDirective = false;
668 uint64_t maxAge = 0;
670 uint32_t sssrv = ParseSSSHeaders(aHeader, foundIncludeSubdomains, foundMaxAge,
671 foundUnrecognizedDirective, maxAge);
672 if (sssrv != nsISiteSecurityService::Success) {
673 if (aFailureResult) {
674 *aFailureResult = sssrv;
676 return NS_ERROR_FAILURE;
679 // after processing all the directives, make sure we came across max-age
680 // somewhere.
681 if (!foundMaxAge) {
682 SSSLOG(("SSS: did not encounter required max-age directive"));
683 if (aFailureResult) {
684 *aFailureResult = nsISiteSecurityService::ERROR_NO_MAX_AGE;
686 return NS_ERROR_FAILURE;
689 // Cap the specified max-age.
690 if (maxAge > sMaxMaxAgeInSeconds) {
691 maxAge = sMaxMaxAgeInSeconds;
694 nsAutoCString hostname;
695 nsresult rv = GetHost(aSourceURI, hostname);
696 NS_ENSURE_SUCCESS(rv, rv);
698 // record the successfully parsed header data.
699 rv = SetHSTSState(hostname.get(), maxAge, foundIncludeSubdomains,
700 SecurityPropertySet, aOriginAttributes);
701 if (NS_FAILED(rv)) {
702 SSSLOG(("SSS: failed to set STS state"));
703 if (aFailureResult) {
704 *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
706 return rv;
709 if (aMaxAge != nullptr) {
710 *aMaxAge = maxAge;
713 if (aIncludeSubdomains != nullptr) {
714 *aIncludeSubdomains = foundIncludeSubdomains;
717 return foundUnrecognizedDirective ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
718 : NS_OK;
721 NS_IMETHODIMP
722 nsSiteSecurityService::IsSecureURIScriptable(
723 nsIURI* aURI, JS::Handle<JS::Value> aOriginAttributes, JSContext* aCx,
724 uint8_t aArgc, bool* aResult) {
725 OriginAttributes originAttributes;
726 if (aArgc > 0) {
727 if (!aOriginAttributes.isObject() ||
728 !originAttributes.Init(aCx, aOriginAttributes)) {
729 return NS_ERROR_INVALID_ARG;
732 return IsSecureURI(aURI, originAttributes, aResult);
735 NS_IMETHODIMP
736 nsSiteSecurityService::IsSecureURI(nsIURI* aURI,
737 const OriginAttributes& aOriginAttributes,
738 bool* aResult) {
739 NS_ENSURE_ARG(aURI);
740 NS_ENSURE_ARG(aResult);
742 nsAutoCString hostname;
743 nsresult rv = GetHost(aURI, hostname);
744 NS_ENSURE_SUCCESS(rv, rv);
745 /* An IP address never qualifies as a secure URI. */
746 if (HostIsIPAddress(hostname)) {
747 *aResult = false;
748 return NS_OK;
751 return IsSecureHost(hostname, aOriginAttributes, aResult);
754 // Checks if the given host is in the preload list.
756 // @param aHost The host to match. Only does exact host matching.
757 // @param aIncludeSubdomains Out, optional. Indicates whether or not to include
758 // subdomains. Only set if the host is matched and this function returns
759 // true.
761 // @return True if the host is matched, false otherwise.
762 bool nsSiteSecurityService::GetPreloadStatus(const nsACString& aHost,
763 bool* aIncludeSubdomains) const {
764 const int kIncludeSubdomains = 1;
765 bool found = false;
767 PRTime currentTime = PR_Now() + (mPreloadListTimeOffset * PR_USEC_PER_SEC);
768 if (mUsePreloadList && currentTime < gPreloadListExpirationTime) {
769 int result = mDafsa.Lookup(aHost);
770 found = (result != mozilla::Dafsa::kKeyNotFound);
771 if (found && aIncludeSubdomains) {
772 *aIncludeSubdomains = (result == kIncludeSubdomains);
776 return found;
779 nsresult nsSiteSecurityService::GetWithMigration(
780 const nsACString& aHostname, const OriginAttributes& aOriginAttributes,
781 nsIDataStorage::DataType aDataStorageType, nsACString& aValue) {
782 // First see if this entry exists and has already been migrated.
783 nsAutoCString storageKey;
784 GetStorageKey(aHostname, aOriginAttributes, storageKey);
785 nsresult rv = mSiteStateStorage->Get(storageKey, aDataStorageType, aValue);
786 if (NS_SUCCEEDED(rv)) {
787 return NS_OK;
789 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
790 return rv;
792 // Otherwise, it potentially needs to be migrated, if it's persistent data.
793 if (aDataStorageType != nsIDataStorage::DataType::Persistent) {
794 return NS_ERROR_NOT_AVAILABLE;
796 nsAutoCString oldStorageKey;
797 GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey);
798 rv = mSiteStateStorage->Get(oldStorageKey,
799 nsIDataStorage::DataType::Persistent, aValue);
800 if (NS_FAILED(rv)) {
801 return rv;
803 // If there was a value, remove the old entry, insert a new one with the new
804 // key, and return the value.
805 rv = mSiteStateStorage->Remove(oldStorageKey,
806 nsIDataStorage::DataType::Persistent);
807 if (NS_FAILED(rv)) {
808 return rv;
810 return mSiteStateStorage->Put(storageKey, aValue,
811 nsIDataStorage::DataType::Persistent);
814 nsresult nsSiteSecurityService::PutWithMigration(
815 const nsACString& aHostname, const OriginAttributes& aOriginAttributes,
816 nsIDataStorage::DataType aDataStorageType, const nsACString& aStateString) {
817 // Only persistent data needs migrating.
818 if (aDataStorageType == nsIDataStorage::DataType::Persistent) {
819 // Since the intention is to overwrite the previously-stored data anyway,
820 // the old entry can be removed.
821 nsAutoCString oldStorageKey;
822 GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey);
823 nsresult rv = mSiteStateStorage->Remove(
824 oldStorageKey, nsIDataStorage::DataType::Persistent);
825 if (NS_FAILED(rv)) {
826 return rv;
830 nsAutoCString storageKey;
831 GetStorageKey(aHostname, aOriginAttributes, storageKey);
832 return mSiteStateStorage->Put(storageKey, aStateString, aDataStorageType);
835 nsresult nsSiteSecurityService::RemoveWithMigration(
836 const nsACString& aHostname, const OriginAttributes& aOriginAttributes,
837 nsIDataStorage::DataType aDataStorageType) {
838 // Only persistent data needs migrating.
839 if (aDataStorageType == nsIDataStorage::DataType::Persistent) {
840 nsAutoCString oldStorageKey;
841 GetOldStorageKey(aHostname, aOriginAttributes, oldStorageKey);
842 nsresult rv = mSiteStateStorage->Remove(
843 oldStorageKey, nsIDataStorage::DataType::Persistent);
844 if (NS_FAILED(rv)) {
845 return rv;
849 nsAutoCString storageKey;
850 GetStorageKey(aHostname, aOriginAttributes, storageKey);
851 return mSiteStateStorage->Remove(storageKey, aDataStorageType);
854 // Determines whether or not there is a matching HSTS entry for the given host.
855 // If aRequireIncludeSubdomains is set, then for there to be a matching HSTS
856 // entry, it must assert includeSubdomains.
857 nsresult nsSiteSecurityService::HostMatchesHSTSEntry(
858 const nsAutoCString& aHost, bool aRequireIncludeSubdomains,
859 const OriginAttributes& aOriginAttributes, bool& aHostMatchesHSTSEntry) {
860 aHostMatchesHSTSEntry = false;
861 // First we check for an entry in site security storage. If that entry exists,
862 // we don't want to check in the preload lists. We only want to use the
863 // stored value if it is not a knockout entry, however.
864 // Additionally, if it is a knockout entry, we want to stop looking for data
865 // on the host, because the knockout entry indicates "we have no information
866 // regarding the security status of this host".
867 bool isPrivate = aOriginAttributes.IsPrivateBrowsing();
868 nsIDataStorage::DataType storageType =
869 isPrivate ? nsIDataStorage::DataType::Private
870 : nsIDataStorage::DataType::Persistent;
871 SSSLOG(("Seeking HSTS entry for %s", aHost.get()));
872 nsAutoCString value;
873 nsresult rv = GetWithMigration(aHost, aOriginAttributes, storageType, value);
874 // If this fails for a reason other than nothing by that key exists,
875 // propagate the failure.
876 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
877 return rv;
879 bool checkPreloadList = true;
880 // If something by that key does exist, decode and process that information.
881 if (NS_SUCCEEDED(rv)) {
882 SiteHSTSState siteState(aHost, aOriginAttributes, value);
883 if (siteState.mHSTSState != SecurityPropertyUnset) {
884 SSSLOG(("Found HSTS entry for %s", aHost.get()));
885 bool expired = siteState.IsExpired();
886 if (!expired) {
887 SSSLOG(("Entry for %s is not expired", aHost.get()));
888 if (siteState.mHSTSState == SecurityPropertySet) {
889 aHostMatchesHSTSEntry = aRequireIncludeSubdomains
890 ? siteState.mHSTSIncludeSubdomains
891 : true;
892 return NS_OK;
896 if (expired) {
897 SSSLOG(
898 ("Entry %s is expired - checking for preload state", aHost.get()));
899 if (!GetPreloadStatus(aHost)) {
900 SSSLOG(("No static preload - removing expired entry"));
901 nsAutoCString storageKey;
902 GetStorageKey(aHost, aOriginAttributes, storageKey);
903 rv = mSiteStateStorage->Remove(storageKey, storageType);
904 if (NS_FAILED(rv)) {
905 return rv;
909 return NS_OK;
911 checkPreloadList = false;
914 bool includeSubdomains = false;
915 // Finally look in the static preload list.
916 if (checkPreloadList && GetPreloadStatus(aHost, &includeSubdomains)) {
917 SSSLOG(("%s is a preloaded HSTS host", aHost.get()));
918 aHostMatchesHSTSEntry =
919 aRequireIncludeSubdomains ? includeSubdomains : true;
922 return NS_OK;
925 nsresult nsSiteSecurityService::IsSecureHost(
926 const nsACString& aHost, const OriginAttributes& aOriginAttributes,
927 bool* aResult) {
928 NS_ENSURE_ARG(aResult);
929 *aResult = false;
931 /* An IP address never qualifies as a secure URI. */
932 const nsCString& flatHost = PromiseFlatCString(aHost);
933 if (HostIsIPAddress(flatHost)) {
934 return NS_OK;
937 nsAutoCString host(
938 PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
940 // First check the exact host.
941 bool hostMatchesHSTSEntry = false;
942 nsresult rv = HostMatchesHSTSEntry(host, false, aOriginAttributes,
943 hostMatchesHSTSEntry);
944 if (NS_FAILED(rv)) {
945 return rv;
947 if (hostMatchesHSTSEntry) {
948 *aResult = true;
949 return NS_OK;
952 SSSLOG(("%s not congruent match for any known HSTS host", host.get()));
953 const char* superdomain;
955 uint32_t offset = 0;
956 for (offset = host.FindChar('.', offset) + 1; offset > 0;
957 offset = host.FindChar('.', offset) + 1) {
958 superdomain = host.get() + offset;
960 // If we get an empty string, don't continue.
961 if (strlen(superdomain) < 1) {
962 break;
965 // Do the same thing as with the exact host except now we're looking at
966 // ancestor domains of the original host and, therefore, we have to require
967 // that the entry asserts includeSubdomains.
968 nsAutoCString superdomainString(superdomain);
969 hostMatchesHSTSEntry = false;
970 rv = HostMatchesHSTSEntry(superdomainString, true, aOriginAttributes,
971 hostMatchesHSTSEntry);
972 if (NS_FAILED(rv)) {
973 return rv;
975 if (hostMatchesHSTSEntry) {
976 *aResult = true;
977 return NS_OK;
980 SSSLOG(
981 ("superdomain %s not known HSTS host (or includeSubdomains not set), "
982 "walking up domain",
983 superdomain));
986 // If we get here, there was no congruent match, and no superdomain matched
987 // while asserting includeSubdomains, so this host is not HSTS.
988 *aResult = false;
989 return NS_OK;
992 NS_IMETHODIMP
993 nsSiteSecurityService::ClearAll() { return mSiteStateStorage->Clear(); }
995 //------------------------------------------------------------
996 // nsSiteSecurityService::nsIObserver
997 //------------------------------------------------------------
999 NS_IMETHODIMP
1000 nsSiteSecurityService::Observe(nsISupports* /*subject*/, const char* topic,
1001 const char16_t* /*data*/) {
1002 // Don't access Preferences off the main thread.
1003 if (!NS_IsMainThread()) {
1004 MOZ_ASSERT_UNREACHABLE("Preferences accessed off main thread");
1005 return NS_ERROR_NOT_SAME_THREAD;
1008 if (strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
1009 mUsePreloadList = mozilla::Preferences::GetBool(
1010 "network.stricttransportsecurity.preloadlist", true);
1011 mPreloadListTimeOffset =
1012 mozilla::Preferences::GetInt("test.currentTimeOffsetSeconds", 0);
1015 return NS_OK;