зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1115712 - make DataStorage for HPKP and HSTS enumerable via xpcom. r=Cykesiopka,keeler
MozReview-Commit-ID: GEOtuTAiPIX --HG-- extra : rebase_source : 88b060d57e269e238d9283ac386b9ffff9ff2764
This commit is contained in:
Родитель
fe2f735f6d
Коммит
05723f22a1
|
@ -8,6 +8,7 @@ interface nsIURI;
|
|||
interface nsIObserver;
|
||||
interface nsIHttpChannel;
|
||||
interface nsISSLStatus;
|
||||
interface nsISimpleEnumerator;
|
||||
|
||||
%{C++
|
||||
#include "nsTArrayForwardDeclare.h"
|
||||
|
@ -23,6 +24,44 @@ namespace mozilla
|
|||
[ref] native nsCStringTArrayRef(nsTArray<nsCString>);
|
||||
[ref] native mozillaPkixTime(mozilla::pkix::Time);
|
||||
|
||||
// [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;
|
||||
|
||||
/*
|
||||
* 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
|
||||
* HSTS priming.
|
||||
*/
|
||||
const short SECURITY_PROPERTY_UNSET = 0;
|
||||
const short SECURITY_PROPERTY_SET = 1;
|
||||
const short SECURITY_PROPERTY_KNOCKOUT = 2;
|
||||
const short SECURITY_PROPERTY_NEGATIVE = 3;
|
||||
};
|
||||
|
||||
// This has to be a builtinclass because it derives from a builtinclass.
|
||||
[scriptable, uuid(9ff16e40-1029-496c-95c2-bc819872b216), builtinclass]
|
||||
interface nsISiteHSTSState : nsISiteSecurityState
|
||||
{
|
||||
};
|
||||
|
||||
// This has to be a builtinclass because it derives from a builtinclass.
|
||||
[scriptable, uuid(ae395078-c7d0-474d-b147-f4aa203a9b2c), builtinclass]
|
||||
interface nsISiteHPKPState : nsISiteSecurityState
|
||||
{
|
||||
readonly attribute nsISimpleEnumerator sha256Keys;
|
||||
};
|
||||
|
||||
[scriptable, uuid(275127f8-dbd7-4681-afbf-6df0c6587a01)]
|
||||
interface nsISiteSecurityService : nsISupports
|
||||
{
|
||||
|
@ -210,6 +249,17 @@ interface nsISiteSecurityService : nsISupports
|
|||
* @param aMaxAge lifetime (in seconds) of this negative cache
|
||||
*/
|
||||
[noscript] void cacheNegativeHSTSResult(in nsIURI aURI, in unsigned long long aMaxAge);
|
||||
|
||||
/**
|
||||
* 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).
|
||||
*
|
||||
* @param aType the type of security state in question.
|
||||
*/
|
||||
nsISimpleEnumerator enumerate(in uint32_t aType);
|
||||
};
|
||||
|
||||
%{C++
|
||||
|
|
|
@ -11,9 +11,12 @@
|
|||
#include "base64.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
#include "mozilla/Base64.h"
|
||||
#include "mozilla/dom/PContent.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 "nsISocketProvider.h"
|
||||
|
@ -22,9 +25,11 @@
|
|||
#include "nsNSSComponent.h"
|
||||
#include "nsNetUtil.h"
|
||||
#include "nsPromiseFlatString.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "nsSecurityHeaderParser.h"
|
||||
#include "nsThreadUtils.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include "nsVariant.h"
|
||||
#include "plstr.h"
|
||||
#include "prnetdb.h"
|
||||
#include "prprf.h"
|
||||
|
@ -45,10 +50,17 @@ static LazyLogModule gSSSLog("nsSSService");
|
|||
|
||||
#define SSSLOG(args) MOZ_LOG(gSSSLog, mozilla::LogLevel::Debug, args)
|
||||
|
||||
const char kHSTSKeySuffix[] = ":HSTS";
|
||||
const char kHPKPKeySuffix[] = ":HPKP";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
SiteHSTSState::SiteHSTSState(nsCString& aStateString)
|
||||
: mHSTSExpireTime(0)
|
||||
NS_IMPL_ISUPPORTS(SiteHSTSState, nsISiteSecurityState, nsISiteHSTSState)
|
||||
|
||||
SiteHSTSState::SiteHSTSState(const nsCString& aHost,
|
||||
const nsCString& aStateString)
|
||||
: mHostname(aHost)
|
||||
, mHSTSExpireTime(0)
|
||||
, mHSTSState(SecurityPropertyUnset)
|
||||
, mHSTSIncludeSubdomains(false)
|
||||
{
|
||||
|
@ -74,11 +86,13 @@ SiteHSTSState::SiteHSTSState(nsCString& aStateString)
|
|||
}
|
||||
}
|
||||
|
||||
SiteHSTSState::SiteHSTSState(PRTime aHSTSExpireTime,
|
||||
SiteHSTSState::SiteHSTSState(const nsCString& aHost,
|
||||
PRTime aHSTSExpireTime,
|
||||
SecurityPropertyState aHSTSState,
|
||||
bool aHSTSIncludeSubdomains)
|
||||
|
||||
: mHSTSExpireTime(aHSTSExpireTime)
|
||||
: mHostname(aHost)
|
||||
, mHSTSExpireTime(aHSTSExpireTime)
|
||||
, mHSTSState(aHSTSState)
|
||||
, mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
|
||||
{
|
||||
|
@ -95,7 +109,41 @@ SiteHSTSState::ToString(nsCString& aString)
|
|||
aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHSTSState::GetHostname(nsACString& aHostname)
|
||||
{
|
||||
aHostname = mHostname;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHSTSState::GetExpireTime(int64_t* aExpireTime)
|
||||
{
|
||||
NS_ENSURE_ARG(aExpireTime);
|
||||
*aExpireTime = mHSTSExpireTime;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHSTSState::GetSecurityPropertyState(int16_t* aSecurityPropertyState)
|
||||
{
|
||||
NS_ENSURE_ARG(aSecurityPropertyState);
|
||||
*aSecurityPropertyState = mHSTSState;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHSTSState::GetIncludeSubdomains(bool* aIncludeSubdomains)
|
||||
{
|
||||
NS_ENSURE_ARG(aIncludeSubdomains);
|
||||
*aIncludeSubdomains = mHSTSIncludeSubdomains;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
NS_IMPL_ISUPPORTS(SiteHPKPState, nsISiteSecurityState, nsISiteHPKPState)
|
||||
|
||||
static bool
|
||||
stringIsBase64EncodingOf256bitValue(nsCString& encodedString) {
|
||||
nsAutoCString binaryValue;
|
||||
|
@ -116,8 +164,10 @@ SiteHPKPState::SiteHPKPState()
|
|||
{
|
||||
}
|
||||
|
||||
SiteHPKPState::SiteHPKPState(nsCString& aStateString)
|
||||
: mExpireTime(0)
|
||||
SiteHPKPState::SiteHPKPState(const nsCString& aHost,
|
||||
const nsCString& aStateString)
|
||||
: mHostname(aHost)
|
||||
, mExpireTime(0)
|
||||
, mState(SecurityPropertyUnset)
|
||||
, mIncludeSubdomains(false)
|
||||
{
|
||||
|
@ -177,17 +227,50 @@ SiteHPKPState::SiteHPKPState(nsCString& aStateString)
|
|||
}
|
||||
}
|
||||
|
||||
SiteHPKPState::SiteHPKPState(PRTime aExpireTime,
|
||||
SiteHPKPState::SiteHPKPState(const nsCString& aHost,
|
||||
PRTime aExpireTime,
|
||||
SecurityPropertyState aState,
|
||||
bool aIncludeSubdomains,
|
||||
nsTArray<nsCString>& aSHA256keys)
|
||||
: mExpireTime(aExpireTime)
|
||||
: mHostname(aHost)
|
||||
, mExpireTime(aExpireTime)
|
||||
, mState(aState)
|
||||
, mIncludeSubdomains(aIncludeSubdomains)
|
||||
, mSHA256keys(aSHA256keys)
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHPKPState::GetHostname(nsACString& aHostname)
|
||||
{
|
||||
aHostname = mHostname;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHPKPState::GetExpireTime(int64_t* aExpireTime)
|
||||
{
|
||||
NS_ENSURE_ARG(aExpireTime);
|
||||
*aExpireTime = mExpireTime;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHPKPState::GetSecurityPropertyState(int16_t* aSecurityPropertyState)
|
||||
{
|
||||
NS_ENSURE_ARG(aSecurityPropertyState);
|
||||
*aSecurityPropertyState = mState;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHPKPState::GetIncludeSubdomains(bool* aIncludeSubdomains)
|
||||
{
|
||||
NS_ENSURE_ARG(aIncludeSubdomains);
|
||||
*aIncludeSubdomains = mIncludeSubdomains;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
SiteHPKPState::ToString(nsCString& aString)
|
||||
{
|
||||
|
@ -203,6 +286,25 @@ SiteHPKPState::ToString(nsCString& aString)
|
|||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
SiteHPKPState::GetSha256Keys(nsISimpleEnumerator** aSha256Keys)
|
||||
{
|
||||
NS_ENSURE_ARG(aSha256Keys);
|
||||
|
||||
nsCOMArray<nsIVariant> keys;
|
||||
for (const nsCString& key : mSHA256keys) {
|
||||
nsCOMPtr<nsIWritableVariant> variant = new nsVariant();
|
||||
nsresult rv = variant->SetAsAUTF8String(key);
|
||||
if (NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
if (!keys.AppendObject(variant)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
return NS_NewArrayEnumerator(aSha256Keys, keys);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const uint64_t kSixtyDaysInSeconds = 60 * 24 * 60 * 60;
|
||||
|
@ -299,10 +401,10 @@ SetStorageKey(nsAutoCString& storageKey, const nsACString& hostname, uint32_t aT
|
|||
storageKey = hostname;
|
||||
switch (aType) {
|
||||
case nsISiteSecurityService::HEADER_HSTS:
|
||||
storageKey.AppendLiteral(":HSTS");
|
||||
storageKey.AppendASCII(kHSTSKeySuffix);
|
||||
break;
|
||||
case nsISiteSecurityService::HEADER_HPKP:
|
||||
storageKey.AppendLiteral(":HPKP");
|
||||
storageKey.AppendASCII(kHPKPKeySuffix);
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("SSS:SetStorageKey got invalid type");
|
||||
|
@ -338,9 +440,10 @@ nsSiteSecurityService::SetHSTSState(uint32_t aType,
|
|||
"HSTS State must be SecurityPropertySet or SecurityPropertyNegative");
|
||||
|
||||
int64_t expiretime = ExpireTimeFromMaxAge(maxage);
|
||||
SiteHSTSState siteState(expiretime, aHSTSState, includeSubdomains);
|
||||
RefPtr<SiteHSTSState> siteState =
|
||||
new SiteHSTSState(hostname, expiretime, aHSTSState, includeSubdomains);
|
||||
nsAutoCString stateString;
|
||||
siteState.ToString(stateString);
|
||||
siteState->ToString(stateString);
|
||||
SSSLOG(("SSS: setting state for %s", hostname.get()));
|
||||
bool isPrivate = flags & nsISocketProvider::NO_PERMANENT_STORAGE;
|
||||
mozilla::DataStorageType storageType = isPrivate
|
||||
|
@ -398,13 +501,14 @@ nsSiteSecurityService::RemoveStateInternal(uint32_t aType,
|
|||
|
||||
nsCString value = mPreloadStateStorage->Get(storageKey,
|
||||
mozilla::DataStorage_Persistent);
|
||||
SiteHSTSState dynamicState(value);
|
||||
RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
|
||||
if (GetPreloadListEntry(aHost.get()) ||
|
||||
dynamicState.mHSTSState != SecurityPropertyUnset) {
|
||||
dynamicState->mHSTSState != SecurityPropertyUnset) {
|
||||
SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
|
||||
SiteHSTSState siteState(0, SecurityPropertyKnockout, false);
|
||||
RefPtr<SiteHSTSState> siteState =
|
||||
new SiteHSTSState(aHost, 0, SecurityPropertyKnockout, false);
|
||||
nsAutoCString stateString;
|
||||
siteState.ToString(stateString);
|
||||
siteState->ToString(stateString);
|
||||
nsresult rv;
|
||||
if (aIsPreload) {
|
||||
rv = mPreloadStateStorage->Put(storageKey, stateString,
|
||||
|
@ -852,12 +956,13 @@ nsSiteSecurityService::ProcessPKPHeader(nsIURI* aSourceURI,
|
|||
}
|
||||
|
||||
int64_t expireTime = ExpireTimeFromMaxAge(maxAge);
|
||||
SiteHPKPState dynamicEntry(expireTime, SecurityPropertySet,
|
||||
foundIncludeSubdomains, sha256keys);
|
||||
RefPtr<SiteHPKPState> dynamicEntry =
|
||||
new SiteHPKPState(host, expireTime, SecurityPropertySet,
|
||||
foundIncludeSubdomains, sha256keys);
|
||||
SSSLOG(("SSS: about to set pins for %s, expires=%ld now=%ld maxAge=%lu\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);
|
||||
if (NS_FAILED(rv)) {
|
||||
SSSLOG(("SSS: failed to set pins for %s\n", host.get()));
|
||||
if (aFailureResult) {
|
||||
|
@ -1028,20 +1133,20 @@ nsSiteSecurityService::HostHasHSTSEntry(const nsAutoCString& aHost,
|
|||
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) {
|
||||
RefPtr<SiteHSTSState> siteState = new SiteHSTSState(aHost, value);
|
||||
if (siteState->mHSTSState != SecurityPropertyUnset) {
|
||||
SSSLOG(("Found HSTS entry for %s", aHost.get()));
|
||||
bool expired = siteState.IsExpired(nsISiteSecurityService::HEADER_HSTS);
|
||||
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
|
||||
if (siteState->mHSTSState == SecurityPropertySet) {
|
||||
*aResult = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
|
||||
: true;
|
||||
return true;
|
||||
} else if (siteState.mHSTSState == SecurityPropertyNegative) {
|
||||
} else if (siteState->mHSTSState == SecurityPropertyNegative) {
|
||||
*aResult = false;
|
||||
return true;
|
||||
}
|
||||
|
@ -1054,8 +1159,8 @@ nsSiteSecurityService::HostHasHSTSEntry(const nsAutoCString& aHost,
|
|||
// First, check the dynamic preload list.
|
||||
value = mPreloadStateStorage->Get(storageKey,
|
||||
mozilla::DataStorage_Persistent);
|
||||
SiteHSTSState dynamicState(value);
|
||||
if (dynamicState.mHSTSState == SecurityPropertyUnset) {
|
||||
RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
|
||||
if (dynamicState->mHSTSState == SecurityPropertyUnset) {
|
||||
SSSLOG(("No dynamic preload - checking for static preload"));
|
||||
// Now check the static preload list.
|
||||
if (!GetPreloadListEntry(aHost.get())) {
|
||||
|
@ -1070,16 +1175,16 @@ nsSiteSecurityService::HostHasHSTSEntry(const nsAutoCString& aHost,
|
|||
// Next, look in the dynamic preload list.
|
||||
value = mPreloadStateStorage->Get(storageKey,
|
||||
mozilla::DataStorage_Persistent);
|
||||
SiteHSTSState dynamicState(value);
|
||||
if (dynamicState.mHSTSState != SecurityPropertyUnset) {
|
||||
RefPtr<SiteHSTSState> dynamicState = new SiteHSTSState(aHost, value);
|
||||
if (dynamicState->mHSTSState != SecurityPropertyUnset) {
|
||||
SSSLOG(("Found dynamic preload entry for %s", aHost.get()));
|
||||
bool expired = dynamicState.IsExpired(nsISiteSecurityService::HEADER_HSTS);
|
||||
bool expired = dynamicState->IsExpired(nsISiteSecurityService::HEADER_HSTS);
|
||||
if (!expired) {
|
||||
if (dynamicState.mHSTSState == SecurityPropertySet) {
|
||||
*aResult = aRequireIncludeSubdomains ? dynamicState.mHSTSIncludeSubdomains
|
||||
if (dynamicState->mHSTSState == SecurityPropertySet) {
|
||||
*aResult = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
|
||||
: true;
|
||||
return true;
|
||||
} else if (dynamicState.mHSTSState == SecurityPropertyNegative) {
|
||||
} else if (dynamicState->mHSTSState == SecurityPropertyNegative) {
|
||||
*aResult = false;
|
||||
return true;
|
||||
}
|
||||
|
@ -1097,8 +1202,8 @@ nsSiteSecurityService::HostHasHSTSEntry(const nsAutoCString& aHost,
|
|||
const nsSTSPreload* preload = nullptr;
|
||||
|
||||
// Finally look in the static preload list.
|
||||
if (siteState.mHSTSState == SecurityPropertyUnset &&
|
||||
dynamicState.mHSTSState == SecurityPropertyUnset &&
|
||||
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
|
||||
|
@ -1264,17 +1369,17 @@ nsSiteSecurityService::GetKeyPinsForHostname(const nsACString& aHostname,
|
|||
nsCString value = mSiteStateStorage->Get(storageKey, storageType);
|
||||
|
||||
// decode now
|
||||
SiteHPKPState foundEntry(value);
|
||||
if (entryStateNotOK(foundEntry, aEvalTime)) {
|
||||
RefPtr<SiteHPKPState> foundEntry = new SiteHPKPState(host, value);
|
||||
if (entryStateNotOK(*foundEntry, aEvalTime)) {
|
||||
// not in permanent storage, try now private
|
||||
value = mSiteStateStorage->Get(storageKey, mozilla::DataStorage_Private);
|
||||
SiteHPKPState privateEntry(value);
|
||||
if (entryStateNotOK(privateEntry, aEvalTime)) {
|
||||
RefPtr<SiteHPKPState> privateEntry = new SiteHPKPState(host, value);
|
||||
if (entryStateNotOK(*privateEntry, aEvalTime)) {
|
||||
// not in private storage, try dynamic preload
|
||||
value = mPreloadStateStorage->Get(storageKey,
|
||||
mozilla::DataStorage_Persistent);
|
||||
SiteHPKPState preloadEntry(value);
|
||||
if (entryStateNotOK(preloadEntry, aEvalTime)) {
|
||||
RefPtr<SiteHPKPState> preloadEntry = new SiteHPKPState(host, value);
|
||||
if (entryStateNotOK(*preloadEntry, aEvalTime)) {
|
||||
return NS_OK;
|
||||
}
|
||||
foundEntry = preloadEntry;
|
||||
|
@ -1282,8 +1387,8 @@ nsSiteSecurityService::GetKeyPinsForHostname(const nsACString& aHostname,
|
|||
foundEntry = privateEntry;
|
||||
}
|
||||
}
|
||||
pinArray = foundEntry.mSHA256keys;
|
||||
*aIncludeSubdomains = foundEntry.mIncludeSubdomains;
|
||||
pinArray = foundEntry->mSHA256keys;
|
||||
*aIncludeSubdomains = foundEntry->mIncludeSubdomains;
|
||||
*afound = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -1316,13 +1421,14 @@ nsSiteSecurityService::SetKeyPins(const nsACString& aHost,
|
|||
}
|
||||
sha256keys.AppendElement(pin);
|
||||
}
|
||||
SiteHPKPState dynamicEntry(aExpires, SecurityPropertySet,
|
||||
aIncludeSubdomains, sha256keys);
|
||||
// we always store data in permanent storage (ie no flags)
|
||||
const nsCString& flatHost = PromiseFlatCString(aHost);
|
||||
nsAutoCString host(
|
||||
PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
|
||||
return SetHPKPState(host.get(), dynamicEntry, 0, aIsPreload);
|
||||
RefPtr<SiteHPKPState> dynamicEntry =
|
||||
new SiteHPKPState(host, aExpires, SecurityPropertySet, aIncludeSubdomains,
|
||||
sha256keys);
|
||||
return SetHPKPState(host.get(), *dynamicEntry, 0, aIsPreload);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
|
@ -1374,6 +1480,55 @@ nsSiteSecurityService::SetHPKPState(const char* aHost, SiteHPKPState& entry,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsSiteSecurityService::Enumerate(uint32_t aType,
|
||||
nsISimpleEnumerator** aEnumerator)
|
||||
{
|
||||
NS_ENSURE_ARG(aEnumerator);
|
||||
|
||||
nsAutoCString keySuffix;
|
||||
switch (aType) {
|
||||
case nsISiteSecurityService::HEADER_HSTS:
|
||||
keySuffix.AssignASCII(kHSTSKeySuffix);
|
||||
break;
|
||||
case nsISiteSecurityService::HEADER_HPKP:
|
||||
keySuffix.AssignASCII(kHPKPKeySuffix);
|
||||
break;
|
||||
default:
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
InfallibleTArray<mozilla::dom::DataStorageItem> items;
|
||||
mSiteStateStorage->GetAll(&items);
|
||||
|
||||
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(
|
||||
StringHead(item.key(), item.key().Length() - keySuffix.Length()));
|
||||
nsCOMPtr<nsISiteSecurityState> state;
|
||||
switch(aType) {
|
||||
case nsISiteSecurityService::HEADER_HSTS:
|
||||
state = new SiteHSTSState(hostname, item.value());
|
||||
break;
|
||||
case nsISiteSecurityService::HEADER_HPKP:
|
||||
state = new SiteHPKPState(hostname, item.value());
|
||||
break;
|
||||
default:
|
||||
MOZ_ASSERT_UNREACHABLE("SSS:Enumerate got invalid type");
|
||||
}
|
||||
|
||||
states.AppendObject(state);
|
||||
}
|
||||
|
||||
NS_NewArrayEnumerator(aEnumerator, states);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------
|
||||
// nsSiteSecurityService::nsIObserver
|
||||
//------------------------------------------------------------
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#define __nsSiteSecurityService_h__
|
||||
|
||||
#include "mozilla/DataStorage.h"
|
||||
#include "mozilla/RefPtr.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIObserver.h"
|
||||
#include "nsISiteSecurityService.h"
|
||||
|
@ -32,29 +33,36 @@ class nsISSLStatus;
|
|||
* in question.
|
||||
*/
|
||||
enum SecurityPropertyState {
|
||||
SecurityPropertyUnset = 0,
|
||||
SecurityPropertySet = 1,
|
||||
SecurityPropertyKnockout = 2,
|
||||
SecurityPropertyNegative = 3,
|
||||
SecurityPropertyUnset = nsISiteSecurityState::SECURITY_PROPERTY_UNSET,
|
||||
SecurityPropertySet = nsISiteSecurityState::SECURITY_PROPERTY_SET,
|
||||
SecurityPropertyKnockout = nsISiteSecurityState::SECURITY_PROPERTY_KNOCKOUT,
|
||||
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)
|
||||
* - 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
|
||||
class SiteHPKPState : public nsISiteHPKPState
|
||||
{
|
||||
public:
|
||||
SiteHPKPState();
|
||||
explicit SiteHPKPState(nsCString& aStateString);
|
||||
SiteHPKPState(PRTime aExpireTime, SecurityPropertyState aState,
|
||||
bool aIncludeSubdomains, nsTArray<nsCString>& SHA256keys);
|
||||
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);
|
||||
|
||||
nsCString mHostname;
|
||||
PRTime mExpireTime;
|
||||
SecurityPropertyState mState;
|
||||
bool mIncludeSubdomains;
|
||||
|
@ -70,23 +78,32 @@ public:
|
|||
}
|
||||
|
||||
void ToString(nsCString& aString);
|
||||
|
||||
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)
|
||||
* - Expiry time (PRTime (aka int64_t) in milliseconds)
|
||||
* - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
|
||||
* - An include subdomains flag (bool, default false)
|
||||
*/
|
||||
class SiteHSTSState
|
||||
class SiteHSTSState : public nsISiteHSTSState
|
||||
{
|
||||
public:
|
||||
explicit SiteHSTSState(nsCString& aStateString);
|
||||
SiteHSTSState(PRTime aHSTSExpireTime, SecurityPropertyState aHSTSState,
|
||||
bool aHSTSIncludeSubdomains);
|
||||
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);
|
||||
|
||||
nsCString mHostname;
|
||||
PRTime mHSTSExpireTime;
|
||||
SecurityPropertyState mHSTSState;
|
||||
bool mHSTSIncludeSubdomains;
|
||||
|
@ -108,6 +125,9 @@ public:
|
|||
}
|
||||
|
||||
void ToString(nsCString &aString);
|
||||
|
||||
protected:
|
||||
virtual ~SiteHSTSState() {}
|
||||
};
|
||||
|
||||
struct nsSTSPreload;
|
||||
|
|
|
@ -0,0 +1,125 @@
|
|||
/* 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";
|
||||
|
||||
// This had better not be larger than the maximum maxAge for HPKP.
|
||||
const NON_ISSUED_KEY_HASH = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
|
||||
const PINNING_ROOT_KEY_HASH = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=";
|
||||
const KEY_HASHES = [ NON_ISSUED_KEY_HASH, PINNING_ROOT_KEY_HASH ];
|
||||
const SECS_IN_A_WEEK = 7 * 24 * 60 * 60 * 1000;
|
||||
const TESTCASES = [
|
||||
{
|
||||
hostname: "a.pinning2.example.com",
|
||||
includeSubdomains: true,
|
||||
expireTime: Date.now() + 12 * SECS_IN_A_WEEK * 1000,
|
||||
},
|
||||
{
|
||||
hostname: "b.pinning2.example.com",
|
||||
includeSubdomains: false,
|
||||
expireTime: Date.now() + 13 * SECS_IN_A_WEEK * 1000,
|
||||
},
|
||||
].sort((a, b) => a.expireTime - b.expireTime);
|
||||
|
||||
do_register_cleanup(() => {
|
||||
Services.prefs.clearUserPref(
|
||||
"security.cert_pinning.process_headers_from_non_builtin_roots");
|
||||
Services.prefs.clearUserPref("security.cert_pinning.max_max_age_seconds");
|
||||
});
|
||||
|
||||
do_get_profile();
|
||||
|
||||
Services.prefs.setBoolPref(
|
||||
"security.cert_pinning.process_headers_from_non_builtin_roots", true);
|
||||
Services.prefs.setIntPref("security.cert_pinning.max_max_age_seconds",
|
||||
20 * SECS_IN_A_WEEK);
|
||||
|
||||
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);
|
||||
|
||||
function insertEntries() {
|
||||
for (let testcase of TESTCASES) {
|
||||
let uri = Services.io.newURI("https://" + testcase.hostname);
|
||||
let sslStatus = new FakeSSLStatus(constructCertFromFile(
|
||||
`test_pinning_dynamic/${testcase.hostname}-pinningroot.pem`));
|
||||
// MaxAge is in seconds.
|
||||
let maxAge = Math.round((testcase.expireTime - Date.now()) / 1000);
|
||||
let header = `max-age=${maxAge}`;
|
||||
if (testcase.includeSubdomains) {
|
||||
header += "; includeSubdomains";
|
||||
}
|
||||
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, header,
|
||||
sslStatus, 0);
|
||||
for (let key of KEY_HASHES) {
|
||||
header += `; pin-sha256="${key}"`;
|
||||
}
|
||||
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri, header,
|
||||
sslStatus, 0);
|
||||
}
|
||||
}
|
||||
|
||||
function getEntries(type) {
|
||||
let entryEnumerator = sss.enumerate(type);
|
||||
let entries = [];
|
||||
while (entryEnumerator.hasMoreElements()) {
|
||||
let entry = entryEnumerator.getNext();
|
||||
entries.push(entry.QueryInterface(Ci.nsISiteSecurityState));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
function checkSiteSecurityStateAttrs(entries) {
|
||||
entries.sort((a, b) => a.expireTime - b.expireTime);
|
||||
equal(entries.length, TESTCASES.length,
|
||||
"Should get correct number of entries");
|
||||
for (let i = 0; i < TESTCASES.length; i++) {
|
||||
equal(entries[i].hostname, TESTCASES[i].hostname, "Hostnames should match");
|
||||
equal(entries[i].securityPropertyState,
|
||||
Ci.nsISiteSecurityState.SECURITY_PROPERTY_SET,
|
||||
"Entries should have security property set");
|
||||
equal(entries[i].includeSubdomains, TESTCASES[i].includeSubdomains,
|
||||
"IncludeSubdomains should match");
|
||||
// There's a delay from our "now" and the "now" that the implementation uses.
|
||||
less(Math.abs(entries[i].expireTime - TESTCASES[i].expireTime), 60000,
|
||||
"ExpireTime should be within 60-second error");
|
||||
}
|
||||
}
|
||||
|
||||
function checkSha256Keys(hpkpEntries) {
|
||||
for (let hpkpEntry of hpkpEntries) {
|
||||
let enumerator = hpkpEntry.QueryInterface(Ci.nsISiteHPKPState).sha256Keys;
|
||||
let keys = [];
|
||||
while (enumerator.hasMoreElements()) {
|
||||
keys.push(enumerator.getNext().QueryInterface(Ci.nsIVariant));
|
||||
}
|
||||
equal(keys.length, KEY_HASHES.length, "Should get correct number of keys");
|
||||
keys.sort();
|
||||
for (let i = 0; i < KEY_HASHES.length; i++) {
|
||||
equal(keys[i], KEY_HASHES[i], "Should get correct keys");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
sss.clearAll();
|
||||
|
||||
insertEntries();
|
||||
|
||||
let hstsEntries = getEntries(Ci.nsISiteSecurityService.HEADER_HSTS);
|
||||
let hpkpEntries = getEntries(Ci.nsISiteSecurityService.HEADER_HPKP);
|
||||
|
||||
checkSiteSecurityStateAttrs(hstsEntries);
|
||||
checkSiteSecurityStateAttrs(hpkpEntries);
|
||||
|
||||
checkSha256Keys(hpkpEntries);
|
||||
|
||||
sss.clearAll();
|
||||
hstsEntries = getEntries(Ci.nsISiteSecurityService.HEADER_HSTS);
|
||||
hpkpEntries = getEntries(Ci.nsISiteSecurityService.HEADER_HPKP);
|
||||
|
||||
equal(hstsEntries.length, 0, "Should clear all HSTS entries");
|
||||
equal(hpkpEntries.length, 0, "Should clear all HPKP entries");
|
||||
}
|
|
@ -124,6 +124,7 @@ run-sequentially = hardcoded ports
|
|||
[test_signed_apps-marketplace.js]
|
||||
[test_signed_dir.js]
|
||||
tags = addons psm
|
||||
[test_sss_enumerate.js]
|
||||
[test_sss_eviction.js]
|
||||
[test_sss_readstate.js]
|
||||
[test_sss_readstate_child.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче