test_webauthn_loopback.html (9577B)
1 <!DOCTYPE html> 2 <meta charset=utf-8> 3 <head> 4 <title>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</title> 5 <script 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 <script type="text/javascript" src="cbor.js"></script> 12 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> 13 </head> 14 <body> 15 16 <h1>Full-run test for MakeCredential/GetAssertion for W3C Web Authentication</h1> 17 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a> 18 19 <script class="testbody" type="text/javascript"> 20 "use strict"; 21 22 add_task(async function() { 23 // This test intentionally compares items to themselves. 24 /* eslint-disable no-self-compare */ 25 await SpecialPowers.pushPrefEnv({"set": [["security.webauthn.always_allow_direct_attestation", true]]}); 26 await addVirtualAuthenticator(); 27 is(navigator.authentication, undefined, "navigator.authentication does not exist any longer"); 28 isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist"); 29 isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist"); 30 isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist"); 31 32 let credm = navigator.credentials; 33 34 let gCredentialChallenge = new Uint8Array(16); 35 window.crypto.getRandomValues(gCredentialChallenge); 36 let gAssertionChallenge = new Uint8Array(16); 37 window.crypto.getRandomValues(gAssertionChallenge); 38 39 await testMakeCredential(); 40 41 async function decodeCreatedCredential(aCredInfo) { 42 /* PublicKeyCredential : Credential 43 - rawId: Key Handle buffer pulled from U2F Register() Response 44 - id: Key Handle buffer in base64url form, should == rawId 45 - type: Literal 'public-key' 46 - response : AuthenticatorAttestationResponse : AuthenticatorResponse 47 - attestationObject: CBOR object 48 - clientDataJSON: serialized JSON 49 */ 50 51 is(aCredInfo.type, "public-key", "Credential type must be public-key") 52 53 ok(aCredInfo.rawId.byteLength > 0, "Key ID exists"); 54 is(aCredInfo.id, bytesToBase64UrlSafe(aCredInfo.rawId), "Encoded Key ID and Raw Key ID match"); 55 56 ok(aCredInfo.rawId === aCredInfo.rawId, "PublicKeyCredential.RawID is SameObject"); 57 ok(aCredInfo.response === aCredInfo.response, "PublicKeyCredential.Response is SameObject"); 58 ok(aCredInfo.response.clientDataJSON === aCredInfo.response.clientDataJSON, "PublicKeyCredential.Response.ClientDataJSON is SameObject"); 59 ok(aCredInfo.response.attestationObject === aCredInfo.response.attestationObject, "PublicKeyCredential.Response.AttestationObject is SameObject"); 60 61 let clientDataJSON = buffer2string(aCredInfo.response.clientDataJSON); 62 let clientData = JSON.parse(clientDataJSON); 63 let challengeB64 = bytesToBase64UrlSafe(gCredentialChallenge); 64 let expectedClientDataJSON = '{"type":"webauthn.create","challenge":"'+challengeB64+'","origin":"'+window.location.origin+'","crossOrigin":false}'; 65 is(clientData.challenge, challengeB64, "Challenge is correct"); 66 is(clientData.origin, window.location.origin, "Origin is correct"); 67 is(clientData.type, "webauthn.create", "Type is correct"); 68 is(clientDataJSON, expectedClientDataJSON, "clientData is in the correct order for limited verification"); 69 70 let attestationObj = await webAuthnDecodeCBORAttestation(aCredInfo.response.attestationObject); 71 // Make sure the RP ID hash matches what we calculate. 72 let calculatedRpIdHash = await crypto.subtle.digest("SHA-256", string2buffer(document.domain)); 73 let calcHashStr = bytesToBase64UrlSafe(new Uint8Array(calculatedRpIdHash)); 74 let providedHashStr = bytesToBase64UrlSafe(new Uint8Array(attestationObj.authDataObj.rpIdHash)); 75 is(calcHashStr, providedHashStr, "Calculated RP ID hash must match what the browser derived."); 76 ok(true, attestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT)); 77 ok(attestationObj.authDataObj.flags[0] & (flag_TUP | flag_AT) == (flag_TUP | flag_AT), 78 "User presence and Attestation Object flags must be set"); 79 aCredInfo.clientDataObj = clientData; 80 aCredInfo.publicKeyHandle = attestationObj.authDataObj.publicKeyHandle; 81 aCredInfo.attestationObject = attestationObj.authDataObj.attestationAuthData; 82 return aCredInfo; 83 } 84 85 async function checkAssertionAndSigValid(aPublicKey, aAssertion) { 86 /* PublicKeyCredential : Credential 87 - rawId: ID of Credential from AllowList that succeeded 88 - id: Key Handle buffer in base64url form, should == rawId 89 - type: Literal 'public-key' 90 - response : AuthenticatorAssertionResponse : AuthenticatorResponse 91 - clientDataJSON: serialized JSON 92 - authenticatorData: RP ID Hash || U2F Sign() Response 93 - signature: U2F Sign() Response 94 */ 95 is(aAssertion.type, "public-key", "Credential type must be public-key") 96 ok(aAssertion.rawId.byteLength > 0, "Key ID exists"); 97 is(aAssertion.id, bytesToBase64UrlSafe(new Uint8Array(aAssertion.rawId)), "Encoded Key ID and Raw Key ID match"); 98 ok(aAssertion.response.authenticatorData === aAssertion.response.authenticatorData, "AuthenticatorAssertionResponse.AuthenticatorData is SameObject"); 99 ok(aAssertion.response.authenticatorData instanceof ArrayBuffer, "AuthenticatorAssertionResponse.AuthenticatorData is an ArrayBuffer"); 100 ok(aAssertion.response.signature === aAssertion.response.signature, "AuthenticatorAssertionResponse.Signature is SameObject"); 101 ok(aAssertion.response.signature instanceof ArrayBuffer, "AuthenticatorAssertionResponse.Signature is an ArrayBuffer"); 102 ok(aAssertion.response.userHandle === null, "AuthenticatorAssertionResponse.UserHandle is null for u2f authenticators"); 103 104 ok(aAssertion.response.authenticatorData.byteLength > 0, "Authenticator data exists"); 105 let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON)); 106 is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct"); 107 is(clientData.origin, window.location.origin, "Origin is correct"); 108 is(clientData.type, "webauthn.get", "Type is correct"); 109 110 let attestation = await webAuthnDecodeAuthDataArray(aAssertion.response.authenticatorData); 111 ok(new Uint8Array(attestation.flags)[0] & flag_TUP == flag_TUP, "User presence flag must be set"); 112 is(attestation.counter.byteLength, 4, "Counter must be 4 bytes"); 113 let params = await deriveAppAndChallengeParam(window.location.host, aAssertion.response.clientDataJSON, attestation); 114 console.log(params); 115 console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON)); 116 console.log("ClientDataHash: ", hexEncode(params.challengeParam)); 117 let signedData = await assembleSignedData(params.appParam, params.attestation.flags, 118 params.attestation.counter, params.challengeParam); 119 console.log(aPublicKey, signedData, aAssertion.response.signature); 120 return await verifySignature(aPublicKey, signedData, aAssertion.response.signature); 121 } 122 123 async function testMakeCredential() { 124 let rp = {id: document.domain, name: "none"}; 125 let user = {id: new Uint8Array(), name: "none", displayName: "none"}; 126 let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256}; 127 let makeCredentialOptions = { 128 rp, 129 user, 130 challenge: gCredentialChallenge, 131 pubKeyCredParams: [param], 132 attestation: "direct" 133 }; 134 let credential = await credm.create({publicKey: makeCredentialOptions}) 135 let decodedCredential = await decodeCreatedCredential(credential); 136 await testMakeDuplicate(decodedCredential); 137 } 138 139 async function testMakeDuplicate(aCredInfo) { 140 let rp = {id: document.domain, name: "none"}; 141 let user = {id: new Uint8Array(), name: "none", displayName: "none"}; 142 let param = {type: "public-key", alg: cose_alg_ECDSA_w_SHA256}; 143 let makeCredentialOptions = { 144 rp, 145 user, 146 challenge: gCredentialChallenge, 147 pubKeyCredParams: [param], 148 excludeCredentials: [{type: "public-key", id: new Uint8Array(aCredInfo.rawId), 149 transports: ["usb"]}] 150 }; 151 await credm.create({publicKey: makeCredentialOptions}) 152 .then(function() { 153 // We should have errored here! 154 ok(false, "The excludeList didn't stop a duplicate being created!"); 155 }).catch((aReason) => { 156 ok(aReason.toString().startsWith("InvalidStateError"), "Expect InvalidStateError, got " + aReason); 157 }); 158 await testAssertion(aCredInfo); 159 } 160 161 async function testAssertion(aCredInfo) { 162 let newCredential = { 163 type: "public-key", 164 id: new Uint8Array(aCredInfo.rawId), 165 transports: ["usb"], 166 } 167 168 let publicKeyCredentialRequestOptions = { 169 challenge: gAssertionChallenge, 170 timeout: 5000, // the minimum timeout is actually 15 seconds 171 rpId: document.domain, 172 allowCredentials: [newCredential] 173 }; 174 let assertion = await credm.get({publicKey: publicKeyCredentialRequestOptions}); 175 let sigVerifyResult = await checkAssertionAndSigValid(aCredInfo.publicKeyHandle, assertion); 176 ok(sigVerifyResult, "Signing signature verified"); 177 } 178 }); 179 180 </script> 181 182 </body> 183 </html>