UNPKG

@digitalcredentials/minimal-cipher

Version:
85 lines (77 loc) 3.48 kB
/*! * Copyright (c) 2019-2020 Digital Bazaar, Inc. All rights reserved. */ import crypto from '../crypto.js'; // only supported algorithm const KEY_ALGORITHM = 'ECDH-ES+A256KW'; // create static ALGORITHM_ID const ALGORITHM_CONTENT = new TextEncoder().encode(KEY_ALGORITHM); const ALGORITHM_ID = new Uint8Array(4 + ALGORITHM_CONTENT.length); // write length of content as 32-bit big endian integer, then write content const dv = new DataView( ALGORITHM_ID.buffer, ALGORITHM_ID.byteOffset, ALGORITHM_ID.byteLength); dv.setUint32(0, ALGORITHM_CONTENT.length); ALGORITHM_ID.set(ALGORITHM_CONTENT, 4); // RFC 7518 Section 4.6.2 specifies using SHA-256 for ECDH-ES KDF // https://tools.ietf.org/html/rfc7518#section-4.6.2 const HASH_ALGORITHM = {name: 'SHA-256'}; // derived keys are always 256-bits const KEY_LENGTH = 256; /** * Derives a 256-bit AES-KW key encryption key from a shared secret that * was derived from an ephemeral and static pair * of Elliptic Curve Diffie-Hellman keys. * * The KDF used is described in RFC 7518. This KDF is referenced by RFC 8037, * which defines how to perform Curve25519 (X25519) ECDH key agreement. * * @param {object} options - The options to use. * @param {Uint8Array} options.secret - The shared secret (i.e., `Z`) to use. * @param {Uint8Array} options.producerInfo - An array of application-specific * bytes describing the consumer (aka the "encrypter" or "sender"). * @param {Uint8Array} options.consumerInfo - An array of application-specific * bytes describing the producer (aka the "decrypter" or * "receiver"/"recipient"). * * @returns {Promise<Uint8Array>} - Resolves to the generated key. */ export async function deriveKey({secret, producerInfo, consumerInfo}) { if(!(secret instanceof Uint8Array && secret.length > 0)) { throw new TypeError('"secret" must be a non-empty Uint8Array.'); } if(!(producerInfo instanceof Uint8Array && producerInfo.length > 0)) { throw new TypeError('"producerInfo" must be a non-empty Uint8Array.'); } if(!(consumerInfo instanceof Uint8Array && consumerInfo.length > 0)) { throw new TypeError('"consumerInfo" must be a non-empty Uint8Array.'); } // the output of Concat KDF is hash(roundNumber || Z || OtherInfo) // where roundNumber is always 1 because the hash length is presumed to // ...match the key length, encoded as a big endian 32-bit integer // where OtherInfo is: // AlgorithmID || PartyUInfo || PartyVInfo || SuppPubInfo // where SuppPubInfo is the key length in bits, big endian encoded as a // 32-bit number, i.e., 256 === [0, 0, 1, 0] const input = new Uint8Array( 4 + // round number secret.length + // `Z` ALGORITHM_ID.length + // AlgorithmID 4 + producerInfo.length + // PartyUInfo 4 + consumerInfo.length + // PartyVInfo 4); // SuppPubInfo (key data length in bits) let offset = 0; const dv = new DataView(input.buffer, input.byteOffset, input.byteLength); dv.setUint32(offset, 1); input.set(secret, offset += 4); input.set(ALGORITHM_ID, offset += secret.length); dv.setUint32(offset += ALGORITHM_ID.length, producerInfo.length); input.set(producerInfo, offset += 4); dv.setUint32(offset += producerInfo.length, consumerInfo.length); input.set(consumerInfo, offset += 4); dv.setUint32(offset += consumerInfo.length, KEY_LENGTH); // hash input and return result as derived key return new Uint8Array( await crypto.subtle.digest(HASH_ALGORITHM, input)); }