mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
src: move hkdf, scrypto, pbkdf2 impl to ncrypto
PR-URL: https://github.com/nodejs/node/pull/54651 Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Minwoo Jung <nodecorelab@gmail.com>
This commit is contained in:
parent
16e6747c35
commit
dcc2ed944f
132
deps/ncrypto/ncrypto.cc
vendored
132
deps/ncrypto/ncrypto.cc
vendored
@ -4,6 +4,7 @@
|
||||
#include <openssl/dh.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/hmac.h>
|
||||
#include <openssl/pkcs12.h>
|
||||
#include <openssl/x509v3.h>
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
@ -1252,4 +1253,135 @@ DataPointer DHPointer::stateless(const EVPKeyPointer& ourKey,
|
||||
return out;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// KDF
|
||||
|
||||
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<const unsigned char>& key,
|
||||
const Buffer<const unsigned char>& info,
|
||||
const Buffer<const unsigned char>& salt,
|
||||
size_t length) {
|
||||
ClearErrorOnReturn clearErrorOnReturn;
|
||||
|
||||
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<const char*>(salt.data), salt.len};
|
||||
} else {
|
||||
actual_salt = {default_salt, static_cast<unsigned>(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<unsigned char*>(buf.get()), &length) <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem) {
|
||||
return EVP_PBE_scrypt(nullptr, 0, nullptr, 0, N, r, p, maxmem, nullptr, 0) == 1;
|
||||
}
|
||||
|
||||
DataPointer scrypt(const Buffer<const char>& pass,
|
||||
const Buffer<const unsigned char>& salt,
|
||||
uint64_t N,
|
||||
uint64_t r,
|
||||
uint64_t p,
|
||||
uint64_t maxmem,
|
||||
size_t length) {
|
||||
ClearErrorOnReturn clearErrorOnReturn;
|
||||
|
||||
if (pass.len > INT_MAX ||
|
||||
salt.len > INT_MAX) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto dp = DataPointer::Alloc(length);
|
||||
if (dp && EVP_PBE_scrypt(
|
||||
pass.data, pass.len, salt.data, salt.len, N, r, p, maxmem,
|
||||
reinterpret_cast<unsigned char*>(dp.get()), length)) {
|
||||
return dp;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
DataPointer pbkdf2(const EVP_MD* md,
|
||||
const Buffer<const char>& pass,
|
||||
const Buffer<const unsigned char>& salt,
|
||||
uint32_t iterations,
|
||||
size_t length) {
|
||||
ClearErrorOnReturn clearErrorOnReturn;
|
||||
|
||||
if (pass.len > INT_MAX ||
|
||||
salt.len > INT_MAX ||
|
||||
length > INT_MAX) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto dp = DataPointer::Alloc(length);
|
||||
if (dp && PKCS5_PBKDF2_HMAC(pass.data, pass.len, salt.data, salt.len,
|
||||
iterations, md, length,
|
||||
reinterpret_cast<unsigned char*>(dp.get()))) {
|
||||
return dp;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace ncrypto
|
||||
|
32
deps/ncrypto/ncrypto.h
vendored
32
deps/ncrypto/ncrypto.h
vendored
@ -588,6 +588,38 @@ BIOPointer ExportPublicKey(const char* input, size_t length);
|
||||
// The caller takes ownership of the returned Buffer<char>
|
||||
Buffer<char> ExportChallenge(const char* input, size_t length);
|
||||
|
||||
// ============================================================================
|
||||
// KDF
|
||||
|
||||
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<const unsigned char>& key,
|
||||
const Buffer<const unsigned char>& info,
|
||||
const Buffer<const unsigned char>& salt,
|
||||
size_t length);
|
||||
|
||||
bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem);
|
||||
|
||||
DataPointer scrypt(const Buffer<const char>& pass,
|
||||
const Buffer<const unsigned char>& salt,
|
||||
uint64_t N,
|
||||
uint64_t r,
|
||||
uint64_t p,
|
||||
uint64_t maxmem,
|
||||
size_t length);
|
||||
|
||||
DataPointer pbkdf2(const EVP_MD* md,
|
||||
const Buffer<const char>& pass,
|
||||
const Buffer<const unsigned char>& salt,
|
||||
uint32_t iterations,
|
||||
size_t length);
|
||||
|
||||
// ============================================================================
|
||||
// Version metadata
|
||||
#define NCRYPTO_VERSION "0.0.1"
|
||||
|
@ -56,7 +56,7 @@ Maybe<bool> 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<bool>();
|
||||
@ -90,9 +90,7 @@ Maybe<bool> 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<bool>();
|
||||
}
|
||||
@ -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<unsigned char>(), params.info.size())) {
|
||||
return false;
|
||||
}
|
||||
auto dp = ncrypto::hkdf(params.digest,
|
||||
ncrypto::Buffer<const unsigned char>{
|
||||
.data = reinterpret_cast<const unsigned char*>(
|
||||
params.key->GetSymmetricKey()),
|
||||
.len = params.key->GetSymmetricKeySize(),
|
||||
},
|
||||
ncrypto::Buffer<const unsigned char>{
|
||||
.data = params.info.data<const unsigned char>(),
|
||||
.len = params.info.size(),
|
||||
},
|
||||
ncrypto::Buffer<const unsigned char>{
|
||||
.data = params.salt.data<const unsigned char>(),
|
||||
.len = params.salt.size(),
|
||||
},
|
||||
params.length);
|
||||
if (!dp) 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<char>(), params.salt.size()};
|
||||
} else {
|
||||
static const char default_salt[EVP_MAX_MD_SIZE] = {0};
|
||||
salt = {default_salt, static_cast<unsigned>(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<const unsigned char*>(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<unsigned char>(), &length) <= 0)
|
||||
return false;
|
||||
|
||||
*out = std::move(buf).release();
|
||||
*out = ByteSource::Allocated(dp.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -102,7 +102,7 @@ Maybe<bool> PBKDF2Traits::AdditionalConfig(
|
||||
}
|
||||
|
||||
Utf8Value name(args.GetIsolate(), args[offset + 4]);
|
||||
params->digest = EVP_get_digestbyname(*name);
|
||||
params->digest = ncrypto::getDigestByName(name.ToStringView());
|
||||
if (params->digest == nullptr) {
|
||||
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *name);
|
||||
return Nothing<bool>();
|
||||
@ -111,27 +111,24 @@ Maybe<bool> PBKDF2Traits::AdditionalConfig(
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
bool PBKDF2Traits::DeriveBits(
|
||||
Environment* env,
|
||||
const PBKDF2Config& params,
|
||||
ByteSource* out) {
|
||||
ByteSource::Builder buf(params.length);
|
||||
|
||||
bool PBKDF2Traits::DeriveBits(Environment* env,
|
||||
const PBKDF2Config& params,
|
||||
ByteSource* out) {
|
||||
// Both pass and salt may be zero length here.
|
||||
// The generated bytes are stored in buf, which is
|
||||
// assigned to out on success.
|
||||
auto dp = ncrypto::pbkdf2(params.digest,
|
||||
ncrypto::Buffer<const char>{
|
||||
.data = params.pass.data<const char>(),
|
||||
.len = params.pass.size(),
|
||||
},
|
||||
ncrypto::Buffer<const unsigned char>{
|
||||
.data = params.salt.data<unsigned char>(),
|
||||
.len = params.salt.size(),
|
||||
},
|
||||
params.iterations,
|
||||
params.length);
|
||||
|
||||
if (PKCS5_PBKDF2_HMAC(params.pass.data<char>(),
|
||||
params.pass.size(),
|
||||
params.salt.data<unsigned char>(),
|
||||
params.salt.size(),
|
||||
params.iterations,
|
||||
params.digest,
|
||||
params.length,
|
||||
buf.data<unsigned char>()) <= 0) {
|
||||
return false;
|
||||
}
|
||||
*out = std::move(buf).release();
|
||||
if (!dp) return false;
|
||||
*out = ByteSource::Allocated(dp.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -93,17 +93,11 @@ Maybe<bool> ScryptTraits::AdditionalConfig(
|
||||
params->p = args[offset + 4].As<Uint32>()->Value();
|
||||
params->maxmem = args[offset + 5]->IntegerValue(env->context()).ToChecked();
|
||||
|
||||
if (EVP_PBE_scrypt(
|
||||
nullptr,
|
||||
0,
|
||||
nullptr,
|
||||
0,
|
||||
params->N,
|
||||
params->r,
|
||||
params->p,
|
||||
params->maxmem,
|
||||
nullptr,
|
||||
0) != 1) {
|
||||
params->length = args[offset + 6].As<Int32>()->Value();
|
||||
CHECK_GE(params->length, 0);
|
||||
|
||||
if (!ncrypto::checkScryptParams(
|
||||
params->N, params->r, params->p, params->maxmem)) {
|
||||
// Do not use CryptoErrorStore or ThrowCryptoError here in order to maintain
|
||||
// backward compatibility with ERR_CRYPTO_INVALID_SCRYPT_PARAMS.
|
||||
uint32_t err = ERR_peek_last_error();
|
||||
@ -118,9 +112,6 @@ Maybe<bool> ScryptTraits::AdditionalConfig(
|
||||
return Nothing<bool>();
|
||||
}
|
||||
|
||||
params->length = args[offset + 6].As<Int32>()->Value();
|
||||
CHECK_GE(params->length, 0);
|
||||
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
@ -128,23 +119,30 @@ bool ScryptTraits::DeriveBits(
|
||||
Environment* env,
|
||||
const ScryptConfig& params,
|
||||
ByteSource* out) {
|
||||
ByteSource::Builder buf(params.length);
|
||||
|
||||
// Both the pass and salt may be zero-length at this point
|
||||
|
||||
if (!EVP_PBE_scrypt(params.pass.data<char>(),
|
||||
params.pass.size(),
|
||||
params.salt.data<unsigned char>(),
|
||||
params.salt.size(),
|
||||
params.N,
|
||||
params.r,
|
||||
params.p,
|
||||
params.maxmem,
|
||||
buf.data<unsigned char>(),
|
||||
params.length)) {
|
||||
return false;
|
||||
// If the params.length is zero-length, just return an empty buffer.
|
||||
// It's useless, yes, but allowed via the API.
|
||||
if (params.length == 0) {
|
||||
*out = ByteSource();
|
||||
return true;
|
||||
}
|
||||
*out = std::move(buf).release();
|
||||
|
||||
auto dp = ncrypto::scrypt(
|
||||
ncrypto::Buffer<const char>{
|
||||
.data = params.pass.data<char>(),
|
||||
.len = params.pass.size(),
|
||||
},
|
||||
ncrypto::Buffer<const unsigned char>{
|
||||
.data = params.salt.data<unsigned char>(),
|
||||
.len = params.salt.size(),
|
||||
},
|
||||
params.N,
|
||||
params.r,
|
||||
params.p,
|
||||
params.maxmem,
|
||||
params.length);
|
||||
|
||||
if (!dp) return false;
|
||||
*out = ByteSource::Allocated(dp.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
// Flags: --expose-internals
|
||||
// Flags: --expose-internals --no-warnings
|
||||
'use strict';
|
||||
const common = require('../common');
|
||||
if (!common.hasCrypto)
|
||||
|
Loading…
Reference in New Issue
Block a user