diff --git a/deps/ncrypto/engine.cc b/deps/ncrypto/engine.cc index de708d1c13cf3e..6bb827dcab0dc1 100644 --- a/deps/ncrypto/engine.cc +++ b/deps/ncrypto/engine.cc @@ -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) { diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 9a70b52d8504b1..3f9a96e0833bcc 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -1,12 +1,12 @@ #include "ncrypto.h" #include #include -#include "openssl/bn.h" -#include "openssl/evp.h" -#include "openssl/pkcs12.h" -#include "openssl/x509v3.h" +#include +#include +#include +#include #if OPENSSL_VERSION_MAJOR >= 3 -#include "openssl/provider.h" +#include #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(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(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(cert_))) { + if (auto bn = BignumPointer(ASN1_INTEGER_to_BN(serial_number, nullptr))) { + return bn.toHex(); + } + } + return {}; +} + +Result X509View::getPublicKey() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return Result(EVPKeyPointer {}); + auto pkey = EVPKeyPointer(X509_get_pubkey(const_cast(cert_))); + if (!pkey) return Result(ERR_get_error()); + return pkey; +} + +StackOfASN1 X509View::getKeyUsage() const { + ClearErrorOnReturn clearErrorOnReturn; + if (cert_ == nullptr) return {}; + return StackOfASN1(static_cast( + 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(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(issuer.cert_), + const_cast(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(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(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(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(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(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::Parse(Buffer buffer) { + ClearErrorOnReturn clearErrorOnReturn; + BIOPointer bio(BIO_new_mem_buf(buffer.data, buffer.len)); + if (!bio) return Result(ERR_get_error()); + + X509Pointer pem(PEM_read_bio_X509_AUX(bio.get(), nullptr, NoPasswordCallback, nullptr)); + if (pem) return Result(std::move(pem)); + BIO_reset(bio.get()); + + X509Pointer der(d2i_X509_bio(bio.get(), nullptr)); + if (der) return Result(std::move(der)); + + return Result(ERR_get_error()); +} } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index 633309aa0cbc6c..b646d2e90b734d 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -6,7 +6,7 @@ #include #include #include -#include "openssl/bn.h" +#include #include #include #include @@ -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 @@ class MarkPopErrorOnReturn final { CryptoErrorList* errors_; }; +template +struct Result final { + T value; + std::optional 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; using SSLCtxPointer = DeleteFnPtr; using SSLPointer = DeleteFnPtr; using SSLSessionPointer = DeleteFnPtr; -using X509Pointer = DeleteFnPtr; + +struct StackOfXASN1Deleter { + void operator()(STACK_OF(ASN1_OBJECT)* p) const { + sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); + } +}; +using StackOfASN1 = std::unique_ptr; // An unowned, unmanaged pointer to a buffer of data. template @@ -290,6 +311,74 @@ class BignumPointer final { DeleteFnPtr 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 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 Parse(Buffer 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 cert_; +}; + #ifndef OPENSSL_NO_ENGINE class EnginePointer final { public: diff --git a/src/crypto/crypto_common.h b/src/crypto/crypto_common.h index 449f06ae021627..32dee8e6107880 100644 --- a/src/crypto/crypto_common.h +++ b/src/crypto/crypto_common.h @@ -3,10 +3,11 @@ #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS -#include "node_crypto.h" -#include "v8.h" #include #include +#include "ncrypto.h" +#include "node_crypto.h" +#include "v8.h" #include @@ -26,12 +27,7 @@ struct StackOfX509Deleter { }; using StackOfX509 = std::unique_ptr; -struct StackOfXASN1Deleter { - void operator()(STACK_OF(ASN1_OBJECT)* p) const { - sk_ASN1_OBJECT_pop_free(p, ASN1_OBJECT_free); - } -}; -using StackOfASN1 = std::unique_ptr; +using StackOfASN1 = ncrypto::StackOfASN1; X509Pointer SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert); diff --git a/src/crypto/crypto_keys.h b/src/crypto/crypto_keys.h index 32c50b52188a85..11d4e5cdf71e02 100644 --- a/src/crypto/crypto_keys.h +++ b/src/crypto/crypto_keys.h @@ -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; diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc index a5d40e1bf17978..fc400f47757756 100644 --- a/src/crypto/crypto_x509.cc +++ b/src/crypto/crypto_x509.cc @@ -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,246 +63,220 @@ void Fingerprint(const FunctionCallbackInfo& args) { X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); Local ret; - if (GetFingerprintDigest(env, algo(), cert->get()).ToLocal(&ret)) + if (GetFingerprintDigest(env, algo(), cert->get()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); -} -} // namespace - -Local X509Certificate::GetConstructorTemplate( - Environment* env) { - Local tmpl = env->x509_constructor_template(); - if (tmpl.IsEmpty()) { - Isolate* isolate = env->isolate(); - tmpl = NewFunctionTemplate(isolate, nullptr); - tmpl->InstanceTemplate()->SetInternalFieldCount( - BaseObject::kInternalFieldCount); - tmpl->SetClassName( - FIXED_ONE_BYTE_STRING(env->isolate(), "X509Certificate")); - SetProtoMethod(isolate, tmpl, "subject", Subject); - SetProtoMethod(isolate, tmpl, "subjectAltName", SubjectAltName); - SetProtoMethod(isolate, tmpl, "infoAccess", InfoAccess); - SetProtoMethod(isolate, tmpl, "issuer", Issuer); - SetProtoMethod(isolate, tmpl, "validTo", ValidTo); - SetProtoMethod(isolate, tmpl, "validFrom", ValidFrom); - SetProtoMethod(isolate, tmpl, "fingerprint", Fingerprint); - SetProtoMethod(isolate, tmpl, "fingerprint256", Fingerprint); - SetProtoMethod(isolate, tmpl, "fingerprint512", Fingerprint); - SetProtoMethod(isolate, tmpl, "keyUsage", KeyUsage); - SetProtoMethod(isolate, tmpl, "serialNumber", SerialNumber); - SetProtoMethod(isolate, tmpl, "pem", Pem); - SetProtoMethod(isolate, tmpl, "raw", Raw); - SetProtoMethod(isolate, tmpl, "publicKey", PublicKey); - SetProtoMethod(isolate, tmpl, "checkCA", CheckCA); - SetProtoMethod(isolate, tmpl, "checkHost", CheckHost); - SetProtoMethod(isolate, tmpl, "checkEmail", CheckEmail); - SetProtoMethod(isolate, tmpl, "checkIP", CheckIP); - SetProtoMethod(isolate, tmpl, "checkIssued", CheckIssued); - SetProtoMethod(isolate, tmpl, "checkPrivateKey", CheckPrivateKey); - SetProtoMethod(isolate, tmpl, "verify", Verify); - SetProtoMethod(isolate, tmpl, "toLegacy", ToLegacy); - SetProtoMethod(isolate, tmpl, "getIssuerCert", GetIssuerCert); - env->set_x509_constructor_template(tmpl); } - return tmpl; } -bool X509Certificate::HasInstance(Environment* env, Local object) { - return GetConstructorTemplate(env)->HasInstance(object); +MaybeLocal ToV8Value(Local context, BIOPointer&& bio) { + if (!bio) return {}; + BUF_MEM* mem; + BIO_get_mem_ptr(bio.get(), &mem); + Local ret; + if (!String::NewFromUtf8(context->GetIsolate(), + mem->data, + NewStringType::kNormal, + mem->length) + .ToLocal(&ret)) + return {}; + return ret; +} + +MaybeLocal 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(data)); + }, + bio.release()); + auto ab = ArrayBuffer::New(env->isolate(), std::move(backing)); + Local ret; + if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&ret)) return {}; + return ret; } -MaybeLocal X509Certificate::New( - Environment* env, - X509Pointer cert, - STACK_OF(X509)* issuer_chain) { - std::shared_ptr mcert(new ManagedX509(std::move(cert))); - return New(env, std::move(mcert), issuer_chain); +void Pem(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + Local ret; + if (ToV8Value(env->context(), cert->view().toPEM()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } -MaybeLocal X509Certificate::New( - Environment* env, - std::shared_ptr cert, - STACK_OF(X509)* issuer_chain) { - EscapableHandleScope scope(env->isolate()); - Local ctor; - if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor)) - return MaybeLocal(); - - Local obj; - if (!ctor->NewInstance(env->context()).ToLocal(&obj)) - return MaybeLocal(); - - new X509Certificate(env, obj, std::move(cert), issuer_chain); - return scope.Escape(obj); +void Der(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + Local ret; + if (ToBuffer(env, cert->view().toDER()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } -MaybeLocal X509Certificate::GetCert( - Environment* env, - const SSLPointer& ssl) { - ClearErrorOnReturn clear_error_on_return; - X509* cert = SSL_get_certificate(ssl.get()); - if (cert == nullptr) - return MaybeLocal(); - - X509Pointer ptr(X509_dup(cert)); - return New(env, std::move(ptr)); +void Subject(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + Local ret; + if (ToV8Value(env->context(), cert->view().getSubject()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } -MaybeLocal X509Certificate::GetPeerCert( - Environment* env, - const SSLPointer& ssl, - GetPeerCertificateFlag flag) { - ClearErrorOnReturn clear_error_on_return; - MaybeLocal maybe_cert; - - bool is_server = - static_cast(flag) & static_cast(GetPeerCertificateFlag::SERVER); - - X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr); - STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get()); - if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) - return MaybeLocal(); - - std::vector> certs; - - if (!cert) { - cert.reset(sk_X509_value(ssl_certs, 0)); - sk_X509_delete(ssl_certs, 0); +void SubjectAltName(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + Local ret; + if (ToV8Value(env->context(), cert->view().getSubjectAltName()) + .ToLocal(&ret)) { + args.GetReturnValue().Set(ret); } - - return sk_X509_num(ssl_certs) - ? New(env, std::move(cert), ssl_certs) - : New(env, std::move(cert)); } -void X509Certificate::Parse(const FunctionCallbackInfo& args) { +void Issuer(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); - - CHECK(args[0]->IsArrayBufferView()); - ArrayBufferViewContents buf(args[0].As()); - 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 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; + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + Local ret; + if (ToV8Value(env->context(), cert->view().getIssuer()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); } - - args.GetReturnValue().Set(cert); } -template Property( - Environment* env, X509* cert, const BIOPointer& bio)> -static void ReturnPropertyThroughBIO(const FunctionCallbackInfo& args) { +void InfoAccess(const FunctionCallbackInfo& 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 ret; - if (Property(env, cert->get(), bio).ToLocal(&ret)) + if (ToV8Value(env->context(), cert->view().getInfoAccess()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); + } } -void X509Certificate::Subject(const FunctionCallbackInfo& args) { - ReturnPropertyThroughBIO(args); +void ValidFrom(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + Local ret; + if (ToV8Value(env->context(), cert->view().getValidFrom()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } -void X509Certificate::Issuer(const FunctionCallbackInfo& args) { - ReturnPropertyThroughBIO(args); +void ValidTo(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + Local ret; + if (ToV8Value(env->context(), cert->view().getValidTo()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } -void X509Certificate::SubjectAltName(const FunctionCallbackInfo& args) { - ReturnPropertyThroughBIO(args); +void SerialNumber(const FunctionCallbackInfo& 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(serial.get()))); + } } -void X509Certificate::InfoAccess(const FunctionCallbackInfo& args) { - ReturnPropertyThroughBIO(args); -} +void PublicKey(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); -void X509Certificate::ValidFrom(const FunctionCallbackInfo& args) { - ReturnPropertyThroughBIO(args); -} + // 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 key_data = KeyObjectData::CreateAsymmetric( + kKeyTypePublic, ManagedEVPPKey(std::move(result.value))); -void X509Certificate::ValidTo(const FunctionCallbackInfo& args) { - ReturnPropertyThroughBIO(args); + Local ret; + if (KeyObjectHandle::Create(env, std::move(key_data)).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } } -template Property(Environment* env, X509* cert)> -static void ReturnProperty(const FunctionCallbackInfo& args) { +void KeyUsage(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); - Local ret; - if (Property(env, cert->get()).ToLocal(&ret)) args.GetReturnValue().Set(ret); -} -void X509Certificate::KeyUsage(const FunctionCallbackInfo& args) { - ReturnProperty(args); -} + auto eku = cert->view().getKeyUsage(); + if (!eku) return; + + const int count = sk_ASN1_OBJECT_num(eku.get()); + MaybeStackBuffer, 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); + } + } -void X509Certificate::SerialNumber(const FunctionCallbackInfo& args) { - ReturnProperty(args); + args.GetReturnValue().Set( + Array::New(env->isolate(), ext_key_usage.out(), count)); } -void X509Certificate::Raw(const FunctionCallbackInfo& args) { - ReturnProperty(args); +void CheckCA(const FunctionCallbackInfo& args) { + X509Certificate* cert; + ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + args.GetReturnValue().Set(cert->view().isCA()); } -void X509Certificate::PublicKey(const FunctionCallbackInfo& args) { +void CheckIssued(const FunctionCallbackInfo& 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 key_data = - KeyObjectData::CreateAsymmetric(kKeyTypePublic, epkey); - - Local ret; - if (KeyObjectHandle::Create(env, key_data).ToLocal(&ret)) - args.GetReturnValue().Set(ret); + CHECK(args[0]->IsObject()); + CHECK(X509Certificate::HasInstance(env, args[0].As())); + X509Certificate* issuer; + ASSIGN_OR_RETURN_UNWRAP(&issuer, args[0]); + args.GetReturnValue().Set(cert->view().isIssuedBy(issuer->view())); } -void X509Certificate::Pem(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); +void CheckPrivateKey(const FunctionCallbackInfo& 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)); + 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 X509Certificate::CheckCA(const FunctionCallbackInfo& args) { +void CheckPublicKey(const FunctionCallbackInfo& args) { X509Certificate* cert; - ClearErrorOnReturn clear_error_on_return; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); - args.GetReturnValue().Set(X509_check_ca(cert->get()) == 1); + + 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 X509Certificate::CheckHost(const FunctionCallbackInfo& args) { +void CheckHost(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); @@ -307,32 +286,28 @@ void X509Certificate::CheckHost(const FunctionCallbackInfo& args) { Utf8Value name(env->isolate(), args[0]); uint32_t flags = args[1].As()->Value(); - char* peername; - - switch (X509_check_host( - cert->get(), - *name, - name.length(), - flags, - &peername)) { - case 1: { // Match! + ncrypto::DataPointer peername; + + switch (cert->view().checkHost(name.ToStringView(), flags, &peername)) { + case ncrypto::X509View::CheckMatch::MATCH: { // Match! Local ret = args[0]; - if (peername != nullptr) { - ret = OneByteString(env->isolate(), peername); - OPENSSL_free(peername); + if (peername) { + ret = OneByteString(env->isolate(), + static_cast(peername.get()), + peername.size()); } return args.GetReturnValue().Set(ret); } - case 0: // No Match! + case ncrypto::X509View::CheckMatch::NO_MATCH: // No Match! return; // No return value is set - case -2: // Error! + 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 X509Certificate::CheckEmail(const FunctionCallbackInfo& args) { +void CheckEmail(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); @@ -343,23 +318,19 @@ void X509Certificate::CheckEmail(const FunctionCallbackInfo& args) { Utf8Value name(env->isolate(), args[0]); uint32_t flags = args[1].As()->Value(); - switch (X509_check_email( - cert->get(), - *name, - name.length(), - flags)) { - case 1: // Match! + switch (cert->view().checkEmail(name.ToStringView(), flags)) { + case ncrypto::X509View::CheckMatch::MATCH: // Match! return args.GetReturnValue().Set(args[0]); - case 0: // No Match! + case ncrypto::X509View::CheckMatch::NO_MATCH: // No Match! return; // No return value is set - case -2: // Error! + 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 X509Certificate::CheckIP(const FunctionCallbackInfo& args) { +void CheckIP(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); @@ -370,84 +341,162 @@ void X509Certificate::CheckIP(const FunctionCallbackInfo& args) { Utf8Value name(env->isolate(), args[0]); uint32_t flags = args[1].As()->Value(); - switch (X509_check_ip_asc(cert->get(), *name, flags)) { - case 1: // Match! + switch (cert->view().checkIp(name.ToStringView(), flags)) { + case ncrypto::X509View::CheckMatch::MATCH: // Match! return args.GetReturnValue().Set(args[0]); - case 0: // No Match! + case ncrypto::X509View::CheckMatch::NO_MATCH: // No Match! return; // No return value is set - case -2: // Error! + 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 X509Certificate::CheckIssued(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); +void GetIssuerCert(const FunctionCallbackInfo& args) { X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + auto issuer = cert->getIssuerCert(); + if (issuer) args.GetReturnValue().Set(issuer->object()); +} - CHECK(args[0]->IsObject()); - CHECK(X509Certificate::HasInstance(env, args[0].As())); +void Parse(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args[0]->IsArrayBufferView()); + ArrayBufferViewContents buf(args[0].As()); + Local cert; - X509Certificate* issuer; - ASSIGN_OR_RETURN_UNWRAP(&issuer, args[0]); + auto result = X509Pointer::Parse(ncrypto::Buffer{ + .data = buf.data(), + .len = buf.length(), + }); - ClearErrorOnReturn clear_error_on_return; + if (!result.value) return ThrowCryptoError(env, result.error.value_or(0)); - args.GetReturnValue().Set( - X509_check_issued(issuer->get(), cert->get()) == X509_V_OK); + if (X509Certificate::New(env, std::move(result.value)).ToLocal(&cert)) { + args.GetReturnValue().Set(cert); + } } -void X509Certificate::CheckPrivateKey(const FunctionCallbackInfo& args) { +void ToLegacy(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); + ClearErrorOnReturn clear_error_on_return; + Local ret; + if (X509ToObject(env, cert->get()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); + } +} +} // namespace - CHECK(args[0]->IsObject()); - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args[0]); - CHECK_EQ(key->Data()->GetKeyType(), kKeyTypePrivate); +Local X509Certificate::GetConstructorTemplate( + Environment* env) { + Local tmpl = env->x509_constructor_template(); + if (tmpl.IsEmpty()) { + Isolate* isolate = env->isolate(); + tmpl = NewFunctionTemplate(isolate, nullptr); + tmpl->InstanceTemplate()->SetInternalFieldCount( + BaseObject::kInternalFieldCount); + tmpl->SetClassName( + FIXED_ONE_BYTE_STRING(env->isolate(), "X509Certificate")); + SetProtoMethod(isolate, tmpl, "subject", Subject); + SetProtoMethod(isolate, tmpl, "subjectAltName", SubjectAltName); + SetProtoMethod(isolate, tmpl, "infoAccess", InfoAccess); + SetProtoMethod(isolate, tmpl, "issuer", Issuer); + SetProtoMethod(isolate, tmpl, "validTo", ValidTo); + SetProtoMethod(isolate, tmpl, "validFrom", ValidFrom); + SetProtoMethod(isolate, tmpl, "fingerprint", Fingerprint); + SetProtoMethod(isolate, tmpl, "fingerprint256", Fingerprint); + SetProtoMethod(isolate, tmpl, "fingerprint512", Fingerprint); + SetProtoMethod(isolate, tmpl, "keyUsage", KeyUsage); + SetProtoMethod(isolate, tmpl, "serialNumber", SerialNumber); + SetProtoMethod(isolate, tmpl, "pem", Pem); + SetProtoMethod(isolate, tmpl, "raw", Der); + SetProtoMethod(isolate, tmpl, "publicKey", PublicKey); + SetProtoMethod(isolate, tmpl, "checkCA", CheckCA); + SetProtoMethod(isolate, tmpl, "checkHost", CheckHost); + SetProtoMethod(isolate, tmpl, "checkEmail", CheckEmail); + SetProtoMethod(isolate, tmpl, "checkIP", CheckIP); + SetProtoMethod(isolate, tmpl, "checkIssued", CheckIssued); + SetProtoMethod(isolate, tmpl, "checkPrivateKey", CheckPrivateKey); + SetProtoMethod(isolate, tmpl, "verify", CheckPublicKey); + SetProtoMethod(isolate, tmpl, "toLegacy", ToLegacy); + SetProtoMethod(isolate, tmpl, "getIssuerCert", GetIssuerCert); + env->set_x509_constructor_template(tmpl); + } + return tmpl; +} - ClearErrorOnReturn clear_error_on_return; +bool X509Certificate::HasInstance(Environment* env, Local object) { + return GetConstructorTemplate(env)->HasInstance(object); +} - args.GetReturnValue().Set( - X509_check_private_key( - cert->get(), - key->Data()->GetAsymmetricKey().get()) == 1); +MaybeLocal X509Certificate::New(Environment* env, + X509Pointer cert, + STACK_OF(X509) * issuer_chain) { + std::shared_ptr mcert(new ManagedX509(std::move(cert))); + return New(env, std::move(mcert), issuer_chain); } -void X509Certificate::Verify(const FunctionCallbackInfo& args) { - X509Certificate* cert; - ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); +MaybeLocal X509Certificate::New(Environment* env, + std::shared_ptr cert, + STACK_OF(X509) * issuer_chain) { + EscapableHandleScope scope(env->isolate()); + Local ctor; + if (!GetConstructorTemplate(env)->GetFunction(env->context()).ToLocal(&ctor)) + return MaybeLocal(); - CHECK(args[0]->IsObject()); - KeyObjectHandle* key; - ASSIGN_OR_RETURN_UNWRAP(&key, args[0]); - CHECK_EQ(key->Data()->GetKeyType(), kKeyTypePublic); + Local obj; + if (!ctor->NewInstance(env->context()).ToLocal(&obj)) + return MaybeLocal(); + + new X509Certificate(env, obj, std::move(cert), issuer_chain); + return scope.Escape(obj); +} +MaybeLocal X509Certificate::GetCert(Environment* env, + const SSLPointer& ssl) { ClearErrorOnReturn clear_error_on_return; + X509* cert = SSL_get_certificate(ssl.get()); + if (cert == nullptr) return MaybeLocal(); - args.GetReturnValue().Set( - X509_verify( - cert->get(), - key->Data()->GetAsymmetricKey().get()) > 0); + X509Pointer ptr(X509_dup(cert)); + return New(env, std::move(ptr)); } -void X509Certificate::ToLegacy(const FunctionCallbackInfo& args) { - Environment* env = Environment::GetCurrent(args); - X509Certificate* cert; - ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); +MaybeLocal X509Certificate::GetPeerCert(Environment* env, + const SSLPointer& ssl, + GetPeerCertificateFlag flag) { ClearErrorOnReturn clear_error_on_return; - Local ret; - if (X509ToObject(env, cert->get()).ToLocal(&ret)) - args.GetReturnValue().Set(ret); + MaybeLocal maybe_cert; + + bool is_server = + static_cast(flag) & static_cast(GetPeerCertificateFlag::SERVER); + + X509Pointer cert(is_server ? SSL_get_peer_certificate(ssl.get()) : nullptr); + STACK_OF(X509)* ssl_certs = SSL_get_peer_cert_chain(ssl.get()); + if (!cert && (ssl_certs == nullptr || sk_X509_num(ssl_certs) == 0)) + return MaybeLocal(); + + std::vector> certs; + + if (!cert) { + cert.reset(sk_X509_value(ssl_certs, 0)); + 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::GetIssuerCert(const FunctionCallbackInfo& args) { +template Property(Environment* env, X509* cert)> +static void ReturnProperty(const FunctionCallbackInfo& args) { + Environment* env = Environment::GetCurrent(args); X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); - if (cert->issuer_cert_) - args.GetReturnValue().Set(cert->issuer_cert_->object()); + Local ret; + if (Property(env, cert->get()).ToLocal(&ret)) args.GetReturnValue().Set(ret); } X509Certificate::X509Certificate( @@ -504,7 +553,7 @@ std::unique_ptr X509Certificate::CloneForMessaging() void X509Certificate::Initialize(Environment* env, Local 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 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); } diff --git a/src/crypto/crypto_x509.h b/src/crypto/crypto_x509.h index 00b7689a194a32..d01defa5daaafa 100644 --- a/src/crypto/crypto_x509.h +++ b/src/crypto/crypto_x509.h @@ -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 object, X509Pointer cert); - static void Parse(const v8::FunctionCallbackInfo& args); - static void Subject(const v8::FunctionCallbackInfo& args); - static void SubjectAltName(const v8::FunctionCallbackInfo& args); - static void Issuer(const v8::FunctionCallbackInfo& args); - static void InfoAccess(const v8::FunctionCallbackInfo& args); - static void ValidFrom(const v8::FunctionCallbackInfo& args); - static void ValidTo(const v8::FunctionCallbackInfo& args); - static void KeyUsage(const v8::FunctionCallbackInfo& args); - static void SerialNumber(const v8::FunctionCallbackInfo& args); - static void Raw(const v8::FunctionCallbackInfo& args); - static void PublicKey(const v8::FunctionCallbackInfo& args); - static void Pem(const v8::FunctionCallbackInfo& args); - static void CheckCA(const v8::FunctionCallbackInfo& args); - static void CheckHost(const v8::FunctionCallbackInfo& args); - static void CheckEmail(const v8::FunctionCallbackInfo& args); - static void CheckIP(const v8::FunctionCallbackInfo& args); - static void CheckIssued(const v8::FunctionCallbackInfo& args); - static void CheckPrivateKey(const v8::FunctionCallbackInfo& args); - static void Verify(const v8::FunctionCallbackInfo& args); - static void ToLegacy(const v8::FunctionCallbackInfo& args); - static void GetIssuerCert(const v8::FunctionCallbackInfo& args); + inline BaseObjectPtr getIssuerCert() const { + return issuer_cert_; + } + inline ncrypto::X509View view() const { return *cert_; } X509* get() { return cert_->get(); } void MemoryInfo(MemoryTracker* tracker) const override;