diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc index 3f9a96e0833..df5bd6f33c4 100644 --- a/deps/ncrypto/ncrypto.cc +++ b/deps/ncrypto/ncrypto.cc @@ -908,6 +908,24 @@ X509View::CheckMatch X509View::checkIp(const std::string_view ip, int flags) con } } +X509View X509View::From(const SSLPointer& ssl) { + ClearErrorOnReturn clear_error_on_return; + if (!ssl) return {}; + return X509View(SSL_get_certificate(ssl.get())); +} + +X509View X509View::From(const SSLCtxPointer& ctx) { + ClearErrorOnReturn clear_error_on_return; + if (!ctx) return {}; + return X509View(SSL_CTX_get0_certificate(ctx.get())); +} + +X509Pointer X509View::clone() const { + ClearErrorOnReturn clear_error_on_return; + if (!cert_) return {}; + return X509Pointer(X509_dup(const_cast(cert_))); +} + Result X509Pointer::Parse(Buffer buffer) { ClearErrorOnReturn clearErrorOnReturn; BIOPointer bio(BIO_new_mem_buf(buffer.data, buffer.len)); @@ -922,4 +940,27 @@ Result X509Pointer::Parse(Buffer buffer) return Result(ERR_get_error()); } + + +X509Pointer X509Pointer::IssuerFrom(const SSLPointer& ssl, const X509View& view) { + return IssuerFrom(SSL_get_SSL_CTX(ssl.get()), view); +} + +X509Pointer X509Pointer::IssuerFrom(const SSL_CTX* ctx, const X509View& cert) { + X509_STORE* store = SSL_CTX_get_cert_store(ctx); + DeleteFnPtr store_ctx( + X509_STORE_CTX_new()); + X509Pointer result; + X509* issuer; + if (store_ctx.get() != nullptr && + X509_STORE_CTX_init(store_ctx.get(), store, nullptr, nullptr) == 1 && + X509_STORE_CTX_get1_issuer(&issuer, store_ctx.get(), cert.get()) == 1) { + result.reset(issuer); + } + return result; +} + +X509Pointer X509Pointer::PeerFrom(const SSLPointer& ssl) { + return X509Pointer(SSL_get_peer_certificate(ssl.get())); +} } // namespace ncrypto diff --git a/deps/ncrypto/ncrypto.h b/deps/ncrypto/ncrypto.h index b646d2e90b7..50e86538edd 100644 --- a/deps/ncrypto/ncrypto.h +++ b/deps/ncrypto/ncrypto.h @@ -311,14 +311,21 @@ class BignumPointer final { DeleteFnPtr bn_; }; +class X509Pointer; + class X509View final { public: + static X509View From(const SSLPointer& ssl); + static X509View From(const SSLCtxPointer& ctx); + 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 X509* get() const { return const_cast(cert_); } + inline bool operator==(std::nullptr_t) noexcept { return cert_ == nullptr; } inline operator bool() const { return cert_ != nullptr; } @@ -340,6 +347,8 @@ class X509View final { bool checkPrivateKey(const EVPKeyPointer& pkey) const; bool checkPublicKey(const EVPKeyPointer& pkey) const; + X509Pointer clone() const; + enum class CheckMatch { NO_MATCH, MATCH, @@ -358,6 +367,9 @@ class X509View final { class X509Pointer final { public: static Result Parse(Buffer buffer); + static X509Pointer IssuerFrom(const SSLPointer& ssl, const X509View& view); + static X509Pointer IssuerFrom(const SSL_CTX* ctx, const X509View& view); + static X509Pointer PeerFrom(const SSLPointer& ssl); X509Pointer() = default; explicit X509Pointer(X509* cert); diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc index 15f25e3cc42..6a967702b22 100644 --- a/src/crypto/crypto_common.cc +++ b/src/crypto/crypto_common.cc @@ -1,6 +1,7 @@ #include "crypto/crypto_common.h" #include "base_object-inl.h" #include "crypto/crypto_util.h" +#include "crypto/crypto_x509.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "nbytes.h" @@ -29,40 +30,17 @@ namespace node { using v8::Array; using v8::ArrayBuffer; using v8::BackingStore; -using v8::Boolean; using v8::Context; using v8::EscapableHandleScope; using v8::Integer; using v8::Local; using v8::MaybeLocal; -using v8::NewStringType; using v8::Object; using v8::String; using v8::Undefined; using v8::Value; namespace crypto { -static constexpr int kX509NameFlagsMultiline = - ASN1_STRFLGS_ESC_2253 | - ASN1_STRFLGS_ESC_CTRL | - ASN1_STRFLGS_UTF8_CONVERT | - XN_FLAG_SEP_MULTILINE | - XN_FLAG_FN_SN; - -X509Pointer SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert) { - X509_STORE* store = SSL_CTX_get_cert_store(ctx); - DeleteFnPtr store_ctx( - X509_STORE_CTX_new()); - X509Pointer result; - X509* issuer; - if (store_ctx.get() != nullptr && - X509_STORE_CTX_init(store_ctx.get(), store, nullptr, nullptr) == 1 && - X509_STORE_CTX_get1_issuer(&issuer, store_ctx.get(), cert) == 1) { - result.reset(issuer); - } - return result; -} - void LogSecret( const SSLPointer& ssl, const char* name, @@ -120,8 +98,7 @@ long VerifyPeerCertificate( // NOLINT(runtime/int) const SSLPointer& ssl, long def) { // NOLINT(runtime/int) long err = def; // NOLINT(runtime/int) - if (X509* peer_cert = SSL_get_peer_certificate(ssl.get())) { - X509_free(peer_cert); + if (X509Pointer::PeerFrom(ssl)) { err = SSL_get_verify_result(ssl.get()); } else { const SSL_CIPHER* curr_cipher = SSL_get_current_cipher(ssl.get()); @@ -140,13 +117,14 @@ long VerifyPeerCertificate( // NOLINT(runtime/int) bool UseSNIContext( const SSLPointer& ssl, BaseObjectPtr context) { + auto x509 = ncrypto::X509View::From(context->ctx()); + if (!x509) return false; SSL_CTX* ctx = context->ctx().get(); - X509* x509 = SSL_CTX_get0_certificate(ctx); EVP_PKEY* pkey = SSL_CTX_get0_privatekey(ctx); STACK_OF(X509)* chain; int err = SSL_CTX_get0_chain_certs(ctx, &chain); - if (err == 1) err = SSL_use_certificate(ssl.get(), x509); + if (err == 1) err = SSL_use_certificate(ssl.get(), x509.get()); if (err == 1) err = SSL_use_PrivateKey(ssl.get(), pkey); if (err == 1 && chain != nullptr) err = SSL_set1_chain(ssl.get(), chain); return err == 1; @@ -263,25 +241,9 @@ MaybeLocal GetValidationErrorCode(Environment* env, int err) { MaybeLocal GetCert(Environment* env, const SSLPointer& ssl) { ClearErrorOnReturn clear_error_on_return; - X509* cert = SSL_get_certificate(ssl.get()); - if (cert == nullptr) - return Undefined(env->isolate()); - - MaybeLocal maybe_cert = X509ToObject(env, cert); - return maybe_cert.FromMaybe(Local()); -} - -Local ToV8Value(Environment* env, const BIOPointer& bio) { - BUF_MEM* mem; - BIO_get_mem_ptr(bio.get(), &mem); - MaybeLocal ret = - String::NewFromUtf8( - env->isolate(), - mem->data, - NewStringType::kNormal, - mem->length); - CHECK_EQ(BIO_reset(bio.get()), 1); - return ret.FromMaybe(Local()); + ncrypto::X509View cert(SSL_get_certificate(ssl.get())); + if (!cert) return Undefined(env->isolate()); + return X509Certificate::toObject(env, cert); } namespace { @@ -330,28 +292,28 @@ StackOfX509 CloneSSLCerts(X509Pointer&& cert, return peer_certs; } -MaybeLocal AddIssuerChainToObject( - X509Pointer* cert, - Local object, - StackOfX509&& peer_certs, - Environment* const env) { - Local context = env->isolate()->GetCurrentContext(); +MaybeLocal AddIssuerChainToObject(X509Pointer* cert, + Local object, + StackOfX509&& peer_certs, + Environment* const env) { cert->reset(sk_X509_delete(peer_certs.get(), 0)); for (;;) { int i; for (i = 0; i < sk_X509_num(peer_certs.get()); i++) { - X509* ca = sk_X509_value(peer_certs.get(), i); - if (X509_check_issued(ca, cert->get()) != X509_V_OK) - continue; + ncrypto::X509View ca(sk_X509_value(peer_certs.get(), i)); + if (!cert->view().isIssuedBy(ca)) continue; - Local ca_info; - MaybeLocal maybe_ca_info = X509ToObject(env, ca); - if (!maybe_ca_info.ToLocal(&ca_info)) - return MaybeLocal(); + Local ca_info; + if (!X509Certificate::toObject(env, ca).ToLocal(&ca_info)) return {}; + CHECK(ca_info->IsObject()); - if (!Set(context, object, env->issuercert_string(), ca_info)) - return MaybeLocal(); - object = ca_info; + if (!Set(env->context(), + object, + env->issuercert_string(), + ca_info.As())) { + return {}; + } + object = ca_info.As(); // NOTE: Intentionally freeing cert that is not used anymore. // Delete cert and continue aggregating issuers. @@ -371,20 +333,22 @@ MaybeLocal GetLastIssuedCert( const SSLPointer& ssl, Local issuer_chain, Environment* const env) { - Local context = env->isolate()->GetCurrentContext(); - while (X509_check_issued(cert->get(), cert->get()) != X509_V_OK) { - X509Pointer ca; - if (!(ca = SSL_CTX_get_issuer(SSL_get_SSL_CTX(ssl.get()), cert->get()))) - break; + Local ca_info; + while (!cert->view().isIssuedBy(cert->view())) { + auto ca = X509Pointer::IssuerFrom(ssl, cert->view()); + if (!ca) break; - Local ca_info; - MaybeLocal maybe_ca_info = X509ToObject(env, ca.get()); - if (!maybe_ca_info.ToLocal(&ca_info)) - return MaybeLocal(); + if (!X509Certificate::toObject(env, ca.view()).ToLocal(&ca_info)) return {}; - if (!Set(context, issuer_chain, env->issuercert_string(), ca_info)) - return MaybeLocal(); - issuer_chain = ca_info; + CHECK(ca_info->IsObject()); + + if (!Set(env->context(), + issuer_chain, + env->issuercert_string(), + ca_info.As())) { + return {}; + } + issuer_chain = ca_info.As(); // For self-signed certificates whose keyUsage field does not include // keyCertSign, X509_check_issued() will return false. Avoid going into an @@ -398,144 +362,8 @@ MaybeLocal GetLastIssuedCert( return MaybeLocal(issuer_chain); } -void AddFingerprintDigest( - const unsigned char* md, - unsigned int md_size, - char fingerprint[3 * EVP_MAX_MD_SIZE]) { - unsigned int i; - const char hex[] = "0123456789ABCDEF"; - - for (i = 0; i < md_size; i++) { - fingerprint[3*i] = hex[(md[i] & 0xf0) >> 4]; - fingerprint[(3*i)+1] = hex[(md[i] & 0x0f)]; - fingerprint[(3*i)+2] = ':'; - } - - DCHECK_GT(md_size, 0); - fingerprint[(3 * (md_size - 1)) + 2] = '\0'; -} - -template -MaybeLocal GetCurveName(Environment* env, const int nid) { - const char* name = nid2string(nid); - return name != nullptr ? - MaybeLocal(OneByteString(env->isolate(), name)) : - MaybeLocal(Undefined(env->isolate())); -} - -MaybeLocal GetECPubKey(Environment* env, - const EC_GROUP* group, - OSSL3_CONST EC_KEY* ec) { - const EC_POINT* pubkey = EC_KEY_get0_public_key(ec); - if (pubkey == nullptr) - return Undefined(env->isolate()); - - return ECPointToBuffer(env, group, pubkey, EC_KEY_get_conv_form(ec), nullptr) - .FromMaybe(Local()); -} - -MaybeLocal GetECGroupBits(Environment* env, const EC_GROUP* group) { - if (group == nullptr) - return Undefined(env->isolate()); - - int bits = EC_GROUP_order_bits(group); - if (bits <= 0) - return Undefined(env->isolate()); - - return Integer::New(env->isolate(), bits); -} - -MaybeLocal GetPubKey(Environment* env, OSSL3_CONST RSA* rsa) { - int size = i2d_RSA_PUBKEY(rsa, nullptr); - CHECK_GE(size, 0); - - std::unique_ptr bs; - { - NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); - bs = ArrayBuffer::NewBackingStore(env->isolate(), size); - } - - unsigned char* serialized = reinterpret_cast(bs->Data()); - CHECK_GE(i2d_RSA_PUBKEY(rsa, &serialized), 0); - - Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); - return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local()); -} - -MaybeLocal GetExponentString( - Environment* env, - const BIOPointer& bio, - const BIGNUM* e) { - uint64_t exponent_word = static_cast(BignumPointer::GetWord(e)); - BIO_printf(bio.get(), "0x%" PRIx64, exponent_word); - return ToV8Value(env, bio); -} - -Local GetBits(Environment* env, const BIGNUM* n) { - return Integer::New(env->isolate(), BignumPointer::GetBitCount(n)); -} - -MaybeLocal GetModulusString( - Environment* env, - const BIOPointer& bio, - const BIGNUM* n) { - BN_print(bio.get(), n); - return ToV8Value(env, bio); -} } // namespace -MaybeLocal GetRawDERCertificate(Environment* env, X509* cert) { - int size = i2d_X509(cert, nullptr); - - std::unique_ptr bs; - { - NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); - bs = ArrayBuffer::NewBackingStore(env->isolate(), size); - } - - unsigned char* serialized = reinterpret_cast(bs->Data()); - CHECK_GE(i2d_X509(cert, &serialized), 0); - - Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); - return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local()); -} - -MaybeLocal GetSerialNumber(Environment* env, X509* cert) { - if (ASN1_INTEGER* serial_number = X509_get_serialNumber(cert)) { - if (auto bn = BignumPointer(ASN1_INTEGER_to_BN(serial_number, nullptr))) { - if (auto hex = bn.toHex()) { - return OneByteString(env->isolate(), - static_cast(hex.get())); - } - } - } - - return Undefined(env->isolate()); -} - -MaybeLocal GetKeyUsage(Environment* env, X509* cert) { - StackOfASN1 eku(static_cast( - X509_get_ext_d2i(cert, NID_ext_key_usage, nullptr, nullptr))); - if (eku) { - 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); - } - } - - return Array::New(env->isolate(), ext_key_usage.out(), count); - } - - return Undefined(env->isolate()); -} - MaybeLocal GetCurrentCipherName(Environment* env, const SSLPointer& ssl) { return GetCipherName(env, SSL_get_current_cipher(ssl.get())); @@ -546,199 +374,6 @@ MaybeLocal GetCurrentCipherVersion(Environment* env, return GetCipherVersion(env, SSL_get_current_cipher(ssl.get())); } -MaybeLocal GetFingerprintDigest( - Environment* env, - const EVP_MD* method, - X509* cert) { - unsigned char md[EVP_MAX_MD_SIZE]; - unsigned int md_size; - char fingerprint[EVP_MAX_MD_SIZE * 3]; - - if (X509_digest(cert, method, md, &md_size)) { - AddFingerprintDigest(md, md_size, fingerprint); - return OneByteString(env->isolate(), fingerprint); - } - return Undefined(env->isolate()); -} - -MaybeLocal GetValidTo( - Environment* env, - X509* cert, - const BIOPointer& bio) { - ASN1_TIME_print(bio.get(), X509_get0_notAfter(cert)); - return ToV8Value(env, bio); -} - -MaybeLocal GetValidFrom( - Environment* env, - X509* cert, - const BIOPointer& bio) { - ASN1_TIME_print(bio.get(), X509_get0_notBefore(cert)); - return ToV8Value(env, bio); -} - -v8::MaybeLocal GetSubjectAltNameString(Environment* env, - X509* cert, - const BIOPointer& bio) { - int index = X509_get_ext_by_NID(cert, NID_subject_alt_name, -1); - if (index < 0) - return Undefined(env->isolate()); - - X509_EXTENSION* ext = X509_get_ext(cert, index); - CHECK_NOT_NULL(ext); - - if (!ncrypto::SafeX509SubjectAltNamePrint(bio, ext)) { - CHECK_EQ(BIO_reset(bio.get()), 1); - return v8::Null(env->isolate()); - } - - return ToV8Value(env, bio); -} - -v8::MaybeLocal GetInfoAccessString(Environment* env, - X509* cert, - const BIOPointer& bio) { - int index = X509_get_ext_by_NID(cert, NID_info_access, -1); - if (index < 0) - return Undefined(env->isolate()); - - X509_EXTENSION* ext = X509_get_ext(cert, index); - CHECK_NOT_NULL(ext); - - if (!ncrypto::SafeX509InfoAccessPrint(bio, ext)) { - CHECK_EQ(BIO_reset(bio.get()), 1); - return v8::Null(env->isolate()); - } - - return ToV8Value(env, bio); -} - -MaybeLocal GetIssuerString(Environment* env, - X509* cert, - const BIOPointer& bio) { - X509_NAME* issuer_name = X509_get_issuer_name(cert); - if (X509_NAME_print_ex( - bio.get(), - issuer_name, - 0, - kX509NameFlagsMultiline) <= 0) { - CHECK_EQ(BIO_reset(bio.get()), 1); - return Undefined(env->isolate()); - } - - return ToV8Value(env, bio); -} - -MaybeLocal GetSubject(Environment* env, - X509* cert, - const BIOPointer& bio) { - if (X509_NAME_print_ex( - bio.get(), - X509_get_subject_name(cert), - 0, - kX509NameFlagsMultiline) <= 0) { - CHECK_EQ(BIO_reset(bio.get()), 1); - return Undefined(env->isolate()); - } - - return ToV8Value(env, bio); -} - -template -static MaybeLocal GetX509NameObject(Environment* env, X509* cert) { - X509_NAME* name = get_name(cert); - CHECK_NOT_NULL(name); - - int cnt = X509_NAME_entry_count(name); - CHECK_GE(cnt, 0); - - Local result = - Object::New(env->isolate(), Null(env->isolate()), nullptr, nullptr, 0); - if (result.IsEmpty()) { - return MaybeLocal(); - } - - for (int i = 0; i < cnt; i++) { - X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i); - CHECK_NOT_NULL(entry); - - // We intentionally ignore the value of X509_NAME_ENTRY_set because the - // representation as an object does not allow grouping entries into sets - // anyway, and multi-value RDNs are rare, i.e., the vast majority of - // Relative Distinguished Names contains a single type-value pair only. - const ASN1_OBJECT* type = X509_NAME_ENTRY_get_object(entry); - const ASN1_STRING* value = X509_NAME_ENTRY_get_data(entry); - - // If OpenSSL knows the type, use the short name of the type as the key, and - // the numeric representation of the type's OID otherwise. - int type_nid = OBJ_obj2nid(type); - char type_buf[80]; - const char* type_str; - if (type_nid != NID_undef) { - type_str = OBJ_nid2sn(type_nid); - CHECK_NOT_NULL(type_str); - } else { - OBJ_obj2txt(type_buf, sizeof(type_buf), type, true); - type_str = type_buf; - } - - Local v8_name; - if (!String::NewFromUtf8(env->isolate(), type_str).ToLocal(&v8_name)) { - return MaybeLocal(); - } - - // The previous implementation used X509_NAME_print_ex, which escapes some - // characters in the value. The old implementation did not decode/unescape - // values correctly though, leading to ambiguous and incorrect - // representations. The new implementation only converts to Unicode and does - // not escape anything. - unsigned char* value_str; - int value_str_size = ASN1_STRING_to_UTF8(&value_str, value); - if (value_str_size < 0) { - return Undefined(env->isolate()); - } - auto free_value_str = OnScopeLeave([&]() { OPENSSL_free(value_str); }); - - Local v8_value; - if (!String::NewFromUtf8(env->isolate(), - reinterpret_cast(value_str), - NewStringType::kNormal, - value_str_size) - .ToLocal(&v8_value)) { - return MaybeLocal(); - } - - // For backward compatibility, we only create arrays if multiple values - // exist for the same key. That is not great but there is not much we can - // change here without breaking things. Note that this creates nested data - // structures, yet still does not allow representing Distinguished Names - // accurately. - bool multiple; - if (!result->HasOwnProperty(env->context(), v8_name).To(&multiple)) { - return MaybeLocal(); - } else if (multiple) { - Local accum; - if (!result->Get(env->context(), v8_name).ToLocal(&accum)) { - return MaybeLocal(); - } - if (!accum->IsArray()) { - accum = Array::New(env->isolate(), &accum, 1); - if (result->Set(env->context(), v8_name, accum).IsNothing()) { - return MaybeLocal(); - } - } - Local array = accum.As(); - if (array->Set(env->context(), array->Length(), v8_value).IsNothing()) { - return MaybeLocal(); - } - } else if (result->Set(env->context(), v8_name, v8_value).IsNothing()) { - return MaybeLocal(); - } - } - - return result; -} - template (*Get)(Environment* env, const SSL_CIPHER* cipher)> MaybeLocal GetCurrentCipherValue(Environment* env, const SSLPointer& ssl) { @@ -897,8 +532,6 @@ MaybeLocal GetPeerCert( bool abbreviated, bool is_server) { ClearErrorOnReturn clear_error_on_return; - Local result; - MaybeLocal maybe_cert; // NOTE: This is because of the odd OpenSSL behavior. On client `cert_chain` // contains the `peer_certificate`, but on server it doesn't. @@ -909,9 +542,11 @@ MaybeLocal GetPeerCert( // Short result requested. if (abbreviated) { - maybe_cert = - X509ToObject(env, cert ? cert.get() : sk_X509_value(ssl_certs, 0)); - return maybe_cert.ToLocal(&result) ? result : MaybeLocal(); + if (cert) { + return X509Certificate::toObject(env, cert.view()); + } + return X509Certificate::toObject( + env, ncrypto::X509View(sk_X509_value(ssl_certs, 0))); } StackOfX509 peer_certs = CloneSSLCerts(std::move(cert), ssl_certs); @@ -919,23 +554,18 @@ MaybeLocal GetPeerCert( return Undefined(env->isolate()); // First and main certificate. - X509Pointer first_cert(sk_X509_value(peer_certs.get(), 0)); + Local result; + ncrypto::X509View first_cert(sk_X509_value(peer_certs.get(), 0)); CHECK(first_cert); - maybe_cert = X509ToObject(env, first_cert.release()); - if (!maybe_cert.ToLocal(&result)) - return MaybeLocal(); + if (!X509Certificate::toObject(env, first_cert).ToLocal(&result)) return {}; + CHECK(result->IsObject()); Local issuer_chain; MaybeLocal maybe_issuer_chain; - maybe_issuer_chain = - AddIssuerChainToObject( - &cert, - result, - std::move(peer_certs), - env); - if (!maybe_issuer_chain.ToLocal(&issuer_chain)) - return MaybeLocal(); + maybe_issuer_chain = AddIssuerChainToObject( + &cert, result.As(), std::move(peer_certs), env); + if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return {}; maybe_issuer_chain = GetLastIssuedCert( @@ -945,155 +575,19 @@ MaybeLocal GetPeerCert( env); issuer_chain.Clear(); - if (!maybe_issuer_chain.ToLocal(&issuer_chain)) - return MaybeLocal(); + if (!maybe_issuer_chain.ToLocal(&issuer_chain)) return {}; // Last certificate should be self-signed. - if (X509_check_issued(cert.get(), cert.get()) == X509_V_OK && + if (cert.view().isIssuedBy(cert.view()) && !Set(env->context(), - issuer_chain, - env->issuercert_string(), - issuer_chain)) { - return MaybeLocal(); + issuer_chain, + env->issuercert_string(), + issuer_chain)) { + return {}; } return result; } -MaybeLocal X509ToObject( - Environment* env, - X509* cert) { - EscapableHandleScope scope(env->isolate()); - Local context = env->context(); - Local info = Object::New(env->isolate()); - - BIOPointer bio(BIO_new(BIO_s_mem())); - CHECK(bio); - - // X509_check_ca() returns a range of values. Only 1 means "is a CA" - auto is_ca = Boolean::New(env->isolate(), 1 == X509_check_ca(cert)); - if (!Set(context, - info, - env->subject_string(), - GetX509NameObject(env, cert)) || - !Set(context, - info, - env->issuer_string(), - GetX509NameObject(env, cert)) || - !Set(context, - info, - env->subjectaltname_string(), - GetSubjectAltNameString(env, cert, bio)) || - !Set(context, - info, - env->infoaccess_string(), - GetInfoAccessString(env, cert, bio)) || - !Set(context, info, env->ca_string(), is_ca)) { - return MaybeLocal(); - } - - OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert); - OSSL3_CONST RSA* rsa = nullptr; - OSSL3_CONST EC_KEY* ec = nullptr; - if (pkey != nullptr) { - switch (EVP_PKEY_id(pkey)) { - case EVP_PKEY_RSA: - rsa = EVP_PKEY_get0_RSA(pkey); - break; - case EVP_PKEY_EC: - ec = EVP_PKEY_get0_EC_KEY(pkey); - break; - } - } - - if (rsa) { - const BIGNUM* n; - const BIGNUM* e; - RSA_get0_key(rsa, &n, &e, nullptr); - if (!Set(context, - info, - env->modulus_string(), - GetModulusString(env, bio, n)) || - !Set(context, info, env->bits_string(), GetBits(env, n)) || - !Set(context, - info, - env->exponent_string(), - GetExponentString(env, bio, e)) || - !Set(context, - info, - env->pubkey_string(), - GetPubKey(env, rsa))) { - return MaybeLocal(); - } - } else if (ec) { - const EC_GROUP* group = EC_KEY_get0_group(ec); - - if (!Set( - context, info, env->bits_string(), GetECGroupBits(env, group)) || - !Set( - context, info, env->pubkey_string(), GetECPubKey(env, group, ec))) { - return MaybeLocal(); - } - - const int nid = EC_GROUP_get_curve_name(group); - if (nid != 0) { - // Curve is well-known, get its OID and NIST nick-name (if it has one). - - if (!Set(context, - info, - env->asn1curve_string(), - GetCurveName(env, nid)) || - !Set(context, - info, - env->nistcurve_string(), - GetCurveName(env, nid))) { - return MaybeLocal(); - } - } else { - // Unnamed curves can be described by their mathematical properties, - // but aren't used much (at all?) with X.509/TLS. Support later if needed. - } - } - - if (!Set(context, - info, - env->valid_from_string(), - GetValidFrom(env, cert, bio)) || - !Set(context, - info, - env->valid_to_string(), - GetValidTo(env, cert, bio))) { - return MaybeLocal(); - } - - // bio is no longer needed - bio.reset(); - - if (!Set(context, - info, - env->fingerprint_string(), - GetFingerprintDigest(env, EVP_sha1(), cert)) || - !Set(context, - info, - env->fingerprint256_string(), - GetFingerprintDigest(env, EVP_sha256(), cert)) || - !Set(context, - info, - env->fingerprint512_string(), - GetFingerprintDigest(env, EVP_sha512(), cert)) || - !Set( - context, info, env->ext_key_usage_string(), GetKeyUsage(env, cert)) || - !Set(context, - info, - env->serial_number_string(), - GetSerialNumber(env, cert)) || - !Set( - context, info, env->raw_string(), GetRawDERCertificate(env, cert))) { - return MaybeLocal(); - } - - return scope.Escape(info); -} - } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_common.h b/src/crypto/crypto_common.h index 32dee8e6107..284aadd6cc2 100644 --- a/src/crypto/crypto_common.h +++ b/src/crypto/crypto_common.h @@ -27,10 +27,6 @@ struct StackOfX509Deleter { }; using StackOfX509 = std::unique_ptr; -using StackOfASN1 = ncrypto::StackOfASN1; - -X509Pointer SSL_CTX_get_issuer(SSL_CTX* ctx, X509* cert); - void LogSecret( const SSLPointer& ssl, const char* name, @@ -95,53 +91,11 @@ v8::MaybeLocal ECPointToBuffer( point_conversion_form_t form, const char** error); -v8::MaybeLocal X509ToObject( - Environment* env, - X509* cert); - -v8::MaybeLocal GetValidTo( - Environment* env, - X509* cert, - const BIOPointer& bio); - -v8::MaybeLocal GetValidFrom( - Environment* env, - X509* cert, - const BIOPointer& bio); - -v8::MaybeLocal GetFingerprintDigest( - Environment* env, - const EVP_MD* method, - X509* cert); - -v8::MaybeLocal GetKeyUsage(Environment* env, X509* cert); v8::MaybeLocal GetCurrentCipherName(Environment* env, const SSLPointer& ssl); v8::MaybeLocal GetCurrentCipherVersion(Environment* env, const SSLPointer& ssl); -v8::MaybeLocal GetSerialNumber(Environment* env, X509* cert); - -v8::MaybeLocal GetRawDERCertificate(Environment* env, X509* cert); - -v8::Local ToV8Value(Environment* env, const BIOPointer& bio); - -v8::MaybeLocal GetSubject(Environment* env, - X509* cert, - const BIOPointer& bio); - -v8::MaybeLocal GetIssuerString(Environment* env, - X509* cert, - const BIOPointer& bio); - -v8::MaybeLocal GetSubjectAltNameString(Environment* env, - X509* cert, - const BIOPointer& bio); - -v8::MaybeLocal GetInfoAccessString(Environment* env, - X509* cert, - const BIOPointer& bio); - } // namespace crypto } // namespace node diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc index a60443a2aa1..1efe7bfcdfe 100644 --- a/src/crypto/crypto_context.cc +++ b/src/crypto/crypto_context.cc @@ -121,7 +121,7 @@ int SSL_CTX_use_certificate_chain(SSL_CTX* ctx, // TODO(tniessen): SSL_CTX_get_issuer does not allow the caller to // distinguish between a failed operation and an empty result. Fix that // and then handle the potential error properly here (set ret to 0). - *issuer_ = SSL_CTX_get_issuer(ctx, x.get()); + *issuer_ = X509Pointer::IssuerFrom(ctx, x.view()); // NOTE: get_cert_store doesn't increment reference count, // no need to free `store` } else { diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc index fc400f47757..367ae2bea38 100644 --- a/src/crypto/crypto_x509.cc +++ b/src/crypto/crypto_x509.cc @@ -1,9 +1,8 @@ -#include "crypto_x509.h" +#include "crypto/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/crypto_common.h" +#include "crypto/crypto_keys.h" +#include "crypto/crypto_util.h" #include "env-inl.h" #include "memory_tracker-inl.h" #include "ncrypto.h" @@ -19,11 +18,14 @@ namespace node { using v8::Array; using v8::ArrayBuffer; using v8::ArrayBufferView; +using v8::BackingStore; +using v8::Boolean; using v8::Context; using v8::EscapableHandleScope; using v8::Function; using v8::FunctionCallbackInfo; using v8::FunctionTemplate; +using v8::Integer; using v8::Isolate; using v8::Local; using v8::MaybeLocal; @@ -57,13 +59,43 @@ void ManagedX509::MemoryInfo(MemoryTracker* tracker) const { } namespace { +void AddFingerprintDigest(const unsigned char* md, + unsigned int md_size, + char fingerprint[3 * EVP_MAX_MD_SIZE]) { + unsigned int i; + static constexpr char hex[] = "0123456789ABCDEF"; + + for (i = 0; i < md_size; i++) { + fingerprint[3 * i] = hex[(md[i] & 0xf0) >> 4]; + fingerprint[(3 * i) + 1] = hex[(md[i] & 0x0f)]; + fingerprint[(3 * i) + 2] = ':'; + } + + DCHECK_GT(md_size, 0); + fingerprint[(3 * (md_size - 1)) + 2] = '\0'; +} + +MaybeLocal GetFingerprintDigest(Environment* env, + const EVP_MD* method, + const ncrypto::X509View& cert) { + unsigned char md[EVP_MAX_MD_SIZE]; + unsigned int md_size; + char fingerprint[EVP_MAX_MD_SIZE * 3]; + + if (X509_digest(cert.get(), method, md, &md_size)) { + AddFingerprintDigest(md, md_size, fingerprint); + return OneByteString(env->isolate(), fingerprint); + } + return Undefined(env->isolate()); +} + template void Fingerprint(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(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->view()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } @@ -82,23 +114,164 @@ MaybeLocal ToV8Value(Local context, BIOPointer&& bio) { return ret; } -MaybeLocal ToBuffer(Environment* env, BIOPointer&& bio) { +MaybeLocal ToV8Value(Local context, const ASN1_OBJECT* obj) { + // If OpenSSL knows the type, use the short name of the type as the key, and + // the numeric representation of the type's OID otherwise. + int nid = OBJ_obj2nid(obj); + char buf[80]; + const char* str; + if (nid != NID_undef) { + str = OBJ_nid2sn(nid); + CHECK_NOT_NULL(str); + } else { + OBJ_obj2txt(buf, sizeof(buf), obj, true); + str = buf; + } + + Local result; + if (!String::NewFromUtf8(context->GetIsolate(), str).ToLocal(&result)) { + return {}; + } + return result; +} + +MaybeLocal ToV8Value(Local context, const ASN1_STRING* str) { + // The previous implementation used X509_NAME_print_ex, which escapes some + // characters in the value. The old implementation did not decode/unescape + // values correctly though, leading to ambiguous and incorrect + // representations. The new implementation only converts to Unicode and does + // not escape anything. + unsigned char* value_str; + int value_str_size = ASN1_STRING_to_UTF8(&value_str, str); + if (value_str_size < 0) { + return Undefined(context->GetIsolate()); + } + ncrypto::DataPointer free_value_str(value_str, value_str_size); + + Local result; + if (!String::NewFromUtf8(context->GetIsolate(), + reinterpret_cast(value_str), + NewStringType::kNormal, + value_str_size) + .ToLocal(&result)) { + return {}; + } + return result; +} + +MaybeLocal ToV8Value(Local context, const 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 == nullptr || !*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()); + 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 GetDer(Environment* env, const ncrypto::X509View& view) { + Local ret; + auto bio = view.toDER(); + if (!bio) return Undefined(env->isolate()); + if (!ToBuffer(env, &bio).ToLocal(&ret)) { + return {}; + } + return ret; +} + +MaybeLocal GetSubjectAltNameString(Environment* env, + const ncrypto::X509View& view) { + Local ret; + auto bio = view.getSubjectAltName(); + if (!bio) return Undefined(env->isolate()); + if (!ToV8Value(env->context(), bio).ToLocal(&ret)) return {}; + return ret; +} + +MaybeLocal GetInfoAccessString(Environment* env, + const ncrypto::X509View& view) { + Local ret; + auto bio = view.getInfoAccess(); + if (!bio) return Undefined(env->isolate()); + if (!ToV8Value(env->context(), bio).ToLocal(&ret)) { + return {}; + } + return ret; +} + +MaybeLocal GetValidFrom(Environment* env, + const ncrypto::X509View& view) { + Local ret; + auto bio = view.getValidFrom(); + if (!bio) return Undefined(env->isolate()); + if (!ToV8Value(env->context(), bio).ToLocal(&ret)) { + return {}; + } + return ret; +} + +MaybeLocal GetValidTo(Environment* env, const ncrypto::X509View& view) { + Local ret; + auto bio = view.getValidTo(); + if (!bio) return Undefined(env->isolate()); + if (!ToV8Value(env->context(), bio).ToLocal(&ret)) { + return {}; + } + return ret; +} + +MaybeLocal GetSerialNumber(Environment* env, + const ncrypto::X509View& view) { + if (auto serial = view.getSerialNumber()) { + return OneByteString(env->isolate(), + static_cast(serial.get())); + } + return Undefined(env->isolate()); +} + +MaybeLocal GetKeyUsage(Environment* env, const ncrypto::X509View& cert) { + ncrypto::StackOfASN1 eku(static_cast( + X509_get_ext_d2i(cert.get(), NID_ext_key_usage, nullptr, nullptr))); + if (eku) { + 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); + } + } + + return Array::New(env->isolate(), ext_key_usage.out(), count); + } + + return Undefined(env->isolate()); +} + void Pem(const FunctionCallbackInfo& args) { Environment* env = Environment::GetCurrent(args); X509Certificate* cert; @@ -114,7 +287,7 @@ void Der(const FunctionCallbackInfo& args) { X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); Local ret; - if (ToBuffer(env, cert->view().toDER()).ToLocal(&ret)) { + if (GetDer(env, cert->view()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } @@ -134,8 +307,7 @@ void SubjectAltName(const FunctionCallbackInfo& args) { X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); Local ret; - if (ToV8Value(env->context(), cert->view().getSubjectAltName()) - .ToLocal(&ret)) { + if (GetSubjectAltNameString(env, cert->view()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } @@ -155,7 +327,7 @@ void InfoAccess(const FunctionCallbackInfo& args) { X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); Local ret; - if (ToV8Value(env->context(), cert->view().getInfoAccess()).ToLocal(&ret)) { + if (GetInfoAccessString(env, cert->view()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } @@ -165,7 +337,7 @@ void ValidFrom(const FunctionCallbackInfo& args) { X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); Local ret; - if (ToV8Value(env->context(), cert->view().getValidFrom()).ToLocal(&ret)) { + if (GetValidFrom(env, cert->view()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } @@ -175,7 +347,7 @@ void ValidTo(const FunctionCallbackInfo& args) { X509Certificate* cert; ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); Local ret; - if (ToV8Value(env->context(), cert->view().getValidTo()).ToLocal(&ret)) { + if (GetValidTo(env, cert->view()).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } @@ -184,9 +356,9 @@ 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()))); + Local ret; + if (GetSerialNumber(env, cert->view()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); } } @@ -215,24 +387,10 @@ void KeyUsage(const FunctionCallbackInfo& 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, 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); - } + Local ret; + if (GetKeyUsage(env, cert->view()).ToLocal(&ret)) { + args.GetReturnValue().Set(ret); } - - args.GetReturnValue().Set( - Array::New(env->isolate(), ext_key_usage.out(), count)); } void CheckCA(const FunctionCallbackInfo& args) { @@ -384,10 +542,280 @@ void ToLegacy(const FunctionCallbackInfo& args) { ASSIGN_OR_RETURN_UNWRAP(&cert, args.This()); ClearErrorOnReturn clear_error_on_return; Local ret; - if (X509ToObject(env, cert->get()).ToLocal(&ret)) { + if (cert->toObject(env).ToLocal(&ret)) { args.GetReturnValue().Set(ret); } } + +template +bool Set(Environment* env, + Local target, + Local name, + MaybeLocal maybe_value) { + Local value; + if (!maybe_value.ToLocal(&value)) return false; + + // Undefined is ignored, but still considered successful + if (value->IsUndefined()) return true; + + return !target->Set(env->context(), name, value).IsNothing(); +} + +template +bool Set(Environment* env, + Local target, + uint32_t index, + MaybeLocal maybe_value) { + Local value; + if (!maybe_value.ToLocal(&value)) return false; + + // Undefined is ignored, but still considered successful + if (value->IsUndefined()) return true; + + return !target->Set(env->context(), index, value).IsNothing(); +} + +// Convert an X509_NAME* into a JavaScript object. +// Each entry of the name is converted into a property of the object. +// The property value may be a single string or an array of strings. +template +static MaybeLocal GetX509NameObject(Environment* env, + const ncrypto::X509View& cert) { + X509_NAME* name = get_name(cert.get()); + CHECK_NOT_NULL(name); + + int cnt = X509_NAME_entry_count(name); + CHECK_GE(cnt, 0); + + Local v8_name; + Local v8_value; + // Note the the resulting object uses a null prototype. + Local result = + Object::New(env->isolate(), Null(env->isolate()), nullptr, nullptr, 0); + if (result.IsEmpty()) return {}; + + for (int i = 0; i < cnt; i++) { + X509_NAME_ENTRY* entry = X509_NAME_get_entry(name, i); + CHECK_NOT_NULL(entry); + + if (!ToV8Value(env->context(), X509_NAME_ENTRY_get_object(entry)) + .ToLocal(&v8_name) || + !ToV8Value(env->context(), X509_NAME_ENTRY_get_data(entry)) + .ToLocal(&v8_value)) { + return {}; + } + + // For backward compatibility, we only create arrays if multiple values + // exist for the same key. That is not great but there is not much we can + // change here without breaking things. Note that this creates nested data + // structures, yet still does not allow representing Distinguished Names + // accurately. + bool multiple; + if (!result->Has(env->context(), v8_name).To(&multiple)) { + return {}; + } + + if (multiple) { + Local accum; + if (!result->Get(env->context(), v8_name).ToLocal(&accum)) { + return {}; + } + if (!accum->IsArray()) { + Local items[] = { + accum, + v8_value, + }; + accum = Array::New(env->isolate(), items, arraysize(items)); + if (!Set(env, result, v8_name, accum)) { + return {}; + } + } else { + Local array = accum.As(); + if (!Set(env, array, array->Length(), v8_value)) { + return {}; + } + } + continue; + } + + if (!Set(env, result, v8_name, v8_value)) { + return {}; + } + } + + return result; +} + +MaybeLocal GetPubKey(Environment* env, OSSL3_CONST RSA* rsa) { + int size = i2d_RSA_PUBKEY(rsa, nullptr); + CHECK_GE(size, 0); + + std::unique_ptr bs; + { + NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data()); + bs = ArrayBuffer::NewBackingStore(env->isolate(), size); + } + + unsigned char* serialized = reinterpret_cast(bs->Data()); + CHECK_GE(i2d_RSA_PUBKEY(rsa, &serialized), 0); + + Local ab = ArrayBuffer::New(env->isolate(), std::move(bs)); + return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local()); +} + +MaybeLocal GetModulusString(Environment* env, const BIGNUM* n) { + BIOPointer bio(BIO_new(BIO_s_mem())); + BN_print(bio.get(), n); + return ToV8Value(env->context(), bio); +} + +MaybeLocal GetExponentString(Environment* env, const BIGNUM* e) { + uint64_t exponent_word = static_cast(BignumPointer::GetWord(e)); + BIOPointer bio(BIO_new(BIO_s_mem())); + BIO_printf(bio.get(), "0x%" PRIx64, exponent_word); + return ToV8Value(env->context(), bio); +} + +MaybeLocal GetECPubKey(Environment* env, + const EC_GROUP* group, + OSSL3_CONST EC_KEY* ec) { + const EC_POINT* pubkey = EC_KEY_get0_public_key(ec); + if (pubkey == nullptr) return Undefined(env->isolate()); + + return ECPointToBuffer(env, group, pubkey, EC_KEY_get_conv_form(ec), nullptr) + .FromMaybe(Local()); +} + +MaybeLocal GetECGroupBits(Environment* env, const EC_GROUP* group) { + if (group == nullptr) return Undefined(env->isolate()); + + int bits = EC_GROUP_order_bits(group); + if (bits <= 0) return Undefined(env->isolate()); + + return Integer::New(env->isolate(), bits); +} + +template +MaybeLocal GetCurveName(Environment* env, const int nid) { + const char* name = nid2string(nid); + return name != nullptr + ? MaybeLocal(OneByteString(env->isolate(), name)) + : MaybeLocal(Undefined(env->isolate())); +} + +MaybeLocal X509ToObject(Environment* env, + const ncrypto::X509View& cert) { + EscapableHandleScope scope(env->isolate()); + Local info = Object::New(env->isolate()); + + if (!Set(env, + info, + env->subject_string(), + GetX509NameObject(env, cert)) || + !Set(env, + info, + env->issuer_string(), + GetX509NameObject(env, cert)) || + !Set(env, + info, + env->subjectaltname_string(), + GetSubjectAltNameString(env, cert)) || + !Set(env, + info, + env->infoaccess_string(), + GetInfoAccessString(env, cert)) || + !Set(env, + info, + env->ca_string(), + Boolean::New(env->isolate(), cert.isCA()))) { + return {}; + } + + OSSL3_CONST EVP_PKEY* pkey = X509_get0_pubkey(cert.get()); + OSSL3_CONST RSA* rsa = nullptr; + OSSL3_CONST EC_KEY* ec = nullptr; + if (pkey != nullptr) { + switch (EVP_PKEY_id(pkey)) { + case EVP_PKEY_RSA: + rsa = EVP_PKEY_get0_RSA(pkey); + break; + case EVP_PKEY_EC: + ec = EVP_PKEY_get0_EC_KEY(pkey); + break; + } + } + + if (rsa) { + const BIGNUM* n; + const BIGNUM* e; + RSA_get0_key(rsa, &n, &e, nullptr); + if (!Set( + env, info, env->modulus_string(), GetModulusString(env, n)) || + !Set( + env, + info, + env->bits_string(), + Integer::New(env->isolate(), BignumPointer::GetBitCount(n))) || + !Set( + env, info, env->exponent_string(), GetExponentString(env, e)) || + !Set(env, info, env->pubkey_string(), GetPubKey(env, rsa))) { + return {}; + } + } else if (ec) { + const EC_GROUP* group = EC_KEY_get0_group(ec); + + if (!Set( + env, info, env->bits_string(), GetECGroupBits(env, group)) || + !Set( + env, info, env->pubkey_string(), GetECPubKey(env, group, ec))) { + return {}; + } + + const int nid = EC_GROUP_get_curve_name(group); + if (nid != 0) { + // Curve is well-known, get its OID and NIST nick-name (if it has one). + + if (!Set(env, + info, + env->asn1curve_string(), + GetCurveName(env, nid)) || + !Set(env, + info, + env->nistcurve_string(), + GetCurveName(env, nid))) { + return {}; + } + } else { + // Unnamed curves can be described by their mathematical properties, + // but aren't used much (at all?) with X.509/TLS. Support later if needed. + } + } + + if (!Set( + env, info, env->valid_from_string(), GetValidFrom(env, cert)) || + !Set(env, info, env->valid_to_string(), GetValidTo(env, cert)) || + !Set(env, + info, + env->fingerprint_string(), + GetFingerprintDigest(env, EVP_sha1(), cert)) || + !Set(env, + info, + env->fingerprint256_string(), + GetFingerprintDigest(env, EVP_sha256(), cert)) || + !Set(env, + info, + env->fingerprint512_string(), + GetFingerprintDigest(env, EVP_sha512(), cert)) || + !Set( + env, info, env->ext_key_usage_string(), GetKeyUsage(env, cert)) || + !Set( + env, info, env->serial_number_string(), GetSerialNumber(env, cert)) || + !Set(env, info, env->raw_string(), GetDer(env, cert))) { + return {}; + } + + return scope.Escape(info); +} } // namespace Local X509Certificate::GetConstructorTemplate( @@ -400,29 +828,33 @@ Local X509Certificate::GetConstructorTemplate( 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); + SetProtoMethodNoSideEffect(isolate, tmpl, "subject", Subject); + SetProtoMethodNoSideEffect(isolate, tmpl, "subjectAltName", SubjectAltName); + SetProtoMethodNoSideEffect(isolate, tmpl, "infoAccess", InfoAccess); + SetProtoMethodNoSideEffect(isolate, tmpl, "issuer", Issuer); + SetProtoMethodNoSideEffect(isolate, tmpl, "validTo", ValidTo); + SetProtoMethodNoSideEffect(isolate, tmpl, "validFrom", ValidFrom); + SetProtoMethodNoSideEffect( + isolate, tmpl, "fingerprint", Fingerprint); + SetProtoMethodNoSideEffect( + isolate, tmpl, "fingerprint256", Fingerprint); + SetProtoMethodNoSideEffect( + isolate, tmpl, "fingerprint512", Fingerprint); + SetProtoMethodNoSideEffect(isolate, tmpl, "keyUsage", KeyUsage); + SetProtoMethodNoSideEffect(isolate, tmpl, "serialNumber", SerialNumber); + SetProtoMethodNoSideEffect(isolate, tmpl, "pem", Pem); + SetProtoMethodNoSideEffect(isolate, tmpl, "raw", Der); + SetProtoMethodNoSideEffect(isolate, tmpl, "publicKey", PublicKey); + SetProtoMethodNoSideEffect(isolate, tmpl, "checkCA", CheckCA); + SetProtoMethodNoSideEffect(isolate, tmpl, "checkHost", CheckHost); + SetProtoMethodNoSideEffect(isolate, tmpl, "checkEmail", CheckEmail); + SetProtoMethodNoSideEffect(isolate, tmpl, "checkIP", CheckIP); + SetProtoMethodNoSideEffect(isolate, tmpl, "checkIssued", CheckIssued); + SetProtoMethodNoSideEffect( + isolate, tmpl, "checkPrivateKey", CheckPrivateKey); + SetProtoMethodNoSideEffect(isolate, tmpl, "verify", CheckPublicKey); + SetProtoMethodNoSideEffect(isolate, tmpl, "toLegacy", ToLegacy); + SetProtoMethodNoSideEffect(isolate, tmpl, "getIssuerCert", GetIssuerCert); env->set_x509_constructor_template(tmpl); } return tmpl; @@ -457,12 +889,9 @@ MaybeLocal X509Certificate::New(Environment* env, 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)); + auto cert = ncrypto::X509View::From(ssl); + if (!cert) return {}; + return New(env, cert.clone()); } MaybeLocal X509Certificate::GetPeerCert(Environment* env, @@ -471,16 +900,16 @@ MaybeLocal X509Certificate::GetPeerCert(Environment* env, ClearErrorOnReturn clear_error_on_return; MaybeLocal maybe_cert; - bool is_server = - static_cast(flag) & static_cast(GetPeerCertificateFlag::SERVER); + X509Pointer cert; + if ((flag & GetPeerCertificateFlag::SERVER) == + GetPeerCertificateFlag::SERVER) { + cert = X509Pointer::PeerFrom(ssl); + } - 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); @@ -490,13 +919,14 @@ MaybeLocal X509Certificate::GetPeerCert(Environment* env, : New(env, std::move(cert)); } -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()); - Local ret; - if (Property(env, cert->get()).ToLocal(&ret)) args.GetReturnValue().Set(ret); +v8::MaybeLocal X509Certificate::toObject(Environment* env) { + return toObject(env, view()); +} + +v8::MaybeLocal X509Certificate::toObject( + Environment* env, const ncrypto::X509View& cert) { + if (!cert) return {}; + return X509ToObject(env, cert).FromMaybe(Local()); } X509Certificate::X509Certificate( @@ -551,7 +981,6 @@ std::unique_ptr X509Certificate::CloneForMessaging() return std::make_unique(cert_); } - void X509Certificate::Initialize(Environment* env, Local target) { SetMethod(env->context(), target, "parseX509", Parse); diff --git a/src/crypto/crypto_x509.h b/src/crypto/crypto_x509.h index d01defa5daa..595a2344641 100644 --- a/src/crypto/crypto_x509.h +++ b/src/crypto/crypto_x509.h @@ -82,6 +82,10 @@ class X509Certificate final : public BaseObject { inline ncrypto::X509View view() const { return *cert_; } X509* get() { return cert_->get(); } + v8::MaybeLocal toObject(Environment* env); + static v8::MaybeLocal toObject(Environment* env, + const ncrypto::X509View& cert); + void MemoryInfo(MemoryTracker* tracker) const override; SET_MEMORY_INFO_NAME(X509Certificate) SET_SELF_SIZE(X509Certificate) @@ -119,6 +123,20 @@ class X509Certificate final : public BaseObject { BaseObjectPtr issuer_cert_; }; +inline X509Certificate::GetPeerCertificateFlag operator|( + X509Certificate::GetPeerCertificateFlag lhs, + X509Certificate::GetPeerCertificateFlag rhs) { + return static_cast( + static_cast(lhs) | static_cast(rhs)); +} + +inline X509Certificate::GetPeerCertificateFlag operator&( + X509Certificate::GetPeerCertificateFlag lhs, + X509Certificate::GetPeerCertificateFlag rhs) { + return static_cast( + static_cast(lhs) & static_cast(rhs)); +} + } // namespace crypto } // namespace node diff --git a/test/parallel/test-tls-empty-sni-context.js b/test/parallel/test-tls-empty-sni-context.js index 3424e057bde..0a8a344c779 100644 --- a/test/parallel/test-tls-empty-sni-context.js +++ b/test/parallel/test-tls-empty-sni-context.js @@ -16,7 +16,7 @@ const options = { const server = tls.createServer(options, (c) => { assert.fail('Should not be called'); }).on('tlsClientError', common.mustCall((err, c) => { - assert.match(err.message, /passed a null parameter/i); + assert.match(err.message, /no suitable signature algorithm/i); server.close(); })).listen(0, common.mustCall(() => { const c = tls.connect({