diff options
Diffstat (limited to 'dom/webauthn/tests/test_webauthn_loopback.html')
-rw-r--r-- | dom/webauthn/tests/test_webauthn_loopback.html | 213 |
1 files changed, 213 insertions, 0 deletions
diff --git a/dom/webauthn/tests/test_webauthn_loopback.html b/dom/webauthn/tests/test_webauthn_loopback.html new file mode 100644 index 0000000000..5eb5df5c8c --- /dev/null +++ b/dom/webauthn/tests/test_webauthn_loopback.html @@ -0,0 +1,213 @@ +<!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"; + +// Execute the full-scope test +SimpleTest.waitForExplicitFinish(); + +SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn_testing_allow_direct_attestation", true]]}, +function() { +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); + + testMakeCredential(); + + 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 clientData = JSON.parse(buffer2string(aCredInfo.response.clientDataJSON)); + is(clientData.challenge, bytesToBase64UrlSafe(gCredentialChallenge), "Challenge is correct"); + is(clientData.origin, window.location.origin, "Origin is correct"); + is(clientData.type, "webauthn.create", "Type is correct"); + + return webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject) + .then(function(aAttestationObj) { + // Make sure the RP ID hash matches what we calculate. + return crypto.subtle.digest("SHA-256", string2buffer(document.domain)) + .then(function(calculatedRpIdHash) { + let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash)); + let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(aAttestationObj.authDataObj.rpIdHash)); + + is(calcHashStr, providedHashStr, + "Calculated RP ID hash must match what the browser derived."); + return Promise.resolve(aAttestationObj); + }); + }) + .then(function(aAttestationObj) { + ok(aAttestationObj.authDataObj.flags == (flag_TUP | flag_AT), + "User presence and Attestation Object must be the only flags set"); + + aCredInfo.clientDataObj = clientData; + aCredInfo.publicKeyHandle = aAttestationObj.authDataObj.publicKeyHandle; + aCredInfo.attestationObject = aAttestationObj.authDataObj.attestationAuthData; + return aCredInfo; + }); +} + + 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"); + + return webAuthnDecodeAuthDataArray(aAssertion.response.authenticatorData) + .then(function(aAttestation) { + ok(new Uint8Array(aAttestation.flags) == flag_TUP, "User presence must be the only flag set"); + is(aAttestation.counter.byteLength, 4, "Counter must be 4 bytes"); + return deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, aAttestation) + }) + .then(function(aParams) { + console.log(aParams); + console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON)); + console.log("ClientDataHash: ", hexEncode(aParams.challengeParam)); + return assembleSignedData(aParams.appParam, aParams.attestation.flags, + aParams.attestation.counter, aParams.challengeParam); + }) + .then(function(aSignedData) { + console.log(aPublicKey, aSignedData, aAssertion.response.signature); + return verifySignature(aPublicKey, aSignedData, aAssertion.response.signature); + }) +} + + function testMakeCredential() { + let rp = {id: document.domain, name: "none", icon: "none"}; + let user = {id: new Uint8Array(), name: "none", icon: "none", displayName: "none"}; + let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256}; + let makeCredentialOptions = { + rp, + user, + challenge: gCredentialChallenge, + pubKeyCredParams: [param], + attestation: "direct" + }; + credm.create({publicKey: makeCredentialOptions}) + .then(decodeCreatedCredential) + .then(testMakeDuplicate) + .catch(function(aReason) { + ok(false, aReason); + SimpleTest.finish(); + }); +} + + function testMakeDuplicate(aCredInfo) { + let rp = {id: document.domain, name: "none", icon: "none"}; + let user = {id: new Uint8Array(), name: "none", icon: "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"]}] + }; + credm.create({publicKey: makeCredentialOptions}) + .then(function() { + // We should have errored here! + ok(false, "The excludeList didn't stop a duplicate being created!"); + SimpleTest.finish(); + }) + .catch(function(aReason) { + ok(aReason.toString().startsWith("InvalidStateError"), "Expect InvalidStateError, got " + aReason); + testAssertion(aCredInfo); + }); +} + + 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] + }; + credm.get({publicKey: publicKeyCredentialRequestOptions}) + .then(function(aAssertion) { + /* Pass along the pubKey. */ + return checkAssertionAndSigValid(aCredInfo.publicKeyHandle, aAssertion); + }) + .then(function(aSigVerifyResult) { + ok(aSigVerifyResult, "Signing signature verified"); + SimpleTest.finish(); + }) + .catch(function(reason) { + ok(false, "Signing signature invalid: " + reason); + SimpleTest.finish(); + }); +} +}); + +</script> + +</body> +</html> |