Skip to content
File

Blob: src/workerd/api/node/tests/crypto_scrypt-test.js

javascript258 lines
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 
28import { strictEqual, throws, rejects } from 'node:assert';
29 
30import { scrypt, scryptSync } from 'node:crypto';
31 
32import { mock } from 'node:test';
33 
34const 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.
117const 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.
123const 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 
130const 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 
177export 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 
195export 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 
212export 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 
228export 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 
245export 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};