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:
Jonathan Hao 2017-01-12 14:58:04 +08:00
Родитель fe2f735f6d
Коммит 05723f22a1
5 изменённых файлов: 410 добавлений и 59 удалений

Просмотреть файл

@ -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]