From 2f8dce44d3f2be74b5c6ec0a2e7f4ceced715328 Mon Sep 17 00:00:00 2001 From: pennae Date: Wed, 13 Jul 2022 10:33:30 +0200 Subject: initial import --- web/js/browser/lib/crypto.js | 163 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 web/js/browser/lib/crypto.js (limited to 'web/js/browser/lib/crypto.js') diff --git a/web/js/browser/lib/crypto.js b/web/js/browser/lib/crypto.js new file mode 100644 index 0000000..6fa6107 --- /dev/null +++ b/web/js/browser/lib/crypto.js @@ -0,0 +1,163 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { hexToUint8, uint8ToBase64Url, uint8ToHex, xor } from './utils'; +const encoder = () => new TextEncoder(); +const NAMESPACE = 'identity.mozilla.com/picl/v1/'; +// These functions implement the onepw protocol +// https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol +export function getCredentials(email, password) { + return __awaiter(this, void 0, void 0, function* () { + const passkey = yield crypto.subtle.importKey('raw', encoder().encode(password), 'PBKDF2', false, ['deriveBits']); + const quickStretchedRaw = yield crypto.subtle.deriveBits({ + name: 'PBKDF2', + salt: encoder().encode(`${NAMESPACE}quickStretch:${email}`), + iterations: 1000, + hash: 'SHA-256', + }, passkey, 256); + const quickStretchedKey = yield crypto.subtle.importKey('raw', quickStretchedRaw, 'HKDF', false, ['deriveBits']); + const authPW = yield crypto.subtle.deriveBits({ + name: 'HKDF', + salt: new Uint8Array(0), + // The builtin ts type definition for HKDF was wrong + // at the time this was written, hence the ignore + // @ts-ignore + info: encoder().encode(`${NAMESPACE}authPW`), + hash: 'SHA-256', + }, quickStretchedKey, 256); + const unwrapBKey = yield crypto.subtle.deriveBits({ + name: 'HKDF', + salt: new Uint8Array(0), + // @ts-ignore + info: encoder().encode(`${NAMESPACE}unwrapBkey`), + hash: 'SHA-256', + }, quickStretchedKey, 256); + return { + authPW: uint8ToHex(new Uint8Array(authPW)), + unwrapBKey: uint8ToHex(new Uint8Array(unwrapBKey)), + }; + }); +} +export function deriveBundleKeys(key, keyInfo, payloadBytes = 64) { + return __awaiter(this, void 0, void 0, function* () { + const baseKey = yield crypto.subtle.importKey('raw', hexToUint8(key), 'HKDF', false, ['deriveBits']); + const keyMaterial = yield crypto.subtle.deriveBits({ + name: 'HKDF', + salt: new Uint8Array(0), + // @ts-ignore + info: encoder().encode(`${NAMESPACE}${keyInfo}`), + hash: 'SHA-256', + }, baseKey, (32 + payloadBytes) * 8); + const hmacKey = yield crypto.subtle.importKey('raw', new Uint8Array(keyMaterial.slice(0, 32)), { + name: 'HMAC', + hash: 'SHA-256', + length: 256, + }, true, ['verify']); + const xorKey = new Uint8Array(keyMaterial.slice(32)); + return { + hmacKey, + xorKey, + }; + }); +} +export function unbundleKeyFetchResponse(key, bundle) { + return __awaiter(this, void 0, void 0, function* () { + const b = hexToUint8(bundle); + const keys = yield deriveBundleKeys(key, 'account/keys'); + const ciphertext = b.subarray(0, 64); + const expectedHmac = b.subarray(b.byteLength - 32); + const valid = yield crypto.subtle.verify('HMAC', keys.hmacKey, expectedHmac, ciphertext); + if (!valid) { + throw new Error('Bad HMac'); + } + const keyAWrapB = xor(ciphertext, keys.xorKey); + return { + kA: uint8ToHex(keyAWrapB.subarray(0, 32)), + wrapKB: uint8ToHex(keyAWrapB.subarray(32)), + }; + }); +} +export function unwrapKB(wrapKB, unwrapBKey) { + return uint8ToHex(xor(hexToUint8(wrapKB), hexToUint8(unwrapBKey))); +} +export function hkdf(keyMaterial, salt, info, bytes) { + return __awaiter(this, void 0, void 0, function* () { + const key = yield crypto.subtle.importKey('raw', keyMaterial, 'HKDF', false, [ + 'deriveBits', + ]); + const result = yield crypto.subtle.deriveBits({ + name: 'HKDF', + salt, + // @ts-ignore + info, + hash: 'SHA-256', + }, key, bytes * 8); + return new Uint8Array(result); + }); +} +export function jweEncrypt(keyMaterial, kid, data, forTestingOnly) { + return __awaiter(this, void 0, void 0, function* () { + const key = yield crypto.subtle.importKey('raw', keyMaterial, { + name: 'AES-GCM', + }, false, ['encrypt']); + const jweHeader = uint8ToBase64Url(encoder().encode(JSON.stringify({ + enc: 'A256GCM', + alg: 'dir', + kid, + }))); + const iv = (forTestingOnly === null || forTestingOnly === void 0 ? void 0 : forTestingOnly.testIV) || crypto.getRandomValues(new Uint8Array(12)); + const encrypted = yield crypto.subtle.encrypt({ + name: 'AES-GCM', + iv, + additionalData: encoder().encode(jweHeader), + tagLength: 128, + }, key, data); + const ciphertext = new Uint8Array(encrypted.slice(0, encrypted.byteLength - 16)); + const authenticationTag = new Uint8Array(encrypted.slice(encrypted.byteLength - 16)); + // prettier-ignore + const compactJWE = `${jweHeader}..${uint8ToBase64Url(iv)}.${uint8ToBase64Url(ciphertext)}.${uint8ToBase64Url(authenticationTag)}`; + return compactJWE; + }); +} +export function checkWebCrypto() { + return __awaiter(this, void 0, void 0, function* () { + try { + yield crypto.subtle.importKey('raw', crypto.getRandomValues(new Uint8Array(16)), 'PBKDF2', false, ['deriveKey']); + yield crypto.subtle.importKey('raw', crypto.getRandomValues(new Uint8Array(32)), 'HKDF', false, ['deriveKey']); + yield crypto.subtle.importKey('raw', crypto.getRandomValues(new Uint8Array(32)), { + name: 'HMAC', + hash: 'SHA-256', + length: 256, + }, false, ['sign']); + yield crypto.subtle.importKey('raw', crypto.getRandomValues(new Uint8Array(32)), { + name: 'AES-GCM', + }, false, ['encrypt']); + yield crypto.subtle.digest('SHA-256', crypto.getRandomValues(new Uint8Array(16))); + return true; + } + catch (err) { + try { + console.warn('loading webcrypto shim', err); + // prettier-ignore + // @ts-ignore + window.asmCrypto = yield import(/* webpackChunkName: "asmcrypto.js" */ 'asmcrypto.js'); + // prettier-ignore + // @ts-ignore + yield import(/* webpackChunkName: "webcrypto-liner" */ 'webcrypto-liner/build/shim'); + return true; + } + catch (e) { + return false; + } + } + }); +} -- cgit v1.2.3