--- 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) {