• 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 fs = require('fs');
8const path = require('path');
9const exec = require('child_process').exec;
10const crypto = require('crypto');
11const fixtures = require('../common/fixtures');
12
13// Test certificates
14const certPem = fixtures.readKey('rsa_cert.crt');
15const keyPem = fixtures.readKey('rsa_private.pem');
16const keySize = 2048;
17
18{
19  const Sign = crypto.Sign;
20  const instance = Sign('SHA256');
21  assert(instance instanceof Sign, 'Sign is expected to return a new ' +
22                                   'instance when called without `new`');
23}
24
25{
26  const Verify = crypto.Verify;
27  const instance = Verify('SHA256');
28  assert(instance instanceof Verify, 'Verify is expected to return a new ' +
29                                     'instance when called without `new`');
30}
31
32// Test handling of exceptional conditions
33{
34  const library = {
35    configurable: true,
36    set() {
37      throw new Error('bye, bye, library');
38    }
39  };
40  Object.defineProperty(Object.prototype, 'library', library);
41
42  assert.throws(() => {
43    crypto.createSign('sha1').sign(
44      `-----BEGIN RSA PRIVATE KEY-----
45      AAAAAAAAAAAA
46      -----END RSA PRIVATE KEY-----`);
47  }, { message: 'bye, bye, library' });
48
49  delete Object.prototype.library;
50
51  const errorStack = {
52    configurable: true,
53    set() {
54      throw new Error('bye, bye, error stack');
55    }
56  };
57  Object.defineProperty(Object.prototype, 'opensslErrorStack', errorStack);
58
59  assert.throws(() => {
60    crypto.createSign('SHA1')
61      .update('Test123')
62      .sign({
63        key: keyPem,
64        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
65      });
66  }, { message: 'bye, bye, error stack' });
67
68  delete Object.prototype.opensslErrorStack;
69}
70
71assert.throws(
72  () => crypto.createVerify('SHA256').verify({
73    key: certPem,
74    padding: null,
75  }, ''),
76  {
77    code: 'ERR_INVALID_OPT_VALUE',
78    name: 'TypeError',
79    message: 'The value "null" is invalid for option "padding"'
80  });
81
82assert.throws(
83  () => crypto.createVerify('SHA256').verify({
84    key: certPem,
85    saltLength: null,
86  }, ''),
87  {
88    code: 'ERR_INVALID_OPT_VALUE',
89    name: 'TypeError',
90    message: 'The value "null" is invalid for option "saltLength"'
91  });
92
93// Test signing and verifying
94{
95  const s1 = crypto.createSign('SHA1')
96                   .update('Test123')
97                   .sign(keyPem, 'base64');
98  let s1stream = crypto.createSign('SHA1');
99  s1stream.end('Test123');
100  s1stream = s1stream.sign(keyPem, 'base64');
101  assert.strictEqual(s1, s1stream, `${s1} should equal ${s1stream}`);
102
103  const verified = crypto.createVerify('SHA1')
104                         .update('Test')
105                         .update('123')
106                         .verify(certPem, s1, 'base64');
107  assert.strictEqual(verified, true);
108}
109
110{
111  const s2 = crypto.createSign('SHA256')
112                   .update('Test123')
113                   .sign(keyPem, 'latin1');
114  let s2stream = crypto.createSign('SHA256');
115  s2stream.end('Test123');
116  s2stream = s2stream.sign(keyPem, 'latin1');
117  assert.strictEqual(s2, s2stream, `${s2} should equal ${s2stream}`);
118
119  let verified = crypto.createVerify('SHA256')
120                       .update('Test')
121                       .update('123')
122                       .verify(certPem, s2, 'latin1');
123  assert.strictEqual(verified, true);
124
125  const verStream = crypto.createVerify('SHA256');
126  verStream.write('Tes');
127  verStream.write('t12');
128  verStream.end('3');
129  verified = verStream.verify(certPem, s2, 'latin1');
130  assert.strictEqual(verified, true);
131}
132
133{
134  const s3 = crypto.createSign('SHA1')
135                   .update('Test123')
136                   .sign(keyPem, 'buffer');
137  let verified = crypto.createVerify('SHA1')
138                       .update('Test')
139                       .update('123')
140                       .verify(certPem, s3);
141  assert.strictEqual(verified, true);
142
143  const verStream = crypto.createVerify('SHA1');
144  verStream.write('Tes');
145  verStream.write('t12');
146  verStream.end('3');
147  verified = verStream.verify(certPem, s3);
148  assert.strictEqual(verified, true);
149}
150
151// Special tests for RSA_PKCS1_PSS_PADDING
152{
153  function testPSS(algo, hLen) {
154    // Maximum permissible salt length
155    const max = keySize / 8 - hLen - 2;
156
157    function getEffectiveSaltLength(saltLength) {
158      switch (saltLength) {
159        case crypto.constants.RSA_PSS_SALTLEN_DIGEST:
160          return hLen;
161        case crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN:
162          return max;
163        default:
164          return saltLength;
165      }
166    }
167
168    const signSaltLengths = [
169      crypto.constants.RSA_PSS_SALTLEN_DIGEST,
170      getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST),
171      crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN,
172      getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN),
173      0, 16, 32, 64, 128,
174    ];
175
176    const verifySaltLengths = [
177      crypto.constants.RSA_PSS_SALTLEN_DIGEST,
178      getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_DIGEST),
179      getEffectiveSaltLength(crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN),
180      0, 16, 32, 64, 128,
181    ];
182    const errMessage = /^Error:.*data too large for key size$/;
183
184    const data = Buffer.from('Test123');
185
186    signSaltLengths.forEach((signSaltLength) => {
187      if (signSaltLength > max) {
188        // If the salt length is too big, an Error should be thrown
189        assert.throws(() => {
190          crypto.createSign(algo)
191            .update(data)
192            .sign({
193              key: keyPem,
194              padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
195              saltLength: signSaltLength
196            });
197        }, errMessage);
198        assert.throws(() => {
199          crypto.sign(algo, data, {
200            key: keyPem,
201            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
202            saltLength: signSaltLength
203          });
204        }, errMessage);
205      } else {
206        // Otherwise, a valid signature should be generated
207        const s4 = crypto.createSign(algo)
208                         .update(data)
209                         .sign({
210                           key: keyPem,
211                           padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
212                           saltLength: signSaltLength
213                         });
214        const s4_2 = crypto.sign(algo, data, {
215          key: keyPem,
216          padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
217          saltLength: signSaltLength
218        });
219
220        [s4, s4_2].forEach((sig) => {
221          let verified;
222          verifySaltLengths.forEach((verifySaltLength) => {
223            // Verification should succeed if and only if the salt length is
224            // correct
225            verified = crypto.createVerify(algo)
226                             .update(data)
227                             .verify({
228                               key: certPem,
229                               padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
230                               saltLength: verifySaltLength
231                             }, sig);
232            assert.strictEqual(verified, crypto.verify(algo, data, {
233              key: certPem,
234              padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
235              saltLength: verifySaltLength
236            }, sig));
237            const saltLengthCorrect = getEffectiveSaltLength(signSaltLength) ===
238                                      getEffectiveSaltLength(verifySaltLength);
239            assert.strictEqual(verified, saltLengthCorrect);
240          });
241
242          // Verification using RSA_PSS_SALTLEN_AUTO should always work
243          verified = crypto.createVerify(algo)
244                           .update(data)
245                           .verify({
246                             key: certPem,
247                             padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
248                             saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
249                           }, sig);
250          assert.strictEqual(verified, true);
251          assert.strictEqual(verified, crypto.verify(algo, data, {
252            key: certPem,
253            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
254            saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
255          }, sig));
256
257          // Verifying an incorrect message should never work
258          const wrongData = Buffer.from('Test1234');
259          verified = crypto.createVerify(algo)
260                           .update(wrongData)
261                           .verify({
262                             key: certPem,
263                             padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
264                             saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
265                           }, sig);
266          assert.strictEqual(verified, false);
267          assert.strictEqual(verified, crypto.verify(algo, wrongData, {
268            key: certPem,
269            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
270            saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
271          }, sig));
272        });
273      }
274    });
275  }
276
277  testPSS('SHA1', 20);
278  testPSS('SHA256', 32);
279}
280
281// Test vectors for RSA_PKCS1_PSS_PADDING provided by the RSA Laboratories:
282// https://www.emc.com/emc-plus/rsa-labs/standards-initiatives/pkcs-rsa-cryptography-standard.htm
283{
284  // We only test verification as we cannot specify explicit salts when signing
285  function testVerify(cert, vector) {
286    const verified = crypto.createVerify('SHA1')
287                          .update(Buffer.from(vector.message, 'hex'))
288                          .verify({
289                            key: cert,
290                            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
291                            saltLength: vector.salt.length / 2
292                          }, vector.signature, 'hex');
293    assert.strictEqual(verified, true);
294  }
295
296  const examples = JSON.parse(fixtures.readSync('pss-vectors.json', 'utf8'));
297
298  for (const key in examples) {
299    const example = examples[key];
300    const publicKey = example.publicKey.join('\n');
301    example.tests.forEach((test) => testVerify(publicKey, test));
302  }
303}
304
305// Test exceptions for invalid `padding` and `saltLength` values
306{
307  [null, NaN, 'boom', {}, [], true, false]
308    .forEach((invalidValue) => {
309      assert.throws(() => {
310        crypto.createSign('SHA256')
311          .update('Test123')
312          .sign({
313            key: keyPem,
314            padding: invalidValue
315          });
316      }, {
317        code: 'ERR_INVALID_OPT_VALUE',
318        name: 'TypeError'
319      });
320
321      assert.throws(() => {
322        crypto.createSign('SHA256')
323          .update('Test123')
324          .sign({
325            key: keyPem,
326            padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
327            saltLength: invalidValue
328          });
329      }, {
330        code: 'ERR_INVALID_OPT_VALUE',
331        name: 'TypeError'
332      });
333    });
334
335  assert.throws(() => {
336    crypto.createSign('SHA1')
337      .update('Test123')
338      .sign({
339        key: keyPem,
340        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING
341      });
342  }, {
343    code: 'ERR_OSSL_RSA_ILLEGAL_OR_UNSUPPORTED_PADDING_MODE',
344    message: /illegal or unsupported padding mode/,
345    opensslErrorStack: [
346      'error:06089093:digital envelope routines:EVP_PKEY_CTX_ctrl:' +
347      'command not supported',
348    ],
349  });
350}
351
352// Test throws exception when key options is null
353{
354  assert.throws(() => {
355    crypto.createSign('SHA1').update('Test123').sign(null, 'base64');
356  }, {
357    code: 'ERR_CRYPTO_SIGN_KEY_REQUIRED',
358    name: 'Error'
359  });
360}
361
362{
363  const sign = crypto.createSign('SHA1');
364  const verify = crypto.createVerify('SHA1');
365
366  [1, [], {}, undefined, null, true, Infinity].forEach((input) => {
367    const errObj = {
368      code: 'ERR_INVALID_ARG_TYPE',
369      name: 'TypeError',
370      message: 'The "algorithm" argument must be of type string.' +
371               `${common.invalidArgTypeHelper(input)}`
372    };
373    assert.throws(() => crypto.createSign(input), errObj);
374    assert.throws(() => crypto.createVerify(input), errObj);
375
376    errObj.message = 'The "data" argument must be of type string or an ' +
377                     'instance of Buffer, TypedArray, or DataView.' +
378                     common.invalidArgTypeHelper(input);
379    assert.throws(() => sign.update(input), errObj);
380    assert.throws(() => verify.update(input), errObj);
381    assert.throws(() => sign._write(input, 'utf8', () => {}), errObj);
382    assert.throws(() => verify._write(input, 'utf8', () => {}), errObj);
383  });
384
385  [
386    Uint8Array, Uint16Array, Uint32Array, Float32Array, Float64Array,
387  ].forEach((clazz) => {
388    // These should all just work
389    sign.update(new clazz());
390    verify.update(new clazz());
391  });
392
393  [1, {}, [], Infinity].forEach((input) => {
394    let prop = '"key" argument';
395    let value = input;
396    if (typeof input === 'object') {
397      prop = '"key.key" property';
398      value = undefined;
399    }
400    const errObj = {
401      code: 'ERR_INVALID_ARG_TYPE',
402      name: 'TypeError',
403      message: `The ${prop} must be of type string or ` +
404               'an instance of Buffer, TypedArray, DataView, or KeyObject.' +
405               common.invalidArgTypeHelper(value)
406    };
407
408    assert.throws(() => sign.sign(input), errObj);
409    assert.throws(() => verify.verify(input), errObj);
410
411    errObj.message = 'The "signature" argument must be of type string or an ' +
412                     'instance of Buffer, TypedArray, or DataView.' +
413                     common.invalidArgTypeHelper(input);
414    assert.throws(() => verify.verify('test', input), errObj);
415  });
416}
417
418{
419  assert.throws(
420    () => crypto.createSign('sha8'),
421    /Unknown message digest/);
422  assert.throws(
423    () => crypto.sign('sha8', Buffer.alloc(1), keyPem),
424    /Unknown message digest/);
425}
426
427[
428  { private: fixtures.readKey('ed25519_private.pem', 'ascii'),
429    public: fixtures.readKey('ed25519_public.pem', 'ascii'),
430    algo: null,
431    sigLen: 64 },
432  { private: fixtures.readKey('ed448_private.pem', 'ascii'),
433    public: fixtures.readKey('ed448_public.pem', 'ascii'),
434    algo: null,
435    sigLen: 114 },
436  { private: fixtures.readKey('rsa_private_2048.pem', 'ascii'),
437    public: fixtures.readKey('rsa_public_2048.pem', 'ascii'),
438    algo: 'sha1',
439    sigLen: 256 },
440].forEach((pair) => {
441  const algo = pair.algo;
442
443  {
444    const data = Buffer.from('Hello world');
445    const sig = crypto.sign(algo, data, pair.private);
446    assert.strictEqual(sig.length, pair.sigLen);
447
448    assert.strictEqual(crypto.verify(algo, data, pair.private, sig),
449                       true);
450    assert.strictEqual(crypto.verify(algo, data, pair.public, sig),
451                       true);
452  }
453
454  {
455    const data = Buffer.from('Hello world');
456    const privKeyObj = crypto.createPrivateKey(pair.private);
457    const pubKeyObj = crypto.createPublicKey(pair.public);
458
459    const sig = crypto.sign(algo, data, privKeyObj);
460    assert.strictEqual(sig.length, pair.sigLen);
461
462    assert.strictEqual(crypto.verify(algo, data, privKeyObj, sig), true);
463    assert.strictEqual(crypto.verify(algo, data, pubKeyObj, sig), true);
464  }
465
466  {
467    const data = Buffer.from('Hello world');
468    const otherData = Buffer.from('Goodbye world');
469    const otherSig = crypto.sign(algo, otherData, pair.private);
470    assert.strictEqual(crypto.verify(algo, data, pair.private, otherSig),
471                       false);
472  }
473
474  [
475    Uint8Array, Uint16Array, Uint32Array, Float32Array, Float64Array,
476  ].forEach((clazz) => {
477    const data = new clazz();
478    const sig = crypto.sign(algo, data, pair.private);
479    assert.strictEqual(crypto.verify(algo, data, pair.private, sig),
480                       true);
481  });
482});
483
484[1, {}, [], true, Infinity].forEach((input) => {
485  const data = Buffer.alloc(1);
486  const sig = Buffer.alloc(1);
487  const errObj = {
488    code: 'ERR_INVALID_ARG_TYPE',
489    name: 'TypeError',
490    message: 'The "data" argument must be an instance of Buffer, ' +
491             'TypedArray, or DataView.' +
492             common.invalidArgTypeHelper(input)
493  };
494
495  assert.throws(() => crypto.sign(null, input, 'asdf'), errObj);
496  assert.throws(() => crypto.verify(null, input, 'asdf', sig), errObj);
497
498  let prop = '"key" argument';
499  let value = input;
500  if (typeof input === 'object') {
501    prop = '"key.key" property';
502    value = undefined;
503  }
504  errObj.message = `The ${prop} must be of type string or ` +
505              'an instance of Buffer, TypedArray, DataView, or KeyObject.' +
506              common.invalidArgTypeHelper(value);
507
508  assert.throws(() => crypto.sign(null, data, input), errObj);
509  assert.throws(() => crypto.verify(null, data, input, sig), errObj);
510
511  errObj.message = 'The "signature" argument must be an instance of ' +
512                   'Buffer, TypedArray, or DataView.' +
513                   common.invalidArgTypeHelper(input);
514  assert.throws(() => crypto.verify(null, data, 'test', input), errObj);
515});
516
517{
518  const data = Buffer.from('Hello world');
519  const keys = [['ec-key.pem', 64], ['dsa_private_1025.pem', 40]];
520
521  for (const [file, length] of keys) {
522    const privKey = fixtures.readKey(file);
523    [
524      crypto.createSign('sha1').update(data).sign(privKey),
525      crypto.sign('sha1', data, privKey),
526      crypto.sign('sha1', data, { key: privKey, dsaEncoding: 'der' }),
527    ].forEach((sig) => {
528      // Signature length variability due to DER encoding
529      assert(sig.length >= length + 4 && sig.length <= length + 8);
530
531      assert.strictEqual(
532        crypto.createVerify('sha1').update(data).verify(privKey, sig),
533        true
534      );
535      assert.strictEqual(crypto.verify('sha1', data, privKey, sig), true);
536    });
537
538    // Test (EC)DSA signature conversion.
539    const opts = { key: privKey, dsaEncoding: 'ieee-p1363' };
540    let sig = crypto.sign('sha1', data, opts);
541    // Unlike DER signatures, IEEE P1363 signatures have a predictable length.
542    assert.strictEqual(sig.length, length);
543    assert.strictEqual(crypto.verify('sha1', data, opts, sig), true);
544    assert.strictEqual(crypto.createVerify('sha1')
545                             .update(data)
546                             .verify(opts, sig), true);
547
548    // Test invalid signature lengths.
549    for (const i of [-2, -1, 1, 2, 4, 8]) {
550      sig = crypto.randomBytes(length + i);
551      assert.throws(() => {
552        crypto.verify('sha1', data, opts, sig);
553      }, {
554        message: 'Malformed signature'
555      });
556    }
557  }
558
559  // Test verifying externally signed messages.
560  const extSig = Buffer.from('494c18ab5c8a62a72aea5041966902bcfa229821af2bf65' +
561                             '0b5b4870d1fe6aebeaed9460c62210693b5b0a300033823' +
562                             '33d9529c8abd8c5948940af944828be16c', 'hex');
563  for (const ok of [true, false]) {
564    assert.strictEqual(
565      crypto.verify('sha256', data, {
566        key: fixtures.readKey('ec-key.pem'),
567        dsaEncoding: 'ieee-p1363'
568      }, extSig),
569      ok
570    );
571
572    assert.strictEqual(
573      crypto.createVerify('sha256').update(data).verify({
574        key: fixtures.readKey('ec-key.pem'),
575        dsaEncoding: 'ieee-p1363'
576      }, extSig),
577      ok
578    );
579
580    extSig[Math.floor(Math.random() * extSig.length)] ^= 1;
581  }
582
583  // Non-(EC)DSA keys should ignore the option.
584  const sig = crypto.sign('sha1', data, {
585    key: keyPem,
586    dsaEncoding: 'ieee-p1363'
587  });
588  assert.strictEqual(crypto.verify('sha1', data, certPem, sig), true);
589  assert.strictEqual(
590    crypto.verify('sha1', data, {
591      key: certPem,
592      dsaEncoding: 'ieee-p1363'
593    }, sig),
594    true
595  );
596  assert.strictEqual(
597    crypto.verify('sha1', data, {
598      key: certPem,
599      dsaEncoding: 'der'
600    }, sig),
601    true
602  );
603
604  for (const dsaEncoding of ['foo', null, {}, 5, true, NaN]) {
605    assert.throws(() => {
606      crypto.sign('sha1', data, {
607        key: certPem,
608        dsaEncoding
609      });
610    }, {
611      code: 'ERR_INVALID_OPT_VALUE'
612    });
613  }
614}
615
616
617// RSA-PSS Sign test by verifying with 'openssl dgst -verify'
618// Note: this particular test *must* be the last in this file as it will exit
619// early if no openssl binary is found
620{
621  if (!common.opensslCli)
622    common.skip('node compiled without OpenSSL CLI.');
623
624  const pubfile = fixtures.path('keys', 'rsa_public_2048.pem');
625  const privkey = fixtures.readKey('rsa_private_2048.pem');
626
627  const msg = 'Test123';
628  const s5 = crypto.createSign('SHA256')
629    .update(msg)
630    .sign({
631      key: privkey,
632      padding: crypto.constants.RSA_PKCS1_PSS_PADDING
633    });
634
635  const tmpdir = require('../common/tmpdir');
636  tmpdir.refresh();
637
638  const sigfile = path.join(tmpdir.path, 's5.sig');
639  fs.writeFileSync(sigfile, s5);
640  const msgfile = path.join(tmpdir.path, 's5.msg');
641  fs.writeFileSync(msgfile, msg);
642
643  const cmd =
644    `"${common.opensslCli}" dgst -sha256 -verify "${pubfile}" -signature "${
645      sigfile}" -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:-2 "${
646      msgfile}"`;
647
648  exec(cmd, common.mustCall((err, stdout, stderr) => {
649    assert(stdout.includes('Verified OK'));
650  }));
651}
652