Skip to content
File

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

javascript464 lines
1// Copyright (c) 2023 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
4import { ok, rejects, strictEqual, throws } from 'node:assert';
5 
6import {
7 generatePrime,
8 generatePrimeSync,
9 checkPrime,
10 checkPrimeSync,
11 timingSafeEqual,
12} from 'node:crypto';
13 
14import { Buffer } from 'node:buffer';
15 
16export const test = {
17 async test(ctrl, env, ctx) {
18 [1, 'hello', {}, []].forEach((i) => {
19 throws(() => checkPrimeSync(i), {
20 code: 'ERR_INVALID_ARG_TYPE',
21 });
22 });
23 
24 // prettier-ignore
25 for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) {
26 throws(() => checkPrimeSync(2n, { checks }), {
27 code: 'ERR_OUT_OF_RANGE',
28 message: /<= 2147483647/
29 });
30 }
31 
32 ok(
33 !checkPrimeSync(Buffer.from([0x1]), {
34 fast: true,
35 trialDivision: true,
36 checks: 10,
37 })
38 );
39 
40 ok(!checkPrimeSync(Buffer.from([0x1])));
41 ok(checkPrimeSync(Buffer.from([0x2])));
42 ok(checkPrimeSync(Buffer.from([0x3])));
43 ok(!checkPrimeSync(Buffer.from([0x4])));
44 
45 ////////////////
46 
47 ['hello', false, 123].forEach((i) => {
48 throws(() => generatePrimeSync(80, i), {
49 code: 'ERR_INVALID_ARG_TYPE',
50 });
51 });
52 
53 for (const checks of ['hello', {}, []]) {
54 throws(() => checkPrimeSync(2n, { checks }), {
55 code: 'ERR_INVALID_ARG_TYPE',
56 message: /checks/,
57 });
58 }
59 
60 // //////////////////
61 
62 ['hello', false, {}, []].forEach((i) => {
63 throws(() => generatePrime(i), {
64 code: 'ERR_INVALID_ARG_TYPE',
65 });
66 throws(() => generatePrimeSync(i), {
67 code: 'ERR_INVALID_ARG_TYPE',
68 });
69 });
70 
71 ['hello', false, 123].forEach((i) => {
72 throws(() => generatePrime(80, i, {}), {
73 code: 'ERR_INVALID_ARG_TYPE',
74 });
75 throws(() => generatePrimeSync(80, i), {
76 code: 'ERR_INVALID_ARG_TYPE',
77 });
78 });
79 
80 ['hello', false, 123].forEach((i) => {
81 throws(() => generatePrime(80, {}), {
82 code: 'ERR_INVALID_ARG_TYPE',
83 });
84 });
85 
86 [-1, 0, 2 ** 31, 2 ** 31 + 1, 2 ** 32 - 1, 2 ** 32].forEach((size) => {
87 throws(() => generatePrime(-1), {
88 code: 'ERR_OUT_OF_RANGE',
89 message: />= 1 && <= 2147483647/,
90 });
91 throws(() => generatePrimeSync(size), {
92 code: 'ERR_OUT_OF_RANGE',
93 message: />= 1 && <= 2147483647/,
94 });
95 });
96 
97 // TODO: Fix and enable asynchronous tests
98 ['test', -1, {}, []].forEach((i) => {
99 throws(() => generatePrime(8, { safe: i }, () => {}), {
100 code: 'ERR_INVALID_ARG_TYPE',
101 });
102 throws(() => generatePrime(8, { rem: i }, () => {}), {
103 code: 'ERR_INVALID_ARG_TYPE',
104 });
105 throws(() => generatePrime(8, { add: i }, () => {}), {
106 code: 'ERR_INVALID_ARG_TYPE',
107 });
108 throws(() => generatePrimeSync(8, { safe: i }), {
109 code: 'ERR_INVALID_ARG_TYPE',
110 });
111 throws(() => generatePrimeSync(8, { rem: i }), {
112 code: 'ERR_INVALID_ARG_TYPE',
113 });
114 throws(() => generatePrimeSync(8, { add: i }), {
115 code: 'ERR_INVALID_ARG_TYPE',
116 });
117 });
118 
119 {
120 // Negative BigInts should not be converted to 0 silently.
121 throws(() => generatePrime(20, { add: -1n }, () => {}), {
122 code: 'ERR_OUT_OF_RANGE',
123 });
124 
125 throws(() => generatePrime(20, { rem: -1n }, () => {}), {
126 code: 'ERR_OUT_OF_RANGE',
127 });
128 throws(() => checkPrime(-1n, () => {}), {
129 code: 'ERR_OUT_OF_RANGE',
130 });
131 }
132 
133 {
134 const p = Promise.withResolvers();
135 generatePrime(80, (err, prime) => {
136 ok(checkPrimeSync(prime));
137 checkPrime(prime, (err, result) => {
138 ok(result);
139 p.resolve();
140 });
141 });
142 await p.promise;
143 }
144 
145 ok(checkPrimeSync(generatePrimeSync(80)));
146 
147 {
148 const p = Promise.withResolvers();
149 generatePrime(80, {}, (err, prime) => {
150 if (err) return p.reject(err);
151 ok(checkPrimeSync(prime));
152 p.resolve();
153 });
154 await p.promise;
155 }
156 
157 ok(checkPrimeSync(generatePrimeSync(80, {})));
158 
159 {
160 const p = Promise.withResolvers();
161 generatePrime(32, { safe: true }, (err, prime) => {
162 if (err) return p.reject(err);
163 ok(checkPrimeSync(prime));
164 const buf = Buffer.from(prime);
165 const val = buf.readUInt32BE();
166 const check = (val - 1) / 2;
167 buf.writeUInt32BE(check);
168 ok(checkPrimeSync(buf));
169 p.resolve();
170 });
171 await p.promise;
172 }
173 
174 {
175 const prime = generatePrimeSync(32, { safe: true });
176 ok(checkPrimeSync(prime));
177 const buf = Buffer.from(prime);
178 const val = buf.readUInt32BE();
179 const check = (val - 1) / 2;
180 buf.writeUInt32BE(check);
181 ok(checkPrimeSync(buf));
182 }
183 
184 const add = 12;
185 const rem = 11;
186 const add_buf = Buffer.from([add]);
187 const rem_buf = Buffer.from([rem]);
188 
189 {
190 const p = Promise.withResolvers();
191 generatePrime(32, { add: add_buf, rem: rem_buf }, (err, prime) => {
192 if (err) return p.reject(err);
193 ok(checkPrimeSync(prime));
194 const buf = Buffer.from(prime);
195 const val = buf.readUInt32BE();
196 strictEqual(val % add, rem);
197 p.resolve();
198 });
199 await p.promise;
200 }
201 
202 {
203 const prime = generatePrimeSync(32, { add: add_buf, rem: rem_buf });
204 ok(checkPrimeSync(prime));
205 const buf = Buffer.from(prime);
206 const val = buf.readUInt32BE();
207 strictEqual(val % add, rem);
208 }
209 
210 {
211 const prime = generatePrimeSync(32, {
212 add: BigInt(add),
213 rem: BigInt(rem),
214 });
215 ok(checkPrimeSync(prime));
216 const buf = Buffer.from(prime);
217 const val = buf.readUInt32BE();
218 strictEqual(val % add, rem);
219 }
220 
221 {
222 const p = Promise.withResolvers();
223 generatePrime(
224 128,
225 {
226 bigint: true,
227 add: 5n,
228 },
229 (err, prime) => {
230 // Fails because the add option is not a supported value
231 if (err) return p.reject(err);
232 }
233 );
234 await rejects(p.promise);
235 }
236 {
237 const p = Promise.withResolvers();
238 generatePrime(
239 128,
240 {
241 bigint: true,
242 safe: true,
243 add: 5n,
244 },
245 (err, prime) => {
246 // Fails because the add option is not a supported value
247 if (err) return p.reject(err);
248 }
249 );
250 await rejects(p.promise);
251 }
252 
253 // This is impossible because it implies (prime % 2**64) == 1 and
254 // prime < 2**64, meaning prime = 1, but 1 is not prime.
255 for (const add of [2n ** 64n, 2n ** 65n]) {
256 throws(
257 () => {
258 generatePrimeSync(64, { add });
259 },
260 {
261 name: 'RangeError',
262 }
263 );
264 }
265 
266 // Any parameters with rem >= add lead to an impossible condition.
267 for (const rem of [7n, 8n, 3000n]) {
268 throws(
269 () => {
270 generatePrimeSync(64, { add: 7n, rem });
271 },
272 {
273 name: 'RangeError',
274 }
275 );
276 }
277 
278 // This is possible, but not allowed. It implies prime == 7, which means that
279 // we did not actually generate a random prime.
280 throws(
281 () => {
282 generatePrimeSync(3, { add: 8n, rem: 7n });
283 },
284 {
285 name: 'RangeError',
286 }
287 );
288 
289 // We only allow specific values of add and rem
290 throws(
291 () =>
292 generatePrimeSync(8, {
293 add: 7n,
294 rem: 1n,
295 }),
296 {
297 name: 'RangeError',
298 }
299 );
300 throws(
301 () =>
302 generatePrimeSync(8, {
303 add: 12n,
304 rem: 10n,
305 }),
306 {
307 name: 'RangeError',
308 }
309 );
310 throws(
311 () =>
312 generatePrimeSync(8, {
313 add: 12n,
314 }),
315 {
316 name: 'RangeError',
317 }
318 );
319 
320 [1, 'hello', {}, []].forEach((i) => {
321 throws(() => checkPrime(i), {
322 code: 'ERR_INVALID_ARG_TYPE',
323 });
324 });
325 
326 for (const checks of ['hello', {}, []]) {
327 throws(() => checkPrime(2n, { checks }, () => {}), {
328 code: 'ERR_INVALID_ARG_TYPE',
329 message: /checks/,
330 });
331 throws(() => checkPrimeSync(2n, { checks }), {
332 code: 'ERR_INVALID_ARG_TYPE',
333 message: /checks/,
334 });
335 }
336 
337 // prettier-ignore
338 for (const checks of [-(2 ** 31), -1, 2 ** 31, 2 ** 32 - 1, 2 ** 32, 2 ** 50]) {
339 throws(() => checkPrime(2n, { checks }, () => {}), {
340 code: 'ERR_OUT_OF_RANGE',
341 message: /<= 2147483647/
342 });
343 throws(() => checkPrimeSync(2n, { checks }), {
344 code: 'ERR_OUT_OF_RANGE',
345 message: /<= 2147483647/
346 });
347 }
348 
349 ok(
350 !checkPrimeSync(Buffer.from([0x1]), {
351 fast: true,
352 trialDivision: true,
353 checks: 10,
354 })
355 );
356 
357 throws(
358 () => {
359 generatePrimeSync(32, { bigint: '' });
360 },
361 { code: 'ERR_INVALID_ARG_TYPE' }
362 );
363 
364 throws(
365 () => {
366 generatePrime(32, { bigint: '' }, () => {});
367 },
368 { code: 'ERR_INVALID_ARG_TYPE' }
369 );
370 
371 {
372 const prime = generatePrimeSync(3, { bigint: true });
373 strictEqual(typeof prime, 'bigint');
374 strictEqual(prime, 7n);
375 ok(checkPrimeSync(prime));
376 const p = Promise.withResolvers();
377 checkPrime(prime, (err, result) => {
378 if (err) return p.reject(err);
379 p.resolve(result);
380 });
381 await p.promise;
382 }
383 
384 {
385 const p = Promise.withResolvers();
386 generatePrime(3, { bigint: true }, (err, prime) => {
387 if (err) return p.reject(err);
388 strictEqual(typeof prime, 'bigint');
389 strictEqual(prime, 7n);
390 ok(checkPrimeSync(prime));
391 checkPrime(prime, (err, result) => {
392 if (err) return p.reject(err);
393 p.resolve(result);
394 });
395 });
396 await p.promise;
397 }
398 },
399};
400 
401export const timingSafeEqualTest = {
402 test() {
403 timingSafeEqual(new Uint8Array(1), new Uint8Array(1));
404 },
405};
406 
407export const randomIntTest = {
408 async test() {
409 const { randomInt } = await import('node:crypto');
410 
411 // min === max should throw, not infinite-loop
412 throws(() => randomInt(5, 5), { code: 'ERR_OUT_OF_RANGE' });
413 
414 // min > max should throw
415 throws(() => randomInt(10, 5), { code: 'ERR_OUT_OF_RANGE' });
416 
417 // Valid range returns value in [min, max)
418 const val = randomInt(0, 10);
419 ok(val >= 0 && val < 10);
420 
421 // Single-arg form: randomInt(max) means [0, max)
422 strictEqual(randomInt(1), 0);
423 },
424};
425 
426export const randomFillSyncTest = {
427 async test() {
428 const { randomFillSync } = await import('node:crypto');
429 
430 // With offset but no size, should fill only buf.length - offset bytes
431 const buf = Buffer.alloc(10, 0);
432 randomFillSync(buf, 4);
433 
434 // First 4 bytes must be untouched (all zero)
435 for (let i = 0; i < 4; i++) {
436 strictEqual(buf[i], 0, `byte ${i} should be untouched`);
437 }
438 },
439};
440 
441// Ref: https://github.com/cloudflare/workerd/issues/2716
442export const getRandomValuesIllegalInvocation = {
443 async test() {
444 const crypto = await import('node:crypto');
445 {
446 // The following assertion doesn't fail as of Node.js v20.17.0
447 const getRandomValues = crypto.getRandomValues;
448 strictEqual(getRandomValues(new Uint8Array(6)).length, 6);
449 }
450 throws(
451 () => {
452 // This ensures that we are replicating Node.js behavior as of v20.17.0
453 const getRandomValues = crypto.webcrypto.getRandomValues;
454 strictEqual(getRandomValues(new Uint8Array(6)).length, 6);
455 },
456 {
457 name: 'TypeError',
458 message: /Illegal invocation/,
459 }
460 );
461 strictEqual(crypto.getRandomValues(new Uint8Array(6)).length, 6);
462 },
463};