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; } } }); }