Bug 1407829 - WebAuthn: Implement CredMan's Store method r?qdot r?ttaubert
Credential Management defines a Store operation [1], which needs to be
implemented for WebAuthn's spec compliance. It only returns a NotSupportedError
for WebAuthn [2], so it's pretty simple.
[1] https://w3c.github.io/webappsec-credential-management/#dom-credentialscontainer-store
[2] https://w3c.github.io/webauthn/#storeCredential
MozReview-Commit-ID: KDEB8r5feQt
--- a/dom/credentialmanagement/CredentialsContainer.cpp
+++ b/dom/credentialmanagement/CredentialsContainer.cpp
@@ -43,10 +43,17 @@ CredentialsContainer::Get(const Credenti
already_AddRefed<Promise>
CredentialsContainer::Create(const CredentialCreationOptions& aOptions)
{
RefPtr<WebAuthnManager> mgr = WebAuthnManager::GetOrCreate();
return mgr->MakeCredential(mParent, aOptions.mPublicKey);
}
+already_AddRefed<Promise>
+CredentialsContainer::Store(const Credential& aCredential)
+{
+ RefPtr<WebAuthnManager> mgr = WebAuthnManager::GetOrCreate();
+ return mgr->Store(mParent, aCredential);
+}
+
} // namespace dom
} // namespace mozilla
--- a/dom/credentialmanagement/CredentialsContainer.h
+++ b/dom/credentialmanagement/CredentialsContainer.h
@@ -29,16 +29,18 @@ public:
virtual JSObject*
WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
already_AddRefed<Promise>
Get(const CredentialRequestOptions& aOptions);
already_AddRefed<Promise>
Create(const CredentialCreationOptions& aOptions);
+ already_AddRefed<Promise>
+ Store(const Credential& aCredential);
private:
~CredentialsContainer();
nsCOMPtr<nsPIDOMWindowInner> mParent;
};
} // namespace dom
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -682,16 +682,37 @@ WebAuthnManager::GetAssertion(nsPIDOMWin
mClientData = Some(clientDataJSON);
mCurrentParent = aParent;
mInfo = Some(info);
ListenForVisibilityEvents(aParent, this);
return promise.forget();
}
+already_AddRefed<Promise>
+WebAuthnManager::Store(nsPIDOMWindowInner* aParent,
+ const Credential& aCredential)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aParent);
+
+ MaybeClearTransaction();
+
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
+
+ ErrorResult rv;
+ RefPtr<Promise> promise = Promise::Create(global, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+ return promise.forget();
+}
+
void
WebAuthnManager::FinishMakeCredential(nsTArray<uint8_t>& aRegBuffer)
{
MOZ_ASSERT(mTransactionPromise);
MOZ_ASSERT(mInfo.isSome());
CryptoBuffer regData;
if (NS_WARN_IF(!regData.Assign(aRegBuffer.Elements(), aRegBuffer.Length()))) {
--- a/dom/webauthn/WebAuthnManager.h
+++ b/dom/webauthn/WebAuthnManager.h
@@ -84,16 +84,19 @@ public:
already_AddRefed<Promise>
MakeCredential(nsPIDOMWindowInner* aParent,
const MakePublicKeyCredentialOptions& aOptions);
already_AddRefed<Promise>
GetAssertion(nsPIDOMWindowInner* aParent,
const PublicKeyCredentialRequestOptions& aOptions);
+ already_AddRefed<Promise>
+ Store(nsPIDOMWindowInner* aParent, const Credential& aCredential);
+
void StartRegister();
void StartSign();
void StartCancel();
// nsIIPCbackgroundChildCreateCallback methods
void ActorCreated(PBackgroundChild* aActor) override;
void ActorFailed() override;
void ActorDestroyed();
--- a/dom/webauthn/tests/mochitest.ini
+++ b/dom/webauthn/tests/mochitest.ini
@@ -5,10 +5,11 @@ support-files =
u2futil.js
skip-if = !e10s
scheme = https
[test_webauthn_loopback.html]
[test_webauthn_no_token.html]
[test_webauthn_make_credential.html]
[test_webauthn_get_assertion.html]
+[test_webauthn_store_credential.html]
[test_webauthn_sameorigin.html]
[test_webauthn_isplatformauthenticatoravailable.html]
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/tests/test_webauthn_store_credential.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<head>
+ <title>Tests for Store for W3C Web Authentication</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" />
+</head>
+<body>
+
+ <h1>Tests for Store for W3C Web Authentication</h1>
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
+
+ <script class="testbody" type="text/javascript">
+ "use strict";
+
+ function arrivingHereIsBad(aResult) {
+ ok(false, "Bad result! Received a: " + aResult);
+ return Promise.resolve();
+ }
+
+ function expectNotSupportedError(aResult) {
+ ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError, received: " + aResult);
+ return Promise.resolve();
+ }
+
+ add_task(async function(){
+ await SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
+ ["security.webauth.webauthn_enable_softtoken", true],
+ ["security.webauth.webauthn_enable_usbtoken", false]]});
+
+ isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+ isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+ isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+ isnot(navigator.credentials.store, undefined, "CredentialManagement store API endpoint must exist");
+
+ let credentialChallenge = new Uint8Array(16);
+ window.crypto.getRandomValues(credentialChallenge);
+
+ let rp = {id: document.domain, name: "none", icon: "none"};
+ let user = {id: new Uint8Array(64), name: "none", icon: "none", displayName: "none"};
+ let params = [ {type: "public-key", alg: "es256"}, {type: "public-key", alg: -7} ]
+
+ let makeCredentialOptions = {
+ rp: rp, user: user, challenge: credentialChallenge, pubKeyCredParams: params
+ };
+
+ let credential = await navigator.credentials.create({publicKey: makeCredentialOptions})
+ .catch(arrivingHereIsBad);
+
+ await navigator.credentials.store(credential)
+ .then(arrivingHereIsBad)
+ .catch(expectNotSupportedError);
+ });
+ </script>
+
+</body>
+</html>
--- a/dom/webidl/CredentialManagement.webidl
+++ b/dom/webidl/CredentialManagement.webidl
@@ -12,16 +12,17 @@ interface Credential {
readonly attribute USVString id;
readonly attribute DOMString type;
};
[Exposed=Window, SecureContext, Pref="security.webauth.webauthn"]
interface CredentialsContainer {
Promise<Credential?> get(optional CredentialRequestOptions options);
Promise<Credential?> create(optional CredentialCreationOptions options);
+ Promise<Credential> store(Credential credential);
};
dictionary CredentialRequestOptions {
PublicKeyCredentialRequestOptions publicKey;
};
dictionary CredentialCreationOptions {
MakePublicKeyCredentialOptions publicKey;