Bug 1748005 - Getting Uncaught DOMException: The operation is insecure while opening Websocket using 'wss' protocol r=smaug

Use the 'IsSecure' field from windowContexts where possible to determine WebSockets mixed content behaviour.

Differential Revision: https://phabricator.services.mozilla.com/D153105
This commit is contained in:
Andrew Creskey
2022-08-26 12:23:36 +00:00
parent 2595d2f756
commit 30dd7074b0
12 changed files with 363 additions and 50 deletions

View File

@@ -149,9 +149,9 @@ class WebSocketImpl final : public nsIInterfaceRequestor,
bool IsTargetThread() const;
nsresult Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal,
nsIPrincipal* aPrincipal, bool aIsServerSide,
const nsAString& aURL, nsTArray<nsString>& aProtocolArray,
nsresult Init(JSContext* aCx, bool aIsSecure, nsIPrincipal* aPrincipal,
bool aIsServerSide, const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile, uint32_t aScriptLine,
uint32_t aScriptColumn);
@@ -194,7 +194,7 @@ class WebSocketImpl final : public nsIInterfaceRequestor,
nsresult CancelInternal();
nsresult GetLoadingPrincipal(nsIPrincipal** aPrincipal);
nsresult IsSecure(bool* aValue);
RefPtr<WebSocket> mWebSocket;
@@ -1116,10 +1116,10 @@ class InitRunnable final : public WebSocketMainThreadRunnable {
return true;
}
mErrorCode =
mImpl->Init(jsapi.cx(), mWorkerPrivate->GetPrincipal(),
doc->NodePrincipal(), mIsServerSide, mURL, mProtocolArray,
mScriptFile, mScriptLine, mScriptColumn);
mErrorCode = mImpl->Init(
jsapi.cx(), mWorkerPrivate->GetPrincipal()->SchemeIs("https"),
doc->NodePrincipal(), mIsServerSide, mURL, mProtocolArray, mScriptFile,
mScriptLine, mScriptColumn);
return true;
}
@@ -1128,7 +1128,7 @@ class InitRunnable final : public WebSocketMainThreadRunnable {
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mErrorCode =
mImpl->Init(nullptr, mWorkerPrivate->GetPrincipal(),
mImpl->Init(nullptr, mWorkerPrivate->GetPrincipal()->SchemeIs("https"),
aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide, mURL,
mProtocolArray, mScriptFile, mScriptLine, mScriptColumn);
return true;
@@ -1313,13 +1313,13 @@ already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
webSocket->GetOwner()->UpdateWebSocketCount(1);
}
nsCOMPtr<nsIPrincipal> loadingPrincipal;
aRv = webSocketImpl->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal));
bool isSecure = true;
aRv = webSocketImpl->IsSecure(&isSecure);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
aRv = webSocketImpl->Init(aGlobal.Context(), loadingPrincipal, principal,
aRv = webSocketImpl->Init(aGlobal.Context(), isSecure, principal,
!!aTransportProvider, aUrl, protocolArray, ""_ns,
0, 0);
@@ -1529,7 +1529,7 @@ void WebSocket::DisconnectFromOwner() {
// WebSocketImpl:: initialization
//-----------------------------------------------------------------------------
nsresult WebSocketImpl::Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal,
nsresult WebSocketImpl::Init(JSContext* aCx, bool aIsSecure,
nsIPrincipal* aPrincipal, bool aIsServerSide,
const nsAString& aURL,
nsTArray<nsString>& aProtocolArray,
@@ -1698,7 +1698,7 @@ nsresult WebSocketImpl::Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal,
false) &&
!nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
mAsciiHost)) {
if (aLoadingPrincipal->SchemeIs("https")) {
if (aIsSecure) {
return NS_ERROR_DOM_SECURITY_ERR;
}
}
@@ -2783,7 +2783,7 @@ void WebSocket::AssertIsOnTargetThread() const {
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
}
nsresult WebSocketImpl::GetLoadingPrincipal(nsIPrincipal** aPrincipal) {
nsresult WebSocketImpl::IsSecure(bool* aValue) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mIsMainThread);
@@ -2800,32 +2800,26 @@ nsresult WebSocketImpl::GetLoadingPrincipal(nsIPrincipal** aPrincipal) {
// If we are in a XPConnect sandbox or in a JS component,
// innerWindow will be null. There is nothing on top of this to be
// considered.
principal.forget(aPrincipal);
if (NS_WARN_IF(!principal)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
*aValue = principal->SchemeIs("https");
return NS_OK;
}
RefPtr<WindowContext> windowContext = innerWindow->GetWindowContext();
while (true) {
if (principal && !principal->GetIsNullPrincipal()) {
break;
}
if (NS_WARN_IF(!windowContext)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
while (true) {
if (windowContext->GetIsSecure()) {
*aValue = true;
return NS_OK;
}
if (windowContext->IsTop()) {
if (!windowContext->GetBrowsingContext()->HadOriginalOpener()) {
break;
}
// We are at the top. Let's see if we have an opener window.
RefPtr<BrowsingContext> opener =
windowContext->GetBrowsingContext()->GetOpener();
if (!opener) {
break;
}
windowContext = opener->GetCurrentWindowContext();
} else {
// If we're not a top window get the parent window context instead.
windowContext = windowContext->GetParentWindowContext();
@@ -2834,16 +2828,9 @@ nsresult WebSocketImpl::GetLoadingPrincipal(nsIPrincipal** aPrincipal) {
if (NS_WARN_IF(!windowContext)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<Document> document = windowContext->GetExtantDoc();
if (NS_WARN_IF(!document)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
principal = document->NodePrincipal();
}
principal.forget(aPrincipal);
*aValue = windowContext->GetIsSecure();
return NS_OK;
}

View File

@@ -0,0 +1,32 @@
<html><body>
Creating WebSocket
<script type="application/javascript">
onmessage = function(e) {
parent.postMessage(e.data, '*');
}
try{
let socket;
if (location.search == '?insecure') {
socket = new WebSocket('ws://mochi.test:8888/tests/dom/websocket/tests/file_websocket_hello');
}
else {
socket = new WebSocket('wss://example.com/tests/dom/websocket/tests/file_websocket_hello');
}
socket.onerror = function(e) {
parent.postMessage('WS onerror', '*');
};
socket.onopen = function(event) {
parent.postMessage('WS onopen', '*');
};
} catch(e) {
if (e.name == 'SecurityError') {
parent.postMessage('SecurityError', '*');
} else {
parent.postMessage('WS Throws something else!', '*');
}
close();
}
</script>
</body></html>

View File

@@ -28,8 +28,14 @@ skip-if = (os == "win" && processor == "aarch64") #bug 1535784
[test_websocket_basic.html]
[test_websocket_hello.html]
[test_websocket_permessage_deflate.html]
[test_webSocket_sandbox.html]
support-files = iframe_webSocket_sandbox.html
[test_websocket_sandbox.html]
support-files = iframe_websocket_sandbox.html
[test_websocket_mixed_content.html]
scheme=https
support-files = iframe_websocket_wss.html
[test_websocket_mixed_content_opener.html]
scheme=https
support-files = window_websocket_wss.html
[test_worker_websocket1.html]
support-files = websocket_worker1.js
[test_worker_websocket2.html]
@@ -46,10 +52,10 @@ support-files = websocket_basic_worker.js
support-files = websocket_worker_https.html websocket_https_worker.js
[test_worker_websocket_loadgroup.html]
support-files = websocket_loadgroup_worker.js
[test_webSocket_sharedWorker.html]
support-files = webSocket_sharedWorker.js
[test_websocket_sharedWorker.html]
support-files = websocket_sharedWorker.js
[test_websocket_bigBlob.html]
support-files = file_websocket_bigBlob_wsh.py
[test_webSocket_longString.html]
[test_webSocket_no_duplicate_packet.html]
[test_websocket_longString.html]
[test_websocket_no_duplicate_packet.html]
scheme = https

View File

@@ -0,0 +1,96 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
<title>WebSocket mixed content tests - load secure and insecure websockets</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="websocket_helpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="testWebSockets()">
<div id="container"></div>
<iframe id="frame" sandbox="allow-scripts"></iframe>
<script class="testbody" type="text/javascript">
let iFrameTests = [testSameOriginSandboxInsecure, testSameOriginSandboxSecure, testCrossOriginSandboxInsecure, testCrossOriginSandboxSecure];
function nextIFrameTest() {
if(iFrameTests.length == 0) {
SimpleTest.finish();
}
else {
let test = iFrameTests.shift();
test();
}
}
async function testWebSockets () {
SimpleTest.waitForExplicitFinish();
testWebSocketSecure();
testWebSocketInsecure();
nextIFrameTest();
}
function testWebSocketSecure () {
var ws = CreateTestWS("wss://example.com/tests/dom/websocket/tests/file_websocket_hello");
ws.onopen = function(e) {
ws.send("data");
}
ws.onmessage = function(e) {
is(e.data, "Hello world!", "Wrong data");
ws.close();
}
}
// Negative test: this should fail as the page was loaded over https
function testWebSocketInsecure () {
try {
ws = new WebSocket("ws://mochi.test:8888/tests/dom/websocket/tests/file_websocket_hello");
ok(false, "Should throw DOMException");
} catch (e) {
ok(e instanceof DOMException, "DOMException thrown ");
}
}
// Negative test: this should fail as the page was loaded over https
function testSameOriginSandboxInsecure() {
document.getElementById("frame").src = "https://example.com/tests/dom/websocket/tests/iframe_websocket_wss.html?insecure";
onmessage = function(e) {
is(e.data, "SecurityError", "ws://URI cannot be used when loaded over https");
nextIFrameTest();
}
}
function testSameOriginSandboxSecure() {
document.getElementById("frame").src = "https://example.com/tests/dom/websocket/tests/iframe_websocket_wss.html"
onmessage = function(e) {
is(e.data, "WS onopen", "wss://URI opened");
nextIFrameTest();
}
}
// Negative test: this should fail as the page was loaded over https
function testCrossOriginSandboxInsecure() {
document.getElementById("frame").src = "https://example.org/tests/dom/websocket/tests/iframe_websocket_wss.html?insecure";
onmessage = function(e) {
is(e.data, "SecurityError", "ws://URI cannot be used when loaded over https");
nextIFrameTest();
}
}
function testCrossOriginSandboxSecure() {
document.getElementById("frame").src = "https://example.org/tests/dom/websocket/tests/iframe_websocket_wss.html"
onmessage = function(e) {
is(e.data, "WS onopen", "wss://URI opened");
nextIFrameTest();
}
}
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@@ -0,0 +1,139 @@
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8"></meta>
<title>WebSocket mixed content opener tests - load secure and insecure websockets in secure and insecure iframes through secure and insecure opened windows</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script type="text/javascript" src="websocket_helpers.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body onload="testWebSockets()">
<div id="container"></div>
<script class="testbody" type="text/javascript">
let tests = [ testSecureWindowWSS, testSecureWindowWS, testInsecureWindowWSS, testInsecureWindowWS,
testSecureWindowSecureIframeWSS, testSecureWindowSecureIframeWS, testSecureWindowInsecureIframeWSS, testSecureWindowInsecureIframeWS,
testInsecureWindowSecureIframeWSS, testInsecureWindowSecureIframeWS, testInsecureWindowInsecureIframeWSS, testInsecureWindowInsecureIframeWS]
function nextTest() {
if(tests.length == 0) {
SimpleTest.finish();
}
else {
let test = tests.shift();
test();
}
}
function testWebSockets () {
SimpleTest.waitForExplicitFinish();
nextTest();
}
function cleanupAndLaunchNextTest(win) {
win.close();
nextTest();
}
function testSecureWindowWSS() {
let win = window.open("https://example.com/tests/dom/websocket/tests/window_websocket_wss.html", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "WS onopen", "wss://URI opened");
cleanupAndLaunchNextTest(win);
}
}
function testSecureWindowWS() {
let win = window.open("https://example.com/tests/dom/websocket/tests/window_websocket_wss.html?insecure", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "SecurityError", "ws://URI cannot be used when loaded over https");
cleanupAndLaunchNextTest(win);
}
}
function testInsecureWindowWSS() {
let win = window.open("http://example.com/tests/dom/websocket/tests/window_websocket_wss.html", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "WS onopen", "wss://URI opened");
cleanupAndLaunchNextTest(win);
}
}
function testInsecureWindowWS() {
let win = window.open("http://example.com/tests/dom/websocket/tests/window_websocket_wss.html?insecure", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "WS onopen", "ws://URI opened");
cleanupAndLaunchNextTest(win);
}
}
function testSecureWindowSecureIframeWSS() {
let win = window.open("https://example.com/tests/dom/websocket/tests/window_websocket_wss.html?https_iframe_wss", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "WS onopen", "ws://URI opened");
cleanupAndLaunchNextTest(win);
}
}
function testSecureWindowSecureIframeWS() {
let win = window.open("https://example.com/tests/dom/websocket/tests/window_websocket_wss.html?https_iframe_ws", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "SecurityError", "ws://URI cannot be used when loaded over https");
cleanupAndLaunchNextTest(win);
}
}
function testSecureWindowInsecureIframeWSS() {
let win = window.open("https://example.com/tests/dom/websocket/tests/window_websocket_wss.html?http_iframe_wss", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "Error - iframe not loaded", "http iframe cannot be loaded in secure context (mixed content)");
cleanupAndLaunchNextTest(win);
}
}
function testSecureWindowInsecureIframeWS() {
let win = window.open("https://example.com/tests/dom/websocket/tests/window_websocket_wss.html?http_iframe_ws", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "Error - iframe not loaded", "http iframe cannot be loaded in secure context (mixed content)");
cleanupAndLaunchNextTest(win);
}
}
function testInsecureWindowSecureIframeWSS() {
let win = window.open("http://example.com/tests/dom/websocket/tests/window_websocket_wss.html?https_iframe_wss", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "WS onopen", "ws://URI opened");
cleanupAndLaunchNextTest(win);
}
}
function testInsecureWindowSecureIframeWS() {
let win = window.open("http://example.com/tests/dom/websocket/tests/window_websocket_wss.html?https_iframe_ws", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "SecurityError", "ws://URI cannot be used when loaded from an https iframe");
cleanupAndLaunchNextTest(win);
}
}
function testInsecureWindowInsecureIframeWSS() {
let win = window.open("http://example.com/tests/dom/websocket/tests/window_websocket_wss.html?http_iframe_wss", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "WS onopen", "ws://URI opened");
cleanupAndLaunchNextTest(win);
}
}
function testInsecureWindowInsecureIframeWS() {
let win = window.open("http://example.com/tests/dom/websocket/tests/window_websocket_wss.html?http_iframe_ws", '_blank', 'location=yes,height=570,width=520,scrollbars=yes,status=yes');
onmessage = function(e) {
is(e.data, "WS onopen", "ws://URI opened");
cleanupAndLaunchNextTest(win);
}
}
SimpleTest.waitForExplicitFinish();
</script>
</body>
</html>

