• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1'use strict';
2const common = require('../common');
3if (!common.hasCrypto)
4  common.skip('missing crypto');
5
6const assert = require('assert');
7const crypto = require('crypto');
8
9// Test Diffie-Hellman with two parties sharing a secret,
10// using various encodings as we go along
11const dh1 = crypto.createDiffieHellman(common.hasFipsCrypto ? 1024 : 256);
12const p1 = dh1.getPrime('buffer');
13const dh2 = crypto.createDiffieHellman(p1, 'buffer');
14let key1 = dh1.generateKeys();
15let key2 = dh2.generateKeys('hex');
16let secret1 = dh1.computeSecret(key2, 'hex', 'base64');
17let secret2 = dh2.computeSecret(key1, 'latin1', 'buffer');
18
19assert.strictEqual(secret2.toString('base64'), secret1);
20assert.strictEqual(dh1.verifyError, 0);
21assert.strictEqual(dh2.verifyError, 0);
22
23// https://github.com/nodejs/node/issues/32738
24// XXX(bnoordhuis) validateInt32() throwing ERR_OUT_OF_RANGE and RangeError
25// instead of ERR_INVALID_ARG_TYPE and TypeError is questionable, IMO.
26assert.throws(() => crypto.createDiffieHellman(13.37), {
27  code: 'ERR_OUT_OF_RANGE',
28  name: 'RangeError',
29  message: 'The value of "sizeOrKey" is out of range. ' +
30           'It must be an integer. Received 13.37',
31});
32
33assert.throws(() => crypto.createDiffieHellman('abcdef', 13.37), {
34  code: 'ERR_OUT_OF_RANGE',
35  name: 'RangeError',
36  message: 'The value of "generator" is out of range. ' +
37           'It must be an integer. Received 13.37',
38});
39
40for (const bits of [-1, 0, 1]) {
41  assert.throws(() => crypto.createDiffieHellman(bits), {
42    code: 'ERR_OSSL_BN_BITS_TOO_SMALL',
43    name: 'Error',
44    message: /bits too small/,
45  });
46}
47
48// Through a fluke of history, g=0 defaults to DH_GENERATOR (2).
49{
50  const g = 0;
51  crypto.createDiffieHellman('abcdef', g);
52  crypto.createDiffieHellman('abcdef', 'hex', g);
53}
54
55for (const g of [-1, 1]) {
56  const ex = {
57    code: 'ERR_OSSL_DH_BAD_GENERATOR',
58    name: 'Error',
59    message: /bad generator/,
60  };
61  assert.throws(() => crypto.createDiffieHellman('abcdef', g), ex);
62  assert.throws(() => crypto.createDiffieHellman('abcdef', 'hex', g), ex);
63}
64
65crypto.createDiffieHellman('abcdef', Buffer.from([2]));  // OK
66
67for (const g of [Buffer.from([]),
68                 Buffer.from([0]),
69                 Buffer.from([1])]) {
70  const ex = {
71    code: 'ERR_OSSL_DH_BAD_GENERATOR',
72    name: 'Error',
73    message: /bad generator/,
74  };
75  assert.throws(() => crypto.createDiffieHellman('abcdef', g), ex);
76  assert.throws(() => crypto.createDiffieHellman('abcdef', 'hex', g), ex);
77}
78
79{
80  const DiffieHellman = crypto.DiffieHellman;
81  const dh = DiffieHellman(p1, 'buffer');
82  assert(dh instanceof DiffieHellman, 'DiffieHellman is expected to return a ' +
83                                      'new instance when called without `new`');
84}
85
86{
87  const DiffieHellmanGroup = crypto.DiffieHellmanGroup;
88  const dhg = DiffieHellmanGroup('modp5');
89  assert(dhg instanceof DiffieHellmanGroup, 'DiffieHellmanGroup is expected ' +
90                                            'to return a new instance when ' +
91                                            'called without `new`');
92}
93
94{
95  const ECDH = crypto.ECDH;
96  const ecdh = ECDH('prime256v1');
97  assert(ecdh instanceof ECDH, 'ECDH is expected to return a new instance ' +
98                               'when called without `new`');
99}
100
101[
102  [0x1, 0x2],
103  () => { },
104  /abc/,
105  {},
106].forEach((input) => {
107  assert.throws(
108    () => crypto.createDiffieHellman(input),
109    {
110      code: 'ERR_INVALID_ARG_TYPE',
111      name: 'TypeError',
112      message: 'The "sizeOrKey" argument must be one of type number or string' +
113               ' or an instance of Buffer, TypedArray, or DataView.' +
114               common.invalidArgTypeHelper(input)
115    }
116  );
117});
118
119// Create "another dh1" using generated keys from dh1,
120// and compute secret again
121const dh3 = crypto.createDiffieHellman(p1, 'buffer');
122const privkey1 = dh1.getPrivateKey();
123dh3.setPublicKey(key1);
124dh3.setPrivateKey(privkey1);
125
126assert.deepStrictEqual(dh1.getPrime(), dh3.getPrime());
127assert.deepStrictEqual(dh1.getGenerator(), dh3.getGenerator());
128assert.deepStrictEqual(dh1.getPublicKey(), dh3.getPublicKey());
129assert.deepStrictEqual(dh1.getPrivateKey(), dh3.getPrivateKey());
130assert.strictEqual(dh3.verifyError, 0);
131
132const secret3 = dh3.computeSecret(key2, 'hex', 'base64');
133
134assert.strictEqual(secret1, secret3);
135
136// computeSecret works without a public key set at all.
137const dh4 = crypto.createDiffieHellman(p1, 'buffer');
138dh4.setPrivateKey(privkey1);
139
140assert.deepStrictEqual(dh1.getPrime(), dh4.getPrime());
141assert.deepStrictEqual(dh1.getGenerator(), dh4.getGenerator());
142assert.deepStrictEqual(dh1.getPrivateKey(), dh4.getPrivateKey());
143assert.strictEqual(dh4.verifyError, 0);
144
145const secret4 = dh4.computeSecret(key2, 'hex', 'base64');
146
147assert.strictEqual(secret1, secret4);
148
149const wrongBlockLength = {
150  message: 'error:0606506D:digital envelope' +
151    ' routines:EVP_DecryptFinal_ex:wrong final block length',
152  code: 'ERR_OSSL_EVP_WRONG_FINAL_BLOCK_LENGTH',
153  library: 'digital envelope routines',
154  reason: 'wrong final block length'
155};
156
157// Run this one twice to make sure that the dh3 clears its error properly
158{
159  const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), '');
160  assert.throws(() => {
161    c.final('utf8');
162  }, wrongBlockLength);
163}
164
165{
166  const c = crypto.createDecipheriv('aes-128-ecb', crypto.randomBytes(16), '');
167  assert.throws(() => {
168    c.final('utf8');
169  }, wrongBlockLength);
170}
171
172assert.throws(() => {
173  dh3.computeSecret('');
174}, { message: 'Supplied key is too small' });
175
176// Create a shared using a DH group.
177const alice = crypto.createDiffieHellmanGroup('modp5');
178const bob = crypto.createDiffieHellmanGroup('modp5');
179alice.generateKeys();
180bob.generateKeys();
181const aSecret = alice.computeSecret(bob.getPublicKey()).toString('hex');
182const bSecret = bob.computeSecret(alice.getPublicKey()).toString('hex');
183assert.strictEqual(aSecret, bSecret);
184
185// Ensure specific generator (buffer) works as expected.
186// The values below (modp2/modp2buf) are for a 1024 bits long prime from
187// RFC 2412 E.2, see https://tools.ietf.org/html/rfc2412. */
188const modp2 = crypto.createDiffieHellmanGroup('modp2');
189const modp2buf = Buffer.from([
190  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f,
191  0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, 0xc4, 0xc6, 0x62, 0x8b,
192  0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67,
193  0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22,
194  0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, 0xef, 0x95,
195  0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d,
196  0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51,
197  0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6,
198  0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x37, 0xed, 0x6b, 0x0b, 0xff,
199  0x5c, 0xb6, 0xf4, 0x06, 0xb7, 0xed, 0xee, 0x38, 0x6b, 0xfb,
200  0x5a, 0x89, 0x9f, 0xa5, 0xae, 0x9f, 0x24, 0x11, 0x7c, 0x4b,
201  0x1f, 0xe6, 0x49, 0x28, 0x66, 0x51, 0xec, 0xe6, 0x53, 0x81,
202  0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
203]);
204
205{
206  const exmodp2 = crypto.createDiffieHellman(modp2buf, Buffer.from([2]));
207  modp2.generateKeys();
208  exmodp2.generateKeys();
209  const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey())
210      .toString('hex');
211  const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey())
212      .toString('hex');
213  assert.strictEqual(modp2Secret, exmodp2Secret);
214}
215
216for (const buf of [modp2buf, ...common.getArrayBufferViews(modp2buf)]) {
217  // Ensure specific generator (string with encoding) works as expected with
218  // any ArrayBufferViews as the first argument to createDiffieHellman().
219  const exmodp2 = crypto.createDiffieHellman(buf, '02', 'hex');
220  exmodp2.generateKeys();
221  const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey())
222      .toString('hex');
223  const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey())
224      .toString('hex');
225  assert.strictEqual(modp2Secret, exmodp2Secret);
226}
227
228{
229  // Ensure specific generator (string without encoding) works as expected.
230  const exmodp2 = crypto.createDiffieHellman(modp2buf, '\x02');
231  exmodp2.generateKeys();
232  const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey())
233      .toString('hex');
234  const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey())
235      .toString('hex');
236  assert.strictEqual(modp2Secret, exmodp2Secret);
237}
238
239{
240  // Ensure specific generator (numeric) works as expected.
241  const exmodp2 = crypto.createDiffieHellman(modp2buf, 2);
242  exmodp2.generateKeys();
243  const modp2Secret = modp2.computeSecret(exmodp2.getPublicKey())
244      .toString('hex');
245  const exmodp2Secret = exmodp2.computeSecret(modp2.getPublicKey())
246      .toString('hex');
247  assert.strictEqual(modp2Secret, exmodp2Secret);
248}
249
250// Second OAKLEY group, see
251// https://github.com/nodejs/node-v0.x-archive/issues/2338 and
252// https://xml2rfc.tools.ietf.org/public/rfc/html/rfc2412.html#anchor49
253const p = 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74' +
254          '020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F1437' +
255          '4FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED' +
256          'EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF';
257crypto.createDiffieHellman(p, 'hex');
258
259// Confirm DH_check() results are exposed for optional examination.
260const bad_dh = crypto.createDiffieHellman('02', 'hex');
261assert.notStrictEqual(bad_dh.verifyError, 0);
262
263const availableCurves = new Set(crypto.getCurves());
264const availableHashes = new Set(crypto.getHashes());
265
266// Oakley curves do not clean up ERR stack, it was causing unexpected failure
267// when accessing other OpenSSL APIs afterwards.
268if (availableCurves.has('Oakley-EC2N-3')) {
269  crypto.createECDH('Oakley-EC2N-3');
270  crypto.createHash('sha256');
271}
272
273// Test ECDH
274if (availableCurves.has('prime256v1') && availableCurves.has('secp256k1')) {
275  const ecdh1 = crypto.createECDH('prime256v1');
276  const ecdh2 = crypto.createECDH('prime256v1');
277  key1 = ecdh1.generateKeys();
278  key2 = ecdh2.generateKeys('hex');
279  secret1 = ecdh1.computeSecret(key2, 'hex', 'base64');
280  secret2 = ecdh2.computeSecret(key1, 'latin1', 'buffer');
281
282  assert.strictEqual(secret1, secret2.toString('base64'));
283
284  // Point formats
285  assert.strictEqual(ecdh1.getPublicKey('buffer', 'uncompressed')[0], 4);
286  let firstByte = ecdh1.getPublicKey('buffer', 'compressed')[0];
287  assert(firstByte === 2 || firstByte === 3);
288  firstByte = ecdh1.getPublicKey('buffer', 'hybrid')[0];
289  assert(firstByte === 6 || firstByte === 7);
290  // Format value should be string
291
292  assert.throws(
293    () => ecdh1.getPublicKey('buffer', 10),
294    {
295      code: 'ERR_CRYPTO_ECDH_INVALID_FORMAT',
296      name: 'TypeError',
297      message: 'Invalid ECDH format: 10'
298    });
299
300  // ECDH should check that point is on curve
301  const ecdh3 = crypto.createECDH('secp256k1');
302  const key3 = ecdh3.generateKeys();
303
304  assert.throws(
305    () => ecdh2.computeSecret(key3, 'latin1', 'buffer'),
306    {
307      code: 'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY',
308      name: 'Error',
309      message: 'Public key is not valid for specified curve'
310    });
311
312  // ECDH should allow .setPrivateKey()/.setPublicKey()
313  const ecdh4 = crypto.createECDH('prime256v1');
314
315  ecdh4.setPrivateKey(ecdh1.getPrivateKey());
316  ecdh4.setPublicKey(ecdh1.getPublicKey());
317
318  assert.throws(() => {
319    ecdh4.setPublicKey(ecdh3.getPublicKey());
320  }, { message: 'Failed to convert Buffer to EC_POINT' });
321
322  // Verify that we can use ECDH without having to use newly generated keys.
323  const ecdh5 = crypto.createECDH('secp256k1');
324
325  // Verify errors are thrown when retrieving keys from an uninitialized object.
326  assert.throws(() => {
327    ecdh5.getPublicKey();
328  }, /^Error: Failed to get ECDH public key$/);
329
330  assert.throws(() => {
331    ecdh5.getPrivateKey();
332  }, /^Error: Failed to get ECDH private key$/);
333
334  // A valid private key for the secp256k1 curve.
335  const cafebabeKey = 'cafebabe'.repeat(8);
336  // Associated compressed and uncompressed public keys (points).
337  const cafebabePubPtComp =
338  '03672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3';
339  const cafebabePubPtUnComp =
340  '04672a31bfc59d3f04548ec9b7daeeba2f61814e8ccc40448045007f5479f693a3' +
341  '2e02c7f93d13dc2732b760ca377a5897b9dd41a1c1b29dc0442fdce6d0a04d1d';
342  ecdh5.setPrivateKey(cafebabeKey, 'hex');
343  assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey);
344  // Show that the public point (key) is generated while setting the
345  // private key.
346  assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp);
347
348  // Compressed and uncompressed public points/keys for other party's
349  // private key.
350  // 0xDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF
351  const peerPubPtComp =
352  '02c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae';
353  const peerPubPtUnComp =
354  '04c6b754b20826eb925e052ee2c25285b162b51fdca732bcf67e39d647fb6830ae' +
355  'b651944a574a362082a77e3f2b5d9223eb54d7f2f76846522bf75f3bedb8178e';
356
357  const sharedSecret =
358  '1da220b5329bbe8bfd19ceef5a5898593f411a6f12ea40f2a8eead9a5cf59970';
359
360  assert.strictEqual(ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex'),
361                     sharedSecret);
362  assert.strictEqual(ecdh5.computeSecret(peerPubPtUnComp, 'hex', 'hex'),
363                     sharedSecret);
364
365  // Verify that we still have the same key pair as before the computation.
366  assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey);
367  assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp);
368
369  // Verify setting and getting compressed and non-compressed serializations.
370  ecdh5.setPublicKey(cafebabePubPtComp, 'hex');
371  assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp);
372  assert.strictEqual(
373    ecdh5.getPublicKey('hex', 'compressed'),
374    cafebabePubPtComp
375  );
376  ecdh5.setPublicKey(cafebabePubPtUnComp, 'hex');
377  assert.strictEqual(ecdh5.getPublicKey('hex'), cafebabePubPtUnComp);
378  assert.strictEqual(
379    ecdh5.getPublicKey('hex', 'compressed'),
380    cafebabePubPtComp
381  );
382
383  // Show why allowing the public key to be set on this type
384  // does not make sense.
385  ecdh5.setPublicKey(peerPubPtComp, 'hex');
386  assert.strictEqual(ecdh5.getPublicKey('hex'), peerPubPtUnComp);
387  assert.throws(() => {
388    // Error because the public key does not match the private key anymore.
389    ecdh5.computeSecret(peerPubPtComp, 'hex', 'hex');
390  }, /^Error: Invalid key pair$/);
391
392  // Set to a valid key to show that later attempts to set an invalid key are
393  // rejected.
394  ecdh5.setPrivateKey(cafebabeKey, 'hex');
395
396  // Some invalid private keys for the secp256k1 curve.
397  const errMessage = /^Error: Private key is not valid for specified curve\.$/;
398  ['0000000000000000000000000000000000000000000000000000000000000000',
399   'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141',
400   'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF',
401  ].forEach((element) => {
402    assert.throws(() => {
403      ecdh5.setPrivateKey(element, 'hex');
404    }, errMessage);
405    // Verify object state did not change.
406    assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey);
407  });
408}
409
410// Use of invalid keys was not cleaning up ERR stack, and was causing
411// unexpected failure in subsequent signing operations.
412if (availableCurves.has('prime256v1') && availableHashes.has('sha256')) {
413  const curve = crypto.createECDH('prime256v1');
414  const invalidKey = Buffer.alloc(65);
415  invalidKey.fill('\0');
416  curve.generateKeys();
417  assert.throws(
418    () => curve.computeSecret(invalidKey),
419    {
420      code: 'ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY',
421      name: 'Error',
422      message: 'Public key is not valid for specified curve'
423    });
424  // Check that signing operations are not impacted by the above error.
425  const ecPrivateKey =
426    '-----BEGIN EC PRIVATE KEY-----\n' +
427    'MHcCAQEEIF+jnWY1D5kbVYDNvxxo/Y+ku2uJPDwS0r/VuPZQrjjVoAoGCCqGSM49\n' +
428    'AwEHoUQDQgAEurOxfSxmqIRYzJVagdZfMMSjRNNhB8i3mXyIMq704m2m52FdfKZ2\n' +
429    'pQhByd5eyj3lgZ7m7jbchtdgyOF8Io/1ng==\n' +
430    '-----END EC PRIVATE KEY-----';
431  crypto.createSign('SHA256').sign(ecPrivateKey);
432}
433
434// Invalid test: curve argument is undefined
435assert.throws(
436  () => crypto.createECDH(),
437  {
438    code: 'ERR_INVALID_ARG_TYPE',
439    name: 'TypeError',
440    message: 'The "curve" argument must be of type string. ' +
441             'Received undefined'
442  });
443
444assert.throws(
445  function() {
446    crypto.getDiffieHellman('unknown-group');
447  },
448  {
449    name: 'Error',
450    code: 'ERR_CRYPTO_UNKNOWN_DH_GROUP',
451    message: 'Unknown DH group'
452  },
453  'crypto.getDiffieHellman(\'unknown-group\') ' +
454  'failed to throw the expected error.'
455);
456assert.throws(
457  function() {
458    crypto.getDiffieHellman('modp1').setPrivateKey('');
459  },
460  new RegExp('^TypeError: crypto\\.getDiffieHellman\\(\\.\\.\\.\\)\\.' +
461  'setPrivateKey is not a function$'),
462  'crypto.getDiffieHellman(\'modp1\').setPrivateKey(\'\') ' +
463  'failed to throw the expected error.'
464);
465assert.throws(
466  function() {
467    crypto.getDiffieHellman('modp1').setPublicKey('');
468  },
469  new RegExp('^TypeError: crypto\\.getDiffieHellman\\(\\.\\.\\.\\)\\.' +
470  'setPublicKey is not a function$'),
471  'crypto.getDiffieHellman(\'modp1\').setPublicKey(\'\') ' +
472  'failed to throw the expected error.'
473);
474assert.throws(
475  () => crypto.createDiffieHellman('', true),
476  {
477    code: 'ERR_INVALID_ARG_TYPE'
478  }
479);
480