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:
julianwels 2020-04-29 15:52:19 +00:00
Родитель 8779e89d94
Коммит 00925bd53b
6 изменённых файлов: 263 добавлений и 27 удалений

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

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