tor-browser

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

browser_fido_appid_extension.js (5502B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
      3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const TEST_URL = "https://example.com/";
      8 
      9 let expectNotSupportedError = expectError("NotSupported");
     10 let expectNotAllowedError = expectError("NotAllowed");
     11 let expectSecurityError = expectError("Security");
     12 
     13 let gAppId = "https://example.com/appId";
     14 let gSameSiteAppId = "https://subdomain.example.com/appId";
     15 let gCrossOriginAppId = "https://example.org/appId";
     16 let gAuthenticatorId = add_virtual_authenticator();
     17 
     18 add_task(async function test_appid() {
     19  // Open a new tab.
     20  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
     21 
     22  // The FIDO AppId extension can't be used for MakeCredential.
     23  await promiseWebAuthnMakeCredential(tab, "none", "discouraged", {
     24    appid: gAppId,
     25  })
     26    .then(arrivingHereIsBad)
     27    .catch(expectNotSupportedError);
     28 
     29  // Side-load a credential with an RP ID matching the App ID.
     30  let credIdB64 = await addCredential(gAuthenticatorId, gAppId);
     31  let credId = base64ToBytesUrlSafe(credIdB64);
     32 
     33  // And another for a different origin
     34  let crossOriginCredIdB64 = await addCredential(
     35    gAuthenticatorId,
     36    gCrossOriginAppId
     37  );
     38  let crossOriginCredId = base64ToBytesUrlSafe(crossOriginCredIdB64);
     39 
     40  // The App ID extension is required
     41  await promiseWebAuthnGetAssertion(tab, credId)
     42    .then(arrivingHereIsBad)
     43    .catch(expectNotAllowedError);
     44 
     45  // The value in the App ID extension must match the origin.
     46  await promiseWebAuthnGetAssertion(tab, crossOriginCredId, {
     47    appid: gCrossOriginAppId,
     48  })
     49    .then(arrivingHereIsBad)
     50    .catch(expectSecurityError);
     51 
     52  // The value in the App ID extension must match the credential's RP ID.
     53  await promiseWebAuthnGetAssertion(tab, credId, { appid: gAppId + "2" })
     54    .then(arrivingHereIsBad)
     55    .catch(expectNotAllowedError);
     56 
     57  // Succeed with the right App ID.
     58  let rpIdHash = await promiseWebAuthnGetAssertion(tab, credId, {
     59    appid: gAppId,
     60  })
     61    .then(({ authenticatorData, extensions }) => {
     62      is(extensions.appid, true, "appid extension was acted upon");
     63      return authenticatorData.slice(0, 32);
     64    })
     65    .then(rpIdHash => {
     66      // Make sure the returned RP ID hash matches the hash of the App ID.
     67      checkRpIdHash(rpIdHash, gAppId);
     68    })
     69    .catch(arrivingHereIsBad);
     70 
     71  removeCredential(gAuthenticatorId, credIdB64);
     72  removeCredential(gAuthenticatorId, crossOriginCredIdB64);
     73 
     74  // Close tab.
     75  BrowserTestUtils.removeTab(tab);
     76 });
     77 
     78 add_task(async function test_same_site_appid() {
     79  // Open a new tab.
     80  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
     81 
     82  // Side-load a credential with an RP ID matching the App ID.
     83  let credIdB64 = await addCredential(gAuthenticatorId, gSameSiteAppId);
     84  let credId = base64ToBytesUrlSafe(credIdB64);
     85 
     86  // A request without the AppID extension should fail.
     87  await promiseWebAuthnGetAssertion(tab, credId)
     88    .then(arrivingHereIsBad)
     89    .catch(expectNotAllowedError);
     90 
     91  // As should a request with a cross-origin AppID.
     92  await promiseWebAuthnGetAssertion(tab, credId, {
     93    appid: gCrossOriginAppId,
     94  })
     95    .then(arrivingHereIsBad)
     96    .catch(expectSecurityError);
     97 
     98  // A request with the correct AppID extension should succeed.
     99  let rpIdHash = await promiseWebAuthnGetAssertion(tab, credId, {
    100    appid: gSameSiteAppId,
    101  })
    102    .then(({ authenticatorData, extensions }) => {
    103      is(extensions.appid, true, "appid extension was acted upon");
    104      return authenticatorData.slice(0, 32);
    105    })
    106    .then(rpIdHash => {
    107      // Make sure the returned RP ID hash matches the hash of the App ID.
    108      checkRpIdHash(rpIdHash, gSameSiteAppId);
    109    })
    110    .catch(arrivingHereIsBad);
    111 
    112  removeCredential(gAuthenticatorId, credIdB64);
    113 
    114  // Close tab.
    115  BrowserTestUtils.removeTab(tab);
    116 });
    117 
    118 add_task(async function test_appid_unused() {
    119  // Open a new tab.
    120  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);
    121 
    122  let appid = "https://example.com/appId";
    123 
    124  let { attObj, rawId } = await promiseWebAuthnMakeCredential(tab);
    125  let { authDataObj } = await webAuthnDecodeCBORAttestation(attObj);
    126 
    127  // Make sure the RP ID hash matches what we calculate.
    128  await checkRpIdHash(authDataObj.rpIdHash, "example.com");
    129 
    130  // Get a new assertion.
    131  let { clientDataJSON, authenticatorData, signature, extensions } =
    132    await promiseWebAuthnGetAssertion(tab, rawId, { appid });
    133 
    134  ok(
    135    "appid" in extensions,
    136    `appid should be populated in the extensions data, but saw: ` +
    137      `${JSON.stringify(extensions)}`
    138  );
    139  is(extensions.appid, false, "appid extension should indicate it was unused");
    140 
    141  // Check auth data.
    142  let attestation = await webAuthnDecodeAuthDataArray(
    143    new Uint8Array(authenticatorData)
    144  );
    145  is(
    146    "" + (attestation.flags & flag_TUP),
    147    "" + flag_TUP,
    148    "Assertion's user presence byte set correctly"
    149  );
    150 
    151  // Verify the signature.
    152  let params = await deriveAppAndChallengeParam(
    153    "example.com",
    154    clientDataJSON,
    155    attestation
    156  );
    157  let signedData = await assembleSignedData(
    158    params.appParam,
    159    params.attestation.flags,
    160    params.attestation.counter,
    161    params.challengeParam
    162  );
    163  let valid = await verifySignature(
    164    authDataObj.publicKeyHandle,
    165    signedData,
    166    signature
    167  );
    168  ok(valid, "signature is valid");
    169 
    170  // Close tab.
    171  BrowserTestUtils.removeTab(tab);
    172 });