Bug 1321780 - Modify SiteSecurityService to allow dynamic STS preloads rr=?keeler r=keeler
MozReview-Commit-ID: 2a75179pIH4
This commit is contained in:
@@ -294,7 +294,7 @@ nsSiteSecurityService::GetHost(nsIURI* aURI, nsACString& aResult)
|
||||
}
|
||||
|
||||
static void
|
||||
SetStorageKey(nsAutoCString& storageKey, nsCString& hostname, uint32_t aType)
|
||||
SetStorageKey(nsAutoCString& storageKey, const nsACString& hostname, uint32_t aType)
|
||||
{
|
||||
storageKey = hostname;
|
||||
switch (aType) {
|
||||
@@ -319,16 +319,18 @@ ExpireTimeFromMaxAge(uint64_t maxAge)
|
||||
|
||||
nsresult
|
||||
nsSiteSecurityService::SetHSTSState(uint32_t aType,
|
||||
nsIURI* aSourceURI,
|
||||
const char* aHost,
|
||||
int64_t maxage,
|
||||
bool includeSubdomains,
|
||||
uint32_t flags,
|
||||
SecurityPropertyState aHSTSState)
|
||||
SecurityPropertyState aHSTSState,
|
||||
bool aIsPreload)
|
||||
{
|
||||
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 RemoveState(aType, aSourceURI, flags);
|
||||
return RemoveStateInternal(aType, hostname, flags, aIsPreload);
|
||||
}
|
||||
|
||||
MOZ_ASSERT((aHSTSState == SecurityPropertySet ||
|
||||
@@ -339,9 +341,6 @@ nsSiteSecurityService::SetHSTSState(uint32_t aType,
|
||||
SiteHSTSState siteState(expiretime, aHSTSState, includeSubdomains);
|
||||
nsAutoCString stateString;
|
||||
siteState.ToString(stateString);
|
||||
nsAutoCString hostname;
|
||||
nsresult rv = GetHost(aSourceURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
SSSLOG(("SSS: setting state for %s", hostname.get()));
|
||||
bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
|
||||
mozilla::DataStorageType storageType = isPrivate
|
||||
@@ -349,7 +348,15 @@ nsSiteSecurityService::SetHSTSState(uint32_t aType,
|
||||
: mozilla::DataStorage_Persistent;
|
||||
nsAutoCString storageKey;
|
||||
SetStorageKey(storageKey, hostname, aType);
|
||||
rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
|
||||
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;
|
||||
@@ -359,17 +366,21 @@ NS_IMETHODIMP
|
||||
nsSiteSecurityService::CacheNegativeHSTSResult(nsIURI* aSourceURI,
|
||||
uint64_t aMaxAge)
|
||||
{
|
||||
return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, aSourceURI,
|
||||
aMaxAge, false, 0, SecurityPropertyNegative);
|
||||
nsAutoCString hostname;
|
||||
nsresult rv = GetHost(aSourceURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, hostname.get(),
|
||||
aMaxAge, false, 0, SecurityPropertyNegative, false);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI,
|
||||
uint32_t aFlags)
|
||||
nsresult
|
||||
nsSiteSecurityService::RemoveStateInternal(uint32_t aType,
|
||||
const nsAutoCString& aHost,
|
||||
uint32_t aFlags, bool aIsPreload)
|
||||
{
|
||||
// Child processes are not allowed direct access to this.
|
||||
if (!XRE_IsParentProcess()) {
|
||||
MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::RemoveState");
|
||||
MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::RemoveStateInternal");
|
||||
}
|
||||
|
||||
// Only HSTS is supported at the moment.
|
||||
@@ -377,34 +388,52 @@ nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI,
|
||||
aType == nsISiteSecurityService::HEADER_HPKP,
|
||||
NS_ERROR_NOT_IMPLEMENTED);
|
||||
|
||||
nsAutoCString hostname;
|
||||
nsresult rv = GetHost(aURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
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.
|
||||
if (GetPreloadListEntry(hostname.get())) {
|
||||
SSSLOG(("SSS: storing knockout entry for %s", hostname.get()));
|
||||
nsAutoCString storageKey;
|
||||
SetStorageKey(storageKey, aHost, aType);
|
||||
|
||||
nsCString value = mPreloadStateStorage->Get(storageKey,
|
||||
mozilla::DataStorage_Persistent);
|
||||
SiteHSTSState dynamicState(value);
|
||||
if (GetPreloadListEntry(aHost.get()) ||
|
||||
dynamicState.mHSTSState != SecurityPropertyUnset) {
|
||||
SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
|
||||
SiteHSTSState siteState(0, SecurityPropertyKnockout, false);
|
||||
nsAutoCString stateString;
|
||||
siteState.ToString(stateString);
|
||||
nsAutoCString storageKey;
|
||||
SetStorageKey(storageKey, hostname, aType);
|
||||
rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
|
||||
nsresult rv;
|
||||
if (aIsPreload) {
|
||||
rv = mPreloadStateStorage->Put(storageKey, stateString,
|
||||
mozilla::DataStorage_Persistent);
|
||||
} else {
|
||||
rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
} else {
|
||||
SSSLOG(("SSS: removing entry for %s", hostname.get()));
|
||||
nsAutoCString storageKey;
|
||||
SetStorageKey(storageKey, hostname, aType);
|
||||
mSiteStateStorage->Remove(storageKey, storageType);
|
||||
SSSLOG(("SSS: removing entry for %s", aHost.get()));
|
||||
if (aIsPreload) {
|
||||
mPreloadStateStorage->Remove(storageKey, mozilla::DataStorage_Persistent);
|
||||
} else {
|
||||
mSiteStateStorage->Remove(storageKey, storageType);
|
||||
}
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::RemoveState(uint32_t aType, nsIURI* aURI,
|
||||
uint32_t aFlags)
|
||||
{
|
||||
nsAutoCString hostname;
|
||||
GetHost(aURI, hostname);
|
||||
return RemoveStateInternal(aType, hostname, aFlags, false);
|
||||
}
|
||||
|
||||
static bool
|
||||
HostIsIPAddress(const char *hostname)
|
||||
{
|
||||
@@ -885,9 +914,13 @@ nsSiteSecurityService::ProcessSTSHeader(nsIURI* aSourceURI,
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsAutoCString hostname;
|
||||
nsresult rv = GetHost(aSourceURI, hostname);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// record the successfully parsed header data.
|
||||
nsresult rv = SetHSTSState(aType, aSourceURI, maxAge, foundIncludeSubdomains,
|
||||
aFlags, SecurityPropertySet);
|
||||
rv = SetHSTSState(aType, hostname.get(), maxAge, foundIncludeSubdomains,
|
||||
aFlags, SecurityPropertySet, false);
|
||||
if (NS_FAILED(rv)) {
|
||||
SSSLOG(("SSS: failed to set STS state"));
|
||||
if (aFailureResult) {
|
||||
@@ -964,6 +997,116 @@ nsSiteSecurityService::GetPreloadListEntry(const char *aHost)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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);
|
||||
nsCString value = mSiteStateStorage->Get(storageKey, storageType);
|
||||
SiteHSTSState siteState(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;
|
||||
}
|
||||
if (siteState.mHSTSState == SecurityPropertySet) {
|
||||
*aResult = aRequireIncludeSubdomains ? siteState.mHSTSIncludeSubdomains
|
||||
: true;
|
||||
return true;
|
||||
} else if (siteState.mHSTSState == SecurityPropertyNegative) {
|
||||
*aResult = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
mozilla::DataStorage_Persistent);
|
||||
SiteHSTSState dynamicState(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,
|
||||
mozilla::DataStorage_Persistent);
|
||||
SiteHSTSState dynamicState(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,
|
||||
mozilla::DataStorage_Persistent);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const nsSTSPreload* preload = nullptr;
|
||||
|
||||
// Finally look in the static preload list.
|
||||
if (siteState.mHSTSState == SecurityPropertyUnset &&
|
||||
dynamicState.mHSTSState == SecurityPropertyUnset &&
|
||||
(preload = GetPreloadListEntry(aHost.get())) != nullptr) {
|
||||
SSSLOG(("%s is a preloaded HSTS host", aHost.get()));
|
||||
*aResult = aRequireIncludeSubdomains ? preload->mIncludeSubdomains
|
||||
: true;
|
||||
if (aCached) {
|
||||
*aCached = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost,
|
||||
uint32_t aFlags, bool* aCached,
|
||||
@@ -1018,55 +1161,12 @@ nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
const nsSTSPreload *preload = nullptr;
|
||||
|
||||
// First check the exact host. This involves first checking for an entry in
|
||||
// site security storage. If that entry exists, we don't want to check
|
||||
// in the preload list. 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;
|
||||
SetStorageKey(storageKey, host, aType);
|
||||
nsCString value = mSiteStateStorage->Get(storageKey, storageType);
|
||||
SiteHSTSState siteState(value);
|
||||
if (siteState.mHSTSState != SecurityPropertyUnset) {
|
||||
SSSLOG(("Found entry for %s", host.get()));
|
||||
bool expired = siteState.IsExpired(aType);
|
||||
if (!expired) {
|
||||
if (aCached) {
|
||||
*aCached = true;
|
||||
}
|
||||
if (siteState.mHSTSState == SecurityPropertySet) {
|
||||
*aResult = true;
|
||||
return NS_OK;
|
||||
} else if (siteState.mHSTSState == SecurityPropertyNegative) {
|
||||
*aResult = false;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// If the entry is expired and not in the preload list, we can remove it.
|
||||
if (expired && !GetPreloadListEntry(host.get())) {
|
||||
mSiteStateStorage->Remove(storageKey, storageType);
|
||||
}
|
||||
}
|
||||
// Finally look in the preloaded list. This is the exact host,
|
||||
// so if an entry exists at all, this host is HSTS.
|
||||
else if (GetPreloadListEntry(host.get())) {
|
||||
SSSLOG(("%s is a preloaded STS host", host.get()));
|
||||
*aResult = true;
|
||||
if (aCached) {
|
||||
*aCached = true;
|
||||
}
|
||||
// First check the exact host.
|
||||
if (HostHasHSTSEntry(host, false, aFlags, aResult, aCached)) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
|
||||
const char *subdomain;
|
||||
|
||||
@@ -1082,49 +1182,13 @@ nsSiteSecurityService::IsSecureHost(uint32_t aType, const char* aHost,
|
||||
break;
|
||||
}
|
||||
|
||||
// Do the same thing as with the exact host, except now we're looking at
|
||||
// ancestor domains of the original host. So, we have to look at the
|
||||
// include subdomains flag (although we still have to check for a
|
||||
// SecurityPropertySet flag first to check that this is a secure host and
|
||||
// not a knockout entry - and again, if it is a knockout entry, we stop
|
||||
// looking for data on it and skip to the next higher up ancestor domain).
|
||||
nsCString subdomainString(subdomain);
|
||||
nsAutoCString storageKey;
|
||||
SetStorageKey(storageKey, subdomainString, aType);
|
||||
value = mSiteStateStorage->Get(storageKey, storageType);
|
||||
SiteHSTSState siteState(value);
|
||||
if (siteState.mHSTSState != SecurityPropertyUnset) {
|
||||
SSSLOG(("Found entry for %s", subdomain));
|
||||
bool expired = siteState.IsExpired(aType);
|
||||
if (!expired) {
|
||||
if (aCached) {
|
||||
*aCached = true;
|
||||
}
|
||||
if (siteState.mHSTSState == SecurityPropertySet) {
|
||||
*aResult = siteState.mHSTSIncludeSubdomains;
|
||||
break;
|
||||
} else if (siteState.mHSTSState == SecurityPropertyNegative) {
|
||||
*aResult = false;
|
||||
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 the entry is expired and not in the preload list, we can remove it.
|
||||
if (expired && !GetPreloadListEntry(subdomain)) {
|
||||
mSiteStateStorage->Remove(storageKey, storageType);
|
||||
}
|
||||
}
|
||||
// This is an ancestor, so if we get a match, we have to check if the
|
||||
// preloaded entry includes subdomains.
|
||||
else if ((preload = GetPreloadListEntry(subdomain)) != nullptr) {
|
||||
if (preload->mIncludeSubdomains) {
|
||||
SSSLOG(("%s is a preloaded STS host", subdomain));
|
||||
*aResult = true;
|
||||
if (aCached) {
|
||||
*aCached = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (HostHasHSTSEntry(subdomainString, true, aFlags, aResult, aCached)) {
|
||||
break;
|
||||
}
|
||||
|
||||
SSSLOG(("no HSTS data for %s found, walking up domain", subdomain));
|
||||
@@ -1229,7 +1293,7 @@ nsSiteSecurityService::SetKeyPins(const char* aHost, bool aIncludeSubdomains,
|
||||
NS_ENSURE_ARG_POINTER(aResult);
|
||||
NS_ENSURE_ARG_POINTER(aSha256Pins);
|
||||
|
||||
SSSLOG(("Top of SetPins"));
|
||||
SSSLOG(("Top of SetKeyPins"));
|
||||
|
||||
nsTArray<nsCString> sha256keys;
|
||||
for (unsigned int i = 0; i < aPinCount; i++) {
|
||||
@@ -1247,6 +1311,27 @@ nsSiteSecurityService::SetKeyPins(const char* aHost, bool aIncludeSubdomains,
|
||||
return SetHPKPState(host.get(), dynamicEntry, 0, aIsPreload);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::SetHSTSPreload(const char* aHost,
|
||||
bool aIncludeSubdomains,
|
||||
int64_t aExpires,
|
||||
/*out*/ bool* aResult)
|
||||
{
|
||||
// Child processes are not allowed direct access to this.
|
||||
if (!XRE_IsParentProcess()) {
|
||||
MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::SetHSTSPreload");
|
||||
}
|
||||
|
||||
NS_ENSURE_ARG_POINTER(aHost);
|
||||
NS_ENSURE_ARG_POINTER(aResult);
|
||||
|
||||
SSSLOG(("Top of SetHSTSPreload"));
|
||||
|
||||
nsAutoCString host(PublicKeyPinningService::CanonicalizeHostname(aHost));
|
||||
return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, host.get(), aExpires,
|
||||
aIncludeSubdomains, 0, SecurityPropertySet, true);
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsSiteSecurityService::SetHPKPState(const char* aHost, SiteHPKPState& entry,
|
||||
uint32_t aFlags, bool aIsPreload)
|
||||
@@ -1264,7 +1349,8 @@ nsSiteSecurityService::SetHPKPState(const char* aHost, SiteHPKPState& entry,
|
||||
|
||||
nsresult rv;
|
||||
if (aIsPreload) {
|
||||
rv = mPreloadStateStorage->Put(storageKey, stateString, storageType);
|
||||
rv = mPreloadStateStorage->Put(storageKey, stateString,
|
||||
mozilla::DataStorage_Persistent);
|
||||
} else {
|
||||
rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user