Skip to content

Commit 2469639

Browse files
committed
Bug 1406456 - WebAuthn WebIDL Updates for WD-07 (part 1) r=keeler,qdot
This covers these renames: * In PublicKeyCredentialParameters, algorithm => alg * MakeCredentialOptions => MakePublicKeyCredentialOptions * PublicKeyCredentialEntity => PublicKeyCredentialRpEntity * Attachment => AuthenticatorAttachment It sets a default excludeList and allowList for the make / get options. It adds the method isPlatformAuthenticatorAvailable which is incomplete and not callable, to be completed in Bug 1406468. Adds type PublicKeyCredentialRpEntity. Adds "userId" to AuthenticatorAssertionResponse. Adds "id" as a buffer source to PublicKeyCredentialUserEntity and as a DOMString to PublicKeyCredentialRpEntity, refactoring out the "id" field from the parent PublicKeyCredentialEntity. It also adds a simple enforcement per spec 4.4.3 "User Account Parameters for Credential Generation" that the new user ID buffer, if set, be no more than 64 bytes long. I mostly added it here so I could adjust the tests all at once in this commit. MozReview-Commit-ID: IHUdGVoWocq --HG-- extra : rebase_source : bc1793f74700b2785d2bf2099c0dba068f717a59
1 parent 4a9eee9 commit 2469639

15 files changed

+224
-81
lines changed

dom/webauthn/AuthenticatorAssertionResponse.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,5 +92,20 @@ AuthenticatorAssertionResponse::SetSignature(CryptoBuffer& aBuffer)
9292
return NS_OK;
9393
}
9494

95+
void
96+
AuthenticatorAssertionResponse::GetUserId(DOMString& aRetVal)
97+
{
98+
// This requires mUserId to not be re-set for the life of the caller's in-var.
99+
aRetVal.SetOwnedString(mUserId);
100+
}
101+
102+
nsresult
103+
AuthenticatorAssertionResponse::SetUserId(const nsAString& aUserId)
104+
{
105+
MOZ_ASSERT(mUserId.IsEmpty(), "We already have a UserID?");
106+
mUserId.Assign(aUserId);
107+
return NS_OK;
108+
}
109+
95110
} // namespace dom
96111
} // namespace mozilla

dom/webauthn/AuthenticatorAssertionResponse.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,18 @@ class AuthenticatorAssertionResponse final : public AuthenticatorResponse
4747
nsresult
4848
SetSignature(CryptoBuffer& aBuffer);
4949

50+
void
51+
GetUserId(DOMString& aRetVal);
52+
53+
nsresult
54+
SetUserId(const nsAString& aUserId);
55+
5056
private:
5157
CryptoBuffer mAuthenticatorData;
5258
JS::Heap<JSObject*> mAuthenticatorDataCachedObj;
5359
CryptoBuffer mSignature;
5460
JS::Heap<JSObject*> mSignatureCachedObj;
61+
nsString mUserId;
5562
};
5663

5764
} // namespace dom

dom/webauthn/PublicKeyCredential.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* License, v. 2.0. If a copy of the MPL was not distributed with this
55
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
66

7+
#include "mozilla/dom/Promise.h"
78
#include "mozilla/dom/PublicKeyCredential.h"
89
#include "mozilla/dom/WebAuthenticationBinding.h"
910
#include "nsCycleCollectionParticipant.h"
@@ -82,5 +83,29 @@ PublicKeyCredential::SetResponse(RefPtr<AuthenticatorResponse> aResponse)
8283
mResponse = aResponse;
8384
}
8485

86+
/* static */ already_AddRefed<Promise>
87+
PublicKeyCredential::IsPlatformAuthenticatorAvailable(GlobalObject& aGlobal)
88+
{
89+
nsIGlobalObject* globalObject =
90+
xpc::NativeGlobal(JS::CurrentGlobalOrNull(aGlobal.Context()));
91+
if (NS_WARN_IF(!globalObject)) {
92+
return nullptr;
93+
}
94+
95+
ErrorResult rv;
96+
RefPtr<Promise> promise = Promise::Create(globalObject, rv);
97+
if(rv.Failed()) {
98+
return nullptr;
99+
}
100+
101+
// Complete in Bug 1406468. This shouldn't just always return true, it should
102+
// follow the guidelines in
103+
// https://w3c.github.io/webauthn/#isPlatformAuthenticatorAvailable
104+
// such as ensuring that U2FTokenManager isn't in some way disabled.
105+
promise->MaybeResolve(true);
106+
return promise.forget();
107+
}
108+
109+
85110
} // namespace dom
86111
} // namespace mozilla

