File
Blob: src/workerd/api/node/tests/crypto_scrypt-test.js
| 1 | // Copyright (c) 2017-2022 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | // |
| 5 | // Adapted from Node.js. Copyright Joyent, Inc. and other Node contributors. |
| 6 | // |
| 7 | // Permission is hereby granted, free of charge, to any person obtaining a |
| 8 | // copy of this software and associated documentation files (the |
| 9 | // "Software"), to deal in the Software without restriction, including |
| 10 | // without limitation the rights to use, copy, modify, merge, publish, |
| 11 | // distribute, sublicense, and/or sell copies of the Software, and to permit |
| 12 | // persons to whom the Software is furnished to do so, subject to the |
| 13 | // following conditions: |
| 14 | // |
| 15 | // The above copyright notice and this permission notice shall be included |
| 16 | // in all copies or substantial portions of the Software. |
| 17 | // |
| 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| 19 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| 20 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| 21 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| 22 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| 23 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| 24 | // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| 25 | |
| 26 | 'use strict'; |
| 27 | |
| 28 | import { strictEqual, throws, rejects } from 'node:assert'; |
| 29 | |
| 30 | import { scrypt, scryptSync } from 'node:crypto'; |
| 31 | |
| 32 | import { mock } from 'node:test'; |
| 33 | |
| 34 | const good = [ |
| 35 | // Zero-length key is legal, functions as a parameter validation check. |
| 36 | { |
| 37 | pass: '', |
| 38 | salt: '', |
| 39 | keylen: 0, |
| 40 | N: 16, |
| 41 | p: 1, |
| 42 | r: 1, |
| 43 | expected: '', |
| 44 | }, |
| 45 | // Test vectors from https://tools.ietf.org/html/rfc7914#page-13 that |
| 46 | // should pass. Note that the test vector with N=1048576 is omitted |
| 47 | // because it takes too long to complete and uses over 1 GiB of memory. |
| 48 | { |
| 49 | pass: '', |
| 50 | salt: '', |
| 51 | keylen: 64, |
| 52 | N: 16, |
| 53 | p: 1, |
| 54 | r: 1, |
| 55 | expected: |
| 56 | '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' + |
| 57 | 'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906', |
| 58 | }, |
| 59 | { |
| 60 | pass: 'password', |
| 61 | salt: 'NaCl', |
| 62 | keylen: 64, |
| 63 | N: 1024, |
| 64 | p: 16, |
| 65 | r: 8, |
| 66 | expected: |
| 67 | 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' + |
| 68 | '2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640', |
| 69 | }, |
| 70 | { |
| 71 | pass: 'pleaseletmein', |
| 72 | salt: 'SodiumChloride', |
| 73 | keylen: 64, |
| 74 | N: 16384, |
| 75 | p: 1, |
| 76 | r: 8, |
| 77 | expected: |
| 78 | '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' + |
| 79 | 'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887', |
| 80 | }, |
| 81 | { |
| 82 | pass: '', |
| 83 | salt: '', |
| 84 | keylen: 64, |
| 85 | cost: 16, |
| 86 | parallelization: 1, |
| 87 | blockSize: 1, |
| 88 | expected: |
| 89 | '77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442' + |
| 90 | 'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906', |
| 91 | }, |
| 92 | { |
| 93 | pass: 'password', |
| 94 | salt: 'NaCl', |
| 95 | keylen: 64, |
| 96 | cost: 1024, |
| 97 | parallelization: 16, |
| 98 | blockSize: 8, |
| 99 | expected: |
| 100 | 'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162' + |
| 101 | '2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640', |
| 102 | }, |
| 103 | { |
| 104 | pass: 'pleaseletmein', |
| 105 | salt: 'SodiumChloride', |
| 106 | keylen: 64, |
| 107 | cost: 16384, |
| 108 | parallelization: 1, |
| 109 | blockSize: 8, |
| 110 | expected: |
| 111 | '7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2' + |
| 112 | 'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887', |
| 113 | }, |
| 114 | ]; |
| 115 | |
| 116 | // Test vectors that should fail. |
| 117 | const bad = [ |
| 118 | { N: 1, p: 1, r: 1 }, // N < 2 |
| 119 | { N: 3, p: 1, r: 1 }, // Not power of 2. |
| 120 | ]; |
| 121 | |
| 122 | // Test vectors where 128*N*r exceeds maxmem. |
| 123 | const toobig = [ |
| 124 | { N: 2 ** 16, p: 1, r: 1 }, // N >= 2**(r*16) |
| 125 | { N: 2, p: 2 ** 30, r: 1 }, // p > (2**30-1)/r |
| 126 | { N: 2 ** 20, p: 1, r: 8 }, |
| 127 | { N: 2 ** 10, p: 1, r: 8, maxmem: 2 ** 20 }, |
| 128 | ]; |
| 129 | |
| 130 | const badargs = [ |
| 131 | { |
| 132 | args: [], |
| 133 | expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ }, |
| 134 | }, |
| 135 | { |
| 136 | args: [null], |
| 137 | expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"password"/ }, |
| 138 | }, |
| 139 | { |
| 140 | args: [''], |
| 141 | expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ }, |
| 142 | }, |
| 143 | { |
| 144 | args: ['', null], |
| 145 | expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"salt"/ }, |
| 146 | }, |
| 147 | { |
| 148 | args: ['', ''], |
| 149 | expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ }, |
| 150 | }, |
| 151 | { |
| 152 | args: ['', '', null], |
| 153 | expected: { code: 'ERR_INVALID_ARG_TYPE', message: /"keylen"/ }, |
| 154 | }, |
| 155 | { |
| 156 | args: ['', '', 0.42], |
| 157 | expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, |
| 158 | }, |
| 159 | { |
| 160 | args: ['', '', -42], |
| 161 | expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, |
| 162 | }, |
| 163 | { |
| 164 | args: ['', '', 2 ** 31], |
| 165 | expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, |
| 166 | }, |
| 167 | { |
| 168 | args: ['', '', 2147485780], |
| 169 | expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, |
| 170 | }, |
| 171 | { |
| 172 | args: ['', '', 2 ** 32], |
| 173 | expected: { code: 'ERR_OUT_OF_RANGE', message: /"keylen"/ }, |
| 174 | }, |
| 175 | ]; |
| 176 | |
| 177 | export const goodTests = { |
| 178 | async test() { |
| 179 | for (const options of good) { |
| 180 | const { pass, salt, keylen, expected } = options; |
| 181 | const actual = scryptSync(pass, salt, keylen, options); |
| 182 | strictEqual(actual.toString('hex'), expected); |
| 183 | const { promise, resolve } = Promise.withResolvers(); |
| 184 | const fn = mock.fn((err, actual) => { |
| 185 | strictEqual(actual.toString('hex'), expected); |
| 186 | resolve(); |
| 187 | }); |
| 188 | scrypt(pass, salt, keylen, options, fn); |
| 189 | await promise; |
| 190 | strictEqual(fn.mock.calls.length, 1); |
| 191 | } |
| 192 | }, |
| 193 | }; |
| 194 | |
| 195 | export const badTests = { |
| 196 | async test() { |
| 197 | for (const options of bad) { |
| 198 | const { promise, reject } = Promise.withResolvers(); |
| 199 | const fn = mock.fn((err, actual) => { |
| 200 | if (err) reject(err); |
| 201 | }); |
| 202 | scrypt('pass', 'salt', 1, options, fn); |
| 203 | await rejects(promise); |
| 204 | throws(() => scryptSync('pass', 'salt', 1, options)); |
| 205 | } |
| 206 | throws(() => scryptSync('pass', 'salt', 1, { N: 1, cost: 1 })); |
| 207 | throws(() => scryptSync('pass', 'salt', 1, { p: 1, parallelization: 1 })); |
| 208 | throws(() => scryptSync('pass', 'salt', 1, { r: 1, blockSize: 1 })); |
| 209 | }, |
| 210 | }; |
| 211 | |
| 212 | export const tooBigTests = { |
| 213 | async test() { |
| 214 | for (const options of toobig) { |
| 215 | const { promise, reject } = Promise.withResolvers(); |
| 216 | const fn = mock.fn((err, actual) => { |
| 217 | if (err) reject(err); |
| 218 | }); |
| 219 | scrypt('pass', 'salt', 1, options, fn); |
| 220 | await rejects(promise); |
| 221 | strictEqual(fn.mock.calls.length, 1); |
| 222 | |
| 223 | throws(() => scryptSync('pass', 'salt', 1, options)); |
| 224 | } |
| 225 | }, |
| 226 | }; |
| 227 | |
| 228 | export const defaultsTest = { |
| 229 | async test() { |
| 230 | const defaults = { N: 16384, p: 1, r: 8 }; |
| 231 | const expected = scryptSync('pass', 'salt', 1, defaults); |
| 232 | const actual = scryptSync('pass', 'salt', 1); |
| 233 | strictEqual(actual.toString('hex'), expected.toString('hex')); |
| 234 | const { promise, resolve } = Promise.withResolvers(); |
| 235 | const fn = mock.fn((err, actual) => { |
| 236 | strictEqual(actual.toString('hex'), expected.toString('hex')); |
| 237 | resolve(); |
| 238 | }); |
| 239 | scrypt('pass', 'salt', 1, fn); |
| 240 | await promise; |
| 241 | strictEqual(fn.mock.calls.length, 1); |
| 242 | }, |
| 243 | }; |
| 244 | |
| 245 | export const badArgsTest = { |
| 246 | test() { |
| 247 | for (const { args, expected: _expected } of badargs) { |
| 248 | throws(() => scrypt(...args)); |
| 249 | throws(() => scryptSync(...args)); |
| 250 | } |
| 251 | |
| 252 | throws(() => scrypt('', '', 42, null)); |
| 253 | throws(() => scrypt('', '', 42, {}, null)); |
| 254 | throws(() => scrypt('', '', 42, {})); |
| 255 | throws(() => scrypt('', '', 42, {}, {})); |
| 256 | }, |
| 257 | }; |