--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -6,30 +6,35 @@
#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/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"
#include "nsIURI.h"
#include "nsIX509Cert.h"
#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"
// A note about the preload list:
// When a site specifically disables HSTS by sending a header with
// 'max-age: 0', we keep a "knockout" value that means "we have no information
// regarding the HSTS state of this host" (any ancestor of "this host" can still
@@ -40,20 +45,27 @@
using namespace mozilla;
using namespace mozilla::psm;
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)
{
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,
&hstsIncludeSubdomains);
@@ -69,38 +81,74 @@ SiteHSTSState::SiteHSTSState(nsCString&
} else {
SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
mHSTSExpireTime = 0;
mHSTSState = SecurityPropertyUnset;
mHSTSIncludeSubdomains = false;
}
}
-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)
{
}
void
SiteHSTSState::ToString(nsCString& aString)
{
aString.Truncate();
aString.AppendInt(mHSTSExpireTime);
aString.Append(',');
aString.AppendInt(mHSTSState);
aString.Append(',');
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;
nsresult rv = mozilla::Base64Decode(encodedString, binaryValue);
if (NS_FAILED(rv)) {
return false;
}
if (binaryValue.Length() != SHA256_LENGTH) {
@@ -111,18 +159,20 @@ stringIsBase64EncodingOf256bitValue(nsCS
SiteHPKPState::SiteHPKPState()
: mExpireTime(0)
, mState(SecurityPropertyUnset)
, mIncludeSubdomains(false)
{
}
-SiteHPKPState::SiteHPKPState(nsCString& aStateString)
- : mExpireTime(0)
+SiteHPKPState::SiteHPKPState(const nsCString& aHost,
+ const nsCString& aStateString)
+ : mHostname(aHost)
+ , 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];
memset(mergedHPKPins, 0, MaxMergedHPKPPinSize);
@@ -172,42 +222,94 @@ SiteHPKPState::SiteHPKPState(nsCString&
mState = SecurityPropertyUnset;
mIncludeSubdomains = false;
if (!mSHA256keys.IsEmpty()) {
mSHA256keys.Clear();
}
}
}
-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)
{
aString.Truncate();
aString.AppendInt(mExpireTime);
aString.Append(',');
aString.AppendInt(mState);
aString.Append(',');
aString.AppendInt(static_cast<uint32_t>(mIncludeSubdomains));
aString.Append(',');
for (unsigned int i = 0; i < mSHA256keys.Length(); i++) {
aString.Append(mSHA256keys[i]);
}
}
+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;
nsSiteSecurityService::nsSiteSecurityService()
: mMaxMaxAge(kSixtyDaysInSeconds)
, mUsePreloadList(true)
, mPreloadListTimeOffset(0)
@@ -294,20 +396,20 @@ nsSiteSecurityService::GetHost(nsIURI* a
}
static void
SetStorageKey(nsAutoCString& storageKey, const nsACString& hostname, uint32_t aType)
{
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");
}
}
// Expire times are in millis. Since Headers max-age is in seconds, and
// PR_Now() is in micros, normalize the units at milliseconds.
@@ -333,19 +435,20 @@ nsSiteSecurityService::SetHSTSState(uint
return RemoveStateInternal(aType, hostname, flags, aIsPreload);
}
MOZ_ASSERT((aHSTSState == SecurityPropertySet ||
aHSTSState == SecurityPropertyNegative),
"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
? mozilla::DataStorage_Private
: mozilla::DataStorage_Persistent;
nsAutoCString storageKey;
SetStorageKey(storageKey, hostname, aType);
nsresult rv;
@@ -393,23 +496,24 @@ nsSiteSecurityService::RemoveStateIntern
? 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);
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,
mozilla::DataStorage_Persistent);
} else {
rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
}
NS_ENSURE_SUCCESS(rv, rv);
@@ -847,22 +951,23 @@ nsSiteSecurityService::ProcessPKPHeader(
SSSLOG(("SSS: Pins provided by %s are invalid no backupPin\n", host.get()));
if (aFailureResult) {
*aFailureResult = nsISiteSecurityService::ERROR_NO_BACKUP_PIN;
}
return NS_ERROR_FAILURE;
}
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) {
*aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
}
return rv;
}
@@ -1023,68 +1128,68 @@ nsSiteSecurityService::HostHasHSTSEntry(
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) {
+ 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;
}
}
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) {
+ 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())) {
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) {
+ 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;
}
} 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,
@@ -1092,18 +1197,18 @@ nsSiteSecurityService::HostHasHSTSEntry(
}
}
return false;
}
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
: true;
if (aCached) {
*aCached = true;
}
return true;
@@ -1259,36 +1364,36 @@ nsSiteSecurityService::GetKeyPinsForHost
nsAutoCString storageKey;
SetStorageKey(storageKey, host, nsISiteSecurityService::HEADER_HPKP);
SSSLOG(("storagekey '%s'\n", storageKey.get()));
mozilla::DataStorageType storageType = mozilla::DataStorage_Persistent;
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;
} else {
foundEntry = privateEntry;
}
}
- pinArray = foundEntry.mSHA256keys;
- *aIncludeSubdomains = foundEntry.mIncludeSubdomains;
+ pinArray = foundEntry->mSHA256keys;
+ *aIncludeSubdomains = foundEntry->mIncludeSubdomains;
*afound = true;
return NS_OK;
}
NS_IMETHODIMP
nsSiteSecurityService::SetKeyPins(const nsACString& aHost,
bool aIncludeSubdomains,
int64_t aExpires, uint32_t aPinCount,
@@ -1311,23 +1416,24 @@ nsSiteSecurityService::SetKeyPins(const
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);
}
- 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
nsSiteSecurityService::SetHSTSPreload(const nsACString& aHost,
bool aIncludeSubdomains,
int64_t aExpires,
/*out*/ bool* aResult)
{
@@ -1369,16 +1475,65 @@ nsSiteSecurityService::SetHPKPState(cons
mozilla::DataStorage_Persistent);
} else {
rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
}
NS_ENSURE_SUCCESS(rv, rv);
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
//------------------------------------------------------------
NS_IMETHODIMP
nsSiteSecurityService::Observe(nsISupports* /*subject*/, const char* topic,
const char16_t* /*data*/)
{
--- a/security/manager/ssl/nsSiteSecurityService.h
+++ b/security/manager/ssl/nsSiteSecurityService.h
@@ -1,16 +1,17 @@
/* 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/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"
@@ -27,71 +28,87 @@ class nsISSLStatus;
* a security property can be in.
* SecurityPropertySet and SecurityPropertyUnset correspond to indicating
* a site has or does not have the security property in question, respectively.
* SecurityPropertyKnockout indicates a value on a preloaded list is being
* overridden, and the associated site does not have the security property
* 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:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISITEHPKPSTATE
+ NS_DECL_NSISITESECURITYSTATE
+
SiteHPKPState();
- explicit SiteHPKPState(nsCString& aStateString);
- SiteHPKPState(PRTime aExpireTime, SecurityPropertyState aState,
- bool aIncludeSubdomains, nsTArray<nsCString>& SHA256keys);
+ 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;
nsTArray<nsCString> mSHA256keys;
bool IsExpired(mozilla::pkix::Time aTime)
{
if (aTime > mozilla::pkix::TimeFromEpochInSeconds(mExpireTime /
PR_MSEC_PER_SEC)) {
return true;
}
return false;
}
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;
bool IsExpired(uint32_t aType)
{
// If mHSTSExpireTime is 0, this entry never expires (this is the case for
// knockout entries).
@@ -103,16 +120,19 @@ public:
if (now > mHSTSExpireTime) {
return true;
}
return false;
}
void ToString(nsCString &aString);
+
+protected:
+ virtual ~SiteHSTSState() {}
};
struct nsSTSPreload;
class nsSiteSecurityService : public nsISiteSecurityService
, public nsIObserver
{
public:
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/unit/test_sss_enumerate.js
@@ -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");
+}