Bug 1323644 - Isolate the HSTS and HPKP storage by first party domain (PSM) r=keeler,Cykesiopka draft
authorJonathan Hao <jhao@mozilla.com>
Tue, 14 Feb 2017 10:29:10 +0800
changeset 486881 f8fa9d42da5a41c551bc41d34443fb85c5759265
parent 486880 91300520556b2a0b377d4ea11d76e893efee358f
child 486882 ed9e4f89718a122e8f2de8fb5b9981c003dba190
push id46085
push userbmo:jhao@mozilla.com
push dateMon, 20 Feb 2017 05:50:46 +0000
reviewerskeeler, Cykesiopka
bugs1323644
milestone54.0a1
Bug 1323644 - Isolate the HSTS and HPKP storage by first party domain (PSM) r=keeler,Cykesiopka MozReview-Commit-ID: HhFFqtpBNjO
security/certverifier/NSSCertDBTrustDomain.cpp
security/manager/ssl/PublicKeyPinningService.cpp
security/manager/ssl/PublicKeyPinningService.h
security/manager/ssl/SSLServerCertVerification.cpp
security/manager/ssl/nsISiteSecurityService.idl
security/manager/ssl/nsSiteSecurityService.cpp
security/manager/ssl/nsSiteSecurityService.h
security/manager/ssl/tests/unit/test_pinning_header_parsing.js
security/manager/ssl/tests/unit/test_sss_originAttributes.js
security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
security/manager/ssl/tests/unit/test_sts_parser.js
security/manager/ssl/tests/unit/xpcshell.ini
security/manager/tools/getHSTSPreloadList.js
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -862,18 +862,18 @@ NSSCertDBTrustDomain::IsChainValid(const
   // If mHostname isn't set, we're not verifying in the context of a TLS
   // handshake, so don't verify HPKP in those cases.
   if (mHostname && (mPinningMode != CertVerifier::pinningDisabled) &&
       !skipPinningChecksBecauseOfMITMMode) {
     bool enforceTestMode =
       (mPinningMode == CertVerifier::pinningEnforceTestMode);
     bool chainHasValidPins;
     nsresult nsrv = PublicKeyPinningService::ChainHasValidPins(
-      certList, mHostname, time, enforceTestMode, chainHasValidPins,
-      mPinningTelemetryInfo);
+      certList, mHostname, time, enforceTestMode, mOriginAttributes,
+      chainHasValidPins, mPinningTelemetryInfo);
     if (NS_FAILED(nsrv)) {
       return Result::FATAL_ERROR_LIBRARY_FAILURE;
     }
     if (!chainHasValidPins) {
       return Result::ERROR_KEY_PINNING_FAILURE;
     }
   }
 
--- a/security/manager/ssl/PublicKeyPinningService.cpp
+++ b/security/manager/ssl/PublicKeyPinningService.cpp
@@ -159,16 +159,17 @@ PublicKeyPinningService::ChainMatchesPin
   return EvalChain(certList, nullptr, &aSHA256keys, chainMatchesPinset);
 }
 
 // Returns via one of the output parameters the most relevant pinning
 // information that is valid for the given host at the given time.
 // Dynamic pins are prioritized over static pins.
 static nsresult
 FindPinningInformation(const char* hostname, mozilla::pkix::Time time,
+                       const OriginAttributes& originAttributes,
                /*out*/ nsTArray<nsCString>& dynamicFingerprints,
                /*out*/ const TransportSecurityPreload*& staticFingerprints)
 {
   if (!hostname || hostname[0] == 0) {
     return NS_ERROR_INVALID_ARG;
   }
   staticFingerprints = nullptr;
   dynamicFingerprints.Clear();
@@ -185,17 +186,18 @@ FindPinningInformation(const char* hostn
     MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
            ("pkpin: Querying pinsets for host: '%s'\n", evalHost));
     // Attempt dynamic pins first
     nsresult rv;
     bool found;
     bool includeSubdomains;
     nsTArray<nsCString> pinArray;
     rv = sssService->GetKeyPinsForHostname(nsDependentCString(evalHost), time,
-                                           pinArray, &includeSubdomains, &found);
+                                           originAttributes, pinArray,
+                                           &includeSubdomains, &found);
     if (NS_FAILED(rv)) {
       return rv;
     }
     if (found && (evalHost == hostname || includeSubdomains)) {
       MOZ_LOG(gPublicKeyPinningLog, LogLevel::Debug,
              ("pkpin: Found dyn match for host: '%s'\n", evalHost));
       dynamicFingerprints = pinArray;
       return NS_OK;
@@ -236,31 +238,32 @@ FindPinningInformation(const char* hostn
 // Returns true via the output parameter if the given certificate list meets
 // pinning requirements for the given host at the given time. It must be the
 // case that either there is an intersection between the set of hashes of
 // subject public key info data in the list and the most relevant non-expired
 // pinset for the host or there is no pinning information for the host.
 static nsresult
 CheckPinsForHostname(const UniqueCERTCertList& certList, const char* hostname,
                      bool enforceTestMode, mozilla::pkix::Time time,
+                     const OriginAttributes& originAttributes,
              /*out*/ bool& chainHasValidPins,
     /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
 {
   chainHasValidPins = false;
   if (!certList) {
     return NS_ERROR_INVALID_ARG;
   }
   if (!hostname || hostname[0] == 0) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsTArray<nsCString> dynamicFingerprints;
   const TransportSecurityPreload* staticFingerprints = nullptr;
-  nsresult rv = FindPinningInformation(hostname, time, dynamicFingerprints,
-                                       staticFingerprints);
+  nsresult rv = FindPinningInformation(hostname, time, originAttributes,
+                                       dynamicFingerprints, staticFingerprints);
   // If we have no pinning information, the certificate chain trivially
   // validates with respect to pinning.
   if (dynamicFingerprints.Length() == 0 && !staticFingerprints) {
     chainHasValidPins = true;
     return NS_OK;
   }
   if (dynamicFingerprints.Length() > 0) {
     return EvalChain(certList, nullptr, &dynamicFingerprints, chainHasValidPins);
@@ -321,48 +324,52 @@ CheckPinsForHostname(const UniqueCERTCer
             staticFingerprints->mIsMoz ? "mozilla" : "non-mozilla",
             hostname, staticFingerprints->mTestMode ? "test" : "production"));
   }
 
   return NS_OK;
 }
 
 nsresult
-PublicKeyPinningService::ChainHasValidPins(const UniqueCERTCertList& certList,
-                                           const char* hostname,
-                                           mozilla::pkix::Time time,
-                                           bool enforceTestMode,
-                                   /*out*/ bool& chainHasValidPins,
-                          /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
+PublicKeyPinningService::ChainHasValidPins(
+  const UniqueCERTCertList& certList,
+  const char* hostname,
+  mozilla::pkix::Time time,
+  bool enforceTestMode,
+  const OriginAttributes& originAttributes,
+  /*out*/ bool& chainHasValidPins,
+  /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo)
 {
   chainHasValidPins = false;
   if (!certList) {
     return NS_ERROR_INVALID_ARG;
   }
   if (!hostname || hostname[0] == 0) {
     return NS_ERROR_INVALID_ARG;
   }
   nsAutoCString canonicalizedHostname(CanonicalizeHostname(hostname));
   return CheckPinsForHostname(certList, canonicalizedHostname.get(),
-                              enforceTestMode, time, chainHasValidPins,
-                              pinningTelemetryInfo);
+                              enforceTestMode, time, originAttributes,
+                              chainHasValidPins, pinningTelemetryInfo);
 }
 
 nsresult
 PublicKeyPinningService::HostHasPins(const char* hostname,
                                      mozilla::pkix::Time time,
                                      bool enforceTestMode,
+                                     const OriginAttributes& originAttributes,
                                      /*out*/ bool& hostHasPins)
 {
   hostHasPins = false;
   nsAutoCString canonicalizedHostname(CanonicalizeHostname(hostname));
   nsTArray<nsCString> dynamicFingerprints;
   const TransportSecurityPreload* staticFingerprints = nullptr;
   nsresult rv = FindPinningInformation(canonicalizedHostname.get(), time,
-                                       dynamicFingerprints, staticFingerprints);
+                                       originAttributes, dynamicFingerprints,
+                                       staticFingerprints);
   if (NS_FAILED(rv)) {
     return rv;
   }
   if (dynamicFingerprints.Length() > 0) {
     hostHasPins = true;
   } else if (staticFingerprints) {
     hostHasPins = !staticFingerprints->mTestMode || enforceTestMode;
   }
--- a/security/manager/ssl/PublicKeyPinningService.h
+++ b/security/manager/ssl/PublicKeyPinningService.h
@@ -8,16 +8,22 @@
 #include "CertVerifier.h"
 #include "ScopedNSSTypes.h"
 #include "cert.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "pkix/Time.h"
 
 namespace mozilla {
+class OriginAttributes;
+}
+
+using mozilla::OriginAttributes;
+
+namespace mozilla {
 namespace psm {
 
 class PublicKeyPinningService
 {
 public:
   /**
    * Sets chainHasValidPins to true if the given (host, certList) passes pinning
    * checks, or to false otherwise. If the host is pinned, returns true via
@@ -26,16 +32,17 @@ public:
    * and the tail is the trust anchor.
    * Note: if an alt name is a wildcard, it won't necessarily find a pinset
    * that would otherwise be valid for it
    */
   static nsresult ChainHasValidPins(const UniqueCERTCertList& certList,
                                     const char* hostname,
                                     mozilla::pkix::Time time,
                                     bool enforceTestMode,
+                                    const OriginAttributes& originAttributes,
                             /*out*/ bool& chainHasValidPins,
                    /*optional out*/ PinningTelemetryInfo* pinningTelemetryInfo);
   /**
    * Sets chainMatchesPinset to true if there is any intersection between the
    * certificate list and the pins specified in the aSHA256keys array.
    * Values passed in are assumed to be in base64 encoded form.
    */
   static nsresult ChainMatchesPinset(const UniqueCERTCertList& certList,
@@ -45,16 +52,17 @@ public:
   /**
    * Returns true via the output parameter hostHasPins if there is pinning
    * information for the given host that is valid at the given time, and false
    * otherwise.
    */
   static nsresult HostHasPins(const char* hostname,
                               mozilla::pkix::Time time,
                               bool enforceTestMode,
+                              const OriginAttributes& originAttributes,
                       /*out*/ bool& hostHasPins);
 
   /**
    * Given a hostname of potentially mixed case with potentially multiple
    * trailing '.' (see bug 1118522), canonicalizes it to lowercase with no
    * trailing '.'.
    */
   static nsAutoCString CanonicalizeHostname(const char* hostname);
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -519,27 +519,29 @@ CertErrorRunnable::CheckCertOverrides()
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
             ("[%p][%p] Creating new URI failed\n", mFdForLogging, this));
     return new SSLServerCertVerificationResult(mInfoObject,
                                                mDefaultErrorCodeToReport);
   }
   nsrv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS,
                           uri,
                           mProviderFlags,
+                          mInfoObject->GetOriginAttributes(),
                           nullptr,
                           &strictTransportSecurityEnabled);
   if (NS_FAILED(nsrv)) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("[%p][%p] checking for HSTS failed\n", mFdForLogging, this));
     return new SSLServerCertVerificationResult(mInfoObject,
                                                mDefaultErrorCodeToReport);
   }
   nsrv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP,
                           uri,
                           mProviderFlags,
+                          mInfoObject->GetOriginAttributes(),
                           nullptr,
                           &hasPinningInformation);
   if (NS_FAILED(nsrv)) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
            ("[%p][%p] checking for HPKP failed\n", mFdForLogging, this));
     return new SSLServerCertVerificationResult(mInfoObject,
                                                mDefaultErrorCodeToReport);
   }
