Skip to content

Commit

Permalink
crypto: allow zero-length IKM in HKDF
Browse files Browse the repository at this point in the history
  • Loading branch information
panva committed Aug 10, 2022
1 parent a16ebe8 commit f0bdeaa
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 1,898 deletions.
11 changes: 9 additions & 2 deletions doc/api/crypto.md
Original file line number Diff line number Diff line change
Expand Up @@ -4209,6 +4209,9 @@ web-compatible code use [`crypto.webcrypto.getRandomValues()`][] instead.
<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: REPLACEME
description: The input keying material can now be zero-length.
- version: v18.0.0
pr-url: https://github.com/nodejs/node/pull/41678
description: Passing an invalid callback to the `callback` argument
Expand All @@ -4218,7 +4221,7 @@ changes:

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

<!-- YAML
added: v15.0.0
changes:
- version: REPLACEME
pr-url: REPLACEME
description: The input keying material can now be zero-length.
-->

* `digest` {string} The digest algorithm to use.
* `ikm` {string|ArrayBuffer|Buffer|TypedArray|DataView|KeyObject} The input
keying material. It must be at least one byte in length.
keying material. Must be provided but can be zero-length.
* `salt` {string|ArrayBuffer|Buffer|TypedArray|DataView} The salt value. Must
be provided but can be zero-length.
* `info` {string|ArrayBuffer|Buffer|TypedArray|DataView} Additional info value.
Expand Down
3 changes: 0 additions & 3 deletions lib/internal/crypto/mac.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,6 @@ async function hmacImportKey(
case 'raw': {
const checkLength = keyData.byteLength * 8;

if (checkLength === 0 || algorithm.length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');

// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
Expand Down
3 changes: 0 additions & 3 deletions lib/internal/crypto/webcrypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -494,9 +494,6 @@ async function importGenericSecretKey(

const checkLength = keyData.byteLength * 8;

if (checkLength === 0 || length === 0)
throw lazyDOMException('Zero-length key is not supported', 'DataError');

// The Web Crypto spec allows for key lengths that are not multiples of
// 8. We don't. Our check here is stricter than that defined by the spec
// in that we require that algorithm.length match keyData.length * 8 if
Expand Down
54 changes: 46 additions & 8 deletions src/crypto/crypto_hkdf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,58 @@ bool HKDFTraits::DeriveBits(
EVPKeyCtxPointer ctx =
EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr));
if (!ctx || !EVP_PKEY_derive_init(ctx.get()) ||
!EVP_PKEY_CTX_hkdf_mode(ctx.get(),
EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) ||
!EVP_PKEY_CTX_set_hkdf_md(ctx.get(), params.digest) ||
!EVP_PKEY_CTX_set1_hkdf_salt(
ctx.get(), params.salt.data<unsigned char>(), params.salt.size()) ||
!EVP_PKEY_CTX_set1_hkdf_key(
ctx.get(),
reinterpret_cast<const unsigned char*>(params.key->GetSymmetricKey()),
params.key->GetSymmetricKeySize()) ||
!EVP_PKEY_CTX_add1_hkdf_info(
ctx.get(), params.info.data<unsigned char>(), params.info.size())) {
return false;
}

// TODO(panva): Once support for OpenSSL 1.1.1 is dropped the whole
// of HKDFTraits::DeriveBits can be refactored to use
// EVP_KDF which does handle zero length key.
if (params.key->GetSymmetricKeySize() != 0) {
if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(),
EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND) ||
!EVP_PKEY_CTX_set1_hkdf_salt(
ctx.get(), params.salt.data<unsigned char>(), params.salt.size()) ||
!EVP_PKEY_CTX_set1_hkdf_key(ctx.get(),
reinterpret_cast<const unsigned char*>(
params.key->GetSymmetricKey()),
params.key->GetSymmetricKeySize())) {
return false;
}
} else {
// Workaround for EVP_PKEY_derive HKDF not handling zero length keys.
unsigned char temp_key[EVP_MAX_MD_SIZE];
unsigned int len = sizeof(temp_key);
if (params.salt.size() != 0) {
if (HMAC(params.digest,
params.salt.data(),
params.salt.size(),
nullptr,
0,
temp_key,
&len) == nullptr) {
return false;
}
} else {
char salt[EVP_MAX_MD_SIZE] = {0};
if (HMAC(params.digest,
salt,
EVP_MD_size(params.digest),
nullptr,
0,
temp_key,
&len) == nullptr) {
return false;
}
}
if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) ||
!EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), temp_key, len)) {
return false;
}
}

size_t length = params.length;
ByteSource::Builder buf(length);
if (EVP_PKEY_derive(ctx.get(), buf.data<unsigned char>(), &length) <= 0)
Expand Down
9 changes: 0 additions & 9 deletions test/parallel/test-webcrypto-export-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,6 @@ const { subtle } = webcrypto;
name: 'SyntaxError',
message: 'Unsupported key usage for an HMAC key'
});
await assert.rejects(
subtle.importKey('raw', keyData, {
name: 'HMAC',
hash: 'SHA-256',
length: 0
}, false, ['sign', 'verify']), {
name: 'DataError',
message: 'Zero-length key is not supported'
});
await assert.rejects(
subtle.importKey('raw', keyData, {
name: 'HMAC',
Expand Down
Loading

0 comments on commit f0bdeaa

Please sign in to comment.