Skip to content

Commit

Permalink
src: move evp stuff to ncrypto
Browse files Browse the repository at this point in the history
PR-URL: nodejs#54911
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
  • Loading branch information
jasnell authored and louwers committed Nov 2, 2024
1 parent b88c1c8 commit 4802db0
Show file tree
Hide file tree
Showing 11 changed files with 492 additions and 262 deletions.
209 changes: 209 additions & 0 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1425,4 +1425,213 @@ DataPointer pbkdf2(const EVP_MD* md,
return {};
}

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

EVPKeyPointer EVPKeyPointer::New() {
return EVPKeyPointer(EVP_PKEY_new());
}

EVPKeyPointer EVPKeyPointer::NewRawPublic(int id, const Buffer<const unsigned char>& data) {
if (id == 0) return {};
return EVPKeyPointer(EVP_PKEY_new_raw_public_key(id, nullptr, data.data, data.len));
}

EVPKeyPointer EVPKeyPointer::NewRawPrivate(int id, const Buffer<const unsigned char>& data) {
if (id == 0) return {};
return EVPKeyPointer(EVP_PKEY_new_raw_private_key(id, nullptr, data.data, data.len));
}

EVPKeyPointer::EVPKeyPointer(EVP_PKEY* pkey) : pkey_(pkey) {}

EVPKeyPointer::EVPKeyPointer(EVPKeyPointer&& other) noexcept
: pkey_(other.release()) {}

EVPKeyPointer& EVPKeyPointer::operator=(EVPKeyPointer&& other) noexcept {
if (this == &other) return *this;
this->~EVPKeyPointer();
return *new (this) EVPKeyPointer(std::move(other));
}

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

void EVPKeyPointer::reset(EVP_PKEY* pkey) {
pkey_.reset(pkey);
}

EVP_PKEY* EVPKeyPointer::release() {
return pkey_.release();
}

int EVPKeyPointer::id(const EVP_PKEY* key) {
if (key == nullptr) return 0;
return EVP_PKEY_id(key);
}

int EVPKeyPointer::base_id(const EVP_PKEY* key) {
if (key == nullptr) return 0;
return EVP_PKEY_base_id(key);
}

int EVPKeyPointer::id() const {
return id(get());
}

int EVPKeyPointer::base_id() const {
return base_id(get());
}

int EVPKeyPointer::bits() const {
if (get() == nullptr) return 0;
return EVP_PKEY_bits(get());
}

size_t EVPKeyPointer::size() const {
if (get() == nullptr) return 0;
return EVP_PKEY_size(get());
}

EVPKeyCtxPointer EVPKeyPointer::newCtx() const {
if (!pkey_) return {};
return EVPKeyCtxPointer(EVP_PKEY_CTX_new(get(), nullptr));
}

size_t EVPKeyPointer::rawPublicKeySize() const {
if (!pkey_) return 0;
size_t len = 0;
if (EVP_PKEY_get_raw_public_key(get(), nullptr, &len) == 1) return len;
return 0;
}

size_t EVPKeyPointer::rawPrivateKeySize() const {
if (!pkey_) return 0;
size_t len = 0;
if (EVP_PKEY_get_raw_private_key(get(), nullptr, &len) == 1) return len;
return 0;
}

DataPointer EVPKeyPointer::rawPublicKey() const {
if (!pkey_) return {};
if (auto data = DataPointer::Alloc(rawPublicKeySize())) {
const Buffer<unsigned char> buf = data;
size_t len = data.size();
if (EVP_PKEY_get_raw_public_key(get(),
buf.data,
&len) != 1) return {};
return data;
}
return {};
}

DataPointer EVPKeyPointer::rawPrivateKey() const {
if (!pkey_) return {};
if (auto data = DataPointer::Alloc(rawPrivateKeySize())) {
const Buffer<unsigned char> buf = data;
size_t len = data.size();
if (EVP_PKEY_get_raw_private_key(get(),
buf.data,
&len) != 1) return {};
return data;
}
return {};
}

BIOPointer EVPKeyPointer::derPublicKey() const {
if (!pkey_) return {};
auto bio = BIOPointer::NewMem();
if (!bio) return {};
if (!i2d_PUBKEY_bio(bio.get(), get())) return {};
return bio;
}

namespace {
EVPKeyPointer::ParseKeyResult TryParsePublicKeyInner(
const BIOPointer& bp,
const char* name,
auto&& parse) {
if (!bp.resetBio()) {
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED);
}
unsigned char* der_data;
long der_len;

// This skips surrounding data and decodes PEM to DER.
{
MarkPopErrorOnReturn mark_pop_error_on_return;
if (PEM_bytes_read_bio(&der_data, &der_len, nullptr, name,
bp.get(), nullptr, nullptr) != 1)
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::NOT_RECOGNIZED);
}
DataPointer data(der_data, der_len);