--- a/security/manager/ssl/nsISiteSecurityService.idl
+++ b/security/manager/ssl/nsISiteSecurityService.idl
@@ -18,26 +18,30 @@ namespace mozilla
   namespace pkix
   {
     class Time;
   }
 }
 %}
 [ref] native nsCStringTArrayRef(nsTArray<nsCString>);
 [ref] native mozillaPkixTime(mozilla::pkix::Time);
+[ref] native const_OriginAttributesRef(const mozilla::OriginAttributes);
 
 // [infallible] attributes are only allowed on [builtinclass]
 [scriptable, uuid(31313372-842c-4110-bdf1-6aea17c845ad), builtinclass]
 interface nsISiteSecurityState : nsISupports
 {
   readonly attribute ACString hostname;
   [infallible] readonly attribute long long expireTime;
   [infallible] readonly attribute short securityPropertyState;
   [infallible] readonly attribute boolean includeSubdomains;
 
+  [implicit_jscontext]
+  readonly attribute jsval originAttributes;
+
   /*
    * SECURITY_PROPERTY_SET and SECURITY_PROPERTY_UNSET correspond to indicating
    * a site has or does not have the security property in question,
    * respectively.
    * SECURITY_PROPERTY_KNOCKOUT indicates a value on a preloaded
    * list is being overridden, and the associated site does not have the
    * security property in question.
    * SECURITY_PROPERTY_NEGATIVE is used when we've gotten a negative result from
@@ -98,65 +102,103 @@ interface nsISiteSecurityService : nsISu
      * in future HTTPS connections.
      *
      * @param aType the type of security header in question.
      * @param aSourceURI the URI of the resource with the HTTP header.
      * @param aHeader the HTTP response header specifying security data.
      * @param aSSLStatus the SSLStatus of the current channel.
      * @param aFlags  options for this request as defined in nsISocketProvider:
      *                  NO_PERMANENT_STORAGE
+     * @param aOriginAttributes the origin attributes that isolate this origin,
+     *                          (note that this implementation does not isolate
+     *                          by userContextId because of the risk of man-in-
+     *                          the-middle attacks before trust-on-second-use
+     *                          happens).
      * @param aMaxAge the parsed max-age directive of the header.
      * @param aIncludeSubdomains the parsed includeSubdomains directive.
      * @param aFailureResult a more specific failure result if NS_ERROR_FAILURE
                              was returned.
      * @return NS_OK            if it succeeds
      *         NS_ERROR_FAILURE if it can't be parsed
      *         NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
      *                          if there are unrecognized tokens in the header.
      */
+    [binaryname(ProcessHeader), noscript]
+    void processHeaderNative(in uint32_t aType,
+                             in nsIURI aSourceURI,
+                             in ACString aHeader,
+                             in nsISSLStatus aSSLStatus,
+                             in uint32_t aFlags,
+                             in const_OriginAttributesRef aOriginAttributes,
+                             [optional] out unsigned long long aMaxAge,
+                             [optional] out boolean aIncludeSubdomains,
+                             [optional] out uint32_t aFailureResult);
+
+    [binaryname(ProcessHeaderScriptable), implicit_jscontext, optional_argc]
     void processHeader(in uint32_t aType,
                        in nsIURI aSourceURI,
                        in ACString aHeader,
                        in nsISSLStatus aSSLStatus,
                        in uint32_t aFlags,
+                       [optional] in jsval aOriginAttributes,
                        [optional] out unsigned long long aMaxAge,
                        [optional] out boolean aIncludeSubdomains,
                        [optional] out uint32_t aFailureResult);
 
     /**
      * Given a header type, removes state relating to that header of a host,
      * including the includeSubdomains state that would affect subdomains.
      * This essentially removes the state for the domain tree rooted at this
      * host.
      * @param aType   the type of security state in question
      * @param aURI    the URI of the target host
      * @param aFlags  options for this request as defined in nsISocketProvider:
      *                  NO_PERMANENT_STORAGE
+     * @param aOriginAttributes the origin attributes that isolate this origin,
+     *                          (note that this implementation does not isolate
+     *                          by userContextId because of the risk of man-in-
+     *                          the-middle attacks before trust-on-second-use
+     *                          happens).
      */
+    [implicit_jscontext, optional_argc]
     void removeState(in uint32_t aType,
                      in nsIURI aURI,
-                     in uint32_t aFlags);
+                     in uint32_t aFlags,
+                     [optional] in jsval aOriginAttributes);
 
     /**
      * Checks whether or not the URI's hostname has a given security state set.
      * For example, for HSTS:
      * The URI is an HSTS URI if either the host has the HSTS state set, or one
      * of its super-domains has the HSTS "includeSubdomains" flag set.
      * NOTE: this function makes decisions based only on the
      * host contained in the URI, and disregards other portions of the URI
      * such as path and port.
      *
      * @param aType the type of security state in question.
      * @param aURI the URI to query for STS state.
      * @param aFlags  options for this request as defined in nsISocketProvider:
      *                  NO_PERMANENT_STORAGE
+     * @param aOriginAttributes the origin attributes that isolate this origin,
+     *                          (note that this implementation does not isolate
+     *                          by userContextId because of the risk of man-in-
+     *                          the-middle attacks before trust-on-second-use
+     *                          happens).
      * @param aCached true if we have cached information regarding whether or not
      *                  the host is HSTS, false otherwise.
      */
+    [binaryname(IsSecureURI), noscript]
+    boolean isSecureURINative(in uint32_t aType, in nsIURI aURI,
+                              in uint32_t aFlags,
+                              in const_OriginAttributesRef aOriginAttributes,
+                              [optional] out boolean aCached);
+
+    [binaryname(IsSecureURIScriptable), implicit_jscontext, optional_argc]
     boolean isSecureURI(in uint32_t aType, in nsIURI aURI, in uint32_t aFlags,
+                        [optional] in jsval aOriginAttributes,
                         [optional] out boolean aCached);
 
     /**
      * Removes all non-preloaded security state by resetting to factory-original
      * settings.
      */
     void clearAll();
 
@@ -169,64 +211,86 @@ interface nsISiteSecurityService : nsISu
      * Returns an array of sha256-hashed key pins for the given domain, if any.
      * If these pins also apply to subdomains of the given domain,
      * aIncludeSubdomains will be true. Pins returned are only for non-built-in
      * pin entries.
      *
      * @param aHostname the hostname (punycode) to be queried about
      * @param evalTime the time at which the pins should be valid. This is in
               mozilla::pkix::Time which uses internally seconds since 0 AD.
+     * @param aOriginAttributes the origin attributes that isolate this origin,
+     *                          (note that this implementation does not isolate
+     *                          by userContextId because of the risk of man-in-
+     *                          the-middle attacks before trust-on-second-use
+     *                          happens).
      * @param aPinArray the set of sha256-hashed key pins for the given domain
      * @param aIncludeSubdomains true if the pins apply to subdomains of the
      *        given domain
      */
-    [noscript] boolean getKeyPinsForHostname(in ACString aHostname,
-                                             in mozillaPkixTime evalTime,
-                                             out nsCStringTArrayRef aPinArray,
-                                             out boolean aIncludeSubdomains);
+    [noscript] boolean getKeyPinsForHostname(
+      in ACString aHostname,
+      in mozillaPkixTime evalTime,
+      in const_OriginAttributesRef aOriginAttributes,
+      out nsCStringTArrayRef aPinArray,
+      out boolean aIncludeSubdomains);
 
     /**
      * Set public-key pins for a host. The resulting pins will be permanent
      * and visible from private and non-private contexts. These pins replace
      * any already set by this mechanism or those built-in to Gecko.
      *
      * @param aHost the hostname (punycode) that pins will apply to
      * @param aIncludeSubdomains whether these pins also apply to subdomains
      * @param aExpires the time this pin should expire (millis since epoch)
      * @param aPinCount number of keys being pinnned
      * @param aSha256Pins array of hashed key fingerprints (SHA-256, base64)
      * @param aIsPreload are these key pins for a preload entry? (false by
      *        default)
+     * @param aOriginAttributes the origin attributes that isolate this origin,
+     *                          (note that this implementation does not isolate
+     *                          by userContextId because of the risk of man-in-
+     *                          the-middle attacks before trust-on-second-use
+     *                          happens).
      */
-     boolean setKeyPins(in ACString aHost, in boolean aIncludeSubdomains,
-                        in int64_t aExpires, in unsigned long aPinCount,
-                        [array, size_is(aPinCount)] in string aSha256Pins,
-                        [optional] in boolean aIsPreload);
+    [implicit_jscontext, optional_argc]
+    boolean setKeyPins(in ACString aHost, in boolean aIncludeSubdomains,
+                       in int64_t aExpires, in unsigned long aPinCount,
+                       [array, size_is(aPinCount)] in string aSha256Pins,
+                       [optional] in boolean aIsPreload,
+                       [optional] in jsval aOriginAttributes);
 
     /**
      * Set an HSTS preload entry for a host. The resulting entries will be
      * permanent and visible from private and non-private contexts. These
      * entries replace any already set by this mechanism or those built-in to
      * Gecko.
      *
      * @param aHost the hostname (punycode) that the entry applies to
      * @param aIncludeSubdomains whether this entry also applies to subdomains
      * @param aExpires the time this entry should expire (millis since epoch)
      */
