Skip to content

Commit

Permalink
src: move some X509Certificate stuff to ncrypto
Browse files Browse the repository at this point in the history
PR-URL: nodejs#54241
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
  • Loading branch information
jasnell committed Aug 12, 2024
1 parent 624db50 commit 5f230d2
Show file tree
Hide file tree
Showing 7 changed files with 635 additions and 295 deletions.
2 changes: 1 addition & 1 deletion deps/ncrypto/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ EnginePointer EnginePointer::getEngineByName(const std::string_view name,
}
}
}
return std::move(engine);
return engine;
}

bool EnginePointer::setAsDefault(uint32_t flags, CryptoErrorList* errors) {
Expand Down
229 changes: 224 additions & 5 deletions deps/ncrypto/ncrypto.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#include "ncrypto.h"
#include <algorithm>
#include <cstring>
#include "openssl/bn.h"
#include "openssl/evp.h"
#include "openssl/pkcs12.h"
#include "openssl/x509v3.h"
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/pkcs12.h>
#include <openssl/x509v3.h>
#if OPENSSL_VERSION_MAJOR >= 3
#include "openssl/provider.h"
#include <openssl/provider.h>
#endif

namespace ncrypto {
Expand Down Expand Up @@ -703,4 +703,223 @@ bool SafeX509InfoAccessPrint(const BIOPointer& out, X509_EXTENSION* ext) {
return ok;
}

// ============================================================================
// X509Pointer

X509Pointer::X509Pointer(X509* x509) : cert_(x509) {}

X509Pointer::X509Pointer(X509Pointer&& other) noexcept
: cert_(other.release()) {}

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

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

void X509Pointer::reset(X509* x509) {
cert_.reset(x509);
}

X509* X509Pointer::release() {
return cert_.release();
}

X509View X509Pointer::view() const {
return X509View(cert_.get());
}

BIOPointer X509View::toPEM() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return {};
if (PEM_write_bio_X509(bio.get(), const_cast<X509*>(cert_)) <= 0) return {};
return bio;
}

BIOPointer X509View::toDER() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return {};
if (i2d_X509_bio(bio.get(), const_cast<X509*>(cert_)) <= 0) return {};
return bio;
}

BIOPointer X509View::getSubject() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return {};
if (X509_NAME_print_ex(bio.get(), X509_get_subject_name(cert_),
0, kX509NameFlagsMultiline) <= 0) {
return {};
}
return bio;
}

BIOPointer X509View::getSubjectAltName() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return {};
int index = X509_get_ext_by_NID(cert_, NID_subject_alt_name, -1);
if (index < 0 || !SafeX509SubjectAltNamePrint(bio, X509_get_ext(cert_, index))) {
return {};
}
return bio;
}

BIOPointer X509View::getIssuer() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return {};
if (X509_NAME_print_ex(bio.get(), X509_get_issuer_name(cert_), 0,
kX509NameFlagsMultiline) <= 0) {
return {};
}
return bio;
}

BIOPointer X509View::getInfoAccess() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return {};
int index = X509_get_ext_by_NID(cert_, NID_info_access, -1);
if (index < 0) return {};
if (!SafeX509InfoAccessPrint(bio, X509_get_ext(cert_, index))) {
return {};
}
return bio;
}

BIOPointer X509View::getValidFrom() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return {};
ASN1_TIME_print(bio.get(), X509_get_notBefore(cert_));
return bio;
}

BIOPointer X509View::getValidTo() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
BIOPointer bio(BIO_new(BIO_s_mem()));
if (!bio) return {};
ASN1_TIME_print(bio.get(), X509_get_notAfter(cert_));
return bio;
}

DataPointer X509View::getSerialNumber() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
if (ASN1_INTEGER* serial_number = X509_get_serialNumber(const_cast<X509*>(cert_))) {
if (auto bn = BignumPointer(ASN1_INTEGER_to_BN(serial_number, nullptr))) {
return bn.toHex();
}
}
return {};
}

