summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/xpcshell/test_ext_storage_sync_kinto_crypto.js
blob: 89eac15937fa1104ef1ae5758338b8bd5ef4815e (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
/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

// This is a kinto-specific test...
Services.prefs.setBoolPref("webextensions.storage.sync.kinto", true);

const {
  KintoStorageTestUtils: { EncryptionRemoteTransformer },
} = ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionStorageSyncKinto.sys.mjs"
);
const { CryptoUtils } = ChromeUtils.importESModule(
  "resource://services-crypto/utils.sys.mjs"
);
const { Utils } = ChromeUtils.importESModule(
  "resource://services-sync/util.sys.mjs"
);

/**
 * Like Assert.throws, but for generators.
 *
 * @param {string | object | Function} constraint
 *        What to use to check the exception.
 * @param {Function} f
 *        The function to call.
 */
async function throwsGen(constraint, f) {
  let threw = false;
  let exception;
  try {
    await f();
  } catch (e) {
    threw = true;
    exception = e;
  }

  ok(threw, "did not throw an exception");

  const debuggingMessage = `got ${exception}, expected ${constraint}`;

  if (typeof constraint === "function") {
    ok(constraint(exception), debuggingMessage);
  } else {
    let message = exception;
    if (typeof exception === "object") {
      message = exception.message;
    }
    Assert.strictEqual(constraint, message, debuggingMessage);
  }
}

/**
 * An EncryptionRemoteTransformer that uses a fixed key bundle,
 * suitable for testing.
 */
class StaticKeyEncryptionRemoteTransformer extends EncryptionRemoteTransformer {
  constructor(keyBundle) {
    super();
    this.keyBundle = keyBundle;
  }

  getKeys() {
    return Promise.resolve(this.keyBundle);
  }
}
const BORING_KB =
  "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
let transformer;
add_task(async function setup() {
  const STRETCHED_KEY = await CryptoUtils.hkdfLegacy(
    BORING_KB,
    undefined,
    `testing storage.sync encryption`,
    2 * 32
  );
  const KEY_BUNDLE = {
    hmacKey: STRETCHED_KEY.slice(0, 32),
    encryptionKeyB64: btoa(STRETCHED_KEY.slice(32, 64)),
  };
  transformer = new StaticKeyEncryptionRemoteTransformer(KEY_BUNDLE);
});

add_task(async function test_encryption_transformer_roundtrip() {
  const POSSIBLE_DATAS = [
    "string",
    2, // number
    [1, 2, 3], // array
    { key: "value" }, // object
  ];

  for (let data of POSSIBLE_DATAS) {
    const record = { data, id: "key-some_2D_key", key: "some-key" };

    deepEqual(
      record,
      await transformer.decode(await transformer.encode(record))
    );
  }
});

add_task(async function test_refuses_to_decrypt_tampered() {
  const encryptedRecord = await transformer.encode({
    data: [1, 2, 3],
    id: "key-some_2D_key",
    key: "some-key",
  });
  const tamperedHMAC = Object.assign({}, encryptedRecord, {
    hmac: "0000000000000000000000000000000000000000000000000000000000000001",
  });
  await throwsGen(Utils.isHMACMismatch, async function () {
    await transformer.decode(tamperedHMAC);
  });

  const tamperedIV = Object.assign({}, encryptedRecord, {
    IV: "aaaaaaaaaaaaaaaaaaaaaa==",
  });
  await throwsGen(Utils.isHMACMismatch, async function () {
    await transformer.decode(tamperedIV);
  });
});