tor-browser

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

test_qwacs.js (16432B)


      1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
      2 // This Source Code Form is subject to the terms of the Mozilla Public
      3 // License, v. 2.0. If a copy of the MPL was not distributed with this
      4 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
      5 
      6 "use strict";
      7 
      8 do_get_profile(); // must be called before getting nsIX509CertDB
      9 const certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
     10  Ci.nsIX509CertDB
     11 );
     12 
     13 const { QWACs } = ChromeUtils.importESModule(
     14  "resource://gre/modules/psm/QWACs.sys.mjs"
     15 );
     16 
     17 async function verify_1_qwacs(filename, expectSuccess, extraCertNames = []) {
     18  let cert = constructCertFromFile(filename);
     19  let result = await certdb.asyncVerifyQWAC(
     20    Ci.nsIX509CertDB.OneQWAC,
     21    cert,
     22    "example.com",
     23    extraCertNames.map(filename => constructCertFromFile(filename))
     24  );
     25  equal(
     26    result,
     27    expectSuccess,
     28    `${filename} ${expectSuccess ? "should" : "should not"} verify as 1-QWAC`
     29  );
     30 }
     31 
     32 add_task(async function test_verify_1_qwacs() {
     33  Services.prefs.clearUserPref("security.qwacs.enable_test_trust_anchors");
     34  // By default, the QWACs test trust anchors are not used.
     35  await verify_1_qwacs("test_qwacs/1-qwac.pem", false);
     36  await verify_1_qwacs("test_qwacs/1-qwac-qevcpw.pem", false);
     37 
     38  Services.prefs.setBoolPref("security.qwacs.enable_test_trust_anchors", true);
     39 
     40  await verify_1_qwacs("test_qwacs/1-qwac.pem", true);
     41  await verify_1_qwacs("test_qwacs/1-qwac-qevcpw.pem", true);
     42 
     43  await verify_1_qwacs("test_qwacs/1-qwac-other-optional-qcs.pem", true);
     44 
     45  // One or more intermediates may be necessary for path building.
     46  await verify_1_qwacs("test_qwacs/1-qwac-via-intermediate.pem", false);
     47  await verify_1_qwacs("test_qwacs/1-qwac-via-intermediate.pem", true, [
     48    "test_qwacs/test-int.pem",
     49  ]);
     50 
     51  await verify_1_qwacs("test_qwacs/empty-qc-type-statement.pem", false);
     52  await verify_1_qwacs("test_qwacs/missing-qc-type-statement.pem", false);
     53  await verify_1_qwacs("test_qwacs/missing-qcs-compliance.pem", false);
     54  await verify_1_qwacs("test_qwacs/wrong-qc-type.pem", false);
     55  await verify_1_qwacs("test_qwacs/no-1-qwac-policies.pem", false);
     56  await verify_1_qwacs("test_qwacs/no-policies.pem", false);
     57  await verify_1_qwacs("test_qwacs/2-qwac.pem", false);
     58 });
     59 
     60 async function verify_2_qwacs(
     61  filename,
     62  expectSuccess,
     63  hostname = "example.com"
     64 ) {
     65  let cert = constructCertFromFile(filename);
     66  let result = await certdb.asyncVerifyQWAC(
     67    Ci.nsIX509CertDB.TwoQWAC,
     68    cert,
     69    hostname,
     70    []
     71  );
     72  equal(
     73    result,
     74    expectSuccess,
     75    `${filename} ${expectSuccess ? "should" : "should not"} verify as 2-QWAC`
     76  );
     77 }
     78 
     79 add_task(async function test_verify_2_qwacs() {
     80  Services.prefs.clearUserPref("security.qwacs.enable_test_trust_anchors");
     81  // By default, the QWACs test trust anchors are not used.
     82  await verify_2_qwacs("test_qwacs/2-qwac.pem", false);
     83 
     84  Services.prefs.setBoolPref("security.qwacs.enable_test_trust_anchors", true);
     85 
     86  await verify_2_qwacs("test_qwacs/2-qwac.pem", true);
     87 
     88  await verify_2_qwacs("test_qwacs/1-qwac.pem", false);
     89  await verify_2_qwacs("test_qwacs/2-qwac-no-eku.pem", false);
     90  await verify_2_qwacs("test_qwacs/2-qwac-tls-server-eku.pem", false);
     91  await verify_2_qwacs("test_qwacs/2-qwac-multiple-key-purpose-eku.pem", false);
     92  await verify_2_qwacs("test_qwacs/2-qwac.pem", false, "example.org");
     93 });
     94 
     95 // Produces base64url(hash(base64url(certificate DER)))
     96 async function certificateHash(certificate, hashAlg) {
     97  let hash = await crypto.subtle.digest(
     98    hashAlg.replace("S", "SHA-"),
     99    new Uint8Array(
    100      stringToArray(
    101        QWACs.toBase64URLEncoding(arrayToString(certificate.getRawDER()))
    102      )
    103    )
    104  );
    105  return QWACs.toBase64URLEncoding(arrayToString(new Uint8Array(hash)));
    106 }
    107 
    108 const kTLSCertificateBindingEE = constructCertFromFile("test_qwacs/2-qwac.pem");
    109 
    110 const kTLSCertificateBindingHeader = {
    111  alg: "",
    112  cty: "TLS-Certificate-Binding-v1",
    113  // RFC 7515 Section 4.1.6: "Each string in the array is a base64-encoded
    114  // (Section 4 of [RFC4648] -- not base64url-encoded) DER [ITU.X690.2008] PKIX
    115  // certificate value."
    116  x5c: [],
    117  sigD: {
    118    mId: "http://uri.etsi.org/19182/ObjectIdByURIHash",
    119    pars: [],
    120    hashM: "",
    121    hashV: [],
    122  },
    123 };
    124 
    125 async function makeBindingHeader(
    126  certificateChain,
    127  certificatesToBind,
    128  signingAlg,
    129  hashAlg
    130 ) {
    131  let header = structuredClone(kTLSCertificateBindingHeader);
    132  header.alg = signingAlg;
    133  header.x5c = certificateChain.map(c => btoa(arrayToString(c.getRawDER())));
    134  header.sigD.hashM = hashAlg;
    135  for (let toBind of certificatesToBind) {
    136    header.sigD.pars.push("");
    137    header.sigD.hashV.push(await certificateHash(toBind, hashAlg));
    138  }
    139  return header;
    140 }
    141 
    142 add_task(async function test_validate_tls_certificate_binding_header() {
    143  let serverCertificate = constructCertFromFile("bad_certs/default-ee.pem");
    144  let testHeader = await makeBindingHeader(
    145    [kTLSCertificateBindingEE],
    146    [serverCertificate],
    147    "RS256",
    148    "S256"
    149  );
    150  let validatedHeader = QWACs.validateTLSCertificateBindingHeader(testHeader);
    151  ok(validatedHeader, "header should validate successfully");
    152  deepEqual(validatedHeader.algorithm, {
    153    name: "RSASSA-PKCS1-v1_5",
    154    hash: "SHA-256",
    155  });
    156  equal(validatedHeader.certificates.length, 1);
    157  equal(validatedHeader.hashAlg, "SHA-256");
    158  equal(validatedHeader.hashes.length, 1);
    159 
    160  let headerWithExtraKey = structuredClone(testHeader);
    161  headerWithExtraKey.extra = "foo";
    162  ok(
    163    !QWACs.validateTLSCertificateBindingHeader(headerWithExtraKey),
    164    "header with extra key should not validate"
    165  );
    166 
    167  let headerWithExtraSigDKey = structuredClone(testHeader);
    168  headerWithExtraSigDKey.sigD.additional = "bar";
    169  ok(
    170    !QWACs.validateTLSCertificateBindingHeader(headerWithExtraSigDKey),
    171    "header with extra key in sigD should not parse"
    172  );
    173 
    174  let headerWithWrongCty = structuredClone(testHeader);
    175  headerWithWrongCty.cty = "TLS-Certificate-Binding-v2";
    176  ok(
    177    !QWACs.validateTLSCertificateBindingHeader(headerWithWrongCty),
    178    "header with wrong cty should not parse"
    179  );
    180 
    181  let headerWithWrongMId = structuredClone(testHeader);
    182  headerWithWrongMId.sigD.mId = "http://example.org";
    183  ok(
    184    !QWACs.validateTLSCertificateBindingHeader(headerWithWrongMId),
    185    "header with wrong sigD.mId should not parse"
    186  );
    187 
    188  let headerWithTooManyPars = structuredClone(testHeader);
    189  headerWithTooManyPars.sigD.pars.push("");
    190  ok(
    191    !QWACs.validateTLSCertificateBindingHeader(headerWithTooManyPars),
    192    "header with too many sigD.pars elements should not parse"
    193  );
    194 
    195  let headerWithInvalidHashV = structuredClone(testHeader);
    196  headerWithInvalidHashV.sigD.hashV[0] = 1234;
    197  ok(
    198    !QWACs.validateTLSCertificateBindingHeader(headerWithInvalidHashV),
    199    "header with invalid sigD.hashV should not parse"
    200  );
    201 
    202  let headerWithTooManyHashV = structuredClone(testHeader);
    203  headerWithTooManyHashV.sigD.hashV.push(headerWithTooManyHashV.sigD.hashV[0]);
    204  ok(
    205    !QWACs.validateTLSCertificateBindingHeader(headerWithTooManyHashV),
    206    "header with too many sigD.hashV elements should not parse"
    207  );
    208 
    209  let headerWithEmptyX5c = structuredClone(testHeader);
    210  headerWithEmptyX5c.x5c = [];
    211  ok(
    212    !QWACs.validateTLSCertificateBindingHeader(headerWithEmptyX5c),
    213    "header with empty x5c should not parse"
    214  );
    215 
    216  let headerWithUnsupportedAlg = structuredClone(testHeader);
    217  headerWithUnsupportedAlg.alg = "RS384";
    218  ok(
    219    !QWACs.validateTLSCertificateBindingHeader(headerWithUnsupportedAlg),
    220    "header with unsupported alg should not parse"
    221  );
    222 
    223  let headerWithUnsupportedHashM = structuredClone(testHeader);
    224  headerWithUnsupportedHashM.sigD.hashM = "S224";
    225  ok(
    226    !QWACs.validateTLSCertificateBindingHeader(headerWithUnsupportedHashM),
    227    "header with unsupported sigD.hashM should not parse"
    228  );
    229 
    230  let headerWithOptionalHeaders = structuredClone(testHeader);
    231  headerWithOptionalHeaders.kid = "optional kid";
    232  headerWithOptionalHeaders.iat = "optional iat";
    233  ok(
    234    QWACs.validateTLSCertificateBindingHeader(headerWithOptionalHeaders),
    235    "header with optional headers should parse"
    236  );
    237 
    238  let headerWithX5tS256Match = structuredClone(testHeader);
    239  let signingCertificateHash = await crypto.subtle.digest(
    240    "SHA-256",
    241    new Uint8Array(kTLSCertificateBindingEE.getRawDER())
    242  );
    243  headerWithX5tS256Match["x5t#S256"] = QWACs.toBase64URLEncoding(
    244    arrayToString(new Uint8Array(signingCertificateHash))
    245  );
    246  ok(
    247    QWACs.validateTLSCertificateBindingHeader(headerWithX5tS256Match),
    248    "header with matching x5t#S256 should parse"
    249  );
    250 
    251  let headerWithX5tS256Mismatch = structuredClone(testHeader);
    252  headerWithX5tS256Mismatch["x5t#S256"] =
    253    "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
    254  ok(
    255    !QWACs.validateTLSCertificateBindingHeader(headerWithX5tS256Mismatch),
    256    "header with x5t#S256 mismatch should not parse"
    257  );
    258 
    259  let headerWithBadExp = structuredClone(testHeader);
    260  headerWithBadExp.exp = "not a number";
    261  ok(
    262    !QWACs.validateTLSCertificateBindingHeader(headerWithBadExp),
    263    "header with bad expiration time should not parse"
    264  );
    265 
    266  let expiredHeader = structuredClone(testHeader);
    267  expiredHeader.exp = "946684800";
    268  ok(
    269    !QWACs.validateTLSCertificateBindingHeader(expiredHeader),
    270    "expired header should not parse"
    271  );
    272 
    273  let unexpiredHeader = structuredClone(testHeader);
    274  unexpiredHeader.exp = "4102444800";
    275  ok(
    276    QWACs.validateTLSCertificateBindingHeader(unexpiredHeader),
    277    "unexpired header should parse"
    278  );
    279 });
    280 
    281 async function validate_tls_certificate_binding_header_with_algorithms(
    282  signatureAlg,
    283  hashAlg,
    284  expectedSignatureAlg,
    285  expectedHashAlg
    286 ) {
    287  let serverCertificate = constructCertFromFile("bad_certs/default-ee.pem");
    288  let testHeader = await makeBindingHeader(
    289    [kTLSCertificateBindingEE],
    290    [serverCertificate],
    291    signatureAlg,
    292    hashAlg
    293  );
    294  let validatedHeader = QWACs.validateTLSCertificateBindingHeader(testHeader);
    295  ok(validatedHeader, "header should validate successfully");
    296  deepEqual(validatedHeader.algorithm, expectedSignatureAlg);
    297  equal(validatedHeader.certificates.length, 1);
    298  equal(validatedHeader.hashAlg, expectedHashAlg);
    299  equal(validatedHeader.hashes.length, 1);
    300 }
    301 
    302 add_task(
    303  async function test_validate_tls_certificate_binding_header_with_algorithms() {
    304    let options = [
    305      {
    306        signatureAlg: "RS256",
    307        hashAlg: "S256",
    308        expectedSignatureAlg: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    309        expectedHashAlg: "SHA-256",
    310      },
    311      {
    312        signatureAlg: "PS256",
    313        hashAlg: "S384",
    314        expectedSignatureAlg: {
    315          name: "RSA-PSS",
    316          saltLength: 32,
    317          hash: "SHA-256",
    318        },
    319        expectedHashAlg: "SHA-384",
    320      },
    321      {
    322        signatureAlg: "ES256",
    323        hashAlg: "S512",
    324        expectedSignatureAlg: {
    325          name: "ECDSA",
    326          namedCurve: "P-256",
    327          hash: "SHA-256",
    328        },
    329        expectedHashAlg: "SHA-512",
    330      },
    331    ];
    332    for (let option of options) {
    333      await validate_tls_certificate_binding_header_with_algorithms(
    334        option.signatureAlg,
    335        option.hashAlg,
    336        option.expectedSignatureAlg,
    337        option.expectedHashAlg
    338      );
    339    }
    340  }
    341 );
    342 
    343 async function sign(privateKeyInfo, algorithm, header) {
    344  let toSign = new Uint8Array(stringToArray(header + "."));
    345  let key = await crypto.subtle.importKey(
    346    "pkcs8",
    347    privateKeyInfo,
    348    algorithm,
    349    true,
    350    ["sign"]
    351  );
    352  let signature = await crypto.subtle.sign(algorithm, key, toSign);
    353  return (
    354    header +
    355    ".." +
    356    QWACs.toBase64URLEncoding(arrayToString(new Uint8Array(signature)))
    357  );
    358 }
    359 
    360 async function signTLSCertificateBinding(header, key, algorithm) {
    361  return sign(
    362    key,
    363    algorithm,
    364    QWACs.toBase64URLEncoding(JSON.stringify(header))
    365  );
    366 }
    367 
    368 async function verify_tls_certificate_binding_signature(
    369  headerSignatureAlgorithm,
    370  headerHashAlgorithm,
    371  signatureAlgorithm,
    372  signingKeyFilename,
    373  bindingCertificateFilename,
    374  serverCertificateFilename,
    375  presentedServerCertificateFilename,
    376  expectSuccess,
    377  expectedCertificateSubject
    378 ) {
    379  let signingKey = new Uint8Array(
    380    stringToArray(
    381      QWACs.fromBase64URLEncoding(
    382        pemToBase64(readFile(do_get_file(signingKeyFilename, false)))
    383      )
    384    )
    385  );
    386  let bindingCertificate = constructCertFromFile(bindingCertificateFilename);
    387  let serverCertificate = constructCertFromFile(serverCertificateFilename);
    388  let testHeader = await makeBindingHeader(
    389    [bindingCertificate],
    390    [serverCertificate],
    391    headerSignatureAlgorithm,
    392    headerHashAlgorithm
    393  );
    394  let binding = await signTLSCertificateBinding(
    395    testHeader,
    396    signingKey,
    397    signatureAlgorithm
    398  );
    399  let presentedServerCertificate = constructCertFromFile(
    400    presentedServerCertificateFilename
    401  );
    402  let qwac = await QWACs.verifyTLSCertificateBinding(
    403    binding,
    404    presentedServerCertificate,
    405    "example.com"
    406  );
    407  equal(
    408    !!qwac,
    409    expectSuccess,
    410    `TLS certificate binding ${expectSuccess ? "should" : "should not"} verify correctly`
    411  );
    412  if (expectSuccess) {
    413    equal(
    414      qwac.commonName,
    415      expectedCertificateSubject,
    416      "Verification should return the expected certificate"
    417    );
    418  }
    419 }
    420 
    421 add_task(async function test_verify_tls_certificate_binding_signatures() {
    422  let options = [
    423    {
    424      headerSignatureAlgorithm: "RS256",
    425      headerHashAlgorithm: "S512",
    426      signatureAlgorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    427      signingKeyFilename: "bad_certs/default-ee.key",
    428      bindingCertificateFilename: "test_qwacs/2-qwac.pem",
    429      serverCertificateFilename: "bad_certs/default-ee.pem",
    430      presentedServerCertificateFilename: "bad_certs/default-ee.pem",
    431      expectSuccess: true,
    432      expectedCertificateSubject: "2-QWAC",
    433    },
    434    // presented server certificate / bound server certificate mismatch
    435    {
    436      headerSignatureAlgorithm: "RS256",
    437      headerHashAlgorithm: "S512",
    438      signatureAlgorithm: { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" },
    439      signingKeyFilename: "bad_certs/default-ee.key",
    440      bindingCertificateFilename: "test_qwacs/2-qwac.pem",
    441      serverCertificateFilename: "bad_certs/default-ee.pem",
    442      presentedServerCertificateFilename:
    443        "bad_certs/ee-from-missing-intermediate.pem",
    444      expectSuccess: false,
    445      expectedCertificateSubject: null,
    446    },
    447    {
    448      headerSignatureAlgorithm: "PS256",
    449      headerHashAlgorithm: "S256",
    450      signatureAlgorithm: {
    451        name: "RSA-PSS",
    452        hash: "SHA-256",
    453        saltLength: 32,
    454      },
    455      signingKeyFilename: "bad_certs/default-ee.key",
    456      bindingCertificateFilename: "test_qwacs/2-qwac.pem",
    457      serverCertificateFilename: "bad_certs/default-ee.pem",
    458      presentedServerCertificateFilename: "bad_certs/default-ee.pem",
    459      expectSuccess: true,
    460      expectedCertificateSubject: "2-QWAC",
    461    },
    462    {
    463      headerSignatureAlgorithm: "ES256",
    464      headerHashAlgorithm: "S384",
    465      signatureAlgorithm: {
    466        name: "ECDSA",
    467        namedCurve: "P-256",
    468        hash: "SHA-256",
    469      },
    470      signingKeyFilename: "test_qwacs/secp256r1.key",
    471      bindingCertificateFilename: "test_qwacs/2-qwac-ec.pem",
    472      serverCertificateFilename: "bad_certs/default-ee.pem",
    473      presentedServerCertificateFilename: "bad_certs/default-ee.pem",
    474      expectSuccess: true,
    475      expectedCertificateSubject: "2-QWAC with EC key",
    476    },
    477    // header signature algorithm / actual signature algorithm mismatch
    478    {
    479      headerSignatureAlgorithm: "RS256",
    480      headerHashAlgorithm: "S384",
    481      signatureAlgorithm: {
    482        name: "ECDSA",
    483        namedCurve: "P-256",
    484        hash: "SHA-256",
    485      },
    486      signingKeyFilename: "test_qwacs/secp256r1.key",
    487      bindingCertificateFilename: "test_qwacs/2-qwac-ec.pem",
    488      serverCertificateFilename: "bad_certs/default-ee.pem",
    489      presentedServerCertificateFilename: "bad_certs/default-ee.pem",
    490      expectSuccess: false,
    491      expectedCertificateSubject: null,
    492    },
    493  ];
    494 
    495  for (let option of options) {
    496    await verify_tls_certificate_binding_signature(
    497      option.headerSignatureAlgorithm,
    498      option.headerHashAlgorithm,
    499      option.signatureAlgorithm,
    500      option.signingKeyFilename,
    501      option.bindingCertificateFilename,
    502      option.serverCertificateFilename,
    503      option.presentedServerCertificateFilename,
    504      option.expectSuccess,
    505      option.expectedCertificateSubject
    506    );
    507  }
    508 });