dom/webauthn/PublicKeyCredential.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ class PublicKeyCredential final : public Credential
4646
void
4747
SetResponse(RefPtr<AuthenticatorResponse>);
4848

49+
static already_AddRefed<Promise>
50+
IsPlatformAuthenticatorAvailable(GlobalObject& aGlobal);
51+
4952
private:
5053
CryptoBuffer mRawId;
5154
JS::Heap<JSObject*> mRawIdCachedObj;

dom/webauthn/WebAuthnManager.cpp

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -282,7 +282,7 @@ WebAuthnManager::Get()
282282

283283
already_AddRefed<Promise>
284284
WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
285-
const MakeCredentialOptions& aOptions)
285+
const MakePublicKeyCredentialOptions& aOptions)
286286
{
287287
MOZ_ASSERT(aParent);
288288

@@ -304,6 +304,18 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
304304
return promise.forget();
305305
}
306306

307+
// Enforce 4.4.3 User Account Parameters for Credential Generation
308+
if (aOptions.mUser.mId.WasPassed()) {
309+
// When we add UX, we'll want to do more with this value, but for now
310+
// we just have to verify its correctness.
311+
CryptoBuffer userId;
312+
userId.Assign(aOptions.mUser.mId.Value());
313+
if (userId.Length() > 64) {
314+
promise->MaybeReject(NS_ERROR_DOM_TYPE_ERR);
315+
return promise.forget();
316+
}
317+
}
318+
307319
// If timeoutSeconds was specified, check if its value lies within a
308320
// reasonable range as defined by the platform and if not, correct it to the
309321
// closest value lying within that range.
@@ -371,7 +383,7 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
371383
// element in cryptoParameters.
372384