Result<EVPKeyPointer, int> X509View::getPublicKey() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return Result<EVPKeyPointer, int>(EVPKeyPointer {});
auto pkey = EVPKeyPointer(X509_get_pubkey(const_cast<X509*>(cert_)));
if (!pkey) return Result<EVPKeyPointer, int>(ERR_get_error());
return pkey;
}

StackOfASN1 X509View::getKeyUsage() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return {};
return StackOfASN1(static_cast<STACK_OF(ASN1_OBJECT)*>(
X509_get_ext_d2i(cert_, NID_ext_key_usage, nullptr, nullptr)));
}

bool X509View::isCA() const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return false;
return X509_check_ca(const_cast<X509*>(cert_)) == 1;
}

bool X509View::isIssuedBy(const X509View& issuer) const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr || issuer.cert_ == nullptr) return false;
return X509_check_issued(const_cast<X509*>(issuer.cert_),
const_cast<X509*>(cert_)) == X509_V_OK;
}

bool X509View::checkPrivateKey(const EVPKeyPointer& pkey) const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr || pkey == nullptr) return false;
return X509_check_private_key(const_cast<X509*>(cert_), pkey.get()) == 1;
}

bool X509View::checkPublicKey(const EVPKeyPointer& pkey) const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr || pkey == nullptr) return false;
return X509_verify(const_cast<X509*>(cert_), pkey.get()) == 1;
}

X509View::CheckMatch X509View::checkHost(const std::string_view host, int flags,
DataPointer* peerName) const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return CheckMatch::NO_MATCH;
char* peername;
switch (X509_check_host(const_cast<X509*>(cert_), host.data(), host.size(), flags, &peername)) {
case 0: return CheckMatch::NO_MATCH;
case 1: {
if (peername != nullptr) {
DataPointer name(peername, strlen(peername));
if (peerName != nullptr) *peerName = std::move(name);
}
return CheckMatch::MATCH;
}
case -2: return CheckMatch::INVALID_NAME;
default: return CheckMatch::OPERATION_FAILED;
}
}

X509View::CheckMatch X509View::checkEmail(const std::string_view email, int flags) const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return CheckMatch::NO_MATCH;
switch (X509_check_email(const_cast<X509*>(cert_), email.data(), email.size(), flags)) {
case 0: return CheckMatch::NO_MATCH;
case 1: return CheckMatch::MATCH;
case -2: return CheckMatch::INVALID_NAME;
default: return CheckMatch::OPERATION_FAILED;
}
}

X509View::CheckMatch X509View::checkIp(const std::string_view ip, int flags) const {
ClearErrorOnReturn clearErrorOnReturn;
if (cert_ == nullptr) return CheckMatch::NO_MATCH;
switch (X509_check_ip_asc(const_cast<X509*>(cert_), ip.data(), flags)) {
case 0: return CheckMatch::NO_MATCH;
case 1: return CheckMatch::MATCH;
case -2: return CheckMatch::INVALID_NAME;
default: return CheckMatch::OPERATION_FAILED;
}
}

Result<X509Pointer, int> X509Pointer::Parse(Buffer<const unsigned char> buffer) {
ClearErrorOnReturn clearErrorOnReturn;
BIOPointer bio(BIO_new_mem_buf(buffer.data, buffer.len));
if (!bio) return Result<X509Pointer, int>(ERR_get_error());

X509Pointer pem(PEM_read_bio_X509_AUX(bio.get(), nullptr, NoPasswordCallback, nullptr));
if (pem) return Result<X509Pointer, int>(std::move(pem));
BIO_reset(bio.get());

X509Pointer der(d2i_X509_bio(bio.get(), nullptr));
if (der) return Result<X509Pointer, int>(std::move(der));

return Result<X509Pointer, int>(ERR_get_error());
}
} // namespace ncrypto
93 changes: 91 additions & 2 deletions deps/ncrypto/ncrypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <optional>
#include <string>
#include <string_view>
#include "openssl/bn.h"
#include <openssl/bn.h>
#include <openssl/x509.h>
#include <openssl/dh.h>
#include <openssl/dsa.h>
Expand Down Expand Up @@ -91,6 +91,13 @@ namespace ncrypto {
#endif
}

