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; bool IsTargetThread() const;
nsresult Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal, nsresult Init(JSContext* aCx, bool aIsSecure, nsIPrincipal* aPrincipal,
nsIPrincipal* aPrincipal, bool aIsServerSide, bool aIsServerSide, const nsAString& aURL,
const nsAString& aURL, nsTArray<nsString>& aProtocolArray, nsTArray<nsString>& aProtocolArray,
const nsACString& aScriptFile, uint32_t aScriptLine, const nsACString& aScriptFile, uint32_t aScriptLine,
uint32_t aScriptColumn); uint32_t aScriptColumn);
@@ -194,7 +194,7 @@ class WebSocketImpl final : public nsIInterfaceRequestor,
nsresult CancelInternal(); nsresult CancelInternal();
nsresult GetLoadingPrincipal(nsIPrincipal** aPrincipal); nsresult IsSecure(bool* aValue);
RefPtr<WebSocket> mWebSocket; RefPtr<WebSocket> mWebSocket;
@@ -1116,10 +1116,10 @@ class InitRunnable final : public WebSocketMainThreadRunnable {
return true; return true;
} }
mErrorCode = mErrorCode = mImpl->Init(
mImpl->Init(jsapi.cx(), mWorkerPrivate->GetPrincipal(), jsapi.cx(), mWorkerPrivate->GetPrincipal()->SchemeIs("https"),
doc->NodePrincipal(), mIsServerSide, mURL, mProtocolArray, doc->NodePrincipal(), mIsServerSide, mURL, mProtocolArray, mScriptFile,
mScriptFile, mScriptLine, mScriptColumn); mScriptLine, mScriptColumn);
return true; return true;
} }
@@ -1128,7 +1128,7 @@ class InitRunnable final : public WebSocketMainThreadRunnable {
MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow()); MOZ_ASSERT(aTopLevelWorkerPrivate && !aTopLevelWorkerPrivate->GetWindow());
mErrorCode = mErrorCode =
mImpl->Init(nullptr, mWorkerPrivate->GetPrincipal(), mImpl->Init(nullptr, mWorkerPrivate->GetPrincipal()->SchemeIs("https"),
aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide, mURL, aTopLevelWorkerPrivate->GetPrincipal(), mIsServerSide, mURL,
mProtocolArray, mScriptFile, mScriptLine, mScriptColumn); mProtocolArray, mScriptFile, mScriptLine, mScriptColumn);
return true; return true;
@@ -1313,13 +1313,13 @@ already_AddRefed<WebSocket> WebSocket::ConstructorCommon(
webSocket->GetOwner()->UpdateWebSocketCount(1); webSocket->GetOwner()->UpdateWebSocketCount(1);
} }
nsCOMPtr<nsIPrincipal> loadingPrincipal; bool isSecure = true;
aRv = webSocketImpl->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal)); aRv = webSocketImpl->IsSecure(&isSecure);
if (NS_WARN_IF(aRv.Failed())) { if (NS_WARN_IF(aRv.Failed())) {
return nullptr; return nullptr;
} }
aRv = webSocketImpl->Init(aGlobal.Context(), loadingPrincipal, principal, aRv = webSocketImpl->Init(aGlobal.Context(), isSecure, principal,
!!aTransportProvider, aUrl, protocolArray, ""_ns, !!aTransportProvider, aUrl, protocolArray, ""_ns,
0, 0); 0, 0);
@@ -1529,7 +1529,7 @@ void WebSocket::DisconnectFromOwner() {
// WebSocketImpl:: initialization // WebSocketImpl:: initialization
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
nsresult WebSocketImpl::Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal, nsresult WebSocketImpl::Init(JSContext* aCx, bool aIsSecure,
nsIPrincipal* aPrincipal, bool aIsServerSide, nsIPrincipal* aPrincipal, bool aIsServerSide,
const nsAString& aURL, const nsAString& aURL,
nsTArray<nsString>& aProtocolArray, nsTArray<nsString>& aProtocolArray,
@@ -1698,7 +1698,7 @@ nsresult WebSocketImpl::Init(JSContext* aCx, nsIPrincipal* aLoadingPrincipal,
false) && false) &&
!nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost( !nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackHost(
mAsciiHost)) { mAsciiHost)) {
if (aLoadingPrincipal->SchemeIs("https")) { if (aIsSecure) {
return NS_ERROR_DOM_SECURITY_ERR; return NS_ERROR_DOM_SECURITY_ERR;
} }
} }
@@ -2783,7 +2783,7 @@ void WebSocket::AssertIsOnTargetThread() const {
MOZ_ASSERT(NS_IsMainThread() == mIsMainThread); MOZ_ASSERT(NS_IsMainThread() == mIsMainThread);
} }
nsresult WebSocketImpl::GetLoadingPrincipal(nsIPrincipal** aPrincipal) { nsresult WebSocketImpl::IsSecure(bool* aValue) {
MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mIsMainThread); MOZ_ASSERT(mIsMainThread);
@@ -2800,32 +2800,26 @@ nsresult WebSocketImpl::GetLoadingPrincipal(nsIPrincipal** aPrincipal) {
// If we are in a XPConnect sandbox or in a JS component, // 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 // innerWindow will be null. There is nothing on top of this to be
// considered. // considered.
principal.forget(aPrincipal); if (NS_WARN_IF(!principal)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
*aValue = principal->SchemeIs("https");
return NS_OK; return NS_OK;
} }
RefPtr<WindowContext> windowContext = innerWindow->GetWindowContext(); RefPtr<WindowContext> windowContext = innerWindow->GetWindowContext();
if (NS_WARN_IF(!windowContext)) {
return NS_ERROR_DOM_SECURITY_ERR;
}
while (true) { while (true) {
if (principal && !principal->GetIsNullPrincipal()) { if (windowContext->GetIsSecure()) {
break; *aValue = true;
} return NS_OK;
if (NS_WARN_IF(!windowContext)) {
return NS_ERROR_DOM_SECURITY_ERR;
} }
if (windowContext->IsTop()) { if (windowContext->IsTop()) {
if (!windowContext->GetBrowsingContext()->HadOriginalOpener()) { break;
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 { } else {
// If we're not a top window get the parent window context instead. // If we're not a top window get the parent window context instead.
windowContext = windowContext->GetParentWindowContext(); windowContext = windowContext->GetParentWindowContext();
@@ -2834,16 +2828,9 @@ nsresult WebSocketImpl::GetLoadingPrincipal(nsIPrincipal** aPrincipal) {
if (NS_WARN_IF(!windowContext)) { if (NS_WARN_IF(!windowContext)) {
return NS_ERROR_DOM_SECURITY_ERR; 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; 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_basic.html]
[test_websocket_hello.html] [test_websocket_hello.html]
[test_websocket_permessage_deflate.html] [test_websocket_permessage_deflate.html]
[test_webSocket_sandbox.html] [test_websocket_sandbox.html]
support-files = iframe_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] [test_worker_websocket1.html]
support-files = websocket_worker1.js support-files = websocket_worker1.js
[test_worker_websocket2.html] [test_worker_websocket2.html]
@@ -46,10 +52,10 @@ support-files = websocket_basic_worker.js
support-files = websocket_worker_https.html websocket_https_worker.js support-files = websocket_worker_https.html websocket_https_worker.js
[test_worker_websocket_loadgroup.html] [test_worker_websocket_loadgroup.html]
support-files = websocket_loadgroup_worker.js support-files = websocket_loadgroup_worker.js
[test_webSocket_sharedWorker.html] [test_websocket_sharedWorker.html]
support-files = webSocket_sharedWorker.js support-files = websocket_sharedWorker.js
[test_websocket_bigBlob.html] [test_websocket_bigBlob.html]
support-files = file_websocket_bigBlob_wsh.py support-files = file_websocket_bigBlob_wsh.py
[test_webSocket_longString.html] [test_websocket_longString.html]
[test_webSocket_no_duplicate_packet.html] [test_websocket_no_duplicate_packet.html]
scheme = https 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> <div id="container"></div>
<iframe id="frame"></iframe> <iframe id="frame"></iframe>
<script type="application/javascript"> <script type="application/javascript">
var urls = [ "https://example.com/tests/dom/websocket/tests/iframe_webSocket_sandbox.html", 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?nested",
"https://example.com/tests/dom/websocket/tests/iframe_webSocket_sandbox.html?popup" ]; "https://example.com/tests/dom/websocket/tests/iframe_websocket_sandbox.html?popup" ];
onmessage = function(e) { onmessage = function(e) {
is(e.data, "WS Throws!", "ws://URI cannot be used by a https iframe"); 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"> <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) { sw.port.onmessage = function(event) {
if (event.data.type == 'finish') { if (event.data.type == 'finish') {
SimpleTest.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>