summaryrefslogtreecommitdiff
path: root/web/js/browser/lib/crypto.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/js/browser/lib/crypto.js')
-rw-r--r--web/js/browser/lib/crypto.js163
1 files changed, 163 insertions, 0 deletions
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;
+ }
+ }
+ });
+}