Remove internal, unused RandomBuffer (#1593)

The changes do not affect the public API:
`RandomBuffer` was used internally for secure randomness generation before
`crypto.getRandomValues` was made available to WebWorkers, requiring
generating randomness in the main thread.
As a result of the change, the internal `getRandomBytes()` and some functions
that use it are no longer async.
This commit is contained in:
larabr 2023-02-09 23:11:53 +01:00 committed by GitHub
parent 9175b76887
commit 126ab53840
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 43 additions and 198 deletions

View File

@ -367,8 +367,7 @@ export async function getPrefixRandom(algo) {
* Generating a session key for the specified symmetric algorithm
* See {@link https://tools.ietf.org/html/rfc4880#section-9.2|RFC 4880 9.2} for algorithms.
* @param {module:enums.symmetric} algo - Symmetric encryption algorithm
* @returns {Promise<Uint8Array>} Random bytes as a string to be used as a key.
* @async
* @returns {Uint8Array} Random bytes as a string to be used as a key.
*/
export function generateSessionKey(algo) {
const { keySize } = getCipher(algo);

View File

@ -50,14 +50,13 @@ hash_headers[11] = [0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01,
* Create padding with secure random data
* @private
* @param {Integer} length - Length of the padding in bytes
* @returns {Promise<Uint8Array>} Random padding.
* @async
* @returns {Uint8Array} Random padding.
*/
async function getPKCS1Padding(length) {
function getPKCS1Padding(length) {
const result = new Uint8Array(length);
let count = 0;
while (count < length) {
const randomBytes = await getRandomBytes(length - count);
const randomBytes = getRandomBytes(length - count);
for (let i = 0; i < randomBytes.length; i++) {
if (randomBytes[i] !== 0) {
result[count++] = randomBytes[i];
@ -72,10 +71,9 @@ async function getPKCS1Padding(length) {
* @see {@link https://tools.ietf.org/html/rfc4880#section-13.1.1|RFC 4880 13.1.1}
* @param {Uint8Array} message - Message to be encoded
* @param {Integer} keyLength - The length in octets of the key modulus
* @returns {Promise<Uint8Array>} EME-PKCS1 padded message.
* @async
* @returns {Uint8Array} EME-PKCS1 padded message.
*/
export async function emeEncode(message, keyLength) {
export function emeEncode(message, keyLength) {
const mLength = message.length;
// length checking
if (mLength > keyLength - 11) {
@ -83,7 +81,7 @@ export async function emeEncode(message, keyLength) {
}
// Generate an octet string PS of length k - mLen - 3 consisting of
// pseudo-randomly generated nonzero octets
const PS = await getPKCS1Padding(keyLength - mLength - 3);
const PS = getPKCS1Padding(keyLength - mLength - 3);
// Concatenate PS, the message M, and other padding to form an
// encoded message EM of length k octets as EM = 0x00 || 0x02 || PS || 0x00 || M.
const encoded = new Uint8Array(keyLength);

View File

@ -41,7 +41,7 @@ export async function encrypt(data, p, g, y) {
g = new BigInteger(g);
y = new BigInteger(y);
const padded = await emeEncode(data, p.byteLength());
const padded = emeEncode(data, p.byteLength());
const m = new BigInteger(padded);
// OpenPGP uses a "special" version of ElGamal where g is generator of the full group Z/pZ*

View File

@ -182,7 +182,7 @@ class Curve {
case 'node':
return nodeGenKeyPair(this.name);
case 'curve25519': {
const privateKey = await getRandomBytes(32);
const privateKey = getRandomBytes(32);
privateKey[0] = (privateKey[0] & 127) | 64;
privateKey[31] &= 248;
const secretKey = privateKey.slice().reverse();
@ -191,7 +191,7 @@ class Curve {
return { publicKey, privateKey };
}
case 'ed25519': {
const privateKey = await getRandomBytes(32);
const privateKey = getRandomBytes(32);
const keyPair = nacl.sign.keyPair.fromSeed(privateKey);
const publicKey = util.concatUint8Array([new Uint8Array([0x40]), keyPair.publicKey]);
return { publicKey, privateKey };
@ -199,7 +199,7 @@ class Curve {
}
const indutnyCurve = await getIndutnyCurve(this.name);
keyPair = await indutnyCurve.genKeyPair({
entropy: util.uint8ArrayToString(await getRandomBytes(32))
entropy: util.uint8ArrayToString(getRandomBytes(32))
});
return { publicKey: new Uint8Array(keyPair.getPublic('array', false)), privateKey: keyPair.getPrivate().toArrayLike(Uint8Array) };
}

View File

@ -94,7 +94,7 @@ async function kdf(hashAlgo, X, length, param, stripLeading = false, stripTraili
async function genPublicEphemeralKey(curve, Q) {
switch (curve.type) {
case 'curve25519': {
const d = await getRandomBytes(32);
const d = getRandomBytes(32);
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d);
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]);

View File

@ -136,7 +136,7 @@ export async function validateParams(oid, Q, d) {
switch (curve.type) {
case 'web':
case 'node': {
const message = await getRandomBytes(8);
const message = getRandomBytes(8);
const hashAlgo = enums.hash.sha256;
const hashed = await hash.digest(hashAlgo, message);
try {

View File

@ -433,7 +433,7 @@ async function nodeEncrypt(data, n, e) {
async function bnEncrypt(data, n, e) {
const BigInteger = await util.getBigInteger();
n = new BigInteger(n);
data = new BigInteger(await emeEncode(data, n.byteLength()));
data = new BigInteger(emeEncode(data, n.byteLength()));
e = new BigInteger(e);
if (data.gte(n)) {
throw new Error('Message size cannot exceed modulus size');

View File

@ -26,88 +26,18 @@ import util from '../util';
const nodeCrypto = util.getNodeCrypto();
/**
* Buffer for secure random numbers
*/
class RandomBuffer {
constructor() {
this.buffer = null;
this.size = null;
this.callback = null;
}
/**
* Initialize buffer
* @param {Integer} size - size of buffer
*/
init(size, callback) {
this.buffer = new Uint8Array(size);
this.size = 0;
this.callback = callback;
}
/**
* Concat array of secure random numbers to buffer
* @param {Uint8Array} buf
*/
set(buf) {
if (!this.buffer) {
throw new Error('RandomBuffer is not initialized');
}
if (!(buf instanceof Uint8Array)) {
throw new Error('Invalid type: buf not an Uint8Array');
}
const freeSpace = this.buffer.length - this.size;
if (buf.length > freeSpace) {
buf = buf.subarray(0, freeSpace);
}
// set buf with offset old size of buffer
this.buffer.set(buf, this.size);
this.size += buf.length;
}
/**
* Take numbers out of buffer and copy to array
* @param {Uint8Array} buf - The destination array
*/
async get(buf) {
if (!this.buffer) {
throw new Error('RandomBuffer is not initialized');
}
if (!(buf instanceof Uint8Array)) {
throw new Error('Invalid type: buf not an Uint8Array');
}
if (this.size < buf.length) {
if (!this.callback) {
throw new Error('Random number buffer depleted');
}
// Wait for random bytes from main context, then try again
await this.callback();
return this.get(buf);
}
for (let i = 0; i < buf.length; i++) {
buf[i] = this.buffer[--this.size];
// clear buffer value
this.buffer[this.size] = 0;
}
}
}
/**
* Retrieve secure random byte array of the specified length
* @param {Integer} length - Length in bytes to generate
* @returns {Promise<Uint8Array>} Random byte array.
* @async
* @returns {Uint8Array} Random byte array.
*/
export async function getRandomBytes(length) {
export function getRandomBytes(length) {
const buf = new Uint8Array(length);
if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
crypto.getRandomValues(buf);
} else if (nodeCrypto) {
const bytes = nodeCrypto.randomBytes(buf.length);
buf.set(bytes);
} else if (randomBuffer.buffer) {
await randomBuffer.get(buf);
} else {
throw new Error('No secure random number generator available.');
}
@ -137,5 +67,3 @@ export async function getRandomBigInteger(min, max) {
const r = new BigInteger(await getRandomBytes(bytes + 8));
return r.mod(modulus).add(min);
}
export const randomBuffer = new RandomBuffer();

View File

@ -241,7 +241,7 @@ export class Message {
pkeskPacketCopy.read(serialisedPKESK);
const randomSessionKey = {
sessionKeyAlgorithm,
sessionKey: await crypto.generateSessionKey(sessionKeyAlgorithm)
sessionKey: crypto.generateSessionKey(sessionKeyAlgorithm)
};
try {
await pkeskPacketCopy.decrypt(decryptionKeyPacket, randomSessionKey);
@ -345,7 +345,7 @@ export class Message {
enums.read(enums.aead, await getPreferredAlgo('aead', encryptionKeys, date, userIDs, config)) :
undefined;
const sessionKeyData = await crypto.generateSessionKey(algo);
const sessionKeyData = crypto.generateSessionKey(algo);
return { data: sessionKeyData, algorithm: algorithmName, aeadAlgorithm: aeadAlgorithmName };
}

View File

@ -119,7 +119,7 @@ class AEADEncryptedDataPacket {
this.cipherAlgorithm = sessionKeyAlgorithm;
const { ivLength } = crypto.getAEADMode(this.aeadAlgorithm);
this.iv = await crypto.random.getRandomBytes(ivLength); // generate new random IV
this.iv = crypto.random.getRandomBytes(ivLength); // generate new random IV
this.chunkSizeByte = config.aeadChunkSizeByte;
const data = this.packets.write();
this.encrypted = await this.crypt('encrypt', key, data);

View File

@ -283,13 +283,13 @@ class SecretKeyPacket extends PublicKeyPacket {
}
this.s2k = new S2K(config);
this.s2k.salt = await crypto.random.getRandomBytes(8);
this.s2k.salt = crypto.random.getRandomBytes(8);
const cleartext = crypto.serializeParams(this.algorithm, this.privateParams);
this.symmetric = enums.symmetric.aes256;
const key = await produceEncryptionKey(this.s2k, passphrase, this.symmetric);
const { blockSize } = crypto.getCipher(this.symmetric);
this.iv = await crypto.random.getRandomBytes(blockSize);
this.iv = crypto.random.getRandomBytes(blockSize);
if (config.aeadProtect) {
this.s2kUsage = 253;

View File

@ -179,18 +179,18 @@ class SymEncryptedSessionKeyPacket {
this.sessionKeyEncryptionAlgorithm = algo;
this.s2k = new S2K(config);
this.s2k.salt = await crypto.random.getRandomBytes(8);
this.s2k.salt = crypto.random.getRandomBytes(8);
const { blockSize, keySize } = crypto.getCipher(algo);
const encryptionKey = await this.s2k.produceKey(passphrase, keySize);
if (this.sessionKey === null) {
this.sessionKey = await crypto.generateSessionKey(this.sessionKeyAlgorithm);
this.sessionKey = crypto.generateSessionKey(this.sessionKeyAlgorithm);
}
if (this.version === 5) {
const mode = crypto.getAEADMode(this.aeadAlgorithm);
this.iv = await crypto.random.getRandomBytes(mode.ivLength); // generate new random IV
this.iv = crypto.random.getRandomBytes(mode.ivLength); // generate new random IV
const associatedData = new Uint8Array([0xC0 | SymEncryptedSessionKeyPacket.tag, this.version, this.sessionKeyEncryptionAlgorithm, this.aeadAlgorithm]);
const modeInstance = await mode(algo, encryptionKey);
this.encrypted = await modeInstance.encrypt(this.sessionKey, this.iv, associatedData);

View File

@ -242,7 +242,7 @@ module.exports = () => describe('API functional testing', function() {
await Promise.all(symmAlgoNames.map(async function(algoName) {
const algo = openpgp.enums.write(openpgp.enums.symmetric, algoName);
const { blockSize } = crypto.getCipher(algo);
const symmKey = await crypto.generateSessionKey(algo);
const symmKey = crypto.generateSessionKey(algo);
const IV = new Uint8Array(blockSize);
const symmencData = await crypto.mode.cfb.encrypt(algo, symmKey, util.stringToUint8Array(plaintext), IV, config);
const text = util.uint8ArrayToString(await crypto.mode.cfb.decrypt(algo, symmKey, symmencData, new Uint8Array(blockSize)));
@ -269,8 +269,8 @@ module.exports = () => describe('API functional testing', function() {
});
it('Asymmetric using RSA with eme_pkcs1 padding', async function () {
const symmKey = await crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
it('Asymmetric using RSA with eme_pkcs1 padding', function () {
const symmKey = crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
return crypto.publicKeyEncrypt(algoRSA, RSAPublicParams, symmKey).then(RSAEncryptedData => {
return crypto.publicKeyDecrypt(
algoRSA, RSAPublicParams, RSAPrivateParams, RSAEncryptedData
@ -280,8 +280,8 @@ module.exports = () => describe('API functional testing', function() {
});
});
it('Asymmetric using Elgamal with eme_pkcs1 padding', async function () {
const symmKey = await crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
it('Asymmetric using Elgamal with eme_pkcs1 padding', function () {
const symmKey = crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
return crypto.publicKeyEncrypt(algoElGamal, elGamalPublicParams, symmKey).then(ElgamalEncryptedData => {
return crypto.publicKeyDecrypt(
algoElGamal, elGamalPublicParams, elGamalPrivateParams, ElgamalEncryptedData

View File

@ -45,8 +45,8 @@ module.exports = () => describe('Symmetric AES-GCM (experimental)', function() {
this.skip(); // eslint-disable-line no-invalid-this
}
const algo = openpgp.enums.write(openpgp.enums.symmetric, algoName);
const key = await crypto.generateSessionKey(algo);
const iv = await crypto.random.getRandomBytes(crypto.mode.gcm.ivLength);
const key = crypto.generateSessionKey(algo);
const iv = crypto.random.getRandomBytes(crypto.mode.gcm.ivLength);
const nativeEncryptSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'encrypt') : sinonSandbox.spy(nodeCrypto, 'createCipheriv');
const nativeDecryptSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'decrypt') : sinonSandbox.spy(nodeCrypto, 'createDecipheriv');

View File

@ -1,7 +1,6 @@
module.exports = () => describe('Crypto', function () {
require('./cipher')();
require('./hash')();
require('./random.js')();
require('./crypto.js')();
require('./elliptic.js')();
require('./ecdh.js')();

View File

@ -1,79 +0,0 @@
const random = require('../../src/crypto/random');
const chai = require('chai');
const { expect } = chai;
module.exports = () => describe('Random Buffer', function() {
let randomBuffer;
before(function() {
randomBuffer = new random.randomBuffer.constructor();
expect(randomBuffer).to.exist;
});
it('Throw error if not initialized', async function () {
expect(randomBuffer.set.bind(randomBuffer)).to.throw('RandomBuffer is not initialized');
await expect(randomBuffer.get(new Uint8Array(1))).to.eventually.be.rejectedWith('RandomBuffer is not initialized');
});
it('Initialization', function () {
randomBuffer.init(5);
expect(randomBuffer.buffer).to.exist;
expect(randomBuffer.buffer).to.have.length(5);
expect(randomBuffer.size).to.equal(0);
});
function equal(buf, arr) {
for (let i = 0; i < buf.length; i++) {
if (buf[i] !== arr[i]) {
return false;
}
}
return true;
}
it('Set Method', function () {
randomBuffer.init(5);
let buf = new Uint32Array(2);
expect(randomBuffer.set.bind(randomBuffer, buf)).to.throw('Invalid type: buf not an Uint8Array');
buf = new Uint8Array(2);
buf[0] = 1; buf[1] = 2;
randomBuffer.set(buf);
expect(equal(randomBuffer.buffer, [1,2,0,0,0])).to.be.true;
expect(randomBuffer.size).to.equal(2);
randomBuffer.set(buf);
expect(equal(randomBuffer.buffer, [1,2,1,2,0])).to.be.true;
expect(randomBuffer.size).to.equal(4);
randomBuffer.set(buf);
expect(equal(randomBuffer.buffer, [1,2,1,2,1])).to.be.true;
expect(randomBuffer.size).to.equal(5);
randomBuffer.init(1);
buf = new Uint8Array(2);
buf[0] = 1; buf[1] = 2;
randomBuffer.set(buf);
expect(buf).to.to.have.property('0', 1);
expect(randomBuffer.size).to.equal(1);
});
it('Get Method', async function () {
randomBuffer.init(5);
let buf = new Uint8Array(5);
buf[0] = 1; buf[1] = 2; buf[2] = 5; buf[3] = 7; buf[4] = 8;
randomBuffer.set(buf);
buf = new Uint32Array(2);
await expect(randomBuffer.get(buf)).to.eventually.be.rejectedWith('Invalid type: buf not an Uint8Array');
buf = new Uint8Array(2);
randomBuffer.get(buf);
expect(equal(randomBuffer.buffer, [1,2,5,0,0])).to.be.true;
expect(randomBuffer.size).to.equal(3);
expect(buf).to.to.have.property('0', 8);
expect(buf).to.to.have.property('1', 7);
randomBuffer.get(buf);
expect(buf).to.to.have.property('0', 5);
expect(buf).to.to.have.property('1', 2);
expect(equal(randomBuffer.buffer, [1,0,0,0,0])).to.be.true;
expect(randomBuffer.size).to.equal(1);
await expect(randomBuffer.get(buf)).to.eventually.be.rejectedWith('Random number buffer depleted');
});
});

View File

@ -65,7 +65,7 @@ module.exports = () => describe('basic RSA cryptography', function () {
it('sign and verify using generated key params', async function() {
const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const message = await random.getRandomBytes(64);
const message = random.getRandomBytes(64);
const hashAlgo = openpgp.enums.write(openpgp.enums.hash, 'sha256');
const hashed = await crypto.hash.digest(hashAlgo, message);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
@ -79,7 +79,7 @@ module.exports = () => describe('basic RSA cryptography', function () {
const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = await crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
const message = crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
const encrypted = await crypto.publicKey.rsa.encrypt(message, n, e);
const decrypted = await crypto.publicKey.rsa.decrypt(encrypted, n, e, d, p, q, u);
expect(decrypted).to.deep.equal(message);
@ -92,7 +92,7 @@ module.exports = () => describe('basic RSA cryptography', function () {
const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = await crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
const message = crypto.generateSessionKey(openpgp.enums.symmetric.aes256);
disableNative();
const encryptedBn = await crypto.publicKey.rsa.encrypt(message, n, e);
enableNative();
@ -108,7 +108,7 @@ module.exports = () => describe('basic RSA cryptography', function () {
const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = await random.getRandomBytes(64);
const message = random.getRandomBytes(64);
const hashName = 'sha256';
const hashAlgo = openpgp.enums.write(openpgp.enums.hash, hashName);
const hashed = await crypto.hash.digest(hashAlgo, message);
@ -123,7 +123,7 @@ module.exports = () => describe('basic RSA cryptography', function () {
const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = await random.getRandomBytes(64);
const message = random.getRandomBytes(64);
const hashName = 'sha256';
const hashAlgo = openpgp.enums.write(openpgp.enums.hash, hashName);
const hashed = await crypto.hash.digest(hashAlgo, message);

View File

@ -15,7 +15,7 @@ async function getRandomBN(min, max) {
const modulus = max.sub(min);
const bytes = modulus.byteLength();
const r = new BN(await random.getRandomBytes(bytes + 8));
const r = new BN(random.getRandomBytes(bytes + 8));
return r.mod(modulus).add(min);
}

View File

@ -2419,7 +2419,7 @@ aOU=
it('should encrypt using custom session key and decrypt using session key', async function () {
const sessionKey = {
data: await crypto.generateSessionKey(openpgp.enums.symmetric.aes256),
data: crypto.generateSessionKey(openpgp.enums.symmetric.aes256),
algorithm: 'aes256'
};
const encOpt = {
@ -2442,7 +2442,7 @@ aOU=
it('should encrypt using custom session key and decrypt using private key', async function () {
const sessionKey = {
data: await crypto.generateSessionKey(openpgp.enums.symmetric.aes128),
data: crypto.generateSessionKey(openpgp.enums.symmetric.aes128),
algorithm: 'aes128'
};
const encOpt = {
@ -3140,9 +3140,9 @@ aOU=
await stream.loadStreamsPonyfill();
const ReadableStream = useNativeStream ? global.ReadableStream : stream.ReadableStream;
const data = new ReadableStream({
async pull(controller) {
pull(controller) {
if (i++ < 4) {
const randomBytes = await random.getRandomBytes(10);
const randomBytes = random.getRandomBytes(10);
controller.enqueue(randomBytes);
plaintext.push(randomBytes.slice());
} else {

View File

@ -977,7 +977,7 @@ module.exports = () => describe('Streaming', function() {
if (test === currentTest && i < (expectedType === 'web' ? 100 : 500)) {
i++;
if (i === 4) await dataArrivedPromise;
const randomBytes = await random.getRandomBytes(1024);
const randomBytes = random.getRandomBytes(1024);
controller.enqueue(randomBytes);
plaintext.push(randomBytes);
} else {
@ -997,7 +997,7 @@ module.exports = () => describe('Streaming', function() {
if (test === currentTest && i < (expectedType === 'web' ? 100 : 500)) {
i++;
if (i === 4) await dataArrivedPromise;
const randomBytes = await random.getRandomBytes(1024);
const randomBytes = random.getRandomBytes(1024);
plaintext.push(randomBytes);
if (!this.push(randomBytes)) break;
} else {