Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

src: move some X509Certificate stuff to ncrypto #54241

Closed
wants to merge 10 commits into from
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;
anonrig marked this conversation as resolved.
Show resolved Hide resolved
}

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 {
jasnell marked this conversation as resolved.
Show resolved Hide resolved
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
Loading