mirror of
https://github.com/nodejs/node.git
synced 2025-05-01 17:03:34 +00:00

WebCryptoAPI functions' arguments are now coersed and validated as per their WebIDL definitions like in other Web Crypto API implementations. This further improves interoperability with other implementations of Web Crypto API. PR-URL: https://github.com/nodejs/node/pull/46067 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
384 lines
10 KiB
JavaScript
384 lines
10 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayBufferPrototypeSlice,
|
|
MathCeil,
|
|
ObjectDefineProperty,
|
|
SafeSet,
|
|
} = primordials;
|
|
|
|
const { Buffer } = require('buffer');
|
|
|
|
const {
|
|
DiffieHellman: _DiffieHellman,
|
|
DiffieHellmanGroup: _DiffieHellmanGroup,
|
|
ECDH: _ECDH,
|
|
ECDHBitsJob,
|
|
ECDHConvertKey: _ECDHConvertKey,
|
|
statelessDH,
|
|
kCryptoJobAsync,
|
|
} = internalBinding('crypto');
|
|
|
|
const {
|
|
codes: {
|
|
ERR_CRYPTO_ECDH_INVALID_FORMAT,
|
|
ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY,
|
|
ERR_CRYPTO_INCOMPATIBLE_KEY,
|
|
ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE,
|
|
ERR_INVALID_ARG_TYPE,
|
|
ERR_INVALID_ARG_VALUE,
|
|
}
|
|
} = require('internal/errors');
|
|
|
|
const {
|
|
validateInt32,
|
|
validateObject,
|
|
validateString,
|
|
} = require('internal/validators');
|
|
|
|
const {
|
|
isArrayBufferView,
|
|
isAnyArrayBuffer,
|
|
} = require('internal/util/types');
|
|
|
|
const {
|
|
lazyDOMException,
|
|
} = require('internal/util');
|
|
|
|
const {
|
|
KeyObject,
|
|
} = require('internal/crypto/keys');
|
|
|
|
const {
|
|
getArrayBufferOrView,
|
|
getDefaultEncoding,
|
|
jobPromise,
|
|
toBuf,
|
|
kHandle,
|
|
kKeyObject,
|
|
} = 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');
|
|
|
|
if (keyEncoding && !Buffer.isEncoding(keyEncoding) &&
|
|
keyEncoding !== 'buffer') {
|
|
genEncoding = generator;
|
|
generator = keyEncoding;
|
|
keyEncoding = false;
|
|
}
|
|
|
|
const encoding = getDefaultEncoding();
|
|
keyEncoding = keyEncoding || encoding;
|
|
genEncoding = genEncoding || encoding;
|
|
|
|
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();
|
|
encoding = encoding || getDefaultEncoding();
|
|
return encode(keys, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.computeSecret =
|
|
DiffieHellman.prototype.computeSecret =
|
|
dhComputeSecret;
|
|
|
|
function dhComputeSecret(key, inEnc, outEnc) {
|
|
const encoding = getDefaultEncoding();
|
|
inEnc = inEnc || encoding;
|
|
outEnc = outEnc || encoding;
|
|
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();
|
|
encoding = encoding || getDefaultEncoding();
|
|
return encode(prime, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.getGenerator =
|
|
DiffieHellman.prototype.getGenerator =
|
|
dhGetGenerator;
|
|
|
|
function dhGetGenerator(encoding) {
|
|
const generator = this[kHandle].getGenerator();
|
|
encoding = encoding || getDefaultEncoding();
|
|
return encode(generator, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.getPublicKey =
|
|
DiffieHellman.prototype.getPublicKey =
|
|
dhGetPublicKey;
|
|
|
|
function dhGetPublicKey(encoding) {
|
|
const key = this[kHandle].getPublicKey();
|
|
encoding = encoding || getDefaultEncoding();
|
|
return encode(key, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellmanGroup.prototype.getPrivateKey =
|
|
DiffieHellman.prototype.getPrivateKey =
|
|
dhGetPrivateKey;
|
|
|
|
function dhGetPrivateKey(encoding) {
|
|
const key = this[kHandle].getPrivateKey();
|
|
encoding = encoding || getDefaultEncoding();
|
|
return encode(key, encoding);
|
|
}
|
|
|
|
|
|
DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) {
|
|
encoding = encoding || getDefaultEncoding();
|
|
key = getArrayBufferOrView(key, 'key', encoding);
|
|
this[kHandle].setPublicKey(key);
|
|
return this;
|
|
};
|
|
|
|
|
|
DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) {
|
|
encoding = encoding || getDefaultEncoding();
|
|
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 = DiffieHellman.prototype.setPublicKey;
|
|
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);
|
|
encoding = encoding || getDefaultEncoding();
|
|
return encode(key, encoding);
|
|
};
|
|
|
|
ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) {
|
|
validateString(curve, 'curve');
|
|
const encoding = inEnc || getDefaultEncoding();
|
|
key = getArrayBufferOrView(key, 'key', encoding);
|
|
outEnc = outEnc || encoding;
|
|
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;
|
|
}
|
|
|
|
const dhEnabledKeyTypes = new SafeSet(['dh', 'ec', 'x448', 'x25519']);
|
|
|
|
function diffieHellman(options) {
|
|
validateObject(options, 'options');
|
|
|
|
const { privateKey, publicKey } = options;
|
|
if (!(privateKey instanceof KeyObject))
|
|
throw new ERR_INVALID_ARG_VALUE('options.privateKey', privateKey);
|
|
|
|
if (!(publicKey instanceof KeyObject))
|
|
throw new ERR_INVALID_ARG_VALUE('options.publicKey', publicKey);
|
|
|
|
if (privateKey.type !== 'private')
|
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private');
|
|
|
|
if (publicKey.type !== 'public' && publicKey.type !== 'private') {
|
|
throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type,
|
|
'private or public');
|
|
}
|
|
|
|
const privateType = privateKey.asymmetricKeyType;
|
|
const publicType = publicKey.asymmetricKeyType;
|
|
if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) {
|
|
throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman',
|
|
`${privateType} and ${publicType}`);
|
|
}
|
|
|
|
return statelessDH(privateKey[kHandle], publicKey[kHandle]);
|
|
}
|
|
|
|
// The ecdhDeriveBits function is part of the Web Crypto API and serves both
|
|
// deriveKeys and deriveBits functions.
|
|
async function ecdhDeriveBits(algorithm, baseKey, length) {
|
|
const { 'public': key } = algorithm;
|
|
|
|
if (key.type !== 'public') {
|
|
throw lazyDOMException(
|
|
'algorithm.public must be a public key', 'InvalidAccessError');
|
|
}
|
|
if (baseKey.type !== 'private') {
|
|
throw lazyDOMException(
|
|
'baseKey must be a private key', 'InvalidAccessError');
|
|
}
|
|
|
|
if (
|
|
key.algorithm.name !== 'ECDH' &&
|
|
key.algorithm.name !== 'X25519' &&
|
|
key.algorithm.name !== 'X448'
|
|
) {
|
|
throw lazyDOMException('Keys must be ECDH, X25519, or X448 keys', 'InvalidAccessError');
|
|
}
|
|
|
|
if (key.algorithm.name !== baseKey.algorithm.name) {
|
|
throw lazyDOMException(
|
|
'The public and private keys must be of the same type',
|
|
'InvalidAccessError');
|
|
}
|
|
|
|
if (
|
|
key.algorithm.name === 'ECDH' &&
|
|
key.algorithm.namedCurve !== baseKey.algorithm.namedCurve
|
|
) {
|
|
throw lazyDOMException('Named curve mismatch', 'InvalidAccessError');
|
|
}
|
|
|
|
const bits = await jobPromise(() => new ECDHBitsJob(
|
|
kCryptoJobAsync,
|
|
key.algorithm.name === 'ECDH' ? baseKey.algorithm.namedCurve : baseKey.algorithm.name,
|
|
key[kKeyObject][kHandle],
|
|
baseKey[kKeyObject][kHandle]));
|
|
|
|
// If a length is not specified, return the full derived secret
|
|
if (length === null)
|
|
return bits;
|
|
|
|
// If the length is not a multiple of 8 the nearest ceiled
|
|
// multiple of 8 is sliced.
|
|
length = MathCeil(length / 8);
|
|
const { byteLength } = bits;
|
|
|
|
// If the length is larger than the derived secret, throw.
|
|
// Otherwise, we either return the secret or a truncated
|
|
// slice.
|
|
if (byteLength < length)
|
|
throw lazyDOMException('derived bit length is too small', 'OperationError');
|
|
|
|
return length === byteLength ?
|
|
bits :
|
|
ArrayBufferPrototypeSlice(bits, 0, length);
|
|
}
|
|
|
|
module.exports = {
|
|
DiffieHellman,
|
|
DiffieHellmanGroup,
|
|
ECDH,
|
|
diffieHellman,
|
|
ecdhDeriveBits,
|
|
};
|