diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 2a02ae79e4e30c..3a94ca19682073 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #if OPENSSL_VERSION_MAJOR >= 3 @@ -1252,4 +1253,82 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey, return out; } +// ============================================================================ +// HKDF + +const EVP_MD* getDigestByName(const std::string_view name) { + return EVP_get_digestbyname(name.data()); +} + +bool checkHkdfLength(const EVP_MD* md, size_t length) { + // HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as + // the output of the hash function. 255 is a hard limit because HKDF appends + // an 8-bit counter to each HMAC'd message, starting at 1. + static constexpr size_t kMaxDigestMultiplier = 255; + size_t max_length = EVP_MD_size(md) * kMaxDigestMultiplier; + if (length > max_length) return false; + return true; +} + +DataPointer hkdf(const EVP_MD* md, + const Buffer& key, + const Buffer& info, + const Buffer& salt, + size_t length) { + if (!checkHkdfLength(md, length) || + info.len > INT_MAX || + salt.len > INT_MAX) { + return {}; + } + + EVPKeyCtxPointer ctx = + EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr)); + if (!ctx || + !EVP_PKEY_derive_init(ctx.get()) || + !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), md) || + !EVP_PKEY_CTX_add1_hkdf_info(ctx.get(), info.data, info.len)) { + return {}; + } + + std::string_view actual_salt; + static const char default_salt[EVP_MAX_MD_SIZE] = {0}; + if (salt.len > 0) { + actual_salt = {reinterpret_cast(salt.data), salt.len}; + } else { + actual_salt = {default_salt, static_cast(EVP_MD_size(md))}; + } + + // We do not use EVP_PKEY_HKDF_MODE_EXTRACT_AND_EXPAND because and instead + // implement the extraction step ourselves because EVP_PKEY_derive does not + // handle zero-length keys, which are required for Web Crypto. + // TODO: Once OpenSSL 1.1.1 support is dropped completely, and once BoringSSL + // is confirmed to support it, wen can hopefully drop this and use EVP_KDF + // directly which does support zero length keys. + unsigned char pseudorandom_key[EVP_MAX_MD_SIZE]; + unsigned pseudorandom_key_len = sizeof(pseudorandom_key); + + if (HMAC(md, + actual_salt.data(), + actual_salt.size(), + key.data, + key.len, + pseudorandom_key, + &pseudorandom_key_len) == nullptr) { + return {}; + } + if (!EVP_PKEY_CTX_hkdf_mode(ctx.get(), EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) || + !EVP_PKEY_CTX_set1_hkdf_key(ctx.get(), pseudorandom_key, pseudorandom_key_len)) { + return {}; + } + + auto buf = DataPointer::Alloc(length); + if (!buf) return {}; + + if (EVP_PKEY_derive(ctx.get(), static_cast(buf.get()), &length) <= 0) { + return {}; + } + + return buf; +} + } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 9b6aecaaecd6f9..9808503c62cb3f 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -588,6 +588,22 @@ BIOPointer ExportPublicKey(const char* input, size_t length); // The caller takes ownership of the returned Buffer Buffer ExportChallenge(const char* input, size_t length); +// ============================================================================ +// HKDF + +const EVP_MD* getDigestByName(const std::string_view name); + +// Verify that the specified HKDF output length is valid for the given digest. +// The maximum length for HKDF output for a given digest is 255 times the +// hash size for the given digest algorithm. +bool checkHkdfLength(const EVP_MD* md, size_t length); + +DataPointer hkdf(const EVP_MD* md, + const Buffer& key, + const Buffer& info, + const Buffer& salt, + size_t length); + // ============================================================================ // Version metadata #define NCRYPTO_VERSION "0.0.1" diff --git a/src/crypto/crypto_hkdf.cc b/src/crypto/crypto_hkdf.cc index 0dd9b42473ca73..9990f11edc6e13 100644 --- a/src/crypto/crypto_hkdf.cc +++ b/src/crypto/crypto_hkdf.cc @@ -56,7 +56,7 @@ Maybe HKDFTraits::AdditionalConfig( CHECK(args[offset + 4]->IsUint32()); // Length Utf8Value hash(env->isolate(), args[offset]); - params->digest = EVP_get_digestbyname(*hash); + params->digest = ncrypto::getDigestByName(hash.ToStringView()); if (params->digest == nullptr) { THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *hash); return Nothing(); @@ -90,9 +90,7 @@ Maybe HKDFTraits::AdditionalConfig( // HKDF-Expand computes up to 255 HMAC blocks, each having as many bits as the // output of the hash function. 255 is a hard limit because HKDF appends an // 8-bit counter to each HMAC'd message, starting at 1. - constexpr size_t kMaxDigestMultiplier = 255; - size_t max_length = EVP_MD_size(params->digest) * kMaxDigestMultiplier; - if (params->length > max_length) { + if (!ncrypto::checkHkdfLength(params->digest, params->length)) { THROW_ERR_CRYPTO_INVALID_KEYLEN(env); return Nothing(); } @@ -104,53 +102,24 @@ bool HKDFTraits::DeriveBits( Environment* env, const HKDFConfig& params, ByteSource* out) { - EVPKeyCtxPointer ctx = - EVPKeyCtxPointer(EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, nullptr)); - if (!ctx || !EVP_PKEY_derive_init(ctx.get()) || - !EVP_PKEY_CTX_set_hkdf_md(ctx.get(), params.digest) || - !EVP_PKEY_CTX_add1_hkdf_info( - ctx.get(), params.info.data(), 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. - - std::string_view salt; - if (params.salt.size() != 0) { - salt = {params.salt.data(), params.salt.size()}; - } else { - static const char default_salt[EVP_MAX_MD_SIZE] = {0}; - salt = {default_salt, static_cast(EVP_MD_size(params.digest))}; - } - - // We do not use EVP_PKEY_HKDEF_MODE_EXTRACT_AND_EXPAND and instead implement - // the extraction step ourselves because EVP_PKEY_derive does not handle - // zero-length keys, which are required for Web Crypto. - unsigned char pseudorandom_key[EVP_MAX_MD_SIZE]; - unsigned int prk_len = sizeof(pseudorandom_key); - if (HMAC( - params.digest, - salt.data(), - salt.size(), - reinterpret_cast(params.key->GetSymmetricKey()), - params.key->GetSymmetricKeySize(), - pseudorandom_key, - &prk_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(), pseudorandom_key, prk_len)) { - return false; - } - - size_t length = params.length; - ByteSource::Builder buf(length); - if (EVP_PKEY_derive(ctx.get(), buf.data(), &length) <= 0) - return false; - - *out = std::move(buf).release(); + auto dp = ncrypto::hkdf( + params.digest, + ncrypto::Buffer { + .data = reinterpret_cast(params.key->GetSymmetricKey()), + .len = params.key->GetSymmetricKeySize(), + }, + ncrypto::Buffer { + .data = params.info.data(), + .len = params.info.size(), + }, + ncrypto::Buffer { + .data = params.salt.data(), + .len = params.salt.size(), + }, + params.length); + if (!dp) return false; + + *out = ByteSource::Allocated(dp.release()); return true; }