183 lines
9.4 KiB
HTML
183 lines
9.4 KiB
HTML
<!DOCTYPE html>
|
|
<meta charset=utf-8>
|
|
<head>
|
|
<title>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</title>
|
|
<script src="/tests/SimpleTest/SimpleTest.js"></script>
|
|
<script type="text/javascript" src="u2futil.js"></script>
|
|
<script type="text/javascript" src="pkijs/common.js"></script>
|
|
<script type="text/javascript" src="pkijs/asn1.js"></script>
|
|
<script type="text/javascript" src="pkijs/x509_schema.js"></script>
|
|
<script type="text/javascript" src="pkijs/x509_simpl.js"></script>
|
|
<script type="text/javascript" src="cbor.js"></script>
|
|
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
|
</head>
|
|
<body>
|
|
|
|
<h1>Full-run test for MakeCredential/GetAssertion 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";
|
|
|
|
add_task(async function() {
|
|
// This test intentionally compares items to themselves.
|
|
/* eslint-disable no-self-compare */
|
|
await SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn_testing_allow_direct_attestation", true]]});
|
|
await addVirtualAuthenticator();
|
|
is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
|
|
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");
|
|
|
|
let credm = navigator.credentials;
|
|
|
|
let gCredentialChallenge = new Uint8Array(16);
|
|
window.crypto.getRandomValues(gCredentialChallenge);
|
|
let gAssertionChallenge = new Uint8Array(16);
|
|
window.crypto.getRandomValues(gAssertionChallenge);
|
|
|
|
await testMakeCredential();
|
|
|
|
async function decodeCreatedCredential(aCredInfo) {
|
|
/* PublicKeyCredential : Credential
|
|
- rawId: Key Handle buffer pulled from U2F Register() Response
|
|
- id: Key Handle buffer in base64url form, should == rawId
|
|
- type: Literal 'public-key'
|
|
- response : AuthenticatorAttestationResponse : AuthenticatorResponse
|
|
- attestationObject: CBOR object
|
|
- clientDataJSON: serialized JSON
|
|
*/
|
|
|
|
is(aCredInfo.type, "public-key", "Credential type must be public-key")
|
|
|
|
ok(aCredInfo.rawId.byteLength > 0, "Key ID exists");
|
|
is(aCredInfo.id, bytesToBase64UrlSafe(aCredInfo.rawId), "Encoded Key ID and Raw Key ID match");
|
|
|
|
ok(aCredInfo.rawId === aCredInfo.rawId, "PublicKeyCredential.RawID is SameObject");
|
|
ok(aCredInfo.response === aCredInfo.response, "PublicKeyCredential.Response is SameObject");
|
|
ok(aCredInfo.response.clientDataJSON === aCredInfo.response.clientDataJSON, "PublicKeyCredential.Response.ClientDataJSON is SameObject");
|
|
ok(aCredInfo.response.attestationObject === aCredInfo.response.attestationObject, "PublicKeyCredential.Response.AttestationObject is SameObject");
|
|
|
|
let clientDataJSON = buffer2string(aCredInfo.response.clientDataJSON);
|
|
let clientData = JSON.parse(clientDataJSON);
|
|
let challengeB64 = bytesToBase64UrlSafe(gCredentialChallenge);
|
|
let expectedClientDataJSON = '{"type":"webauthn.create","challenge":"'+challengeB64+'","origin":"'+window.location.origin+'","crossOrigin":false}';
|
|
is(clientData.challenge, challengeB64, "Challenge is correct");
|
|
is(clientData.origin, window.location.origin, "Origin is correct");
|
|
is(clientData.type, "webauthn.create", "Type is correct");
|
|
is(clientDataJSON, expectedClientDataJSON, "clientData is in the correct order for limited verification");
|
|
|
|
let attestationObj = await webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject);
|
|
// Make sure the RP ID hash matches what we calculate.
|
|
let calculatedRpIdHash = await crypto.subtle.digest("SHA-256", string2buffer(document.domain));
|
|
let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash));
|
|
let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(attestationObj.authDataObj.rpIdHash));
|
|
is(calcHashStr, providedHashStr, "Calculated RP ID hash must match what the browser derived.");
|
|
ok(true, attestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT));
|
|
ok(attestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT) == (flag_TUP | flag_AT),
|
|
"User presence and Attestation Object flags must be set");
|
|
aCredInfo.clientDataObj = clientData;
|
|
aCredInfo.publicKeyHandle = attestationObj.authDataObj.publicKeyHandle;
|
|
aCredInfo.attestationObject = attestationObj.authDataObj.attestationAuthData;
|
|
return aCredInfo;
|
|
}
|
|
|
|
async function checkAssertionAndSigValid(aPublicKey, aAssertion) {
|
|
/* PublicKeyCredential : Credential
|
|
- rawId: ID of Credential from AllowList that succeeded
|
|
- id: Key Handle buffer in base64url form, should == rawId
|
|
- type: Literal 'public-key'
|
|
- response : AuthenticatorAssertionResponse : AuthenticatorResponse
|
|
- clientDataJSON: serialized JSON
|
|
- authenticatorData: RP ID Hash || U2F Sign() Response
|
|
- signature: U2F Sign() Response
|
|
*/
|
|
is(aAssertion.type, "public-key", "Credential type must be public-key")
|
|
ok(aAssertion.rawId.byteLength > 0, "Key ID exists");
|
|
is(aAssertion.id, bytesToBase64UrlSafe(new Uint8Array(aAssertion.rawId)), "Encoded Key ID and Raw Key ID match");
|
|
ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject");
|
|
ok(aAssertion.response.authenticatorData instanceof ArrayBuffer, "AuthenticatorAssertionResponse.AuthenticatorData is an ArrayBuffer");
|
|
ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject");
|
|
ok(aAssertion.response.signature instanceof ArrayBuffer, "AuthenticatorAssertionResponse.Signature is an ArrayBuffer");
|
|
ok(aAssertion.response.userHandle === null, "AuthenticatorAssertionResponse.UserHandle is null for u2f authenticators");
|
|
|
|
ok(aAssertion.response.authenticatorData.byteLength > 0, "Authenticator data exists");
|
|
let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
|
|
is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
|
|
is(clientData.origin, window.location.origin, "Origin is correct");
|
|
is(clientData.type, "webauthn.get", "Type is correct");
|
|
|
|
let attestation = await webAuthnDecodeAuthDataArray(aAssertion.response.authenticatorData);
|
|
ok(new Uint8Array(attestation.flags)[0] & flag_TUP == flag_TUP, "User presence flag must be set");
|
|
is(attestation.counter.byteLength, 4, "Counter must be 4 bytes");
|
|
let params = await deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, attestation);
|
|
console.log(params);
|
|
console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON));
|
|
console.log("ClientDataHash: ", hexEncode(params.challengeParam));
|
|
let signedData = await assembleSignedData(params.appParam, params.attestation.flags,
|
|
params.attestation.counter, params.challengeParam);
|
|
console.log(aPublicKey, signedData, aAssertion.response.signature);
|
|
return await verifySignature(aPublicKey, signedData, aAssertion.response.signature);
|
|
}
|
|
|
|
async function testMakeCredential() {
|
|
let rp = {id: document.domain, name: "none"};
|
|
let user = {id: new Uint8Array(), name: "none", displayName: "none"};
|
|
let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
|
|
let makeCredentialOptions = {
|
|
rp,
|
|
user,
|
|
challenge: gCredentialChallenge,
|
|
pubKeyCredParams: [param],
|
|
attestation: "direct"
|
|
};
|
|
let credential = await credm.create({publicKey: makeCredentialOptions})
|
|
let decodedCredential = await decodeCreatedCredential(credential);
|
|
await testMakeDuplicate(decodedCredential);
|
|
}
|
|
|
|
async function testMakeDuplicate(aCredInfo) {
|
|
let rp = {id: document.domain, name: "none"};
|
|
let user = {id: new Uint8Array(), name: "none", displayName: "none"};
|
|
let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256};
|
|
let makeCredentialOptions = {
|
|
rp,
|
|
user,
|
|
challenge: gCredentialChallenge,
|
|
pubKeyCredParams: [param],
|
|
excludeCredentials: [{type: "public-key", id: new Uint8Array(aCredInfo.rawId),
|
|
transports: ["usb"]}]
|
|
};
|
|
await credm.create({publicKey: makeCredentialOptions})
|
|
.then(function() {
|
|
// We should have errored here!
|
|
ok(false, "The excludeList didn't stop a duplicate being created!");
|
|
}).catch((aReason) => {
|
|
ok(aReason.toString().startsWith("InvalidStateError"), "Expect InvalidStateError, got " + aReason);
|
|
});
|
|
await testAssertion(aCredInfo);
|
|
}
|
|
|
|
async function testAssertion(aCredInfo) {
|
|
let newCredential = {
|
|
type: "public-key",
|
|
id: new Uint8Array(aCredInfo.rawId),
|
|
transports: ["usb"],
|
|
}
|
|
|
|
let publicKeyCredentialRequestOptions = {
|
|
challenge: gAssertionChallenge,
|
|
timeout: 5000, // the minimum timeout is actually 15 seconds
|
|
rpId: document.domain,
|
|
allowCredentials: [newCredential]
|
|
};
|
|
let assertion = await credm.get({publicKey: publicKeyCredentialRequestOptions});
|
|
let sigVerifyResult = await checkAssertionAndSigValid(aCredInfo.publicKeyHandle, assertion);
|
|
ok(sigVerifyResult, "Signing signature verified");
|
|
}
|
|
});
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|