1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const common = require('../common');
24
25if (!common.hasCrypto)
26  common.skip('missing crypto');
27
28common.expectWarning({
29  DeprecationWarning: [
30    ['crypto.createCipher is deprecated.', 'DEP0106'],
31  ]
32});
33
34const assert = require('assert');
35const crypto = require('crypto');
36const tls = require('tls');
37const fixtures = require('../common/fixtures');
38
39// Test Certificates
40const certPfx = fixtures.readKey('rsa_cert.pfx');
41
42// 'this' safety
43// https://github.com/joyent/node/issues/6690
44assert.throws(() => {
45  const credentials = tls.createSecureContext();
46  const context = credentials.context;
47  const notcontext = { setOptions: context.setOptions };
48
49  // Methods of native objects should not segfault when reassigned to a new
50  // object and called illegally. This core dumped in 0.10 and was fixed in
51  // 0.11.
52  notcontext.setOptions();
53}, (err) => {
54  // Throws TypeError, so there is no opensslErrorStack property.
55  return err instanceof TypeError &&
56         err.name === 'TypeError' &&
57         /^TypeError: Illegal invocation$/.test(err) &&
58         !('opensslErrorStack' in err);
59});
60
61// PFX tests
62tls.createSecureContext({ pfx: certPfx, passphrase: 'sample' });
63
64assert.throws(() => {
65  tls.createSecureContext({ pfx: certPfx });
66}, (err) => {
67  // Throws general Error, so there is no opensslErrorStack property.
68  return err instanceof Error &&
69         err.name === 'Error' &&
70         /^Error: mac verify failure$/.test(err) &&
71         !('opensslErrorStack' in err);
72});
73
74assert.throws(() => {
75  tls.createSecureContext({ pfx: certPfx, passphrase: 'test' });
76}, (err) => {
77  // Throws general Error, so there is no opensslErrorStack property.
78  return err instanceof Error &&
79         err.name === 'Error' &&
80         /^Error: mac verify failure$/.test(err) &&
81         !('opensslErrorStack' in err);
82});
83
84assert.throws(() => {
85  tls.createSecureContext({ pfx: 'sample', passphrase: 'test' });
86}, (err) => {
87  // Throws general Error, so there is no opensslErrorStack property.
88  return err instanceof Error &&
89         err.name === 'Error' &&
90         /^Error: not enough data$/.test(err) &&
91         !('opensslErrorStack' in err);
92});
93
94
95// update() should only take buffers / strings
96assert.throws(
97  () => crypto.createHash('sha1').update({ foo: 'bar' }),
98  {
99    code: 'ERR_INVALID_ARG_TYPE',
100    name: 'TypeError'
101  });
102
103
104function validateList(list) {
105  // The list must not be empty
106  assert(list.length > 0);
107
108  // The list should be sorted.
109  // Array#sort() modifies the list in place so make a copy.
110  const sorted = [...list].sort();
111  assert.deepStrictEqual(list, sorted);
112
113  // Each element should be unique.
114  assert.strictEqual([...new Set(list)].length, list.length);
115
116  // Each element should be a string.
117  assert(list.every((value) => typeof value === 'string'));
118}
119
120// Assume that we have at least AES-128-CBC.
121const cryptoCiphers = crypto.getCiphers();
122assert(crypto.getCiphers().includes('aes-128-cbc'));
123validateList(cryptoCiphers);
124// Make sure all of the ciphers are supported by OpenSSL
125for (const algo of cryptoCiphers) {
126  const { ivLength, keyLength, mode } = crypto.getCipherInfo(algo);
127  let options;
128  if (mode === 'ccm')
129    options = { authTagLength: 8 };
130  else if (mode === 'ocb' || algo === 'chacha20-poly1305')
131    options = { authTagLength: 16 };
132  crypto.createCipheriv(algo,
133                        crypto.randomBytes(keyLength),
134                        crypto.randomBytes(ivLength || 0),
135                        options);
136}
137
138// Assume that we have at least AES256-SHA.
139const tlsCiphers = tls.getCiphers();
140assert(tls.getCiphers().includes('aes256-sha'));
141assert(tls.getCiphers().includes('tls_aes_128_ccm_8_sha256'));
142// There should be no capital letters in any element.
143const noCapitals = /^[^A-Z]+$/;
144assert(tlsCiphers.every((value) => noCapitals.test(value)));
145validateList(tlsCiphers);
146
147// Assert that we have sha1 and sha256 but not SHA1 and SHA256.
148assert.notStrictEqual(crypto.getHashes().length, 0);
149assert(crypto.getHashes().includes('sha1'));
150assert(crypto.getHashes().includes('sha256'));
151assert(!crypto.getHashes().includes('SHA1'));
152assert(!crypto.getHashes().includes('SHA256'));
153assert(crypto.getHashes().includes('RSA-SHA1'));
154assert(!crypto.getHashes().includes('rsa-sha1'));
155validateList(crypto.getHashes());
156// Make sure all of the hashes are supported by OpenSSL
157for (const algo of crypto.getHashes())
158  crypto.createHash(algo);
159
160// Assume that we have at least secp384r1.
161assert.notStrictEqual(crypto.getCurves().length, 0);
162assert(crypto.getCurves().includes('secp384r1'));
163assert(!crypto.getCurves().includes('SECP384R1'));
164validateList(crypto.getCurves());
165
166// Modifying return value from get* functions should not mutate subsequent
167// return values.
168function testImmutability(fn) {
169  const list = fn();
170  const copy = [...list];
171  list.push('some-arbitrary-value');
172  assert.deepStrictEqual(fn(), copy);
173}
174
175testImmutability(crypto.getCiphers);
176testImmutability(tls.getCiphers);
177testImmutability(crypto.getHashes);
178testImmutability(crypto.getCurves);
179
180const encodingError = {
181  code: 'ERR_INVALID_ARG_VALUE',
182  name: 'TypeError',
183  message: "The argument 'encoding' is invalid for data of length 1." +
184           " Received 'hex'",
185};
186
187// Regression tests for https://github.com/nodejs/node-v0.x-archive/pull/5725:
188// hex input that's not a power of two should throw, not assert in C++ land.
189['createCipher', 'createDecipher'].forEach((funcName) => {
190  assert.throws(
191    () => crypto[funcName]('aes192', 'test').update('0', 'hex'),
192    (error) => {
193      assert.ok(!('opensslErrorStack' in error));
194      if (common.hasFipsCrypto) {
195        return error instanceof Error &&
196               error.name === 'Error' &&
197               /^Error: not supported in FIPS mode$/.test(error);
198      }
199      assert.throws(() => { throw error; }, encodingError);
200      return true;
201    }
202  );
203});
204
205assert.throws(
206  () => crypto.createHash('sha1').update('0', 'hex'),
207  (error) => {
208    assert.ok(!('opensslErrorStack' in error));
209    assert.throws(() => { throw error; }, encodingError);
210    return true;
211  }
212);
213
214assert.throws(
215  () => crypto.createHmac('sha256', 'a secret').update('0', 'hex'),
216  (error) => {
217    assert.ok(!('opensslErrorStack' in error));
218    assert.throws(() => { throw error; }, encodingError);
219    return true;
220  }
221);
222
223assert.throws(() => {
224  const priv = [
225    '-----BEGIN RSA PRIVATE KEY-----',
226    'MIGrAgEAAiEA+3z+1QNF2/unumadiwEr+C5vfhezsb3hp4jAnCNRpPcCAwEAAQIgQNriSQK4',
227    'EFwczDhMZp2dvbcz7OUUyt36z3S4usFPHSECEQD/41K7SujrstBfoCPzwC1xAhEA+5kt4BJy',
228    'eKN7LggbF3Dk5wIQN6SL+fQ5H/+7NgARsVBp0QIRANxYRukavs4QvuyNhMx+vrkCEQCbf6j/',
229    'Ig6/HueCK/0Jkmp+',
230    '-----END RSA PRIVATE KEY-----',
231    '',
232  ].join('\n');
233  crypto.createSign('SHA256').update('test').sign(priv);
234}, (err) => {
235  if (!common.hasOpenSSL3)
236    assert.ok(!('opensslErrorStack' in err));
237  assert.throws(() => { throw err; }, common.hasOpenSSL3 ? {
238    name: 'Error',
239    message: 'error:02000070:rsa routines::digest too big for rsa key',
240    library: 'rsa routines',
241  } : {
242    name: 'Error',
243    message: /routines:RSA_sign:digest too big for rsa key$/,
244    library: 'rsa routines',
245    function: 'RSA_sign',
246    reason: 'digest too big for rsa key',
247    code: 'ERR_OSSL_RSA_DIGEST_TOO_BIG_FOR_RSA_KEY'
248  });
249  return true;
250});
251
252if (!common.hasOpenSSL3) {
253  assert.throws(() => {
254    // The correct header inside `rsa_private_pkcs8_bad.pem` should have been
255    // -----BEGIN PRIVATE KEY----- and -----END PRIVATE KEY-----
256    // instead of
257    // -----BEGIN RSA PRIVATE KEY----- and -----END RSA PRIVATE KEY-----
258    const sha1_privateKey = fixtures.readKey('rsa_private_pkcs8_bad.pem',
259                                             'ascii');
260    // This would inject errors onto OpenSSL's error stack
261    crypto.createSign('sha1').sign(sha1_privateKey);
262  }, (err) => {
263    // Do the standard checks, but then do some custom checks afterwards.
264    assert.throws(() => { throw err; }, {
265      message: 'error:0D0680A8:asn1 encoding routines:asn1_check_tlen:' +
266               'wrong tag',
267      library: 'asn1 encoding routines',
268      function: 'asn1_check_tlen',
269      reason: 'wrong tag',
270      code: 'ERR_OSSL_ASN1_WRONG_TAG',
271    });
272    // Throws crypto error, so there is an opensslErrorStack property.
273    // The openSSL stack should have content.
274    assert(Array.isArray(err.opensslErrorStack));
275    assert(err.opensslErrorStack.length > 0);
276    return true;
277  });
278}
279
280// Make sure memory isn't released before being returned
281console.log(crypto.randomBytes(16));
282
283assert.throws(() => {
284  tls.createSecureContext({ crl: 'not a CRL' });
285}, (err) => {
286  // Throws general error, so there is no opensslErrorStack property.
287  return err instanceof Error &&
288         /^Error: Failed to parse CRL$/.test(err) &&
289         !('opensslErrorStack' in err);
290});
291
292/**
293 * Check if the stream function uses utf8 as a default encoding.
294 */
295
296function testEncoding(options, assertionHash) {
297  const hash = crypto.createHash('sha256', options);
298  let hashValue = '';
299
300  hash.on('data', (data) => {
301    hashValue += data.toString('hex');
302  });
303
304  hash.on('end', common.mustCall(() => {
305    assert.strictEqual(hashValue, assertionHash);
306  }));
307
308  hash.write('öäü');
309  hash.end();
310}
311
312// Hash of "öäü" in utf8 format
313const assertionHashUtf8 =
314  '4f53d15bee524f082380e6d7247cc541e7cb0d10c64efdcc935ceeb1e7ea345c';
315
316// Hash of "öäü" in latin1 format
317const assertionHashLatin1 =
318  'cd37bccd5786e2e76d9b18c871e919e6eb11cc12d868f5ae41c40ccff8e44830';
319
320testEncoding(undefined, assertionHashUtf8);
321testEncoding({}, assertionHashUtf8);
322
323testEncoding({
324  defaultEncoding: 'utf8'
325}, assertionHashUtf8);
326
327testEncoding({
328  defaultEncoding: 'latin1'
329}, assertionHashLatin1);
330