junkurihara/jscu

View on GitHub
packages/js-crypto-hkdf/src/hkdf.ts

Summary

Maintainability
A
25 mins
Test Coverage
/**
* hkdf.js
*/
 
import params, {HashTypes} from './params';
import * as util from 'js-crypto-env';
 
import random from 'js-crypto-random';
import hmac from 'js-crypto-hmac';
import jseu from 'js-encoding-utils';
 
/**
* Hash-based Key Derivation Function computing from given master secret and salt.
* If salt is not given, salt would be automatically generated inside.
* Specification is given in RFC5869 {@link https://tools.ietf.org/html/rfc5869}.
* @param {Uint8Array} master - Master secret to derive the key.
* @param {String} [hash='SHA-256] - Name of hash algorithm used to derive the key.
* @param {Number} [length = 32] - Intended length of derived key.
* @param {String} [info=''] - String for information field of HKDF.
* @param {Uint8Array} [salt=null] - Byte array of salt.
* @return {Promise<{key: Uint8Array, salt: Uint8Array}>} - Derived key and salt used to derive the key.
*/
Function `compute` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.
export const compute = async (
master: Uint8Array,
hash: HashTypes = 'SHA-256',
length: number = 32,
info: string = '',
salt: Uint8Array|null = null
): Promise<{key: Uint8Array, salt: Uint8Array}> => {
const env = util.getCrypto();
 
let key;
if(!salt) salt = random.getRandomBytes(length);
 
if (env.name === 'webCrypto' && typeof env.crypto.importKey === 'function' && typeof env.crypto.deriveBits === 'function') {
try { // modern browsers supporting HKDF
const masterObj = await env.crypto.importKey(
'raw', master, {name: 'HKDF'}, false, ['deriveKey', 'deriveBits']
);
const hkdfCtrParams = { name: 'HKDF', salt, info: jseu.encoder.stringToArrayBuffer(info), hash};
key = await env.crypto.deriveBits(hkdfCtrParams, masterObj, length * 8);
key = new Uint8Array(key);
}
catch (e) { // fall back to pure js implementation
key = await rfc5869(master, hash, length, info, salt);
}
}
else { // node and IE
key = await rfc5869(master, hash, length, info, salt);
}
 
return {key, salt};
};
 
/**
* Naive implementation of RFC5869 in PureJavaScript
* @param {Uint8Array} master - Master secret to derive the key.
* @param {String} hash - Name of hash algorithm used to derive the key.
* @param {Number} length - Intended length of derived key.
* @param {String} info - String for information field of HKDF.
* @param {Uint8Array} salt - Byte array of salt.
* @return {Promise<Uint8Array>} - Derived key.
*/
const rfc5869 = async (
master: Uint8Array,
hash: HashTypes,
length: number,
info: string,
salt: Uint8Array) => {
const len = params.hashes[hash].hashSize;
 
// RFC5869 Step 1 (Extract)
const prk = await hmac.compute(salt, master, hash);
 
// RFC5869 Step 2 (Expand)
let t = new Uint8Array([]);
const okm = new Uint8Array(Math.ceil(length / len) * len);
const uintInfo = jseu.encoder.stringToArrayBuffer(info);
for(let i = 0; i < Math.ceil(length / len); i++){
const concat = new Uint8Array(t.length + uintInfo.length + 1);
concat.set(t);
concat.set(uintInfo, t.length);
concat.set(new Uint8Array([i+1]), t.length + uintInfo.length);
t = await hmac.compute(prk, concat, hash);
okm.set(t, len * i);
}
return okm.slice(0, length);
};