Skip to content
File

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

javascript284 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, kMaxLength } from 'node:buffer';
29import * as assert from 'node:assert';
30 
31import {
32 // createSecretKey,
33 hkdf,
34 hkdfSync,
35 getHashes,
36 createSecretKey,
37} from 'node:crypto';
38 
39export const hkdf_error_tests = {
40 async test(ctrl, env, ctx) {
41 assert.throws(() => hkdf(), {
42 code: 'ERR_INVALID_ARG_TYPE',
43 message: /The "digest" argument must be of type string/,
44 });
45 
46 [1, {}, [], false, Infinity].forEach((i) => {
47 assert.throws(() => hkdf(i, 'a'), {
48 code: 'ERR_INVALID_ARG_TYPE',
49 message: /^The "digest" argument must be of type string/,
50 });
51 assert.throws(() => hkdfSync(i, 'a'), {
52 code: 'ERR_INVALID_ARG_TYPE',
53 message: /^The "digest" argument must be of type string/,
54 });
55 });
56 
57 [1, {}, [], false, Infinity].forEach((i) => {
58 assert.throws(() => hkdf('sha256', i), {
59 code: 'ERR_INVALID_ARG_TYPE',
60 message: /^The "ikm" argument must be /,
61 });
62 assert.throws(() => hkdfSync('sha256', i), {
63 code: 'ERR_INVALID_ARG_TYPE',
64 message: /^The "ikm" argument must be /,
65 });
66 });
67 
68 [1, {}, [], false, Infinity].forEach((i) => {
69 assert.throws(() => hkdf('sha256', 'secret', i), {
70 code: 'ERR_INVALID_ARG_TYPE',
71 message: /^The "salt" argument must be /,
72 });
73 assert.throws(() => hkdfSync('sha256', 'secret', i), {
74 code: 'ERR_INVALID_ARG_TYPE',
75 message: /^The "salt" argument must be /,
76 });
77 });
78 
79 [1, {}, [], false, Infinity].forEach((i) => {
80 assert.throws(() => hkdf('sha256', 'secret', 'salt', i), {
81 code: 'ERR_INVALID_ARG_TYPE',
82 message: /^The "info" argument must be /,
83 });
84 assert.throws(() => hkdfSync('sha256', 'secret', 'salt', i), {
85 code: 'ERR_INVALID_ARG_TYPE',
86 message: /^The "info" argument must be /,
87 });
88 });
89 
90 ['test', {}, [], false].forEach((i) => {
91 assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', i), {
92 code: 'ERR_INVALID_ARG_TYPE',
93 message: /^The "length" argument must be of type number/,
94 });
95 assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', i), {
96 code: 'ERR_INVALID_ARG_TYPE',
97 message: /^The "length" argument must be of type number/,
98 });
99 });
100 
101 assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', -1), {
102 code: 'ERR_OUT_OF_RANGE',
103 });
104 assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', -1), {
105 code: 'ERR_OUT_OF_RANGE',
106 });
107 assert.throws(
108 () => hkdf('sha256', 'secret', 'salt', 'info', kMaxLength + 1),
109 {
110 code: 'ERR_OUT_OF_RANGE',
111 }
112 );
113 assert.throws(
114 () => hkdfSync('sha256', 'secret', 'salt', 'info', kMaxLength + 1),
115 {
116 code: 'ERR_OUT_OF_RANGE',
117 }
118 );
119 
120 {
121 const p = Promise.withResolvers();
122 hkdf('unknown', 'a', '', '', 10, (err, asyncResult) => {
123 if (err) {
124 return p.reject(err);
125 }
126 p.resolve();
127 });
128 await assert.rejects(p.promise);
129 }
130 assert.throws(() => hkdfSync('unknown', 'a', '', '', 10), {
131 name: 'TypeError',
132 });
133 
134 assert.throws(() => hkdf('unknown', 'a', '', Buffer.alloc(1025), 10), {
135 code: 'ERR_OUT_OF_RANGE',
136 });
137 assert.throws(() => hkdfSync('unknown', 'a', '', Buffer.alloc(1025), 10), {
138 code: 'ERR_OUT_OF_RANGE',
139 });
140 
141 {
142 const p = Promise.withResolvers();
143 hkdf('sha512', 'a', '', '', 64 * 255 + 1, (err, asyncResult) => {
144 if (err) {
145 return p.reject(err);
146 }
147 p.resolve();
148 });
149 await assert.rejects(p.promise);
150 }
151 assert.throws(() => hkdfSync('sha512', 'a', '', '', 64 * 255 + 1), {
152 name: 'RangeError',
153 });
154 },
155};
156 
157async function hkdfTestAlg([hash, secret, salt, info, length]) {
158 {
159 const syncResult = hkdfSync(hash, secret, salt, info, length);
160 assert.ok(syncResult instanceof ArrayBuffer);
161 let is_async = false;
162 const p = Promise.withResolvers();
163 
164 hkdf(hash, secret, salt, info, length, (err, asyncResult) => {
165 if (err) return p.reject(err);
166 assert.ok(is_async);
167 assert.ok(asyncResult instanceof ArrayBuffer);
168 assert.deepStrictEqual(syncResult, asyncResult);
169 p.resolve();
170 });
171 // Keep this after the hkdf call above. This verifies
172 // that the callback is invoked asynchronously.
173 is_async = true;
174 await p.promise;
175 }
176 
177 {
178 const buf_secret = Buffer.from(secret);
179 const buf_salt = Buffer.from(salt);
180 const buf_info = Buffer.from(info);
181 const p = Promise.withResolvers();
182 
183 const syncResult = hkdfSync(hash, buf_secret, buf_salt, buf_info, length);
184 hkdf(hash, buf_secret, buf_salt, buf_info, length, (err, asyncResult) => {
185 if (err) return p.reject(err);
186 assert.deepStrictEqual(syncResult, asyncResult);
187 p.resolve();
188 });
189 await p.promise;
190 }
191 
192 // Disabled for now, requires KeyObject support
193 // {
194 // const key_secret = createSecretKey(Buffer.from(secret));
195 // const buf_salt = Buffer.from(salt);
196 // const buf_info = Buffer.from(info);
197 // const p = Promise.withResolvers();
198 //
199 // const syncResult = hkdfSync(hash, key_secret, buf_salt, buf_info, length);
200 // hkdf(hash, key_secret, buf_salt, buf_info, length, (err, asyncResult) => {
201 // if (err) return p.reject(err);
202 // assert.deepStrictEqual(syncResult, asyncResult);
203 // p.resolve();
204 // });
205 // await p.promise;
206 // }
207 
208 {
209 const p = Promise.withResolvers();
210 const ta_secret = new Uint8Array(Buffer.from(secret));
211 const ta_salt = new Uint16Array(Buffer.from(salt));
212 const ta_info = new Uint32Array(Buffer.from(info));
213 
214 const syncResult = hkdfSync(hash, ta_secret, ta_salt, ta_info, length);
215 hkdf(hash, ta_secret, ta_salt, ta_info, length, (err, asyncResult) => {
216 if (err) return p.reject(err);
217 assert.deepStrictEqual(syncResult, asyncResult);
218 p.resolve();
219 });
220 await p.promise;
221 
222 const syncResultBuf = hkdfSync(
223 hash,
224 ta_secret.buffer,
225 ta_salt.buffer,
226 ta_info.buffer,
227 length
228 );
229 assert.deepStrictEqual(syncResult, syncResultBuf);
230 }
231 
232 {
233 const p = Promise.withResolvers();
234 const ta_secret = new Uint8Array(Buffer.from(secret));
235 const a_salt = new ArrayBuffer(0);
236 const a_info = new ArrayBuffer(1);
237 
238 const syncResult = hkdfSync(hash, ta_secret.buffer, a_salt, a_info, length);
239 hkdf(hash, ta_secret, a_salt, a_info, length, (err, asyncResult) => {
240 if (err) return p.reject(err);
241 assert.deepStrictEqual(syncResult, asyncResult);
242 p.resolve();
243 });
244 await p.promise;
245 }
246}
247 
248export const hkdf_correctness_tests = {
249 async test(ctrl, env, ctx) {
250 const algorithms = [
251 ['sha256', 'secret', 'salt', 'info', 10],
252 ['sha256', '', '', '', 10],
253 ['sha256', '', 'salt', '', 10],
254 ['sha512', 'secret', 'salt', '', 15],
255 ];
256 for (const element of algorithms) {
257 await hkdfTestAlg(element);
258 }
259 
260 getHashes().forEach((hash) => {
261 assert.ok(hkdfSync(hash, 'key', 'salt', 'info', 5));
262 });
263 },
264};
265 
266export const hkdf_secret_key = {
267 async test() {
268 const check = 'ae1db23b1051ea6bc1f182e5114d869c1b5e0b8d';
269 
270 const key = createSecretKey('abcdef01020304', 'hex');
271 const result = hkdfSync('sha256', key, 'hello', 'there', 20);
272 assert.strictEqual(Buffer.from(result).toString('hex'), check);
273 
274 const { promise, resolve, reject } = Promise.withResolvers();
275 
276 hkdf('sha256', key, 'hello', 'there', 20, (err, result) => {
277 if (err) return reject(err);
278 resolve(Buffer.from(result).toString('hex'));
279 });
280 
281 assert.strictEqual(await promise, check);
282 },
283};