File
Blob: src/workerd/api/node/tests/crypto_hkdf-test.js
| 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 | |
| 28 | import { Buffer, kMaxLength } from 'node:buffer'; |
| 29 | import * as assert from 'node:assert'; |
| 30 | |
| 31 | import { |
| 32 | // createSecretKey, |
| 33 | hkdf, |
| 34 | hkdfSync, |
| 35 | getHashes, |
| 36 | createSecretKey, |
| 37 | } from 'node:crypto'; |
| 38 | |
| 39 | export 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 | |
| 157 | async 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 | |
| 248 | export 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 | |
| 266 | export 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 | }; |