mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-27 04:38:02 +00:00
Bug 1407789 - Prohibit cross-site iframes for Credential Management r=baku,keeler,ttaubert
Credential Management defines a parameter `sameOriginWithAncestors` which is set true if the responsible document is not either in a top-level browsing context, or is in a nested context whose heirarchy is all loaded from the same origin as the top-level context [1][2]. The individual credential types of CredMan can use this flag to make decisions on whether to error or not. Our Credential Management implementation right now is a shim to Web Authentication, which says that if `sameOriginWithAncestors` is false, return `"NotAllowedError"`. This ensures that https://webauthn.bin.coffee/iframe.html works, but the cross-origin https://u2f.bin.coffee/iframe-webauthn.html does not. [1] https://w3c.github.io/webappsec-credential-management/#algorithm-request [2] https://w3c.github.io/webappsec-credential-management/#algorithm-create [3] https://w3c.github.io/webauthn/#createCredential [4] https://w3c.github.io/webauthn/#getAssertion MozReview-Commit-ID: KIyakgl0kGv --HG-- extra : rebase_source : dace4f4d73823913bff759fce8255da8e18ad5e3
This commit is contained in:
parent
102f4d3a4b
commit
bce88244c0
@ -7,6 +7,7 @@
|
||||
#include "mozilla/dom/CredentialsContainer.h"
|
||||
#include "mozilla/dom/Promise.h"
|
||||
#include "mozilla/dom/WebAuthnManager.h"
|
||||
#include "nsContentUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
@ -19,6 +20,63 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CredentialsContainer)
|
||||
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
||||
NS_INTERFACE_MAP_END
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CreateAndReject(nsPIDOMWindowInner* aParent, ErrorResult& aRv)
|
||||
{
|
||||
MOZ_ASSERT(aParent);
|
||||
|
||||
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
|
||||
if (NS_WARN_IF(!global)) {
|
||||
aRv.Throw(NS_ERROR_FAILURE);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefPtr<Promise> promise = Promise::Create(global, aRv);
|
||||
if (NS_WARN_IF(aRv.Failed())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
|
||||
return promise.forget();
|
||||
}
|
||||
|
||||
bool
|
||||
IsSameOriginWithAncestors(nsPIDOMWindowInner* aParent)
|
||||
{
|
||||
// This method returns true if aParent is either not in a frame / iframe, or
|
||||
// is in a frame or iframe and all ancestors for aParent are the same origin.
|
||||
// This is useful for Credential Management because we need to prohibit
|
||||
// iframes, but not break mochitests (which use iframes to embed the tests).
|
||||
MOZ_ASSERT(aParent);
|
||||
|
||||
if (aParent->IsTopInnerWindow()) {
|
||||
// Not in a frame or iframe
|
||||
return true;
|
||||
}
|
||||
|
||||
// We're in some kind of frame, so let's get the parent and start checking
|
||||
// the same origin policy
|
||||
nsINode* node = nsContentUtils::GetCrossDocParentNode(aParent->GetExtantDoc());
|
||||
if (NS_WARN_IF(!node)) {
|
||||
// This is a sanity check, since there has to be a parent. Fail safe.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that all ancestors are the same origin, repeating until we find a
|
||||
// null parent
|
||||
do {
|
||||
nsresult rv = nsContentUtils::CheckSameOrigin(aParent->GetExtantDoc(), node);
|
||||
if (NS_FAILED(rv)) {
|
||||
// same-origin policy is violated
|
||||
return false;
|
||||
}
|
||||
|
||||
node = nsContentUtils::GetCrossDocParentNode(node);
|
||||
} while (node);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
CredentialsContainer::CredentialsContainer(nsPIDOMWindowInner* aParent) :
|
||||
mParent(aParent)
|
||||
{
|
||||
@ -45,22 +103,42 @@ CredentialsContainer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenPro
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CredentialsContainer::Get(const CredentialRequestOptions& aOptions)
|
||||
CredentialsContainer::Get(const CredentialRequestOptions& aOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (!IsSameOriginWithAncestors(mParent)) {
|
||||
return CreateAndReject(mParent, aRv);
|
||||
}
|
||||
|
||||
// TODO: Check that we're an active document, too. See bug 1409202.
|
||||
|
||||
EnsureWebAuthnManager();
|
||||
return mManager->GetAssertion(aOptions.mPublicKey, aOptions.mSignal);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CredentialsContainer::Create(const CredentialCreationOptions& aOptions)
|
||||
CredentialsContainer::Create(const CredentialCreationOptions& aOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
if (!IsSameOriginWithAncestors(mParent)) {
|
||||
return CreateAndReject(mParent, aRv);
|
||||
}
|
||||
|
||||
// TODO: Check that we're an active document, too. See bug 1409202.
|
||||
|
||||
EnsureWebAuthnManager();
|
||||
return mManager->MakeCredential(aOptions.mPublicKey, aOptions.mSignal);
|
||||
}
|
||||
|
||||
already_AddRefed<Promise>
|
||||
CredentialsContainer::Store(const Credential& aCredential)
|
||||
CredentialsContainer::Store(const Credential& aCredential, ErrorResult& aRv)
|
||||
{
|
||||
if (!IsSameOriginWithAncestors(mParent)) {
|
||||
return CreateAndReject(mParent, aRv);
|
||||
}
|
||||
|
||||
// TODO: Check that we're an active document, too. See bug 1409202.
|
||||
|
||||
EnsureWebAuthnManager();
|
||||
return mManager->Store(aCredential);
|
||||
}
|
||||
|
@ -33,13 +33,13 @@ public:
|
||||
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Get(const CredentialRequestOptions& aOptions);
|
||||
Get(const CredentialRequestOptions& aOptions, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Create(const CredentialCreationOptions& aOptions);
|
||||
Create(const CredentialCreationOptions& aOptions, ErrorResult& aRv);
|
||||
|
||||
already_AddRefed<Promise>
|
||||
Store(const Credential& aCredential);
|
||||
Store(const Credential& aCredential, ErrorResult& aRv);
|
||||
|
||||
private:
|
||||
~CredentialsContainer();
|
||||
|
@ -20,3 +20,5 @@ UNIFIED_SOURCES += [
|
||||
include('/ipc/chromium/chromium-config.mozbuild')
|
||||
|
||||
FINAL_LIBRARY = 'xul'
|
||||
|
||||
MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
|
||||
|
10
dom/credentialmanagement/tests/.eslintrc.js
Normal file
10
dom/credentialmanagement/tests/.eslintrc.js
Normal file
@ -0,0 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
module.exports = {
|
||||
"extends": [
|
||||
"plugin:mozilla/mochitest-test",
|
||||
],
|
||||
"plugins": [
|
||||
"mozilla"
|
||||
]
|
||||
};
|
@ -0,0 +1,87 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Embedded Frame for Credential Management: Prohibit use in cross-origin iframes</title>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<meta charset=utf-8>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
const cose_alg_ECDSA_w_SHA256 = -7;
|
||||
var _parentOrigin = "https://example.com/";
|
||||
|
||||
function log(msg) {
|
||||
console.log(msg);
|
||||
let logBox = document.getElementById("log");
|
||||
if (logBox) {
|
||||
logBox.textContent += "\n" + msg;
|
||||
}
|
||||
}
|
||||
|
||||
function local_finished() {
|
||||
parent.postMessage({"done": true}, _parentOrigin);
|
||||
log("Done.");
|
||||
}
|
||||
|
||||
function local_ok(expression, message) {
|
||||
let body = {"test": expression, "status": expression, "msg": message};
|
||||
parent.postMessage(body, _parentOrigin);
|
||||
log(expression + ": " + message);
|
||||
}
|
||||
|
||||
function testSameOrigin() {
|
||||
log("Same origin: " + document.domain);
|
||||
|
||||
navigator.credentials.create({publicKey: makeCredentialOptions})
|
||||
.then(function sameOriginCreateThen(aResult) {
|
||||
local_ok(aResult != undefined, "Create worked " + aResult);
|
||||
})
|
||||
.catch(function sameOriginCatch(aResult) {
|
||||
local_ok(false, "Should not have failed " + aResult);
|
||||
})
|
||||
.then(function() {
|
||||
local_finished();
|
||||
});
|
||||
}
|
||||
|
||||
function testCrossOrigin() {
|
||||
log("Cross-origin: " + document.domain);
|
||||
|
||||
navigator.credentials.create({publicKey: makeCredentialOptions})
|
||||
.then(function crossOriginThen(aBad) {
|
||||
local_ok(false, "Should not have succeeded " + aBad);
|
||||
})
|
||||
.catch(function crossOriginCatch(aResult) {
|
||||
local_ok(aResult.toString().startsWith("NotAllowedError"),
|
||||
"Expecting a NotAllowedError, received " + aResult);
|
||||
})
|
||||
.then(function() {
|
||||
local_finished();
|
||||
});
|
||||
}
|
||||
|
||||
let rp = {id: document.domain, name: "none", icon: "none"};
|
||||
let user = {
|
||||
id: crypto.getRandomValues(new Uint8Array(16)),
|
||||
name: "none", icon: "none", displayName: "none"
|
||||
};
|
||||
let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
|
||||
let makeCredentialOptions = {
|
||||
rp, user, challenge: new Uint8Array(), pubKeyCredParams: [param]
|
||||
};
|
||||
|
||||
if (document.domain == "example.com") {
|
||||
testSameOrigin();
|
||||
} else {
|
||||
testCrossOrigin();
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<div id="log"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
7
dom/credentialmanagement/tests/mochitest/mochitest.ini
Normal file
7
dom/credentialmanagement/tests/mochitest/mochitest.ini
Normal file
@ -0,0 +1,7 @@
|
||||
[DEFAULT]
|
||||
support-files =
|
||||
frame_credman_iframes.html
|
||||
scheme = https
|
||||
skip-if = !e10s
|
||||
|
||||
[test_credman_iframes.html]
|
@ -0,0 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<head>
|
||||
<title>Credential Management: Prohibit use in cross-origin iframes</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<meta charset=utf-8>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Credential Management: Prohibit use in cross-origin iframes</h1>
|
||||
<ul>
|
||||
<li><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1407789">Mozilla Bug 1407789</a></li>
|
||||
</ul>
|
||||
|
||||
<div id="framediv">
|
||||
<h2>Same Origin Test</h2>
|
||||
<iframe id="frame_top"></iframe>
|
||||
|
||||
<h2>Cross-Origin Test</h2>
|
||||
<iframe id="frame_bottom"></iframe>
|
||||
</div>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
var _countCompletes = 0;
|
||||
var _expectedCompletes = 2; // 2 iframes
|
||||
|
||||
function handleEventMessage(event) {
|
||||
if ("test" in event.data) {
|
||||
let summary = event.data.test + ": " + event.data.msg;
|
||||
ok(event.data.status, summary);
|
||||
} else if ("done" in event.data) {
|
||||
_countCompletes += 1;
|
||||
if (_countCompletes == _expectedCompletes) {
|
||||
console.log("Test compeleted. Finished.");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
} else {
|
||||
ok(false, "Unexpected message in the test harness: " + event.data);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleEventMessage);
|
||||
SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
|
||||
["security.webauth.webauthn_enable_softtoken", true],
|
||||
["security.webauth.webauthn_enable_usbtoken", false]]},
|
||||
function() {
|
||||
document.getElementById("frame_top").src = "https://example.com/tests/dom/credentialmanagement/tests/mochitest/frame_credman_iframes.html";
|
||||
|
||||
document.getElementById("frame_bottom").src = "https://test1.example.com/tests/dom/credentialmanagement/tests/mochitest/frame_credman_iframes.html";
|
||||
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -15,8 +15,11 @@ interface Credential {
|
||||
|
||||
[Exposed=Window, SecureContext, Pref="security.webauth.webauthn"]
|
||||
interface CredentialsContainer {
|
||||
[Throws]
|
||||
Promise<Credential?> get(optional CredentialRequestOptions options);
|
||||
[Throws]
|
||||
Promise<Credential?> create(optional CredentialCreationOptions options);
|
||||
[Throws]
|
||||
Promise<Credential> store(Credential credential);
|
||||
};
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user