tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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>