Add shared bit-length helpers for WebCrypto operations that accept bit sequences whose length is not byte-aligned. Use the helpers for cSHAKE output, ECDH-derived bits, HMAC/KMAC key generation/import/derivation, and KMAC sign/verify output. Preserve the requested bit length in CryptoKey algorithm metadata while storing and exporting rounded-up byte material with unused low bits cleared. Keep byte-multiple validation for algorithms whose specs require it. Extend the lower-end of KMAC's key length support. Enable cSHAKE customization and functionName parameters. Signed-off-by: Filip Skokan <panva.ip@gmail.com> PR-URL: https://github.com/nodejs/node/pull/63988 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
397 lines
9.9 KiB
JavaScript
397 lines
9.9 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayBufferPrototypeSlice,
|
|
FunctionPrototypeCall,
|
|
ObjectDefineProperty,
|
|
TypedArrayPrototypeGetBuffer,
|
|
} = primordials;
|
|
|
|
const { Buffer } = require('buffer');
|
|
|
|
const {
|
|
DHBitsJob,
|
|
DiffieHellman: _DiffieHellman,
|
|
DiffieHellmanGroup: _DiffieHellmanGroup,
|
|
ECDH: _ECDH,
|
|
ECDHConvertKey: _ECDHConvertKey,
|
|
kCryptoJobAsync,
|
|
kCryptoJobSync,
|
|
kCryptoJobWebCrypto,
|
|
} = internalBinding('crypto');
|
|
|
|
const {
|
|
codes: {
|
|
ERR_CRYPTO_ECDH_INVALID_FORMAT,
|
|
ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY,
|
|
ERR_INVALID_ARG_TYPE,
|
|
},
|
|
} = require('internal/errors');
|
|
|
|
const {
|
|
validateFunction,
|
|
validateInt32,
|
|
validateObject,
|
|
validateString,
|
|
} = require('internal/validators');
|
|
|
|
const {
|
|
isArrayBufferView,
|
|
isAnyArrayBuffer,
|
|
} = require('internal/util/types');
|
|
|
|
const {
|
|
deprecate,
|
|
lazyDOMException,
|
|
} = require('internal/util');
|
|
|
|
const {
|
|
getCryptoKeyAlgorithm,
|
|
getCryptoKeyHandle,
|
|
getCryptoKeyType,
|
|
preparePrivateKey,
|
|
preparePublicOrPrivateKey,
|
|
} = require('internal/crypto/keys');
|
|
|
|
const {
|
|
getArrayBufferOrView,
|
|
jobPromise,
|
|
jobPromiseThen,
|
|
numBitsToBytes,
|
|
toBuf,
|
|
truncateToBitLength,
|
|
kHandle,
|
|
} = require('internal/crypto/util');
|
|
|
|
const {
|
|
crypto: {
|
|
POINT_CONVERSION_COMPRESSED,
|
|
POINT_CONVERSION_HYBRID,
|
|
POINT_CONVERSION_UNCOMPRESSED,
|
|
},
|
|
} = internalBinding('constants');
|
|
|
|
const DH_GENERATOR = 2;
|
|
|
|
function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) {
|
|
if (!(this instanceof DiffieHellman))
|
|
return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding);
|
|
|
|
if (typeof sizeOrKey !== 'number' &&
|
|
typeof sizeOrKey !== 'string' &&
|
|
!isArrayBufferView(sizeOrKey) &&
|
|
!isAnyArrayBuffer(sizeOrKey)) {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'sizeOrKey',
|
|
['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
|
|
sizeOrKey,
|
|
);
|
|
}
|
|
|
|
// Sizes < 0 don't make sense but they _are_ accepted (and subsequently
|
|
// rejected with ERR_OSSL_BN_BITS_TOO_SMALL) by OpenSSL. The glue code
|
|
// in node_crypto.cc accepts values that are IsInt32() for that reason
|
|
// and that's why we do that here too.
|
|
if (typeof sizeOrKey === 'number') {
|
|
validateInt32(sizeOrKey, 'sizeOrKey');
|
|
// Coerce -0 to +0.
|
|
sizeOrKey += 0;
|
|
}
|
|
|
|
if (keyEncoding && !Buffer.isEncoding(keyEncoding) &&
|
|
keyEncoding !== 'buffer') {
|
|
genEncoding = generator;
|
|
generator = keyEncoding;
|
|
keyEncoding = false;
|
|
}
|
|
|
|
if (typeof sizeOrKey !== 'number')
|
|
sizeOrKey = toBuf(sizeOrKey, keyEncoding);
|
|
|
|
if (!generator) {
|
|
generator = DH_GENERATOR;
|
|
} else if (typeof generator === 'number') {
|
|
validateInt32(generator, 'generator');
|
|
} else if (typeof generator === 'string') {
|
|
generator = toBuf(generator, genEncoding);
|
|
} else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'generator',
|
|
['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'],
|
|
generator,
|
|
);
|
|
}
|
|
|
|
|
|
this[kHandle] = new _DiffieHellman(sizeOrKey, generator);
|
|
ObjectDefineProperty(this, 'verifyError', {
|
|
__proto__: null,
|
|
enumerable: true,
|
|
value: this[kHandle].verifyError,
|
|
writable: false,
|
|
});
|
|
}
|
|
|
|
|
|
function DiffieHellmanGroup(name) {
|
|
if (!(this instanceof DiffieHellmanGroup))
|
|
return new DiffieHellmanGroup(name);
|
|
this[kHandle] = new _DiffieHellmanGroup(name);
|
|
ObjectDefineProperty(this, 'verifyError', {
|
|
__proto__: null,
|
|
enumerable: true,
|
|
value: this[kHandle].verifyError,
|
|
writable: false,
|
|
});
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.generateKeys =
|
|
DiffieHellman.prototype.generateKeys =
|
|
dhGenerateKeys;
|
|
|
|
function dhGenerateKeys(encoding) {
|
|
const keys = this[kHandle].generateKeys();
|
|
return encode(keys, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.computeSecret =
|
|
DiffieHellman.prototype.computeSecret =
|
|
dhComputeSecret;
|
|
|
|
function dhComputeSecret(key, inEnc, outEnc) {
|
|
key = getArrayBufferOrView(key, 'key', inEnc);
|
|
const ret = this[kHandle].computeSecret(key);
|
|
if (typeof ret === 'string')
|
|
throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY();
|
|
return encode(ret, outEnc);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.getPrime =
|
|
DiffieHellman.prototype.getPrime =
|
|
dhGetPrime;
|
|
|
|
function dhGetPrime(encoding) {
|
|
const prime = this[kHandle].getPrime();
|
|
return encode(prime, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.getGenerator =
|
|
DiffieHellman.prototype.getGenerator =
|
|
dhGetGenerator;
|
|
|
|
function dhGetGenerator(encoding) {
|
|
const generator = this[kHandle].getGenerator();
|
|
return encode(generator, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.getPublicKey =
|
|
DiffieHellman.prototype.getPublicKey =
|
|
dhGetPublicKey;
|
|
|
|
function dhGetPublicKey(encoding) {
|
|
const key = this[kHandle].getPublicKey();
|
|
return encode(key, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.getPrivateKey =
|
|
DiffieHellman.prototype.getPrivateKey =
|
|
dhGetPrivateKey;
|
|
|
|
function dhGetPrivateKey(encoding) {
|
|
const key = this[kHandle].getPrivateKey();
|
|
return encode(key, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) {
|
|
key = getArrayBufferOrView(key, 'key', encoding);
|
|
this[kHandle].setPublicKey(key);
|
|
return this;
|
|
};
|
|
|
|
|
|
DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) {
|
|
key = getArrayBufferOrView(key, 'key', encoding);
|
|
this[kHandle].setPrivateKey(key);
|
|
return this;
|
|
};
|
|
|
|
|
|
function ECDH(curve) {
|
|
if (!(this instanceof ECDH))
|
|
return new ECDH(curve);
|
|
|
|
validateString(curve, 'curve');
|
|
this[kHandle] = new _ECDH(curve);
|
|
}
|
|
|
|
ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret;
|
|
ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey;
|
|
ECDH.prototype.setPublicKey = deprecate(DiffieHellman.prototype.setPublicKey,
|
|
'ecdh.setPublicKey() is deprecated.',
|
|
'DEP0031');
|
|
ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey;
|
|
|
|
ECDH.prototype.generateKeys = function generateKeys(encoding, format) {
|
|
this[kHandle].generateKeys();
|
|
|
|
return this.getPublicKey(encoding, format);
|
|
};
|
|
|
|
ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) {
|
|
const f = getFormat(format);
|
|
const key = this[kHandle].getPublicKey(f);
|
|
return encode(key, encoding);
|
|
};
|
|
|
|
ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
|
|
validateString(curve, 'curve');
|
|
key = getArrayBufferOrView(key, 'key', inEnc);
|
|
const f = getFormat(format);
|
|
const convertedKey = _ECDHConvertKey(key, curve, f);
|
|
return encode(convertedKey, outEnc);
|
|
};
|
|
|
|
function encode(buffer, encoding) {
|
|
if (encoding && encoding !== 'buffer')
|
|
buffer = buffer.toString(encoding);
|
|
return buffer;
|
|
}
|
|
|
|
function getFormat(format) {
|
|
if (format) {
|
|
if (format === 'compressed')
|
|
return POINT_CONVERSION_COMPRESSED;
|
|
if (format === 'hybrid')
|
|
return POINT_CONVERSION_HYBRID;
|
|
if (format !== 'uncompressed')
|
|
throw new ERR_CRYPTO_ECDH_INVALID_FORMAT(format);
|
|
}
|
|
return POINT_CONVERSION_UNCOMPRESSED;
|
|
}
|
|
|
|
function diffieHellman(options, callback) {
|
|
validateObject(options, 'options');
|
|
|
|
if (callback !== undefined)
|
|
validateFunction(callback, 'callback');
|
|
|
|
const { privateKey, publicKey } = options;
|
|
|
|
const {
|
|
data: pubData,
|
|
format: pubFormat,
|
|
type: pubType,
|
|
passphrase: pubPassphrase,
|
|
namedCurve: pubNamedCurve,
|
|
} = preparePublicOrPrivateKey(publicKey, 'options.publicKey');
|
|
|
|
const {
|
|
data: privData,
|
|
format: privFormat,
|
|
type: privType,
|
|
passphrase: privPassphrase,
|
|
namedCurve: privNamedCurve,
|
|
} = preparePrivateKey(privateKey, 'options.privateKey');
|
|
|
|
const job = new DHBitsJob(
|
|
callback ? kCryptoJobAsync : kCryptoJobSync,
|
|
pubData,
|
|
pubFormat,
|
|
pubType,
|
|
pubPassphrase,
|
|
pubNamedCurve,
|
|
privData,
|
|
privFormat,
|
|
privType,
|
|
privPassphrase,
|
|
privNamedCurve);
|
|
|
|
if (!callback) {
|
|
const { 0: err, 1: secret } = job.run();
|
|
if (err !== undefined)
|
|
throw err;
|
|
|
|
return Buffer.from(secret);
|
|
}
|
|
|
|
job.ondone = (error, secret) => {
|
|
if (error) return FunctionPrototypeCall(callback, job, error);
|
|
FunctionPrototypeCall(callback, job, null, Buffer.from(secret));
|
|
};
|
|
job.run();
|
|
}
|
|
|
|
// The ecdhDeriveBits function is part of the Web Crypto API and serves both
|
|
// deriveKeys and deriveBits functions.
|
|
function ecdhDeriveBits(algorithm, baseKey, length) {
|
|
const { 'public': key } = algorithm;
|
|
|
|
if (getCryptoKeyType(baseKey) !== 'private') {
|
|
throw lazyDOMException(
|
|
'baseKey must be a private key', 'InvalidAccessError');
|
|
}
|
|
|
|
const keyAlgorithm = getCryptoKeyAlgorithm(key);
|
|
const baseKeyAlgorithm = getCryptoKeyAlgorithm(baseKey);
|
|
if (keyAlgorithm.name !== baseKeyAlgorithm.name) {
|
|
throw lazyDOMException(
|
|
'The public and private keys must be of the same type',
|
|
'InvalidAccessError');
|
|
}
|
|
|
|
if (
|
|
keyAlgorithm.name === 'ECDH' &&
|
|
keyAlgorithm.namedCurve !== baseKeyAlgorithm.namedCurve
|
|
) {
|
|
throw lazyDOMException('Named curve mismatch', 'InvalidAccessError');
|
|
}
|
|
|
|
const bits = jobPromise(() => new DHBitsJob(
|
|
kCryptoJobWebCrypto,
|
|
getCryptoKeyHandle(key),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
getCryptoKeyHandle(baseKey),
|
|
undefined,
|
|
undefined,
|
|
undefined,
|
|
undefined));
|
|
|
|
// If a length is not specified, return the full derived secret
|
|
if (length === null)
|
|
return bits;
|
|
|
|
return jobPromiseThen(bits, (bits) => {
|
|
const sliceLength = numBitsToBytes(length);
|
|
|
|
const { byteLength } = bits;
|
|
// If the length is larger than the derived secret, throw.
|
|
if (byteLength < sliceLength)
|
|
throw lazyDOMException('derived bit length is too small', 'OperationError');
|
|
|
|
if (length % 8 === 0) {
|
|
if (byteLength === sliceLength)
|
|
return bits;
|
|
return ArrayBufferPrototypeSlice(bits, 0, sliceLength);
|
|
}
|
|
|
|
return TypedArrayPrototypeGetBuffer(truncateToBitLength(length, bits));
|
|
});
|
|
}
|
|
|
|
module.exports = {
|
|
DiffieHellman,
|
|
DiffieHellmanGroup,
|
|
ECDH,
|
|
diffieHellman,
|
|
ecdhDeriveBits,
|
|
};
|