123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 |
- /* global AUTH_CONFIG */
- import { arrayToB64, b64ToArray } from './utils';
- const encoder = new TextEncoder();
- const decoder = new TextDecoder();
- function getOtherInfo(enc) {
- const name = encoder.encode(enc);
- const length = 256;
- const buffer = new ArrayBuffer(name.length + 16);
- const dv = new DataView(buffer);
- const result = new Uint8Array(buffer);
- let i = 0;
- dv.setUint32(i, name.length);
- i += 4;
- result.set(name, i);
- i += name.length;
- dv.setUint32(i, 0);
- i += 4;
- dv.setUint32(i, 0);
- i += 4;
- dv.setUint32(i, length);
- return result;
- }
- function concat(b1, b2) {
- const result = new Uint8Array(b1.length + b2.length);
- result.set(b1, 0);
- result.set(b2, b1.length);
- return result;
- }
- async function concatKdf(key, enc) {
- if (key.length !== 32) {
- throw new Error('unsupported key length');
- }
- const otherInfo = getOtherInfo(enc);
- const buffer = new ArrayBuffer(4 + key.length + otherInfo.length);
- const dv = new DataView(buffer);
- const concat = new Uint8Array(buffer);
- dv.setUint32(0, 1);
- concat.set(key, 4);
- concat.set(otherInfo, key.length + 4);
- const result = await crypto.subtle.digest('SHA-256', concat);
- return new Uint8Array(result);
- }
- export async function prepareScopedBundleKey(storage) {
- const keys = await crypto.subtle.generateKey(
- {
- name: 'ECDH',
- namedCurve: 'P-256'
- },
- true,
- ['deriveBits']
- );
- const privateJwk = await crypto.subtle.exportKey('jwk', keys.privateKey);
- const publicJwk = await crypto.subtle.exportKey('jwk', keys.publicKey);
- const kid = await crypto.subtle.digest(
- 'SHA-256',
- encoder.encode(JSON.stringify(publicJwk))
- );
- privateJwk.kid = kid;
- publicJwk.kid = kid;
- storage.set('scopedBundlePrivateKey', JSON.stringify(privateJwk));
- return arrayToB64(encoder.encode(JSON.stringify(publicJwk)));
- }
- export async function decryptBundle(storage, bundle) {
- const privateJwk = JSON.parse(storage.get('scopedBundlePrivateKey'));
- storage.remove('scopedBundlePrivateKey');
- const privateKey = await crypto.subtle.importKey(
- 'jwk',
- privateJwk,
- {
- name: 'ECDH',
- namedCurve: 'P-256'
- },
- false,
- ['deriveBits']
- );
- const jweParts = bundle.split('.');
- if (jweParts.length !== 5) {
- throw new Error('invalid jwe');
- }
- const header = JSON.parse(decoder.decode(b64ToArray(jweParts[0])));
- const additionalData = encoder.encode(jweParts[0]);
- const iv = b64ToArray(jweParts[2]);
- const ciphertext = b64ToArray(jweParts[3]);
- const tag = b64ToArray(jweParts[4]);
- if (header.alg !== 'ECDH-ES' || header.enc !== 'A256GCM') {
- throw new Error('unsupported jwe type');
- }
- const publicKey = await crypto.subtle.importKey(
- 'jwk',
- header.epk,
- {
- name: 'ECDH',
- namedCurve: 'P-256'
- },
- false,
- []
- );
- const sharedBits = await crypto.subtle.deriveBits(
- {
- name: 'ECDH',
- public: publicKey
- },
- privateKey,
- 256
- );
- const rawSharedKey = await concatKdf(new Uint8Array(sharedBits), header.enc);
- const sharedKey = await crypto.subtle.importKey(
- 'raw',
- rawSharedKey,
- {
- name: 'AES-GCM'
- },
- false,
- ['decrypt']
- );
- const plaintext = await crypto.subtle.decrypt(
- {
- name: 'AES-GCM',
- iv: iv,
- additionalData: additionalData,
- tagLength: tag.length * 8
- },
- sharedKey,
- concat(ciphertext, tag)
- );
- return JSON.parse(decoder.decode(plaintext));
- }
- export async function preparePkce(storage) {
- const verifier = arrayToB64(crypto.getRandomValues(new Uint8Array(64)));
- storage.set('pkceVerifier', verifier);
- const challenge = await crypto.subtle.digest(
- 'SHA-256',
- encoder.encode(verifier)
- );
- return arrayToB64(new Uint8Array(challenge));
- }
- export async function deriveFileListKey(ikm) {
- const baseKey = await crypto.subtle.importKey(
- 'raw',
- b64ToArray(ikm),
- { name: 'HKDF' },
- false,
- ['deriveKey']
- );
- const fileListKey = await crypto.subtle.deriveKey(
- {
- name: 'HKDF',
- salt: new Uint8Array(),
- info: encoder.encode('fileList'),
- hash: 'SHA-256'
- },
- baseKey,
- {
- name: 'AES-GCM',
- length: 128
- },
- true,
- ['encrypt', 'decrypt']
- );
- const rawFileListKey = await crypto.subtle.exportKey('raw', fileListKey);
- return arrayToB64(new Uint8Array(rawFileListKey));
- }
- export async function getFileListKey(storage, bundle) {
- const jwks = await decryptBundle(storage, bundle);
- const jwk = jwks[AUTH_CONFIG.key_scope];
- return deriveFileListKey(jwk.k);
- }
|