'use strict'; const { ObjectDefineProperty, Set } = primordials; const { Buffer } = require('buffer'); const { 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_OPT_VALUE } = require('internal/errors').codes; const { validateString, validateInt32, } = require('internal/validators'); const { isArrayBufferView } = require('internal/util/types'); const { KeyObject } = require('internal/crypto/keys'); const { getDefaultEncoding, kHandle, toBuf } = require('internal/crypto/util'); const { DiffieHellman: _DiffieHellman, DiffieHellmanGroup: _DiffieHellmanGroup, ECDH: _ECDH, ECDHConvertKey: _ECDHConvertKey, statelessDH } = internalBinding('crypto'); const { POINT_CONVERSION_COMPRESSED, POINT_CONVERSION_HYBRID, POINT_CONVERSION_UNCOMPRESSED } = internalBinding('constants').crypto; 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)) { throw new ERR_INVALID_ARG_TYPE( 'sizeOrKey', ['number', 'string', '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 (generator !== true) { generator = toBuf(generator, genEncoding); } else { throw new ERR_INVALID_ARG_TYPE( 'generator', ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], generator ); } this[kHandle] = new _DiffieHellman(sizeOrKey, generator); ObjectDefineProperty(this, 'verifyError', { 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', { 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; const ret = this[kHandle].computeSecret(toBuf(key, inEnc)); 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(); this[kHandle].setPublicKey(toBuf(key, encoding)); return this; }; DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) { encoding = encoding || getDefaultEncoding(); this[kHandle].setPrivateKey(toBuf(key, encoding)); 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) { if (typeof key !== 'string' && !isArrayBufferView(key)) { throw new ERR_INVALID_ARG_TYPE( 'key', ['string', 'Buffer', 'TypedArray', 'DataView'], key ); } validateString(curve, 'curve'); const encoding = getDefaultEncoding(); inEnc = inEnc || encoding; outEnc = outEnc || encoding; const f = getFormat(format); const convertedKey = _ECDHConvertKey(toBuf(key, inEnc), 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 Set(['dh', 'ec', 'x448', 'x25519']); function diffieHellman(options) { if (typeof options !== 'object') throw new ERR_INVALID_ARG_TYPE('options', 'object', options); const { privateKey, publicKey } = options; if (!(privateKey instanceof KeyObject)) throw new ERR_INVALID_OPT_VALUE('privateKey', privateKey); if (!(publicKey instanceof KeyObject)) throw new ERR_INVALID_OPT_VALUE('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]); } module.exports = { DiffieHellman, DiffieHellmanGroup, ECDH, diffieHellman };