Commit d1eeb248 authored by Kim Paulhamus's avatar Kim Paulhamus Committed by Commit Bot

Check publickey security requirements for calls to get(publicKey)

and handle empty or missing rpIds.

Add layout tests for various combinations of origins and rpId to
get(publicKey) just like the existing ones for create(publicKey).

Bug: 807774, 664630
Change-Id: I86d3d36c3f3825743f003da69245c74bdca10d5b
Reviewed-on: https://chromium-review.googlesource.com/896384
Commit-Queue: Kim Paulhamus <kpaulhamus@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarBalazs Engedy <engedy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537594}
parent 694cc3d4
...@@ -591,5 +591,10 @@ ...@@ -591,5 +591,10 @@
"prefix": "navigation-mojo-response", "prefix": "navigation-mojo-response",
"base": "external/wpt/service-workers/service-worker", "base": "external/wpt/service-workers/service-worker",
"args": ["--enable-features=NavigationMojoResponse"] "args": ["--enable-features=NavigationMojoResponse"]
},
{
"prefix": "enable-webauthn",
"base": "http/tests/credentialmanager/",
"args": ["--enable-features=WebAuthentication"]
} }
] ]
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
<script src="resources/credential-helpers.js"></script> <script src="resources/credential-helpers.js"></script>
<script> <script>
if (document.location.hostname == "127.0.0.1")
document.location = "https://subdomain.example.test:8443/credentialmanager/credentialscontainer-create-basics.html";
promise_test(t => { promise_test(t => {
return promise_rejects(t, "NotSupportedError", return promise_rejects(t, "NotSupportedError",
navigator.credentials.create()); navigator.credentials.create());
...@@ -20,13 +23,6 @@ promise_test(t => { ...@@ -20,13 +23,6 @@ promise_test(t => {
navigator.credentials.create({publicKey : MAKE_CREDENTIAL_OPTIONS})); navigator.credentials.create({publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that invalid domain error returned by mock is properly handled."); }, "Verify that invalid domain error returned by mock is properly handled.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.INSECURE_ORIGIN);
return promise_rejects(t, "SecurityError",
navigator.credentials.create({publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that insecure origin error returned by mock is properly handled.");
promise_test(t => { promise_test(t => {
var customMakeCredOptions = { var customMakeCredOptions = {
// No challenge. // No challenge.
...@@ -137,20 +133,73 @@ promise_test(t => { ...@@ -137,20 +133,73 @@ promise_test(t => {
navigator.credentials.create({publicKey: customMakeCredOptions})); navigator.credentials.create({publicKey: customMakeCredOptions}));
}, "navigator.credentials.create() with missing user.displayName"); }, "navigator.credentials.create() with missing user.displayName");
promise_test(_ => {
mockAuthenticator.setRawId(RAW_ID);
mockAuthenticator.setId(ID);
mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON);
mockAuthenticator.setAttestationObject(ATTESTATION_OBJECT);
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.SUCCESS);
return navigator.credentials.create({publicKey : MAKE_CREDENTIAL_OPTIONS}).then(r => {
assertValidMakeCredentialResponse(r);
});
}, "Verify that the mock returns the values we give it.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.PENDING_REQUEST);
return promise_rejects(t, "InvalidStateError",
navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that pending request error returned by mock is properly handled.");
promise_test(function (t) { promise_test(function (t) {
return promise_rejects(t, "NotSupportedError", mockAuthenticator.setAuthenticatorStatus(
navigator.credentials.create()); webauth.mojom.AuthenticatorStatus.UNKNOWN_ERROR);
}, "navigator.credentials.get() with no argument."); return promise_rejects(t, "NotReadableError",
navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that unknown error returned by mock is properly handled.");
promise_test(function(t) { promise_test(t => {
var customGetAssertionOptions = { mockAuthenticator.setAuthenticatorStatus(
// No challenge. webauth.mojom.AuthenticatorStatus.NOT_ALLOWED_ERROR);
rpId: "google.com", return promise_rejects(t, "NotAllowedError",
allowCredentials: [ACCEPTABLE_CREDENTIAL] navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}; }, "Verify that not allowed error returned by mock is properly handled.");
return promise_rejects(t, new TypeError(), promise_test(t => {
navigator.credentials.get({publicKey: customGetAssertionOptions})); mockAuthenticator.setAuthenticatorStatus(
}, "navigator.credentials.get() with missing challenge"); webauth.mojom.AuthenticatorStatus.NOT_SUPPORTED_ERROR);
return promise_rejects(t, "NotSupportedError",
navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that not supported error returned by mock is properly handled.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.TIMED_OUT);
return promise_rejects(t, "NotAllowedError",
navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that timed out error returned by mock is properly handled.");
promise_test(_ => {
mockAuthenticator.reset();
mockAuthenticator.setRawId(RAW_ID);
mockAuthenticator.setId(ID);
mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON);
mockAuthenticator.setAttestationObject(ATTESTATION_OBJECT);
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.SUCCESS);
var customPublicKey = {
challenge: CHALLENGE,
rp: { name: "Acme" },
user: PUBLIC_KEY_USER,
pubKeyCredParams: PUBLIC_KEY_PARAMETERS,
};
return navigator.credentials.create({publicKey: customPublicKey}).then(r => {
assertValidMakeCredentialResponse(r);
});
}, "navigator.credentials.create() with missing rp.id");
</script> </script>
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/origin.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/url.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: mojo/common/time.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/origin.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/url.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: mojo/common/time.mojom
This is a testharness.js-based test.
PASS navigator.credentials should be undefined in documents generated from `data:` URLs.
PASS navigator.credentials.create({publicKey}) in a javascript url should should succeed.
PASS navigator.credentials.create({publicKey}) in srcdoc should succeed.
PASS navigator.credentials.create({publicKey}) in about:blank embedded in a secure context should succeed.
FAIL navigator.credentials.create({publicKey}) in an about:blank page embedded in a secure context should pass rpID checks. assert_equals: expected "NotAllowedError" but got "NotSupportedError"
Harness: the test ran to completion.
...@@ -12,10 +12,6 @@ ...@@ -12,10 +12,6 @@
if (document.location.hostname == "127.0.0.1") if (document.location.hostname == "127.0.0.1")
document.location = "https://subdomain.example.test:8443/credentialmanager/credentialscontainer-create-from-nested-frame.html"; document.location = "https://subdomain.example.test:8443/credentialmanager/credentialscontainer-create-from-nested-frame.html";
function EncloseInScriptTag(code) {
return "<script>" + code + "</scr" + "ipt>";
}
promise_test(t => { promise_test(t => {
let PROBE_CREDENTIALS = "window.parent.postMessage(String(navigator.credentials), '*');"; let PROBE_CREDENTIALS = "window.parent.postMessage(String(navigator.credentials), '*');";
...@@ -44,12 +40,13 @@ promise_test(t => { ...@@ -44,12 +40,13 @@ promise_test(t => {
}); });
}, "navigator.credentials.create({publicKey}) in a javascript url should should succeed."); }, "navigator.credentials.create({publicKey}) in a javascript url should should succeed.");
// Load the content we want to inject into the nested frames below into // Load the content we want to inject into the nested frames below into
// |templateFrame| so that we don't have to use string literals here. // |templateFrame| so that we don't have to use string literals here.
var templateFrame = document.createElement("iframe"); var templateFrame = document.createElement("iframe");
templateFrame.src = "resources/nested-mock-authenticator-client.html"; templateFrame.src = "resources/nested-mock-authenticator-client.html";
templateFrame.addEventListener("load", _ => { templateFrame.addEventListener("load", _ => {
// Uses mockAuthenticator.
promise_test(t => { promise_test(t => {
let frame = document.createElement("iframe"); let frame = document.createElement("iframe");
frame.srcdoc = templateFrame.contentDocument.documentElement.outerHTML; frame.srcdoc = templateFrame.contentDocument.documentElement.outerHTML;
...@@ -66,6 +63,7 @@ templateFrame.addEventListener("load", _ => { ...@@ -66,6 +63,7 @@ templateFrame.addEventListener("load", _ => {
}); });
}, "navigator.credentials.create({publicKey}) in srcdoc should succeed."); }, "navigator.credentials.create({publicKey}) in srcdoc should succeed.");
// Uses mockAuthenticator.
promise_test(t => { promise_test(t => {
let frame = document.createElement("iframe"); let frame = document.createElement("iframe");
frame.src = "about:blank"; frame.src = "about:blank";
...@@ -83,6 +81,7 @@ templateFrame.addEventListener("load", _ => { ...@@ -83,6 +81,7 @@ templateFrame.addEventListener("load", _ => {
}); });
}, "navigator.credentials.create({publicKey}) in about:blank embedded in a secure context should succeed."); }, "navigator.credentials.create({publicKey}) in about:blank embedded in a secure context should succeed.");
// Does not use mockAuthenticator, but times out instead.
promise_test(t => { promise_test(t => {
let frame = document.createElement("iframe"); let frame = document.createElement("iframe");
frame.src = "about:blank"; frame.src = "about:blank";
...@@ -95,7 +94,7 @@ templateFrame.addEventListener("load", _ => { ...@@ -95,7 +94,7 @@ templateFrame.addEventListener("load", _ => {
let eventWatcher = new EventWatcher(t, window, "message"); let eventWatcher = new EventWatcher(t, window, "message");
return eventWatcher.wait_for("message").then(message => { return eventWatcher.wait_for("message").then(message => {
assert_equals(message.data, "NotSupportedError"); assert_equals(message.data, "NotAllowedError");
}); });
}, "navigator.credentials.create({publicKey}) in an about:blank page embedded in a secure context should pass rpID checks."); }, "navigator.credentials.create({publicKey}) in an about:blank page embedded in a secure context should pass rpID checks.");
}); });
......
...@@ -8,73 +8,8 @@ ...@@ -8,73 +8,8 @@
<script src="resources/credential-helpers.js"></script> <script src="resources/credential-helpers.js"></script>
<script> <script>
// For tests that don't require custom-set origins.
if (document.location.hostname == "127.0.0.1")
document.location = "https://subdomain.example.test:8443/credentialmanager/credentialscontainer-create-origins.html";
promise_test(_ => {
mockAuthenticator.setRawId(RAW_ID);
mockAuthenticator.setId(ID);
mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON);
mockAuthenticator.setAttestationObject(ATTESTATION_OBJECT);
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.SUCCESS);
return navigator.credentials.create({publicKey : MAKE_CREDENTIAL_OPTIONS}).then(r => {
assertValidMakeCredentialResponse(r);
});
}, "Verify that the mock returns the values we give it.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.PENDING_REQUEST);
return promise_rejects(t, "InvalidStateError",
navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that pending request error returned by mock is properly handled.");
promise_test(function (t) {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.UNKNOWN_ERROR);
return promise_rejects(t, "NotReadableError",
navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that unknown error returned by mock is properly handled.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.NOT_ALLOWED_ERROR);
return promise_rejects(t, "NotAllowedError",
navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that not allowed error returned by mock is properly handled.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.NOT_SUPPORTED_ERROR);
return promise_rejects(t, "NotSupportedError",
navigator.credentials.create({ publicKey : MAKE_CREDENTIAL_OPTIONS}));
}, "Verify that not supported error returned by mock is properly handled.");
promise_test(_ => {
mockAuthenticator.reset();
mockAuthenticator.setRawId(RAW_ID);
mockAuthenticator.setId(ID);
mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON);
mockAuthenticator.setAttestationObject(ATTESTATION_OBJECT);
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.SUCCESS);
var customPublicKey = {
challenge: CHALLENGE,
rp: { name: "Acme" },
user: PUBLIC_KEY_USER,
pubKeyCredParams: PUBLIC_KEY_PARAMETERS,
};
return navigator.credentials.create({publicKey: customPublicKey}).then(r => {
assertValidMakeCredentialResponse(r);
});
}, "navigator.credentials.create() with missing rp.id");
// For tests that require custom-set origins. // For tests that require custom-set origins.
const VALID_ORIGIN_RPID_PAIRS = [ const VALID_ORIGIN_RPID_PAIRS = [
{ 'origin': 'https://google.test:8443', { 'origin': 'https://google.test:8443',
'rpId': 'google.test' }, 'rpId': 'google.test' },
...@@ -111,7 +46,7 @@ const INVALID_ORIGIN_RPID_PAIRS = [ ...@@ -111,7 +46,7 @@ const INVALID_ORIGIN_RPID_PAIRS = [
{ 'origin': 'https://google.test:8443', { 'origin': 'https://google.test:8443',
'rpId': null }, 'rpId': null },
{ 'origin': 'https://google.test:8443', { 'origin': 'https://google.test:8443',
'rpId': String(0) }, 'rpId': String(0) },
{ 'origin': 'https://google.test:8443', { 'origin': 'https://google.test:8443',
'rpId': 'test' }, 'rpId': 'test' },
]; ];
......
...@@ -8,6 +8,9 @@ ...@@ -8,6 +8,9 @@
<script src="resources/credential-helpers.js"></script> <script src="resources/credential-helpers.js"></script>
<script> <script>
if (document.location.hostname == "127.0.0.1")
document.location = "https://subdomain.example.test:8443/credentialmanager/credentialscontainer-get-basics.html";
add_completion_callback(() => { add_completion_callback(() => {
mockCredentialManager.reset(); mockCredentialManager.reset();
}); });
...@@ -108,4 +111,84 @@ promise_test(_ => { ...@@ -108,4 +111,84 @@ promise_test(_ => {
}); });
}, "Verify that the mock returns the values we give it."); }, "Verify that the mock returns the values we give it.");
promise_test(_ => {
mockAuthenticator.setRawId(RAW_ID);
mockAuthenticator.setId(ID);
mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON);
mockAuthenticator.setAuthenticatorData(AUTHENTICATOR_DATA);
mockAuthenticator.setSignature(SIGNATURE);
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.SUCCESS);
return navigator.credentials.get({publicKey : GET_CREDENTIAL_OPTIONS}).then(r => {
assertValidGetCredentialResponse(r);
});
}, "Verify that mockAuthenticator returns the values we give it.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.PENDING_REQUEST);
return promise_rejects(t, "InvalidStateError",
navigator.credentials.get({ publicKey : GET_CREDENTIAL_OPTIONS}));
}, "Verify that pending request error returned by mock is properly handled.");
promise_test(function (t) {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.UNKNOWN_ERROR);
return promise_rejects(t, "NotReadableError",
navigator.credentials.get({ publicKey : GET_CREDENTIAL_OPTIONS}));
}, "Verify that unknown error returned by mock is properly handled.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.NOT_ALLOWED_ERROR);
return promise_rejects(t, "NotAllowedError",
navigator.credentials.get({ publicKey : GET_CREDENTIAL_OPTIONS}));
}, "Verify that not allowed error returned by mock is properly handled.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.NOT_SUPPORTED_ERROR);
return promise_rejects(t, "NotSupportedError",
navigator.credentials.get({ publicKey : GET_CREDENTIAL_OPTIONS}));
}, "Verify that not supported error returned by mock is properly handled.");
promise_test(t => {
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.TIMED_OUT);
return promise_rejects(t, "NotAllowedError",
navigator.credentials.get({ publicKey : GET_CREDENTIAL_OPTIONS}));
}, "Verify that timed out error returned by mock is properly handled.");
promise_test(function(t) {
var customGetCredentialOptions = {
// No challenge.
rpId: "google.com",
allowCredentials: [ACCEPTABLE_CREDENTIAL]
};
return promise_rejects(t, new TypeError(),
navigator.credentials.get({publicKey: customGetCredentialOptions}));
}, "navigator.credentials.get() with missing challenge");
promise_test(_ => {
mockAuthenticator.reset();
mockAuthenticator.setRawId(RAW_ID);
mockAuthenticator.setId(ID);
mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON);
mockAuthenticator.setAuthenticatorData(AUTHENTICATOR_DATA);
mockAuthenticator.setSignature(SIGNATURE);
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.SUCCESS);
var customPublicKey = {
challenge: CHALLENGE,
allowCredentials: [ACCEPTABLE_CREDENTIAL]
};
return navigator.credentials.get({publicKey: customPublicKey}).then(r => {
assertValidGetCredentialResponse(r);
});
}, "navigator.credentials.get() with missing rpId");
</script> </script>
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/origin.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/url.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: mojo/common/time.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/origin.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/url.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: mojo/common/time.mojom
This is a testharness.js-based test.
PASS navigator.credentials should be undefined in documents generated from `data:` URLs.
PASS navigator.credentials.get({publicKey}) in a javascript url should should succeed.
PASS navigator.credentials.get({publicKey}) in srcdoc should succeed.
PASS navigator.credentials.get({publicKey}) in about:blank embedded in a secure context should succeed.
FAIL navigator.credentials.get({publicKey}) in an about:blank page embedded in a secure context should pass rpID checks. assert_equals: expected "NotAllowedError" but got "NotSupportedError"
Harness: the test ran to completion.
<!DOCTYPE html>
<title>Credential Manager: Call get() across browsing contexts.</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="/gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="/gen/third_party/WebKit/public/platform/modules/credentialmanager/credential_manager.mojom.js"></script>
<script src="/gen/third_party/WebKit/public/platform/modules/webauth/authenticator.mojom.js"></script>
<script src="resources/credential-helpers.js"></script>
<body>
<script>
if (document.location.hostname == "127.0.0.1")
document.location = "https://subdomain.example.test:8443/credentialmanager/credentialscontainer-get-from-nested-frame.html";
promise_test(t => {
let PROBE_CREDENTIALS = "window.parent.postMessage(String(navigator.credentials), '*');";
let frame = document.createElement("iframe");
frame.src = "data:text/html," + EncloseInScriptTag(PROBE_CREDENTIALS);
window.setTimeout(_ => document.body.append(frame));
let eventWatcher = new EventWatcher(t, window, "message");
return eventWatcher.wait_for("message").then(message => {
assert_equals(message.data, "undefined");
});
}, "navigator.credentials should be undefined in documents generated from `data:` URLs.");
promise_test(t => {
let frame = document.createElement("iframe");
frame.src = "resources/nested-mock-authenticator-client.html";
window.setTimeout(_ => document.body.append(frame));
let loadWatcher = new EventWatcher(t, frame, "load");
loadWatcher.wait_for("load").then(_ =>
frame.contentWindow.location = "javascript:" + GET_CREDENTIAL);
let messageWatcher = new EventWatcher(t, window, "message");
return messageWatcher.wait_for("message").then(message => {
assert_equals(message.data, TEST_NESTED_CREDENTIAL_ID);
});
}, "navigator.credentials.get({publicKey}) in a javascript url should should succeed.");
// Load the content we want to inject into the nested frames below into
// |templateFrame| so that we don't have to use string literals here.
var templateFrame = document.createElement("iframe");
templateFrame.src = "resources/nested-mock-authenticator-client.html";
templateFrame.addEventListener("load", _ => {
// Uses mockAuthenticator.
promise_test(t => {
let frame = document.createElement("iframe");
frame.srcdoc = templateFrame.contentDocument.documentElement.outerHTML;
window.setTimeout(_ => document.body.append(frame));
let loadWatcher = new EventWatcher(t, frame, "load");
loadWatcher.wait_for("load").then(_ => {
frame.contentWindow.eval(GET_CREDENTIAL);
});
let eventWatcher = new EventWatcher(t, window, "message");
return eventWatcher.wait_for("message").then(message => {
assert_equals(message.data, TEST_NESTED_CREDENTIAL_ID);
});
}, "navigator.credentials.get({publicKey}) in srcdoc should succeed.");
// Uses mockAuthenticator.
promise_test(t => {
let frame = document.createElement("iframe");
frame.src = "about:blank";
window.setTimeout(_ => document.body.append(frame));
let loadWatcher = new EventWatcher(t, frame, "load");
loadWatcher.wait_for("load").then(_ => {
frame.contentDocument.write(templateFrame.contentDocument.documentElement.outerHTML);
frame.contentDocument.write(EncloseInScriptTag(GET_CUSTOM_CREDENTIALS));
});
let eventWatcher = new EventWatcher(t, window, "message");
return eventWatcher.wait_for("message").then(message => {
assert_equals(message.data, TEST_NESTED_CREDENTIAL_ID);
});
}, "navigator.credentials.get({publicKey}) in about:blank embedded in a secure context should succeed.");
// Does not use mockAuthenticator, but times out instead.
promise_test(t => {
let frame = document.createElement("iframe");
frame.src = "about:blank";
window.setTimeout(_ => document.body.append(frame));
let loadWatcher = new EventWatcher(t, frame, "load");
loadWatcher.wait_for("load").then(_ => {
frame.contentWindow.eval(GET_CUSTOM_CREDENTIALS);
});
let eventWatcher = new EventWatcher(t, window, "message");
return eventWatcher.wait_for("message").then(message => {
assert_equals(message.data, "NotAllowedError");
});
}, "navigator.credentials.get({publicKey}) in an about:blank page embedded in a secure context should pass rpID checks.");
});
document.body.append(templateFrame);
</script>
<!DOCTYPE html>
<title>Credential Manager: get() with custom origins.</title>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script src="/gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="/gen/third_party/WebKit/public/platform/modules/credentialmanager/credential_manager.mojom.js"></script>
<script src="/gen/third_party/WebKit/public/platform/modules/webauth/authenticator.mojom.js"></script>
<script src="resources/credential-helpers.js"></script>
<script>
// For tests that require custom-set origins.
const VALID_ORIGIN_RPID_PAIRS = [
{ 'origin': 'https://google.test:8443',
'rpId': 'google.test' },
{ 'origin': 'https://google.test:8443',
'rpId': '' },
{'origin': 'https://subdomain.example.test:8443',
'rpId': 'example.test' },
{'origin': 'https://subdomain.example.test:8443',
'rpId': 'subdomain.example.test' },
{'origin': 'https://localhost:8443',
'rpId': 'localhost' },
];
for (let test of VALID_ORIGIN_RPID_PAIRS) {
promise_test(t => {
let eventWatcher = new EventWatcher(t, window, "message");
var w = window.open(test.origin
+ "/credentialmanager/resources/publickey-get-helper.html?rpId="
+ test.rpId);
return eventWatcher.wait_for("message").then(message => {
assert_equals(message.data, "SUCCESS");
});
}, "navigator.credentials.get({publicKey}) in '" + test.origin
+ "' with valid |rpId| '" + test.rpId + "' should succeed.");
}
const INVALID_ORIGIN_RPID_PAIRS = [
{ 'origin': 'https://google.test:8443',
'rpId': 'localhost' },
{ 'origin': 'https://google.test:8443',
'rpId': 'foo.google.test' },
{ 'origin': 'https://google.test:8443',
'rpId': null },
{ 'origin': 'https://google.test:8443',
'rpId': String(0) },
{ 'origin': 'https://google.test:8443',
'rpId': 'test' },
];
for (let test of INVALID_ORIGIN_RPID_PAIRS) {
promise_test(t => {
let eventWatcher = new EventWatcher(t, window, "message");
var w = window.open(test.origin
+ "/credentialmanager/resources/publickey-get-helper.html?rpId="
+ test.rpId);
return eventWatcher.wait_for("message").then(message => {
assert_equals(message.data, "SecurityError");
});
}, "navigator.credentials.get({publicKey}) in '" + test.origin
+ "' with invalid |rpId| '" + test.rpId + "' should fail.");
}
</script>
...@@ -15,12 +15,12 @@ class MockCredentialManager { ...@@ -15,12 +15,12 @@ class MockCredentialManager {
} }
constructCredentialInfo_(type, id, password, name, icon) { constructCredentialInfo_(type, id, password, name, icon) {
return new passwordManager.mojom.CredentialInfo({ return new passwordManager.mojom.CredentialInfo({
type: type, type: type,
id: id, id: id,
name: name, name: name,
icon: new url.mojom.Url({url: icon}), icon: new url.mojom.Url({url: icon}),
password: password, password: password,
federation: new url.mojom.Origin( federation: new url.mojom.Origin(
{scheme: '', host: '', port: 0, unique: true}) {scheme: '', host: '', port: 0, unique: true})
}); });
...@@ -198,7 +198,7 @@ var ACCEPTABLE_CREDENTIAL = { ...@@ -198,7 +198,7 @@ var ACCEPTABLE_CREDENTIAL = {
transports: ["usb", "nfc", "ble"] transports: ["usb", "nfc", "ble"]
}; };
var GET_ASSERTION_OPTIONS = { var GET_CREDENTIAL_OPTIONS = {
challenge: CHALLENGE, challenge: CHALLENGE,
rpId: "subdomain.example.test", rpId: "subdomain.example.test",
allowCredentials: [ACCEPTABLE_CREDENTIAL] allowCredentials: [ACCEPTABLE_CREDENTIAL]
...@@ -213,17 +213,18 @@ var SIGNATURE = new TextEncoder("utf-8").encode("signature"); ...@@ -213,17 +213,18 @@ var SIGNATURE = new TextEncoder("utf-8").encode("signature");
var TEST_NESTED_CREDENTIAL_ID = "nestedCredentialId"; var TEST_NESTED_CREDENTIAL_ID = "nestedCredentialId";
// Use an invalid algorithm in the parameters for "success" cases // Use a 3-second timeout in the parameters for "success" cases
// so each test will exercise the rpID checks in both the renderer // so that each test will exercise the rpID checks in both the renderer
// and browser but return prior to reaching the device layer. // and browser but will time out instead of wait for a device response.
var CUSTOM_PUBLIC_KEY = 'var customPublicKey = ' var CUSTOM_MAKE_CREDENTIAL_OPTIONS = 'var customPublicKey = '
+ '{challenge : new TextEncoder().encode("challenge"), ' + '{challenge: new TextEncoder().encode("challenge"), '
+ 'rp: {id: "subdomain.example.test", name: "Acme"}, ' + 'rp: {id: "subdomain.example.test", name: "Acme"}, '
+ 'user: {id: new TextEncoder().encode("1098237235409872"), ' + 'user: {id: new TextEncoder().encode("1098237235409872"), '
+ 'name: "acme@example.com", displayName: "Acme", icon:"iconUrl"}, ' + 'name: "acme@example.com", displayName: "Acme", icon:"iconUrl"}, '
+ 'pubKeyCredParams: [{type: "public-key", alg: 0,},], excludeCredentials:[],};'; + 'timeout: 2000, '
+ 'pubKeyCredParams: [{type: "public-key", alg: -7,},], excludeCredentials:[],};';
var CREATE_CUSTOM_CREDENTIALS = CUSTOM_PUBLIC_KEY var CREATE_CUSTOM_CREDENTIALS = CUSTOM_MAKE_CREDENTIAL_OPTIONS
+ "navigator.credentials.create({publicKey : customPublicKey})" + "navigator.credentials.create({publicKey : customPublicKey})"
+ ".then(c => window.parent.postMessage(c.id, '*'))" + ".then(c => window.parent.postMessage(c.id, '*'))"
+ ".catch(e => window.parent.postMessage(e.name, '*'));"; + ".catch(e => window.parent.postMessage(e.name, '*'));";
...@@ -231,6 +232,24 @@ var CREATE_CUSTOM_CREDENTIALS = CUSTOM_PUBLIC_KEY ...@@ -231,6 +232,24 @@ var CREATE_CUSTOM_CREDENTIALS = CUSTOM_PUBLIC_KEY
var CREATE_CREDENTIALS = "navigator.credentials.create({publicKey : MAKE_CREDENTIAL_OPTIONS})" var CREATE_CREDENTIALS = "navigator.credentials.create({publicKey : MAKE_CREDENTIAL_OPTIONS})"
+ ".then(c => window.parent.postMessage(c.id, '*'));"; + ".then(c => window.parent.postMessage(c.id, '*'));";
var CUSTOM_GET_CREDENTIAL_OPTIONS = 'var customPublicKey = '
+ '{challenge: new TextEncoder().encode("challenge"), '
+ 'rpId: "subdomain.example.test", '
+ 'timeout: 2000, '
+ 'allowCredentials: [{type: "public-key", id: new TextEncoder().encode("allowedCredential"), transports: ["usb", "nfc", "ble"]},],};';
var GET_CUSTOM_CREDENTIALS = CUSTOM_GET_CREDENTIAL_OPTIONS
+ "navigator.credentials.get({publicKey : customPublicKey})"
+ ".then(c => window.parent.postMessage(c.id, '*'))"
+ ".catch(e => window.parent.postMessage(e.name, '*'));";
var GET_CREDENTIAL = "navigator.credentials.get({publicKey : GET_CREDENTIAL_OPTIONS})"
+ ".then(c => window.parent.postMessage(c.id, '*'));";
function EncloseInScriptTag(code) {
return "<script>" + code + "</scr" + "ipt>";
}
// Verifies if |r| is the valid response to credentials.create(publicKey). // Verifies if |r| is the valid response to credentials.create(publicKey).
function assertValidMakeCredentialResponse(r) { function assertValidMakeCredentialResponse(r) {
assert_equals(r.id, ID, 'id'); assert_equals(r.id, ID, 'id');
...@@ -249,7 +268,7 @@ assert_equals(r.id, ID, 'id'); ...@@ -249,7 +268,7 @@ assert_equals(r.id, ID, 'id');
} }
// Verifies if |r| is the valid response to credentials.get(publicKey). // Verifies if |r| is the valid response to credentials.get(publicKey).
function assertValidGetAssertionResponse(r) { function assertValidGetCredentialResponse(r) {
assert_equals(r.id, ID, 'id'); assert_equals(r.id, ID, 'id');
assert_true(r.rawId instanceof ArrayBuffer); assert_true(r.rawId instanceof ArrayBuffer);
assert_array_equals(new Uint8Array(r.rawId), assert_array_equals(new Uint8Array(r.rawId),
......
...@@ -11,6 +11,8 @@ mockAuthenticator.setRawId(RAW_ID); ...@@ -11,6 +11,8 @@ mockAuthenticator.setRawId(RAW_ID);
mockAuthenticator.setId(TEST_NESTED_CREDENTIAL_ID); mockAuthenticator.setId(TEST_NESTED_CREDENTIAL_ID);
mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON); mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON);
mockAuthenticator.setAttestationObject(ATTESTATION_OBJECT); mockAuthenticator.setAttestationObject(ATTESTATION_OBJECT);
mockAuthenticator.setAuthenticatorData(AUTHENTICATOR_DATA);
mockAuthenticator.setSignature(SIGNATURE);
mockAuthenticator.setAuthenticatorStatus( mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.SUCCESS); webauth.mojom.AuthenticatorStatus.SUCCESS);
</script> </script>
\ No newline at end of file
<!DOCTYPE HTML>
<script src="/gen/layout_test_data/mojo/public/js/mojo_bindings.js"></script>
<script src="/gen/third_party/WebKit/public/platform/modules/credentialmanager/credential_manager.mojom.js"></script>
<script src="/gen/third_party/WebKit/public/platform/modules/webauth/authenticator.mojom.js"></script>
<script src="credential-helpers.js"></script>
<script>
mockAuthenticator.setRawId(RAW_ID);
mockAuthenticator.setId(ID);
mockAuthenticator.setClientDataJson(CLIENT_DATA_JSON);
mockAuthenticator.setAuthenticatorData(AUTHENTICATOR_DATA);
mockAuthenticator.setSignature(SIGNATURE);
mockAuthenticator.setAuthenticatorStatus(
webauth.mojom.AuthenticatorStatus.SUCCESS);
let queryParams = new URLSearchParams(window.location.search);
let relyingPartyId = queryParams.get('rpId');
var customPublicKey = {
challenge: CHALLENGE,
rpId: relyingPartyId,
allowCredentials: [ACCEPTABLE_CREDENTIAL]
};
navigator.credentials.get({publicKey : customPublicKey})
.then(r => window.opener.postMessage("SUCCESS", "*"))
.catch(t => window.opener.postMessage(t.name, "*"));
</script>
# This suite runs tests that depend on --enable-features=WebAuthentication.
# This flag enables the authenticator mojom implementation, which is needed
# for certain tests that cannot use a mock authenticator.
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/origin.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/url.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: mojo/common/time.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/origin.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/url.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: mojo/common/time.mojom
This is a testharness.js-based test.
PASS navigator.credentials should be undefined in documents generated from `data:` URLs.
PASS navigator.credentials.create({publicKey}) in a javascript url should should succeed.
PASS navigator.credentials.create({publicKey}) in srcdoc should succeed.
PASS navigator.credentials.create({publicKey}) in about:blank embedded in a secure context should succeed.
PASS navigator.credentials.create({publicKey}) in an about:blank page embedded in a secure context should pass rpID checks.
Harness: the test ran to completion.
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/origin.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/url.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: mojo/common/time.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/origin.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: url/mojom/url.mojom
CONSOLE WARNING: line 10: The following mojom is loaded multiple times: mojo/common/time.mojom
This is a testharness.js-based test.
PASS navigator.credentials should be undefined in documents generated from `data:` URLs.
PASS navigator.credentials.get({publicKey}) in a javascript url should should succeed.
PASS navigator.credentials.get({publicKey}) in srcdoc should succeed.
PASS navigator.credentials.get({publicKey}) in about:blank embedded in a secure context should succeed.
PASS navigator.credentials.get({publicKey}) in an about:blank page embedded in a secure context should pass rpID checks.
Harness: the test ran to completion.
...@@ -22,8 +22,8 @@ ...@@ -22,8 +22,8 @@
namespace { namespace {
// Time to wait for an authenticator to successfully complete an operation. // Time to wait for an authenticator to successfully complete an operation.
constexpr TimeDelta kAdjustedTimeoutLower = TimeDelta::FromMinutes(1); constexpr TimeDelta kAdjustedTimeoutLower = TimeDelta::FromSeconds(1);
constexpr TimeDelta kAdjustedTimeoutUpper = TimeDelta::FromMinutes(2); constexpr TimeDelta kAdjustedTimeoutUpper = TimeDelta::FromMinutes(1);
WTF::TimeDelta AdjustTimeout(uint32_t timeout) { WTF::TimeDelta AdjustTimeout(uint32_t timeout) {
WTF::TimeDelta adjusted_timeout; WTF::TimeDelta adjusted_timeout;
...@@ -249,7 +249,7 @@ TypeConverter<PublicKeyCredentialCreationOptionsPtr, ...@@ -249,7 +249,7 @@ TypeConverter<PublicKeyCredentialCreationOptionsPtr,
if (options.hasTimeout()) { if (options.hasTimeout()) {
mojo_options->adjusted_timeout = AdjustTimeout(options.timeout()); mojo_options->adjusted_timeout = AdjustTimeout(options.timeout());
} else { } else {
mojo_options->adjusted_timeout = kAdjustedTimeoutLower; mojo_options->adjusted_timeout = kAdjustedTimeoutUpper;
} }
// Steps 8 and 9 of // Steps 8 and 9 of
...@@ -312,7 +312,7 @@ TypeConverter<PublicKeyCredentialRequestOptionsPtr, ...@@ -312,7 +312,7 @@ TypeConverter<PublicKeyCredentialRequestOptionsPtr,
if (options.hasTimeout()) { if (options.hasTimeout()) {
mojo_options->adjusted_timeout = AdjustTimeout(options.timeout()); mojo_options->adjusted_timeout = AdjustTimeout(options.timeout());
} else { } else {
mojo_options->adjusted_timeout = kAdjustedTimeoutLower; mojo_options->adjusted_timeout = kAdjustedTimeoutUpper;
} }
mojo_options->relying_party_id = options.rpId(); mojo_options->relying_party_id = options.rpId();
......
...@@ -69,7 +69,7 @@ class ScopedPromiseResolver { ...@@ -69,7 +69,7 @@ class ScopedPromiseResolver {
} }
// Releases the owned |resolver_|. This is to be called by the Mojo response // Releases the owned |resolver_|. This is to be called by the Mojo response
// callback responsible for resolving the corresponding ScriptPromise. // callback responsible for resolving the corresponding ScriptPromise
// //
// If this method is not called before |this| goes of scope, it is assumed // If this method is not called before |this| goes of scope, it is assumed
// that a Mojo connection error has occurred, and the response callback was // that a Mojo connection error has occurred, and the response callback was
...@@ -391,9 +391,19 @@ ScriptPromise CredentialsContainer::get( ...@@ -391,9 +391,19 @@ ScriptPromise CredentialsContainer::get(
return promise; return promise;
if (options.hasPublicKey()) { if (options.hasPublicKey()) {
const String& relying_party_id = options.publicKey().rpId();
if (!CheckPublicKeySecurityRequirements(resolver, relying_party_id))
return promise;
auto mojo_options = auto mojo_options =
MojoPublicKeyCredentialRequestOptions::From(options.publicKey()); MojoPublicKeyCredentialRequestOptions::From(options.publicKey());
if (mojo_options) { if (mojo_options) {
if (!mojo_options->relying_party_id) {
mojo_options->relying_party_id = resolver->GetFrame()
->GetSecurityContext()
->GetSecurityOrigin()
->Domain();
}
auto* authenticator = auto* authenticator =
CredentialManagerProxy::From(script_state)->Authenticator(); CredentialManagerProxy::From(script_state)->Authenticator();
authenticator->GetAssertion( authenticator->GetAssertion(
...@@ -515,9 +525,9 @@ ScriptPromise CredentialsContainer::create( ...@@ -515,9 +525,9 @@ ScriptPromise CredentialsContainer::create(
} else { } else {
DCHECK(options.hasPublicKey()); DCHECK(options.hasPublicKey());
const String& relying_party_id = options.publicKey().rp().id(); const String& relying_party_id = options.publicKey().rp().id();
if (!CheckPublicKeySecurityRequirements(resolver, relying_party_id)) { if (!CheckPublicKeySecurityRequirements(resolver, relying_party_id))
return promise; return promise;
}
auto mojo_options = auto mojo_options =
MojoPublicKeyCredentialCreationOptions::From(options.publicKey()); MojoPublicKeyCredentialCreationOptions::From(options.publicKey());
if (mojo_options) { if (mojo_options) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment