1'use strict'; 2 3const { 4 ArrayBufferPrototypeSlice, 5 MathCeil, 6 ObjectDefineProperty, 7 SafeSet, 8} = primordials; 9 10const { Buffer } = require('buffer'); 11 12const { 13 DiffieHellman: _DiffieHellman, 14 DiffieHellmanGroup: _DiffieHellmanGroup, 15 ECDH: _ECDH, 16 ECDHBitsJob, 17 ECDHConvertKey: _ECDHConvertKey, 18 statelessDH, 19 kCryptoJobAsync, 20} = internalBinding('crypto'); 21 22const { 23 codes: { 24 ERR_CRYPTO_ECDH_INVALID_FORMAT, 25 ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY, 26 ERR_CRYPTO_INCOMPATIBLE_KEY, 27 ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE, 28 ERR_INVALID_ARG_TYPE, 29 ERR_INVALID_ARG_VALUE, 30 }, 31} = require('internal/errors'); 32 33const { 34 validateInt32, 35 validateObject, 36 validateString, 37} = require('internal/validators'); 38 39const { 40 isArrayBufferView, 41 isAnyArrayBuffer, 42} = require('internal/util/types'); 43 44const { 45 lazyDOMException, 46} = require('internal/util'); 47 48const { 49 KeyObject, 50} = require('internal/crypto/keys'); 51 52const { 53 getArrayBufferOrView, 54 getDefaultEncoding, 55 jobPromise, 56 toBuf, 57 kHandle, 58 kKeyObject, 59} = require('internal/crypto/util'); 60 61const { 62 crypto: { 63 POINT_CONVERSION_COMPRESSED, 64 POINT_CONVERSION_HYBRID, 65 POINT_CONVERSION_UNCOMPRESSED, 66 }, 67} = internalBinding('constants'); 68 69const DH_GENERATOR = 2; 70 71function DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding) { 72 if (!(this instanceof DiffieHellman)) 73 return new DiffieHellman(sizeOrKey, keyEncoding, generator, genEncoding); 74 75 if (typeof sizeOrKey !== 'number' && 76 typeof sizeOrKey !== 'string' && 77 !isArrayBufferView(sizeOrKey) && 78 !isAnyArrayBuffer(sizeOrKey)) { 79 throw new ERR_INVALID_ARG_TYPE( 80 'sizeOrKey', 81 ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], 82 sizeOrKey, 83 ); 84 } 85 86 // Sizes < 0 don't make sense but they _are_ accepted (and subsequently 87 // rejected with ERR_OSSL_BN_BITS_TOO_SMALL) by OpenSSL. The glue code 88 // in node_crypto.cc accepts values that are IsInt32() for that reason 89 // and that's why we do that here too. 90 if (typeof sizeOrKey === 'number') 91 validateInt32(sizeOrKey, 'sizeOrKey'); 92 93 if (keyEncoding && !Buffer.isEncoding(keyEncoding) && 94 keyEncoding !== 'buffer') { 95 genEncoding = generator; 96 generator = keyEncoding; 97 keyEncoding = false; 98 } 99 100 const encoding = getDefaultEncoding(); 101 keyEncoding = keyEncoding || encoding; 102 genEncoding = genEncoding || encoding; 103 104 if (typeof sizeOrKey !== 'number') 105 sizeOrKey = toBuf(sizeOrKey, keyEncoding); 106 107 if (!generator) { 108 generator = DH_GENERATOR; 109 } else if (typeof generator === 'number') { 110 validateInt32(generator, 'generator'); 111 } else if (typeof generator === 'string') { 112 generator = toBuf(generator, genEncoding); 113 } else if (!isArrayBufferView(generator) && !isAnyArrayBuffer(generator)) { 114 throw new ERR_INVALID_ARG_TYPE( 115 'generator', 116 ['number', 'string', 'ArrayBuffer', 'Buffer', 'TypedArray', 'DataView'], 117 generator, 118 ); 119 } 120 121 122 this[kHandle] = new _DiffieHellman(sizeOrKey, generator); 123 ObjectDefineProperty(this, 'verifyError', { 124 __proto__: null, 125 enumerable: true, 126 value: this[kHandle].verifyError, 127 writable: false, 128 }); 129} 130 131 132function DiffieHellmanGroup(name) { 133 if (!(this instanceof DiffieHellmanGroup)) 134 return new DiffieHellmanGroup(name); 135 this[kHandle] = new _DiffieHellmanGroup(name); 136 ObjectDefineProperty(this, 'verifyError', { 137 __proto__: null, 138 enumerable: true, 139 value: this[kHandle].verifyError, 140 writable: false, 141 }); 142} 143 144 145DiffieHellmanGroup.prototype.generateKeys = 146 DiffieHellman.prototype.generateKeys = 147 dhGenerateKeys; 148 149function dhGenerateKeys(encoding) { 150 const keys = this[kHandle].generateKeys(); 151 encoding = encoding || getDefaultEncoding(); 152 return encode(keys, encoding); 153} 154 155 156DiffieHellmanGroup.prototype.computeSecret = 157 DiffieHellman.prototype.computeSecret = 158 dhComputeSecret; 159 160function dhComputeSecret(key, inEnc, outEnc) { 161 const encoding = getDefaultEncoding(); 162 inEnc = inEnc || encoding; 163 outEnc = outEnc || encoding; 164 key = getArrayBufferOrView(key, 'key', inEnc); 165 const ret = this[kHandle].computeSecret(key); 166 if (typeof ret === 'string') 167 throw new ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY(); 168 return encode(ret, outEnc); 169} 170 171 172DiffieHellmanGroup.prototype.getPrime = 173 DiffieHellman.prototype.getPrime = 174 dhGetPrime; 175 176function dhGetPrime(encoding) { 177 const prime = this[kHandle].getPrime(); 178 encoding = encoding || getDefaultEncoding(); 179 return encode(prime, encoding); 180} 181 182 183DiffieHellmanGroup.prototype.getGenerator = 184 DiffieHellman.prototype.getGenerator = 185 dhGetGenerator; 186 187function dhGetGenerator(encoding) { 188 const generator = this[kHandle].getGenerator(); 189 encoding = encoding || getDefaultEncoding(); 190 return encode(generator, encoding); 191} 192 193 194DiffieHellmanGroup.prototype.getPublicKey = 195 DiffieHellman.prototype.getPublicKey = 196 dhGetPublicKey; 197 198function dhGetPublicKey(encoding) { 199 const key = this[kHandle].getPublicKey(); 200 encoding = encoding || getDefaultEncoding(); 201 return encode(key, encoding); 202} 203 204 205DiffieHellmanGroup.prototype.getPrivateKey = 206 DiffieHellman.prototype.getPrivateKey = 207 dhGetPrivateKey; 208 209function dhGetPrivateKey(encoding) { 210 const key = this[kHandle].getPrivateKey(); 211 encoding = encoding || getDefaultEncoding(); 212 return encode(key, encoding); 213} 214 215 216DiffieHellman.prototype.setPublicKey = function setPublicKey(key, encoding) { 217 encoding = encoding || getDefaultEncoding(); 218 key = getArrayBufferOrView(key, 'key', encoding); 219 this[kHandle].setPublicKey(key); 220 return this; 221}; 222 223 224DiffieHellman.prototype.setPrivateKey = function setPrivateKey(key, encoding) { 225 encoding = encoding || getDefaultEncoding(); 226 key = getArrayBufferOrView(key, 'key', encoding); 227 this[kHandle].setPrivateKey(key); 228 return this; 229}; 230 231 232function ECDH(curve) { 233 if (!(this instanceof ECDH)) 234 return new ECDH(curve); 235 236 validateString(curve, 'curve'); 237 this[kHandle] = new _ECDH(curve); 238} 239 240ECDH.prototype.computeSecret = DiffieHellman.prototype.computeSecret; 241ECDH.prototype.setPrivateKey = DiffieHellman.prototype.setPrivateKey; 242ECDH.prototype.setPublicKey = DiffieHellman.prototype.setPublicKey; 243ECDH.prototype.getPrivateKey = DiffieHellman.prototype.getPrivateKey; 244 245ECDH.prototype.generateKeys = function generateKeys(encoding, format) { 246 this[kHandle].generateKeys(); 247 248 return this.getPublicKey(encoding, format); 249}; 250 251ECDH.prototype.getPublicKey = function getPublicKey(encoding, format) { 252 const f = getFormat(format); 253 const key = this[kHandle].getPublicKey(f); 254 encoding = encoding || getDefaultEncoding(); 255 return encode(key, encoding); 256}; 257 258ECDH.convertKey = function convertKey(key, curve, inEnc, outEnc, format) { 259 validateString(curve, 'curve'); 260 const encoding = inEnc || getDefaultEncoding(); 261 key = getArrayBufferOrView(key, 'key', encoding); 262 outEnc = outEnc || encoding; 263 const f = getFormat(format); 264 const convertedKey = _ECDHConvertKey(key, curve, f); 265 return encode(convertedKey, outEnc); 266}; 267 268function encode(buffer, encoding) { 269 if (encoding && encoding !== 'buffer') 270 buffer = buffer.toString(encoding); 271 return buffer; 272} 273 274function getFormat(format) { 275 if (format) { 276 if (format === 'compressed') 277 return POINT_CONVERSION_COMPRESSED; 278 if (format === 'hybrid') 279 return POINT_CONVERSION_HYBRID; 280 if (format !== 'uncompressed') 281 throw new ERR_CRYPTO_ECDH_INVALID_FORMAT(format); 282 } 283 return POINT_CONVERSION_UNCOMPRESSED; 284} 285 286const dhEnabledKeyTypes = new SafeSet(['dh', 'ec', 'x448', 'x25519']); 287 288function diffieHellman(options) { 289 validateObject(options, 'options'); 290 291 const { privateKey, publicKey } = options; 292 if (!(privateKey instanceof KeyObject)) 293 throw new ERR_INVALID_ARG_VALUE('options.privateKey', privateKey); 294 295 if (!(publicKey instanceof KeyObject)) 296 throw new ERR_INVALID_ARG_VALUE('options.publicKey', publicKey); 297 298 if (privateKey.type !== 'private') 299 throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(privateKey.type, 'private'); 300 301 if (publicKey.type !== 'public' && publicKey.type !== 'private') { 302 throw new ERR_CRYPTO_INVALID_KEY_OBJECT_TYPE(publicKey.type, 303 'private or public'); 304 } 305 306 const privateType = privateKey.asymmetricKeyType; 307 const publicType = publicKey.asymmetricKeyType; 308 if (privateType !== publicType || !dhEnabledKeyTypes.has(privateType)) { 309 throw new ERR_CRYPTO_INCOMPATIBLE_KEY('key types for Diffie-Hellman', 310 `${privateType} and ${publicType}`); 311 } 312 313 return statelessDH(privateKey[kHandle], publicKey[kHandle]); 314} 315 316// The ecdhDeriveBits function is part of the Web Crypto API and serves both 317// deriveKeys and deriveBits functions. 318async function ecdhDeriveBits(algorithm, baseKey, length) { 319 const { 'public': key } = algorithm; 320 321 if (key.type !== 'public') { 322 throw lazyDOMException( 323 'algorithm.public must be a public key', 'InvalidAccessError'); 324 } 325 if (baseKey.type !== 'private') { 326 throw lazyDOMException( 327 'baseKey must be a private key', 'InvalidAccessError'); 328 } 329 330 if ( 331 key.algorithm.name !== 'ECDH' && 332 key.algorithm.name !== 'X25519' && 333 key.algorithm.name !== 'X448' 334 ) { 335 throw lazyDOMException('Keys must be ECDH, X25519, or X448 keys', 'InvalidAccessError'); 336 } 337 338 if (key.algorithm.name !== baseKey.algorithm.name) { 339 throw lazyDOMException( 340 'The public and private keys must be of the same type', 341 'InvalidAccessError'); 342 } 343 344 if ( 345 key.algorithm.name === 'ECDH' && 346 key.algorithm.namedCurve !== baseKey.algorithm.namedCurve 347 ) { 348 throw lazyDOMException('Named curve mismatch', 'InvalidAccessError'); 349 } 350 351 const bits = await jobPromise(() => new ECDHBitsJob( 352 kCryptoJobAsync, 353 key.algorithm.name === 'ECDH' ? baseKey.algorithm.namedCurve : baseKey.algorithm.name, 354 key[kKeyObject][kHandle], 355 baseKey[kKeyObject][kHandle])); 356 357 // If a length is not specified, return the full derived secret 358 if (length === null) 359 return bits; 360 361 // If the length is not a multiple of 8 the nearest ceiled 362 // multiple of 8 is sliced. 363 length = MathCeil(length / 8); 364 const { byteLength } = bits; 365 366 // If the length is larger than the derived secret, throw. 367 // Otherwise, we either return the secret or a truncated 368 // slice. 369 if (byteLength < length) 370 throw lazyDOMException('derived bit length is too small', 'OperationError'); 371 372 return length === byteLength ? 373 bits : 374 ArrayBufferPrototypeSlice(bits, 0, length); 375} 376 377module.exports = { 378 DiffieHellman, 379 DiffieHellmanGroup, 380 ECDH, 381 diffieHellman, 382 ecdhDeriveBits, 383}; 384