Bug 1321780 - Modify SiteSecurityService to allow dynamic STS preloads rr=?keeler r=keeler

MozReview-Commit-ID: 2a75179pIH4
This commit is contained in:
Mark Goodwin
2016-12-20 07:16:41 +00:00
parent a118ef712f
commit 1060ddf643
5 changed files with 298 additions and 122 deletions

View File

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