diff options
Diffstat (limited to 'web/js/browser/lib/hawk.js')
-rw-r--r-- | web/js/browser/lib/hawk.js | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/web/js/browser/lib/hawk.js b/web/js/browser/lib/hawk.js new file mode 100644 index 0000000..69c7153 --- /dev/null +++ b/web/js/browser/lib/hawk.js @@ -0,0 +1,145 @@ +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, uint8ToBase64, uint8ToHex } from './utils'; +const encoder = () => new TextEncoder(); +const NAMESPACE = 'identity.mozilla.com/picl/v1/'; +export function deriveHawkCredentials(token, context) { + return __awaiter(this, void 0, void 0, function* () { + const baseKey = yield crypto.subtle.importKey('raw', hexToUint8(token), 'HKDF', false, ['deriveBits']); + const keyMaterial = yield crypto.subtle.deriveBits({ + name: 'HKDF', + salt: new Uint8Array(0), + // @ts-ignore + info: encoder().encode(`${NAMESPACE}${context}`), + hash: 'SHA-256', + }, baseKey, 32 * 3 * 8); + const id = new Uint8Array(keyMaterial.slice(0, 32)); + const authKey = new Uint8Array(keyMaterial.slice(32, 64)); + const bundleKey = new Uint8Array(keyMaterial.slice(64)); + return { + id: uint8ToHex(id), + key: authKey, + bundleKey: uint8ToHex(bundleKey), + }; + }); +} +// The following is adapted from https://github.com/hapijs/hawk/blob/master/lib/browser.js +/* + HTTP Hawk Authentication Scheme + Copyright (c) 2012-2013, Eran Hammer <eran@hueniverse.com> + MIT Licensed + */ +function parseUri(input) { + const parts = input.match(/^([^:]+)\:\/\/(?:[^@/]*@)?([^\/:]+)(?:\:(\d+))?([^#]*)(?:#.*)?$/); + if (!parts) { + return { host: '', port: '', resource: '' }; + } + const scheme = parts[1].toLowerCase(); + const uri = { + host: parts[2], + port: parts[3] || (scheme === 'http' ? '80' : scheme === 'https' ? '443' : ''), + resource: parts[4], + }; + return uri; +} +function randomString(size) { + const randomSource = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + const len = randomSource.length; + const result = []; + for (let i = 0; i < size; ++i) { + result[i] = randomSource[Math.floor(Math.random() * len)]; + } + return result.join(''); +} +function generateNormalizedString(type, options) { + let normalized = 'hawk.1.' + + type + + '\n' + + options.ts + + '\n' + + options.nonce + + '\n' + + (options.method || '').toUpperCase() + + '\n' + + (options.resource || '') + + '\n' + + options.host.toLowerCase() + + '\n' + + options.port + + '\n' + + (options.hash || '') + + '\n'; + if (options.ext) { + normalized += options.ext.replace(/\\/g, '\\\\').replace(/\n/g, '\\n'); + } + normalized += '\n'; + if (options.app) { + normalized += options.app + '\n' + (options.dlg || '') + '\n'; + } + return normalized; +} +function calculatePayloadHash(payload = '', contentType = '') { + return __awaiter(this, void 0, void 0, function* () { + const data = encoder().encode(`hawk.1.payload\n${contentType}\n${payload}\n`); + const hash = yield crypto.subtle.digest('SHA-256', data); + return uint8ToBase64(new Uint8Array(hash)); + }); +} +function calculateMac(type, credentials, options) { + return __awaiter(this, void 0, void 0, function* () { + const normalized = generateNormalizedString(type, options); + const key = yield crypto.subtle.importKey('raw', credentials.key, { + name: 'HMAC', + hash: 'SHA-256', + length: 256, + }, true, ['sign']); + const hmac = yield crypto.subtle.sign('HMAC', key, encoder().encode(normalized)); + return uint8ToBase64(new Uint8Array(hmac)); + }); +} +export function hawkHeader(method, uri, options) { + return __awaiter(this, void 0, void 0, function* () { + const timestamp = options.timestamp || + Math.floor((Date.now() + (options.localtimeOffsetMsec || 0)) / 1000); + const parsedUri = parseUri(uri); + const hash = yield calculatePayloadHash(options.payload, options.contentType); + const artifacts = { + ts: timestamp, + nonce: options.nonce || randomString(6), + method, + resource: parsedUri.resource, + host: parsedUri.host, + port: parsedUri.port, + hash, + }; + const mac = yield calculateMac('header', options.credentials, artifacts); + const header = 'Hawk id="' + + options.credentials.id + + '", ts="' + + artifacts.ts + + '", nonce="' + + artifacts.nonce + + (artifacts.hash ? '", hash="' + artifacts.hash : '') + + '", mac="' + + mac + + '"'; + return header; + }); +} +export function header(method, uri, token, kind, options) { + return __awaiter(this, void 0, void 0, function* () { + const credentials = yield deriveHawkCredentials(token, kind); + const authorization = yield hawkHeader(method, uri, Object.assign({ credentials }, options)); + return new Headers({ authorization }); + }); +} |