summaryrefslogtreecommitdiffstats
path: root/dom/webauthn/tests/browser/browser_fido_appid_extension.js
blob: fca4443074aabed54b33e9ba6430e73f8259b082 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const TEST_URL = "https://example.com/";

let expectNotSupportedError = expectError("NotSupported");
let expectNotAllowedError = expectError("NotAllowed");
let expectSecurityError = expectError("Security");

let gAppId = "https://example.com/appId";
let gCrossOriginAppId = "https://example.org/appId";
let gAuthenticatorId = add_virtual_authenticator();

add_task(async function test_appid() {
  // Open a new tab.
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);

  // The FIDO AppId extension can't be used for MakeCredential.
  await promiseWebAuthnMakeCredential(tab, "none", "discouraged", {
    appid: gAppId,
  })
    .then(arrivingHereIsBad)
    .catch(expectNotSupportedError);

  // Side-load a credential with an RP ID matching the App ID.
  let credIdB64 = await addCredential(gAuthenticatorId, gAppId);
  let credId = base64ToBytesUrlSafe(credIdB64);

  // And another for a different origin
  let crossOriginCredIdB64 = await addCredential(
    gAuthenticatorId,
    gCrossOriginAppId
  );
  let crossOriginCredId = base64ToBytesUrlSafe(crossOriginCredIdB64);

  // The App ID extension is required
  await promiseWebAuthnGetAssertion(tab, credId)
    .then(arrivingHereIsBad)
    .catch(expectNotAllowedError);

  // The value in the App ID extension must match the origin.
  await promiseWebAuthnGetAssertion(tab, crossOriginCredId, {
    appid: gCrossOriginAppId,
  })
    .then(arrivingHereIsBad)
    .catch(expectSecurityError);

  // The value in the App ID extension must match the credential's RP ID.
  await promiseWebAuthnGetAssertion(tab, credId, { appid: gAppId + "2" })
    .then(arrivingHereIsBad)
    .catch(expectNotAllowedError);

  // Succeed with the right App ID.
  let rpIdHash = await promiseWebAuthnGetAssertion(tab, credId, {
    appid: gAppId,
  })
    .then(({ authenticatorData, extensions }) => {
      is(extensions.appid, true, "appid extension was acted upon");
      return authenticatorData.slice(0, 32);
    })
    .then(rpIdHash => {
      // Make sure the returned RP ID hash matches the hash of the App ID.
      checkRpIdHash(rpIdHash, gAppId);
    })
    .catch(arrivingHereIsBad);

  removeCredential(gAuthenticatorId, credIdB64);
  removeCredential(gAuthenticatorId, crossOriginCredIdB64);

  // Close tab.
  BrowserTestUtils.removeTab(tab);
});

add_task(async function test_appid_unused() {
  // Open a new tab.
  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_URL);

  let appid = "https://example.com/appId";

  let { attObj, rawId } = await promiseWebAuthnMakeCredential(tab);
  let { authDataObj } = await webAuthnDecodeCBORAttestation(attObj);

  // Make sure the RP ID hash matches what we calculate.
  await checkRpIdHash(authDataObj.rpIdHash, "example.com");

  // Get a new assertion.
  let { clientDataJSON, authenticatorData, signature, extensions } =
    await promiseWebAuthnGetAssertion(tab, rawId, { appid });

  ok(
    "appid" in extensions,
    `appid should be populated in the extensions data, but saw: ` +
      `${JSON.stringify(extensions)}`
  );
  is(extensions.appid, false, "appid extension should indicate it was unused");

  // Check auth data.
  let attestation = await webAuthnDecodeAuthDataArray(
    new Uint8Array(authenticatorData)
  );
  is(
    "" + (attestation.flags & flag_TUP),
    "" + flag_TUP,
    "Assertion's user presence byte set correctly"
  );

  // Verify the signature.
  let params = await deriveAppAndChallengeParam(
    "example.com",
    clientDataJSON,
    attestation
  );
  let signedData = await assembleSignedData(
    params.appParam,
    params.attestation.flags,
    params.attestation.counter,
    params.challengeParam
  );
  let valid = await verifySignature(
    authDataObj.publicKeyHandle,
    signedData,
    signature
  );
  ok(valid, "signature is valid");

  // Close tab.
  BrowserTestUtils.removeTab(tab);
});