-     boolean setHSTSPreload(in ACString aHost, in boolean aIncludesSubdomains,
-                            in int64_t aExpires);
+    boolean setHSTSPreload(in ACString aHost,
+                           in boolean aIncludesSubdomains,
+                           in int64_t aExpires);
 
     /**
      * Mark a host as declining to provide a given security state so that features
      * such as HSTS priming will not flood a server with requests.
      *
      * @param aURI the nsIURI that this applies to
      * @param aMaxAge lifetime (in seconds) of this negative cache
+     * @param aOriginAttributes the origin attributes that isolate this origin,
+     *                          (note that this implementation does not isolate
+     *                          by userContextId because of the risk of man-in-
+     *                          the-middle attacks before trust-on-second-use
+     *                          happens).
      */
-    [noscript] void cacheNegativeHSTSResult(in nsIURI aURI, in unsigned long long aMaxAge);
+    [noscript] void cacheNegativeHSTSResult(
+      in nsIURI aURI, in unsigned long long aMaxAge,
+      in const_OriginAttributesRef aOriginAttributes);
 
     /**
      * Returns an enumerator of the nsISiteSecurityService storage. Each item in
      * the enumeration is a nsISiteSecurityState that can be QueryInterfaced to
      * the appropriate nsISiteHSTSState or nsISiteHPKPState, depending on the
      * provided type. Doesn't include preloaded entries (either the hard-coded
      * ones or the preloaded-delivered-by-kinto ones).
      *
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -7,23 +7,25 @@
 #include "CertVerifier.h"
 #include "PublicKeyPinningService.h"
 #include "ScopedNSSTypes.h"
 #include "SharedCertVerifier.h"
 #include "base64.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Base64.h"
 #include "mozilla/dom/PContent.h"
+#include "mozilla/dom/ToJSValue.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "nsArrayEnumerator.h"
 #include "nsCOMArray.h"
 #include "nsCRTGlue.h"
 #include "nsISSLStatus.h"
+#include "nsIScriptSecurityManager.h"
 #include "nsISocketProvider.h"
 #include "nsIURI.h"
 #include "nsIX509Cert.h"
 #include "nsNSSComponent.h"
 #include "nsNetUtil.h"
 #include "nsPromiseFlatString.h"
 #include "nsReadableUtils.h"
 #include "nsSecurityHeaderParser.h"
@@ -53,18 +55,20 @@ static LazyLogModule gSSSLog("nsSSServic
 const char kHSTSKeySuffix[] = ":HSTS";
 const char kHPKPKeySuffix[] = ":HPKP";
 
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(SiteHSTSState, nsISiteSecurityState, nsISiteHSTSState)
 
 SiteHSTSState::SiteHSTSState(const nsCString& aHost,
+                             const OriginAttributes& aOriginAttributes,
                              const nsCString& aStateString)
   : mHostname(aHost)
+  , mOriginAttributes(aOriginAttributes)
   , mHSTSExpireTime(0)
   , mHSTSState(SecurityPropertyUnset)
   , mHSTSIncludeSubdomains(false)
 {
   uint32_t hstsState = 0;
   uint32_t hstsIncludeSubdomains = 0; // PR_sscanf doesn't handle bools.
   int32_t matches = PR_sscanf(aStateString.get(), "%lld,%lu,%lu",
                               &mHSTSExpireTime, &hstsState,
@@ -82,21 +86,23 @@ SiteHSTSState::SiteHSTSState(const nsCSt
     SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
     mHSTSExpireTime = 0;
     mHSTSState = SecurityPropertyUnset;
     mHSTSIncludeSubdomains = false;
   }
 }
 
 SiteHSTSState::SiteHSTSState(const nsCString& aHost,
+                             const OriginAttributes& aOriginAttributes,
                              PRTime aHSTSExpireTime,
                              SecurityPropertyState aHSTSState,
                              bool aHSTSIncludeSubdomains)
 
   : mHostname(aHost)
+  , mOriginAttributes(aOriginAttributes)
   , mHSTSExpireTime(aHSTSExpireTime)
   , mHSTSState(aHSTSState)
   , mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
 {
 }
 
 void
 SiteHSTSState::ToString(nsCString& aString)
@@ -135,16 +141,26 @@ SiteHSTSState::GetSecurityPropertyState(
 NS_IMETHODIMP
 SiteHSTSState::GetIncludeSubdomains(bool* aIncludeSubdomains)
 {
   NS_ENSURE_ARG(aIncludeSubdomains);
   *aIncludeSubdomains = mHSTSIncludeSubdomains;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+SiteHSTSState::GetOriginAttributes(JSContext* aCx,
+  JS::MutableHandle<JS::Value> aOriginAttributes)
+{
+  if (!ToJSValue(aCx, mOriginAttributes, aOriginAttributes)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(SiteHPKPState, nsISiteSecurityState, nsISiteHPKPState)
 
 static bool
 stringIsBase64EncodingOf256bitValue(nsCString& encodedString) {
   nsAutoCString binaryValue;
   nsresult rv = mozilla::Base64Decode(encodedString, binaryValue);
@@ -160,18 +176,20 @@ stringIsBase64EncodingOf256bitValue(nsCS
 SiteHPKPState::SiteHPKPState()
   : mExpireTime(0)
   , mState(SecurityPropertyUnset)
   , mIncludeSubdomains(false)
 {
 }
 
 SiteHPKPState::SiteHPKPState(const nsCString& aHost,
+                             const OriginAttributes& aOriginAttributes,
                              const nsCString& aStateString)
   : mHostname(aHost)
+  , mOriginAttributes(aOriginAttributes)
   , mExpireTime(0)
   , mState(SecurityPropertyUnset)
   , mIncludeSubdomains(false)
 {
   uint32_t hpkpState = 0;
   uint32_t hpkpIncludeSubdomains = 0; // PR_sscanf doesn't handle bools.
   const uint32_t MaxMergedHPKPPinSize = 1024;
   char mergedHPKPins[MaxMergedHPKPPinSize];
@@ -223,21 +241,23 @@ SiteHPKPState::SiteHPKPState(const nsCSt
     mIncludeSubdomains = false;
     if (!mSHA256keys.IsEmpty()) {
       mSHA256keys.Clear();
     }
   }
 }
 
 SiteHPKPState::SiteHPKPState(const nsCString& aHost,
+                             const OriginAttributes& aOriginAttributes,
                              PRTime aExpireTime,
                              SecurityPropertyState aState,
                              bool aIncludeSubdomains,
                              nsTArray<nsCString>& aSHA256keys)
   : mHostname(aHost)
+  , mOriginAttributes(aOriginAttributes)
   , mExpireTime(aExpireTime)
   , mState(aState)
   , mIncludeSubdomains(aIncludeSubdomains)
   , mSHA256keys(aSHA256keys)
 {
 }
 
 NS_IMETHODIMP
@@ -300,16 +320,26 @@ SiteHPKPState::GetSha256Keys(nsISimpleEn
     }
     if (!keys.AppendObject(variant)) {
       return NS_ERROR_FAILURE;
     }
   }
   return NS_NewArrayEnumerator(aSha256Keys, keys);
 }
 
+NS_IMETHODIMP
+SiteHPKPState::GetOriginAttributes(JSContext* aCx,
+  JS::MutableHandle<JS::Value> aOriginAttributes)
+{
+  if (!ToJSValue(aCx, mOriginAttributes, aOriginAttributes)) {
+    return NS_ERROR_FAILURE;
+  }
+  return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 const uint64_t kSixtyDaysInSeconds = 60 * 24 * 60 * 60;
 
 nsSiteSecurityService::nsSiteSecurityService()
   : mMaxMaxAge(kSixtyDaysInSeconds)
   , mUsePreloadList(true)
   , mPreloadListTimeOffset(0)
@@ -391,19 +421,29 @@ nsSiteSecurityService::GetHost(nsIURI* a
   if (aResult.IsEmpty()) {
     return NS_ERROR_UNEXPECTED;
   }
 
   return NS_OK;
 }
 
 static void
-SetStorageKey(nsAutoCString& storageKey, const nsACString& hostname, uint32_t aType)
+SetStorageKey(const nsACString& hostname, uint32_t aType,
+              const OriginAttributes& aOriginAttributes,
+              /*out*/ nsAutoCString& storageKey)
 {
   storageKey = hostname;
+
+  // Don't isolate by userContextId.
+  OriginAttributes originAttributesNoUserContext = aOriginAttributes;
+  originAttributesNoUserContext.mUserContextId =
+    nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
+  nsAutoCString originAttributesSuffix;
+  originAttributesNoUserContext.CreateSuffix(originAttributesSuffix);
+  storageKey.Append(originAttributesSuffix);
   switch (aType) {
     case nsISiteSecurityService::HEADER_HSTS:
       storageKey.AppendASCII(kHSTSKeySuffix);
       break;
     case nsISiteSecurityService::HEADER_HPKP:
       storageKey.AppendASCII(kHPKPKeySuffix);
       break;
     default:
@@ -421,97 +461,121 @@ ExpireTimeFromMaxAge(uint64_t maxAge)
 
 nsresult
 nsSiteSecurityService::SetHSTSState(uint32_t aType,
                                     const char* aHost,
                                     int64_t maxage,
                                     bool includeSubdomains,
                                     uint32_t flags,
                                     SecurityPropertyState aHSTSState,
-                                    bool aIsPreload)
+                                    bool aIsPreload,
+                                    const OriginAttributes& aOriginAttributes)
 {
   nsAutoCString hostname(aHost);
   // If max-age is zero, that's an indication to immediately remove the
   // security state, so here's a shortcut.
   if (!maxage) {
-    return RemoveStateInternal(aType, hostname, flags, aIsPreload);
+    return RemoveStateInternal(aType, hostname, flags, aIsPreload,
+                               aOriginAttributes);
   }
 
   MOZ_ASSERT((aHSTSState == SecurityPropertySet ||
               aHSTSState == SecurityPropertyNegative),
       "HSTS State must be SecurityPropertySet or SecurityPropertyNegative");
+  if (aIsPreload && aOriginAttributes != OriginAttributes()) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   int64_t expiretime = ExpireTimeFromMaxAge(maxage);
-  RefPtr<SiteHSTSState> siteState =
-    new SiteHSTSState(hostname, expiretime, aHSTSState, includeSubdomains);
+  RefPtr<SiteHSTSState> siteState = new SiteHSTSState(
+    hostname, aOriginAttributes, expiretime, aHSTSState, includeSubdomains);
   nsAutoCString stateString;
   siteState->ToString(stateString);
   SSSLOG(("SSS: setting state for %s", hostname.get()));
   bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   nsAutoCString storageKey;
-  SetStorageKey(storageKey, hostname, aType);
+  SetStorageKey(hostname, aType, aOriginAttributes, storageKey);
   nsresult rv;
   if (aIsPreload) {
     SSSLOG(("SSS: storing entry for %s in dynamic preloads", hostname.get()));
     rv = mPreloadStateStorage->Put(storageKey, stateString,
                                    mozilla::DataStorage_Persistent);
   } else {
     SSSLOG(("SSS: storing HSTS site entry for %s", hostname.get()));
     rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsSiteSecurityService::CacheNegativeHSTSResult(nsIURI* aSourceURI,
-                                               uint64_t aMaxAge)
+nsSiteSecurityService::CacheNegativeHSTSResult(
+  nsIURI* aSourceURI,
+  uint64_t aMaxAge,
+  const OriginAttributes& aOriginAttributes)
 {
   nsAutoCString hostname;
   nsresult rv = GetHost(aSourceURI, hostname);
   NS_ENSURE_SUCCESS(rv, rv);
   return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, hostname.get(),
-                      aMaxAge, false, 0, SecurityPropertyNegative, false);
+                      aMaxAge, false, 0, SecurityPropertyNegative, false,
+                      aOriginAttributes);
 }
 
 nsresult
-nsSiteSecurityService::RemoveStateInternal(uint32_t aType,
-                                           const nsAutoCString& aHost,
-                                           uint32_t aFlags, bool aIsPreload)
+nsSiteSecurityService::RemoveStateInternal(
+  uint32_t aType, nsIURI* aURI, uint32_t aFlags,
+  const OriginAttributes& aOriginAttributes)
+{
+  nsAutoCString hostname;
+  GetHost(aURI, hostname);
+  return RemoveStateInternal(aType, hostname, aFlags, false, aOriginAttributes);
+}
+
+nsresult
+nsSiteSecurityService::RemoveStateInternal(
+  uint32_t aType,
+  const nsAutoCString& aHost,
+  uint32_t aFlags, bool aIsPreload,
+  const OriginAttributes& aOriginAttributes)
 {
    // Child processes are not allowed direct access to this.
    if (!XRE_IsParentProcess()) {
      MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::RemoveStateInternal");
    }
 
   // Only HSTS is supported at the moment.
   NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
                  aType == nsISiteSecurityService::HEADER_HPKP,
                  NS_ERROR_NOT_IMPLEMENTED);
+  if (aIsPreload && aOriginAttributes != OriginAttributes()) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   // If this host is in the preload list, we have to store a knockout entry.
   nsAutoCString storageKey;
-  SetStorageKey(storageKey, aHost, aType);
+  SetStorageKey(aHost, aType, aOriginAttributes, storageKey);
 
   nsCString value = mPreloadStateStorage->Get(storageKey,
                                               mozilla::DataStorage_Persistent);
-  RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
+  RefPtr<SiteHSTSState> dynamicState =
+    new SiteHSTSState(aHost, aOriginAttributes, value);
   if (GetPreloadListEntry(aHost.get()) ||
       dynamicState->mHSTSState != SecurityPropertyUnset) {
     SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
-    RefPtr<SiteHSTSState> siteState =
-      new SiteHSTSState(aHost, 0, SecurityPropertyKnockout, false);
+    RefPtr<SiteHSTSState> siteState = new SiteHSTSState(
+      aHost, aOriginAttributes, 0, SecurityPropertyKnockout, false);
     nsAutoCString stateString;
     siteState->ToString(stateString);
     nsresult rv;
     if (aIsPreload) {
       rv = mPreloadStateStorage->Put(storageKey, stateString,
                                      mozilla::DataStorage_Persistent);
     } else {
       rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
@@ -526,37 +590,72 @@ nsSiteSecurityService::RemoveStateIntern
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI,
-                                   uint32_t aFlags)
+                                   uint32_t aFlags,
+                                   JS::HandleValue aOriginAttributes,
+                                   JSContext* aCx, uint8_t aArgc)
 {
-  nsAutoCString hostname;
-  GetHost(aURI, hostname);
-  return RemoveStateInternal(aType, hostname, aFlags, false);
+  OriginAttributes originAttributes;
+  if (aArgc > 0) {
+    // OriginAttributes were passed in.
+    if (!aOriginAttributes.isObject() ||
+        !originAttributes.Init(aCx, aOriginAttributes)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+  return RemoveStateInternal(aType, aURI, aFlags, originAttributes);
 }
 
 static bool
 HostIsIPAddress(const nsCString& hostname)
 {
   PRNetAddr hostAddr;
   PRErrorCode prv = PR_StringToNetAddr(hostname.get(), &hostAddr);
   return (prv == PR_SUCCESS);
 }
 
 NS_IMETHODIMP
+nsSiteSecurityService::ProcessHeaderScriptable(
+  uint32_t aType,
+  nsIURI* aSourceURI,
+  const nsACString& aHeader,
+  nsISSLStatus* aSSLStatus,
+  uint32_t aFlags,
+  JS::HandleValue aOriginAttributes,
+  uint64_t* aMaxAge,
+  bool* aIncludeSubdomains,
+  uint32_t* aFailureResult,
+  JSContext* aCx,
+  uint8_t aArgc)
+{
+  OriginAttributes originAttributes;
+  if (aArgc > 0) {
+    if (!aOriginAttributes.isObject() ||
+        !originAttributes.Init(aCx, aOriginAttributes)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+  return ProcessHeader(aType, aSourceURI, aHeader, aSSLStatus, aFlags,
+                       originAttributes, aMaxAge, aIncludeSubdomains,
+                       aFailureResult);
+}
+
+NS_IMETHODIMP
 nsSiteSecurityService::ProcessHeader(uint32_t aType,
                                      nsIURI* aSourceURI,
                                      const nsACString& aHeader,
                                      nsISSLStatus* aSSLStatus,
                                      uint32_t aFlags,
+                                     const OriginAttributes& aOriginAttributes,
                                      uint64_t* aMaxAge,
                                      bool* aIncludeSubdomains,
                                      uint32_t* aFailureResult)
 {
   // Child processes are not allowed direct access to this.
   if (!XRE_IsParentProcess()) {
     MOZ_CRASH("Child process: no direct access to "
               "nsISiteSecurityService::ProcessHeader");
@@ -566,29 +665,31 @@ nsSiteSecurityService::ProcessHeader(uin
     *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
   }
   NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
                  aType == nsISiteSecurityService::HEADER_HPKP,
                  NS_ERROR_NOT_IMPLEMENTED);
 
   NS_ENSURE_ARG(aSSLStatus);
   return ProcessHeaderInternal(aType, aSourceURI, PromiseFlatCString(aHeader),
-                               aSSLStatus, aFlags, aMaxAge, aIncludeSubdomains,
-                               aFailureResult);
+                               aSSLStatus, aFlags, aOriginAttributes, aMaxAge,
+                               aIncludeSubdomains, aFailureResult);
 }
 
 nsresult
-nsSiteSecurityService::ProcessHeaderInternal(uint32_t aType,
-                                             nsIURI* aSourceURI,
-                                             const nsCString& aHeader,
-                                             nsISSLStatus* aSSLStatus,
-                                             uint32_t aFlags,
-                                             uint64_t* aMaxAge,
-                                             bool* aIncludeSubdomains,
-                                             uint32_t* aFailureResult)
+nsSiteSecurityService::ProcessHeaderInternal(
+  uint32_t aType,
+  nsIURI* aSourceURI,
+  const nsCString& aHeader,
+  nsISSLStatus* aSSLStatus,
+  uint32_t aFlags,
+  const OriginAttributes& aOriginAttributes,
+  uint64_t* aMaxAge,
+  bool* aIncludeSubdomains,
+  uint32_t* aFailureResult)
 {
   if (aFailureResult) {
     *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
   }
   // Only HSTS and HPKP are supported at the moment.
   NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
                  aType == nsISiteSecurityService::HEADER_HPKP,
                  NS_ERROR_NOT_IMPLEMENTED);
@@ -630,22 +731,23 @@ nsSiteSecurityService::ProcessHeaderInte
   NS_ENSURE_SUCCESS(rv, rv);
   if (HostIsIPAddress(host)) {
     /* Don't process headers if a site is accessed by IP address. */
     return NS_OK;
   }
 
   switch (aType) {
     case nsISiteSecurityService::HEADER_HSTS:
-      rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aMaxAge,
+      rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aOriginAttributes, aMaxAge,
                             aIncludeSubdomains, aFailureResult);
       break;
     case nsISiteSecurityService::HEADER_HPKP:
-      rv = ProcessPKPHeader(aSourceURI, aHeader, aSSLStatus, aFlags, aMaxAge,
-                            aIncludeSubdomains, aFailureResult);
+      rv = ProcessPKPHeader(aSourceURI, aHeader, aSSLStatus, aFlags,
+                            aOriginAttributes, aMaxAge, aIncludeSubdomains,
+                            aFailureResult);
       break;
     default:
       MOZ_CRASH("unexpected header type");
   }
   return rv;
 }
 
 static uint32_t
@@ -785,23 +887,25 @@ ParseSSSHeaders(uint32_t aType,
               directive->mName.get()));
       foundUnrecognizedDirective = true;
     }
   }
   return nsISiteSecurityService::Success;
 }
 
 nsresult