View File

@@ -9,9 +9,9 @@
<div id="container"></div>
<iframe id="frame"></iframe>
<script type="application/javascript">
var urls = [ "https://example.com/tests/dom/websocket/tests/iframe_webSocket_sandbox.html",
"https://example.com/tests/dom/websocket/tests/iframe_webSocket_sandbox.html?nested",
"https://example.com/tests/dom/websocket/tests/iframe_webSocket_sandbox.html?popup" ];
var urls = [ "https://example.com/tests/dom/websocket/tests/iframe_websocket_sandbox.html",
"https://example.com/tests/dom/websocket/tests/iframe_websocket_sandbox.html?nested",
"https://example.com/tests/dom/websocket/tests/iframe_websocket_sandbox.html?popup" ];
onmessage = function(e) {
is(e.data, "WS Throws!", "ws://URI cannot be used by a https iframe");

View File

@@ -13,7 +13,7 @@
<script class="testbody" type="text/javascript">
var sw = new SharedWorker('webSocket_sharedWorker.js');
var sw = new SharedWorker('websocket_sharedWorker.js');
sw.port.onmessage = function(event) {
if (event.data.type == 'finish') {
SimpleTest.finish();

View File

@@ -0,0 +1,53 @@
<html><body>
Creating WebSocket
<iframe id="frame" sandbox="allow-scripts"></iframe>
<script type="application/javascript">
onmessage = function(e) {
window.opener.postMessage(e.data, '*');
}
// Mixed content blocker will prevent loading iframes via http, so in that case pass back the error
window.document.getElementById("frame").onerror = function(e) {
window.opener.postMessage("Error - iframe not loaded", '*');
}
// Load one of the iframe variants?
if (location.search == '?https_iframe_wss') {
window.document.getElementById("frame").src = "https://example.com/tests/dom/websocket/tests/iframe_websocket_wss.html";
} else if (location.search == '?https_iframe_ws') {
window.document.getElementById("frame").src = "https://example.com/tests/dom/websocket/tests/iframe_websocket_wss.html?insecure";
} else if (location.search == '?http_iframe_wss' || location.search == '?http_iframe_ws') {
let iFrameUrl = "http://example.com/tests/dom/websocket/tests/iframe_websocket_wss.html";
if (location.search == '?http_iframe_ws') {
iFrameUrl += "?insecure";
}
window.document.getElementById("frame").src = iFrameUrl;
}
else {
try {
let socket;
if (location.search == '?insecure') {
socket = new WebSocket('ws://mochi.test:8888/tests/dom/websocket/tests/file_websocket_hello');
}
else {
socket = new WebSocket('wss://example.com/tests/dom/websocket/tests/file_websocket_hello');
}
socket.onerror = function(e) {
window.opener.postMessage("WS onerror", "*");
};
socket.onopen = function(event) {
window.opener.postMessage("WS onopen", "*");
};
}
catch(e) {
if (e.name == 'SecurityError') {
window.opener.postMessage("SecurityError", "*");
} else {
window.opener.postMessage("Test Throws", "*");
}
}
}
</script>
</body></html>