Skip to content

Commit

Permalink
Bug 1620242 - Basic implementation for HTTPS Only Mode. r=ckerschb,mi…
Browse files Browse the repository at this point in the history
…xedpuppy

Differential Revision: https://phabricator.services.mozilla.com/D62590
  • Loading branch information
JulianGaibler committed Mar 17, 2020
1 parent b640ef2 commit ae075b2
Show file tree
Hide file tree
Showing 29 changed files with 796 additions and 58 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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/**",
Expand Down
6 changes: 6 additions & 0 deletions dom/locales/en-US/chrome/security/security.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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.
1 change: 1 addition & 0 deletions dom/security/CSPEvalChecker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "mozilla/dom/WorkerRunnable.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ErrorResult.h"
#include "nsIParentChannel.h"
#include "nsGlobalWindowInner.h"
#include "nsContentSecurityUtils.h"
#include "nsContentUtils.h"
Expand Down
2 changes: 2 additions & 0 deletions dom/security/moz.build
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ EXPORTS.mozilla.dom += [
'nsCSPContext.h',
'nsCSPService.h',
'nsCSPUtils.h',
'nsHTTPSOnlyUtils.h',
'nsMixedContentBlocker.h',
'PolicyTokenizer.h',
'ReferrerInfo.h',
Expand Down Expand Up @@ -48,6 +49,7 @@ UNIFIED_SOURCES += [
'nsCSPParser.cpp',
'nsCSPService.cpp',
'nsCSPUtils.cpp',
'nsHTTPSOnlyUtils.cpp',
'nsMixedContentBlocker.cpp',
'PolicyTokenizer.cpp',
'ReferrerInfo.cpp',
Expand Down
110 changes: 110 additions & 0 deletions dom/security/nsHTTPSOnlyUtils.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
64 changes: 64 additions & 0 deletions dom/security/nsHTTPSOnlyUtils.h
Original file line number Diff line number Diff line change
@@ -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___ */
9 changes: 9 additions & 0 deletions dom/security/nsMixedContentBlocker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include "nsIParentChannel.h"
#include "mozilla/Preferences.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIProtocolHandler.h"
#include "nsCharSeparatedTokenizer.h"
#include "nsISecureBrowserUI.h"
#include "nsIWebNavigation.h"
#include "nsLoadGroup.h"
Expand All @@ -40,6 +42,7 @@
#include "mozilla/net/DNS.h"

using namespace mozilla;
using namespace mozilla::dom;

enum nsMixedContentBlockerMessageType { eBlocked = 0x00, eUserOverride = 0x01 };

Expand Down Expand Up @@ -812,6 +815,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
Expand Down
37 changes: 37 additions & 0 deletions dom/security/test/https-only/file_redirect.sjs
Original file line number Diff line number Diff line change
@@ -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);
}
89 changes: 89 additions & 0 deletions dom/security/test/https-only/file_upgrade_insecure.html
Original file line number Diff line number Diff line change
@@ -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>
Loading

0 comments on commit ae075b2

Please sign in to comment.