mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-08 16:05:05 -04:00
crypto: Implement HKDF for Node.js < 15.0.0
This commit is contained in:
parent
675c0130b9
commit
df7938f3c9
2 changed files with 84 additions and 0 deletions
43
src/node/utils/crypto.js
Normal file
43
src/node/utils/crypto.js
Normal 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,
|
||||||
|
};
|
41
src/tests/backend/specs/crypto.js
Normal file
41
src/tests/backend/specs/crypto.js
Normal 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)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue