Skip to content

Commit

Permalink
src: move more crypto to ncrypto
Browse files Browse the repository at this point in the history
  • Loading branch information
jasnell committed Jan 18, 2025
1 parent 7b1f097 commit a6bff3a
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 73 deletions.
81 changes: 81 additions & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3721,4 +3721,85 @@ bool extractP1363(const Buffer<const unsigned char>& buf,
BignumPointer::EncodePaddedInto(asn1_sig.s(), dest + n, n) > 0;
}

// ============================================================================

HMACCtxPointer::HMACCtxPointer() : ctx_(nullptr) {}

HMACCtxPointer::HMACCtxPointer(HMAC_CTX* ctx) : ctx_(ctx) {}

HMACCtxPointer::HMACCtxPointer(HMACCtxPointer&& other) noexcept
: ctx_(other.release()) {}

HMACCtxPointer& HMACCtxPointer::operator=(HMACCtxPointer&& other) noexcept {
ctx_.reset(other.release());
return *this;
}

HMACCtxPointer::~HMACCtxPointer() {
reset();
}

void HMACCtxPointer::reset(HMAC_CTX* ctx) {
ctx_.reset(ctx);
}

HMAC_CTX* HMACCtxPointer::release() {
return ctx_.release();
}

bool HMACCtxPointer::init(const Buffer<const void>& buf, const EVP_MD* md) {
if (!ctx_) return false;
return HMAC_Init_ex(ctx_.get(), buf.data, buf.len, md, nullptr) == 1;
}

bool HMACCtxPointer::update(const Buffer<const void>& buf) {
if (!ctx_) return false;
return HMAC_Update(ctx_.get(),
static_cast<const unsigned char*>(buf.data),
buf.len) == 1;
}

DataPointer HMACCtxPointer::digest() {
auto data = DataPointer::Alloc(EVP_MAX_MD_SIZE);
if (!data) return {};
Buffer<void> buf = data;
if (!digestInto(&buf)) return {};
return data.resize(buf.len);
}

bool HMACCtxPointer::digestInto(Buffer<void>* buf) {
if (!ctx_) return false;

unsigned int len = buf->len;
if (!HMAC_Final(ctx_.get(), static_cast<unsigned char*>(buf->data), &len)) {
return false;
}
buf->len = len;
return true;
}

HMACCtxPointer HMACCtxPointer::New() {
return HMACCtxPointer(HMAC_CTX_new());
}