-nsSiteSecurityService::ProcessPKPHeader(nsIURI* aSourceURI,
-                                        const nsCString& aHeader,
-                                        nsISSLStatus* aSSLStatus,
-                                        uint32_t aFlags,
-                                        uint64_t* aMaxAge,
-                                        bool* aIncludeSubdomains,
-                                        uint32_t* aFailureResult)
+nsSiteSecurityService::ProcessPKPHeader(
+  nsIURI* aSourceURI,
+  const nsCString& aHeader,
+  nsISSLStatus* aSSLStatus,
+  uint32_t aFlags,
+  const OriginAttributes& aOriginAttributes,
+  uint64_t* aMaxAge,
+  bool* aIncludeSubdomains,
+  uint32_t* aFailureResult)
 {
   if (aFailureResult) {
     *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
   }
   SSSLOG(("SSS: processing HPKP header '%s'", aHeader.get()));
   NS_ENSURE_ARG(aSSLStatus);
 
   const uint32_t aType = nsISiteSecurityService::HEADER_HPKP;
@@ -858,17 +962,18 @@ nsSiteSecurityService::ProcessPKPHeader(
                               CertVerifier::FLAG_TLS_IGNORE_STATUS_REQUEST;
   if (certVerifier->VerifySSLServerCert(nssCert,
                                         nullptr, // stapledOCSPResponse
                                         nullptr, // sctsFromTLSExtension
                                         now, nullptr, // pinarg
                                         host.get(), // hostname
                                         certList,
                                         false, // don't store intermediates
-                                        flags)
+                                        flags,
+                                        aOriginAttributes)
         != mozilla::pkix::Success) {
     return NS_ERROR_FAILURE;
   }
 
   CERTCertListNode* rootNode = CERT_LIST_TAIL(certList);
   if (CERT_LIST_END(rootNode, certList)) {
     return NS_ERROR_FAILURE;
   }
@@ -882,17 +987,17 @@ nsSiteSecurityService::ProcessPKPHeader(
     if (aFailureResult) {
       *aFailureResult = nsISiteSecurityService::ERROR_ROOT_NOT_BUILT_IN;
     }
     return NS_ERROR_FAILURE;
   }
 
   // if maxAge == 0 we must delete all state, for now no hole-punching
   if (maxAge == 0) {
-    return RemoveState(aType, aSourceURI, aFlags);
+    return RemoveStateInternal(aType, aSourceURI, aFlags, aOriginAttributes);
   }
 
   // clamp maxAge to the maximum set by pref
   if (maxAge > mMaxMaxAge) {
     maxAge = mMaxMaxAge;
   }
 
   bool chainMatchesPinset;
@@ -932,22 +1037,22 @@ nsSiteSecurityService::ProcessPKPHeader(
     if (aFailureResult) {
       *aFailureResult = nsISiteSecurityService::ERROR_NO_BACKUP_PIN;
     }
     return NS_ERROR_FAILURE;
   }
 
   int64_t expireTime = ExpireTimeFromMaxAge(maxAge);
   RefPtr<SiteHPKPState> dynamicEntry =
-    new SiteHPKPState(host, expireTime, SecurityPropertySet,
+    new SiteHPKPState(host, aOriginAttributes, expireTime, SecurityPropertySet,
                       foundIncludeSubdomains, sha256keys);
   SSSLOG(("SSS: about to set pins for  %s, expires=%" PRId64 " now=%" PRId64 " maxAge=%" PRIu64 "\n",
            host.get(), expireTime, PR_Now() / PR_USEC_PER_MSEC, maxAge));
 
-  rv = SetHPKPState(host.get(), *dynamicEntry, aFlags, false);
+  rv = SetHPKPState(host.get(), *dynamicEntry, aFlags, false, aOriginAttributes);
   if (NS_FAILED(rv)) {
     SSSLOG(("SSS: failed to set pins for %s\n", host.get()));
     if (aFailureResult) {
       *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
     }
     return rv;
   }
 
@@ -960,22 +1065,24 @@ nsSiteSecurityService::ProcessPKPHeader(
   }
 
   return foundUnrecognizedDirective
            ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
            : NS_OK;
 }
 
 nsresult
-nsSiteSecurityService::ProcessSTSHeader(nsIURI* aSourceURI,
-                                        const nsCString& aHeader,
-                                        uint32_t aFlags,
-                                        uint64_t* aMaxAge,
-                                        bool* aIncludeSubdomains,
-                                        uint32_t* aFailureResult)
+nsSiteSecurityService::ProcessSTSHeader(
+  nsIURI* aSourceURI,
+  const nsCString& aHeader,
+  uint32_t aFlags,
+  const OriginAttributes& aOriginAttributes,
+  uint64_t* aMaxAge,
+  bool* aIncludeSubdomains,
+  uint32_t* aFailureResult)
 {
   if (aFailureResult) {
     *aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
   }
   SSSLOG(("SSS: processing HSTS header '%s'", aHeader.get()));
 
   const uint32_t aType = nsISiteSecurityService::HEADER_HSTS;
   bool foundMaxAge = false;
@@ -1005,17 +1112,17 @@ nsSiteSecurityService::ProcessSTSHeader(
   }
 
   nsAutoCString hostname;
   nsresult rv = GetHost(aSourceURI, hostname);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // record the successfully parsed header data.
   rv = SetHSTSState(aType, hostname.get(), maxAge, foundIncludeSubdomains,
-                    aFlags, SecurityPropertySet, false);
+                    aFlags, SecurityPropertySet, false, aOriginAttributes);
   if (NS_FAILED(rv)) {
     SSSLOG(("SSS: failed to set STS state"));
     if (aFailureResult) {
       *aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
     }
     return rv;
   }
 
@@ -1028,19 +1135,37 @@ nsSiteSecurityService::ProcessSTSHeader(
   }
 
   return foundUnrecognizedDirective
            ? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
            : NS_OK;
 }
 
 NS_IMETHODIMP
