Skip to content
File

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

javascript345 lines
1// Copyright (c) 2017-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
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 { Buffer } from 'node:buffer';
29import * as assert from 'node:assert';
30import * as crypto from 'node:crypto';
31import * as stream from 'node:stream';
32 
33let cryptoType;
34let digest;
35 
36export const hash_correctness_tests = {
37 async test(ctrl, env, ctx) {
38 const a1 = crypto.createHash('sha1').update('Test123').digest('hex');
39 const a2 = crypto.createHash('sha256').update('Test123').digest('base64');
40 const a3 = crypto.createHash('sha512').update('Test123').digest(); // buffer
41 const a4 = crypto.createHash('sha1').update('Test123').digest('buffer');
42 
43 // stream interface
44 let a5 = crypto.createHash('sha512');
45 a5.end('Test123');
46 a5 = a5.read();
47 
48 let a6 = crypto.createHash('sha512');
49 a6.write('Te');
50 a6.write('st');
51 a6.write('123');
52 a6.end();
53 a6 = a6.read();
54 
55 let a7 = crypto.createHash('sha512');
56 a7.end();
57 a7 = a7.read();
58 
59 let a8 = crypto.createHash('sha512');
60 a8.write('');
61 a8.end();
62 a8 = a8.read();
63 
64 cryptoType = 'md5';
65 digest = 'latin1';
66 const a0 = crypto.createHash(cryptoType).update('Test123').digest(digest);
67 assert.strictEqual(
68 a0,
69 'h\u00ea\u00cb\u0097\u00d8o\fF!\u00fa+\u000e\u0017\u00ca\u00bd\u008c',
70 `${cryptoType} with ${digest} digest failed to evaluate to expected hash`
71 );
72 
73 cryptoType = 'md5';
74 digest = 'hex';
75 assert.strictEqual(
76 a1,
77 '8308651804facb7b9af8ffc53a33a22d6a1c8ac2',
78 `${cryptoType} with ${digest} digest failed to evaluate to expected hash`
79 );
80 cryptoType = 'sha256';
81 digest = 'base64';
82 assert.strictEqual(
83 a2,
84 '2bX1jws4GYKTlxhloUB09Z66PoJZW+y+hq5R8dnx9l4=',
85 `${cryptoType} with ${digest} digest failed to evaluate to expected hash`
86 );
87 
88 cryptoType = 'sha512';
89 digest = 'latin1';
90 assert.deepStrictEqual(
91 a3,
92 Buffer.from(
93 "\u00c1(4\u00f1\u0003\u001fd\u0097!O'\u00d4C/&Qz\u00d4" +
94 '\u0094\u0015l\u00b8\u008dQ+\u00db\u001d\u00c4\u00b5}\u00b2' +
95 '\u00d6\u0092\u00a3\u00df\u00a2i\u00a1\u009b\n\n*\u000f' +
96 '\u00d7\u00d6\u00a2\u00a8\u0085\u00e3<\u0083\u009c\u0093' +
97 "\u00c2\u0006\u00da0\u00a1\u00879(G\u00ed'",
98 'latin1'
99 ),
100 `${cryptoType} with ${digest} digest failed to evaluate to expected hash`
101 );
102 
103 cryptoType = 'sha1';
104 digest = 'hex';
105 assert.deepStrictEqual(
106 a4,
107 Buffer.from('8308651804facb7b9af8ffc53a33a22d6a1c8ac2', 'hex'),
108 `${cryptoType} with ${digest} digest failed to evaluate to expected hash`
109 );
110 
111 cryptoType = 'sha1';
112 digest = 'hex';
113 assert.deepStrictEqual(
114 a4,
115 Buffer.from('8308651804FACB7B9AF8FFC53A33A22D6A1C8AC2', 'hex'),
116 `${cryptoType} with ${digest} digest failed to evaluate to expected hash`
117 );
118 
119 // Stream interface should produce the same result.
120 assert.deepStrictEqual(a5, a3);
121 assert.deepStrictEqual(a6, a3);
122 assert.notStrictEqual(a7, undefined);
123 assert.notStrictEqual(a8, undefined);
124 
125 // Test multiple updates to same hash
126 const h1 = crypto.createHash('sha1').update('Test123').digest('hex');
127 const h2 = crypto
128 .createHash('sha1')
129 .update('Test')
130 .update('123')
131 .digest('hex');
132 assert.strictEqual(h1, h2);
133 },
134};
135 
136export const hash_error_test = {
137 async test(ctrl, env, ctx) {
138 // Issue https://github.com/nodejs/node-v0.x-archive/issues/2227: unknown digest
139 // method should throw an error.
140 assert.throws(function () {
141 crypto.createHash('xyzzy');
142 }, /Digest method not supported/);
143 
144 // Issue https://github.com/nodejs/node/issues/9819: throwing encoding used to
145 // segfault.
146 assert.throws(
147 () =>
148 crypto.createHash('sha256').digest({
149 toString: () => {
150 throw new Error('boom');
151 },
152 }),
153 {
154 name: 'Error',
155 message: 'boom',
156 }
157 );
158 
159 // Issue https://github.com/nodejs/node/issues/25487: error message for invalid
160 // arg type to update method should include all possible types
161 assert.throws(() => crypto.createHash('sha256').update(), {
162 code: 'ERR_INVALID_ARG_TYPE',
163 name: 'TypeError',
164 });
165 
166 // Default UTF-8 encoding
167 const hutf8 = crypto
168 .createHash('sha512')
169 .update('УТФ-8 text')
170 .digest('hex');
171 assert.strictEqual(
172 hutf8,
173 '4b21bbd1a68e690a730ddcb5a8bc94ead9879ffe82580767ad7ec6fa8ba2dea6' +
174 '43a821af66afa9a45b6a78c712fecf0e56dc7f43aef4bcfc8eb5b4d8dca6ea5b'
175 );
176 
177 assert.notStrictEqual(
178 hutf8,
179 crypto.createHash('sha512').update('УТФ-8 text', 'latin1').digest('hex')
180 );
181 
182 const h3 = crypto.createHash('sha256');
183 h3.digest();
184 
185 assert.throws(() => h3.digest(), {
186 code: 'ERR_CRYPTO_HASH_FINALIZED',
187 name: 'Error',
188 });
189 
190 assert.throws(() => h3.update('foo'), {
191 code: 'ERR_CRYPTO_HASH_FINALIZED',
192 name: 'Error',
193 });
194 
195 assert.strictEqual(
196 crypto.createHash('sha256').update('test').digest('ucs2'),
197 crypto.createHash('sha256').update('test').digest().toString('ucs2')
198 );
199 
200 assert.throws(() => crypto.createHash(), {
201 code: 'ERR_INVALID_ARG_TYPE',
202 name: 'TypeError',
203 message:
204 'The "algorithm" argument must be of type string. ' +
205 'Received undefined',
206 });
207 
208 {
209 const Hash = crypto.Hash;
210 const instance = crypto.Hash('sha256');
211 assert.ok(
212 instance instanceof Hash,
213 'Hash is expected to return a new instance' +
214 ' when called without `new`'
215 );
216 }
217 
218 // shake*-based tests for XOF hash function are not supported in FIPS and have been removed.
219 {
220 // Non-XOF hash functions should accept valid outputLength options as well.
221 assert.strictEqual(
222 crypto.createHash('sha224', { outputLength: 28 }).digest('hex'),
223 'd14a028c2a3a2bc9476102bb288234c4' + '15a2b01f828ea62ac5b3e42f'
224 );
225 
226 // Passing invalid sizes should throw during creation.
227 assert.throws(
228 () => {
229 crypto.createHash('sha256', { outputLength: 28 });
230 },
231 {
232 name: 'Error',
233 }
234 );
235 
236 for (const outputLength of [null, {}, 'foo', false]) {
237 assert.throws(() => crypto.createHash('sha256', { outputLength }), {
238 code: 'ERR_INVALID_ARG_TYPE',
239 });
240 }
241 
242 for (const outputLength of [-1, 0.5, Infinity, 2 ** 90]) {
243 assert.throws(() => crypto.createHash('sha256', { outputLength }), {
244 code: 'ERR_OUT_OF_RANGE',
245 });
246 }
247 }
248 },
249};
250 
251export const hash_copy_test = {
252 async test(ctrl, env, ctx) {
253 const h = crypto.createHash('sha512');
254 h.digest();
255 assert.throws(() => h.copy(), { code: 'ERR_CRYPTO_HASH_FINALIZED' });
256 assert.throws(() => h.digest(), { code: 'ERR_CRYPTO_HASH_FINALIZED' });
257 
258 const a = crypto.createHash('sha512').update('abc');
259 const b = a.copy();
260 const c = b.copy().update('def');
261 const d = crypto.createHash('sha512').update('abcdef');
262 assert.strictEqual(a.digest('hex'), b.digest('hex'));
263 assert.strictEqual(c.digest('hex'), d.digest('hex'));
264 },
265};
266 
267export const hash_pipe_test = {
268 async test(ctrl, env, ctx) {
269 const p = Promise.withResolvers();
270 
271 const s = new stream.PassThrough();
272 const h = crypto.createHash('sha512');
273 const expect =
274 'fba055c6fd0c5b6645407749ed7a8b41' +
275 'b8f629f2163c3ca3701d864adabda1f8' +
276 '93c37bf82b22fdd151ba8e357f611da4' +
277 '88a74b6a5525dd9b69554c6ce5138ad7';
278 
279 s.pipe(h)
280 .on('data', function (c) {
281 // Calling digest() after piping into a stream with SHA3 should not cause
282 // a segmentation fault, see https://github.com/nodejs/node/issues/28245.
283 if (c !== expect || h.digest('hex') !== expect) {
284 p.reject('Unexpected value for stream-based hash');
285 }
286 p.resolve();
287 })
288 .setEncoding('hex');
289 
290 s.end('aoeu');
291 await p.promise;
292 },
293};
294 
295export const hash_one_shot = {
296 test() {
297 assert.strictEqual(
298 crypto.hash('sha1', 'Node.js'),
299 '10b3493287f831e81a438811a1ffba01f8cec4b7'
300 );
301 
302 const check = Buffer.from(
303 '10b3493287f831e81a438811a1ffba01f8cec4b7',
304 'hex'
305 );
306 const base64 = 'Tm9kZS5qcw==';
307 assert.deepStrictEqual(
308 crypto.hash('sha1', Buffer.from(base64, 'base64'), 'buffer'),
309 check
310 );
311 
312 assert.throws(() => crypto.hash(1), {
313 code: 'ERR_INVALID_ARG_TYPE',
314 });
315 assert.throws(() => crypto.hash('sha1', 12), {
316 code: 'ERR_INVALID_ARG_TYPE',
317 });
318 assert.throws(() => crypto.hash('sha1', 'test', 123), {
319 code: 'ERR_INVALID_ARG_TYPE',
320 });
321 assert.throws(() => crypto.hash('unknown', 'what'), {
322 message: /Digest method not supported/,
323 });
324 },
325};
326 
327export const HashCopyAfterDigest = {
328 async test() {
329 for (let i = 0; i < 50; i++) {
330 const hash = crypto.createHash('sha256');
331 hash.update('x' + i);
332 
333 const kHandle = Object.getOwnPropertySymbols(hash).find(
334 (s) => s.toString() === 'Symbol(kHandle)'
335 );
336 const handle = hash[kHandle];
337 handle.digest();
338 
339 assert.throws(() => hash.copy(), {
340 message: /already been finalized/,
341 });
342 }
343 },
344};