forked from mirrors/gecko-dev
Bug 1884921 - HTTPS-First should add a temporary exception for sites that it is not able to upgrade r=freddyb,simonf
- Introduce new pref `dom.security.https_first_add_exception_on_failiure` - Add new function `nsHTTPSOnlyUtils::AddFirstExceptionForSession`, which will set a temporary HTTPS-First exception - When detecting a redirect loop or when downgrading, and if the pref is set, call `AddFirstExceptionForSession` Differential Revision: https://phabricator.services.mozilla.com/D204380
This commit is contained in:
parent
08b5db9eb3
commit
5c6f4170ce
8 changed files with 247 additions and 11 deletions
|
|
@ -155,6 +155,8 @@ HTTPSOnlyFailedDowngradeAgain = Upgrading insecure request “%S” failed. Down
|
|||
HTTPSOnlyUpgradeSpeculativeConnection = Upgrading insecure speculative TCP connection “%1$S” to use “%2$S”.
|
||||
|
||||
HTTPSFirstSchemeless = Upgrading URL loaded in the address bar without explicit protocol scheme to use HTTPS.
|
||||
# LOCALIZATION NOTE: %S is the hostname for which a exception will be added;
|
||||
HTTPSFirstAddingSessionException = Website does not appear to support HTTPS. Further attempts to load “http://%S” securely will be skipped temporarily.
|
||||
|
||||
# LOCALIZATION NOTE: %S is the URL of the blocked request;
|
||||
IframeSandboxBlockedDownload = Download of “%S” was blocked because the triggering iframe has the sandbox flag set.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
#include "mozilla/TimeStamp.h"
|
||||
#include "mozilla/glean/GleanMetrics.h"
|
||||
#include "mozilla/NullPrincipal.h"
|
||||
#include "mozilla/OriginAttributes.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "mozilla/net/DNS.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
|
@ -334,6 +335,10 @@ bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
|
|||
return uriHost.Equals(principalHost) && uriPath.Equals(principalPath);
|
||||
};
|
||||
|
||||
// We do dot return early when we find a loop, as we still need to set an
|
||||
// exception at the end.
|
||||
bool isLoop = false;
|
||||
|
||||
// 6. Check actual redirects. If the Principal that kicked off the
|
||||
// load/redirect is not https, then it's definitely not a redirect cause by
|
||||
// https-only. If the scheme of the principal however is https and the
|
||||
|
|
@ -346,7 +351,7 @@ bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
|
|||
entry->GetPrincipal(getter_AddRefs(redirectPrincipal));
|
||||
if (redirectPrincipal && redirectPrincipal->SchemeIs("https") &&
|
||||
uriAndPrincipalComparator(redirectPrincipal)) {
|
||||
return true;
|
||||
isLoop = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
@ -359,18 +364,28 @@ bool nsHTTPSOnlyUtils::IsUpgradeDowngradeEndlessLoop(
|
|||
}
|
||||
}
|
||||
|
||||
// 7. Meta redirects and JS based redirects (win.location). If the security
|
||||
// context that triggered the load is not https, then it's defnitely no
|
||||
// endless loop caused by https-only. If the scheme is http however and the
|
||||
// asciiHost of the URI to be loaded matches the asciiHost of the Principal,
|
||||
// then we are dealing with an upgrade downgrade scenario and we have to break
|
||||
// the cycle.
|
||||
nsCOMPtr<nsIPrincipal> triggeringPrincipal = aLoadInfo->TriggeringPrincipal();
|
||||
if (!triggeringPrincipal->SchemeIs("https")) {
|
||||
return false;
|
||||
if (!isLoop) {
|
||||
// 7. Meta redirects and JS based redirects (win.location). If the security
|
||||
// context that triggered the load is not https, then it's defnitely no
|
||||
// endless loop caused by https-only. If the scheme is http however and the
|
||||
// asciiHost of the URI to be loaded matches the asciiHost of the Principal,
|
||||
// then we are dealing with an upgrade downgrade scenario and we have to
|
||||
// break the cycle.
|
||||
nsCOMPtr<nsIPrincipal> triggeringPrincipal =
|
||||
aLoadInfo->TriggeringPrincipal();
|
||||
if (!triggeringPrincipal->SchemeIs("https")) {
|
||||
return false;
|
||||
}
|
||||
isLoop = uriAndPrincipalComparator(triggeringPrincipal);
|
||||
}
|
||||
|
||||
return uriAndPrincipalComparator(triggeringPrincipal);
|
||||
if (isLoop && enforceForHTTPSFirstMode &&
|
||||
mozilla::StaticPrefs::
|
||||
dom_security_https_first_add_exception_on_failiure()) {
|
||||
AddHTTPSFirstExceptionForSession(aURI, aLoadInfo);
|
||||
}
|
||||
|
||||
return isLoop;
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
@ -588,6 +603,11 @@ nsHTTPSOnlyUtils::PotentiallyDowngradeHttpsFirstRequest(
|
|||
nsIScriptError::warningFlag, loadInfo,
|
||||
uri, true);
|
||||
|
||||
if (mozilla::StaticPrefs::
|
||||
dom_security_https_first_add_exception_on_failiure()) {
|
||||
AddHTTPSFirstExceptionForSession(uri, loadInfo);
|
||||
}
|
||||
|
||||
return newURI.forget();
|
||||
}
|
||||
|
||||
|
|
@ -935,6 +955,41 @@ bool nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef(nsIURI* aHTTPSSchemeURI,
|
|||
return uriEquals;
|
||||
}
|
||||
|
||||
/* static */
|
||||
nsresult nsHTTPSOnlyUtils::AddHTTPSFirstExceptionForSession(
|
||||
nsCOMPtr<nsIURI> aURI, nsILoadInfo* const aLoadInfo) {
|
||||
// We need to reconstruct a principal instead of taking one from the loadinfo,
|
||||
// as the permission needs a http scheme, while the passed URL or principals
|
||||
// on the loadinfo may have a https scheme.
|
||||
nsresult rv =
|
||||
NS_MutateURI(aURI).SetScheme("http"_ns).Finalize(getter_AddRefs(aURI));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mozilla::OriginAttributes oa = aLoadInfo->GetOriginAttributes();
|
||||
oa.SetFirstPartyDomain(true, aURI);
|
||||
|
||||
nsCOMPtr<nsIPermissionManager> permMgr =
|
||||
mozilla::components::PermissionManager::Service();
|
||||
NS_ENSURE_TRUE(permMgr, nsresult::NS_ERROR_SERVICE_NOT_AVAILABLE);
|
||||
|
||||
nsCOMPtr<nsIPrincipal> principal =
|
||||
mozilla::BasePrincipal::CreateContentPrincipal(aURI, oa);
|
||||
|
||||
nsCString host;
|
||||
aURI->GetHost(host);
|
||||
LogLocalizedString("HTTPSFirstAddingSessionException",
|
||||
{NS_ConvertUTF8toUTF16(host)}, nsIScriptError::warningFlag,
|
||||
aLoadInfo, aURI, true);
|
||||
|
||||
rv = permMgr->AddFromPrincipal(
|
||||
principal, "https-only-load-insecure"_ns,
|
||||
nsIHttpsOnlyModePermission::HTTPSFIRST_LOAD_INSECURE_ALLOW_SESSION,
|
||||
nsIPermissionManager::EXPIRE_SESSION, 0);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
/* static */
|
||||
uint32_t nsHTTPSOnlyUtils::GetStatusForSubresourceLoad(
|
||||
uint32_t aHttpsOnlyStatus) {
|
||||
|
|
|
|||
|
|
@ -165,6 +165,17 @@ class nsHTTPSOnlyUtils {
|
|||
nsIURI* aOtherURI,
|
||||
nsILoadInfo* aLoadInfo);
|
||||
|
||||
/**
|
||||
* Will add a special temporary HTTPS-Only exception that only applies to
|
||||
* HTTPS-First, and is not exposed in the UI.
|
||||
* @param aURI The URL for whose HTTP principal the exception should be
|
||||
* added
|
||||
* @param aLoadInfo The loadinfo of the request triggering this exception to
|
||||
* be added (needs to match aURI)
|
||||
*/
|
||||
static nsresult AddHTTPSFirstExceptionForSession(
|
||||
nsCOMPtr<nsIURI> aURI, nsILoadInfo* const aLoadInfo);
|
||||
|
||||
/**
|
||||
* Determines which HTTPS-Only status flags should get propagated to
|
||||
* sub-resources or sub-documents. As sub-resources and sub-documents are
|
||||
|
|
|
|||
27
dom/security/test/https-first/file_bug_1725646_a.sjs
Normal file
27
dom/security/test/https-first/file_bug_1725646_a.sjs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"use strict";
|
||||
/* eslint-disable @microsoft/sdl/no-insecure-url */
|
||||
|
||||
const URL_B =
|
||||
"http://example.com/tests/dom/security/test/https-first/file_bug_1725646_b.sjs";
|
||||
|
||||
const RESPONSE = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Welcome to our insecure site!</h1>
|
||||
<script type="application/javascript">
|
||||
window.opener.postMessage({location: location.href}, '*');
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
function handleRequest(request, response) {
|
||||
response.setHeader("Cache-Control", "no-cache", false);
|
||||
|
||||
if (request.scheme === "http") {
|
||||
response.write(RESPONSE);
|
||||
} else {
|
||||
response.setStatusLine(request.httpVersion, 302, "Found");
|
||||
response.setHeader("Location", URL_B, false);
|
||||
}
|
||||
}
|
||||
34
dom/security/test/https-first/file_bug_1725646_b.sjs
Normal file
34
dom/security/test/https-first/file_bug_1725646_b.sjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
"use strict";
|
||||
/* eslint-disable @microsoft/sdl/no-insecure-url */
|
||||
|
||||
const URL_A =
|
||||
"http://example.com/tests/dom/security/test/https-first/file_bug_1725646_a.sjs";
|
||||
const URL_B =
|
||||
"http://example.com/tests/dom/security/test/https-first/file_bug_1725646_b.sjs";
|
||||
|
||||
const RESPONSE = `
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>We don't support HTTPS :(</h1>
|
||||
<p>You will be redirected</p>
|
||||
<script type="application/javascript">
|
||||
window.opener.postMessage({ location: location.href }, "*");
|
||||
setTimeout(() => {
|
||||
window.location = "${URL_A}";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
|
||||
function handleRequest(request, response) {
|
||||
response.setHeader("Cache-Control", "no-cache", false);
|
||||
|
||||
if (request.scheme === "http") {
|
||||
response.write(RESPONSE);
|
||||
} else {
|
||||
response.setStatusLine(request.httpVersion, 302, "Found");
|
||||
response.setHeader("Location", URL_B, false);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,9 @@ skip-if = [
|
|||
["test_bad_cert.html"]
|
||||
support-files = ["file_bad_cert.sjs"]
|
||||
|
||||
["test_bug_1725646.html"]
|
||||
support-files = ["file_bug_1725646_a.sjs", "file_bug_1725646_b.sjs"]
|
||||
|
||||
["test_break_endless_upgrade_downgrade_loop.html"]
|
||||
support-files = [
|
||||
"file_break_endless_upgrade_downgrade_loop.sjs",
|
||||
|
|
|
|||
97
dom/security/test/https-first/test_bug_1725646.html
Normal file
97
dom/security/test/https-first/test_bug_1725646.html
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<!--
|
||||
Description:
|
||||
|
||||
1. We visit http://example.com/A
|
||||
2. HTTPS-First upgrades to https://example.com/A
|
||||
3. https://example.com/A redirects us to http://example.com/B, because we
|
||||
visit it via https
|
||||
4. HTTPS-First fails to upgrade to https://example.com/B as it gets redirected
|
||||
back to http, which means we set an HTTPS-Only/First exception for
|
||||
"http://example.com"
|
||||
5. http://example.com/B sends HTML informing the user that HTTPS is not
|
||||
supported, and redirecting the user back to http://example.com/A via
|
||||
window.location = "...".
|
||||
6. The load to http://example.com/A will not be upgraded again
|
||||
7. Subsequent visits of http://example.com/A will also not be upgraded
|
||||
-->
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>HTTPS-First-Mode - Simulate site similar to bom.gov.au</title>
|
||||
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
|
||||
</head>
|
||||
<body>
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
/* eslint-disable @microsoft/sdl/no-insecure-url */
|
||||
|
||||
const URL_A =
|
||||
"http://example.com/tests/dom/security/test/https-first/file_bug_1725646_a.sjs";
|
||||
const URL_B =
|
||||
"http://example.com/tests/dom/security/test/https-first/file_bug_1725646_b.sjs";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let testWin;
|
||||
let messageNumber = 0;
|
||||
|
||||
async function receiveMessage(event) {
|
||||
switch (messageNumber) {
|
||||
case 0:
|
||||
is(
|
||||
event.data.location,
|
||||
URL_B,
|
||||
"We should land on page B after being HTTP redirected"
|
||||
);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
is(
|
||||
event.data.location,
|
||||
URL_A,
|
||||
"We should land on page B after being redirected back through JS and not upgraded again"
|
||||
);
|
||||
ok(
|
||||
await SpecialPowers.testPermission(
|
||||
"https-only-load-insecure",
|
||||
SpecialPowers.Ci.nsIHttpsOnlyModePermission
|
||||
.HTTPSFIRST_LOAD_INSECURE_ALLOW_SESSION,
|
||||
URL_A
|
||||
),
|
||||
"A temporary HTTPS-First exception should have been added for the site"
|
||||
);
|
||||
testWin.close();
|
||||
testWin = window.open(URL_A);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
is(event.data.location, URL_A, "We should directly land on page A");
|
||||
testWin.close();
|
||||
window.removeEventListener("message", this);
|
||||
await SpecialPowers.removePermission(
|
||||
"https-only-load-insecure",
|
||||
URL_A
|
||||
);
|
||||
SimpleTest.finish();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw Error("Received too many messages");
|
||||
}
|
||||
messageNumber++;
|
||||
}
|
||||
|
||||
window.addEventListener("message", receiveMessage);
|
||||
|
||||
SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.security.https_first", true]],
|
||||
}).then(() => {
|
||||
testWin = window.open(URL_A);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -3830,6 +3830,13 @@
|
|||
value: @IS_EARLY_BETA_OR_EARLIER@
|
||||
mirror: always
|
||||
|
||||
# If true, will add a special temporary HTTPS-First exception for a site when a
|
||||
# HTTPS-First upgrade fails.
|
||||
- name: dom.security.https_first_add_exception_on_failiure
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
- name: dom.security.unexpected_system_load_telemetry_enabled
|
||||
type: bool
|
||||
value: true
|
||||
|
|
|
|||
Loading…
Reference in a new issue