1// Flags: --expose-internals 2'use strict'; 3const common = require('../common'); 4if (!common.hasCrypto) 5 common.skip('missing crypto'); 6 7const assert = require('assert'); 8const crypto = require('crypto'); 9 10const { internalBinding } = require('internal/test/binding'); 11if (typeof internalBinding('crypto').scrypt !== 'function') 12 common.skip('no scrypt support'); 13 14const good = [ 15 // Zero-length key is legal, functions as a parameter validation check. 16 { 17 pass: '', 18 salt: '', 19 keylen: 0, 20 N: 16, 21 p: 1, 22 r: 1, 23 expected: '', 24 }, 25 // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that 26 // should pass. Note that the test vector with N=1048576 is omitted 27 // because it takes too long to complete and uses over 1 GB of memory. 28 { 29 pass: '', 30 salt: '', 31 keylen: 64, 32 N: 16, 33 p: 1, 34 r: 1, 35 expected: 36 '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' + 37 'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906', 38 }, 39 { 40 pass: 'password', 41 salt: 'NaCl', 42 keylen: 64, 43 N: 1024, 44 p: 16, 45 r: 8, 46 expected: 47 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' + 48 '2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640', 49 }, 50 { 51 pass: 'pleaseletmein', 52 salt: 'SodiumChloride', 53 keylen: 64, 54 N: 16384, 55 p: 1, 56 r: 8, 57 expected: 58 '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' + 59 'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887', 60 }, 61 { 62 pass: '', 63 salt: '', 64 keylen: 64, 65 cost: 16, 66 parallelization: 1, 67 blockSize: 1, 68 expected: 69 '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' + 70 'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906', 71 }, 72 { 73 pass: 'password', 74 salt: 'NaCl', 75 keylen: 64, 76 cost: 1024, 77 parallelization: 16, 78 blockSize: 8, 79 expected: 80 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' + 81 '2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640', 82 }, 83 { 84 pass: 'pleaseletmein', 85 salt: 'SodiumChloride', 86 keylen: 64, 87 cost: 16384, 88 parallelization: 1, 89 blockSize: 8, 90 expected: 91 '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' + 92 'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887', 93 }, 94]; 95 96// Test vectors that should fail. 97const bad = [ 98 { N: 1, p: 1, r: 1 }, // N < 2 99 { N: 3, p: 1, r: 1 }, // Not power of 2. 100 { N: 1, cost: 1 }, // Both N and cost 101 { p: 1, parallelization: 1 }, // Both p and parallelization 102 { r: 1, blockSize: 1 }, // Both r and blocksize 103]; 104 105// Test vectors where 128*N*r exceeds maxmem. 106const toobig = [ 107 { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16) 108 { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r 109 { N: 2 ** 20, p: 1, r: 8 }, 110 { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 }, 111]; 112 113const badargs = [ 114 { 115 args: [], 116 expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ }, 117 }, 118 { 119 args: [null], 120 expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ }, 121 }, 122 { 123 args: [''], 124 expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ }, 125 }, 126 { 127 args: ['', null], 128 expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ }, 129 }, 130 { 131 args: ['', ''], 132 expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ }, 133 }, 134 { 135 args: ['', '', null], 136 expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ }, 137 }, 138 { 139 args: ['', '', .42], 140 expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, 141 }, 142 { 143 args: ['', '', -42], 144 expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, 145 }, 146]; 147 148for (const options of good) { 149 const { pass, salt, keylen, expected } = options; 150 const actual = crypto.scryptSync(pass, salt, keylen, options); 151 assert.strictEqual(actual.toString('hex'), expected); 152 crypto.scrypt(pass, salt, keylen, options, common.mustSucceed((actual) => { 153 assert.strictEqual(actual.toString('hex'), expected); 154 })); 155} 156 157for (const options of bad) { 158 const expected = { 159 code: 'ERR_CRYPTO_SCRYPT_INVALID_PARAMETER', 160 message: 'Invalid scrypt parameter', 161 name: 'Error', 162 }; 163 assert.throws(() => crypto.scrypt('pass', 'salt', 1, options, () => {}), 164 expected); 165 assert.throws(() => crypto.scryptSync('pass', 'salt', 1, options), 166 expected); 167} 168 169for (const options of toobig) { 170 const expected = { 171 message: new RegExp('error:[^:]+:digital envelope routines:' + 172 '(?:EVP_PBE_scrypt|scrypt_alg):memory limit exceeded'), 173 name: 'Error', 174 }; 175 assert.throws(() => crypto.scrypt('pass', 'salt', 1, options, () => {}), 176 expected); 177 assert.throws(() => crypto.scryptSync('pass', 'salt', 1, options), 178 expected); 179} 180 181{ 182 const defaults = { N: 16384, p: 1, r: 8 }; 183 const expected = crypto.scryptSync('pass', 'salt', 1, defaults); 184 const actual = crypto.scryptSync('pass', 'salt', 1); 185 assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex')); 186 crypto.scrypt('pass', 'salt', 1, common.mustSucceed((actual) => { 187 assert.deepStrictEqual(actual.toString('hex'), expected.toString('hex')); 188 })); 189} 190 191{ 192 const defaultEncoding = crypto.DEFAULT_ENCODING; 193 const defaults = { N: 16384, p: 1, r: 8 }; 194 const expected = crypto.scryptSync('pass', 'salt', 1, defaults); 195 196 const testEncoding = 'latin1'; 197 crypto.DEFAULT_ENCODING = testEncoding; 198 const actual = crypto.scryptSync('pass', 'salt', 1); 199 assert.deepStrictEqual(actual, expected.toString(testEncoding)); 200 201 crypto.scrypt('pass', 'salt', 1, common.mustSucceed((actual) => { 202 assert.deepStrictEqual(actual, expected.toString(testEncoding)); 203 })); 204 205 crypto.DEFAULT_ENCODING = defaultEncoding; 206} 207 208for (const { args, expected } of badargs) { 209 assert.throws(() => crypto.scrypt(...args), expected); 210 assert.throws(() => crypto.scryptSync(...args), expected); 211} 212 213{ 214 const expected = { code: 'ERR_INVALID_CALLBACK' }; 215 assert.throws(() => crypto.scrypt('', '', 42, null), expected); 216 assert.throws(() => crypto.scrypt('', '', 42, {}, null), expected); 217 assert.throws(() => crypto.scrypt('', '', 42, {}), expected); 218 assert.throws(() => crypto.scrypt('', '', 42, {}, {}), expected); 219} 220 221{ 222 // Values for maxmem that do not fit in 32 bits but that are still safe 223 // integers should be allowed. 224 crypto.scrypt('', '', 4, { maxmem: 2 ** 52 }, 225 common.mustSucceed((actual) => { 226 assert.strictEqual(actual.toString('hex'), 'd72c87d0'); 227 })); 228 229 // Values that exceed Number.isSafeInteger should not be allowed. 230 assert.throws(() => crypto.scryptSync('', '', 0, { maxmem: 2 ** 53 }), { 231 code: 'ERR_OUT_OF_RANGE' 232 }); 233} 234 235{ 236 // Regression test for https://github.com/nodejs/node/issues/28836. 237 238 function testParameter(name, value) { 239 let accessCount = 0; 240 241 // Find out how often the value is accessed. 242 crypto.scryptSync('', '', 1, { 243 get [name]() { 244 accessCount++; 245 return value; 246 } 247 }); 248 249 // Try to crash the process on the last access. 250 assert.throws(() => { 251 crypto.scryptSync('', '', 1, { 252 get [name]() { 253 if (--accessCount === 0) 254 return ''; 255 return value; 256 } 257 }); 258 }, { 259 code: 'ERR_INVALID_ARG_TYPE' 260 }); 261 } 262 263 [ 264 ['N', 16384], ['cost', 16384], 265 ['r', 8], ['blockSize', 8], 266 ['p', 1], ['parallelization', 1], 267 ].forEach((arg) => testParameter(...arg)); 268} 269