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