зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1631384 - Added upgrade exceptions for HTTPS Only Mode. r=ckerschb,necko-reviewers,valentin
Differential Revision: https://phabricator.services.mozilla.com/D72002
This commit is contained in:
Родитель
8779e89d94
Коммит
00925bd53b
|
@ -4,20 +4,30 @@
|
|||
* 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/. */
|
||||
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsHTTPSOnlyUtils.h"
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "mozilla/net/DNS.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "nsIConsoleService.h"
|
||||
#include "nsIScriptError.h"
|
||||
#include "prnetdb.h"
|
||||
|
||||
/* ------ Upgrade ------ */
|
||||
|
||||
/* static */
|
||||
bool nsHTTPSOnlyUtils::ShouldUpgradeRequest(nsIURI* aURI,
|
||||
nsILoadInfo* aLoadInfo) {
|
||||
// 1. Check if HTTPS-Only mode is enabled
|
||||
// 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
|
||||
if (!mozilla::StaticPrefs::dom_security_https_only_mode()) {
|
||||
return false;
|
||||
}
|
||||
// 2. Check if NoUpgrade-flag is set in LoadInfo
|
||||
|
||||
// 2. Check for general exceptions
|
||||
if (OnionException(aURI) || LoopbackOrLocalException(aURI)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Check if NoUpgrade-flag is set in LoadInfo
|
||||
uint32_t httpsOnlyStatus = aLoadInfo->GetHttpsOnlyStatus();
|
||||
if (httpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
||||
// Let's log to the console, that we didn't upgrade this request
|
||||
|
@ -31,10 +41,8 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeRequest(nsIURI* aURI,
|
|||
return false;
|
||||
}
|
||||
|
||||
// 3. Upgrade the request
|
||||
|
||||
// Let's log it to the console
|
||||
// Append the additional 's' just for the logging
|
||||
// We can upgrade the request - let's log it to the console
|
||||
// Appending an 's' to the scheme for the logging. (http -> https)
|
||||
nsAutoCString scheme;
|
||||
aURI->GetScheme(scheme);
|
||||
scheme.AppendLiteral("s");
|
||||
|
@ -55,11 +63,52 @@ bool nsHTTPSOnlyUtils::ShouldUpgradeRequest(nsIURI* aURI,
|
|||
httpsOnlyStatus |= nsILoadInfo::HTTPS_ONLY_UPGRADED_LISTENER_NOT_REGISTERED;
|
||||
aLoadInfo->SetHttpsOnlyStatus(httpsOnlyStatus);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(nsIURI* aURI,
|
||||
int32_t aInnerWindowId,
|
||||
bool aFromPrivateWindow,
|
||||
uint32_t aHttpsOnlyStatus) {
|
||||
// 1. Check if the HTTPS-Only Mode is even enabled, before we do anything else
|
||||
if (!mozilla::StaticPrefs::dom_security_https_only_mode()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Check for general exceptions
|
||||
if (OnionException(aURI) || LoopbackOrLocalException(aURI)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Check if NoUpgrade-flag is set in LoadInfo
|
||||
if (aHttpsOnlyStatus & nsILoadInfo::HTTPS_ONLY_EXEMPT) {
|
||||
// Let's log to the console, that we didn't upgrade this request
|
||||
AutoTArray<nsString, 1> params = {
|
||||
NS_ConvertUTF8toUTF16(aURI->GetSpecOrDefault())};
|
||||
nsHTTPSOnlyUtils::LogLocalizedString(
|
||||
"HTTPSOnlyNoUpgradeException", params, nsIScriptError::infoFlag,
|
||||
aInnerWindowId, aFromPrivateWindow, aURI);
|
||||
return false;
|
||||
}
|
||||
|
||||
// We can upgrade the request - let's log it to the console
|
||||
// Appending an 's' to the scheme for the logging. (ws -> wss)
|
||||
nsAutoCString scheme;
|
||||
aURI->GetScheme(scheme);
|
||||
scheme.AppendLiteral("s");
|
||||
NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
|
||||
NS_ConvertUTF8toUTF16 reportScheme(scheme);
|
||||
|
||||
AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
|
||||
nsHTTPSOnlyUtils::LogLocalizedString(
|
||||
"HTTPSOnlyUpgradeRequest", params, nsIScriptError::warningFlag,
|
||||
aInnerWindowId, aFromPrivateWindow, aURI);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/** Logging **/
|
||||
/* ------ Logging ------ */
|
||||
|
||||
/* static */
|
||||
void nsHTTPSOnlyUtils::LogLocalizedString(
|
||||
|
@ -94,3 +143,55 @@ void nsHTTPSOnlyUtils::LogMessage(const nsAString& aMessage, uint32_t aFlags,
|
|||
true /* from chrome context */, aFlags);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------ Exceptions ------ */
|
||||
|
||||
/* static */
|
||||
bool nsHTTPSOnlyUtils::OnionException(nsIURI* aURI) {
|
||||
// Onion-host exception can get disabled with a pref
|
||||
if (mozilla::StaticPrefs::dom_security_https_only_mode_upgrade_onion()) {
|
||||
return false;
|
||||
}
|
||||
nsAutoCString host;
|
||||
aURI->GetHost(host);
|
||||
return StringEndsWith(host, NS_LITERAL_CSTRING(".onion"));
|
||||
}
|
||||
|
||||
/* static */
|
||||
bool nsHTTPSOnlyUtils::LoopbackOrLocalException(nsIURI* aURI) {
|
||||
nsAutoCString asciiHost;
|
||||
nsresult rv = aURI->GetAsciiHost(asciiHost);
|
||||
NS_ENSURE_SUCCESS(rv, false);
|
||||
|
||||
// Let's make a quick check if the host matches these loopback strings before
|
||||
// we do anything else
|
||||
if (asciiHost.EqualsLiteral("localhost") || asciiHost.EqualsLiteral("::1")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// The local-ip and loopback checks expect a NetAddr struct. We only have a
|
||||
// host-string but can convert it to a NetAddr by first converting it to
|
||||
// PRNetAddr.
|
||||
PRNetAddr tempAddr;
|
||||
memset(&tempAddr, 0, sizeof(PRNetAddr));
|
||||
// PR_StringToNetAddr does not properly initialize the output buffer in the
|
||||
// case of IPv6 input. See bug 223145.
|
||||
if (PR_StringToNetAddr(asciiHost.get(), &tempAddr) != PR_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// The linter wants this struct to get initialized,
|
||||
// but PRNetAddrToNetAddr will do that.
|
||||
mozilla::net::NetAddr addr; // NOLINT(cppcoreguidelines-pro-type-member-init)
|
||||
PRNetAddrToNetAddr(&tempAddr, &addr);
|
||||
|
||||
// Loopback IPs are always exempt
|
||||
if (IsLoopBackAddress(&addr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Local IP exception can get disabled with a pref
|
||||
bool upgradeLocal =
|
||||
mozilla::StaticPrefs::dom_security_https_only_mode_upgrade_local();
|
||||
return (!upgradeLocal && IsIPAddrLocal(&addr));
|
||||
}
|
||||
|
|
|
@ -12,13 +12,28 @@
|
|||
class nsHTTPSOnlyUtils {
|
||||
public:
|
||||
/**
|
||||
* Determines if a request should get because of the HTTPS-Only mode
|
||||
* @param aURI nsIURI of request
|
||||
* @param aLoadInfo nsILoadInfo of request
|
||||
* @param aShouldUpgrade true if request should get upgraded
|
||||
* Determines if a request should get upgraded because of the HTTPS-Only mode.
|
||||
* If true, the httpsOnlyStatus flag in LoadInfo gets updated and a message is
|
||||
* logged in the console.
|
||||
* @param aURI nsIURI of request
|
||||
* @param aLoadInfo nsILoadInfo of request
|
||||
* @return true if request should get upgraded
|
||||
*/
|
||||
static bool ShouldUpgradeRequest(nsIURI* aURI, nsILoadInfo* aLoadInfo);
|
||||
|
||||
/**
|
||||
* Determines if a request should get upgraded because of the HTTPS-Only mode.
|
||||
* If true, a message is logged in the console.
|
||||
* @param aURI nsIURI of request
|
||||
* @param innerWindowId Inner Window ID
|
||||
* @param aFromPrivateWindow Whether this request comes from a private window
|
||||
* @param httpsOnlyStatus httpsOnlyStatus from nsILoadInfo
|
||||
* @return true if request should get upgraded
|
||||
*/
|
||||
static bool ShouldUpgradeWebSocket(nsIURI* aURI, int32_t aInnerWindowId,
|
||||
bool aFromPrivateWindow,
|
||||
uint32_t aHttpsOnlyStatus);
|
||||
|
||||
/**
|
||||
* Logs localized message to either content console or browser console
|
||||
* @param aName Localization key
|
||||
|
@ -46,6 +61,19 @@ class nsHTTPSOnlyUtils {
|
|||
static void LogMessage(const nsAString& aMessage, uint32_t aFlags,
|
||||
uint64_t aInnerWindowID, bool aFromPrivateWindow,
|
||||
nsIURI* aURI = nullptr);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the URI ends with .onion
|
||||
* @param aURI URI object
|
||||
* @return true if the URI is an Onion URI
|
||||
*/
|
||||
static bool OnionException(nsIURI* aURI);
|
||||
|
||||
/**
|
||||
* Checks whether the URI is a loopback- or local-IP
|
||||
* @param aURI URI object
|
||||
* @return true if the URI is either loopback or local
|
||||
*/
|
||||
static bool LoopbackOrLocalException(nsIURI* aURI);
|
||||
};
|
||||
#endif /* nsHTTPSOnlyUtils_h___ */
|
||||
|
|
|
@ -3,3 +3,4 @@ support-files =
|
|||
file_console_logging.html
|
||||
|
||||
[browser_console_logging.js]
|
||||
[browser_upgrade_exceptions.js]
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
// Bug 1625448 - HTTPS Only Mode - Exceptions for loopback and local IP addresses
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1631384
|
||||
// This test ensures that various configurable upgrade exceptions work
|
||||
"use strict";
|
||||
|
||||
add_task(async function() {
|
||||
requestLongerTimeout(2);
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.security.https_only_mode", true]],
|
||||
});
|
||||
|
||||
// Loopback test
|
||||
await runTest(
|
||||
"Loopback IP addresses should always be exempt from upgrades (127.0.0.1)",
|
||||
"http://localhost",
|
||||
"http://"
|
||||
);
|
||||
await runTest(
|
||||
"Loopback IP addresses should always be exempt from upgrades (127.0.0.1)",
|
||||
"http://127.0.0.1",
|
||||
"http://"
|
||||
);
|
||||
// Default local-IP and onion tests
|
||||
await runTest(
|
||||
"Local IP addresses should be exempt from upgrades by default",
|
||||
"http://10.0.250.250",
|
||||
"http://"
|
||||
);
|
||||
await runTest(
|
||||
"Hosts ending with .onion should be be exempt from HTTPS-Only upgrades by default",
|
||||
"http://grocery.shopping.for.one.onion",
|
||||
"http://"
|
||||
);
|
||||
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.security.https_only_mode.upgrade_local", true],
|
||||
["dom.security.https_only_mode.upgrade_onion", true],
|
||||
],
|
||||
});
|
||||
|
||||
// Local-IP and onion tests with upgrade enabled
|
||||
await runTest(
|
||||
"Local IP addresses should get upgraded when 'dom.security.https_only_mode.upgrade_local' is set to true",
|
||||
"http://10.0.250.250",
|
||||
"https://"
|
||||
);
|
||||
await runTest(
|
||||
"Hosts ending with .onion should get upgraded when 'dom.security.https_only_mode.upgrade_onion' is set to true",
|
||||
"http://grocery.shopping.for.one.onion",
|
||||
"https://"
|
||||
);
|
||||
// Local-IP request with HTTPS_ONLY_EXEMPT flag
|
||||
await runTest(
|
||||
"The HTTPS_ONLY_EXEMPT flag should overrule upgrade-prefs",
|
||||
"http://10.0.250.250",
|
||||
"http://",
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
async function runTest(desc, url, startsWith, exempt = false) {
|
||||
const responseURL = await new Promise(resolve => {
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.timeout = 1200;
|
||||
xhr.open("GET", url);
|
||||
if (exempt) {
|
||||
xhr.channel.loadInfo.httpsOnlyStatus |= Ci.nsILoadInfo.HTTPS_ONLY_EXEMPT;
|
||||
}
|
||||
xhr.onreadystatechange = () => {
|
||||
// We don't care about the result and it's possible that
|
||||
// the requests might even succeed in some testing environments
|
||||
if (
|
||||
xhr.readyState !== XMLHttpRequest.OPENED ||
|
||||
xhr.readyState !== XMLHttpRequest.UNSENT
|
||||
) {
|
||||
// Let's make sure this function doesn't get caled anymore
|
||||
xhr.onreadystatechange = undefined;
|
||||
resolve(xhr.responseURL);
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
});
|
||||
ok(responseURL.startsWith(startsWith), desc);
|
||||
}
|
|
@ -1579,22 +1579,28 @@ nsresult WebSocketImpl::Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal,
|
|||
|
||||
// If the HTTPS-Only mode is enabled, we need to upgrade the websocket
|
||||
// connection from ws:// to wss:// and mark it as secure.
|
||||
if (!mIsServerSide && !mSecure &&
|
||||
StaticPrefs::dom_security_https_only_mode()) {
|
||||
// let's use the old specification before the upgrade for logging
|
||||
AutoTArray<nsString, 2> params;
|
||||
CopyUTF8toUTF16(mURI, *params.AppendElement());
|
||||
if (!mIsServerSide && !mSecure) {
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), mURI);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mURI.ReplaceSubstring("ws://", "wss://");
|
||||
if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
|
||||
return NS_OK;
|
||||
uint32_t httpsOnlyStatus = nsILoadInfo::HTTPS_ONLY_UNINITIALIZED;
|
||||
if (originDoc) {
|
||||
nsCOMPtr<nsIChannel> channel = originDoc->GetChannel();
|
||||
if (channel) {
|
||||
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
|
||||
httpsOnlyStatus = loadInfo->GetHttpsOnlyStatus();
|
||||
}
|
||||
}
|
||||
mSecure = true;
|
||||
|
||||
params.AppendElement(NS_LITERAL_STRING("wss"));
|
||||
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyUpgradeRequest", params,
|
||||
nsIScriptError::warningFlag,
|
||||
mInnerWindowID, mPrivateBrowsing);
|
||||
if (nsHTTPSOnlyUtils::ShouldUpgradeWebSocket(
|
||||
uri, mInnerWindowID, mPrivateBrowsing, httpsOnlyStatus)) {
|
||||
mURI.ReplaceSubstring("ws://", "wss://");
|
||||
if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
|
||||
return NS_OK;
|
||||
}
|
||||
mSecure = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
|
||||
|
|
|
@ -2487,6 +2487,20 @@
|
|||
value: false
|
||||
mirror: always
|
||||
|
||||
# If true and HTTPS-only mode is enabled, requests
|
||||
# to local IP addresses are also upgraded
|
||||
- name: dom.security.https_only_mode.upgrade_local
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# If true and HTTPS-only mode is enabled, requests
|
||||
# to .onion hosts are also upgraded
|
||||
- name: dom.security.https_only_mode.upgrade_onion
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
mirror: always
|
||||
|
||||
# WARNING: Don't ever update that pref manually! It is only used
|
||||
# for telemetry purposes and allows to reason about retention of
|
||||
# the pref dom.security.https_only_mode from above.
|
||||
|
|
Загрузка…
Ссылка в новой задаче