// OpenSSL might modify the pointer, so we need to make a copy before parsing.
const unsigned char* p = der_data;
EVPKeyPointer pkey(parse(&p, der_len));
if (!pkey) return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED);
return EVPKeyPointer::ParseKeyResult(std::move(pkey));
}

EVPKeyPointer::ParseKeyResult TryParsePublicKeyPEM(
const Buffer<const unsigned char>& buffer) {
auto bp = BIOPointer::New(buffer.data, buffer.len);
if (!bp)
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::FAILED);

// Try parsing as SubjectPublicKeyInfo (SPKI) first.
if (auto ret = TryParsePublicKeyInner(bp, "PUBLIC KEY",
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
return d2i_PUBKEY(nullptr, p, l);
})) {
return ret;
}

// Maybe it is PKCS#1.
if (auto ret = TryParsePublicKeyInner(bp, "RSA PUBLIC KEY",
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
return d2i_PublicKey(EVP_PKEY_RSA, nullptr, p, l);
})) {
return ret;
}

// X.509 fallback.
if (auto ret = TryParsePublicKeyInner(bp, "CERTIFICATE",
[](const unsigned char** p, long l) { // NOLINT(runtime/int)
X509Pointer x509(d2i_X509(nullptr, p, l));
return x509 ? X509_get_pubkey(x509.get()) : nullptr;
})) {
return ret;
};

return EVPKeyPointer::ParseKeyResult(EVPKeyPointer::PKParseError::NOT_RECOGNIZED);
}
} // namespace

EVPKeyPointer::ParseKeyResult EVPKeyPointer::TryParsePublicKey(
PKFormatType format,
PKEncodingType encoding,
const Buffer<const unsigned char>& buffer) {
if (format == PKFormatType::PEM) {
return TryParsePublicKeyPEM(buffer);
}

if (format != PKFormatType::DER) {
return ParseKeyResult(PKParseError::FAILED);
}

const unsigned char* start = buffer.data;

EVP_PKEY* key = nullptr;

if (encoding == PKEncodingType::PKCS1 &&
(key = d2i_PublicKey(EVP_PKEY_RSA, nullptr, &start, buffer.len))) {
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key));
}

if (encoding == PKEncodingType::SPKI &&
(key = d2i_PUBKEY(nullptr, &start, buffer.len))) {
return EVPKeyPointer::ParseKeyResult(EVPKeyPointer(key));
}

return ParseKeyResult(PKParseError::FAILED);
}

} // namespace ncrypto
83 changes: 78 additions & 5 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,16 @@ class MarkPopErrorOnReturn final {
CryptoErrorList* errors_;
};

// TODO(@jasnell): Eventually replace with std::expected when we are able to
// bump up to c++23.
template <typename T, typename E>
struct Result final {
const bool has_value;
T value;
std::optional<E> error;
Result(T&& value) : value(std::move(value)) {}
Result(E&& error) : error(std::move(error)) {}
Result(T&& value) : has_value(true), value(std::move(value)) {}
Result(E&& error) : has_value(false), error(std::move(error)) {}
inline operator bool() const { return has_value; }
};

