tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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(); }