static constexpr int kX509NameFlagsMultiline =
ASN1_STRFLGS_ESC_2253 |
ASN1_STRFLGS_ESC_CTRL |
ASN1_STRFLGS_UTF8_CONVERT |
XN_FLAG_SEP_MULTILINE |
XN_FLAG_FN_SN;

// ============================================================================
// Error handling utilities

Expand Down Expand Up @@ -164,6 +171,14 @@ class MarkPopErrorOnReturn final {
CryptoErrorList* errors_;
};

template <typename T, typename E>
struct Result final {
T value;
std::optional<E> error;
Result(T&& value) : value(std::move(value)) {}
Result(E&& error) : error(std::move(error)) {}
};

// ============================================================================
// Various smart pointer aliases for OpenSSL types.

Expand Down Expand Up @@ -197,7 +212,13 @@ using RSAPointer = DeleteFnPtr<RSA, RSA_free>;
using SSLCtxPointer = DeleteFnPtr<SSL_CTX, SSL_CTX_free>;
using SSLPointer = DeleteFnPtr<SSL, SSL_free>;
using SSLSessionPointer = DeleteFnPtr<SSL_SESSION, SSL_SESSION_free>;
using X509Pointer = DeleteFnPtr<X509, X509_free>;

struct StackOfXASN1Deleter {
void operator()(STACK_OF(ASN1_OBJECT)* p) const {
sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free);
}
};
using StackOfASN1 = std::unique_ptr<STACK_OF(ASN1_OBJECT), StackOfXASN1Deleter>;

// An unowned, unmanaged pointer to a buffer of data.
template <typename T>
Expand Down Expand Up @@ -290,6 +311,74 @@ class BignumPointer final {
DeleteFnPtr<BIGNUM, BN_clear_free> bn_;
};

class X509View final {
public:
X509View() = default;
inline explicit X509View(const X509* cert) : cert_(cert) {}
X509View(const X509View& other) = default;
X509View& operator=(const X509View& other) = default;
NCRYPTO_DISALLOW_MOVE(X509View)

inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; }
inline operator bool() const { return cert_ != nullptr; }

BIOPointer toPEM() const;
BIOPointer toDER() const;

BIOPointer getSubject() const;
BIOPointer getSubjectAltName() const;
BIOPointer getIssuer() const;
BIOPointer getInfoAccess() const;
BIOPointer getValidFrom() const;
BIOPointer getValidTo() const;
DataPointer getSerialNumber() const;
Result<EVPKeyPointer, int> getPublicKey() const;
StackOfASN1 getKeyUsage() const;

bool isCA() const;
bool isIssuedBy(const X509View& other) const;
bool checkPrivateKey(const EVPKeyPointer& pkey) const;
bool checkPublicKey(const EVPKeyPointer& pkey) const;

enum class CheckMatch {
NO_MATCH,
MATCH,
INVALID_NAME,
OPERATION_FAILED,
};
CheckMatch checkHost(const std::string_view host, int flags,
DataPointer* peerName = nullptr) const;
CheckMatch checkEmail(const std::string_view email, int flags) const;
CheckMatch checkIp(const std::string_view ip, int flags) const;

private:
const X509* cert_ = nullptr;
};

class X509Pointer final {
public:
static Result<X509Pointer, int> Parse(Buffer<const unsigned char> buffer);

X509Pointer() = default;
explicit X509Pointer(X509* cert);
X509Pointer(X509Pointer&& other) noexcept;
X509Pointer& operator=(X509Pointer&& other) noexcept;
NCRYPTO_DISALLOW_COPY(X509Pointer)
~X509Pointer();

inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; }
inline operator bool() const { return cert_ != nullptr; }
inline X509* get() const { return cert_.get(); }
void reset(X509* cert = nullptr);
X509* release();

X509View view() const;
operator X509View() const { return view(); }

private:
DeleteFnPtr<X509, X509_free> cert_;
};

#ifndef OPENSSL_NO_ENGINE
class EnginePointer final {
public:
Expand Down
Loading

0 comments on commit 5f230d2

Please sign in to comment.