summaryrefslogtreecommitdiff
path: root/web/js/crypto.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/js/crypto.js')
-rw-r--r--web/js/crypto.js137
1 files changed, 137 insertions, 0 deletions
diff --git a/web/js/crypto.js b/web/js/crypto.js
new file mode 100644
index 0000000..5c12a2c
--- /dev/null
+++ b/web/js/crypto.js
@@ -0,0 +1,137 @@
+'use strict';
+
+// taken from fxa
+function hexToUint8(str) {
+ const HEX_STRING = /^(?:[a-fA-F0-9]{2})+$/;
+ if (!HEX_STRING.test(str)) {
+ throw new Error(`invalid hex string: ${str}`);
+ }
+ const bytes = str.match(/[a-fA-F0-9]{2}/g);
+ return new Uint8Array(bytes.map((byte) => parseInt(byte, 16)));
+}
+function uint8ToHex(array) {
+ return array.reduce((str, byte) => str + ('00' + byte.toString(16)).slice(-2), '');
+}
+function uint8ToBase64(array) {
+ return btoa(String.fromCharCode(...array));
+}
+function uint8ToBase64Url(array) {
+ return uint8ToBase64(array).replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
+}
+
+export function urlatob(s) {
+ return atob(s.replace(/-/g, '+').replace(/_/g, "/"));
+}
+
+export async function deriveScopedKey(kB, uid, identifier, key_rotation_secret, key_rotation_timestamp) {
+ async function importKey(k) {
+ return await crypto.subtle.importKey('raw', hexToUint8(k), 'HKDF', false, ['deriveBits']);
+ }
+
+ async function derive(salt, k, info, len) {
+ let params = {
+ name: 'HKDF',
+ salt,
+ info: new TextEncoder().encode(info),
+ hash: 'SHA-256',
+ };
+ return new Uint8Array(await crypto.subtle.deriveBits(params, await importKey(k), len * 8));
+ }
+
+ if (identifier == "https://identity.mozilla.com/apps/oldsync") {
+ const bits = await derive(
+ new Uint8Array(),
+ kB,
+ "identity.mozilla.com/picl/v1/oldsync",
+ 64);
+ const kHash = await crypto.subtle.digest("SHA-256", hexToUint8(kB));
+ const kid = key_rotation_timestamp.toString() + "-" +
+ uint8ToBase64Url(new Uint8Array(kHash.slice(0, 16)));
+ return {
+ k: uint8ToBase64Url(bits),
+ kty: "oct",
+ kid,
+ scope: identifier,
+ };
+ } else {
+ const bits = await derive(
+ hexToUint8(uid),
+ kB + key_rotation_secret,
+ "identity.mozilla.com/picl/v1/scoped_key\n" + identifier,
+ 16 + 32);
+ const fp = new Uint8Array(bits.slice(0, 16));
+ const key = new Uint8Array(bits.slice(16));
+ const kid = key_rotation_timestamp.toString() + "-" + uint8ToBase64Url(fp);
+ return {
+ k: uint8ToBase64Url(key),
+ kty: "oct",
+ kid,
+ scope: identifier,
+ };
+ }
+}
+
+export async function encryptScopedKeys(bundle, to, local_key, iv) {
+ let encode = s => new TextEncoder().encode(s);
+
+ let peer_key = await crypto.subtle.importKey(
+ "jwk",
+ JSON.parse(urlatob(to)),
+ { name: "ECDH", namedCurve: "P-256" },
+ false,
+ ['deriveKey']);
+ local_key = local_key || await crypto.subtle.generateKey(
+ { name: "ECDH", namedCurve: "P-256" },
+ true,
+ ['deriveKey']);
+ iv = iv || new Uint8Array(await crypto.subtle.exportKey(
+ "raw",
+ await crypto.subtle.generateKey({ name: "AES-CBC", length: 128 }, true, ['encrypt'])
+ )).slice(0, 12);
+
+ let key = await crypto.subtle.deriveKey(
+ { name: "ECDH", public: peer_key },
+ local_key.privateKey,
+ { name: "AES-GCM", length: 256 },
+ true,
+ ['encrypt']);
+ key = new Uint8Array(await crypto.subtle.exportKey("raw", key));
+ key = await crypto.subtle.digest(
+ "SHA-256",
+ new Uint8Array([
+ 0, 0, 0, 1, // rounds
+ ...key,
+ 0, 0, 0, 7, ...encode("A256GCM"),
+ 0, 0, 0, 0, // apu
+ 0, 0, 0, 0, // apv
+ 0, 0, 1, 0, // key size
+ ]));
+ key = await crypto.subtle.importKey("raw", key, { name: "AES-GCM" }, false, ['encrypt']);
+
+ let exported_key = await crypto.subtle.exportKey("jwk", local_key.publicKey);
+ let header = {
+ "alg": "ECDH-ES",
+ "enc": "A256GCM",
+ "epk": {
+ crv: "P-256",
+ kty: "EC",
+ x: exported_key.x,
+ y: exported_key.y
+ }
+ };
+
+ let ciphered = await crypto.subtle.encrypt(
+ { name: "AES-GCM", iv, additionalData: encode(uint8ToBase64Url(encode(JSON.stringify(header)))) },
+ key,
+ encode(JSON.stringify(bundle)));
+ let tag = ciphered.slice(-16);
+ ciphered = ciphered.slice(0, -16);
+
+ return (uint8ToBase64Url(encode(JSON.stringify(header))) +
+ ".." +
+ uint8ToBase64Url(new Uint8Array(iv)) +
+ "." +
+ uint8ToBase64Url(new Uint8Array(ciphered)) +
+ "." +
+ uint8ToBase64Url(new Uint8Array(tag)));
+}