373385
nsString algName;
374-
if (NS_FAILED(GetAlgorithmName(aOptions.mParameters[a].mAlgorithm,
386+
if (NS_FAILED(GetAlgorithmName(aOptions.mParameters[a].mAlg,
375387
algName))) {
376388
continue;
377389
}
@@ -381,7 +393,7 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
381393
// normalizedAlgorithm.
382394
PublicKeyCredentialParameters normalizedObj;
383395
normalizedObj.mType = aOptions.mParameters[a].mType;
384-
normalizedObj.mAlgorithm.SetAsString().Assign(algName);
396+
normalizedObj.mAlg.SetAsString().Assign(algName);
385397

386398
if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){
387399
promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
@@ -411,8 +423,8 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
411423

412424
for (size_t a = 0; a < normalizedParams.Length(); ++a) {
413425
if (normalizedParams[a].mType == PublicKeyCredentialType::Public_key &&
414-
normalizedParams[a].mAlgorithm.IsString() &&
415-
normalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
426+
normalizedParams[a].mAlg.IsString() &&
427+
normalizedParams[a].mAlg.GetAsString().EqualsLiteral(
416428
JWK_ALG_ECDSA_P_256)) {
417429
isValidCombination = true;
418430
break;
@@ -464,14 +476,12 @@ WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
464476
}
465477

466478
nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
467-
if (aOptions.mExcludeList.WasPassed()) {
468-
for (const auto& s: aOptions.mExcludeList.Value()) {
469-
WebAuthnScopedCredentialDescriptor c;
470-
CryptoBuffer cb;
471-
cb.Assign(s.mId);
472-
c.id() = cb;
473-
excludeList.AppendElement(c);
474-
}
479+
for (const auto& s: aOptions.mExcludeList) {
480+
WebAuthnScopedCredentialDescriptor c;
481+
CryptoBuffer cb;
482+
cb.Assign(s.mId);
483+
c.id() = cb;
484+
excludeList.AppendElement(c);
475485
}
476486

477487
// TODO: Add extension list building

dom/webauthn/WebAuthnManager.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ struct Account;
5757
class ArrayBufferViewOrArrayBuffer;
5858
struct AssertionOptions;
5959
class OwningArrayBufferViewOrArrayBuffer;
60-
struct ScopedCredentialOptions;
61-
struct ScopedCredentialParameters;
60+
struct MakePublicKeyCredentialOptions;
6261
class Promise;
6362
class WebAuthnTransactionChild;
6463
class WebAuthnTransactionInfo;
@@ -84,7 +83,7 @@ class WebAuthnManager final : public nsIIPCBackgroundChildCreateCallback,
8483

8584
already_AddRefed<Promise>
8685
MakeCredential(nsPIDOMWindowInner* aParent,
87-
const MakeCredentialOptions& aOptions);
86+
const MakePublicKeyCredentialOptions& aOptions);
8887

8988
already_AddRefed<Promise>
9089
GetAssertion(nsPIDOMWindowInner* aParent,

dom/webauthn/tests/browser/tab_webauthn_success.html

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@ <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1
3535
let gState = {};
3636
let makeCredentialOptions = {
3737
rp: {id: document.domain, name: "none", icon: "none"},
38-
user: {id: "none", name: "none", icon: "none", displayName: "none"},
38+
user: {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"},
3939
challenge: gCredentialChallenge,
4040
timeout: 5000, // the minimum timeout is actually 15 seconds
41-
parameters: [{type: "public-key", algorithm: "ES256"}],
41+
parameters: [{type: "public-key", alg: "ES256"}],
4242
};
4343

4444
navigator.credentials.create({publicKey: makeCredentialOptions})
@@ -48,6 +48,7 @@ <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1
4848
return webAuthnDecodeCBORAttestation(aNewCredentialInfo.response.attestationObject);
4949
})
5050
.then(function testAssertion(aCredInfo) {
51+
gState.authDataObj = aCredInfo.authDataObj;
5152
gState.publicKeyHandle = aCredInfo.authDataObj.publicKeyHandle;
5253

5354
let newCredential = {
@@ -63,30 +64,26 @@ <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1
6364
allowList: [newCredential]
6465
};
6566

66-
return navigator.credentials.get({publicKey: publicKeyCredentialRequestOptions});
67-
})
68-
.then(function(aAssertion) {
69-
let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
70-
71-
gState.assertion = aAssertion;
72-
73-
return webAuthnDecodeAuthDataArray(new Uint8Array(aAssertion.response.authenticatorData));
74-
})
75-
.then(function(aAttestationObj) {
76-
gState.attestation = aAttestationObj;
77-
7867
// Make sure the RP ID hash matches what we calculate.
7968
return crypto.subtle.digest("SHA-256", string2buffer(document.domain))
8069
.then(function(calculatedRpIdHash) {
8170
let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
82-
let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(aAttestationObj.authDataObj.rpIdHash));
71+
let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(gState.authDataObj.rpIdHash));
8372

8473
if (calcHashStr != providedHashStr) {
8574
return Promise.reject("Calculated RP ID hash must match what the browser derived.");
8675
}
87-
return Promise.resolve(aAttestationObj);
76+
77+
return navigator.credentials.get({publicKey: publicKeyCredentialRequestOptions});
8878
});
8979
})
80+
.then(function(aAssertion) {
81+
let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
82+
83+
gState.assertion = aAssertion;
84+
85+
return webAuthnDecodeAuthDataArray(new Uint8Array(aAssertion.response.authenticatorData));
86+
})
9087
.then(function(aAttestation) {
9188
if (new Uint8Array(aAttestation.flags) != flag_TUP) {
9289
return Promise.reject("Assertion's user presence byte not set correctly.");
@@ -102,7 +99,7 @@ <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1
10299
.then(function(aSignedData) {
103100
let signature = gState.assertion.response.signature;
104101
console.log(gState.publicKeyHandle, aSignedData, signature);
105-
return verifySignature(gState.publicKeyHandle, aSignedData, new Uint8Array(signature));
102+
return verifySignature(gState.publicKeyHandle, aSignedData, signature);
106103
})
107104
.then(function(aSigVerifyResult) {
108105
signalCompletion("Signing signature verified: " + aSigVerifyResult);

dom/webauthn/tests/mochitest.ini

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,12 @@ support-files =
33
cbor/*
44
pkijs/*
55
u2futil.js
6-
7-
[test_webauthn_loopback.html]
86
skip-if = !e10s
97
scheme = https
8+
9+
[test_webauthn_loopback.html]
1010
[test_webauthn_no_token.html]
11-
skip-if = !e10s
12-
scheme = https
1311
[test_webauthn_make_credential.html]
14-
skip-if = !e10s
15-
scheme = https
1612
[test_webauthn_get_assertion.html]
17-
skip-if = !e10s
18-
scheme = https
1913
[test_webauthn_sameorigin.html]
20-
skip-if = !e10s
21-
scheme = https
14+
[test_webauthn_isplatformauthenticatoravailable.html]
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE html>
2+
<meta charset=utf-8>
3+
<head>
4+
<title>Test for W3C Web Authentication isPlatformAuthenticatorAvailable</title>
5+
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
6+
<script type="text/javascript" src="u2futil.js"></script>
7+
<script type="text/javascript" src="pkijs/common.js"></script>
8+
<script type="text/javascript" src="pkijs/asn1.js"></script>
9+
<script type="text/javascript" src="pkijs/x509_schema.js"></script>
10+
<script type="text/javascript" src="pkijs/x509_simpl.js"></script>
11+
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
12+
</head>
13+
<body>
14+
15+
<h1>Test for W3C Web Authentication isPlatformAuthenticatorAvailable</h1>
16+
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
17+
18+
<script class="testbody" type="text/javascript">
19+
"use strict";
20+
21+
// Execute the full-scope test
22+
SimpleTest.waitForExplicitFinish();
23+
24+
// Turn off all tokens. This should result in "not allowed" failures
25+
SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
26+
["security.webauth.webauthn_enable_softtoken", true],
27+
["security.webauth.webauthn_enable_usbtoken", false]]},
28+
function() {
29+
PublicKeyCredential.isPlatformAuthenticatorAvailable()
30+
.then(function(aResult) {
31+
// The specification requires this method, if will return false, to wait 10
32+
// minutes for anti-fingerprinting reasons. So we really can't test that
33+
// in an automated way.
34+
ok(aResult, "Should be available!");
35+
})
36+
.catch(function(aProblem) {
37+
is(false, "Problem encountered: " + aProblem);
38+
})
39+
.then(function() {
40+
SimpleTest.finish();
41+
})
42+
});
43+
44+
</script>
45+
46+
</body>
47+
</html>

dom/webauthn/tests/test_webauthn_loopback.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1
108108

109109
ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject");
110110
ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject");
111+
isnot(aAssertion.response.userId, undefined, "AuthenticatorAssertionResponse.UserId is defined")
111112

112113
ok(aAssertion.response.authenticatorData.byteLength > 0, "Authenticator data exists");
113114
let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
@@ -136,8 +137,8 @@ <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1
136137

137138
function testMakeCredential() {
138139
let rp = {id: document.domain, name: "none", icon: "none"};
139-
let user = {id: "none", name: "none", icon: "none", displayName: "none"};
140-
let param = {type: "public-key", algorithm: "ES256"};
140+
let user = {name: "none", icon: "none", displayName: "none"};
141+
let param = {type: "public-key", alg: "ES256"};
141142
let makeCredentialOptions = {
142143
rp: rp,
143144
user: user,
@@ -155,8 +156,8 @@ <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1
155156

156157
function testMakeDuplicate(aCredInfo) {
157158
let rp = {id: document.domain, name: "none", icon: "none"};
158-
let user = {id: "none", name: "none", icon: "none", displayName: "none"};
159-
let param = {type: "public-key", algorithm: "ES256"};
159+
let user = {name: "none", icon: "none", displayName: "none"};
160+
let param = {type: "public-key", alg: "ES256"};
160161
let makeCredentialOptions = {
161162
rp: rp,
162163
user: user,

0 commit comments

Comments
 (0)