UNPKG

@dwn-protocol/id-sdk

Version:

SDK for accessing the features and capabilities

152 lines (151 loc) 7.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConcatKdf = void 0; const sha256_1 = require("@noble/hashes/sha256"); const index_js_1 = require("../../common/index.js"); const utils_1 = require("@noble/hashes/utils"); const errors_js_1 = require("../algorithms-api/errors.js"); /** * An implementation of the Concatenation Key Derivation Function (ConcatKDF) * as specified in NIST.800-56A, a single-step key-derivation function (SSKDF). * ConcatKDF produces a derived key from a secret key (like a shared secret * from ECDH), and other optional public information. This implementation * specifically uses SHA-256 as the pseudorandom function (PRF). * * @see {@link https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar3.pdf | NIST.800-56A} * @see {@link https://datatracker.ietf.org/doc/html/rfc7518#section-4.6.2 | RFC 7518 Section 4.6.2} * * Note: This implementation allows for only a single round / repetition * using the function K(1) = H(counter || Z || OtherInfo), where: * K(1) is the derived key material after one round * H is the SHA-256 hashing function * counter is a 32-bit, big-endian bit string counter set to 0x00000001 * Z is the shared secret value obtained from a key agreement protocol * OtherInfo is a bit string used to ensure that the derived keying * material is adequately "bound" to the key-agreement transaction. * * Additional Information: * * Z, or "shared secret": * The shared secret value obtained from a key agreement protocol, such as * Diffie-Hellman, ECDH (Elliptic Curve Diffie-Hellman). Importantly, this * shared secret is not directly used as the encryption or authentication * key, but as an input to a key derivation function (KDF), such as Concat * KDF, to generate the actual key. This adds an extra layer of security, as * even if the shared secret gets compromised, the actual encryption or * authentication key stays safe. This shared secret 'Z' value is kept * confidential between the two parties in the key agreement protocol. */ class ConcatKdf { /** * Derives a key of a specified length from the input parameters. * * @param options - Input parameters for key derivation. * @param options.keyDataLen - The desired length of the derived key in bits. * @param options.sharedSecret - The shared secret key to derive from. * @param options.otherInfo - Additional public information to use in key derivation. * @returns The derived key as a Uint8Array. * * @throws {NotSupportedError} If the keyDataLen would require multiple rounds. */ static async deriveKey(options) { const { keyDataLen, sharedSecret } = options; // RFC 7518 Section 4.6.2 specifies using SHA-256 for ECDH key agreement: // "Key derivation is performed using the Concat KDF, as defined in // Section 5.8.1 of [NIST.800-56A], where the Digest Method is SHA-256." // Reference: https://tools.ietf.org/html/rfc7518#section-4.6.2 const hashLen = 256; // This implementation only supports single round Concat KDF. const roundCount = Math.ceil(keyDataLen / hashLen); if (roundCount !== 1) { throw new errors_js_1.NotSupportedError(`Concat KDF with ${roundCount} rounds not supported.`); } // Initialize a 32-bit, big-endian bit string counter as 0x00000001. const counter = new Uint8Array(4); new DataView(counter.buffer).setUint32(0, roundCount); // Compute the OtherInfo bit-string. const otherInfo = ConcatKdf.computeOtherInfo(options.otherInfo); // Compute K(i) = H(counter || Z || OtherInfo) // return concatBytes(counter, sharedSecretZ, otherInfo); const derivedKeyingMaterial = (0, sha256_1.sha256)((0, utils_1.concatBytes)(counter, sharedSecret, otherInfo)); // Return the bit string of derived keying material of length keyDataLen bits. return derivedKeyingMaterial.slice(0, keyDataLen / 8); } /** * Computes the OtherInfo parameter as specified in NIST.800-56A. * OtherInfo binds the derived key material to the context of the * key agreement transaction. * * This implementation follows the recommended format for OtherInfo * specified in section 5.8.1.2.1 of the NIST.800-56A publication. * * OtherInfo is a bit string equal to the following concatenation: * AlgorithmID || PartyUInfo || PartyVInfo {|| SuppPubInfo }{|| SuppPrivInfo } * * SuppPubInfo is the key length in bits, big endian encoded as a * 32-bit number. For example, 128 would be [0, 0, 0, 128] and * 256 would be [0, 0, 1, 0]. * * @param options - Input data to construct OtherInfo. * @returns OtherInfo as a Uint8Array. */ static computeOtherInfo(options) { // Required sub-fields. const algorithmId = ConcatKdf.toDataLenData({ data: options.algorithmId }); const partyUInfo = ConcatKdf.toDataLenData({ data: options.partyUInfo }); const partyVInfo = ConcatKdf.toDataLenData({ data: options.partyVInfo }); // Optional sub-fields. const suppPubInfo = ConcatKdf.toDataLenData({ data: options.suppPubInfo, variableLength: false }); const suppPrivInfo = ConcatKdf.toDataLenData({ data: options.suppPrivInfo }); // Concatenate AlgorithmID || PartyUInfo || PartyVInfo || SuppPubInfo || SuppPrivInfo. const otherInfo = (0, utils_1.concatBytes)(algorithmId, partyUInfo, partyVInfo, suppPubInfo, suppPrivInfo); return otherInfo; } /** * Encodes input data as a length-prefixed byte string, or * as a fixed-length bit string if specified. * * If variableLength = true, return the data in the form Datalen || Data, * where Data is a variable-length string of zero or more (eight-bit) * bytes, and Datalen is a fixed-length, big-endian counter that * indicates the length (in bytes) of Data. * * If variableLength = false, return the data formatted as a * fixed-length bit string. * * @param options - Input data and options for the conversion. * @param options.data - The input data to encode. Must be a type convertible to Uint8Array by the Convert class. * @param options.variableLength - Whether to output the data as variable length. Default is true. * @returns The input data encoded as a Uint8Array. * * @throws {TypeError} If fixed-length data is not a number. */ static toDataLenData(options) { const { data, variableLength = true } = options; let encodedData; const dataType = (0, index_js_1.universalTypeOf)(data); // Return an emtpy octet sequence if data is not specified. if (dataType === 'Undefined') { return new Uint8Array(0); } if (variableLength) { const dataU8A = (dataType === 'Uint8Array') ? data : new index_js_1.Convert(data, dataType).toUint8Array(); const bufferLength = dataU8A.length; encodedData = new Uint8Array(4 + bufferLength); new DataView(encodedData.buffer).setUint32(0, bufferLength); encodedData.set(dataU8A, 4); } else { if (typeof data !== 'number') { throw TypeError('Fixed length input must be a number.'); } encodedData = new Uint8Array(4); new DataView(encodedData.buffer).setUint32(0, data); } return encodedData; } } exports.ConcatKdf = ConcatKdf; //# sourceMappingURL=concat-kdf.js.map