summaryrefslogtreecommitdiff
path: root/web/js/browser/lib/hawk.js
blob: 69c71534dceb3530668f7072dae8cd16267ea9e5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
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 });
    });
}