Skip to content

Commit de3fc6c

Browse files
committed
crypto: allow zero-length IKM in HKDF
1 parent 0c1bd3f commit de3fc6c

File tree

4 files changed

+56
-1886
lines changed

4 files changed

+56
-1886
lines changed

doc/api/crypto.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4209,6 +4209,9 @@ web-compatible code use [`crypto.webcrypto.getRandomValues()`][] instead.
42094209
<!-- YAML
42104210
added: v15.0.0
42114211
changes:
4212+
- version: REPLACEME
4213+
pr-url: https://github.com/nodejs/node/pull/44201
4214+
description: The input keying material can now be zero-length.
42124215
- version: v18.0.0
42134216
pr-url: https://github.com/nodejs/node/pull/41678
42144217
description: Passing an invalid callback to the `callback` argument
@@ -4218,7 +4221,7 @@ changes:
42184221

42194222
* `digest` {string} The digest algorithm to use.
42204223
* `ikm` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject} The input
4221-
keying material. It must be at least one byte in length.
4224+
keying material. Must be provided but can be zero-length.
42224225
* `salt` {string|ArrayBuffer|Buffer|TypedArray|DataView} The salt value. Must
42234226
be provided but can be zero-length.
42244227
* `info` {string|ArrayBuffer|Buffer|TypedArray|DataView} Additional info value.
@@ -4268,11 +4271,15 @@ hkdf('sha512', 'key', 'salt', 'info', 64, (err, derivedKey) => {
42684271

42694272
<!-- YAML
42704273
added: v15.0.0
4274+
changes:
4275+
- version: REPLACEME
4276+
pr-url: https://github.com/nodejs/node/pull/44201
4277+
description: The input keying material can now be zero-length.
42714278
-->
42724279

42734280
* `digest` {string} The digest algorithm to use.
42744281
* `ikm` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject} The input
4275-
keying material. It must be at least one byte in length.
4282+
keying material. Must be provided but can be zero-length.
42764283
* `salt` {string|ArrayBuffer|Buffer|TypedArray|DataView} The salt value. Must
42774284
be provided but can be zero-length.
42784285
* `info` {string|ArrayBuffer|Buffer|TypedArray|DataView} Additional info value.

lib/internal/crypto/webcrypto.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -494,9 +494,6 @@ async function importGenericSecretKey(
494494

495495
const checkLength = keyData.byteLength * 8;
496496

497-
if (checkLength === 0 || length === 0)
498-
throw lazyDOMException('Zero-length key is not supported', 'DataError');
499-
500497
// The Web Crypto spec allows for key lengths that are not multiples of
501498
// 8. We don't. Our check here is stricter than that defined by the spec
502499
// in that we require that algorithm.length match keyData.length * 8 if

src/crypto/crypto_hkdf.cc

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,20 +103,58 @@ bool HKDFTraits::DeriveBits(
103103
EVPKeyCtxPointer ctx =
104104
EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr));
105105
if (!ctx || !EVP_PKEY_derive_init(ctx.get()) ||
106-
!EVP_PKEY_CTX_hkdf_mode(ctx.get(),
107-
EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) ||
108106
!EVP_PKEY_CTX_set_hkdf_md(ctx.get(), params.digest) ||
109-
!EVP_PKEY_CTX_set1_hkdf_salt(
110-
ctx.get(), params.salt.data<unsigned char>(), params.salt.size()) ||
111-
!EVP_PKEY_CTX_set1_hkdf_key(
112-
ctx.get(),
113-
reinterpret_cast<const unsigned char*>(params.key->GetSymmetricKey()),
114-
params.key->GetSymmetricKeySize()) ||
115107
!EVP_PKEY_CTX_add1_hkdf_info(
116108
ctx.get(), params.info.data<unsigned char>(), params.info.size())) {
117109
return false;
118110
}
119111

112+
// TODO(panva): Once support for OpenSSL 1.1.1 is dropped the whole
113+
// of HKDFTraits::DeriveBits can be refactored to use
114+
// EVP_KDF which does handle zero length key.
115+
if (params.key->GetSymmetricKeySize() != 0) {
116+
if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(),
117+
EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) ||
118+
!EVP_PKEY_CTX_set1_hkdf_salt(
119+
ctx.get(), params.salt.data<unsigned char>(), params.salt.size()) ||
120+
!EVP_PKEY_CTX_set1_hkdf_key(ctx.get(),
121+
reinterpret_cast<const unsigned char*>(
122+
params.key->GetSymmetricKey()),
123+
params.key->GetSymmetricKeySize())) {
124+
return false;
125+
}
126+
} else {
127+
// Workaround for EVP_PKEY_derive HKDF not handling zero length keys.
128+
unsigned char temp_key[EVP_MAX_MD_SIZE];
129+
unsigned int len = sizeof(temp_key);
130+
if (params.salt.size() != 0) {
131+
if (HMAC(params.digest,
132+
params.salt.data(),
133+
params.salt.size(),
134+
nullptr,
135+
0,
136+
temp_key,
137+
&len) == nullptr) {
138+
return false;
139+
}
140+
} else {
141+
char salt[EVP_MAX_MD_SIZE] = {0};
142+
if (HMAC(params.digest,
143+
salt,
144+
EVP_MD_size(params.digest),
145+
nullptr,
146+
0,
147+
temp_key,
148+
&len) == nullptr) {
149+
return false;
150+
}
151+
}
152+
if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) ||
153+
!EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), temp_key, len)) {
154+
return false;
155+
}
156+
}
157+
120158
size_t length = params.length;
121159
ByteSource::Builder buf(length);
122160
if (EVP_PKEY_derive(ctx.get(), buf.data<unsigned char>(), &length) <= 0)

0 commit comments

Comments
 (0)