crypto: Implement HKDF for Node.js < 15.0.0

This commit is contained in:
Richard Hansen 2022-01-26 01:25:17 -05:00 committed by SamTV12345
parent 675c0130b9
commit df7938f3c9
2 changed files with 84 additions and 0 deletions

43
src/node/utils/crypto.js Normal file
View file

@ -0,0 +1,43 @@
'use strict';
const {Buffer} = require('buffer');
const crypto = require('crypto');
const util = require('util');
// TODO: Delete this once support for Node.js < 15.0.0 is dropped.
const hkdfFallback = async (digest, ikm, salt, info, keylen) => {
// https://datatracker.ietf.org/doc/html/rfc5869#section-2.2
const prkHmac = crypto.createHmac(digest, salt);
prkHmac.update(ikm);
const prk = prkHmac.digest();
// https://datatracker.ietf.org/doc/html/rfc5869#section-2.3
let len = 0;
const t = [Buffer.alloc(0)];
while (len < keylen) {
const hmac = crypto.createHmac(digest, prk);
hmac.update(t[t.length - 1]);
hmac.update(info);
hmac.update(Buffer.from([t.length % 256]));
const tn = hmac.digest();
t.push(tn);
len += tn.length;
}
const buf = Buffer.concat(t);
return (buf.byteOffset === 0 && buf.buffer.byteLength === keylen
? buf : Uint8Array.prototype.slice.call(buf, 0, keylen)).buffer;
};
/**
* Promisified version of Node.js's crypto.hkdf.
*/
exports.hkdf = crypto.hkdf ? util.promisify(crypto.hkdf) : hkdfFallback;
/**
* Promisified version of Node.js's crypto.randomBytes
*/
exports.randomBytes = util.promisify(crypto.randomBytes);
exports.exportedForTesting = {
hkdfFallback,
};

View file

@ -0,0 +1,41 @@
'use strict';
const assert = require('assert').strict;
const {Buffer} = require('buffer');
const crypto = require('../../../node/utils/crypto');
const nodeCrypto = require('crypto');
const util = require('util');
const nodeHkdf = nodeCrypto.hkdf ? util.promisify(nodeCrypto.hkdf) : null;
const ab2hex = (ab) => Buffer.from(ab).toString('hex');
describe(__filename, function () {
describe('hkdf fallback', function () {
before(async function () {
if (!nodeHkdf) this.skip();
});
const testCases = [
['minimal', 'sha256', 1, 0, 0, 1],
['huge', 'sha512', 1024, 1024, 1024, 16320],
];
for (const [desc, digest, ikmLen, saltLen, infoLen, keyLen] of testCases) {
for (const strings of [false, true]) {
it(`${desc} (${strings ? 'strings' : 'buffers'})`, async function () {
let isi = await Promise.all([
crypto.randomBytes(ikmLen),
crypto.randomBytes(saltLen),
crypto.randomBytes(infoLen),
]);
if (strings) isi = isi.map((b) => b.toString('hex').slice(0, b.length));
const args = [digest, ...isi, keyLen];
assert.equal(
ab2hex(await crypto.exportedForTesting.hkdfFallback(...args)),
ab2hex(await nodeHkdf(...args)));
});
}
}
});
});