tor-browser

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

utils-bbk.js (4864B)


      1 'use strict';
      2 
      3 // See https://www.iana.org/assignments/cose/cose.xhtml#key-type
      4 const cose_key_type_ec2 = 2;
      5 const cose_key_type_rsa = 3;
      6 
      7 // Decode |encoded| using a base64url decoding.
      8 function base64urlToUint8Array(encoded) {
      9  return Uint8Array.from(base64urlDecode(encoded), c => c.charCodeAt(0));
     10 }
     11 
     12 // The result of a browser bound key verification.
     13 const BrowserBoundKeyVerificationResult = Object.freeze({
     14  // No browser bound key was included.
     15  NoBrowserBoundKey: 'NoBrowserBoundKey',
     16  // A browser bound key was included and the cryptographic signature verifies.
     17  BrowserBoundKeySignatureVerified: 'BrowserBoundKeySignatureVerified',
     18 });
     19 
     20 // This function takes a credential and verifies either that no BBK was
     21 // included (no browser bound public key, and no browser bound signature)
     22 // or that the BBK was included (browser bound public key, browser bound
     23 // signature, and the signature cryptographically verifies).
     24 //
     25 // Returns a BrowserBoundKeyVerificationResult informing the conditions of
     26 // successful verification.
     27 async function verifyBrowserBoundKey(credential, expectedKeyTypes) {
     28  const clientExtensionResults = credential.getClientExtensionResults();
     29  const signatureArray =
     30      clientExtensionResults?.payment?.browserBoundSignature?.signature;
     31  const clientData = JSON.parse(String.fromCharCode.apply(
     32      null, new Uint8Array(credential.response.clientDataJSON)));
     33  const publicKeyCoseKeyEncoded = clientData?.payment?.browserBoundPublicKey;
     34  assert_equals(
     35      signatureArray !== undefined, publicKeyCoseKeyEncoded !== undefined,
     36      'Either both or none of the browser bound public key and signature must ' +
     37          'be present, but only one was present.')
     38  if (signatureArray == undefined) {
     39    return BrowserBoundKeyVerificationResult.NoBrowserBoundKey;
     40  }
     41  assertBrowserBoundSignatureInClientExtensionResults(clientExtensionResults);
     42  await assertBrowserBoundKeySignature(
     43      credential.response.clientDataJSON, signatureArray, expectedKeyTypes);
     44  return BrowserBoundKeyVerificationResult.BrowserBoundKeySignatureVerified;
     45 }
     46 
     47 function getBrowserBoundPublicKeyFromCredential(credential) {
     48  const clientData = JSON.parse(String.fromCharCode.apply(
     49      null, new Uint8Array(credential.response.clientDataJSON)));
     50  return clientData?.payment?.browserBoundPublicKey;
     51 }
     52 
     53 function assertNoBrowserBoundPublicKeyInCredential(credential, message) {
     54  const clientData = JSON.parse(String.fromCharCode.apply(
     55      null, new Uint8Array(credential.response.clientDataJSON)));
     56  assert_equals(clientData?.payment?.browserBoundPublicKey, undefined, message);
     57 }
     58 
     59 function assertBrowserBoundSignatureInClientExtensionResults(
     60    clientExtensionResults) {
     61  assert_not_equals(
     62      clientExtensionResults.payment, undefined,
     63      'getClientExtensionResults().payment is not undefined');
     64  assert_not_equals(
     65      clientExtensionResults.payment.browserBoundSignature, undefined,
     66      'getClientExtensionResults().payment is not undefined');
     67  assert_not_equals(
     68      clientExtensionResults.payment.browserBoundSignature.signature, undefined,
     69      'getClientExtensionResults().payment.signature is not undefined');
     70 }
     71 
     72 async function assertBrowserBoundKeySignature(
     73    clientDataJSON, signatureArray, expectedKeyTypes) {
     74  const clientData = JSON.parse(
     75      String.fromCharCode.apply(null, new Uint8Array(clientDataJSON)));
     76  assert_not_equals(
     77      clientData.payment, undefined,
     78      `Deserialized clientData, ${
     79          JSON.stringify(clientData)}, should contain a 'payment' member`);
     80  assert_not_equals(
     81      clientData.payment.browserBoundPublicKey, undefined,
     82      `ClientData['payment'] should contain a 'browserBoundPublicKey' member.`);
     83  const browserBoundPublicKeyCoseKeyBase64 =
     84      clientData.payment.browserBoundPublicKey;
     85  const browserBoundPublicKeyCoseKeyEncoded =
     86      base64urlToUint8Array(browserBoundPublicKeyCoseKeyBase64);
     87  const keyType = getCoseKeyType(browserBoundPublicKeyCoseKeyEncoded);
     88  assert_true(
     89      expectedKeyTypes.includes(keyType),
     90      `KeyType, ${keyType}, was not one of the expected key types, ${
     91          expectedKeyTypes}`);
     92  if (keyType == cose_key_type_ec2) {
     93    // Verify the signature for a ES256 signature scheme.
     94    const browserBoundPublicKeyCoseKey =
     95        parseCosePublicKey(browserBoundPublicKeyCoseKeyEncoded);
     96    const jwkPublicKey = coseObjectToJWK(browserBoundPublicKeyCoseKey);
     97    const key = await crypto.subtle.importKey(
     98        'jwk', jwkPublicKey, {name: 'ECDSA', namedCurve: 'P-256'},
     99        /*extractable=*/ false, ['verify']);
    100    const signature =
    101        convertDERSignatureToSubtle(new Uint8Array(signatureArray));
    102    assert_true(await crypto.subtle.verify(
    103        {name: 'ECDSA', hash: 'SHA-256'}, key, signature, clientDataJSON));
    104  }
    105  // TODO: Verify the signature in case of an RS256 signature scheme.
    106 }