Bug 1620242 - Basic implementation for HTTPS Only Mode. r=ckerschb,mixedpuppy

Differential Revision: https://phabricator.services.mozilla.com/D62590

--HG--
rename : dom/security/test/csp/file_redirect_report.sjs => dom/security/test/https-only/file_redirect.sjs
rename : dom/security/test/csp/file_upgrade_insecure.html => dom/security/test/https-only/file_upgrade_insecure.html
rename : dom/security/test/csp/file_upgrade_insecure_server.sjs => dom/security/test/https-only/file_upgrade_insecure_server.sjs
rename : dom/security/test/csp/file_upgrade_insecure_wsh.py => dom/security/test/https-only/file_upgrade_insecure_wsh.py
extra : moz-landing-system : lando
This commit is contained in:
JulianWels 2020-03-16 16:47:54 +00:00
Родитель bc32add109
Коммит c2c4e65115
28 изменённых файлов: 792 добавлений и 58 удалений

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

@ -253,6 +253,7 @@ module.exports = {
"dom/security/test/cors/**",
"dom/security/test/csp/**",
"dom/security/test/general/**",
"dom/security/test/https-only/**",
"dom/security/test/mixedcontentblocker/**",
"dom/security/test/sri/**",
"dom/security/test/referrer-policy/**",

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

@ -142,3 +142,9 @@ XFOInvalid = Invalid X-Frame-Options: “%1$S” header from “%2$S” loaded i
XFODeny = Load denied by X-Frame-Options: “%1$S” from “%2$S”, site does not permit any framing. Attempted to load into “%3$S”.
# LOCALIZATION NOTE: %1$S is the header value, %2$S is frame URI and %3$S is the parent document URI.
XFOSameOrigin = Load denied by X-Frame-Options: “%1$S” from “%2$S”, site does not permit cross-origin framing from “%3$S”.
# HTTPS-Only Mode
# LOCALIZATION NOTE: %1$S is the URL of the upgraded request; %2$S is the upgraded scheme.
HTTPSOnlyUpgradeRequest = Upgrading insecure request “%1$S” to use “%2$S”.
# LOCALIZATION NOTE: %1$S is the URL of request.
HTTPSOnlyNoUpgrade = Request for “%1$S” was not upgraded because it had the NoUpgrade-flag.

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

@ -21,6 +21,7 @@ EXPORTS.mozilla.dom += [
'nsCSPContext.h',
'nsCSPService.h',
'nsCSPUtils.h',
'nsHTTPSOnlyUtils.h',
'nsMixedContentBlocker.h',
'PolicyTokenizer.h',
'ReferrerInfo.h',
@ -48,6 +49,7 @@ UNIFIED_SOURCES += [
'nsCSPParser.cpp',
'nsCSPService.cpp',
'nsCSPUtils.cpp',
'nsHTTPSOnlyUtils.cpp',
'nsMixedContentBlocker.cpp',
'PolicyTokenizer.cpp',
'ReferrerInfo.cpp',

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

@ -0,0 +1,110 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/StaticPrefs_dom.h"
#include "nsContentUtils.h"
#include "nsHTTPSOnlyUtils.h"
#include "nsIConsoleService.h"
#include "nsIScriptError.h"
/* static */
bool nsHTTPSOnlyUtils::ShouldUpgradeRequest(nsIURI* aURI,
nsILoadInfo* aLoadInfo) {
// 1. Check if HTTPS-Only mode is enabled
if (!mozilla::StaticPrefs::dom_security_https_only_mode()) {
return false;
}
// 2. Check if NoUpgrade-flag is set in LoadInfo
if (aLoadInfo->GetHttpsOnlyNoUpgrade()) {
// Let's log to the console, that we didn't upgrade this request
uint32_t innerWindowId = aLoadInfo->GetInnerWindowID();
AutoTArray<nsString, 2> params = {
NS_ConvertUTF8toUTF16(aURI->GetSpecOrDefault())};
nsHTTPSOnlyUtils::LogLocalizedString(
"HTTPSOnlyNoUpgrade", params, nsIScriptError::infoFlag, innerWindowId,
!!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId, aURI);
return false;
}
// 3. Upgrade the request
// Let's log it to the console
// Append the additional 's' just for the logging
nsAutoCString scheme;
aURI->GetScheme(scheme);
scheme.AppendLiteral("s");
NS_ConvertUTF8toUTF16 reportSpec(aURI->GetSpecOrDefault());
NS_ConvertUTF8toUTF16 reportScheme(scheme);
uint32_t innerWindowId = aLoadInfo->GetInnerWindowID();
AutoTArray<nsString, 2> params = {reportSpec, reportScheme};
nsHTTPSOnlyUtils::LogLocalizedString(
"HTTPSOnlyUpgradeRequest", params, nsIScriptError::warningFlag,
innerWindowId, !!aLoadInfo->GetOriginAttributes().mPrivateBrowsingId,
aURI);
return true;
}
/** Logging **/
/* static */
void nsHTTPSOnlyUtils::LogLocalizedString(
const char* aName, const nsTArray<nsString>& aParams, uint32_t aFlags,
uint64_t aInnerWindowID, bool aFromPrivateWindow, nsIURI* aURI) {
nsAutoString logMsg;
nsContentUtils::FormatLocalizedString(nsContentUtils::eSECURITY_PROPERTIES,
aName, aParams, logMsg);
LogMessage(logMsg, aFlags, aInnerWindowID, aFromPrivateWindow, aURI);
}
/* static */
void nsHTTPSOnlyUtils::LogMessage(const nsAString& aMessage, uint32_t aFlags,
uint64_t aInnerWindowID,
bool aFromPrivateWindow, nsIURI* aURI) {
// Prepending HTTPS-Only to the outgoing console message
nsString message;
message.AppendLiteral(u"HTTPS-Only Mode: ");
message.Append(aMessage);
// Allow for easy distinction in devtools code.
nsCString category("HTTPSOnly");
if (aInnerWindowID > 0) {
// Send to content console
nsContentUtils::ReportToConsoleByWindowID(message, aFlags, category,
aInnerWindowID, aURI);
} else {
// Send to browser console
LogSimpleConsoleError(message, category.get(), aFromPrivateWindow,
true /* from chrome context */, aFlags);
}
}
/* static */
void nsHTTPSOnlyUtils::LogSimpleConsoleError(const nsAString& aErrorText,
const char* aCategory,
bool aFromPrivateWindow,
bool aFromChromeContext,
uint32_t aErrorFlags) {
nsCOMPtr<nsIScriptError> scriptError =
do_CreateInstance(NS_SCRIPTERROR_CONTRACTID);
if (!scriptError) {
return;
}
nsCOMPtr<nsIConsoleService> console =
do_GetService(NS_CONSOLESERVICE_CONTRACTID);
if (!console) {
return;
}
nsresult rv = scriptError->Init(aErrorText, EmptyString(), EmptyString(), 0,
0, aErrorFlags, aCategory, aFromPrivateWindow,
aFromChromeContext);
if (NS_FAILED(rv)) {
return;
}
console->LogMessage(scriptError);
}

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

@ -0,0 +1,64 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef nsHTTPSOnlyUtils_h___
#define nsHTTPSOnlyUtils_h___
#include "nsIScriptError.h"
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
*/
static bool ShouldUpgradeRequest(nsIURI* aURI, nsILoadInfo* aLoadInfo);
/**
* Logs localized message to either content console or browser console
* @param aName Localization key
* @param aParams Localization parameters
* @param aFlags Logging Flag (see nsIScriptError)
* @param aInnerWindowID Inner Window ID (Logged on browser console if 0)
* @param aFromPrivateWindow If from private window
* @param [aURI] Optional: URI to log
*/
static void LogLocalizedString(const char* aName,
const nsTArray<nsString>& aParams,
uint32_t aFlags, uint64_t aInnerWindowID,
bool aFromPrivateWindow,
nsIURI* aURI = nullptr);
private:
/**
* Logs localized message to either content console or browser console
* @param aMessage Message to log
* @param aFlags Logging Flag (see nsIScriptError)
* @param aInnerWindowID Inner Window ID (Logged on browser console if 0)
* @param aFromPrivateWindow If from private window
* @param [aURI] Optional: URI to log
*/
static void LogMessage(const nsAString& aMessage, uint32_t aFlags,
uint64_t aInnerWindowID, bool aFromPrivateWindow,
nsIURI* aURI = nullptr);
/**
* Report simple error message to the browser console
* @param aErrorText the error message
* @param aCategory Name of the module reporting error
* @param aFromPrivateWindow Whether from private window or not
* @param aFromChromeContext Whether from chrome context or not
* @param [aErrorFlags] See nsIScriptError.
*/
static void LogSimpleConsoleError(
const nsAString& aErrorText, const char* aCategory,
bool aFromPrivateWindow, bool aFromChromeContext,
uint32_t aErrorFlags = nsIScriptError::errorFlag);
};
#endif /* nsHTTPSOnlyUtils_h___ */

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

@ -812,6 +812,12 @@ nsresult nsMixedContentBlocker::ShouldLoad(
return NS_OK;
}
// If https-only mode is enabled we'll upgrade this later anyway
if (StaticPrefs::dom_security_https_only_mode()) {
*aDecision = ACCEPT;
return NS_OK;
}
// The page might have set the CSP directive 'upgrade-insecure-requests'. In
// such a case allow the http: load to succeed with the promise that the
// channel will get upgraded to https before fetching any data from the

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

@ -0,0 +1,37 @@
// https://bugzilla.mozilla.org/show_bug.cgi?id=1613063
// Step 1. Send request with redirect queryString (eg. file_redirect.sjs?302)
// Step 2. Server responds with corresponding redirect code to http://example.com/../file_redirect.sjs?check
// Step 3. Response from ?check indicates whether the redirected request was secure or not.
const RESPONSE_SECURE = "secure-ok";
const RESPONSE_INSECURE = "secure-error";
const RESPONSE_ERROR = "unexpected-query";
function handleRequest(request, response) {
response.setHeader("Cache-Control", "no-cache", false);
const query = request.queryString;
// Send redirect header
if ((query >= 301 && query <= 303) || query == 307) {
const loc =
"http://example.com/tests/dom/security/test/https-only/file_redirect.sjs?check";
response.setStatusLine(request.httpVersion, query, "Moved");
response.setHeader("Location", loc, false);
return;
}
// Check if scheme is http:// oder https://
if (query == "check") {
const secure =
request.scheme == "https" ? RESPONSE_SECURE : RESPONSE_INSECURE;
response.setStatusLine(request.httpVersion, 200, "OK");
response.write(secure);
return;
}
// This should not happen
response.setStatusLine(request.httpVersion, 500, "OK");
response.write(RESPONSE_ERROR);
}

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

@ -0,0 +1,89 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Bug 1613063 - HTTPS Only Mode</title>
<!-- style -->
<link rel='stylesheet' type='text/css' href='http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?style' media='screen' />
<!-- font -->
<style>
@font-face {
font-family: "foofont";
src: url('http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?font');
}
.div_foo { font-family: "foofont"; }
</style>
</head>
<body>
<!-- images: -->
<img src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?img"></img>
<!-- redirects: upgrade http:// to https:// redirect to http:// and then upgrade to https:// again -->
<img src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?redirect-image"></img>
<!-- script: -->
<script src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?script"></script>
<!-- media: -->
<audio src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?media"></audio>
<!-- objects: -->
<object width="10" height="10" data="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?object"></object>
<!-- font: (apply font loaded in header to div) -->
<div class="div_foo">foo</div>
<!-- iframe: (same origin) -->
<iframe src="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?iframe">
<!-- within that iframe we load an image over http and make sure the requested gets upgraded to https -->
</iframe>
<!-- xhr: -->
<script type="application/javascript">
var myXHR = new XMLHttpRequest();
myXHR.open("GET", "http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?xhr");
myXHR.send(null);
</script>
<!-- websockets: upgrade ws:// to wss://-->
<script type="application/javascript">
// WebSocket tests are not supported on Android yet. Bug 1566168
const {AppConstants} = SpecialPowers.Cu.import("resource://gre/modules/AppConstants.jsm", {});
if (AppConstants.platform !== "android") {
var mySocket = new WebSocket("ws://example.com/tests/dom/security/test/https-only/file_upgrade_insecure");
mySocket.onopen = function(e) {
if (mySocket.url.includes("wss://")) {
window.parent.postMessage({result: "websocket-ok"}, "*");
}
else {
window.parent.postMessage({result: "websocket-error"}, "*");
}
mySocket.close();
};
mySocket.onerror = function(e) {
// debug information for Bug 1316305
dump(" xxx mySocket.onerror: (mySocket): " + mySocket + "\n");
dump(" xxx mySocket.onerror: (mySocket.url): " + mySocket.url + "\n");
dump(" xxx mySocket.onerror: (e): " + e + "\n");
dump(" xxx mySocket.onerror: (e.message): " + e.message + "\n");
dump(" xxx mySocket.onerror: This might be related to Bug 1316305!\n");
window.parent.postMessage({result: "websocket-unexpected-error"}, "*");
};
}
</script>
<!-- form action: (upgrade POST from http:// to https://) -->
<iframe name='formFrame' id='formFrame'></iframe>
<form target="formFrame" action="http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?form" method="POST">
<input name="foo" value="foo">
<input type="submit" id="submitButton" formenctype='multipart/form-data' value="Submit form">
</form>
<script type="text/javascript">
var submitButton = document.getElementById('submitButton');
submitButton.click();
</script>
</body>
</html>

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

@ -0,0 +1,112 @@
// SJS file for HTTPS-Only Mode mochitests
// Bug 1613063 - HTTPS Only Mode
const TOTAL_EXPECTED_REQUESTS = 11;
const IFRAME_CONTENT =
"<!DOCTYPE HTML>" +
"<html>" +
"<head><meta charset='utf-8'>" +
"<title>Bug 1613063 - HTTPS Only Mode</title>" +
"</head>" +
"<body>" +
"<img src='http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?nested-img'></img>" +
"</body>" +
"</html>";
const expectedQueries = [
"script",
"style",
"img",
"iframe",
"form",
"xhr",
"media",
"object",
"font",
"img-redir",
"nested-img",
];
function handleRequest(request, response) {
// avoid confusing cache behaviors
response.setHeader("Cache-Control", "no-cache", false);
var queryString = request.queryString;
// initialize server variables and save the object state
// of the initial request, which returns async once the
// server has processed all requests.
if (queryString == "queryresult") {
setState("totaltests", TOTAL_EXPECTED_REQUESTS.toString());
setState("receivedQueries", "");
response.processAsync();
setObjectState("queryResult", response);
return;
}
// handle img redirect (https->http)
if (queryString == "redirect-image") {
var newLocation =
"http://example.com/tests/dom/security/test/https-only/file_upgrade_insecure_server.sjs?img-redir";
response.setStatusLine("1.1", 302, "Found");
response.setHeader("Location", newLocation, false);
return;
}
// just in case error handling for unexpected queries
if (expectedQueries.indexOf(queryString) == -1) {
response.write("unexpected-response");
return;
}
// make sure all the requested queries are indeed https
queryString += request.scheme == "https" ? "-ok" : "-error";
var receivedQueries = getState("receivedQueries");
// images, scripts, etc. get queried twice, do not
// confuse the server by storing the preload as
// well as the actual load. If either the preload
// or the actual load is not https, then we would
// append "-error" in the array and the test would
// fail at the end.
if (receivedQueries.includes(queryString)) {
return;
}
// append the result to the total query string array
if (receivedQueries != "") {
receivedQueries += ",";
}
receivedQueries += queryString;
setState("receivedQueries", receivedQueries);
// keep track of how many more requests the server
// is expecting
var totaltests = parseInt(getState("totaltests"));
totaltests -= 1;
setState("totaltests", totaltests.toString());
// return content (img) for the nested iframe to test
// that subresource requests within nested contexts
// get upgraded as well. We also have to return
// the iframe context in case of an error so we
// can test both, using upgrade-insecure as well
// as the base case of not using upgrade-insecure.
if (queryString == "iframe-ok" || queryString == "iframe-error") {
response.write(IFRAME_CONTENT);
}
// if we have received all the requests, we return
// the result back.
if (totaltests == 0) {
getObjectState("queryResult", function(queryResponse) {
if (!queryResponse) {
return;
}
var receivedQueries = getState("receivedQueries");
queryResponse.write(receivedQueries);
queryResponse.finish();
});
}
}

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

@ -0,0 +1,7 @@
from mod_pywebsocket import msgutil
def web_socket_do_extra_handshake(request):
pass
def web_socket_transfer_data(request):
pass

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

@ -0,0 +1,13 @@
[DEFAULT]
support-files =
file_redirect.sjs
file_upgrade_insecure.html
file_upgrade_insecure_server.sjs
file_upgrade_insecure_wsh.py
prefs =
security.mixed_content.upgrade_display_content=false
[test_resource_upgrade.html]
scheme=https
[test_redirect_upgrade.html]
scheme=https

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

@ -0,0 +1,58 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=1613063
Test that 302 redirect requests get upgraded to https:// with HTTPS-Only Mode enabled
-->
<head>
<title>HTTPS-Only Mode - XHR Redirect Upgrade</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<h1>HTTPS-Only Mode</h1>
<p>Upgrade Test for insecure XHR redirects.</p>
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1613063">Bug 1613063</a>
<script type="application/javascript">
const redirectCodes = ["301", "302", "303", "307"]
let currentTest = 0
function startTest() {
const currentCode = redirectCodes[currentTest];
const myXHR = new XMLHttpRequest();
// Make a request to a site (eg. https://file_redirect.sjs?301), which will redirect to http://file_redirect.sjs?check.
// The response will either be secure-ok, if the request has been upgraded to https:// or secure-error if it didn't.
myXHR.open("GET", `https://example.com/tests/dom/security/test/https-only/file_redirect.sjs?${currentCode}`);
myXHR.onload = (e) => {
is(myXHR.responseText, "secure-ok", `a ${currentCode} redirect when posting violation report should be blocked`)
testDone();
}
// This should not happen
myXHR.onerror = (e) => {
ok(false, `Could not query results from server for ${currentCode}-redirect test (" + e.message + ")`);
testDone();
}
myXHR.send();
}
function testDone() {
// Check if there are remaining tests
if (++currentTest < redirectCodes.length) {
startTest()
} else {
SimpleTest.finish();
}
}
SimpleTest.waitForExplicitFinish();
// Set preference and start test
SpecialPowers.pushPrefEnv({ set: [["dom.security.https_only_mode", true]] }, startTest);
</script>
</body>
</html>

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

@ -0,0 +1,123 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>HTTPS-Only Mode - Resource Upgrade</title>
<!-- Including SimpleTest.js so we can use waitForExplicitFinish !-->
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<h1>HTTPS-Only Mode</h1>
<p>Upgrade Test for various resources</p>
<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1613063">Bug 1613063</a>
<iframe style="width:100%;" id="testframe"></iframe>
<script class="testbody" type="text/javascript">
/* Description of the test:
* We load resources (img, script, sytle, etc) over *http* and make sure
* that all the resources get upgraded to use >> https << when the
* preference "dom.security.https_only_mode" is set to true. We further
* test that subresources within nested contexts (iframes) get upgraded
* and also test the handling of server side redirects.
*
* In detail:
* We perform an XHR request to the *.sjs file which is processed async on
* the server and waits till all the requests were processed by the server.
* Once the server received all the different requests, the server responds
* to the initial XHR request with an array of results which must match
* the expected results from each test, making sure that all requests
* received by the server (*.sjs) were actually *https* requests.
*/
const { AppConstants } = SpecialPowers.Cu.import(
"resource://gre/modules/AppConstants.jsm",
{}
);
const splitRegex = /^(.*)-(.*)$/
const testConfig = {
topLevelScheme: "http://",
results: [
"iframe", "script", "img", "img-redir", "font", "xhr", "style",
"media", "object", "form", "nested-img"
]
}
// TODO: WebSocket tests are not supported on Android Yet. Bug 1566168.
if (AppConstants.platform !== "android") {
testConfig.results.push("websocket");
}
function runTest() {
// sends an xhr request to the server which is processed async, which only
// returns after the server has received all the expected requests.
var myXHR = new XMLHttpRequest();
myXHR.open("GET", "file_upgrade_insecure_server.sjs?queryresult");
myXHR.onload = function (e) {
var results = myXHR.responseText.split(",");
for (var index in results) {
checkResult(results[index]);
}
}
myXHR.onerror = function (e) {
ok(false, "Could not query results from server (" + e.message + ")");
finishTest();
}
myXHR.send();
// give it some time and run the testpage
SimpleTest.executeSoon(() => {
var src = testConfig.topLevelScheme + "example.com/tests/dom/security/test/https-only/file_upgrade_insecure.html";
document.getElementById("testframe").src = src;
});
}
// a postMessage handler that is used by sandboxed iframes without
// 'allow-same-origin' to bubble up results back to this main page.
window.addEventListener("message", receiveMessage);
function receiveMessage(event) {
checkResult(event.data.result);
}
function finishTest() {
window.removeEventListener("message", receiveMessage);
SimpleTest.finish();
}
function checkResult(response) {
// A response looks either like this "iframe-ok" or "[key]-[result]"
const [, key, result] = splitRegex.exec(response)
// try to find the expected result within the results array
var index = testConfig.results.indexOf(key);
// If the response is not even part of the results array, something is super wrong
if (index == -1) {
ok(false, `Unexpected response from server (${response})`);
finishTest();
}
// take the element out the array and continue till the results array is empty
if (index != -1) {
testConfig.results.splice(index, 1);
}
// Check if the result was okay or had an error
is(result, 'ok', `Upgrade all requests on toplevel http for '${key}' came back with: '${result}'`)
// If we're not expecting any more resulsts, finish the test
if (testConfig.results.length == 0) {
finishTest();
}
}
SimpleTest.waitForExplicitFinish();
// Set preference and start test
SpecialPowers.pushPrefEnv({ set: [["dom.security.https_only_mode", true]] }, runTest);
</script>
</body>
</html>

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

@ -19,6 +19,7 @@ MOCHITEST_MANIFESTS += [
'cors/mochitest.ini',
'csp/mochitest.ini',
'general/mochitest.ini',
'https-only/mochitest.ini',
'mixedcontentblocker/mochitest.ini',
'referrer-policy/mochitest.ini',
'sri/mochitest.ini',

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

@ -19,6 +19,7 @@
#include "mozilla/dom/MessageEventBinding.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/SerializedStackHolder.h"
@ -26,6 +27,7 @@
#include "mozilla/dom/WorkerRef.h"
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/dom/WorkerScope.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsAutoPtr.h"
#include "mozilla/LoadInfo.h"
#include "nsGlobalWindow.h"
@ -1578,6 +1580,26 @@ 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());
mURI.ReplaceSubstring("ws://", "wss://");
if (NS_WARN_IF(mURI.Find("wss://") != 0)) {
return NS_OK;
}
mSecure = true;
params.AppendElement(NS_LITERAL_STRING("wss"));
nsHTTPSOnlyUtils::LogLocalizedString("HTTPSOnlyUpgradeInsecureRequest",
params, nsIScriptError::warningFlag,
mInnerWindowID, mPrivateBrowsing);
}
// Potentially the page uses the CSP directive 'upgrade-insecure-requests'.
// In such a case we have to upgrade ws: to wss: and also update mSecure
// to reflect that upgrade. Please note that we can not upgrade from ws:

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

@ -580,6 +580,7 @@ nsresult LoadInfoToLoadInfoArgs(nsILoadInfo* aLoadInfo,
aLoadInfo->GetDocumentHasLoaded(),
aLoadInfo->GetAllowListFutureDocumentsCreatedFromThisRedirectChain(),
cspNonce, aLoadInfo->GetSkipContentSniffing(),
aLoadInfo->GetHttpsOnlyNoUpgrade(),
aLoadInfo->GetIsFromProcessingFrameAttributes(), cookieJarSettingsArgs,
aLoadInfo->GetRequestBlockingReason(), maybeCspToInheritInfo));
@ -777,7 +778,8 @@ nsresult LoadInfoArgsToLoadInfo(
loadInfoArgs.documentHasLoaded(),
loadInfoArgs.allowListFutureDocumentsCreatedFromThisRedirectChain(),
loadInfoArgs.cspNonce(), loadInfoArgs.skipContentSniffing(),
loadInfoArgs.requestBlockingReason(), loadingContext);
loadInfoArgs.httpsOnlyNoUpgrade(), loadInfoArgs.requestBlockingReason(),
loadingContext);
if (loadInfoArgs.isFromProcessingFrameAttributes()) {
loadInfo->SetIsFromProcessingFrameAttributes();
@ -793,6 +795,7 @@ void LoadInfoToParentLoadInfoForwarder(
*aForwarderArgsOut = ParentLoadInfoForwarderArgs(
false, false, Nothing(), nsILoadInfo::TAINTING_BASIC,
false, // SkipContentSniffing
false, // HttpsOnlyNoUpgrade
false, // serviceWorkerTaintingSynthesized
false, // documentHasUserInteracted
false, // documentHasLoaded
@ -827,7 +830,7 @@ void LoadInfoToParentLoadInfoForwarder(
*aForwarderArgsOut = ParentLoadInfoForwarderArgs(
aLoadInfo->GetAllowInsecureRedirectToDataURI(),
aLoadInfo->GetBypassCORSChecks(), ipcController, tainting,
aLoadInfo->GetSkipContentSniffing(),
aLoadInfo->GetSkipContentSniffing(), aLoadInfo->GetHttpsOnlyNoUpgrade(),
aLoadInfo->GetServiceWorkerTaintingSynthesized(),
aLoadInfo->GetDocumentHasUserInteracted(),
aLoadInfo->GetDocumentHasLoaded(),
@ -866,6 +869,9 @@ nsresult MergeParentLoadInfoForwarder(
rv = aLoadInfo->SetSkipContentSniffing(aForwarderArgs.skipContentSniffing());
NS_ENSURE_SUCCESS(rv, rv);
rv = aLoadInfo->SetHttpsOnlyNoUpgrade(aForwarderArgs.httpsOnlyNoUpgrade());
NS_ENSURE_SUCCESS(rv, rv);
MOZ_ALWAYS_SUCCEEDS(aLoadInfo->SetDocumentHasUserInteracted(
aForwarderArgs.documentHasUserInteracted()));
MOZ_ALWAYS_SUCCEEDS(

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

@ -2406,6 +2406,13 @@
value: false
mirror: always
# If true, all content requests will get upgraded to HTTPS://
# (some Firefox functionality requests, like OCSP will not be affected)
- name: dom.security.https_only_mode
type: RelaxedAtomicBool
value: false
mirror: always
# Is support for selection event APIs enabled?
- name: dom.select_events.enabled
type: bool

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

@ -102,6 +102,7 @@ LoadInfo::LoadInfo(
mDocumentHasLoaded(false),
mAllowListFutureDocumentsCreatedFromThisRedirectChain(false),
mSkipContentSniffing(false),
mHttpsOnlyNoUpgrade(false),
mIsFromProcessingFrameAttributes(false) {
MOZ_ASSERT(mLoadingPrincipal);
MOZ_ASSERT(mTriggeringPrincipal);
@ -364,6 +365,7 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* aOuterWindow,
mDocumentHasLoaded(false),
mAllowListFutureDocumentsCreatedFromThisRedirectChain(false),
mSkipContentSniffing(false),
mHttpsOnlyNoUpgrade(false),
mIsFromProcessingFrameAttributes(false) {
// Top-level loads are never third-party
// Grab the information we can out of the window.
@ -464,6 +466,7 @@ LoadInfo::LoadInfo(dom::CanonicalBrowsingContext* aBrowsingContext,
mDocumentHasLoaded(false),
mAllowListFutureDocumentsCreatedFromThisRedirectChain(false),
mSkipContentSniffing(false),
mHttpsOnlyNoUpgrade(false),
mIsFromProcessingFrameAttributes(false) {
// Top-level loads are never third-party
// Grab the information we can out of the window.
@ -564,6 +567,7 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
rhs.mAllowListFutureDocumentsCreatedFromThisRedirectChain),
mCspNonce(rhs.mCspNonce),
mSkipContentSniffing(rhs.mSkipContentSniffing),
mHttpsOnlyNoUpgrade(rhs.mHttpsOnlyNoUpgrade),
mIsFromProcessingFrameAttributes(rhs.mIsFromProcessingFrameAttributes) {}
LoadInfo::LoadInfo(
@ -601,7 +605,8 @@ LoadInfo::LoadInfo(
bool aDocumentHasLoaded,
bool aAllowListFutureDocumentsCreatedFromThisRedirectChain,
const nsAString& aCspNonce, bool aSkipContentSniffing,
uint32_t aRequestBlockingReason, nsINode* aLoadingContext)
bool aHttpsOnlyNoUpgrade, uint32_t aRequestBlockingReason,
nsINode* aLoadingContext)
: mLoadingPrincipal(aLoadingPrincipal),
mTriggeringPrincipal(aTriggeringPrincipal),
mPrincipalToInherit(aPrincipalToInherit),
@ -657,6 +662,7 @@ LoadInfo::LoadInfo(
aAllowListFutureDocumentsCreatedFromThisRedirectChain),
mCspNonce(aCspNonce),
mSkipContentSniffing(aSkipContentSniffing),
mHttpsOnlyNoUpgrade(aHttpsOnlyNoUpgrade),
mIsFromProcessingFrameAttributes(false) {
// Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
MOZ_ASSERT(mLoadingPrincipal ||
@ -1462,6 +1468,18 @@ LoadInfo::SetSkipContentSniffing(bool aSkipContentSniffing) {
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::GetHttpsOnlyNoUpgrade(bool* aHttpsOnlyNoUpgrade) {
*aHttpsOnlyNoUpgrade = mHttpsOnlyNoUpgrade;
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::SetHttpsOnlyNoUpgrade(bool aHttpsOnlyNoUpgrade) {
mHttpsOnlyNoUpgrade = aHttpsOnlyNoUpgrade;
return NS_OK;
}
NS_IMETHODIMP
LoadInfo::GetIsTopLevelLoad(bool* aResult) {
*aResult = mFrameOuterWindowID ? mFrameOuterWindowID == mOuterWindowID

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

@ -163,7 +163,8 @@ class LoadInfo final : public nsILoadInfo {
bool aDocumentHasUserInteracted, bool aDocumentHasLoaded,
bool aAllowListFutureDocumentsCreatedFromThisRedirectChain,
const nsAString& aCspNonce, bool aSkipContentSniffing,
uint32_t aRequestBlockingReason, nsINode* aLoadingContext);
bool aHttpsOnlyNoUpgrade, uint32_t aRequestBlockingReason,
nsINode* aLoadingContext);
LoadInfo(const LoadInfo& rhs);
NS_IMETHOD GetRedirects(JSContext* aCx,
@ -258,6 +259,7 @@ class LoadInfo final : public nsILoadInfo {
bool mAllowListFutureDocumentsCreatedFromThisRedirectChain;
nsString mCspNonce;
bool mSkipContentSniffing;
bool mHttpsOnlyNoUpgrade;
// Is true if this load was triggered by processing the attributes of the
// browsing context container.

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

@ -206,6 +206,12 @@ static inline already_AddRefed<nsIChannel> SetupIPCheckChannel(bool ipv4) {
channel->SetTRRMode(nsIRequest::TRR_DISABLED_MODE);
{
// Prevent HTTPS-Only Mode from upgrading the OCSP request.
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
loadInfo->SetHttpsOnlyNoUpgrade(true);
}
NS_ENSURE_SUCCESS(rv, nullptr);
nsCOMPtr<nsIHttpChannelInternal> internalChan = do_QueryInterface(channel);

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

@ -407,7 +407,6 @@ interface nsILoadInfo : nsISupports
*/
[infallible] readonly attribute unsigned long securityMode;
/**
* This flag is used for any browsing context where we should not sniff
* the content type. E.g if an iframe has the XCTO nosniff header, then
@ -416,6 +415,12 @@ interface nsILoadInfo : nsISupports
*/
[infallible] attribute boolean skipContentSniffing;
/**
* If httpsOnlyNoUpgrade is true, the request won't get upgraded by the
* HTTPS-Only Mode.
*/
[infallible] attribute boolean httpsOnlyNoUpgrade;
/**
* True if this request is embedded in a context that can't be third-party
* (i.e. an iframe embedded in a cross-origin parent window). If this is

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

@ -69,6 +69,7 @@
#include "plstr.h"
#include "nsINestedURI.h"
#include "mozilla/dom/nsCSPUtils.h"
#include "mozilla/dom/nsHTTPSOnlyUtils.h"
#include "mozilla/dom/nsMixedContentBlocker.h"
#include "mozilla/dom/BlobURLProtocolHandler.h"
#include "mozilla/net/HttpBaseChannel.h"
@ -2831,6 +2832,12 @@ nsresult NS_ShouldSecureUpgrade(
if (!isHttps &&
!nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aURI)) {
if (aLoadInfo) {
// Check if the request can get upgraded with the HTTPS-Only mode
if (nsHTTPSOnlyUtils::ShouldUpgradeRequest(aURI, aLoadInfo)) {
aShouldUpgrade = true;
return NS_OK;
}
// If any of the documents up the chain to the root document makes use of
// the CSP directive 'upgrade-insecure-requests', then it's time to
// fulfill the promise to CSP and mixed content blocking to upgrade the

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

@ -144,6 +144,7 @@ struct LoadInfoArgs
bool allowListFutureDocumentsCreatedFromThisRedirectChain;
nsString cspNonce;
bool skipContentSniffing;
bool httpsOnlyNoUpgrade;
bool isFromProcessingFrameAttributes;
CookieJarSettingsArgs cookieJarSettings;
uint32_t requestBlockingReason;
@ -177,12 +178,15 @@ struct ParentLoadInfoForwarderArgs
// tainting value.
uint32_t tainting;
// This flag is used for any browsing context where we should not sniff
// the content type. E.g if an iframe has the XCTO nosniff header, then
// that flag is set to true so we skip content sniffing for that browsing
bool skipContentSniffing;
// If httpsOnlyNoUpgrade is true, the request won't get upgraded by the
// HTTPS-Only Mode.
bool httpsOnlyNoUpgrade;
// We must also note that the tainting value was explicitly set
// by the service worker.
bool serviceWorkerTaintingSynthesized;

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

@ -44,6 +44,7 @@
#include "nsIHttpHeaderVisitor.h"
#include "nsQueryObject.h"
#include "mozilla/StaticPrefs_network.h"
#include "mozilla/StaticPrefs_dom.h"
#include <algorithm>
using namespace mozilla;
@ -774,17 +775,17 @@ nsCORSListenerProxy::CheckListenerChain() {
return retargetableListener->CheckListenerChain();
}
// Please note that the CSP directive 'upgrade-insecure-requests' relies
// on the promise that channels get updated from http: to https: before
// the channel fetches any data from the netwerk. Such channels should
// not be blocked by CORS and marked as cross origin requests. E.g.:
// toplevel page: https://www.example.com loads
// xhr: http://www.example.com/foo which gets updated to
// https://www.example.com/foo
// Please note that the CSP directive 'upgrade-insecure-requests' and the
// HTTPS-Only Mode are relying on the promise that channels get updated from
// http: to https: before the channel fetches any data from the netwerk. Such
// channels should not be blocked by CORS and marked as cross origin requests.
// E.g.: toplevel page: https://www.example.com loads
// xhr: http://www.example.com/foo which gets updated to
// https://www.example.com/foo
// In such a case we should bail out of CORS and rely on the promise that
// nsHttpChannel::Connect() upgrades the request from http to https.
bool CheckUpgradeInsecureRequestsPreventsCORS(
nsIPrincipal* aRequestingPrincipal, nsIChannel* aChannel) {
bool CheckInsecureUpgradePreventsCORS(nsIPrincipal* aRequestingPrincipal,
nsIChannel* aChannel) {
nsCOMPtr<nsIURI> channelURI;
nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI));
NS_ENSURE_SUCCESS(rv, false);
@ -817,11 +818,7 @@ bool CheckUpgradeInsecureRequestsPreventsCORS(
return false;
}
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
// lets see if the loadInfo indicates that the request will
// be upgraded before fetching any data from the netwerk.
return loadInfo->GetUpgradeInsecureRequests() ||
loadInfo->GetBrowserUpgradeInsecureRequests();
return true;
}
nsresult nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel,
@ -879,16 +876,24 @@ nsresult nsCORSListenerProxy::UpdateChannel(nsIChannel* aChannel,
return NS_OK;
}
// if the CSP directive 'upgrade-insecure-requests' is used then we should
// not incorrectly require CORS if the only difference of a subresource
// request and the main page is the scheme.
// e.g. toplevel page: https://www.example.com loads
// xhr: http://www.example.com/somefoo,
// If the CSP directive 'upgrade-insecure-requests' is used or the HTTPS-Only
// Mode is enabled then we should not incorrectly require CORS if the only
// difference of a subresource request and the main page is the scheme. e.g.
// toplevel page: https://www.example.com loads
// xhr: http://www.example.com/somefoo,
// then the xhr request will be upgraded to https before it fetches any data
// from the netwerk, hence we shouldn't require CORS in that specific case.
if (CheckUpgradeInsecureRequestsPreventsCORS(mRequestingPrincipal,
aChannel)) {
return NS_OK;
if (CheckInsecureUpgradePreventsCORS(mRequestingPrincipal, aChannel)) {
// Check if HTTPS-Only Mode is enabled
if (!loadInfo->GetHttpsOnlyNoUpgrade() &&
StaticPrefs::dom_security_https_only_mode()) {
return NS_OK;
}
// Check if 'upgrade-insecure-requests' is used
if (loadInfo->GetUpgradeInsecureRequests() ||
loadInfo->GetBrowserUpgradeInsecureRequests()) {
return NS_OK;
}
}
// Check if we need to do a preflight, and if so set one up. This must be

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

@ -265,6 +265,11 @@ OCSPRequest::Run() {
nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
nsIChannel::LOAD_BYPASS_URL_CLASSIFIER);
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
// Prevent HTTPS-Only Mode from upgrading the OCSP request.
loadInfo->SetHttpsOnlyNoUpgrade(true);
// For OCSP requests, only the first party domain and private browsing id
// aspects of origin attributes are used. This means that:
// a) if first party isolation is enabled, OCSP requests will be isolated
@ -277,7 +282,6 @@ OCSPRequest::Run() {
attrs.mFirstPartyDomain = mOriginAttributes.mFirstPartyDomain;
attrs.mPrivateBrowsingId = mOriginAttributes.mPrivateBrowsingId;
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
rv = loadInfo->SetOriginAttributes(attrs);
if (NS_FAILED(rv)) {
return NotifyDone(rv, lock);

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

@ -37,6 +37,8 @@ function URLFetcher(url, timeout) {
xhr.channel.setTRRMode(Ci.nsIRequest.TRR_DISABLED_MODE);
// We except this from being classified
xhr.channel.loadFlags |= Ci.nsIChannel.LOAD_BYPASS_URL_CLASSIFIER;
// Prevent HTTPS-Only Mode from upgrading the request.
xhr.channel.loadInfo.httpsOnlyNoUpgrade = true;
// We don't want to follow _any_ redirects
xhr.channel.QueryInterface(Ci.nsIHttpChannel).redirectionLimit = 0;

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

@ -395,6 +395,9 @@ GMPAddon.prototype = {
get isEME() {
return this.id == "gmp-widevinecdm" || this.id.indexOf("gmp-eme-") == 0;
},
get isOpenH264() {
return this.id == "gmp-gmpopenh264";
},
/**
* @return true if the addon has been previously installed and this is
* a new version, if this is a fresh install return false
@ -475,31 +478,38 @@ GMPDownloader.prototype = {
type: "downloaderr",
});
}
return ProductAddonChecker.downloadAddon(gmpAddon).then(zipPath => {
let relativePath = OS.Path.join(gmpAddon.id, gmpAddon.version);
log.info("install to directory path: " + relativePath);
let gmpInstaller = new GMPExtractor(zipPath, relativePath);
let installPromise = gmpInstaller.install();
return installPromise.then(extractedPaths => {
// Success, set the prefs
let now = Math.round(Date.now() / 1000);
GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
// Remember our ABI, so that if the profile is migrated to another
// platform or from 32 -> 64 bit, we notice and don't try to load the
// unexecutable plugin library.
let abi = GMPUtils._expectedABI(gmpAddon);
log.info("Setting ABI to '" + abi + "' for " + gmpAddon.id);
GMPPrefs.setString(GMPPrefs.KEY_PLUGIN_ABI, abi, gmpAddon.id);
// Setting the version pref signals installation completion to consumers,
// if you need to set other prefs etc. do it before this.
GMPPrefs.setString(
GMPPrefs.KEY_PLUGIN_VERSION,
gmpAddon.version,
gmpAddon.id
);
return extractedPaths;
});
});
// If the HTTPS-Only Mode is enabled, every insecure request gets upgraded
// by default. This upgrade has to be prevented for openh264 downloads since
// the server doesn't support https://
const downloadOptions = {
httpsOnlyNoUpgrade: gmpAddon.isOpenH264,
};
return ProductAddonChecker.downloadAddon(gmpAddon, downloadOptions).then(
zipPath => {
let relativePath = OS.Path.join(gmpAddon.id, gmpAddon.version);
log.info("install to directory path: " + relativePath);
let gmpInstaller = new GMPExtractor(zipPath, relativePath);
let installPromise = gmpInstaller.install();
return installPromise.then(extractedPaths => {
// Success, set the prefs
let now = Math.round(Date.now() / 1000);
GMPPrefs.setInt(GMPPrefs.KEY_PLUGIN_LAST_UPDATE, now, gmpAddon.id);
// Remember our ABI, so that if the profile is migrated to another
// platform or from 32 -> 64 bit, we notice and don't try to load the
// unexecutable plugin library.
let abi = GMPUtils._expectedABI(gmpAddon);
log.info("Setting ABI to '" + abi + "' for " + gmpAddon.id);
GMPPrefs.setString(GMPPrefs.KEY_PLUGIN_ABI, abi, gmpAddon.id);
// Setting the version pref signals installation completion to consumers,
// if you need to set other prefs etc. do it before this.
GMPPrefs.setString(
GMPPrefs.KEY_PLUGIN_VERSION,
gmpAddon.version,
gmpAddon.id
);
return extractedPaths;
});
}
);
},
};

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

@ -309,10 +309,13 @@ function downloadLocalConfig() {
*
* @param url
* The url to download from.
* @param options (optional)
* @param options.httpsOnlyNoUpgrade
* Prevents upgrade to https:// when HTTPS-Only Mode is enabled.
* @return a promise that resolves to the path of a temporary file or rejects
* with a JS exception in case of error.
*/
function downloadFile(url) {
function downloadFile(url, options = { httpsOnlyNoUpgrade: false }) {
return new Promise((resolve, reject) => {
let xhr = new XMLHttpRequest();
xhr.onload = function(response) {
@ -352,6 +355,7 @@ function downloadFile(url) {
xhr.responseType = "arraybuffer";
try {
xhr.open("GET", url);
xhr.channel.loadInfo.httpsOnlyNoUpgrade = options.httpsOnlyNoUpgrade;
// Use conservative TLS settings. See bug 1325501.
// TODO move to ServiceRequest.
if (xhr.channel instanceof Ci.nsIHttpChannelInternal) {
@ -480,11 +484,14 @@ const ProductAddonChecker = {
*
* @param addon
* The addon to download.
* @param options (optional)
* @param options.httpsOnlyNoUpgrade
* Prevents upgrade to https:// when HTTPS-Only Mode is enabled.
* @return a promise that resolves to the temporary file downloaded or rejects
* with a JS exception in case of error.
*/
async downloadAddon(addon) {
let path = await downloadFile(addon.URL);
async downloadAddon(addon, options = { httpsOnlyNoUpgrade: false }) {
let path = await downloadFile(addon.URL, options);
try {
await verifyFile(addon, path);
return path;