// ============================================================================
Expand All @@ -202,7 +206,6 @@ using ECGroupPointer = DeleteFnPtr<EC_GROUP, EC_GROUP_free>;
using ECKeyPointer = DeleteFnPtr<EC_KEY, EC_KEY_free>;
using ECPointPointer = DeleteFnPtr<EC_POINT, EC_POINT_free>;
using EVPKeyCtxPointer = DeleteFnPtr<EVP_PKEY_CTX, EVP_PKEY_CTX_free>;
using EVPKeyPointer = DeleteFnPtr<EVP_PKEY, EVP_PKEY_free>;
using EVPMDCtxPointer = DeleteFnPtr<EVP_MD_CTX, EVP_MD_CTX_free>;
using HMACCtxPointer = DeleteFnPtr<HMAC_CTX, HMAC_CTX_free>;
using NetscapeSPKIPointer = DeleteFnPtr<NETSCAPE_SPKI, NETSCAPE_SPKI_free>;
Expand Down Expand Up @@ -252,9 +255,10 @@ class DataPointer final {
Buffer<void> release();

// Returns a Buffer struct that is a view of the underlying data.
inline operator const Buffer<void>() const {
template <typename T = void>
inline operator const Buffer<T>() const {
return {
.data = data_,
.data = static_cast<T*>(data_),
.len = len_,
};
}
Expand Down Expand Up @@ -359,6 +363,75 @@ class BignumPointer final {
DeleteFnPtr<BIGNUM, BN_clear_free> bn_;
};

class EVPKeyPointer final {
public:
static EVPKeyPointer New();
static EVPKeyPointer NewRawPublic(int id, const Buffer<const unsigned char>& data);
static EVPKeyPointer NewRawPrivate(int id, const Buffer<const unsigned char>& data);

enum class PKEncodingType {
// RSAPublicKey / RSAPrivateKey according to PKCS#1.
PKCS1,
// PrivateKeyInfo or EncryptedPrivateKeyInfo according to PKCS#8.
PKCS8,
// SubjectPublicKeyInfo according to X.509.
SPKI,
// ECPrivateKey according to SEC1.
SEC1
};

enum class PKFormatType {
DER,
PEM,
JWK
};

enum class PKParseError {
NOT_RECOGNIZED,
NEED_PASSPHRASE,
FAILED
};
using ParseKeyResult = Result<EVPKeyPointer, PKParseError>;

static ParseKeyResult TryParsePublicKey(
PKFormatType format,
PKEncodingType encoding,
const Buffer<const unsigned char>& buffer);

EVPKeyPointer() = default;
explicit EVPKeyPointer(EVP_PKEY* pkey);
EVPKeyPointer(EVPKeyPointer&& other) noexcept;
EVPKeyPointer& operator=(EVPKeyPointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(EVPKeyPointer)
~EVPKeyPointer();

inline bool operator==(std::nullptr_t) const noexcept { return pkey_ == nullptr; }
inline operator bool() const { return pkey_ != nullptr; }
inline EVP_PKEY* get() const { return pkey_.get(); }
void reset(EVP_PKEY* pkey = nullptr);
EVP_PKEY* release();

static int id(const EVP_PKEY* key);
static int base_id(const EVP_PKEY* key);

int id() const;
int base_id() const;
int bits() const;
size_t size() const;

size_t rawPublicKeySize() const;
size_t rawPrivateKeySize() const;
DataPointer rawPublicKey() const;
DataPointer rawPrivateKey() const;

BIOPointer derPublicKey() const;

EVPKeyCtxPointer newCtx() const;

private:
DeleteFnPtr<EVP_PKEY, EVP_PKEY_free> pkey_;
};

class DHPointer final {
public:

Expand Down
4 changes: 2 additions & 2 deletions src/crypto/crypto_cipher.cc
Original file line number Diff line number Diff line change
Expand Up @@ -995,7 +995,7 @@ bool PublicKeyCipher::Cipher(
const ArrayBufferOrViewContents<unsigned char>& oaep_label,
const ArrayBufferOrViewContents<unsigned char>& data,
std::unique_ptr<BackingStore>* out) {
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
EVPKeyCtxPointer ctx = pkey.newCtx();
if (!ctx)
return false;
if (EVP_PKEY_cipher_init(ctx.get()) <= 0)
Expand Down Expand Up @@ -1071,7 +1071,7 @@ void PublicKeyCipher::Cipher(const FunctionCallbackInfo<Value>& args) {

if (EVP_PKEY_cipher == EVP_PKEY_decrypt &&
operation == PublicKeyCipher::kPrivate && padding == RSA_PKCS1_PADDING) {
EVPKeyCtxPointer ctx(EVP_PKEY_CTX_new(pkey.get(), nullptr));
EVPKeyCtxPointer ctx = pkey.newCtx();
CHECK(ctx);

if (EVP_PKEY_decrypt_init(ctx.get()) <= 0) {
Expand Down
27 changes: 12 additions & 15 deletions src/crypto/crypto_common.cc
Original file line number Diff line number Diff line change
Expand Up @@ -449,15 +449,14 @@ MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
Local<Context> context = env->context();
crypto::EVPKeyPointer key(raw_key);

int kid = EVP_PKEY_id(key.get());
int bits = EVP_PKEY_bits(key.get());
int kid = key.id();
switch (kid) {
case EVP_PKEY_DH:
if (!Set<String>(context, info, env->type_string(), env->dh_string()) ||
!Set<Integer>(context,
info,
env->size_string(),
Integer::New(env->isolate(), bits))) {
info,
env->size_string(),
Integer::New(env->isolate(), key.bits()))) {
return MaybeLocal<Object>();
}
break;
Expand All @@ -473,18 +472,16 @@ MaybeLocal<Object> GetEphemeralKey(Environment* env, const SSLPointer& ssl) {
} else {
curve_name = OBJ_nid2sn(kid);
}
if (!Set<String>(context,
info,
env->type_string(),
env->ecdh_string()) ||
if (!Set<String>(
context, info, env->type_string(), env->ecdh_string()) ||
!Set<String>(context,
info,
env->name_string(),
OneByteString(env->isolate(), curve_name)) ||
info,
env->name_string(),
OneByteString(env->isolate(), curve_name)) ||
!Set<Integer>(context,
info,
env->size_string(),
Integer::New(env->isolate(), bits))) {
info,
env->size_string(),
Integer::New(env->isolate(), key.bits()))) {
return MaybeLocal<Object>();
}
}
Expand Down
Loading

0 comments on commit 4802db0

Please sign in to comment.