• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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