DataPointer hashDigest(const Buffer<const unsigned char>& buf,
const EVP_MD* md) {
if (md == nullptr) return {};
size_t md_len = EVP_MD_size(md);
unsigned int result_size;
auto data = DataPointer::Alloc(md_len);
if (!data) return {};

if (!EVP_Digest(buf.data,
buf.len,
reinterpret_cast<unsigned char*>(data.get()),
&result_size,
md,
nullptr)) {
return {};
}

return data.resize(result_size);
}

} // namespace ncrypto
31 changes: 30 additions & 1 deletion deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,6 @@ struct FunctionDeleter {
template <typename T, void (*function)(T*)>
using DeleteFnPtr = typename FunctionDeleter<T, function>::Pointer;

using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
using PKCS8Pointer = DeleteFnPtr<PKCS8_PRIV_KEY_INFO, PKCS8_PRIV_KEY_INFO_free>;
using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
Expand Down Expand Up @@ -239,6 +238,9 @@ struct Buffer {
size_t len = 0;
};

DataPointer hashDigest(const Buffer<const unsigned char>& data,
const EVP_MD* md);

class Cipher final {
public:
Cipher() = default;
Expand Down Expand Up @@ -1185,6 +1187,33 @@ class EVPMDCtxPointer final {
DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free> ctx_;
};

class HMACCtxPointer final {
public:
HMACCtxPointer();
explicit HMACCtxPointer(HMAC_CTX* ctx);
HMACCtxPointer(HMACCtxPointer&& other) noexcept;
HMACCtxPointer& operator=(HMACCtxPointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(HMACCtxPointer)
~HMACCtxPointer();

inline bool operator==(std::nullptr_t) noexcept { return ctx_ == nullptr; }
inline operator bool() const { return ctx_ != nullptr; }
inline HMAC_CTX* get() const { return ctx_.get(); }
inline operator HMAC_CTX*() const { return ctx_.get(); }
void reset(HMAC_CTX* ctx = nullptr);
HMAC_CTX* release();

bool init(const Buffer<const void>& buf, const EVP_MD* md);
bool update(const Buffer<const void>& buf);
DataPointer digest();
bool digestInto(Buffer<void>* buf);

static HMACCtxPointer New();

private:
DeleteFnPtr<HMAC_CTX, HMAC_CTX_free> ctx_;
};

#ifndef OPENSSL_NO_ENGINE
class EnginePointer final {
public:
Expand Down
75 changes: 36 additions & 39 deletions src/crypto/crypto_hash.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

namespace node {

using ncrypto::DataPointer;
using ncrypto::EVPMDCtxPointer;
using ncrypto::MarkPopErrorOnReturn;
using v8::Context;
Expand Down Expand Up @@ -220,7 +221,7 @@ void Hash::OneShotDigest(const FunctionCallbackInfo<Value>& args) {
CHECK(args[5]->IsUint32() || args[5]->IsUndefined()); // outputEncodingId

const EVP_MD* md = GetDigestImplementation(env, args[0], args[1], args[2]);
if (md == nullptr) {
if (md == nullptr) [[unlikely]] {
Utf8Value method(isolate, args[0]);
std::string message =
"Digest method " + method.ToString() + " is not supported";
Expand All @@ -229,41 +230,36 @@ void Hash::OneShotDigest(const FunctionCallbackInfo<Value>& args) {

enum encoding output_enc = ParseEncoding(isolate, args[4], args[5], HEX);

int md_len = EVP_MD_size(md);
unsigned int result_size;
ByteSource::Builder output(md_len);
int success;
// On smaller inputs, EVP_Digest() can be slower than the
// deprecated helpers e.g SHA256_XXX. The speedup may not
// be worth using deprecated APIs, however, so we use
// EVP_Digest(), unless there's a better alternative
// in the future.
// https://github.com/openssl/openssl/issues/19612
if (args[3]->IsString()) {
Utf8Value utf8(isolate, args[3]);
success = EVP_Digest(utf8.out(),
utf8.length(),
output.data<unsigned char>(),
&result_size,
md,
nullptr);
} else {
DataPointer output = ([&] {
if (args[3]->IsString()) {
Utf8Value utf8(isolate, args[3]);
ncrypto::Buffer<const unsigned char> buf{
.data = reinterpret_cast<const unsigned char*>(utf8.out()),
.len = utf8.length(),
};
return ncrypto::hashDigest(buf, md);
}

ArrayBufferViewContents<unsigned char> input(args[3]);
success = EVP_Digest(input.data(),
input.length(),
output.data<unsigned char>(),
&result_size,
md,
nullptr);
}
if (!success) {
ncrypto::Buffer<const unsigned char> buf{
.data = reinterpret_cast<const unsigned char*>(input.data()),
.len = input.length(),
};
return ncrypto::hashDigest(buf, md);
})();

if (!output) [[unlikely]] {
return ThrowCryptoError(env, ERR_get_error());
}

Local<Value> error;
MaybeLocal<Value> rc = StringBytes::Encode(
env->isolate(), output.data<char>(), md_len, output_enc, &error);
if (rc.IsEmpty()) {
MaybeLocal<Value> rc =
StringBytes::Encode(env->isolate(),
static_cast<const char*>(output.get()),
output.size(),
output_enc,
&error);
if (rc.IsEmpty()) [[unlikely]] {
CHECK(!error.IsEmpty());
env->isolate()->ThrowException(error);
return;
Expand Down Expand Up @@ -339,7 +335,7 @@ void Hash::New(const FunctionCallbackInfo<Value>& args) {

bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
mdctx_ = EVPMDCtxPointer::New();
if (!mdctx_.digestInit(md)) {
if (!mdctx_.digestInit(md)) [[unlikely]] {
mdctx_.reset();
return false;
}
Expand All @@ -348,7 +344,7 @@ bool Hash::HashInit(const EVP_MD* md, Maybe<unsigned int> xof_md_len) {
if (xof_md_len.IsJust() && xof_md_len.FromJust() != md_len_) {
// This is a little hack to cause createHash to fail when an incorrect
// hashSize option was passed for a non-XOF hash function.
if (!mdctx_.hasXofFlag()) {
if (!mdctx_.hasXofFlag()) [[unlikely]] {
EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
mdctx_.reset();
return false;
Expand Down Expand Up @@ -406,7 +402,7 @@ void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
// so we need to cache it.
// See https://github.com/nodejs/node/issues/28245.
auto data = hash->mdctx_.digestFinal(len);
if (!data) {
if (!data) [[unlikely]] {
return ThrowCryptoError(env, ERR_get_error());
}

Expand All @@ -416,7 +412,7 @@ void Hash::HashDigest(const FunctionCallbackInfo<Value>& args) {
Local<Value> error;
MaybeLocal<Value> rc = StringBytes::Encode(
env->isolate(), hash->digest_.data<char>(), len, encoding, &error);
if (rc.IsEmpty()) {
if (rc.IsEmpty()) [[unlikely]] {
CHECK(!error.IsEmpty());
env->isolate()->ThrowException(error);
return;
Expand Down Expand Up @@ -482,7 +478,7 @@ Maybe<void> HashTraits::AdditionalConfig(
static_cast<uint32_t>(args[offset + 2]
.As<Uint32>()->Value()) / CHAR_BIT;
if (params->length != expected) {
if ((EVP_MD_flags(params->digest) & EVP_MD_FLAG_XOF) == 0) {
if ((EVP_MD_flags(params->digest) & EVP_MD_FLAG_XOF) == 0) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
return Nothing<void>();
}
Expand All @@ -505,7 +501,8 @@ bool HashTraits::DeriveBits(

if (params.length > 0) [[likely]] {
auto data = ctx.digestFinal(params.length);
if (!data) return false;
if (!data) [[unlikely]]
return false;

*out = ByteSource::Allocated(data.release());
}
Expand Down Expand Up @@ -535,7 +532,7 @@ void InternalVerifyIntegrity(const v8::FunctionCallbackInfo<v8::Value>& args) {
digest,
&digest_size,
md_type,
nullptr) != 1) {
nullptr) != 1) [[unlikely]] {
return ThrowCryptoError(
env, ERR_get_error(), "Digest method not supported");
}
Expand All @@ -549,7 +546,7 @@ void InternalVerifyIntegrity(const v8::FunctionCallbackInfo<v8::Value>& args) {
digest_size,
BASE64,
&error);
if (rc.IsEmpty()) {
if (rc.IsEmpty()) [[unlikely]] {
CHECK(!error.IsEmpty());
env->isolate()->ThrowException(error);
return;
Expand Down
4 changes: 2 additions & 2 deletions src/crypto/crypto_hkdf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Maybe<void> HKDFTraits::AdditionalConfig(

Utf8Value hash(env->isolate(), args[offset]);
params->digest = ncrypto::getDigestByName(hash.ToStringView());
if (params->digest == nullptr) {
if (params->digest == nullptr) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Invalid digest: %s", *hash);
return Nothing<void>();
}
Expand Down Expand Up @@ -88,7 +88,7 @@ Maybe<void> 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.
if (!ncrypto::checkHkdfLength(params->digest, params->length)) {
if (!ncrypto::checkHkdfLength(params->digest, params->length)) [[unlikely]] {
THROW_ERR_CRYPTO_INVALID_KEYLEN(env);
return Nothing<void>();
}
Expand Down
Loading

0 comments on commit a6bff3a

Please sign in to comment.