--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5026,20 +5026,21 @@ nsDocShell::DisplayLoadError(nsresult aE
UsePrivateBrowsing() ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
bool isStsHost = false;
bool isPinnedHost = false;
if (XRE_IsParentProcess()) {
nsCOMPtr<nsISiteSecurityService> sss =
do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI,
- flags, mOriginAttributes, nullptr, &isStsHost);
+ flags, mOriginAttributes, nullptr, nullptr,
+ &isStsHost);
NS_ENSURE_SUCCESS(rv, rv);
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP, aURI,
- flags, mOriginAttributes, nullptr,
+ flags, mOriginAttributes, nullptr, nullptr,
&isPinnedHost);
NS_ENSURE_SUCCESS(rv, rv);
} else {
mozilla::dom::ContentChild* cc =
mozilla::dom::ContentChild::GetSingleton();
mozilla::ipc::URIParams uri;
SerializeURI(aURI, uri);
cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, flags,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -3555,17 +3555,17 @@ ContentParent::RecvIsSecureURI(const uin
if (!sss) {
return IPC_FAIL_NO_REASON(this);
}
nsCOMPtr<nsIURI> ourURI = DeserializeURI(aURI);
if (!ourURI) {
return IPC_FAIL_NO_REASON(this);
}
nsresult rv = sss->IsSecureURI(aType, ourURI, aFlags, aOriginAttributes, nullptr,
- aIsSecureURI);
+ nullptr, aIsSecureURI);
if (NS_FAILED(rv)) {
return IPC_FAIL_NO_REASON(this);
}
return IPC_OK();
}
mozilla::ipc::IPCResult
ContentParent::RecvAccumulateMixedContentHSTS(const URIParams& aURI, const bool& aActive, const bool& aHSTSPriming,
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -919,17 +919,17 @@ nsMixedContentBlocker::ShouldLoad(bool a
bool doHSTSPriming = false;
if (IsEligibleForHSTSPriming(aContentLocation)) {
bool hsts = false;
bool cached = false;
nsCOMPtr<nsISiteSecurityService> sss =
do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aContentLocation,
- 0, originAttributes, &cached, &hsts);
+ 0, originAttributes, &cached, nullptr, &hsts);
NS_ENSURE_SUCCESS(rv, rv);
if (hsts && sUseHSTS) {
// assume we will be upgraded later
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_2,
(active) ? MixedContentHSTSPrimingState::eMCB_HSTS_ACTIVE_UPGRADE
: MixedContentHSTSPrimingState::eMCB_HSTS_PASSIVE_UPGRADE);
*aDecision = ACCEPT;
@@ -1145,17 +1145,17 @@ nsMixedContentBlocker::AccumulateMixedCo
bool hsts;
nsresult rv;
nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
if (NS_FAILED(rv)) {
return;
}
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, 0,
- aOriginAttributes, nullptr, &hsts);
+ aOriginAttributes, nullptr, nullptr, &hsts);
if (NS_FAILED(rv)) {
return;
}
// states: would upgrade, would prime, hsts info cached
// active, passive
//
if (!aActive) {
--- a/dom/security/test/hsts/browser_hsts-priming_allow_active.js
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_active.js
@@ -4,16 +4,17 @@
* content is allowed.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_allow_display.js
+++ b/dom/security/test/hsts/browser_hsts-priming_allow_display.js
@@ -4,16 +4,17 @@
* content is allowed.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_block_active.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active.js
@@ -4,16 +4,17 @@
* content is blocked.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_css.js
@@ -4,16 +4,17 @@
* content is blocked for css.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_active_with_redir_same.js
@@ -4,16 +4,17 @@
* content is blocked and redirect to the same host should still upgrade.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_block_display.js
+++ b/dom/security/test/hsts/browser_hsts-priming_block_display.js
@@ -4,16 +4,17 @@
* content is blocked.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
+++ b/dom/security/test/hsts/browser_hsts-priming_cache-timeout.js
@@ -27,19 +27,19 @@ add_task(function*() {
SetupPrefTestEnvironment(which, [["security.mixed_content.hsts_priming_cache_timeout", 1]]);
clear_hists(expected_telemetry);
yield execute_test("no-ssl", test_settings[which].mimetype);
let pre_promise = performance.now();
- while ((performance.now() - pre_promise) < 2000) {
+ while ((performance.now() - pre_promise) < 1000) {
yield new Promise(function (resolve) {
- setTimeout(resolve, 2000);
+ setTimeout(resolve, 1000);
});
}
// clear the fact that we saw a priming request
test_settings[which].priming = {};
yield execute_test("no-ssl", test_settings[which].mimetype);
is(test_settings[which].priming["no-ssl"], true,
--- a/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
+++ b/dom/security/test/hsts/browser_hsts-priming_hsts_after_mixed.js
@@ -4,16 +4,17 @@
* mixed-content blocks before HSTS.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 6,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,0,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
+++ b/dom/security/test/hsts/browser_hsts-priming_include-subdomains.js
@@ -9,16 +9,17 @@
* subdomains.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 2,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 4,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,2,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-duplicates.js
@@ -4,16 +4,17 @@
* than once.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 3,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 8,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,2,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
--- a/dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
+++ b/dom/security/test/hsts/browser_hsts-priming_no-non-standard-ports.js
@@ -5,16 +5,17 @@
* subdomains.
*/
'use strict';
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 1,
"MIXED_CONTENT_HSTS_PRIMING_REQUESTS": 3,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,1,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
},
}
};
--- a/dom/security/test/hsts/head.js
+++ b/dom/security/test/hsts/head.js
@@ -442,16 +442,17 @@ async function execute_test(test, mimety
}
/* Expected should look something like this:
* The numbers are the sum of all telemetry values.
var expected_telemetry = {
"histograms": {
"MIXED_CONTENT_HSTS_PRIMING_RESULT": 6,
"HSTS_PRIMING_REQUESTS": 10,
+ "HSTS_UPGRADE_SOURCE": [ 0,0,2,0,0,0,0,0,0 ]
},
"keyed-histograms": {
"HSTS_PRIMING_REQUEST_DURATION": {
"success": 1,
"failure": 2,
},
}
};
@@ -468,18 +469,34 @@ function test_telemetry(expected) {
continue;
}
if (!hs) {
ok(false, "No histogram found for key " + key);
continue;
}
- // there may have been other background requests processed
- ok(hs.counts.reduce(sum) >= expected['histograms'][key], "Histogram counts match expected, got " + hs.counts.reduce(sum) + ", expected at least " + expected['histograms'][key]);
+ if (Array.isArray(expected['histograms'][key])) {
+ var is_ok = true;
+ if (expected['histograms'][key].length != hs.counts.length) {
+ ok(false, "Histogram lengths match");
+ continue;
+ }
+
+ for (let idx in expected['histograms'][key]) {
+ is_ok = (hs.counts[idx] >= expected['histograms'][key][idx]);
+ if (!is_ok) {
+ break;
+ }
+ }
+ ok(is_ok, "Histogram counts match for " + key + " - Got " + hs.counts + ", expected " + expected['histograms'][key]);
+ } else {
+ // there may have been other background requests processed
+ ok(hs.counts.reduce(sum) >= expected['histograms'][key], "Histogram counts match expected, got " + hs.counts.reduce(sum) + ", expected at least " + expected['histograms'][key]);
+ }
}
for (let key in expected['keyed-histograms']) {
let hs = undefined;
try {
let hist = Services.telemetry.getKeyedHistogramById(key);
hs = hist.snapshot();
hist.clear();
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -2579,30 +2579,47 @@ NS_ShouldSecureUpgrade(nsIURI* aURI,
}
}
// enforce Strict-Transport-Security
nsISiteSecurityService* sss = gHttpHandler->GetSSService();
NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
bool isStsHost = false;
+ uint32_t hstsSource = 0;
uint32_t flags = aPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, flags,
- aOriginAttributes, nullptr, &isStsHost);
+ aOriginAttributes, nullptr, &hstsSource, &isStsHost);
// if the SSS check fails, it's likely because this load is on a
// malformed URI or something else in the setup is wrong, so any error
// should be reported.
NS_ENSURE_SUCCESS(rv, rv);
if (isStsHost) {
LOG(("nsHttpChannel::Connect() STS permissions found\n"));
if (aAllowSTS) {
Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3);
aShouldUpgrade = true;
+ switch (hstsSource) {
+ case nsISiteSecurityService::SOURCE_PRELOAD_LIST:
+ Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 0);
+ break;
+ case nsISiteSecurityService::SOURCE_ORGANIC_REQUEST:
+ Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 1);
+ break;
+ case nsISiteSecurityService::SOURCE_HSTS_PRIMING:
+ Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 2);
+ break;
+ case nsISiteSecurityService::SOURCE_UNKNOWN:
+ default:
+ // record this as an organic request
+ Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 1);
+ break;
+ }
return NS_OK;
} else {
Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 2);
}
} else {
Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 1);
}
} else {
--- a/netwerk/protocol/http/HSTSPrimerListener.cpp
+++ b/netwerk/protocol/http/HSTSPrimerListener.cpp
@@ -153,17 +153,17 @@ HSTSPrimingListener::CheckHSTSPrimingReq
NS_ENSURE_SUCCESS(rv, rv);
NS_ENSURE_TRUE(uri, NS_ERROR_CONTENT_BLOCKED);
OriginAttributes originAttributes;
NS_GetOriginAttributes(httpChannel, originAttributes);
bool hsts;
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0,
- originAttributes, nullptr, &hsts);
+ originAttributes, nullptr, nullptr, &hsts);
NS_ENSURE_SUCCESS(rv, rv);
if (hsts) {
// An HSTS upgrade was found
return NS_OK;
}
// There is no HSTS upgrade available
@@ -222,35 +222,35 @@ HSTSPrimingListener::StartHSTSPriming(ns
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
rv = NS_GetSecureUpgradedURI(finalChannelURI, getter_AddRefs(uri));
NS_ENSURE_SUCCESS(rv,rv);
// check the HSTS cache
bool hsts;
- bool cached;
+ bool hstsCached;
nsCOMPtr<nsISiteSecurityService> sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
OriginAttributes originAttributes;
NS_GetOriginAttributes(aRequestChannel, originAttributes);
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, 0,
- originAttributes, &cached, &hsts);
+ originAttributes, &hstsCached, nullptr, &hsts);
NS_ENSURE_SUCCESS(rv, rv);
if (hsts) {
// already saw this host and will upgrade if allowed by preferences
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_HSTS);
return aCallback->OnHSTSPrimingSucceeded(true);
}
- if (cached) {
+ if (hstsCached) {
// there is a non-expired entry in the cache that doesn't allow us to
// upgrade, so go ahead and fail early.
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_REQUESTS,
HSTSPrimingRequest::eHSTS_PRIMING_REQUEST_CACHED_NO_HSTS);
return aCallback->OnHSTSPrimingFailed(NS_ERROR_CONTENT_BLOCKED, true);
}
// Either it wasn't cached or the cached result has expired. Build a
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -1791,19 +1791,23 @@ nsHttpChannel::ProcessSingleSecurityHead
if (NS_SUCCEEDED(rv)) {
nsISiteSecurityService* sss = gHttpHandler->GetSSService();
NS_ENSURE_TRUE(sss, NS_ERROR_OUT_OF_MEMORY);
// Process header will now discard the headers itself if the channel
// wasn't secure (whereas before it had to be checked manually)
OriginAttributes originAttributes;
NS_GetOriginAttributes(this, originAttributes);
uint32_t failureResult;
+ uint32_t headerSource = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST;
+ if (mLoadInfo && mLoadInfo->GetIsHSTSPriming()) {
+ headerSource = nsISiteSecurityService::SOURCE_HSTS_PRIMING;
+ }
rv = sss->ProcessHeader(aType, mURI, securityHeader, aSSLStatus,
- aFlags, originAttributes, nullptr, nullptr,
- &failureResult);
+ aFlags, headerSource, originAttributes,
+ nullptr, nullptr, &failureResult);
if (NS_FAILED(rv)) {
nsAutoString consoleErrorCategory;
nsAutoString consoleErrorTag;
switch (aType) {
case nsISiteSecurityService::HEADER_HSTS:
GetSTSConsoleErrorTag(failureResult, consoleErrorTag);
consoleErrorCategory = NS_LITERAL_STRING("Invalid HSTS Headers");
break;
@@ -8655,18 +8659,20 @@ nsHttpChannel::OnHSTSPrimingSucceeded(bo
if (nsMixedContentBlocker::sUseHSTS) {
// redirect the channel to HTTPS if the pref
// "security.mixed_content.use_hsts" is true
LOG(("HSTS Priming succeeded, redirecting to HTTPS [this=%p]", this));
Telemetry::Accumulate(Telemetry::MIXED_CONTENT_HSTS_PRIMING_RESULT,
(aCached) ? HSTSPrimingResult::eHSTS_PRIMING_CACHED_DO_UPGRADE :
HSTSPrimingResult::eHSTS_PRIMING_SUCCEEDED);
- // we have to record this upgrade here
+ // we have to record this upgrade here since we have already
+ // been through NS_ShouldSecureUpgrade
Telemetry::Accumulate(Telemetry::HTTP_SCHEME_UPGRADE, 3);
+ Telemetry::Accumulate(Telemetry::HSTS_UPGRADE_SOURCE, 2);
mLoadInfo->SetIsHSTSPrimingUpgrade(true);
return AsyncCall(&nsHttpChannel::HandleAsyncRedirectChannelToHttps);
}
// preserve the mixed-content-before-hsts order and block if required
if (wouldBlock) {
LOG(("HSTS Priming succeeded, blocking for mixed-content [this=%p]",
this));
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -2407,17 +2407,17 @@ nsHttpHandler::SpeculativeConnectInterna
originAttributes = aPrincipal->OriginAttributesRef();
} else if (loadContext) {
loadContext->GetOriginAttributes(originAttributes);
}
nsCOMPtr<nsIURI> clone;
if (NS_SUCCEEDED(sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS,
aURI, flags, originAttributes,
- nullptr, &isStsHost)) &&
+ nullptr, nullptr, &isStsHost)) &&
isStsHost) {
if (NS_SUCCEEDED(NS_GetSecureUpgradedURI(aURI,
getter_AddRefs(clone)))) {
aURI = clone.get();
// (NOTE: We better make sure |clone| stays alive until the end
// of the function now, since our aURI arg now points to it!)
}
}
--- a/security/manager/ssl/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/SSLServerCertVerification.cpp
@@ -495,27 +495,29 @@ CertErrorRunnable::OverrideAllowedForHos
("[%p][%p] Creating new URI failed", mFdForLogging, this));
return rv;
}
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS,
uri,
mProviderFlags,
mInfoObject->GetOriginAttributes(),
nullptr,
+ nullptr,
&strictTransportSecurityEnabled);
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p][%p] checking for HSTS failed", mFdForLogging, this));
return rv;
}
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP,
uri,
mProviderFlags,
mInfoObject->GetOriginAttributes(),
nullptr,
+ nullptr,
&hasPinningInformation);
if (NS_FAILED(rv)) {
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
("[%p][%p] checking for HPKP failed", mFdForLogging, this));
return rv;
}
overrideAllowed = !strictTransportSecurityEnabled && !hasPinningInformation;
--- a/security/manager/ssl/nsISiteSecurityService.idl
+++ b/security/manager/ssl/nsISiteSecurityService.idl
@@ -87,16 +87,26 @@ interface nsISiteSecurityService : nsISu
const uint32_t ERROR_INVALID_PIN = 9;
const uint32_t ERROR_MULTIPLE_REPORT_URIS = 10;
const uint32_t ERROR_PINSET_DOES_NOT_MATCH_CHAIN = 11;
const uint32_t ERROR_NO_BACKUP_PIN = 12;
const uint32_t ERROR_COULD_NOT_SAVE_STATE = 13;
const uint32_t ERROR_ROOT_NOT_BUILT_IN = 14;
/**
+ * nsISiteSecurityService::IsSecureURI can optionally return a flag
+ * indicating the source of the HSTS cache entry, if it comes from the
+ * preload list, was seen naturally, or is a result of HSTS priming.
+ */
+ const uint32_t SOURCE_UNKNOWN = 0;
+ const uint32_t SOURCE_PRELOAD_LIST = 1;
+ const uint32_t SOURCE_ORGANIC_REQUEST = 2;
+ const uint32_t SOURCE_HSTS_PRIMING = 3;
+
+ /**
* Parses a given HTTP header and records the results internally.
* Currently two header types are supported: HSTS (aka STS) and HPKP
* The format of the HSTS header is defined by the HSTS specification:
* https://tools.ietf.org/html/rfc6797
* and allows a host to specify that future HTTP requests should be
* upgraded to HTTPS.
* The format of the HPKP header is defined by the HPKP specification:
* https://tools.ietf.org/html/rfc7469
@@ -109,43 +119,47 @@ interface nsISiteSecurityService : nsISu
* @param aSSLStatus the SSLStatus of the current channel.
* @param aFlags options for this request as defined in nsISocketProvider:
* NO_PERMANENT_STORAGE
* @param aOriginAttributes the origin attributes that isolate this origin,
* (note that this implementation does not isolate
* by userContextId because of the risk of man-in-
* the-middle attacks before trust-on-second-use
* happens).
+ * @param aSource the source of the header, whether it was from the preload
+ * list, an organic header, or HSTS priming, or unknown.
* @param aMaxAge the parsed max-age directive of the header.
* @param aIncludeSubdomains the parsed includeSubdomains directive.
* @param aFailureResult a more specific failure result if NS_ERROR_FAILURE
was returned.
* @return NS_OK if it succeeds
* NS_ERROR_FAILURE if it can't be parsed
* NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
* if there are unrecognized tokens in the header.
*/
[binaryname(ProcessHeader), noscript, must_use]
void processHeaderNative(in uint32_t aType,
in nsIURI aSourceURI,
in ACString aHeader,
in nsISSLStatus aSSLStatus,
in uint32_t aFlags,
+ in uint32_t aSource,
in const_OriginAttributesRef aOriginAttributes,
[optional] out unsigned long long aMaxAge,
[optional] out boolean aIncludeSubdomains,
[optional] out uint32_t aFailureResult);
[binaryname(ProcessHeaderScriptable), implicit_jscontext, optional_argc,
must_use]
void processHeader(in uint32_t aType,
in nsIURI aSourceURI,
in ACString aHeader,
in nsISSLStatus aSSLStatus,
in uint32_t aFlags,
+ in uint32_t aSource,
[optional] in jsval aOriginAttributes,
[optional] out unsigned long long aMaxAge,
[optional] out boolean aIncludeSubdomains,
[optional] out uint32_t aFailureResult);
/**
* Given a header type, removes state relating to that header of a host,
* including the includeSubdomains state that would affect subdomains.
@@ -180,30 +194,35 @@ interface nsISiteSecurityService : nsISu
* @param aURI the URI to query for STS state.
* @param aFlags options for this request as defined in nsISocketProvider:
* NO_PERMANENT_STORAGE
* @param aOriginAttributes the origin attributes that isolate this origin,
* (note that this implementation does not isolate
* by userContextId because of the risk of man-in-
* the-middle attacks before trust-on-second-use
* happens).
- * @param aCached true if we have cached information regarding whether or not
- * the host is HSTS, false otherwise.
+ * @param aCached true if we have cached information about this host, even
+ * if the security state is false.
+ * @param aSource the source of the HSTS entry. One of SOURCE_PRELOAD_LIST,
+ * SOURCE_ORGANIC_REQUEST, SOURCE_HSTS_PRIMING, or
+ * SOURCE_UNKNOWN. Not implemented for HPKP.
*/
[binaryname(IsSecureURI), noscript, must_use]
boolean isSecureURINative(in uint32_t aType, in nsIURI aURI,
in uint32_t aFlags,
in const_OriginAttributesRef aOriginAttributes,
- [optional] out boolean aCached);
+ [optional] out boolean aCached,
+ [optional] out uint32_t aSource);
[binaryname(IsSecureURIScriptable), implicit_jscontext, optional_argc,
must_use]
boolean isSecureURI(in uint32_t aType, in nsIURI aURI, in uint32_t aFlags,
[optional] in jsval aOriginAttributes,
- [optional] out boolean aCached);
+ [optional] out boolean aCached,
+ [optional] out uint32_t aSource);
/**
* Removes all non-preloaded security state by resetting to factory-original
* settings.
*/
[must_use]
void clearAll();
--- a/security/manager/ssl/nsSiteSecurityService.cpp
+++ b/security/manager/ssl/nsSiteSecurityService.cpp
@@ -111,16 +111,38 @@ public:
break;
default:
return false;
}
return true;
}
+ MOZ_MUST_USE bool
+ ReadSource(/*out*/ SecurityPropertySource& source)
+ {
+ uint32_t rawValue;
+ if (!ReadInteger(&rawValue)) {
+ return false;
+ }
+
+ source = static_cast<SecurityPropertySource>(rawValue);
+ switch (source) {
+ case SourceUnknown:
+ case SourcePreload:
+ case SourceOrganic:
+ case SourceHSTSPriming:
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+ }
+
// Note: Ideally, this method would be able to read SHA256 strings without
// reading all the way to EOF. Unfortunately, if a token starts with digits
// mozilla::Tokenizer will by default not consider the digits part of the
// string. This can be worked around by making mozilla::Tokenizer consider
// digit characters as "word" characters as well, but this can't be changed at
// run time, meaning parsing digits as a number will fail.
MOZ_MUST_USE bool
ReadUntilEOFAsSHA256Keys(/*out*/ nsTArray<nsCString>& keys)
@@ -148,19 +170,21 @@ public:
}
};
// Parses a state string like "1500918564034,1,1" into its constituent parts.
bool
ParseHSTSState(const nsCString& stateString,
/*out*/ PRTime& expireTime,
/*out*/ SecurityPropertyState& state,
- /*out*/ bool& includeSubdomains)
+ /*out*/ bool& includeSubdomains,
+ /*out*/ SecurityPropertySource& source)
{
SSSTokenizer tokenizer(stateString);
+ SSSLOG(("Parsing state from %s", stateString.get()));
if (!tokenizer.ReadInteger(&expireTime)) {
return false;
}
if (!tokenizer.CheckChar(',')) {
return false;
}
@@ -172,63 +196,76 @@ ParseHSTSState(const nsCString& stateStr
if (!tokenizer.CheckChar(',')) {
return false;
}
if (!tokenizer.ReadBool(includeSubdomains)) {
return false;
}
+ source = SourceUnknown;
+ if (tokenizer.CheckChar(',')) {
+ if (!tokenizer.ReadSource(source)) {
+ return false;
+ }
+ }
+
return tokenizer.CheckEOF();
}
} // namespace
SiteHSTSState::SiteHSTSState(const nsCString& aHost,
const OriginAttributes& aOriginAttributes,
const nsCString& aStateString)
: mHostname(aHost)
, mOriginAttributes(aOriginAttributes)
, mHSTSExpireTime(0)
, mHSTSState(SecurityPropertyUnset)
, mHSTSIncludeSubdomains(false)
+ , mHSTSSource(SourceUnknown)
{
bool valid = ParseHSTSState(aStateString, mHSTSExpireTime, mHSTSState,
- mHSTSIncludeSubdomains);
+ mHSTSIncludeSubdomains, mHSTSSource);
if (!valid) {
SSSLOG(("%s is not a valid SiteHSTSState", aStateString.get()));
mHSTSExpireTime = 0;
mHSTSState = SecurityPropertyUnset;
mHSTSIncludeSubdomains = false;
+ mHSTSSource = SourceUnknown;
}
}
SiteHSTSState::SiteHSTSState(const nsCString& aHost,
const OriginAttributes& aOriginAttributes,
PRTime aHSTSExpireTime,
SecurityPropertyState aHSTSState,
- bool aHSTSIncludeSubdomains)
+ bool aHSTSIncludeSubdomains,
+ SecurityPropertySource aSource)
: mHostname(aHost)
, mOriginAttributes(aOriginAttributes)
, mHSTSExpireTime(aHSTSExpireTime)
, mHSTSState(aHSTSState)
, mHSTSIncludeSubdomains(aHSTSIncludeSubdomains)
+ , mHSTSSource(aSource)
{
}
void
SiteHSTSState::ToString(nsCString& aString)
{
aString.Truncate();
aString.AppendInt(mHSTSExpireTime);
aString.Append(',');
aString.AppendInt(mHSTSState);
aString.Append(',');
aString.AppendInt(static_cast<uint32_t>(mHSTSIncludeSubdomains));
+ aString.Append(',');
+ aString.AppendInt(mHSTSSource);
}
NS_IMETHODIMP
SiteHSTSState::GetHostname(nsACString& aHostname)
{
aHostname = mHostname;
return NS_OK;
}
@@ -576,72 +613,84 @@ ExpireTimeFromMaxAge(uint64_t maxAge)
nsresult
nsSiteSecurityService::SetHSTSState(uint32_t aType,
const char* aHost,
int64_t maxage,
bool includeSubdomains,
uint32_t flags,
SecurityPropertyState aHSTSState,
- bool aIsPreload,
+ SecurityPropertySource aSource,
const OriginAttributes& aOriginAttributes)
{
nsAutoCString hostname(aHost);
+ bool isPreload = (aSource == SourcePreload);
// If max-age is zero, that's an indication to immediately remove the
// security state, so here's a shortcut.
if (!maxage) {
- return RemoveStateInternal(aType, hostname, flags, aIsPreload,
+ return RemoveStateInternal(aType, hostname, flags, isPreload,
aOriginAttributes);
}
MOZ_ASSERT((aHSTSState == SecurityPropertySet ||
aHSTSState == SecurityPropertyNegative),
"HSTS State must be SecurityPropertySet or SecurityPropertyNegative");
- if (aIsPreload && aOriginAttributes != OriginAttributes()) {
+ if (isPreload && aOriginAttributes != OriginAttributes()) {
return NS_ERROR_INVALID_ARG;
}
int64_t expiretime = ExpireTimeFromMaxAge(maxage);
RefPtr<SiteHSTSState> siteState = new SiteHSTSState(
- hostname, aOriginAttributes, expiretime, aHSTSState, includeSubdomains);
+ hostname, aOriginAttributes, expiretime, aHSTSState, includeSubdomains,
+ aSource);
nsAutoCString 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(hostname, aType, aOriginAttributes, storageKey);
nsresult rv;
- if (aIsPreload) {
+ if (isPreload) {
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()));
+ nsCString value = mSiteStateStorage->Get(storageKey, storageType);
+ RefPtr<SiteHSTSState> curSiteState =
+ new SiteHSTSState(hostname, aOriginAttributes, value);
+ if (curSiteState->mHSTSState != SecurityPropertyUnset &&
+ curSiteState->mHSTSSource != SourceUnknown) {
+ // don't override the source
+ siteState->mHSTSSource = curSiteState->mHSTSSource;
+ siteState->ToString(stateString);
+ }
rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
}
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
NS_IMETHODIMP
nsSiteSecurityService::CacheNegativeHSTSResult(
nsIURI* aSourceURI,
uint64_t aMaxAge,
const OriginAttributes& aOriginAttributes)
{
nsAutoCString hostname;
nsresult rv = GetHost(aSourceURI, hostname);
NS_ENSURE_SUCCESS(rv, rv);
+ // SecurityPropertyNegative results only come from HSTS priming
return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, hostname.get(),
- aMaxAge, false, 0, SecurityPropertyNegative, false,
- aOriginAttributes);
+ aMaxAge, false, 0, SecurityPropertyNegative,
+ SourceHSTSPriming, aOriginAttributes);
}
nsresult
nsSiteSecurityService::RemoveStateInternal(
uint32_t aType, nsIURI* aURI, uint32_t aFlags,
const OriginAttributes& aOriginAttributes)
{
nsAutoCString hostname;
@@ -680,17 +729,18 @@ nsSiteSecurityService::RemoveStateIntern
nsCString value = mPreloadStateStorage->Get(storageKey,
mozilla::DataStorage_Persistent);
RefPtr<SiteHSTSState> dynamicState =
new SiteHSTSState(aHost, aOriginAttributes, value);
if (GetPreloadListEntry(aHost.get()) ||
dynamicState->mHSTSState != SecurityPropertyUnset) {
SSSLOG(("SSS: storing knockout entry for %s", aHost.get()));
RefPtr<SiteHSTSState> siteState = new SiteHSTSState(
- aHost, aOriginAttributes, 0, SecurityPropertyKnockout, false);
+ aHost, aOriginAttributes, 0, SecurityPropertyKnockout, false,
+ SourceUnknown);
nsAutoCString stateString;
siteState->ToString(stateString);
nsresult rv;
if (aIsPreload) {
rv = mPreloadStateStorage->Put(storageKey, stateString,
mozilla::DataStorage_Persistent);
} else {
rv = mSiteStateStorage->Put(storageKey, stateString, storageType);
@@ -735,41 +785,43 @@ HostIsIPAddress(const nsCString& hostnam
NS_IMETHODIMP
nsSiteSecurityService::ProcessHeaderScriptable(
uint32_t aType,
nsIURI* aSourceURI,
const nsACString& aHeader,
nsISSLStatus* aSSLStatus,
uint32_t aFlags,
+ uint32_t aSource,
JS::HandleValue aOriginAttributes,
uint64_t* aMaxAge,
bool* aIncludeSubdomains,
uint32_t* aFailureResult,
JSContext* aCx,
uint8_t aArgc)
{
OriginAttributes originAttributes;
if (aArgc > 0) {
if (!aOriginAttributes.isObject() ||
!originAttributes.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
}
return ProcessHeader(aType, aSourceURI, aHeader, aSSLStatus, aFlags,
- originAttributes, aMaxAge, aIncludeSubdomains,
+ aSource, originAttributes, aMaxAge, aIncludeSubdomains,
aFailureResult);
}
NS_IMETHODIMP
nsSiteSecurityService::ProcessHeader(uint32_t aType,
nsIURI* aSourceURI,
const nsACString& aHeader,
nsISSLStatus* aSSLStatus,
uint32_t aFlags,
+ uint32_t aHeaderSource,
const OriginAttributes& aOriginAttributes,
uint64_t* aMaxAge,
bool* aIncludeSubdomains,
uint32_t* aFailureResult)
{
// Child processes are not allowed direct access to this.
if (!XRE_IsParentProcess()) {
MOZ_CRASH("Child process: no direct access to "
@@ -777,30 +829,41 @@ nsSiteSecurityService::ProcessHeader(uin
}
if (aFailureResult) {
*aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
}
NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
aType == nsISiteSecurityService::HEADER_HPKP,
NS_ERROR_NOT_IMPLEMENTED);
+ SecurityPropertySource source = static_cast<SecurityPropertySource>(aHeaderSource);
+ switch (source) {
+ case SourceUnknown:
+ case SourcePreload:
+ case SourceOrganic:
+ case SourceHSTSPriming:
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
NS_ENSURE_ARG(aSSLStatus);
return ProcessHeaderInternal(aType, aSourceURI, PromiseFlatCString(aHeader),
- aSSLStatus, aFlags, aOriginAttributes, aMaxAge,
- aIncludeSubdomains, aFailureResult);
+ aSSLStatus, aFlags, source, aOriginAttributes,
+ aMaxAge, aIncludeSubdomains, aFailureResult);
}
nsresult
nsSiteSecurityService::ProcessHeaderInternal(
uint32_t aType,
nsIURI* aSourceURI,
const nsCString& aHeader,
nsISSLStatus* aSSLStatus,
uint32_t aFlags,
+ SecurityPropertySource aSource,
const OriginAttributes& aOriginAttributes,
uint64_t* aMaxAge,
bool* aIncludeSubdomains,
uint32_t* aFailureResult)
{
if (aFailureResult) {
*aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
}
@@ -846,18 +909,19 @@ nsSiteSecurityService::ProcessHeaderInte
NS_ENSURE_SUCCESS(rv, rv);
if (HostIsIPAddress(host)) {
/* Don't process headers if a site is accessed by IP address. */
return NS_OK;
}
switch (aType) {
case nsISiteSecurityService::HEADER_HSTS:
- rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aOriginAttributes, aMaxAge,
- aIncludeSubdomains, aFailureResult);
+ rv = ProcessSTSHeader(aSourceURI, aHeader, aFlags, aSource,
+ aOriginAttributes, aMaxAge, aIncludeSubdomains,
+ aFailureResult);
break;
case nsISiteSecurityService::HEADER_HPKP:
rv = ProcessPKPHeader(aSourceURI, aHeader, aSSLStatus, aFlags,
aOriginAttributes, aMaxAge, aIncludeSubdomains,
aFailureResult);
break;
default:
MOZ_CRASH("unexpected header type");
@@ -1182,16 +1246,17 @@ nsSiteSecurityService::ProcessPKPHeader(
: NS_OK;
}
nsresult
nsSiteSecurityService::ProcessSTSHeader(
nsIURI* aSourceURI,
const nsCString& aHeader,
uint32_t aFlags,
+ SecurityPropertySource aSource,
const OriginAttributes& aOriginAttributes,
uint64_t* aMaxAge,
bool* aIncludeSubdomains,
uint32_t* aFailureResult)
{
if (aFailureResult) {
*aFailureResult = nsISiteSecurityService::ERROR_UNKNOWN;
}
@@ -1225,17 +1290,17 @@ nsSiteSecurityService::ProcessSTSHeader(
}
nsAutoCString hostname;
nsresult rv = GetHost(aSourceURI, hostname);
NS_ENSURE_SUCCESS(rv, rv);
// record the successfully parsed header data.
rv = SetHSTSState(aType, hostname.get(), maxAge, foundIncludeSubdomains,
- aFlags, SecurityPropertySet, false, aOriginAttributes);
+ aFlags, SecurityPropertySet, aSource, aOriginAttributes);
if (NS_FAILED(rv)) {
SSSLOG(("SSS: failed to set STS state"));
if (aFailureResult) {
*aFailureResult = nsISiteSecurityService::ERROR_COULD_NOT_SAVE_STATE;
}
return rv;
}
@@ -1251,34 +1316,36 @@ nsSiteSecurityService::ProcessSTSHeader(
? NS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA
: NS_OK;
}
NS_IMETHODIMP
nsSiteSecurityService::IsSecureURIScriptable(uint32_t aType, nsIURI* aURI,
uint32_t aFlags,
JS::HandleValue aOriginAttributes,
- bool* aCached, JSContext* aCx,
+ bool* aCached,
+ uint32_t* aSource, JSContext* aCx,
uint8_t aArgc, bool* aResult)
{
OriginAttributes originAttributes;
if (aArgc > 0) {
if (!aOriginAttributes.isObject() ||
!originAttributes.Init(aCx, aOriginAttributes)) {
return NS_ERROR_INVALID_ARG;
}
}
- return IsSecureURI(aType, aURI, aFlags, originAttributes, aCached, aResult);
+ return IsSecureURI(aType, aURI, aFlags, originAttributes, aCached, aSource, aResult);
}
NS_IMETHODIMP
nsSiteSecurityService::IsSecureURI(uint32_t aType, nsIURI* aURI,
uint32_t aFlags,
const OriginAttributes& aOriginAttributes,
- bool* aCached, bool* aResult)
+ bool* aCached,
+ uint32_t* aSource, bool* aResult)
{
// Child processes are not allowed direct access to this.
if (!XRE_IsParentProcess() && aType != nsISiteSecurityService::HEADER_HSTS) {
MOZ_CRASH("Child process: no direct access to nsISiteSecurityService::IsSecureURI for non-HSTS entries");
}
NS_ENSURE_ARG(aURI);
NS_ENSURE_ARG(aResult);
@@ -1292,18 +1359,20 @@ nsSiteSecurityService::IsSecureURI(uint3
nsresult rv = GetHost(aURI, hostname);
NS_ENSURE_SUCCESS(rv, rv);
/* An IP address never qualifies as a secure URI. */
if (HostIsIPAddress(hostname)) {
*aResult = false;
return NS_OK;
}
+ SecurityPropertySource* source = BitwiseCast<SecurityPropertySource*>(aSource);
+
return IsSecureHost(aType, hostname, aFlags, aOriginAttributes, aCached,
- aResult);
+ source, aResult);
}
int STSPreloadCompare(const void *key, const void *entry)
{
const char *keyStr = (const char *)key;
const nsSTSPreload *preloadEntry = (const nsSTSPreload *)entry;
return strcmp(keyStr, &kSTSHostTable[preloadEntry->mHostIndex]);
}
@@ -1330,18 +1399,25 @@ nsSiteSecurityService::GetPreloadListEnt
// 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,
- const OriginAttributes& aOriginAttributes, bool* aResult, bool* aCached)
+ const OriginAttributes& aOriginAttributes, bool* aResult, bool* aCached,
+ SecurityPropertySource* aSource)
{
+ if (aSource) {
+ *aSource = SourceUnknown;
+ }
+ if (aCached) {
+ *aCached = false;
+ }
// 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
@@ -1365,23 +1441,30 @@ nsSiteSecurityService::HostHasHSTSEntry(
if (siteState->mHSTSState == SecurityPropertySet) {
*aResult = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
: true;
if (aCached) {
// Only set cached if this includes subdomains
*aCached = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
: true;
}
+ if (aSource) {
+ *aSource = siteState->mHSTSSource;
+ }
return true;
} else if (siteState->mHSTSState == SecurityPropertyNegative) {
*aResult = false;
if (aCached) {
// if it's negative, it is always cached
+ SSSLOG(("Marking HSTS as as cached (SecurityPropertyNegative)"));
*aCached = true;
}
+ if (aSource) {
+ *aSource = siteState->mHSTSSource;
+ }
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.
@@ -1414,23 +1497,29 @@ nsSiteSecurityService::HostHasHSTSEntry(
if (dynamicState->mHSTSState == SecurityPropertySet) {
*aResult = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
: true;
if (aCached) {
// Only set cached if this includes subdomains
*aCached = aRequireIncludeSubdomains ? dynamicState->mHSTSIncludeSubdomains
: true;
}
+ if (aSource) {
+ *aSource = dynamicState->mHSTSSource;
+ }
return true;
} else if (dynamicState->mHSTSState == SecurityPropertyNegative) {
*aResult = false;
if (aCached) {
// if it's negative, it is always cached
*aCached = true;
}
+ if (aSource) {
+ *aSource = dynamicState->mHSTSSource;
+ }
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(preloadKey,
mozilla::DataStorage_Persistent);
@@ -1445,49 +1534,51 @@ nsSiteSecurityService::HostHasHSTSEntry(
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) {
// Only set cached if this includes subdomains
- *aCached = aRequireIncludeSubdomains ? siteState->mHSTSIncludeSubdomains
+ *aCached = aRequireIncludeSubdomains ? preload->mIncludeSubdomains
: true;
}
+ if (aSource) {
+ *aSource = SourcePreload;
+ }
return true;
}
return false;
}
nsresult
nsSiteSecurityService::IsSecureHost(uint32_t aType, const nsACString& aHost,
uint32_t aFlags,
const OriginAttributes& aOriginAttributes,
- bool* aCached, bool* aResult)
+ bool* aCached,
+ SecurityPropertySource* aSource,
+ bool* aResult)
{
// Child processes are not allowed direct access to this.
if (!XRE_IsParentProcess() && aType != nsISiteSecurityService::HEADER_HSTS) {
MOZ_CRASH("Child process: no direct access to "
"nsISiteSecurityService::IsSecureHost for non-HSTS entries");
}
NS_ENSURE_ARG(aResult);
// Only HSTS and HPKP are supported at the moment.
NS_ENSURE_TRUE(aType == nsISiteSecurityService::HEADER_HSTS ||
aType == nsISiteSecurityService::HEADER_HPKP,
NS_ERROR_NOT_IMPLEMENTED);
// set default in case if we can't find any STS information
*aResult = false;
- if (aCached) {
- *aCached = false;
- }
/* An IP address never qualifies as a secure URI. */
const nsCString& flatHost = PromiseFlatCString(aHost);
if (HostIsIPAddress(flatHost)) {
return NS_OK;
}
if (aType == nsISiteSecurityService::HEADER_HPKP) {
@@ -1510,21 +1601,25 @@ nsSiteSecurityService::IsSecureHost(uint
// Holepunch chart.apis.google.com and subdomains.
nsAutoCString host(
PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
if (host.EqualsLiteral("chart.apis.google.com") ||
StringEndsWith(host, NS_LITERAL_CSTRING(".chart.apis.google.com"))) {
if (aCached) {
*aCached = true;
}
+ if (aSource) {
+ *aSource = SourcePreload;
+ }
return NS_OK;
}
// First check the exact host.
- if (HostHasHSTSEntry(host, false, aFlags, aOriginAttributes, aResult, aCached)) {
+ if (HostHasHSTSEntry(host, false, aFlags, aOriginAttributes, aResult,
+ aCached, aSource)) {
return NS_OK;
}
SSSLOG(("no HSTS data for %s found, walking up domain", host.get()));
const char *subdomain;
uint32_t offset = 0;
@@ -1540,17 +1635,17 @@ nsSiteSecurityService::IsSecureHost(uint
}
// 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 (HostHasHSTSEntry(subdomainString, true, aFlags, aOriginAttributes, aResult,
- aCached)) {
+ aCached, aSource)) {
break;
}
SSSLOG(("no HSTS data for %s found, walking up domain", subdomain));
}
// Use whatever we ended up with, which defaults to false.
return NS_OK;
@@ -1713,18 +1808,18 @@ nsSiteSecurityService::SetHSTSPreload(co
NS_ENSURE_ARG_POINTER(aResult);
SSSLOG(("Top of SetHSTSPreload"));
const nsCString& flatHost = PromiseFlatCString(aHost);
nsAutoCString host(
PublicKeyPinningService::CanonicalizeHostname(flatHost.get()));
return SetHSTSState(nsISiteSecurityService::HEADER_HSTS, host.get(), aExpires,
- aIncludeSubdomains, 0, SecurityPropertySet, true,
- OriginAttributes());
+ aIncludeSubdomains, 0, SecurityPropertySet,
+ SourcePreload, OriginAttributes());
}
nsresult
nsSiteSecurityService::SetHPKPState(const char* aHost, SiteHPKPState& entry,
uint32_t aFlags, bool aIsPreload,
const OriginAttributes& aOriginAttributes)
{
if (aIsPreload && aOriginAttributes != OriginAttributes()) {
--- a/security/manager/ssl/nsSiteSecurityService.h
+++ b/security/manager/ssl/nsSiteSecurityService.h
@@ -37,16 +37,23 @@ using mozilla::OriginAttributes;
*/
enum SecurityPropertyState {
SecurityPropertyUnset = nsISiteSecurityState::SECURITY_PROPERTY_UNSET,
SecurityPropertySet = nsISiteSecurityState::SECURITY_PROPERTY_SET,
SecurityPropertyKnockout = nsISiteSecurityState::SECURITY_PROPERTY_KNOCKOUT,
SecurityPropertyNegative = nsISiteSecurityState::SECURITY_PROPERTY_NEGATIVE,
};
+enum SecurityPropertySource {
+ SourceUnknown = nsISiteSecurityService::SOURCE_UNKNOWN,
+ SourcePreload = nsISiteSecurityService::SOURCE_PRELOAD_LIST,
+ SourceOrganic = nsISiteSecurityService::SOURCE_ORGANIC_REQUEST,
+ SourceHSTSPriming = nsISiteSecurityService::SOURCE_HSTS_PRIMING,
+};
+
/**
* SiteHPKPState: A utility class that encodes/decodes a string describing
* the public key pins of a site.
* HPKP state consists of:
* - Hostname (nsCString)
* - Origin attributes (OriginAttributes)
* - Expiry time (PRTime (aka int64_t) in milliseconds)
* - A state flag (SecurityPropertyState, default SecurityPropertyUnset)
@@ -109,23 +116,25 @@ public:
NS_DECL_NSISITESECURITYSTATE
SiteHSTSState(const nsCString& aHost,
const OriginAttributes& aOriginAttributes,
const nsCString& aStateString);
SiteHSTSState(const nsCString& aHost,
const OriginAttributes& aOriginAttributes,
PRTime aHSTSExpireTime, SecurityPropertyState aHSTSState,
- bool aHSTSIncludeSubdomains);
+ bool aHSTSIncludeSubdomains,
+ SecurityPropertySource aSource);
nsCString mHostname;
OriginAttributes mOriginAttributes;
PRTime mHSTSExpireTime;
SecurityPropertyState mHSTSState;
bool mHSTSIncludeSubdomains;
+ SecurityPropertySource mHSTSSource;
bool IsExpired(uint32_t aType)
{
// If mHSTSExpireTime is 0, this entry never expires (this is the case for
// knockout entries).
if (mHSTSExpireTime == 0) {
return false;
}
@@ -159,27 +168,30 @@ public:
protected:
virtual ~nsSiteSecurityService();
private:
nsresult GetHost(nsIURI *aURI, nsACString &aResult);
nsresult SetHSTSState(uint32_t aType, const char* aHost, int64_t maxage,
bool includeSubdomains, uint32_t flags,
- SecurityPropertyState aHSTSState, bool aIsPreload,
+ SecurityPropertyState aHSTSState,
+ SecurityPropertySource aSource,
const OriginAttributes& aOriginAttributes);
nsresult ProcessHeaderInternal(uint32_t aType, nsIURI* aSourceURI,
const nsCString& aHeader,
nsISSLStatus* aSSLStatus,
uint32_t aFlags,
+ SecurityPropertySource aSource,
const OriginAttributes& aOriginAttributes,
uint64_t* aMaxAge, bool* aIncludeSubdomains,
uint32_t* aFailureResult);
nsresult ProcessSTSHeader(nsIURI* aSourceURI, const nsCString& aHeader,
uint32_t flags,
+ SecurityPropertySource aSource,
const OriginAttributes& aOriginAttributes,
uint64_t* aMaxAge, bool* aIncludeSubdomains,
uint32_t* aFailureResult);
nsresult ProcessPKPHeader(nsIURI* aSourceURI, const nsCString& aHeader,
nsISSLStatus* aSSLStatus, uint32_t flags,
const OriginAttributes& aOriginAttributes,
uint64_t* aMaxAge, bool* aIncludeSubdomains,
uint32_t* aFailureResult);
@@ -189,22 +201,24 @@ private:
nsresult RemoveStateInternal(uint32_t aType, nsIURI* aURI, uint32_t aFlags,
const OriginAttributes& aOriginAttributes);
nsresult RemoveStateInternal(uint32_t aType, const nsAutoCString& aHost,
uint32_t aFlags, bool aIsPreload,
const OriginAttributes& aOriginAttributes);
bool HostHasHSTSEntry(const nsAutoCString& aHost,
bool aRequireIncludeSubdomains, uint32_t aFlags,
const OriginAttributes& aOriginAttributes,
- bool* aResult, bool* aCached);
+ bool* aResult, bool* aCached,
+ SecurityPropertySource* aSource);
const nsSTSPreload *GetPreloadListEntry(const char *aHost);
nsresult IsSecureHost(uint32_t aType, const nsACString& aHost,
uint32_t aFlags,
const OriginAttributes& aOriginAttributes,
- bool* aCached, bool* aResult);
+ bool* aCached, SecurityPropertySource* aSource,
+ bool* aResult);
uint64_t mMaxMaxAge;
bool mUsePreloadList;
int64_t mPreloadListTimeOffset;
bool mProcessPKPHeadersFromNonBuiltInRoots;
RefPtr<mozilla::DataStorage> mSiteStateStorage;
RefPtr<mozilla::DataStorage> mPreloadStateStorage;
};
--- a/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
+++ b/security/manager/ssl/tests/mochitest/browser/browser_bug627234_perwindowpb.js
@@ -48,17 +48,18 @@ function test() {
return aIsPrivateMode ? Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
}
function doTest(aIsPrivateMode, aWindow, aCallback) {
aWindow.gBrowser.selectedBrowser.addEventListener("load", function() {
let sslStatus = new FakeSSLStatus();
uri = aWindow.Services.io.newURI("https://localhost/img.png");
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1000", sslStatus, privacyFlags(aIsPrivateMode));
+ "max-age=1000", sslStatus, privacyFlags(aIsPrivateMode),
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
privacyFlags(aIsPrivateMode)),
"checking sts host");
aCallback();
}, {capture: true, once: true});
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
--- a/security/manager/ssl/tests/unit/test_forget_about_site_security_headers.js
+++ b/security/manager/ssl/tests/unit/test_forget_about_site_security_headers.js
@@ -45,19 +45,21 @@ var sslStatus = new FakeSSLStatus(constr
"test_pinning_dynamic/a.pinning2.example.com-pinningroot.pem"));
// Test the normal case of processing HSTS and HPKP headers for
// a.pinning2.example.com, using "Forget About Site" on a.pinning2.example.com,
// and then checking that the platform doesn't consider a.pinning2.example.com
// to be HSTS or HPKP any longer.
add_task(function* () {
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, GOOD_MAX_AGE,
- sslStatus, 0);
+ sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
- GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0);
+ GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
"a.pinning2.example.com should be HSTS");
Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0),
"a.pinning2.example.com should be HPKP");
yield ForgetAboutSite.removeDataFromDomain("a.pinning2.example.com");
@@ -68,30 +70,33 @@ add_task(function* () {
});
// Test the case of processing HSTS and HPKP headers for a.pinning2.example.com,
// using "Forget About Site" on example.com, and then checking that the platform
// doesn't consider the subdomain to be HSTS or HPKP any longer. Also test that
// unrelated sites don't also get removed.
add_task(function* () {
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, GOOD_MAX_AGE,
- sslStatus, 0);
+ sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
- GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0);
+ GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
"a.pinning2.example.com should be HSTS (subdomain case)");
Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0),
"a.pinning2.example.com should be HPKP (subdomain case)");
// Add an unrelated site to HSTS. Not HPKP because we have no valid keys for
// example.org.
let unrelatedURI = Services.io.newURI("https://example.org");
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, unrelatedURI,
- GOOD_MAX_AGE, sslStatus, 0);
+ GOOD_MAX_AGE, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
unrelatedURI, 0), "example.org should be HSTS");
yield ForgetAboutSite.removeDataFromDomain("example.com");
Assert.ok(!sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
"a.pinning2.example.com should not be HSTS now (subdomain case)");
Assert.ok(!sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0),
@@ -114,31 +119,36 @@ add_task(function* () {
{ firstPartyDomain: "foo.com" },
{ userContextId: 1, firstPartyDomain: "foo.com" },
];
let unrelatedURI = Services.io.newURI("https://example.org");
for (let originAttributes of originAttributesList) {
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri, GOOD_MAX_AGE,
- sslStatus, 0, originAttributes);
+ sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
+ originAttributes);
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
GOOD_MAX_AGE + VALID_PIN + BACKUP_PIN, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
originAttributes);
Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
0, originAttributes),
"a.pinning2.example.com should be HSTS (originAttributes case)");
Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
0, originAttributes),
"a.pinning2.example.com should be HPKP (originAttributes case)");
// Add an unrelated site to HSTS. Not HPKP because we have no valid keys.
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, unrelatedURI,
- GOOD_MAX_AGE, sslStatus, 0, originAttributes);
+ GOOD_MAX_AGE, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
+ originAttributes);
Assert.ok(sss.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
unrelatedURI, 0, originAttributes),
"example.org should be HSTS (originAttributes case)");
}
yield ForgetAboutSite.removeDataFromDomain("example.com");
for (let originAttributes of originAttributesList) {
--- a/security/manager/ssl/tests/unit/test_ocsp_must_staple.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_must_staple.js
@@ -33,17 +33,18 @@ function add_tests() {
let uri = Services.io.newURI("https://ocsp-stapling-must-staple-ee-with-must-staple-int.example.com");
let keyHash = "VCIlmPM9NkgFQtrs4Oa5TeFcDu6MWRTKSNdePEhOgD8=";
let backupKeyHash = "KHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAN=";
let header = `max-age=1000; pin-sha256="${keyHash}"; pin-sha256="${backupKeyHash}"`;
let ssservice = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
let sslStatus = new FakeSSLStatus();
sslStatus.serverCert = constructCertFromFile("ocsp_certs/must-staple-ee-with-must-staple-int.pem");
- ssservice.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri, header, sslStatus, 0);
+ ssservice.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri, header, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(ssservice.isSecureURI(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0),
"ocsp-stapling-must-staple-ee-with-must-staple-int.example.com should have HPKP set");
// Clear accumulated state.
ssservice.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0);
Services.prefs.clearUserPref("security.cert_pinning.process_headers_from_non_builtin_roots");
Services.prefs.clearUserPref("security.cert_pinning.enforcement_level");
run_next_test();
--- a/security/manager/ssl/tests/unit/test_ocsp_no_hsts_upgrade.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_no_hsts_upgrade.js
@@ -39,15 +39,16 @@ function run_test() {
add_test(function () { ocspResponder.stop(run_next_test); });
let SSService = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
let uri = Services.io.newURI("http://localhost");
let sslStatus = new FakeSSLStatus();
SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=10000", sslStatus, 0);
+ "max-age=10000", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
"Domain for the OCSP AIA URI should be considered a HSTS host, otherwise" +
" we wouldn't be testing what we think we're testing");
run_next_test();
}
--- a/security/manager/ssl/tests/unit/test_pinning_header_parsing.js
+++ b/security/manager/ssl/tests/unit/test_pinning_header_parsing.js
@@ -23,17 +23,18 @@ function loadCert(cert_name, trust_strin
}
function checkFailParseInvalidPin(pinValue) {
let sslStatus = new FakeSSLStatus(
certFromFile("a.pinning2.example.com-pinningroot"));
let uri = Services.io.newURI("https://a.pinning2.example.com");
throws(() => {
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
- pinValue, sslStatus, 0);
+ pinValue, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
}, /NS_ERROR_FAILURE/, `Invalid pin "${pinValue}" should be rejected`);
}
function checkPassValidPin(pinValue, settingPin, expectedMaxAge) {
let sslStatus = new FakeSSLStatus(
certFromFile("a.pinning2.example.com-pinningroot"));
let uri = Services.io.newURI("https://a.pinning2.example.com");
let maxAge = {};
@@ -41,21 +42,24 @@ function checkPassValidPin(pinValue, set
// setup preconditions for the test, if setting ensure there is no previous
// state, if removing ensure there is a valid pin in place.
if (settingPin) {
gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0);
} else {
// add a known valid pin!
let validPinValue = "max-age=5000;" + VALID_PIN1 + BACKUP_PIN1;
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
- validPinValue, sslStatus, 0);
+ validPinValue, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
}
try {
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri,
- pinValue, sslStatus, 0, {}, maxAge);
+ pinValue, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
+ {}, maxAge);
ok(true, "Valid pin should be accepted");
} catch (e) {
ok(false, "Valid pin should have been accepted");
}
// check that maxAge was processed correctly
if (settingPin && expectedMaxAge) {
ok(maxAge.value == expectedMaxAge, `max-age value should be ${expectedMaxAge}`);
--- a/security/manager/ssl/tests/unit/test_sss_enumerate.js
+++ b/security/manager/ssl/tests/unit/test_sss_enumerate.js
@@ -47,22 +47,24 @@ function insertEntries() {
`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);
+ sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
for (let key of KEY_HASHES) {
header += `; pin-sha256="${key}"`;
}
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HPKP, uri, header,
- sslStatus, 0);
+ sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
}
}
function getEntries(type) {
let entryEnumerator = sss.enumerate(type);
let entries = [];
while (entryEnumerator.hasMoreElements()) {
let entry = entryEnumerator.getNext();
--- a/security/manager/ssl/tests/unit/test_sss_eviction.js
+++ b/security/manager/ssl/tests/unit/test_sss_eviction.js
@@ -53,17 +53,18 @@ function do_state_read(aSubject, aTopic,
ok(gSSService.isSecureURI(
Ci.nsISiteSecurityService.HEADER_HSTS,
Services.io.newURI("https://frequentlyused.example.com"), 0));
let sslStatus = new FakeSSLStatus();
for (let i = 0; i < 2000; i++) {
let uri = Services.io.newURI("http://bad" + i + ".example.com");
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1000", sslStatus, 0);
+ "max-age=1000", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
}
do_test_pending();
Services.obs.addObserver(do_state_written, "data-storage-written");
do_test_finished();
}
function run_test() {
Services.prefs.setIntPref("test.datastorage.write_timer_ms", 100);
--- a/security/manager/ssl/tests/unit/test_sss_originAttributes.js
+++ b/security/manager/ssl/tests/unit/test_sss_originAttributes.js
@@ -48,17 +48,19 @@ function doTest(originAttributes1, origi
sss.clearAll();
for (let type of [Ci.nsISiteSecurityService.HEADER_HSTS,
Ci.nsISiteSecurityService.HEADER_HPKP]) {
let header = GOOD_MAX_AGE;
if (type == Ci.nsISiteSecurityService.HEADER_HPKP) {
header += VALID_PIN + BACKUP_PIN;
}
// Set HSTS or HPKP for originAttributes1.
- sss.processHeader(type, uri, header, sslStatus, 0, originAttributes1);
+ sss.processHeader(type, uri, header, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
+ originAttributes1);
ok(sss.isSecureURI(type, uri, 0, originAttributes1),
"URI should be secure given original origin attributes");
equal(sss.isSecureURI(type, uri, 0, originAttributes2), shouldShare,
"URI should be secure given different origin attributes if and " +
"only if shouldShare is true");
if (!shouldShare) {
// Remove originAttributes2 from the storage.
@@ -95,16 +97,17 @@ function testInvalidOriginAttributes(ori
Ci.nsISiteSecurityService.HEADER_HPKP]) {
let header = GOOD_MAX_AGE;
if (type == Ci.nsISiteSecurityService.HEADER_HPKP) {
header += VALID_PIN + BACKUP_PIN;
}
let callbacks = [
() => sss.processHeader(type, uri, header, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST,
originAttributes),
() => sss.isSecureURI(type, uri, 0, originAttributes),
() => sss.removeState(type, uri, 0, originAttributes),
];
for (let callback of callbacks) {
throws(callback, /NS_ERROR_ILLEGAL_VALUE/,
"Should get an error with invalid origin attributes");
--- a/security/manager/ssl/tests/unit/test_sss_savestate.js
+++ b/security/manager/ssl/tests/unit/test_sss_savestate.js
@@ -2,17 +2,17 @@
* 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";
// The purpose of this test is to see that the site security service properly
// writes its state file.
const EXPECTED_ENTRIES = 6;
-const EXPECTED_HSTS_COLUMNS = 3;
+const EXPECTED_HSTS_COLUMNS = 4;
const EXPECTED_HPKP_COLUMNS = 4;
var gProfileDir = null;
const NON_ISSUED_KEY_HASH = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
// For reference, the format of the state file is a list of:
// <domain name> <expiration time in milliseconds>,<sts status>,<includeSubdomains>
// separated by newlines ('\n')
@@ -113,14 +113,15 @@ function run_test() {
let uriIndex = i % uris.length;
// vary max-age
let maxAge = "max-age=" + (i * 1000);
// alternate setting includeSubdomains
let includeSubdomains = (i % 2 == 0 ? "; includeSubdomains" : "");
let sslStatus = new FakeSSLStatus();
SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS,
uris[uriIndex], maxAge + includeSubdomains,
- sslStatus, 0);
+ sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
}
do_test_pending();
Services.obs.addObserver(checkStateWritten, "data-storage-written");
}
--- a/security/manager/ssl/tests/unit/test_sts_fqdn.js
+++ b/security/manager/ssl/tests/unit/test_sts_fqdn.js
@@ -12,17 +12,18 @@ function run_test() {
let uri2 = Services.io.newURI("https://example.com..");
ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri1, 0));
// These cases are only relevant as long as bug 1118522 hasn't been fixed.
ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri2, 0));
let sslStatus = new FakeSSLStatus();
SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1000;includeSubdomains", sslStatus, 0);
+ "max-age=1000;includeSubdomains", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri1, 0));
ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri2, 0));
SSService.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0);
ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri1, 0));
ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri2, 0));
--- a/security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
+++ b/security/manager/ssl/tests/unit/test_sts_ipv4_ipv6.js
@@ -14,28 +14,31 @@ function check_ip(s, v, ip) {
str += "/";
let uri = Services.io.newURI(str);
ok(!s.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
let parsedMaxAge = {};
let parsedIncludeSubdomains = {};
s.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1000;includeSubdomains", sslStatus, 0, {},
+ "max-age=1000;includeSubdomains", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST, {},
parsedMaxAge, parsedIncludeSubdomains);
ok(!s.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0),
"URI should not be secure if it contains an IP address");
/* Test that processHeader will ignore headers for an uri, if the uri
* contains an IP address not a hostname.
* If processHeader indeed ignore the header, then the output parameters will
* remain empty, and we shouldn't see the values passed as the header.
*/
notEqual(parsedMaxAge.value, 1000);
notEqual(parsedIncludeSubdomains.value, true);
+ notEqual(parsedMaxAge.value, undefined);
+ notEqual(parsedIncludeSubdomains.value, undefined);
}
function run_test() {
let SSService = Cc["@mozilla.org/ssservice;1"]
.getService(Ci.nsISiteSecurityService);
check_ip(SSService, 4, "127.0.0.1");
check_ip(SSService, 4, "10.0.0.1");
--- a/security/manager/ssl/tests/unit/test_sts_parser.js
+++ b/security/manager/ssl/tests/unit/test_sts_parser.js
@@ -12,31 +12,33 @@ let sss = Cc["@mozilla.org/ssservice;1"]
let sslStatus = new FakeSSLStatus();
function testSuccess(header, expectedMaxAge, expectedIncludeSubdomains) {
let dummyUri = Services.io.newURI("https://foo.com/bar.html");
let maxAge = {};
let includeSubdomains = {};
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, dummyUri, header,
- sslStatus, 0, {}, maxAge, includeSubdomains);
+ sslStatus, 0, sss.SOURCE_ORGANIC_REQUEST, {}, maxAge,
+ includeSubdomains);
equal(maxAge.value, expectedMaxAge, "Did not correctly parse maxAge");
equal(includeSubdomains.value, expectedIncludeSubdomains,
"Did not correctly parse presence/absence of includeSubdomains");
}
function testFailure(header) {
let dummyUri = Services.io.newURI("https://foo.com/bar.html");
let maxAge = {};
let includeSubdomains = {};
throws(() => {
sss.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, dummyUri, header,
- sslStatus, 0, {}, maxAge, includeSubdomains);
+ sslStatus, 0, sss.SOURCE_ORGANIC_REQUEST, {}, maxAge,
+ includeSubdomains);
}, "Parsed invalid header: " + header);
}
function run_test() {
// SHOULD SUCCEED:
testSuccess("max-age=100", 100, false);
testSuccess("max-age =100", 100, false);
testSuccess(" max-age=100", 100, false);
--- a/security/manager/ssl/tests/unit/test_sts_preload_dynamic.js
+++ b/security/manager/ssl/tests/unit/test_sts_preload_dynamic.js
@@ -55,13 +55,14 @@ function run_test() {
// check that it's now including subdomains
ok(SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, subDomainUri,
0));
// Now let's simulate overriding the entry by setting an entry from a header
// with max-age set to 0
SSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=0", sslStatus, 0);
+ "max-age=0", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
// this should no longer be an HSTS host
ok(!SSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
}
--- a/security/manager/ssl/tests/unit/test_sts_preloadlist_perwindowpb.js
+++ b/security/manager/ssl/tests/unit/test_sts_preloadlist_perwindowpb.js
@@ -72,43 +72,47 @@ function test_part1() {
Services.io.newURI("https://notsts.nonexistent.example.com."), 0));
// check that processing a header with max-age: 0 will remove a preloaded
// site from the list
let uri = Services.io.newURI("https://includesubdomains.preloaded.test");
let subDomainUri =
Services.io.newURI("https://subdomain.includesubdomains.preloaded.test");
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=0", sslStatus, 0);
+ "max-age=0", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
subDomainUri, 0));
// check that processing another header (with max-age non-zero) will
// re-enable a site's sts status
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1000", sslStatus, 0);
+ "max-age=1000", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
// but this time include subdomains was not set, so test for that
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
subDomainUri, 0));
gSSService.clearAll();
// check that processing a header with max-age: 0 from a subdomain of a site
// will not remove that (ancestor) site from the list
uri = Services.io.newURI("https://subdomain.noincludesubdomains.preloaded.test");
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=0", sslStatus, 0);
+ "max-age=0", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
Services.io.newURI("https://noincludesubdomains.preloaded.test"),
0));
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
uri = Services.io.newURI("https://subdomain.includesubdomains.preloaded.test");
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=0", sslStatus, 0);
+ "max-age=0", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
// we received a header with "max-age=0", so we have "no information"
// regarding the sts state of subdomain.includesubdomains.preloaded.test specifically,
// but it is actually still an STS host, because of the preloaded
// includesubdomains.preloaded.test including subdomains.
// Here's a drawing:
// |-- includesubdomains.preloaded.test (in preload list, includes subdomains) IS sts host
// |-- subdomain.includesubdomains.preloaded.test IS sts host
// | `-- another.subdomain.includesubdomains.preloaded.test IS sts host
@@ -123,17 +127,18 @@ function test_part1() {
Ci.nsISiteSecurityService.HEADER_HSTS,
Services.io.newURI("https://sibling.includesubdomains.preloaded.test"), 0));
ok(gSSService.isSecureURI(
Ci.nsISiteSecurityService.HEADER_HSTS,
Services.io.newURI("https://another.subdomain.includesubdomains.preloaded.test"),
0));
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1000", sslStatus, 0);
+ "max-age=1000", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
// Here's what we have now:
// |-- includesubdomains.preloaded.test (in preload list, includes subdomains) IS sts host
// |-- subdomain.includesubdomains.preloaded.test (include subdomains is false) IS sts host
// | `-- another.subdomain.includesubdomains.preloaded.test IS NOT sts host
// `-- sibling.includesubdomains.preloaded.test IS sts host
ok(gSSService.isSecureURI(
Ci.nsISiteSecurityService.HEADER_HSTS,
Services.io.newURI("https://subdomain.includesubdomains.preloaded.test"), 0));
@@ -149,17 +154,18 @@ function test_part1() {
// identifying a host that is on the preload list as no longer sts.
// (This happens when we're in regular browsing mode, we get a header from
// a site on the preload list, and that header later expires. We need to
// then treat that host as no longer an sts host.)
// (sanity check first - this should be in the preload list)
uri = Services.io.newURI("https://includesubdomains2.preloaded.test");
ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1", sslStatus, 0);
+ "max-age=1", sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
do_timeout(1250, function() {
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0));
run_next_test();
});
}
const IS_PRIVATE = Ci.nsISocketProvider.NO_PERMANENT_STORAGE;
@@ -170,49 +176,53 @@ function test_private_browsing1() {
Services.io.newURI("https://a.b.c.subdomain.includesubdomains.preloaded.test");
// sanity - includesubdomains.preloaded.test is preloaded, includeSubdomains set
ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
IS_PRIVATE));
ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, subDomainUri,
IS_PRIVATE));
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=0", sslStatus, IS_PRIVATE);
+ "max-age=0", sslStatus, IS_PRIVATE,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
IS_PRIVATE));
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
subDomainUri, IS_PRIVATE));
// check adding it back in
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1000", sslStatus, IS_PRIVATE);
+ "max-age=1000", sslStatus, IS_PRIVATE,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri, IS_PRIVATE));
// but no includeSubdomains this time
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
subDomainUri, IS_PRIVATE));
// do the hokey-pokey...
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=0", sslStatus, IS_PRIVATE);
+ "max-age=0", sslStatus, IS_PRIVATE,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
IS_PRIVATE));
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS,
subDomainUri, IS_PRIVATE));
// Test that an expired private browsing entry results in correctly
// identifying a host that is on the preload list as no longer sts.
// (This happens when we're in private browsing mode, we get a header from
// a site on the preload list, and that header later expires. We need to
// then treat that host as no longer an sts host.)
// (sanity check first - this should be in the preload list)
uri = Services.io.newURI("https://includesubdomains2.preloaded.test");
ok(gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
IS_PRIVATE));
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
- "max-age=1", sslStatus, IS_PRIVATE);
+ "max-age=1", sslStatus, IS_PRIVATE,
+ Ci.nsISiteSecurityService.SOURCE_ORGANIC_REQUEST);
do_timeout(1250, function() {
ok(!gSSService.isSecureURI(Ci.nsISiteSecurityService.HEADER_HSTS, uri,
IS_PRIVATE));
// Simulate leaving private browsing mode
Services.obs.notifyObservers(null, "last-pb-context-exited");
});
}
--- a/security/manager/tools/getHSTSPreloadList.js
+++ b/security/manager/tools/getHSTSPreloadList.js
@@ -111,18 +111,19 @@ function processStsHeader(host, header,
var includeSubdomains = { value: false };
var error = ERROR_NONE;
if (header != null && securityInfo != null) {
try {
var uri = Services.io.newURI("https://" + host.name);
var sslStatus = securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
.SSLStatus;
gSSService.processHeader(Ci.nsISiteSecurityService.HEADER_HSTS,
- uri, header, sslStatus, 0, {}, maxAge,
- includeSubdomains);
+ uri, header, sslStatus, 0,
+ Ci.nsISiteSecurityService.SOURCE_PRELOAD_LIST,
+ {}, maxAge, includeSubdomains);
} catch (e) {
dump("ERROR: could not process header '" + header + "' from " +
host.name + ": " + e + "\n");
error = e;
}
} else if (status == 0) {
error = ERROR_CONNECTING_TO_HOST;
} else {
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -10207,16 +10207,25 @@
"expires_in_version": "62",
"kind": "exponential",
"low": 100,
"high": 30000,
"n_buckets": 100,
"keyed": true,
"description": "The amount of time required for HSTS priming requests (ms), keyed by success or failure of the priming request. (success, failure)"
},
+ "HSTS_UPGRADE_SOURCE": {
+ "record_in_processes": [ "main" ],
+ "alert_emails": ["seceng-telemetry@mozilla.com"],
+ "bug_numbers": [1363546],
+ "expires_in_version": "62",
+ "kind": "enumerated",
+ "n_values": 8,
+ "description": "When we record an upgrade due to HSTS, record the source of that HSTS entry. (0: HSTS preload list, 1: HSTS Header seen naturally, 2: HSTS priming)"
+ },
"MIXED_CONTENT_OBJECT_SUBREQUEST": {
"record_in_processes": ["main", "content"],
"alert_emails": ["seceng-telemetry@mozilla.com"],
"bug_numbers": [1244116],
"expires_in_version": "60",
"kind": "enumerated",
"n_values": 10,
"description": "How often objects load insecure content on secure pages (counting pages, not objects). 0=pages with no mixed object subrequests, 1=pages with mixed object subrequests"