src: move some X509Certificate stuff to ncrypto

PR-URL: https://github.com/nodejs/node/pull/54241
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
James M Snell 2024-08-05 16:24:51 -07:00
parent 624db50952
commit 5f230d2cf4
7 changed files with 680 additions and 340 deletions

View File

@ -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) {

View File

@ -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 {
@ -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

View File

@ -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>
@ -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
@ -164,6 +171,14 @@ private:
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.
@ -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>
@ -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:

View File

@ -3,10 +3,11 @@
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#include "node_crypto.h"
#include "v8.h"
#include <openssl/ssl.h>
#include <openssl/x509v3.h>
#include "ncrypto.h"
#include "node_crypto.h"
#include "v8.h"
#include <string>
@ -26,12 +27,7 @@ struct StackOfX509Deleter {
};
using StackOfX509 = std::unique_ptr<STACK_OF(X509), StackOfX509Deleter>;
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>;
using StackOfASN1 = ncrypto::StackOfASN1;
X509Pointer SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert);

View File

@ -82,6 +82,7 @@ class ManagedEVPPKey : public MemoryRetainer {
operator bool() const;
EVP_PKEY* get() const;
inline const EVPKeyPointer& pkey() const { return pkey_; }
Mutex* mutex() const;
void MemoryInfo(MemoryTracker* tracker) const override;

View File

@ -1,11 +1,12 @@
#include "base_object-inl.h"
#include "crypto_x509.h"
#include "base_object-inl.h"
#include "crypto_bio.h"
#include "crypto_common.h"
#include "crypto_context.h"
#include "crypto_keys.h"
#include "crypto_bio.h"
#include "env-inl.h"
#include "memory_tracker-inl.h"
#include "ncrypto.h"
#include "node_errors.h"
#include "util-inl.h"
#include "v8.h"
@ -15,6 +16,8 @@
namespace node {
using v8::Array;
using v8::ArrayBuffer;
using v8::ArrayBufferView;
using v8::Context;
using v8::EscapableHandleScope;
@ -24,7 +27,9 @@ using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::MaybeLocal;
using v8::NewStringType;
using v8::Object;
using v8::String;
using v8::Uint32;
using v8::Value;
@ -58,8 +63,330 @@ void Fingerprint(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (GetFingerprintDigest(env, algo(), cert->get()).ToLocal(&ret))
if (GetFingerprintDigest(env, algo(), cert->get()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
MaybeLocal<Value> ToV8Value(Local<Context> context, BIOPointer&& bio) {
if (!bio) return {};
BUF_MEM* mem;
BIO_get_mem_ptr(bio.get(), &mem);
Local<Value> ret;
if (!String::NewFromUtf8(context->GetIsolate(),
mem->data,
NewStringType::kNormal,
mem->length)
.ToLocal(&ret))
return {};
return ret;
}
MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer&& bio) {
if (!bio) return {};
BUF_MEM* mem;
BIO_get_mem_ptr(bio.get(), &mem);
auto backing = ArrayBuffer::NewBackingStore(
mem->data,
mem->length,
[](void*, size_t, void* data) {
BIOPointer free_me(static_cast<BIO*>(data));
},
bio.release());
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
Local<Value> ret;
if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&ret)) return {};
return ret;
}
void Pem(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (ToV8Value(env->context(), cert->view().toPEM()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void Der(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (ToBuffer(env, cert->view().toDER()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void Subject(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (ToV8Value(env->context(), cert->view().getSubject()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void SubjectAltName(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (ToV8Value(env->context(), cert->view().getSubjectAltName())
.ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void Issuer(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (ToV8Value(env->context(), cert->view().getIssuer()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void InfoAccess(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (ToV8Value(env->context(), cert->view().getInfoAccess()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void ValidFrom(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (ToV8Value(env->context(), cert->view().getValidFrom()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void ValidTo(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
Local<Value> ret;
if (ToV8Value(env->context(), cert->view().getValidTo()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void SerialNumber(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
if (auto serial = cert->view().getSerialNumber()) {
args.GetReturnValue().Set(OneByteString(
env->isolate(), static_cast<unsigned char*>(serial.get())));
}
}
void PublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
// TODO(tniessen): consider checking X509_get_pubkey() when the
// X509Certificate object is being created.
auto result = cert->view().getPublicKey();
if (!result.value) {
ThrowCryptoError(env, result.error.value_or(0));
return;
}
std::shared_ptr<KeyObjectData> key_data = KeyObjectData::CreateAsymmetric(
kKeyTypePublic, ManagedEVPPKey(std::move(result.value)));
Local<Value> ret;
if (KeyObjectHandle::Create(env, std::move(key_data)).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
void KeyUsage(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
auto eku = cert->view().getKeyUsage();
if (!eku) return;
const int count = sk_ASN1_OBJECT_num(eku.get());
MaybeStackBuffer<Local<Value>, 16> ext_key_usage(count);
char buf[256];
int j = 0;
for (int i = 0; i < count; i++) {
if (OBJ_obj2txt(buf, sizeof(buf), sk_ASN1_OBJECT_value(eku.get(), i), 1) >=
0) {
ext_key_usage[j++] = OneByteString(env->isolate(), buf);
}
}
args.GetReturnValue().Set(
Array::New(env->isolate(), ext_key_usage.out(), count));
}
void CheckCA(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
args.GetReturnValue().Set(cert->view().isCA());
}
void CheckIssued(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsObject());
CHECK(X509Certificate::HasInstance(env, args[0].As<Object>()));
X509Certificate* issuer;
ASSIGN_OR_RETURN_UNWRAP(&issuer, args[0]);
args.GetReturnValue().Set(cert->view().isIssuedBy(issuer->view()));
}
void CheckPrivateKey(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsObject());
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[0]);
CHECK_EQ(key->Data()->GetKeyType(), kKeyTypePrivate);
args.GetReturnValue().Set(
cert->view().checkPrivateKey(key->Data()->GetAsymmetricKey().pkey()));
}
void CheckPublicKey(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsObject());
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[0]);
CHECK_EQ(key->Data()->GetKeyType(), kKeyTypePublic);
args.GetReturnValue().Set(
cert->view().checkPublicKey(key->Data()->GetAsymmetricKey().pkey()));
}
void CheckHost(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsString()); // name
CHECK(args[1]->IsUint32()); // flags
Utf8Value name(env->isolate(), args[0]);
uint32_t flags = args[1].As<Uint32>()->Value();
ncrypto::DataPointer peername;
switch (cert->view().checkHost(name.ToStringView(), flags, &peername)) {
case ncrypto::X509View::CheckMatch::MATCH: { // Match!
Local<Value> ret = args[0];
if (peername) {
ret = OneByteString(env->isolate(),
static_cast<const char*>(peername.get()),
peername.size());
}
return args.GetReturnValue().Set(ret);
}
case ncrypto::X509View::CheckMatch::NO_MATCH: // No Match!
return; // No return value is set
case ncrypto::X509View::CheckMatch::INVALID_NAME: // Error!
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid name");
default: // Error!
return THROW_ERR_CRYPTO_OPERATION_FAILED(env);
}
}
void CheckEmail(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsString()); // name
CHECK(args[1]->IsUint32()); // flags
Utf8Value name(env->isolate(), args[0]);
uint32_t flags = args[1].As<Uint32>()->Value();
switch (cert->view().checkEmail(name.ToStringView(), flags)) {
case ncrypto::X509View::CheckMatch::MATCH: // Match!
return args.GetReturnValue().Set(args[0]);
case ncrypto::X509View::CheckMatch::NO_MATCH: // No Match!
return; // No return value is set
case ncrypto::X509View::CheckMatch::INVALID_NAME: // Error!
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid name");
default: // Error!
return THROW_ERR_CRYPTO_OPERATION_FAILED(env);
}
}
void CheckIP(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsString()); // IP
CHECK(args[1]->IsUint32()); // flags
Utf8Value name(env->isolate(), args[0]);
uint32_t flags = args[1].As<Uint32>()->Value();
switch (cert->view().checkIp(name.ToStringView(), flags)) {
case ncrypto::X509View::CheckMatch::MATCH: // Match!
return args.GetReturnValue().Set(args[0]);
case ncrypto::X509View::CheckMatch::NO_MATCH: // No Match!
return; // No return value is set
case ncrypto::X509View::CheckMatch::INVALID_NAME: // Error!
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP");
default: // Error!
return THROW_ERR_CRYPTO_OPERATION_FAILED(env);
}
}
void GetIssuerCert(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
auto issuer = cert->getIssuerCert();
if (issuer) args.GetReturnValue().Set(issuer->object());
}
void Parse(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsArrayBufferView());
ArrayBufferViewContents<unsigned char> buf(args[0].As<ArrayBufferView>());
Local<Object> cert;
auto result = X509Pointer::Parse(ncrypto::Buffer<const unsigned char>{
.data = buf.data(),
.len = buf.length(),
});
if (!result.value) return ThrowCryptoError(env, result.error.value_or(0));
if (X509Certificate::New(env, std::move(result.value)).ToLocal(&cert)) {
args.GetReturnValue().Set(cert);
}
}
void ToLegacy(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
ClearErrorOnReturn clear_error_on_return;
Local<Value> ret;
if (X509ToObject(env, cert->get()).ToLocal(&ret)) {
args.GetReturnValue().Set(ret);
}
}
} // namespace
@ -85,7 +412,7 @@ Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
SetProtoMethod(isolate, tmpl, "keyUsage", KeyUsage);
SetProtoMethod(isolate, tmpl, "serialNumber", SerialNumber);
SetProtoMethod(isolate, tmpl, "pem", Pem);
SetProtoMethod(isolate, tmpl, "raw", Raw);
SetProtoMethod(isolate, tmpl, "raw", Der);
SetProtoMethod(isolate, tmpl, "publicKey", PublicKey);
SetProtoMethod(isolate, tmpl, "checkCA", CheckCA);
SetProtoMethod(isolate, tmpl, "checkHost", CheckHost);
@ -93,7 +420,7 @@ Local<FunctionTemplate> X509Certificate::GetConstructorTemplate(
SetProtoMethod(isolate, tmpl, "checkIP", CheckIP);
SetProtoMethod(isolate, tmpl, "checkIssued", CheckIssued);
SetProtoMethod(isolate, tmpl, "checkPrivateKey", CheckPrivateKey);
SetProtoMethod(isolate, tmpl, "verify", Verify);
SetProtoMethod(isolate, tmpl, "verify", CheckPublicKey);
SetProtoMethod(isolate, tmpl, "toLegacy", ToLegacy);
SetProtoMethod(isolate, tmpl, "getIssuerCert", GetIssuerCert);
env->set_x509_constructor_template(tmpl);
@ -105,18 +432,16 @@ bool X509Certificate::HasInstance(Environment* env, Local<Object> object) {
return GetConstructorTemplate(env)->HasInstance(object);
}
MaybeLocal<Object> X509Certificate::New(
Environment* env,
X509Pointer cert,
STACK_OF(X509)* issuer_chain) {
MaybeLocal<Object> X509Certificate::New(Environment* env,
X509Pointer cert,
STACK_OF(X509) * issuer_chain) {
std::shared_ptr<ManagedX509> mcert(new ManagedX509(std::move(cert)));
return New(env, std::move(mcert), issuer_chain);
}
MaybeLocal<Object> X509Certificate::New(
Environment* env,
std::shared_ptr<ManagedX509> cert,
STACK_OF(X509)* issuer_chain) {
MaybeLocal<Object> X509Certificate::New(Environment* env,
std::shared_ptr<ManagedX509> cert,
STACK_OF(X509) * issuer_chain) {
EscapableHandleScope scope(env->isolate());
Local<Function> ctor;
if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor))
@ -130,22 +455,19 @@ MaybeLocal<Object> X509Certificate::New(
return scope.Escape(obj);
}
MaybeLocal<Object> X509Certificate::GetCert(
Environment* env,
const SSLPointer& ssl) {
MaybeLocal<Object> X509Certificate::GetCert(Environment* env,
const SSLPointer& ssl) {
ClearErrorOnReturn clear_error_on_return;
X509* cert = SSL_get_certificate(ssl.get());
if (cert == nullptr)
return MaybeLocal<Object>();
if (cert == nullptr) return MaybeLocal<Object>();
X509Pointer ptr(X509_dup(cert));
return New(env, std::move(ptr));
}
MaybeLocal<Object> X509Certificate::GetPeerCert(
Environment* env,
const SSLPointer& ssl,
GetPeerCertificateFlag flag) {
MaybeLocal<Object> X509Certificate::GetPeerCert(Environment* env,
const SSLPointer& ssl,
GetPeerCertificateFlag flag) {
ClearErrorOnReturn clear_error_on_return;
MaybeLocal<Object> maybe_cert;
@ -164,80 +486,8 @@ MaybeLocal<Object> X509Certificate::GetPeerCert(
sk_X509_delete(ssl_certs, 0);
}
return sk_X509_num(ssl_certs)
? New(env, std::move(cert), ssl_certs)
: New(env, std::move(cert));
}
void X509Certificate::Parse(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsArrayBufferView());
ArrayBufferViewContents<unsigned char> buf(args[0].As<ArrayBufferView>());
const unsigned char* data = buf.data();
unsigned data_len = buf.length();
ClearErrorOnReturn clear_error_on_return;
BIOPointer bio(LoadBIO(env, args[0]));
if (!bio)
return ThrowCryptoError(env, ERR_get_error());
Local<Object> cert;
X509Pointer pem(PEM_read_bio_X509_AUX(
bio.get(), nullptr, NoPasswordCallback, nullptr));
if (!pem) {
// Try as DER, but return the original PEM failure if it isn't DER.
MarkPopErrorOnReturn mark_here;
X509Pointer der(d2i_X509(nullptr, &data, data_len));
if (!der)
return ThrowCryptoError(env, ERR_get_error());
if (!X509Certificate::New(env, std::move(der)).ToLocal(&cert))
return;
} else if (!X509Certificate::New(env, std::move(pem)).ToLocal(&cert)) {
return;
}
args.GetReturnValue().Set(cert);
}
template <MaybeLocal<Value> Property(
Environment* env, X509* cert, const BIOPointer& bio)>
static void ReturnPropertyThroughBIO(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
BIOPointer bio(BIO_new(BIO_s_mem()));
CHECK(bio);
Local<Value> ret;
if (Property(env, cert->get(), bio).ToLocal(&ret))
args.GetReturnValue().Set(ret);
}
void X509Certificate::Subject(const FunctionCallbackInfo<Value>& args) {
ReturnPropertyThroughBIO<GetSubject>(args);
}
void X509Certificate::Issuer(const FunctionCallbackInfo<Value>& args) {
ReturnPropertyThroughBIO<GetIssuerString>(args);
}
void X509Certificate::SubjectAltName(const FunctionCallbackInfo<Value>& args) {
ReturnPropertyThroughBIO<GetSubjectAltNameString>(args);
}
void X509Certificate::InfoAccess(const FunctionCallbackInfo<Value>& args) {
ReturnPropertyThroughBIO<GetInfoAccessString>(args);
}
void X509Certificate::ValidFrom(const FunctionCallbackInfo<Value>& args) {
ReturnPropertyThroughBIO<GetValidFrom>(args);
}
void X509Certificate::ValidTo(const FunctionCallbackInfo<Value>& args) {
ReturnPropertyThroughBIO<GetValidTo>(args);
return sk_X509_num(ssl_certs) ? New(env, std::move(cert), ssl_certs)
: New(env, std::move(cert));
}
template <MaybeLocal<Value> Property(Environment* env, X509* cert)>
@ -249,207 +499,6 @@ static void ReturnProperty(const FunctionCallbackInfo<Value>& args) {
if (Property(env, cert->get()).ToLocal(&ret)) args.GetReturnValue().Set(ret);
}
void X509Certificate::KeyUsage(const FunctionCallbackInfo<Value>& args) {
ReturnProperty<GetKeyUsage>(args);
}
void X509Certificate::SerialNumber(const FunctionCallbackInfo<Value>& args) {
ReturnProperty<GetSerialNumber>(args);
}
void X509Certificate::Raw(const FunctionCallbackInfo<Value>& args) {
ReturnProperty<GetRawDERCertificate>(args);
}
void X509Certificate::PublicKey(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
// TODO(tniessen): consider checking X509_get_pubkey() when the
// X509Certificate object is being created.
ClearErrorOnReturn clear_error_on_return;
EVPKeyPointer pkey(X509_get_pubkey(cert->get()));
if (!pkey) return ThrowCryptoError(env, ERR_get_error());
ManagedEVPPKey epkey(std::move(pkey));
std::shared_ptr<KeyObjectData> key_data =
KeyObjectData::CreateAsymmetric(kKeyTypePublic, epkey);
Local<Value> ret;
if (KeyObjectHandle::Create(env, key_data).ToLocal(&ret))
args.GetReturnValue().Set(ret);
}
void X509Certificate::Pem(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
BIOPointer bio(BIO_new(BIO_s_mem()));
CHECK(bio);
if (PEM_write_bio_X509(bio.get(), cert->get()))
args.GetReturnValue().Set(ToV8Value(env, bio));
}
void X509Certificate::CheckCA(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ClearErrorOnReturn clear_error_on_return;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
args.GetReturnValue().Set(X509_check_ca(cert->get()) == 1);
}
void X509Certificate::CheckHost(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsString()); // name
CHECK(args[1]->IsUint32()); // flags
Utf8Value name(env->isolate(), args[0]);
uint32_t flags = args[1].As<Uint32>()->Value();
char* peername;
switch (X509_check_host(
cert->get(),
*name,
name.length(),
flags,
&peername)) {
case 1: { // Match!
Local<Value> ret = args[0];
if (peername != nullptr) {
ret = OneByteString(env->isolate(), peername);
OPENSSL_free(peername);
}
return args.GetReturnValue().Set(ret);
}
case 0: // No Match!
return; // No return value is set
case -2: // Error!
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid name");
default: // Error!
return THROW_ERR_CRYPTO_OPERATION_FAILED(env);
}
}
void X509Certificate::CheckEmail(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsString()); // name
CHECK(args[1]->IsUint32()); // flags
Utf8Value name(env->isolate(), args[0]);
uint32_t flags = args[1].As<Uint32>()->Value();
switch (X509_check_email(
cert->get(),
*name,
name.length(),
flags)) {
case 1: // Match!
return args.GetReturnValue().Set(args[0]);
case 0: // No Match!
return; // No return value is set
case -2: // Error!
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid name");
default: // Error!
return THROW_ERR_CRYPTO_OPERATION_FAILED(env);
}
}
void X509Certificate::CheckIP(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsString()); // IP
CHECK(args[1]->IsUint32()); // flags
Utf8Value name(env->isolate(), args[0]);
uint32_t flags = args[1].As<Uint32>()->Value();
switch (X509_check_ip_asc(cert->get(), *name, flags)) {
case 1: // Match!
return args.GetReturnValue().Set(args[0]);
case 0: // No Match!
return; // No return value is set
case -2: // Error!
return THROW_ERR_INVALID_ARG_VALUE(env, "Invalid IP");
default: // Error!
return THROW_ERR_CRYPTO_OPERATION_FAILED(env);
}
}
void X509Certificate::CheckIssued(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsObject());
CHECK(X509Certificate::HasInstance(env, args[0].As<Object>()));
X509Certificate* issuer;
ASSIGN_OR_RETURN_UNWRAP(&issuer, args[0]);
ClearErrorOnReturn clear_error_on_return;
args.GetReturnValue().Set(
X509_check_issued(issuer->get(), cert->get()) == X509_V_OK);
}
void X509Certificate::CheckPrivateKey(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsObject());
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[0]);
CHECK_EQ(key->Data()->GetKeyType(), kKeyTypePrivate);
ClearErrorOnReturn clear_error_on_return;
args.GetReturnValue().Set(
X509_check_private_key(
cert->get(),
key->Data()->GetAsymmetricKey().get()) == 1);
}
void X509Certificate::Verify(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
CHECK(args[0]->IsObject());
KeyObjectHandle* key;
ASSIGN_OR_RETURN_UNWRAP(&key, args[0]);
CHECK_EQ(key->Data()->GetKeyType(), kKeyTypePublic);
ClearErrorOnReturn clear_error_on_return;
args.GetReturnValue().Set(
X509_verify(
cert->get(),
key->Data()->GetAsymmetricKey().get()) > 0);
}
void X509Certificate::ToLegacy(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
ClearErrorOnReturn clear_error_on_return;
Local<Value> ret;
if (X509ToObject(env, cert->get()).ToLocal(&ret))
args.GetReturnValue().Set(ret);
}
void X509Certificate::GetIssuerCert(const FunctionCallbackInfo<Value>& args) {
X509Certificate* cert;
ASSIGN_OR_RETURN_UNWRAP(&cert, args.This());
if (cert->issuer_cert_)
args.GetReturnValue().Set(cert->issuer_cert_->object());
}
X509Certificate::X509Certificate(
Environment* env,
Local<Object> object,
@ -504,7 +553,7 @@ std::unique_ptr<worker::TransferData> X509Certificate::CloneForMessaging()
void X509Certificate::Initialize(Environment* env, Local<Object> target) {
SetMethod(env->context(), target, "parseX509", X509Certificate::Parse);
SetMethod(env->context(), target, "parseX509", Parse);
NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT);
NODE_DEFINE_CONSTANT(target, X509_CHECK_FLAG_NEVER_CHECK_SUBJECT);
@ -516,7 +565,7 @@ void X509Certificate::Initialize(Environment* env, Local<Object> target) {
void X509Certificate::RegisterExternalReferences(
ExternalReferenceRegistry* registry) {
registry->Register(X509Certificate::Parse);
registry->Register(Parse);
registry->Register(Subject);
registry->Register(SubjectAltName);
registry->Register(InfoAccess);
@ -529,7 +578,7 @@ void X509Certificate::RegisterExternalReferences(
registry->Register(KeyUsage);
registry->Register(SerialNumber);
registry->Register(Pem);
registry->Register(Raw);
registry->Register(Der);
registry->Register(PublicKey);
registry->Register(CheckCA);
registry->Register(CheckHost);
@ -537,7 +586,7 @@ void X509Certificate::RegisterExternalReferences(
registry->Register(CheckIP);
registry->Register(CheckIssued);
registry->Register(CheckPrivateKey);
registry->Register(Verify);
registry->Register(CheckPublicKey);
registry->Register(ToLegacy);
registry->Register(GetIssuerCert);
}

View File

@ -7,6 +7,7 @@
#include "crypto/crypto_util.h"
#include "env.h"
#include "memory_tracker.h"
#include "ncrypto.h"
#include "node_worker.h"
#include "v8.h"
@ -17,7 +18,7 @@ namespace crypto {
// X509 objects that allows an X509Certificate instance to
// be cloned at the JS level while pointing at the same
// underlying X509 instance.
class ManagedX509 : public MemoryRetainer {
class ManagedX509 final : public MemoryRetainer {
public:
ManagedX509() = default;
explicit ManagedX509(X509Pointer&& cert);
@ -26,6 +27,8 @@ class ManagedX509 : public MemoryRetainer {
operator bool() const { return !!cert_; }
X509* get() const { return cert_.get(); }
ncrypto::X509View view() const { return cert_; }
operator ncrypto::X509View() const { return cert_; }
void MemoryInfo(MemoryTracker* tracker) const override;
SET_MEMORY_INFO_NAME(ManagedX509)
@ -35,7 +38,7 @@ class ManagedX509 : public MemoryRetainer {
X509Pointer cert_;
};
class X509Certificate : public BaseObject {
class X509Certificate final : public BaseObject {
public:
enum class GetPeerCertificateFlag {
NONE,
@ -72,28 +75,11 @@ class X509Certificate : public BaseObject {
v8::Local<v8::Object> object,
X509Pointer cert);
static void Parse(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Subject(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SubjectAltName(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Issuer(const v8::FunctionCallbackInfo<v8::Value>& args);
static void InfoAccess(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ValidFrom(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ValidTo(const v8::FunctionCallbackInfo<v8::Value>& args);
static void KeyUsage(const v8::FunctionCallbackInfo<v8::Value>& args);
static void SerialNumber(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Raw(const v8::FunctionCallbackInfo<v8::Value>& args);
static void PublicKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Pem(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CheckCA(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CheckHost(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CheckEmail(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CheckIP(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CheckIssued(const v8::FunctionCallbackInfo<v8::Value>& args);
static void CheckPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
static void Verify(const v8::FunctionCallbackInfo<v8::Value>& args);
static void ToLegacy(const v8::FunctionCallbackInfo<v8::Value>& args);
static void GetIssuerCert(const v8::FunctionCallbackInfo<v8::Value>& args);
inline BaseObjectPtr<X509Certificate> getIssuerCert() const {
return issuer_cert_;
}
inline ncrypto::X509View view() const { return *cert_; }
X509* get() { return cert_->get(); }
void MemoryInfo(MemoryTracker* tracker) const override;