tor-browser

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

test_crypto.js (25813B)


      1 "use strict";
      2 
      3 const { getCryptoParamsFromHeaders, PushCrypto } = ChromeUtils.importESModule(
      4  "resource://gre/modules/PushCrypto.sys.mjs"
      5 );
      6 
      7 const REJECT_PADDING = { padding: "reject" };
      8 
      9 // A common key to decrypt some aesgcm and aesgcm128 messages. Other decryption
     10 // tests have their own keys.
     11 const LEGACY_PRIVATE_KEY = {
     12  kty: "EC",
     13  crv: "P-256",
     14  d: "4h23G_KkXC9TvBSK2v0Q7ImpS2YAuRd8hQyN0rFAwBg",
     15  x: "sd85ZCbEG6dEkGMCmDyGBIt454Qy-Yo-1xhbaT2Jlk4",
     16  y: "vr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs",
     17  ext: true,
     18 };
     19 
     20 const LEGACY_PUBLIC_KEY =
     21  "BLHfOWQmxBunRJBjApg8hgSLeOeEMvmKPtcYW2k9iZZOvr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs";
     22 
     23 async function assertDecrypts(test, headers) {
     24  let privateKey = test.privateKey;
     25  let publicKey = ChromeUtils.base64URLDecode(test.publicKey, REJECT_PADDING);
     26  let authSecret = null;
     27  if (test.authSecret) {
     28    authSecret = ChromeUtils.base64URLDecode(test.authSecret, REJECT_PADDING);
     29  }
     30  let payload = ChromeUtils.base64URLDecode(test.data, REJECT_PADDING);
     31  let result = await PushCrypto.decrypt(
     32    privateKey,
     33    publicKey,
     34    authSecret,
     35    headers,
     36    payload
     37  );
     38  let decoder = new TextDecoder("utf-8");
     39  equal(decoder.decode(new Uint8Array(result)), test.result, test.desc);
     40 }
     41 
     42 async function assertNotDecrypts(test, headers) {
     43  let authSecret = null;
     44  if (test.authSecret) {
     45    authSecret = ChromeUtils.base64URLDecode(test.authSecret, REJECT_PADDING);
     46  }
     47  let data = ChromeUtils.base64URLDecode(test.data, REJECT_PADDING);
     48  let publicKey = ChromeUtils.base64URLDecode(test.publicKey, REJECT_PADDING);
     49  let promise = PushCrypto.decrypt(
     50    test.privateKey,
     51    publicKey,
     52    authSecret,
     53    headers,
     54    data
     55  );
     56  await Assert.rejects(promise, test.expected, test.desc);
     57 }
     58 
     59 add_setup(async function () {
     60  // Per https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/instrumentation_tests.html#xpcshell-tests
     61  do_get_profile();
     62  Services.fog.initializeFOG();
     63 });
     64 
     65 add_task(async function test_crypto_getCryptoParamsFromHeaders() {
     66  // These headers should parse correctly.
     67  let shouldParse = [
     68    {
     69      desc: "aesgcm with multiple keys",
     70      headers: {
     71        encoding: "aesgcm",
     72        crypto_key: "keyid=p256dh;dh=Iy1Je2Kv11A,p256ecdsa=o2M8QfiEKuI",
     73        encryption: "keyid=p256dh;salt=upk1yFkp1xI",
     74      },
     75      params: {
     76        senderKey: "Iy1Je2Kv11A",
     77        salt: "upk1yFkp1xI",
     78        rs: 4096,
     79      },
     80    },
     81    {
     82      desc: "aesgcm with quoted key param",
     83      headers: {
     84        encoding: "aesgcm",
     85        crypto_key: 'dh="byfHbUffc-k"',
     86        encryption: "salt=C11AvAsp6Gc",
     87      },
     88      params: {
     89        senderKey: "byfHbUffc-k",
     90        salt: "C11AvAsp6Gc",
     91        rs: 4096,
     92      },
     93    },
     94    {
     95      desc: "aesgcm with Crypto-Key and rs = 24",
     96      headers: {
     97        encoding: "aesgcm",
     98        crypto_key: 'dh="ybuT4VDz-Bg"',
     99        encryption: "salt=H7U7wcIoIKs; rs=24",
    100      },
    101      params: {
    102        senderKey: "ybuT4VDz-Bg",
    103        salt: "H7U7wcIoIKs",
    104        rs: 24,
    105      },
    106    },
    107    {
    108      desc: "aesgcm with Crypto-Key and rs = 2",
    109      headers: {
    110        encoding: "aesgcm",
    111        crypto_key: "dh=LqrDQuVl9lY",
    112        encryption: "salt=YngI8B7YapM; rs=2",
    113      },
    114      params: {
    115        senderKey: "LqrDQuVl9lY",
    116        salt: "YngI8B7YapM",
    117        rs: 2,
    118      },
    119    },
    120  ];
    121  for (let test of shouldParse) {
    122    let params = getCryptoParamsFromHeaders(test.headers);
    123    let senderKey = ChromeUtils.base64URLDecode(
    124      test.params.senderKey,
    125      REJECT_PADDING
    126    );
    127    let salt = ChromeUtils.base64URLDecode(test.params.salt, REJECT_PADDING);
    128    deepEqual(
    129      new Uint8Array(params.senderKey),
    130      new Uint8Array(senderKey),
    131      "Sender key should match for " + test.desc
    132    );
    133    deepEqual(
    134      new Uint8Array(params.salt),
    135      new Uint8Array(salt),
    136      "Salt should match for " + test.desc
    137    );
    138    equal(
    139      params.rs,
    140      test.params.rs,
    141      "Record size should match for " + test.desc
    142    );
    143  }
    144 
    145  // These headers should be rejected.
    146  let shouldThrow = [
    147    {
    148      desc: "aesgcm128 with Crypto-Key",
    149      headers: {
    150        encoding: "aesgcm128",
    151        crypto_key: "keyid=v2; dh=VA6wmY1IpiE",
    152        encryption: "keyid=v2; salt=F0Im7RtGgNY",
    153      },
    154      exception: /Unexpected encoding/,
    155    },
    156    {
    157      desc: "aesgcm128 with Encryption-Key and rs = 2",
    158      headers: {
    159        encoding: "aesgcm128",
    160        encryption_key: "keyid=legacy; dh=LqrDQuVl9lY",
    161        encryption: "keyid=legacy; salt=YngI8B7YapM; rs=2",
    162      },
    163      exception: /Unexpected encoding/,
    164    },
    165    {
    166      desc: "aesgcm128 with Encryption-Key",
    167      headers: {
    168        encoding: "aesgcm128",
    169        encryption_key: "keyid=v2; dh=VA6wmY1IpiE",
    170        encryption: "keyid=v2; salt=khtpyXhpDKM",
    171      },
    172      exception: /Unexpected encoding/,
    173    },
    174    {
    175      desc: "Invalid encoding",
    176      headers: {
    177        encoding: "nonexistent",
    178      },
    179      exception: /Unexpected encoding/,
    180    },
    181    {
    182      desc: "Missing salt header",
    183      headers: {
    184        encoding: "aesgcm",
    185        crypto_key: "dh=pbmv1QkcEDY",
    186        encryption: "dh=Esao8aTBfIk;rs=32",
    187      },
    188      exception: /Invalid salt parameter/,
    189    },
    190    {
    191      desc: "Invalid record size",
    192      headers: {
    193        encoding: "aesgcm",
    194        crypto_key: "dh=pbmv1QkcEDY",
    195        encryption: "salt=Esao8aTBfIk;rs=bad",
    196      },
    197      exception: /rs parameter must be/,
    198    },
    199    {
    200      desc: "Zero record size",
    201      headers: {
    202        encoding: "aesgcm",
    203        crypto_key: "dh=pbmv1QkcEDY",
    204        encryption: "salt=Esao8aTBfIk;rs=0",
    205      },
    206      exception: /rs parameter must be/,
    207    },
    208    {
    209      desc: "Too big record size",
    210      headers: {
    211        encoding: "aesgcm",
    212        crypto_key: "dh=pbmv1QkcEDY",
    213        encryption: "salt=Esao8aTBfIk;rs=68719476736",
    214      },
    215      exception: /rs parameter must be/,
    216    },
    217    {
    218      desc: "aesgcm with Encryption-Key",
    219      headers: {
    220        encoding: "aesgcm",
    221        encryption_key: "dh=FplK5KkvUF0",
    222        encryption: "salt=p6YHhFF3BQY",
    223      },
    224      exception: /Missing Crypto-Key header/,
    225    },
    226  ];
    227  for (let test of shouldThrow) {
    228    throws(
    229      () => getCryptoParamsFromHeaders(test.headers),
    230      test.exception,
    231      test.desc
    232    );
    233  }
    234 });
    235 
    236 add_task(async function test_aes128gcm_ok() {
    237  Services.fog.testResetFOG();
    238  let expectedSuccesses = [
    239    {
    240      desc: "Example from draft-ietf-webpush-encryption-latest",
    241      result: "When I grow up, I want to be a watermelon",
    242      data: "DGv6ra1nlYgDCS1FRnbzlwAAEABBBP4z9KsN6nGRTbVYI_c7VJSPQTBtkgcy27mlmlMoZIIgDll6e3vCYLocInmYWAmS6TlzAC8wEqKK6PBru3jl7A_yl95bQpu6cVPTpK4Mqgkf1CXztLVBSt2Ks3oZwbuwXPXLWyouBWLVWGNWQexSgSxsj_Qulcy4a-fN",
    243      authSecret: "BTBZMqHH6r4Tts7J_aSIgg",
    244      privateKey: {
    245        kty: "EC",
    246        crv: "P-256",
    247        d: "q1dXpw3UpT5VOmu_cf_v6ih07Aems3njxI-JWgLcM94",
    248        x: "JXGyvs3942BVGq8e0PTNNmwRzr5VX4m8t7GGpTM5FzE",
    249        y: "aOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4",
    250        ext: true,
    251      },
    252      publicKey:
    253        "BCVxsr7N_eNgVRqvHtD0zTZsEc6-VV-JvLexhqUzORcxaOzi6-AYWXvTBHm4bjyPjs7Vd8pZGH6SRpkNtoIAiw4",
    254    },
    255    {
    256      desc: "rs = 24, pad = 0",
    257      result:
    258        "I am the very model of a modern Major-General; I've information vegetable, animal, and mineral",
    259      data: "goagSH7PP0ZGwUsgShmdkwAAABhBBDJVyIuUJbOSVMeWHP8VNPnxY-dZSw86doqOkEzZZZY1ALBWVXTVf0rUDH3oi68I9Hrp-01zA-mr8XKWl5kcH8cX0KiV2PtCwdkEyaQ73YF5fsDxgoWDiaTA3wPqMvuLDqGsZWHnE9Psnfoy7UMEqKlh2a1nE7ZOXiXcOBHLNj260jYzSJnEPV2eXixSXfyWpaSJHAwfj4wVdAAocmViIg6ywk8wFB1hgJpnX2UVEU_qIOcaP6AOIOr1UUQPfosQqC2MEHe5u9gHXF5pi-E267LAlkoYefq01KV_xK_vjbxpw8GAYfSjQEm0L8FG-CN37c8pnQ2Yf61MkihaXac9OctfNeWq_22cN6hn4qsOq0F7QoWIiZqWhB1vS9cJ3KUlyPQvKI9cvevDxw0fJHWeTFzhuwT9BjdILjjb2Vkqc0-qTDOawqD4c8WXsvdGDQCec5Y1x3UhdQXdjR_mhXypxFM37OZTvKJBr1vPCpRXl-bI6iOd7KScgtMM1x5luKhGzZyz25HyuFyj1ec82A",
    260      authSecret: "_tK2LDGoIt6be6agJ_nvGA",
    261      privateKey: {
    262        kty: "EC",
    263        crv: "P-256",
    264        d: "bGViEe3PvjjFJg8lcnLsqu71b2yqWGnZN9J2MTed-9s",
    265        x: "auB0GHF0AZ2LAocFnvOXDS7EeCMopnzbg-tS21FMHrU",
    266        y: "GpbhrW-_xKj3XhhXA-kDZSicKZ0kn0BuVhqzhLOB-Cc",
    267        ext: true,
    268      },
    269      publicKey:
    270        "BGrgdBhxdAGdiwKHBZ7zlw0uxHgjKKZ824PrUttRTB61GpbhrW-_xKj3XhhXA-kDZSicKZ0kn0BuVhqzhLOB-Cc",
    271    },
    272    {
    273      desc: "rs = 49, pad = 84; ciphertext length falls on record boundary",
    274      result: "Hello, world",
    275      data: "-yiDzsHE_K3W0TcfbqSR4AAAADFBBC1EHuf5_2oDKaZJJ9BST9vnsixvtl4Qq0_cA4-UQgoMo_oo2tNshOyRoWLq4Hj6rSwc7XjegRPhlgKyDolPSXa5c-L89oL6DIzNmvPVv_Ht4W-tWjHOGdOLXh_h94pPrYQrvBAlTCxs3ZaitVKE2XLFPK2MO6yxD19X6w1KQzO2BBAroRfK4pEI-9n2Kai6aWDdAZRbOe03unBsQ0oQ_SvSCU_5JJvNrUUTX1_kX804Bx-LLTlBr9pDmBDXeqyvfOULVDJb9YyVAzN9BzeFoyPfo0M",
    276      authSecret: "lfF1cOUI72orKtG09creMw",
    277      privateKey: {
    278        kty: "EC",
    279        crv: "P-256",
    280        d: "ZwBKTqgg3u2OSdtelIDmPT6jzOGujhpgYJcT1SfQAe8",
    281        x: "AU6PFLktoHzgg7k_ljZ-h7IXpH9-8u6TqdNDqgY-V1o",
    282        y: "nzDVnGkMajmz_IFbFQyn3RSWAXQTN7U1B6UfQbFzpyE",
    283        ext: true,
    284      },
    285      publicKey:
    286        "BAFOjxS5LaB84IO5P5Y2foeyF6R_fvLuk6nTQ6oGPldanzDVnGkMajmz_IFbFQyn3RSWAXQTN7U1B6UfQbFzpyE",
    287    },
    288    {
    289      desc: "rs = 18, pad = 0",
    290      result: "1",
    291      data: "fK69vCCTjuNAqUbxvU9o8QAAABJBBDfP21Ij2fleqgL27ZQP8i6vBbNiLpSdw86fM15u-bJq6qzKD3QICos2RZLyzMbV7d1DAEtwuRiH0UTZ-pPxbDvH6mj0_VR6lOyoSxbhOKYIAXc",
    292      authSecret: "1loE35Xy215gSDn3F9zeeQ",
    293      privateKey: {
    294        kty: "EC",
    295        crv: "P-256",
    296        d: "J0M_q4lws8tShLYRg--0YoZWLNKnMw2MrpYJEaVXHQw",
    297        x: "UV1DJjVWUjmdoksr6SQeYztc8U-vDPOm_WAxe5VMCi8",
    298        y: "SEhUgASyewz3SAvIEMa-wDqPt5yOoA_IsF4A-INFY-8",
    299        ext: true,
    300      },
    301      publicKey:
    302        "BFFdQyY1VlI5naJLK-kkHmM7XPFPrwzzpv1gMXuVTAovSEhUgASyewz3SAvIEMa-wDqPt5yOoA_IsF4A-INFY-8",
    303    },
    304  ];
    305  for (let test of expectedSuccesses) {
    306    let privateKey = test.privateKey;
    307    let publicKey = ChromeUtils.base64URLDecode(test.publicKey, {
    308      padding: "reject",
    309    });
    310    let authSecret = ChromeUtils.base64URLDecode(test.authSecret, {
    311      padding: "reject",
    312    });
    313    let payload = ChromeUtils.base64URLDecode(test.data, {
    314      padding: "reject",
    315    });
    316    let result = await PushCrypto.decrypt(
    317      privateKey,
    318      publicKey,
    319      authSecret,
    320      {
    321        encoding: "aes128gcm",
    322      },
    323      payload
    324    );
    325    let decoder = new TextDecoder("utf-8");
    326    equal(decoder.decode(new Uint8Array(result)), test.result, test.desc);
    327  }
    328  Assert.equal(
    329    Glean.webPush.contentEncoding.aes128gcm.testGetValue(),
    330    4,
    331    "Glean counter should be increased for each decrypt"
    332  );
    333 });
    334 
    335 add_task(async function test_aes128gcm_err() {
    336  let expectedFailures = [
    337    {
    338      // Just the payload; no header at all.
    339      desc: "Missing header block",
    340      data: "RbdNK2m-mwdN47NaqH58FWEd",
    341      privateKey: {
    342        kty: "EC",
    343        crv: "P-256",
    344        d: "G-g_ODMu8JaB-vPzB7H_LhDKt4zHzatoOsDukqw_buE",
    345        x: "26mRyiFTQ_Nr3T6FfK_ePRi_V_GDWygzutQU8IhBYgU",
    346        y: "GslqCyRJADfQfPUo5OGOEAoaZOt0R0hUS_HiINq6zyw",
    347        ext: true,
    348      },
    349      publicKey:
    350        "BNupkcohU0Pza90-hXyv3j0Yv1fxg1soM7rUFPCIQWIFGslqCyRJADfQfPUo5OGOEAoaZOt0R0hUS_HiINq6zyw",
    351      authSecret: "NHG7mEgeAlM785VCvPPbpA",
    352      expected: /Truncated header/,
    353    },
    354    {
    355      // The sender key should be 65 bytes; this header contains an invalid key
    356      // that's only 1 byte.
    357      desc: "Truncated sender key",
    358      data: "3ltpa4fxoVy2revdedb5ngAAABIBALa8GCbDfJ9z3WtIWcK1BRgZUg",
    359      privateKey: {
    360        kty: "EC",
    361        crv: "P-256",
    362        d: "zojo4LMFekdS60yPqTHrYhwwLaWtA7ga9FnPZzVWDK4",
    363        x: "oyXZkITEDeDOcioELESNlKMmkXIcp54890XnjGmIYZQ",
    364        y: "sCzqGSJBdnlanU27sgc68szW-m8KTHxJaFVr5QKjuoE",
    365        ext: true,
    366      },
    367      publicKey:
    368        "BKMl2ZCExA3gznIqBCxEjZSjJpFyHKeePPdF54xpiGGUsCzqGSJBdnlanU27sgc68szW-m8KTHxJaFVr5QKjuoE",
    369      authSecret: "XDHg2W2aE5iZrAlp01n3QA",
    370      expected: /Invalid sender public key/,
    371    },
    372    {
    373      // The message is encrypted with only the first 12 bytes of the 16-byte
    374      // auth secret, so the derived decryption key and nonce won't match.
    375      desc: "Encrypted with mismatched auth secret",
    376      data: "gRX0mIuMOSp7rLQ8jxrFZQAAABJBBBmUSDxUHpvDmmrwP_cTqndFwoThOKQqJDW3l7IMS2mM9RGLT4VVMXwZDqvr-rdJwWTT9r3r4NRBcZExo1fYiQoTxNvUsW_z3VqD98ka1uBArEJzCn8LPNMkXp-Nb_McdR1BDP0",
    377      privateKey: {
    378        kty: "EC",
    379        crv: "P-256",
    380        d: "YMdjalF95wOaCsLQ4wZEAHlMeOfgSTmBKaInzuD5qAE",
    381        x: "_dBBKKhcBYltf4H-EYvcuIe588H_QYOtxMgk0ShgcwA",
    382        y: "6Yay37WmEOWvQ-QIoAcwWE-T49_d_ERzfV8I-y1viRY",
    383        ext: true,
    384      },
    385      publicKey:
    386        "BP3QQSioXAWJbX-B_hGL3LiHufPB_0GDrcTIJNEoYHMA6Yay37WmEOWvQ-QIoAcwWE-T49_d_ERzfV8I-y1viRY",
    387      authSecret: "NVo4zW2b7xWZDi0zCNvWAA",
    388      expected: /Bad encryption/,
    389    },
    390    {
    391      // Multiple records; the first has padding delimiter = 2, but should be 1.
    392      desc: "Early final record",
    393      data: "2-IVUH0a09Lq6r6ubodNjwAAABJBBHvEND80qDSM3E5GL_x8QKpqjGGnOcTEHUUSVQX3Dp_F-e-oaFLdSI3Pjo6iyvt14Hq9XufJ1cA4uv7weVcbC9opRBHOmMdt0DHA5YBXekmAo3XkXtMEKb4OLunafm34aW0BuOw",
    394      privateKey: {
    395        kty: "EC",
    396        crv: "P-256",
    397        d: "XdodkYvEB7o82hLLgBTUmqfgJpACggMERmvIADTKkkA",
    398        x: "yVxlINrRHo9qG_gDGkDCpO4QRcGQO-BqHfp_gpzOst4",
    399        y: "Akga5r0EdhIbEsVTLQsjF4gHfvoGg6W_4NYjObJRyzU",
    400        ext: true,
    401      },
    402      publicKey:
    403        "BMlcZSDa0R6Pahv4AxpAwqTuEEXBkDvgah36f4KczrLeAkga5r0EdhIbEsVTLQsjF4gHfvoGg6W_4NYjObJRyzU",
    404      authSecret: "QMJB_eQmnuHm1yVZLZgnGA",
    405      expected: /Padding is wrong!/,
    406    },
    407  ];
    408  for (let test of expectedFailures) {
    409    await assertNotDecrypts(test, { encoding: "aes128gcm" });
    410  }
    411 });
    412 
    413 add_task(async function test_aesgcm_ok() {
    414  Services.fog.testResetFOG();
    415  let expectedSuccesses = [
    416    {
    417      desc: "padSize = 2, rs = 24, pad = 0",
    418      result: "Some message",
    419      data: "Oo34w2F9VVnTMFfKtdx48AZWQ9Li9M6DauWJVgXU",
    420      authSecret: "aTDc6JebzR6eScy2oLo4RQ",
    421      privateKey: LEGACY_PRIVATE_KEY,
    422      publicKey: LEGACY_PUBLIC_KEY,
    423      headers: {
    424        crypto_key:
    425          "dh=BCHFVrflyxibGLlgztLwKelsRZp4gqX3tNfAKFaxAcBhpvYeN1yIUMrxaDKiLh4LNKPtj0BOXGdr-IQ-QP82Wjo",
    426        encryption: "salt=zCU18Rw3A5aB_Xi-vfixmA; rs=24",
    427        encoding: "aesgcm",
    428      },
    429    },
    430    {
    431      desc: "padSize = 2, rs = 8, pad = 16",
    432      result: "Yet another message",
    433      data: "uEC5B_tR-fuQ3delQcrzrDCp40W6ipMZjGZ78USDJ5sMj-6bAOVG3AK6JqFl9E6AoWiBYYvMZfwThVxmDnw6RHtVeLKFM5DWgl1EwkOohwH2EhiDD0gM3io-d79WKzOPZE9rDWUSv64JstImSfX_ADQfABrvbZkeaWxh53EG59QMOElFJqHue4dMURpsMXg",
    434      authSecret: "6plwZnSpVUbF7APDXus3UQ",
    435      privateKey: LEGACY_PRIVATE_KEY,
    436      publicKey: LEGACY_PUBLIC_KEY,
    437      headers: {
    438        crypto_key:
    439          "dh=BEaA4gzA3i0JDuirGhiLgymS4hfFX7TNTdEhSk_HBlLpkjgCpjPL5c-GL9uBGIfa_fhGNKKFhXz1k9Kyens2ZpQ",
    440        encryption: "salt=ZFhzj0S-n29g9P2p4-I7tA; rs=8",
    441        encoding: "aesgcm",
    442      },
    443    },
    444    {
    445      desc: "padSize = 2, rs = 3, pad = 0",
    446      result: "Small record size",
    447      data: "oY4e5eDatDVt2fpQylxbPJM-3vrfhDasfPc8Q1PWt4tPfMVbz_sDNL_cvr0DXXkdFzS1lxsJsj550USx4MMl01ihjImXCjrw9R5xFgFrCAqJD3GwXA1vzS4T5yvGVbUp3SndMDdT1OCcEofTn7VC6xZ-zP8rzSQfDCBBxmPU7OISzr8Z4HyzFCGJeBfqiZ7yUfNlKF1x5UaZ4X6iU_TXx5KlQy_toV1dXZ2eEAMHJUcSdArvB6zRpFdEIxdcHcJyo1BIYgAYTDdAIy__IJVCPY_b2CE5W_6ohlYKB7xDyH8giNuWWXAgBozUfScLUVjPC38yJTpAUi6w6pXgXUWffende5FreQpnMFL1L4G-38wsI_-ISIOzdO8QIrXHxmtc1S5xzYu8bMqSgCinvCEwdeGFCmighRjj8t1zRWo0D14rHbQLPR_b1P5SvEeJTtS9Nm3iibM",
    448      authSecret: "g2rWVHUCpUxgcL9Tz7vyeQ",
    449      privateKey: LEGACY_PRIVATE_KEY,
    450      publicKey: LEGACY_PUBLIC_KEY,
    451      headers: {
    452        crypto_key:
    453          "dh=BCg6ZIGuE2ZNm2ti6Arf4CDVD_8--aLXAGLYhpghwjl1xxVjTLLpb7zihuEOGGbyt8Qj0_fYHBP4ObxwJNl56bk",
    454        encryption: "salt=5LIDBXbvkBvvb7ZdD-T4PQ; rs=3",
    455        encoding: "aesgcm",
    456      },
    457    },
    458    {
    459      desc: "Example from draft-ietf-httpbis-encryption-encoding-02",
    460      result: "I am the walrus",
    461      data: "6nqAQUME8hNqw5J3kl8cpVVJylXKYqZOeseZG8UueKpA",
    462      authSecret: "R29vIGdvbyBnJyBqb29iIQ",
    463      privateKey: {
    464        kty: "EC",
    465        crv: "P-256",
    466        d: "9FWl15_QUQAWDaD3k3l50ZBZQJ4au27F1V4F0uLSD_M",
    467        x: "ISQGPMvxncL6iLZDugTm3Y2n6nuiyMYuD3epQ_TC-pE",
    468        y: "T21EEWyf0cQDQcakQMqz4hQKYOQ3il2nNZct4HgAUQU",
    469        ext: true,
    470      },
    471      publicKey:
    472        "BCEkBjzL8Z3C-oi2Q7oE5t2Np-p7osjGLg93qUP0wvqRT21EEWyf0cQDQcakQMqz4hQKYOQ3il2nNZct4HgAUQU",
    473      headers: {
    474        crypto_key:
    475          'keyid="dhkey"; dh="BNoRDbb84JGm8g5Z5CFxurSqsXWJ11ItfXEWYVLE85Y7CYkDjXsIEc4aqxYaQ1G8BqkXCJ6DPpDrWtdWj_mugHU"',
    476        encryption: 'keyid="dhkey"; salt="lngarbyKfMoi9Z75xYXmkg"',
    477        encoding: "aesgcm",
    478      },
    479    },
    480  ];
    481  for (let test of expectedSuccesses) {
    482    await assertDecrypts(test, test.headers);
    483  }
    484  Assert.equal(
    485    Glean.webPush.contentEncoding.aesgcm.testGetValue(),
    486    4,
    487    "Glean counter should be increased for each decrypt"
    488  );
    489 });
    490 
    491 add_task(async function test_aesgcm_err() {
    492  let expectedFailures = [
    493    {
    494      desc: "aesgcm128 message decrypted as aesgcm",
    495      data: "fwkuwTTChcLnrzsbDI78Y2EoQzfnbMI8Ax9Z27_rwX8",
    496      authSecret: "BhbpNTWyO5wVJmVKTV6XaA",
    497      privateKey: LEGACY_PRIVATE_KEY,
    498      publicKey: LEGACY_PUBLIC_KEY,
    499      headers: {
    500        crypto_key:
    501          "dh=BCHn-I-J3dfPRLJBlNZ3xFoAqaBLZ6qdhpaz9W7Q00JW1oD-hTxyEECn6KYJNK8AxKUyIDwn6Icx_PYWJiEYjQ0",
    502        encryption: "salt=c6JQl9eJ0VvwrUVCQDxY7Q",
    503        encoding: "aesgcm",
    504      },
    505      expected: /Bad encryption/,
    506    },
    507    {
    508      // The plaintext is "O hai". The ciphertext is exactly `rs + 16` bytes,
    509      // but we didn't include the empty trailing block that aesgcm requires for
    510      // exact multiples.
    511      desc: "rs = 7, no trailing block",
    512      data: "YG4F-b06y590hRlnSsw_vuOw62V9Iz8",
    513      authSecret: "QoDi0u6vcslIVJKiouXMXw",
    514      privateKey: {
    515        kty: "EC",
    516        crv: "P-256",
    517        d: "2bu4paOAZbL2ef1u-wTzONuTIcDPc00o0zUJgg46XTc",
    518        x: "uEvLZUMVn1my0cwnLdcFT0mj1gSU5uzI3HeGwXC7jX8",
    519        y: "SfNVLGL-FurydsuzciDfw8K1cUHyoDWnJJ_16UG6Dbo",
    520        ext: true,
    521      },
    522      publicKey:
    523        "BLhLy2VDFZ9ZstHMJy3XBU9Jo9YElObsyNx3hsFwu41_SfNVLGL-FurydsuzciDfw8K1cUHyoDWnJJ_16UG6Dbo",
    524      headers: {
    525        crypto_key:
    526          "dh=BD_bsTUpxBMvSv8eksith3vijMLj44D4jhJjO51y7wK1ytbUlsyYBBYYyB5AAe5bnREA_WipTgemDVz00LiWcfM",
    527        encryption: "salt=xKWvs_jWWeg4KOsot_uBhA; rs=7",
    528        encoding: "aesgcm",
    529      },
    530      expected: /Encrypted data truncated/,
    531    },
    532    {
    533      // The last block is only 1 byte, but valid blocks must be at least 2 bytes.
    534      desc: "Pad size > last block length",
    535      data: "JvX9HsJ4lL5gzP8_uCKc6s15iRIaNhD4pFCgq5-dfwbUqEcNUkqv",
    536      authSecret: "QtGZeY8MQfCaq-XwKOVGBQ",
    537      privateKey: {
    538        kty: "EC",
    539        crv: "P-256",
    540        d: "CosERAVXgvTvoh7UkrRC2V-iXoNs0bXle9I68qzkles",
    541        x: "_D0YqEwirvTJQJdjG6xXrjstMVpeAzf221cUqZz6hgY",
    542        y: "9MnFbM7U14uiYMDI5e2I4jN29tYmsM9F66QodhKmA-c",
    543        ext: true,
    544      },
    545      publicKey:
    546        "BPw9GKhMIq70yUCXYxusV647LTFaXgM39ttXFKmc-oYG9MnFbM7U14uiYMDI5e2I4jN29tYmsM9F66QodhKmA-c",
    547      headers: {
    548        crypto_key:
    549          "dh=BBNZNEi5Ew_ID5S4Y9jWBi1NeVDje6Mjs7SDLViUn6A8VAZj-6X3QAuYQ3j20BblqjwTgYst7PRnY6UGrKyLbmU",
    550        encryption: "salt=ot8hzbwOo6CYe6ZhdlwKtg; rs=6",
    551        encoding: "aesgcm",
    552      },
    553      expected: /Decoded array is too short/,
    554    },
    555    {
    556      // The last block is 3 bytes (2 bytes for the pad length; 1 byte of data),
    557      // but claims its pad length is 2.
    558      desc: "Padding length > last block length",
    559      data: "oWSOFA-UO5oWq-kI79RHaFfwAejLiQJ4C7eTmrSTBl4gArLXfx7lZ-Y",
    560      authSecret: "gKG_P6-de5pyzS8hyH_NyQ",
    561      privateKey: {
    562        kty: "EC",
    563        crv: "P-256",
    564        d: "9l-ahcBM-I0ykwbWiDS9KRrPdhyvTZ0SxKiPpj2aeaI",
    565        x: "qx0tU4EDaQv6ayFA3xvLLBdMmn4mLxjn7SK6mIeIxeg",
    566        y: "ymbMcmUOEyh_-rLrBsi26NG4UFCis2MTDs5FG2VdDPI",
    567        ext: true,
    568      },
    569      publicKey:
    570        "BKsdLVOBA2kL-mshQN8byywXTJp-Ji8Y5-0iupiHiMXoymbMcmUOEyh_-rLrBsi26NG4UFCis2MTDs5FG2VdDPI",
    571      headers: {
    572        crypto_key:
    573          "dh=BKe2IBO_cwmEzQyTVscSbQcj0Y3uBSzGZ_mHlANMciS8uGpb7U8_Bw7TNdlYfpwWDLd0cxM8YYWNDbNJ_p2Rp4o",
    574        encryption: "salt=z7QJ6UR89SiFRkd4RsC4Vg; rs=6",
    575        encoding: "aesgcm",
    576      },
    577      expected: /Padding is wrong/,
    578    },
    579    {
    580      // The first block has no padding, but claims its pad length is 1.
    581      desc: "Non-zero padding",
    582      data: "Qdvjh0LkZXKu_1Hvv56D0rOSF6Mww3y0F8xkxUNlwVu2U1iakOUUGRs",
    583      authSecret: "cMpWQW58BrpDbJ8KqbS9ig",
    584      privateKey: {
    585        kty: "EC",
    586        crv: "P-256",
    587        d: "IzuaxLqFJmjSu8GjLCo2oEaDZjDButW4m4T0qx02XsM",
    588        x: "Xy7vt_TJTynxwWsQyY069BcKmrhkRjhKPFuTi-AphoY",
    589        y: "0M10IVM1ourR7Q5AUX2b2fgdmGyTWcYsdHcdFK_b4Hk",
    590        ext: true,
    591      },
    592      publicKey:
    593        "BF8u77f0yU8p8cFrEMmNOvQXCpq4ZEY4Sjxbk4vgKYaG0M10IVM1ourR7Q5AUX2b2fgdmGyTWcYsdHcdFK_b4Hk",
    594      headers: {
    595        crypto_key:
    596          "dh=BBicj01QI0ryiFzAaty9VpW_crgq9XbU1bOCtEZI9UNE6tuOgp4lyN_UN0N905ECnLWK5v_sCPUIxnQgOuCseSo",
    597        encryption: "salt=SbkGHONbQBBsBcj9dLyIUw; rs=6",
    598        encoding: "aesgcm",
    599      },
    600      expected: /Padding is wrong/,
    601    },
    602    {
    603      // The first record is 22 bytes: 2 bytes for the pad length, 4 bytes of
    604      // data, and a 16-byte auth tag. The second "record" is missing the pad
    605      // and data, and contains just the auth tag.
    606      desc: "rs = 6, second record truncated to only auth tag",
    607      data: "C7u3j5AL4Yzh2yYB_umN6tzrVHxrt7D5baTEW9DE1Bk3up9fY4w",
    608      authSecret: "3rWhsRCU_KdaqfKPbd3zBQ",
    609      privateKey: {
    610        kty: "EC",
    611        crv: "P-256",
    612        d: "nhOT9171xuoQBJGkiZ3aqT5qw_ILJ94_PPiVNu1LFSY",
    613        x: "lCj7ctQTmRfwzTMcODlNfHjFMAHmgdI44OhTQXX_xpE",
    614        y: "WBdgz4GWGtGAisC63O9DtP5l--hnCzPZiV-YZ-a6Lcw",
    615        ext: true,
    616      },
    617      publicKey:
    618        "BJQo-3LUE5kX8M0zHDg5TXx4xTAB5oHSOODoU0F1_8aRWBdgz4GWGtGAisC63O9DtP5l--hnCzPZiV-YZ-a6Lcw",
    619      headers: {
    620        crypto_key:
    621          "dh=BI38Qs_OhDmQIxbszc6Nako-MrX3FzAE_8HzxM1wgoEIG4ocxyF-YAAVhfkpJUvDpRyKW2LDHIaoylaZuxQfRhE",
    622        encryption: "salt=QClh48OlvGpSjZ0Mg0e8rg; rs=6",
    623        encoding: "aesgcm",
    624      },
    625      expected: /Decoded array is too short/,
    626    },
    627  ];
    628  for (let test of expectedFailures) {
    629    await assertNotDecrypts(test, test.headers);
    630  }
    631 });
    632 
    633 add_task(async function test_aesgcm128_err() {
    634  let expectedFailures = [
    635    {
    636      desc: "padSize = 1, rs = 4096, pad = 2",
    637      result: "aesgcm128 encrypted message",
    638      data: "ljBJ44NPzJFH9EuyT5xWMU4vpZ90MdAqaq1TC1kOLRoPNHtNFXeJ0GtuSaE",
    639      privateKey: LEGACY_PRIVATE_KEY,
    640      publicKey: LEGACY_PUBLIC_KEY,
    641      headers: {
    642        encryption_key:
    643          "dh=BOmnfg02vNd6RZ7kXWWrCGFF92bI-rQ-bV0Pku3-KmlHwbGv4ejWqgasEdLGle5Rhmp6SKJunZw2l2HxKvrIjfI",
    644        encryption: "salt=btxxUtclbmgcc30b9rT3Bg; rs=4096",
    645        encoding: "aesgcm128",
    646      },
    647      expected: /Unsupported Content-Encoding/,
    648    },
    649    {
    650      // aesgcm128 doesn't use an auth secret, but we've mixed one in during
    651      // encryption, so the decryption key and nonce won't match.
    652      desc: "padSize = 1, rs = 4096, auth secret, pad = 8",
    653      data: "h0FmyldY8aT5EQ6CJrbfRn_IdDvytoLeHb9_q5CjtdFRfgDRknxLmOzavLaVG4oOiS0r",
    654      authSecret: "Sxb6u0gJIhGEogyLawjmCw",
    655      privateKey: LEGACY_PRIVATE_KEY,
    656      publicKey: LEGACY_PUBLIC_KEY,
    657      headers: {
    658        crypto_key:
    659          "dh=BCXHk7O8CE-9AOp6xx7g7c-NCaNpns1PyyHpdcmDaijLbT6IdGq0ezGatBwtFc34BBfscFxdk4Tjksa2Mx5rRCM",
    660        encryption: "salt=aGBpoKklLtrLcAUCcCr7JQ",
    661        encoding: "aesgcm128",
    662      },
    663      expected: /Unsupported Content-Encoding/,
    664    },
    665    {
    666      // The first byte of each record must be the pad length.
    667      desc: "Missing padding",
    668      data: "anvsHj7oBQTPMhv7XSJEsvyMS4-8EtbC7HgFZsKaTg",
    669      privateKey: LEGACY_PRIVATE_KEY,
    670      publicKey: LEGACY_PUBLIC_KEY,
    671      headers: {
    672        crypto_key:
    673          "dh=BMSqfc3ohqw2DDgu3nsMESagYGWubswQPGxrW1bAbYKD18dIHQBUmD3ul_lu7MyQiT5gNdzn5JTXQvCcpf-oZE4",
    674        encryption: "salt=Czx2i18rar8XWOXAVDnUuw",
    675        encoding: "aesgcm128",
    676      },
    677      expected: /Unsupported Content-Encoding/,
    678    },
    679    {
    680      desc: "Truncated input",
    681      data: "AlDjj6NvT5HGyrHbT8M5D6XBFSra6xrWS9B2ROaCIjwSu3RyZ1iyuv0",
    682      privateKey: LEGACY_PRIVATE_KEY,
    683      publicKey: LEGACY_PUBLIC_KEY,
    684      headers: {
    685        crypto_key:
    686          "dh=BCHn-I-J3dfPRLJBlNZ3xFoAqaBLZ6qdhpaz9W7Q00JW1oD-hTxyEECn6KYJNK8AxKUyIDwn6Icx_PYWJiEYjQ0",
    687        encryption: "salt=c6JQl9eJ0VvwrUVCQDxY7Q; rs=25",
    688        encoding: "aesgcm128",
    689      },
    690      expected: /Unsupported Content-Encoding/,
    691    },
    692    {
    693      desc: "Padding length > rs",
    694      data: "Ct_h1g7O55e6GvuhmpjLsGnv8Rmwvxgw8iDESNKGxk_8E99iHKDzdV8wJPyHA-6b2E6kzuVa5UWiQ7s4Zms1xzJ4FKgoxvBObXkc_r_d4mnb-j245z3AcvRmcYGk5_HZ0ci26SfhAN3lCgxGzTHS4nuHBRkGwOb4Tj4SFyBRlLoTh2jyVK2jYugNjH9tTrGOBg7lP5lajLTQlxOi91-RYZSfFhsLX3LrAkXuRoN7G1CdiI7Y3_eTgbPIPabDcLCnGzmFBTvoJSaQF17huMl_UnWoCj2WovA4BwK_TvWSbdgElNnQ4CbArJ1h9OqhDOphVu5GUGr94iitXRQR-fqKPMad0ULLjKQWZOnjuIdV1RYEZ873r62Yyd31HoveJcSDb1T8l_QK2zVF8V4k0xmK9hGuC0rF5YJPYPHgl5__usknzxMBnRrfV5_MOL5uPZwUEFsu",
    695      privateKey: LEGACY_PRIVATE_KEY,
    696      publicKey: LEGACY_PUBLIC_KEY,
    697      headers: {
    698        crypto_key:
    699          "dh=BAcMdWLJRGx-kPpeFtwqR3GE1LWzd1TYh2rg6CEFu53O-y3DNLkNe_BtGtKRR4f7ZqpBMVS6NgfE2NwNPm3Ndls",
    700        encryption: "salt=NQVTKhB0rpL7ZzKkotTGlA; rs=1",
    701        encoding: "aesgcm128",
    702      },
    703      expected: /Unsupported Content-Encoding/,
    704    },
    705  ];
    706  for (let test of expectedFailures) {
    707    await assertNotDecrypts(test, test.headers);
    708  }
    709 });