mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
fix(ext/node): implement hkdf-expand (#18612)
Towards https://github.com/denoland/deno/issues/18455
This commit is contained in:
parent
2d0a9ffbcc
commit
df72420d72
8 changed files with 287 additions and 8 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1108,6 +1108,7 @@ dependencies = [
|
|||
"digest 0.10.6",
|
||||
"ecb",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"idna 0.3.0",
|
||||
"indexmap",
|
||||
"libz-sys",
|
||||
|
|
|
@ -138,6 +138,7 @@ zstd = "=0.11.2"
|
|||
|
||||
# crypto
|
||||
rsa = { version = "0.7.0", default-features = false, features = ["std", "pem", "hazmat"] } # hazmat needed for PrehashSigner in ext/node
|
||||
hkdf = "0.12.3"
|
||||
|
||||
# macros
|
||||
proc-macro2 = "1"
|
||||
|
|
|
@ -228,6 +228,7 @@
|
|||
"test-console-sync-write-error.js",
|
||||
"test-console-table.js",
|
||||
"test-console-tty-colors.js",
|
||||
"test-crypto-hkdf.js",
|
||||
"test-crypto-hmac.js",
|
||||
"test-crypto-prime.js",
|
||||
"test-crypto-secret-keygen.js",
|
||||
|
|
203
cli/tests/node_compat/test/parallel/test-crypto-hkdf.js
Normal file
203
cli/tests/node_compat/test/parallel/test-crypto-hkdf.js
Normal file
|
@ -0,0 +1,203 @@
|
|||
// deno-fmt-ignore-file
|
||||
// deno-lint-ignore-file
|
||||
|
||||
// Copyright Joyent and Node contributors. All rights reserved. MIT license.
|
||||
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
|
||||
if (!common.hasCrypto)
|
||||
common.skip('missing crypto');
|
||||
|
||||
const { kMaxLength } = require('buffer');
|
||||
const assert = require('assert');
|
||||
const {
|
||||
createSecretKey,
|
||||
hkdf,
|
||||
hkdfSync
|
||||
} = require('crypto');
|
||||
|
||||
{
|
||||
assert.throws(() => hkdf(), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /The "digest" argument must be of type string/
|
||||
});
|
||||
|
||||
[1, {}, [], false, Infinity].forEach((i) => {
|
||||
assert.throws(() => hkdf(i, 'a'), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "digest" argument must be of type string/
|
||||
});
|
||||
assert.throws(() => hkdfSync(i, 'a'), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "digest" argument must be of type string/
|
||||
});
|
||||
});
|
||||
|
||||
[1, {}, [], false, Infinity].forEach((i) => {
|
||||
assert.throws(() => hkdf('sha256', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "ikm" argument must be /
|
||||
});
|
||||
assert.throws(() => hkdfSync('sha256', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "ikm" argument must be /
|
||||
});
|
||||
});
|
||||
|
||||
[1, {}, [], false, Infinity].forEach((i) => {
|
||||
assert.throws(() => hkdf('sha256', 'secret', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "salt" argument must be /
|
||||
});
|
||||
assert.throws(() => hkdfSync('sha256', 'secret', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "salt" argument must be /
|
||||
});
|
||||
});
|
||||
|
||||
[1, {}, [], false, Infinity].forEach((i) => {
|
||||
assert.throws(() => hkdf('sha256', 'secret', 'salt', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "info" argument must be /
|
||||
});
|
||||
assert.throws(() => hkdfSync('sha256', 'secret', 'salt', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "info" argument must be /
|
||||
});
|
||||
});
|
||||
|
||||
['test', {}, [], false].forEach((i) => {
|
||||
assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "length" argument must be of type number/
|
||||
});
|
||||
assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', i), {
|
||||
code: 'ERR_INVALID_ARG_TYPE',
|
||||
message: /^The "length" argument must be of type number/
|
||||
});
|
||||
});
|
||||
|
||||
assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info', -1), {
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
});
|
||||
assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info', -1), {
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
});
|
||||
assert.throws(() => hkdf('sha256', 'secret', 'salt', 'info',
|
||||
kMaxLength + 1), {
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
});
|
||||
assert.throws(() => hkdfSync('sha256', 'secret', 'salt', 'info',
|
||||
kMaxLength + 1), {
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
});
|
||||
|
||||
assert.throws(() => hkdfSync('unknown', 'a', '', '', 10), {
|
||||
code: 'ERR_CRYPTO_INVALID_DIGEST'
|
||||
});
|
||||
|
||||
assert.throws(() => hkdf('unknown', 'a', '', Buffer.alloc(1025), 10,
|
||||
common.mustNotCall()), {
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
});
|
||||
|
||||
assert.throws(() => hkdfSync('unknown', 'a', '', Buffer.alloc(1025), 10), {
|
||||
code: 'ERR_OUT_OF_RANGE'
|
||||
});
|
||||
}
|
||||
|
||||
const algorithms = [
|
||||
['sha256', 'secret', 'salt', 'info', 10],
|
||||
['sha256', '', '', '', 10],
|
||||
['sha256', '', 'salt', '', 10],
|
||||
['sha512', 'secret', 'salt', '', 15],
|
||||
];
|
||||
|
||||
algorithms.forEach(([ hash, secret, salt, info, length ]) => {
|
||||
{
|
||||
const syncResult = hkdfSync(hash, secret, salt, info, length);
|
||||
assert(syncResult instanceof ArrayBuffer);
|
||||
let is_async = false;
|
||||
hkdf(hash, secret, salt, info, length,
|
||||
common.mustSucceed((asyncResult) => {
|
||||
assert(is_async);
|
||||
assert(asyncResult instanceof ArrayBuffer);
|
||||
assert.deepStrictEqual(syncResult, asyncResult);
|
||||
}));
|
||||
// Keep this after the hkdf call above. This verifies
|
||||
// that the callback is invoked asynchronously.
|
||||
is_async = true;
|
||||
}
|
||||
|
||||
{
|
||||
const buf_secret = Buffer.from(secret);
|
||||
const buf_salt = Buffer.from(salt);
|
||||
const buf_info = Buffer.from(info);
|
||||
|
||||
const syncResult = hkdfSync(hash, buf_secret, buf_salt, buf_info, length);
|
||||
hkdf(hash, buf_secret, buf_salt, buf_info, length,
|
||||
common.mustSucceed((asyncResult) => {
|
||||
assert.deepStrictEqual(syncResult, asyncResult);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const key_secret = createSecretKey(Buffer.from(secret));
|
||||
const buf_salt = Buffer.from(salt);
|
||||
const buf_info = Buffer.from(info);
|
||||
|
||||
const syncResult = hkdfSync(hash, key_secret, buf_salt, buf_info, length);
|
||||
hkdf(hash, key_secret, buf_salt, buf_info, length,
|
||||
common.mustSucceed((asyncResult) => {
|
||||
assert.deepStrictEqual(syncResult, asyncResult);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const ta_secret = new Uint8Array(Buffer.from(secret));
|
||||
const ta_salt = new Uint16Array(Buffer.from(salt));
|
||||
const ta_info = new Uint32Array(Buffer.from(info));
|
||||
|
||||
const syncResult = hkdfSync(hash, ta_secret, ta_salt, ta_info, length);
|
||||
hkdf(hash, ta_secret, ta_salt, ta_info, length,
|
||||
common.mustSucceed((asyncResult) => {
|
||||
assert.deepStrictEqual(syncResult, asyncResult);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const ta_secret = new Uint8Array(Buffer.from(secret));
|
||||
const ta_salt = new Uint16Array(Buffer.from(salt));
|
||||
const ta_info = new Uint32Array(Buffer.from(info));
|
||||
|
||||
const syncResult = hkdfSync(
|
||||
hash,
|
||||
ta_secret.buffer,
|
||||
ta_salt.buffer,
|
||||
ta_info.buffer,
|
||||
length);
|
||||
hkdf(hash, ta_secret, ta_salt, ta_info, length,
|
||||
common.mustSucceed((asyncResult) => {
|
||||
assert.deepStrictEqual(syncResult, asyncResult);
|
||||
}));
|
||||
}
|
||||
|
||||
{
|
||||
const ta_secret = new Uint8Array(Buffer.from(secret));
|
||||
const sa_salt = new ArrayBuffer(0);
|
||||
const sa_info = new ArrayBuffer(1);
|
||||
|
||||
const syncResult = hkdfSync(
|
||||
hash,
|
||||
ta_secret.buffer,
|
||||
sa_salt,
|
||||
sa_info,
|
||||
length);
|
||||
hkdf(hash, ta_secret, sa_salt, sa_info, length,
|
||||
common.mustSucceed((asyncResult) => {
|
||||
assert.deepStrictEqual(syncResult, asyncResult);
|
||||
}));
|
||||
}
|
||||
});
|
|
@ -20,6 +20,7 @@ deno_core.workspace = true
|
|||
digest = { version = "0.10.5", features = ["core-api", "std"] }
|
||||
ecb.workspace = true
|
||||
hex.workspace = true
|
||||
hkdf.workspace = true
|
||||
idna = "0.3.0"
|
||||
indexmap.workspace = true
|
||||
libz-sys = { version = "1.1.8", features = ["static"] }
|
||||
|
|
|
@ -7,6 +7,7 @@ use deno_core::OpState;
|
|||
use deno_core::ResourceId;
|
||||
use deno_core::StringOrBuffer;
|
||||
use deno_core::ZeroCopyBuf;
|
||||
use hkdf::Hkdf;
|
||||
use num_bigint::BigInt;
|
||||
use rand::Rng;
|
||||
use std::future::Future;
|
||||
|
@ -419,3 +420,60 @@ pub async fn op_node_generate_secret_async(len: i32) -> ZeroCopyBuf {
|
|||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn hkdf_sync(
|
||||
hash: &str,
|
||||
ikm: &[u8],
|
||||
salt: &[u8],
|
||||
info: &[u8],
|
||||
okm: &mut [u8],
|
||||
) -> Result<(), AnyError> {
|
||||
macro_rules! hkdf {
|
||||
($hash:ty) => {{
|
||||
let hk = Hkdf::<$hash>::new(Some(salt), ikm);
|
||||
hk.expand(info, okm)
|
||||
.map_err(|_| type_error("HKDF-Expand failed"))?;
|
||||
}};
|
||||
}
|
||||
|
||||
match hash {
|
||||
"md4" => hkdf!(md4::Md4),
|
||||
"md5" => hkdf!(md5::Md5),
|
||||
"ripemd160" => hkdf!(ripemd::Ripemd160),
|
||||
"sha1" => hkdf!(sha1::Sha1),
|
||||
"sha224" => hkdf!(sha2::Sha224),
|
||||
"sha256" => hkdf!(sha2::Sha256),
|
||||
"sha384" => hkdf!(sha2::Sha384),
|
||||
"sha512" => hkdf!(sha2::Sha512),
|
||||
_ => return Err(type_error("Unknown digest")),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub fn op_node_hkdf(
|
||||
hash: &str,
|
||||
ikm: &[u8],
|
||||
salt: &[u8],
|
||||
info: &[u8],
|
||||
okm: &mut [u8],
|
||||
) -> Result<(), AnyError> {
|
||||
hkdf_sync(hash, ikm, salt, info, okm)
|
||||
}
|
||||
|
||||
#[op]
|
||||
pub async fn op_node_hkdf_async(
|
||||
hash: String,
|
||||
ikm: ZeroCopyBuf,
|
||||
salt: ZeroCopyBuf,
|
||||
info: ZeroCopyBuf,
|
||||
okm_len: usize,
|
||||
) -> Result<ZeroCopyBuf, AnyError> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let mut okm = vec![0u8; okm_len];
|
||||
hkdf_sync(&hash, &ikm, &salt, &info, &mut okm)?;
|
||||
Ok(okm.into())
|
||||
})
|
||||
.await?
|
||||
}
|
||||
|
|
|
@ -189,6 +189,8 @@ deno_core::extension!(deno_node,
|
|||
crypto::op_node_check_prime_bytes_async,
|
||||
crypto::op_node_pbkdf2,
|
||||
crypto::op_node_pbkdf2_async,
|
||||
crypto::op_node_hkdf,
|
||||
crypto::op_node_hkdf_async,
|
||||
crypto::op_node_generate_secret,
|
||||
crypto::op_node_generate_secret_async,
|
||||
crypto::op_node_sign,
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
validateString,
|
||||
} from "ext:deno_node/internal/validators.mjs";
|
||||
import {
|
||||
ERR_CRYPTO_INVALID_DIGEST,
|
||||
ERR_INVALID_ARG_TYPE,
|
||||
ERR_OUT_OF_RANGE,
|
||||
hideStackFrames,
|
||||
|
@ -26,17 +27,19 @@ import {
|
|||
isAnyArrayBuffer,
|
||||
isArrayBufferView,
|
||||
} from "ext:deno_node/internal/util/types.ts";
|
||||
import { notImplemented } from "ext:deno_node/_utils.ts";
|
||||
|
||||
const { core } = globalThis.__bootstrap;
|
||||
const { ops } = core;
|
||||
|
||||
const validateParameters = hideStackFrames((hash, key, salt, info, length) => {
|
||||
key = prepareKey(key);
|
||||
salt = toBuf(salt);
|
||||
info = toBuf(info);
|
||||
|
||||
validateString(hash, "digest");
|
||||
key = new Uint8Array(prepareKey(key));
|
||||
validateByteSource(salt, "salt");
|
||||
validateByteSource(info, "info");
|
||||
|
||||
salt = new Uint8Array(toBuf(salt));
|
||||
info = new Uint8Array(toBuf(info));
|
||||
|
||||
validateInteger(length, "length", 0, kMaxLength);
|
||||
|
||||
if (info.byteLength > 1024) {
|
||||
|
@ -91,7 +94,7 @@ export function hkdf(
|
|||
salt: BinaryLike,
|
||||
info: BinaryLike,
|
||||
length: number,
|
||||
callback: (err: Error | null, derivedKey: ArrayBuffer) => void,
|
||||
callback: (err: Error | null, derivedKey: ArrayBuffer | undefined) => void,
|
||||
) {
|
||||
({ hash, key, salt, info, length } = validateParameters(
|
||||
hash,
|
||||
|
@ -103,7 +106,9 @@ export function hkdf(
|
|||
|
||||
validateFunction(callback, "callback");
|
||||
|
||||
notImplemented("crypto.hkdf");
|
||||
core.opAsync("op_node_hkdf_async", hash, key, salt, info, length)
|
||||
.then((okm) => callback(null, okm.buffer))
|
||||
.catch((err) => callback(new ERR_CRYPTO_INVALID_DIGEST(err), undefined));
|
||||
}
|
||||
|
||||
export function hkdfSync(
|
||||
|
@ -121,7 +126,14 @@ export function hkdfSync(
|
|||
length,
|
||||
));
|
||||
|
||||
notImplemented("crypto.hkdfSync");
|
||||
const okm = new Uint8Array(length);
|
||||
try {
|
||||
ops.op_node_hkdf(hash, key, salt, info, okm);
|
||||
} catch (e) {
|
||||
throw new ERR_CRYPTO_INVALID_DIGEST(e);
|
||||
}
|
||||
|
||||
return okm.buffer;
|
||||
}
|
||||
|
||||
export default {
|
||||
|
|
Loading…
Reference in a new issue