+nsSiteSecurityService::IsSecureURIScriptable(uint32_t aType, nsIURI* aURI,
+                                             uint32_t aFlags,
+                                             JS::HandleValue aOriginAttributes,
+                                             bool* aCached, JSContext* aCx,
+                                             uint8_t aArgc,  bool* aResult)
+{
+  OriginAttributes originAttributes;
+  if (aArgc > 0) {
+    if (!aOriginAttributes.isObject() ||
+        !originAttributes.Init(aCx, aOriginAttributes)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+  return IsSecureURI(aType, aURI, aFlags, originAttributes, aCached, aResult);
+}
+
+NS_IMETHODIMP
 nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
-                                   uint32_t aFlags, bool* aCached,
-                                   bool* aResult)
+                                   uint32_t aFlags,
+                                   const OriginAttributes& aOriginAttributes,
+                                   bool* aCached, bool* aResult)
 {
    // Child processes are not allowed direct access to this.
    if (!XRE_IsParentProcess() && aType != nsISiteSecurityService::HEADER_HSTS) {
      MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::IsSecureURI for non-HSTS entries");
    }
 
   NS_ENSURE_ARG(aURI);
   NS_ENSURE_ARG(aResult);
@@ -1054,17 +1179,18 @@ nsSiteSecurityService::IsSecureURI(uint3
   nsresult rv = GetHost(aURI, hostname);
   NS_ENSURE_SUCCESS(rv, rv);
   /* An IP address never qualifies as a secure URI. */
   if (HostIsIPAddress(hostname)) {
     *aResult = false;
     return NS_OK;
   }
 
-  return IsSecureHost(aType, hostname, aFlags, aCached, aResult);
+  return IsSecureHost(aType, hostname, aFlags, aOriginAttributes, aCached,
+                      aResult);
 }
 
 int STSPreloadCompare(const void *key, const void *entry)
 {
   const char *keyStr = (const char *)key;
   const nsSTSPreload *preloadEntry = (const nsSTSPreload *)entry;
   return strcmp(keyStr, &kSTSHostTable[preloadEntry->mHostIndex]);
 }
@@ -1089,36 +1215,40 @@ nsSiteSecurityService::GetPreloadListEnt
 
 // Allows us to determine if we have an HSTS entry for a given host (and, if
 // so, what that state is). The return value says whether or not we know
 // anything about this host (true if the host has an HSTS entry). aHost is
 // the host which we wish to deteming HSTS information on,
 // aRequireIncludeSubdomains specifies whether we require includeSubdomains
 // to be set on the entry (with the other parameters being as per IsSecureHost).
 bool
-nsSiteSecurityService::HostHasHSTSEntry(const nsAutoCString& aHost,
-                                        bool aRequireIncludeSubdomains,
-                                        uint32_t aFlags, bool* aResult,
-                                        bool* aCached)
+nsSiteSecurityService::HostHasHSTSEntry(
+  const nsAutoCString& aHost, bool aRequireIncludeSubdomains, uint32_t aFlags,
+  const OriginAttributes& aOriginAttributes, bool* aResult, bool* aCached)
 {
   // First we check for an entry in site security storage. If that entry exists,
   // we don't want to check in the preload lists. We only want to use the
   // stored value if it is not a knockout entry, however.
   // Additionally, if it is a knockout entry, we want to stop looking for data
   // on the host, because the knockout entry indicates "we have no information
   // regarding the security status of this host".
   bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   nsAutoCString storageKey;
   SSSLOG(("Seeking HSTS entry for %s", aHost.get()));
-  SetStorageKey(storageKey, aHost, nsISiteSecurityService::HEADER_HSTS);
+  SetStorageKey(aHost, nsISiteSecurityService::HEADER_HSTS, aOriginAttributes,
+                storageKey);
+  nsAutoCString preloadKey;
+  SetStorageKey(aHost, nsISiteSecurityService::HEADER_HSTS, OriginAttributes(),
+                preloadKey);
   nsCString value = mSiteStateStorage->Get(storageKey, storageType);
-  RefPtr<SiteHSTSState> siteState = new SiteHSTSState(aHost, value);
+  RefPtr<SiteHSTSState> siteState =
+    new SiteHSTSState(aHost, aOriginAttributes, value);
   if (siteState->mHSTSState != SecurityPropertyUnset) {
     SSSLOG(("Found HSTS entry for %s", aHost.get()));
     bool expired = siteState->IsExpired(nsISiteSecurityService::HEADER_HSTS);
     if (!expired) {
       SSSLOG(("Entry for %s is not expired", aHost.get()));
       if (aCached) {
         *aCached = true;
       }
@@ -1132,52 +1262,54 @@ nsSiteSecurityService::HostHasHSTSEntry(
       }
     }
 
     if (expired) {
       SSSLOG(("Entry %s is expired - checking for preload state", aHost.get()));
       // If the entry is expired and is not in either the static or dynamic
       // preload lists, we can remove it.
       // First, check the dynamic preload list.
-      value = mPreloadStateStorage->Get(storageKey,
+      value = mPreloadStateStorage->Get(preloadKey,
                                         mozilla::DataStorage_Persistent);
-      RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
+      RefPtr<SiteHSTSState> dynamicState =
+        new SiteHSTSState(aHost, aOriginAttributes, value);
       if (dynamicState->mHSTSState == SecurityPropertyUnset) {
         SSSLOG(("No dynamic preload - checking for static preload"));
         // Now check the static preload list.
         if (!GetPreloadListEntry(aHost.get())) {
           SSSLOG(("No static preload - removing expired entry"));
           mSiteStateStorage->Remove(storageKey, storageType);
         }
       }
     }
     return false;
   }
 
   // Next, look in the dynamic preload list.
-  value = mPreloadStateStorage->Get(storageKey,
+  value = mPreloadStateStorage->Get(preloadKey,
                                     mozilla::DataStorage_Persistent);
-  RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
+  RefPtr<SiteHSTSState> dynamicState =
+    new SiteHSTSState(aHost, aOriginAttributes, value);
   if (dynamicState->mHSTSState != SecurityPropertyUnset) {
     SSSLOG(("Found dynamic preload entry for %s", aHost.get()));
     bool expired = dynamicState->IsExpired(nsISiteSecurityService::HEADER_HSTS);
     if (!expired) {
       if (dynamicState->mHSTSState == SecurityPropertySet) {
         *aResult = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
                                              : true;
         return true;
       } else if (dynamicState->mHSTSState == SecurityPropertyNegative) {
         *aResult = false;
         return true;
       }
     } else {
       // if a dynamic preload has expired and is not in the static preload
       // list, we can remove it.
       if (!GetPreloadListEntry(aHost.get())) {
-        mPreloadStateStorage->Remove(storageKey,
+        mPreloadStateStorage->Remove(preloadKey,
                                      mozilla::DataStorage_Persistent);
       }
     }
     return false;
   }
 
   const nsSTSPreload* preload = nullptr;
 
@@ -1194,18 +1326,19 @@ nsSiteSecurityService::HostHasHSTSEntry(
     return true;
   }
 
   return false;
 }
 
 nsresult
 nsSiteSecurityService::IsSecureHost(uint32_t aType, const nsACString& aHost,
-                                    uint32_t aFlags, bool* aCached,
-                                    bool* aResult)
+                                    uint32_t aFlags,
+                                    const OriginAttributes& aOriginAttributes,
+                                    bool* aCached, bool* aResult)
 {
   // Child processes are not allowed direct access to this.
   if (!XRE_IsParentProcess() && aType != nsISiteSecurityService::HEADER_HSTS) {
     MOZ_CRASH("Child process: no direct access to "
               "nsISiteSecurityService::IsSecureHost for non-HSTS entries");
   }
 
   NS_ENSURE_ARG(aResult);
@@ -1235,32 +1368,33 @@ nsSiteSecurityService::IsSecureHost(uint
     if (certVerifier->mPinningMode ==
         CertVerifier::PinningMode::pinningDisabled) {
       return NS_OK;
     }
     bool enforceTestMode = certVerifier->mPinningMode ==
                            CertVerifier::PinningMode::pinningEnforceTestMode;
     return PublicKeyPinningService::HostHasPins(flatHost.get(),
                                                 mozilla::pkix::Now(),
-                                                enforceTestMode, *aResult);
+                                                enforceTestMode, aOriginAttributes,
+                                                *aResult);
   }
 
   // Holepunch chart.apis.google.com and subdomains.
   nsAutoCString host(
     PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
   if (host.EqualsLiteral("chart.apis.google.com") ||
       StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) {
     if (aCached) {
       *aCached = true;
     }
     return NS_OK;
   }
 
   // First check the exact host.
-  if (HostHasHSTSEntry(host, false, aFlags, aResult, aCached)) {
+  if (HostHasHSTSEntry(host, false, aFlags, aOriginAttributes, aResult, aCached)) {
     return NS_OK;
   }
 
 
   SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
   const char *subdomain;
 
   uint32_t offset = 0;
@@ -1275,17 +1409,18 @@ nsSiteSecurityService::IsSecureHost(uint
       break;
     }
 
     // Do the same thing as with the exact host except now we're looking at
     // ancestor domains of the original host and, therefore, we have to require
     // that the entry includes subdomains.
     nsAutoCString subdomainString(subdomain);
 
-    if (HostHasHSTSEntry(subdomainString, true, aFlags, aResult, aCached)) {
+    if (HostHasHSTSEntry(subdomainString, true, aFlags, aOriginAttributes, aResult,
+                         aCached)) {
       break;
     }
 
     SSSLOG(("no HSTS data for %s found, walking up domain", subdomain));
   }
 
   // Use whatever we ended up with, which defaults to false.
   return NS_OK;
@@ -1314,21 +1449,23 @@ nsSiteSecurityService::ClearPreloads()
 }
 
 bool entryStateNotOK(SiteHPKPState& state, mozilla::pkix::Time& aEvalTime) {
   return state.mState != SecurityPropertySet || state.IsExpired(aEvalTime) ||
          state.mSHA256keys.Length() < 1;
 }
 
 NS_IMETHODIMP
-nsSiteSecurityService::GetKeyPinsForHostname(const nsACString& aHostname,
-                                             mozilla::pkix::Time& aEvalTime,
-                                             /*out*/ nsTArray<nsCString>& pinArray,
-                                             /*out*/ bool* aIncludeSubdomains,
-                                             /*out*/ bool* afound)
+nsSiteSecurityService::GetKeyPinsForHostname(
+  const nsACString& aHostname,
+  mozilla::pkix::Time& aEvalTime,
+  const OriginAttributes& aOriginAttributes,
+  /*out*/ nsTArray<nsCString>& pinArray,
+  /*out*/ bool* aIncludeSubdomains,
+  /*out*/ bool* afound)
 {
   // Child processes are not allowed direct access to this.
   if (!XRE_IsParentProcess()) {
     MOZ_CRASH("Child process: no direct access to "
               "nsISiteSecurityService::GetKeyPinsForHostname");
   }
 
   NS_ENSURE_ARG(afound);
@@ -1337,33 +1474,40 @@ nsSiteSecurityService::GetKeyPinsForHost
   SSSLOG(("Top of GetKeyPinsForHostname for %s", flatHostname.get()));
   *afound = false;
   *aIncludeSubdomains = false;
   pinArray.Clear();
 
   nsAutoCString host(
     PublicKeyPinningService::CanonicalizeHostname(flatHostname.get()));
   nsAutoCString storageKey;
-  SetStorageKey(storageKey, host, nsISiteSecurityService::HEADER_HPKP);
+  SetStorageKey(host, nsISiteSecurityService::HEADER_HPKP, aOriginAttributes,
+                storageKey);
 
   SSSLOG(("storagekey '%s'\n", storageKey.get()));
   mozilla::DataStorageType storageType = mozilla::DataStorage_Persistent;
   nsCString value = mSiteStateStorage->Get(storageKey, storageType);
 
   // decode now
-  RefPtr<SiteHPKPState> foundEntry = new SiteHPKPState(host, value);
+  RefPtr<SiteHPKPState> foundEntry =
+    new SiteHPKPState(host, aOriginAttributes, value);
   if (entryStateNotOK(*foundEntry, aEvalTime)) {
     // not in permanent storage, try now private
     value = mSiteStateStorage->Get(storageKey, mozilla::DataStorage_Private);
-    RefPtr<SiteHPKPState> privateEntry = new SiteHPKPState(host, value);
+    RefPtr<SiteHPKPState> privateEntry =
+      new SiteHPKPState(host, aOriginAttributes, value);
     if (entryStateNotOK(*privateEntry, aEvalTime)) {
       // not in private storage, try dynamic preload
-      value = mPreloadStateStorage->Get(storageKey,
+      nsAutoCString preloadKey;
+      SetStorageKey(host, nsISiteSecurityService::HEADER_HPKP,
+                    OriginAttributes(), preloadKey);
+      value = mPreloadStateStorage->Get(preloadKey,
                                         mozilla::DataStorage_Persistent);
-      RefPtr<SiteHPKPState> preloadEntry = new SiteHPKPState(host, value);
+      RefPtr<SiteHPKPState> preloadEntry =
+        new SiteHPKPState(host, aOriginAttributes, value);
       if (entryStateNotOK(*preloadEntry, aEvalTime)) {
         return NS_OK;
       }
       foundEntry = preloadEntry;
     } else {
       foundEntry = privateEntry;
     }
   }
@@ -1374,46 +1518,59 @@ nsSiteSecurityService::GetKeyPinsForHost
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::SetKeyPins(const nsACString& aHost,
                                   bool aIncludeSubdomains,
                                   int64_t aExpires, uint32_t aPinCount,
                                   const char** aSha256Pins,
                                   bool aIsPreload,
+                                  JS::HandleValue aOriginAttributes,
+                                  JSContext* aCx,
+                                  uint8_t aArgc,
                                   /*out*/ bool* aResult)
 {
   // Child processes are not allowed direct access to this.
   if (!XRE_IsParentProcess()) {
     MOZ_CRASH("Child process: no direct access to "
               "nsISiteSecurityService::SetKeyPins");
   }
 
   NS_ENSURE_ARG_POINTER(aResult);
   NS_ENSURE_ARG_POINTER(aSha256Pins);
+  OriginAttributes originAttributes;
+  if (aArgc > 1) {
+    // OriginAttributes were passed in.
+    if (!aOriginAttributes.isObject() ||
+        !originAttributes.Init(aCx, aOriginAttributes)) {
+      return NS_ERROR_INVALID_ARG;
+    }
+  }
+  if (aIsPreload && originAttributes != OriginAttributes()) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   SSSLOG(("Top of SetKeyPins"));
 
   nsTArray<nsCString> sha256keys;
   for (unsigned int i = 0; i < aPinCount; i++) {
     nsAutoCString pin(aSha256Pins[i]);
     SSSLOG(("SetPins pin=%s\n", pin.get()));
     if (!stringIsBase64EncodingOf256bitValue(pin)) {
       return NS_ERROR_INVALID_ARG;
     }
     sha256keys.AppendElement(pin);
   }
   // we always store data in permanent storage (ie no flags)
   const nsCString& flatHost = PromiseFlatCString(aHost);
   nsAutoCString host(
     PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
-  RefPtr<SiteHPKPState> dynamicEntry =
-    new SiteHPKPState(host, aExpires, SecurityPropertySet, aIncludeSubdomains,
-                      sha256keys);
-  return SetHPKPState(host.get(), *dynamicEntry, 0, aIsPreload);
+  RefPtr<SiteHPKPState> dynamicEntry = new SiteHPKPState(host, originAttributes,
+    aExpires, SecurityPropertySet, aIncludeSubdomains, sha256keys);
+  return SetHPKPState(host.get(), *dynamicEntry, 0, aIsPreload, originAttributes);
 }
 
 NS_IMETHODIMP
 nsSiteSecurityService::SetHSTSPreload(const nsACString& aHost,
                                       bool aIncludeSubdomains,
                                       int64_t aExpires,
                               /*out*/ bool* aResult)
 {
@@ -1426,27 +1583,33 @@ nsSiteSecurityService::SetHSTSPreload(co
   NS_ENSURE_ARG_POINTER(aResult);
 
   SSSLOG(("Top of SetHSTSPreload"));
 
   const nsCString& flatHost = PromiseFlatCString(aHost);
   nsAutoCString host(
     PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
   return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, host.get(), aExpires,
-                      aIncludeSubdomains, 0, SecurityPropertySet, true);
+                      aIncludeSubdomains, 0, SecurityPropertySet, true,
+                      OriginAttributes());
 }
 
 nsresult
 nsSiteSecurityService::SetHPKPState(const char* aHost, SiteHPKPState& entry,
-                                    uint32_t aFlags, bool aIsPreload)
+                                    uint32_t aFlags, bool aIsPreload,
+                                    const OriginAttributes& aOriginAttributes)
 {
+  if (aIsPreload && aOriginAttributes != OriginAttributes()) {
+    return NS_ERROR_INVALID_ARG;
+  }
   SSSLOG(("Top of SetPKPState"));
   nsAutoCString host(aHost);
   nsAutoCString storageKey;
-  SetStorageKey(storageKey, host, nsISiteSecurityService::HEADER_HPKP);
+  SetStorageKey(host, nsISiteSecurityService::HEADER_HPKP, aOriginAttributes,
+                storageKey);
   bool isPrivate = aFlags & nsISocketProvider::NO_PERMANENT_STORAGE;
   mozilla::DataStorageType storageType = isPrivate
                                          ? mozilla::DataStorage_Private
                                          : mozilla::DataStorage_Persistent;
   nsAutoCString stateString;
   entry.ToString(stateString);
 
   nsresult rv;
@@ -1483,25 +1646,31 @@ nsSiteSecurityService::Enumerate(uint32_
 
   nsCOMArray<nsISiteSecurityState> states;
   for (const mozilla::dom::DataStorageItem& item : items) {
     if (!StringEndsWith(item.key(), keySuffix)) {
       // The key does not end with correct suffix, so is not the type we want.
       continue;
     }
 
-    nsCString hostname(
+    nsCString origin(
       StringHead(item.key(), item.key().Length() - keySuffix.Length()));
+    nsAutoCString hostname;
+    OriginAttributes originAttributes;
+    if (!originAttributes.PopulateFromOrigin(origin, hostname)) {
+      return NS_ERROR_FAILURE;
+    }
+
     nsCOMPtr<nsISiteSecurityState> state;
     switch(aType) {
       case nsISiteSecurityService::HEADER_HSTS:
-        state = new SiteHSTSState(hostname, item.value());
+        state = new SiteHSTSState(hostname, originAttributes, item.value());
         break;
       case nsISiteSecurityService::HEADER_HPKP:
-        state = new SiteHPKPState(hostname, item.value());
+        state = new SiteHPKPState(hostname, originAttributes, item.value());
         break;
       default:
         MOZ_ASSERT_UNREACHABLE("SSS:Enumerate got invalid type");
     }
 
     states.AppendObject(state);
   }
 
--- a/security/manager/ssl/nsSiteSecurityService.h
+++ b/security/manager/ssl/nsSiteSecurityService.h
@@ -1,28 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __nsSiteSecurityService_h__
 #define __nsSiteSecurityService_h__
 
+#include "mozilla/BasePrincipal.h"
 #include "mozilla/DataStorage.h"
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 #include "nsISiteSecurityService.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "pkix/pkixtypes.h"
 #include "prtime.h"
 
 class nsIURI;
 class nsISSLStatus;
 
+using mozilla::OriginAttributes;
+
 // {16955eee-6c48-4152-9309-c42a465138a1}
 #define NS_SITE_SECURITY_SERVICE_CID \
   {0x16955eee, 0x6c48, 0x4152, \
     {0x93, 0x09, 0xc4, 0x2a, 0x46, 0x51, 0x38, 0xa1} }
 
 /**
  * SecurityPropertyState: A utility enum for representing the different states
  * a security property can be in.
@@ -39,35 +42,40 @@ enum SecurityPropertyState {
   SecurityPropertyNegative = nsISiteSecurityState::SECURITY_PROPERTY_NEGATIVE,
 };
 
 /**
  * SiteHPKPState: A utility class that encodes/decodes a string describing
  * the public key pins of a site.
  * HPKP state consists of:
  *  - Hostname (nsCString)
+ *  - Origin attributes (OriginAttributes)
  *  - Expiry time (PRTime (aka int64_t) in milliseconds)
  *  - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
  *  - An include subdomains flag (bool, default false)
  *  - An array of sha-256 hashed base 64 encoded fingerprints of required keys
  */
 class SiteHPKPState : public nsISiteHPKPState
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISITEHPKPSTATE
   NS_DECL_NSISITESECURITYSTATE
 
   SiteHPKPState();
-  SiteHPKPState(const nsCString& aHost, const nsCString& aStateString);
-  SiteHPKPState(const nsCString& aHost, PRTime aExpireTime,
-                SecurityPropertyState aState, bool aIncludeSubdomains,
-                nsTArray<nsCString>& SHA256keys);
+  SiteHPKPState(const nsCString& aHost,
+                const OriginAttributes& aOriginAttributes,
+                const nsCString& aStateString);
+  SiteHPKPState(const nsCString& aHost,
+                const OriginAttributes& aOriginAttributes,
+                PRTime aExpireTime, SecurityPropertyState aState,
+                bool aIncludeSubdomains, nsTArray<nsCString>& SHA256keys);
 
   nsCString mHostname;
+  OriginAttributes mOriginAttributes;
   PRTime mExpireTime;
   SecurityPropertyState mState;
   bool mIncludeSubdomains;
   nsTArray<nsCString> mSHA256keys;
 
   bool IsExpired(mozilla::pkix::Time aTime)
   {
     if (aTime > mozilla::pkix::TimeFromEpochInSeconds(mExpireTime /
@@ -83,32 +91,38 @@ protected:
   virtual ~SiteHPKPState() {};
 };
 
 /**
  * SiteHSTSState: A utility class that encodes/decodes a string describing
  * the security state of a site. Currently only handles HSTS.
  * HSTS state consists of:
  *  - Hostname (nsCString)
+ *  - Origin attributes (OriginAttributes)
  *  - Expiry time (PRTime (aka int64_t) in milliseconds)
  *  - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
  *  - An include subdomains flag (bool, default false)
  */
 class SiteHSTSState : public nsISiteHSTSState
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISITEHSTSSTATE
   NS_DECL_NSISITESECURITYSTATE
 
-  SiteHSTSState(const nsCString& aHost, const nsCString& aStateString);
-  SiteHSTSState(const nsCString& aHost, PRTime aHSTSExpireTime,
-                SecurityPropertyState aHSTSState, bool aHSTSIncludeSubdomains);
+  SiteHSTSState(const nsCString& aHost,
+                const OriginAttributes& aOriginAttributes,
+                const nsCString& aStateString);
+  SiteHSTSState(const nsCString& aHost,
+                const OriginAttributes& aOriginAttributes,
+                PRTime aHSTSExpireTime, SecurityPropertyState aHSTSState,
+                bool aHSTSIncludeSubdomains);
 
   nsCString mHostname;
+  OriginAttributes mOriginAttributes;
   PRTime mHSTSExpireTime;
   SecurityPropertyState mHSTSState;
   bool mHSTSIncludeSubdomains;
 
   bool IsExpired(uint32_t aType)
   {
     // If mHSTSExpireTime is 0, this entry never expires (this is the case for
     // knockout entries).
@@ -145,40 +159,52 @@ public:
 
 protected:
   virtual ~nsSiteSecurityService();
 
 private:
   nsresult GetHost(nsIURI *aURI, nsACString &aResult);
   nsresult SetHSTSState(uint32_t aType, const char* aHost, int64_t maxage,
                         bool includeSubdomains, uint32_t flags,
-                        SecurityPropertyState aHSTSState, bool aIsPreload);
+                        SecurityPropertyState aHSTSState, bool aIsPreload,
+                        const OriginAttributes& aOriginAttributes);
   nsresult ProcessHeaderInternal(uint32_t aType, nsIURI* aSourceURI,
                                  const nsCString& aHeader,
                                  nsISSLStatus* aSSLStatus,
-                                 uint32_t aFlags, uint64_t* aMaxAge,
-                                 bool* aIncludeSubdomains,
+                                 uint32_t aFlags,
+                                 const OriginAttributes& aOriginAttributes,
+                                 uint64_t* aMaxAge, bool* aIncludeSubdomains,
                                  uint32_t* aFailureResult);
   nsresult ProcessSTSHeader(nsIURI* aSourceURI, const nsCString& aHeader,
-                            uint32_t flags, uint64_t* aMaxAge,
-                            bool* aIncludeSubdomains, uint32_t* aFailureResult);
+                            uint32_t flags,
+                            const OriginAttributes& aOriginAttributes,
+                            uint64_t* aMaxAge, bool* aIncludeSubdomains,
+                            uint32_t* aFailureResult);
   nsresult ProcessPKPHeader(nsIURI* aSourceURI, const nsCString& aHeader,
                             nsISSLStatus* aSSLStatus, uint32_t flags,
+                            const OriginAttributes& aOriginAttributes,
                             uint64_t* aMaxAge, bool* aIncludeSubdomains,
                             uint32_t* aFailureResult);
   nsresult SetHPKPState(const char* aHost, SiteHPKPState& entry, uint32_t flags,
-                        bool aIsPreload);
+                        bool aIsPreload,
+                        const OriginAttributes& aOriginAttributes);
+  nsresult RemoveStateInternal(uint32_t aType, nsIURI* aURI, uint32_t aFlags,
+                               const OriginAttributes& aOriginAttributes);
   nsresult RemoveStateInternal(uint32_t aType, const nsAutoCString& aHost,
-                               uint32_t aFlags, bool aIsPreload);
+                               uint32_t aFlags, bool aIsPreload,
+                               const OriginAttributes& aOriginAttributes);
   bool HostHasHSTSEntry(const nsAutoCString& aHost,
                         bool aRequireIncludeSubdomains, uint32_t aFlags,
+                        const OriginAttributes& aOriginAttributes,
                         bool* aResult, bool* aCached);
   const nsSTSPreload *GetPreloadListEntry(const char *aHost);
   nsresult IsSecureHost(uint32_t aType, const nsACString& aHost,
-                        uint32_t aFlags, bool* aCached, bool* aResult);
+                        uint32_t aFlags,
+                        const OriginAttributes& aOriginAttributes,
+                        bool* aCached, bool* aResult);
 
   uint64_t mMaxMaxAge;
   bool mUsePreloadList;
   int64_t mPreloadListTimeOffset;
   bool mProcessPKPHeadersFromNonBuiltInRoots;
   RefPtr<mozilla::DataStorage> mSiteStateStorage;
   RefPtr<mozilla::DataStorage> mPreloadStateStorage;
 };
--- a/security/manager/ssl/tests/unit/test_pinning_header_parsing.js
+++ b/security/manager/ssl/tests/unit/test_pinning_header_parsing.js
@@ -45,17 +45,17 @@ function checkPassValidPin(pinValue, set
   } else {
     // add a known valid pin!
     let validPinValue = "max-age=5000;" + VALID_PIN1 + BACKUP_PIN1;
     gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
                              validPinValue, sslStatus, 0);
   }
   try {
     gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
-                             pinValue, sslStatus, 0, maxAge);
+                             pinValue, sslStatus, 0, {}, maxAge);
     ok(true, "Valid pin should be accepted");
   } catch (e) {
     ok(false, "Valid pin should have been accepted");
   }
 
   // check that maxAge was processed correctly
   if (settingPin && expectedMaxAge) {
     ok(maxAge.value == expectedMaxAge, `max-age value should be ${expectedMaxAge}`);
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_sss_originAttributes.js
@@ -0,0 +1,141 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+ * vim: sw=2 ts=2 sts=2
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// Ensures nsISiteSecurityService APIs respects origin attributes.
+
+do_register_cleanup(() => {
+  Services.prefs.clearUserPref("security.cert_pinning.enforcement_level");
+  Services.prefs.clearUserPref(
+    "security.cert_pinning.process_headers_from_non_builtin_roots");
+});
+
+const GOOD_MAX_AGE_SECONDS = 69403;
+const NON_ISSUED_KEY_HASH = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+const PINNING_ROOT_KEY_HASH = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=";
+const VALID_PIN = `pin-sha256="${PINNING_ROOT_KEY_HASH}";`;
+const BACKUP_PIN = `pin-sha256="${NON_ISSUED_KEY_HASH}";`;
+const GOOD_MAX_AGE = `max-age=${GOOD_MAX_AGE_SECONDS};`;
+
+do_get_profile(); // must be done before instantiating nsIX509CertDB
+
+Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 2);
+Services.prefs.setBoolPref(
+  "security.cert_pinning.process_headers_from_non_builtin_roots", true);
+
+let certdb = Cc["@mozilla.org/security/x509certdb;1"]
+               .getService(Ci.nsIX509CertDB);
+addCertFromFile(certdb, "test_pinning_dynamic/pinningroot.pem", "CTu,CTu,CTu");
+
+let sss = Cc["@mozilla.org/ssservice;1"]
+            .getService(Ci.nsISiteSecurityService);
+let host = "a.pinning2.example.com";
+let uri = Services.io.newURI("https://" + host);
+
+// This test re-uses certificates from pinning tests because that's easier and
+// simpler than recreating new certificates, hence the slightly longer than
+// necessary domain name.
+let sslStatus = new FakeSSLStatus(constructCertFromFile(
+  "test_pinning_dynamic/a.pinning2.example.com-pinningroot.pem"));
+
+// Check if originAttributes1 and originAttributes2 are isolated with respect
+// to HSTS/HPKP storage.
+function doTest(originAttributes1, originAttributes2, shouldShare) {
+  sss.clearAll();
+  for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
+                    Ci.nsISiteSecurityService.HEADER_HPKP]) {
+    let header = GOOD_MAX_AGE;
+    if (type == Ci.nsISiteSecurityService.HEADER_HPKP) {
+      header += VALID_PIN + BACKUP_PIN;
+    }
+    // Set HSTS or HPKP for originAttributes1.
+    sss.processHeader(type, uri, header, sslStatus, 0, originAttributes1);
+    ok(sss.isSecureURI(type, uri, 0, originAttributes1),
+       "URI should be secure given original origin attributes");
+    equal(sss.isSecureURI(type, uri, 0, originAttributes2), shouldShare,
+          "URI should be secure given different origin attributes if and " +
+          "only if shouldShare is true");
+
+    if (!shouldShare) {
+      // Remove originAttributes2 from the storage.
+      sss.removeState(type, uri, 0, originAttributes2);
+      ok(sss.isSecureURI(type, uri, 0, originAttributes1),
+         "URI should still be secure given original origin attributes");
+    }
+
+    // Remove originAttributes1 from the storage.
+    sss.removeState(type, uri, 0, originAttributes1);
+    ok(!sss.isSecureURI(type, uri, 0, originAttributes1),
+       "URI should be not be secure after removeState");
+  }
+  // Set HPKP for originAttributes1.
+  sss.setKeyPins(host, false, Date.now() + 1234567890, 2,
+                 [NON_ISSUED_KEY_HASH, PINNING_ROOT_KEY_HASH], false,
+                 originAttributes1);
+  ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0,
+                     originAttributes1),
+     "URI should be secure after setKeyPins given original origin attributes");
+  equal(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0,
+                        originAttributes2),
+        shouldShare, "URI should be secure after setKeyPins given different " +
+        "origin attributes if and only if shouldShare is true");
+
+  sss.clearAll();
+  ok(!sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0,
+                      originAttributes1),
+     "URI should not be secure after clearAll");
+}
+
+function testInvalidOriginAttributes(originAttributes) {
+  for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
+                    Ci.nsISiteSecurityService.HEADER_HPKP]) {
+    let header = GOOD_MAX_AGE;
+    if (type == Ci.nsISiteSecurityService.HEADER_HPKP) {
+      header += VALID_PIN + BACKUP_PIN;
+    }
+
+    let callbacks = [
+      () => sss.processHeader(type, uri, header, sslStatus, 0,
+                              originAttributes),
+      () => sss.isSecureURI(type, uri, 0, originAttributes),
+      () => sss.removeState(type, uri, 0, originAttributes),
+    ];
+
+    for (let callback of callbacks) {
+      throws(callback, /NS_ERROR_ILLEGAL_VALUE/,
+             "Should get an error with invalid origin attributes");
+    }
+  }
+
+  throws(() => sss.setKeyPins(host, false, Date.now() + 1234567890, 2,
+                              [NON_ISSUED_KEY_HASH, PINNING_ROOT_KEY_HASH],
+                              false, originAttributes),
+         /NS_ERROR_ILLEGAL_VALUE/,
+         "Should get an error with invalid origin attributes");
+}
+
+function run_test() {
+  sss.clearAll();
+  let originAttributesList = [];
+  for (let userContextId of [0, 1, 2]) {
+    for (let firstPartyDomain of ["", "foo.com", "bar.com"]) {
+      originAttributesList.push({ userContextId, firstPartyDomain });
+    }
+  }
+  for (let attrs1 of originAttributesList) {
+    for (let attrs2 of originAttributesList) {
+      // SSS storage is not isolated by userContext
+      doTest(attrs1, attrs2,
+             attrs1.firstPartyDomain == attrs2.firstPartyDomain);
+    }
+  }
+
+  testInvalidOriginAttributes(undefined);
+  testInvalidOriginAttributes(null);
+  testInvalidOriginAttributes(1);
+  testInvalidOriginAttributes("foo");
+}
--- a/security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
+++ b/security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
@@ -14,17 +14,17 @@ function check_ip(s, v, ip) {
   str += "/";
 
   let uri = Services.io.newURI(str);
   ok(!s.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
 
   let parsedMaxAge = {};
   let parsedIncludeSubdomains = {};
   s.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
-                  "max-age=1000;includeSubdomains", sslStatus, 0,
+                  "max-age=1000;includeSubdomains", sslStatus, 0, {},
                   parsedMaxAge, parsedIncludeSubdomains);
   ok(!s.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
      "URI should not be secure if it contains an IP address");
 
   /* Test that processHeader will ignore headers for an uri, if the uri
    * contains an IP address not a hostname.
    * If processHeader indeed ignore the header, then the output parameters will
    * remain empty, and we shouldn't see the values passed as the header.
--- a/security/manager/ssl/tests/unit/test_sts_parser.js
+++ b/security/manager/ssl/tests/unit/test_sts_parser.js
@@ -12,31 +12,31 @@ let sss = Cc["@mozilla.org/ssservice;1"]
 let sslStatus = new FakeSSLStatus();
 
 function testSuccess(header, expectedMaxAge, expectedIncludeSubdomains) {
   let dummyUri = Services.io.newURI("https://foo.com/bar.html");
   let maxAge = {};
   let includeSubdomains = {};
 
   sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, dummyUri, header,
-                    sslStatus, 0, maxAge, includeSubdomains);
+                    sslStatus, 0, {}, maxAge, includeSubdomains);
 
   equal(maxAge.value, expectedMaxAge, "Did not correctly parse maxAge");
   equal(includeSubdomains.value, expectedIncludeSubdomains,
         "Did not correctly parse presence/absence of includeSubdomains");
 }
 
 function testFailure(header) {
   let dummyUri = Services.io.newURI("https://foo.com/bar.html");
   let maxAge = {};
   let includeSubdomains = {};
 
   throws(() => {
     sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, dummyUri, header,
-                      sslStatus, 0, maxAge, includeSubdomains);
+                      sslStatus, 0, {}, maxAge, includeSubdomains);
   }, "Parsed invalid header: " + header);
 }
 
 function run_test() {
     // SHOULD SUCCEED:
     testSuccess("max-age=100", 100, false);
     testSuccess("max-age  =100", 100, false);
     testSuccess(" max-age=100", 100, false);
--- a/security/manager/ssl/tests/unit/xpcshell.ini
+++ b/security/manager/ssl/tests/unit/xpcshell.ini
@@ -122,16 +122,17 @@ requesttimeoutfactor = 2
 [test_sdr_preexisting.js]
 [test_session_resumption.js]
 run-sequentially = hardcoded ports
 [test_signed_apps.js]
 [test_signed_dir.js]
 tags = addons psm
 [test_sss_enumerate.js]
 [test_sss_eviction.js]
+[test_sss_originAttributes.js]
 [test_sss_readstate.js]
 [test_sss_readstate_child.js]
 support-files = sss_readstate_child_worker.js
 # bug 1124289 - run_test_in_child violates the sandbox on android
 skip-if = toolkit == 'android'
 [test_sss_readstate_empty.js]
 [test_sss_readstate_garbage.js]
 [test_sss_readstate_huge.js]
--- a/security/manager/tools/getHSTSPreloadList.js
+++ b/security/manager/tools/getHSTSPreloadList.js
@@ -114,17 +114,17 @@ function processStsHeader(host, header, 
   var includeSubdomains = { value: false };
   var error = ERROR_NONE;
   if (header != null && securityInfo != null) {
     try {
       var uri = Services.io.newURI("https://" + host.name);
       var sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
                                   .SSLStatus;
       gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS,
-                               uri, header, sslStatus, 0, maxAge,
+                               uri, header, sslStatus, 0, {}, maxAge,
                                includeSubdomains);
     }
     catch (e) {
       dump("ERROR: could not process header '" + header + "' from " +
            host.name + ": " + e + "\n");
       error = e;
     }
   } else if (status == 0) {