88#include " ncrypto.h"
99#include " node_errors.h"
1010#include " util-inl.h"
11+ #include " uv.h"
1112#include " v8.h"
1213
14+ #include < array>
15+ #include < limits>
1316#include < string>
17+ #include < string_view>
1418#include < vector>
1519
1620namespace node {
@@ -20,6 +24,7 @@ using ncrypto::BIOPointer;
2024using ncrypto::ClearErrorOnReturn;
2125using ncrypto::DataPointer;
2226using ncrypto::Digest;
27+ using ncrypto::MarkPopErrorOnReturn;
2328using ncrypto::SSLPointer;
2429using ncrypto::X509Name;
2530using ncrypto::X509Pointer;
@@ -67,12 +72,20 @@ ManagedX509& ManagedX509::operator=(const ManagedX509& that) {
6772
6873void ManagedX509::MemoryInfo (MemoryTracker* tracker) const {
6974 if (!cert_) return ;
75+ MarkPopErrorOnReturn mark_pop_error_on_return;
7076 // This is an approximation based on the der encoding size.
71- int size = i2d_X509 (cert_.get (), nullptr );
72- tracker->TrackFieldWithSize (" cert" , size);
77+ const int size = i2d_X509 (cert_.get (), nullptr );
78+ if (size > 0 ) tracker->TrackFieldWithSize (" cert" , size);
7379}
7480
7581namespace {
82+ enum class MalformedSubjectAltNameMode {
83+ kThrow ,
84+ kReturnLegacySentinel ,
85+ };
86+
87+ constexpr std::string_view kMalformedSubjectAltName = " DNS:" ;
88+
7689MaybeLocal<Value> GetFingerprintDigest (Environment* env,
7790 const Digest& method,
7891 const X509View& cert) {
@@ -180,12 +193,29 @@ MaybeLocal<Value> GetDer(Environment* env, const X509View& view) {
180193 return ret;
181194}
182195
183- MaybeLocal<Value> GetSubjectAltNameString (Environment* env,
184- const X509View& view) {
196+ MaybeLocal<Value> GetSubjectAltNameString (
197+ Environment* env,
198+ const X509View& view,
199+ MalformedSubjectAltNameMode malformed_name_mode) {
185200 Local<Value> ret;
186- auto bio = view.getSubjectAltName ();
187- if (!bio) [[unlikely]]
188- return Undefined (env->isolate ());
201+ bool has_extension = false ;
202+ auto bio = view.getSubjectAltName (&has_extension);
203+ if (!bio) [[unlikely]] {
204+ // An absent SAN maps to undefined. A malformed, present SAN throws for
205+ // direct X509Certificate inspection, while the legacy TLS object uses a
206+ // deliberately nonmatching value to prevent commonName fallback.
207+ if (!has_extension) return Undefined (env->isolate ());
208+ if (malformed_name_mode ==
209+ MalformedSubjectAltNameMode::kReturnLegacySentinel ) {
210+ // The legacy object is used by TLS hostname verification. Return an
211+ // empty DNS name so verification cannot fall back to the common name,
212+ // while keeping getPeerCertificate() usable for malformed certificates.
213+ return OneByteString (env->isolate (), kMalformedSubjectAltName );
214+ }
215+ THROW_ERR_CRYPTO_OPERATION_FAILED (
216+ env, " Failed to format subject alternative name" );
217+ return {};
218+ }
189219 if (!ToV8Value (env->context (), bio).ToLocal (&ret)) return {};
190220 return ret;
191221}
@@ -224,13 +254,19 @@ MaybeLocal<Value> GetValidTo(Environment* env, const X509View& view) {
224254}
225255
226256MaybeLocal<Value> GetValidFromDate (Environment* env, const X509View& view) {
227- int64_t validFromTime = view.getValidFromTime ();
228- return Date::New (env->context (), validFromTime * 1000 .);
257+ const auto valid_from_time = view.getValidFromTime ();
258+ const double milliseconds = valid_from_time.has_value ()
259+ ? valid_from_time.value () * 1000 .
260+ : std::numeric_limits<double >::quiet_NaN ();
261+ return Date::New (env->context (), milliseconds);
229262}
230263
231264MaybeLocal<Value> GetValidToDate (Environment* env, const X509View& view) {
232- int64_t validToTime = view.getValidToTime ();
233- return Date::New (env->context (), validToTime * 1000 .);
265+ const auto valid_to_time = view.getValidToTime ();
266+ const double milliseconds = valid_to_time.has_value ()
267+ ? valid_to_time.value () * 1000 .
268+ : std::numeric_limits<double >::quiet_NaN ();
269+ return Date::New (env->context (), milliseconds);
234270}
235271
236272MaybeLocal<Value> GetSignatureAlgorithm (Environment* env,
@@ -309,7 +345,9 @@ void SubjectAltName(const FunctionCallbackInfo<Value>& args) {
309345 X509Certificate* cert;
310346 ASSIGN_OR_RETURN_UNWRAP (&cert, args.This ());
311347 Local<Value> ret;
312- if (GetSubjectAltNameString (env, cert->view ()).ToLocal (&ret)) {
348+ if (GetSubjectAltNameString (
349+ env, cert->view (), MalformedSubjectAltNameMode::kThrow )
350+ .ToLocal (&ret)) {
313351 args.GetReturnValue ().Set (ret);
314352 }
315353}
@@ -530,11 +568,29 @@ void CheckEmail(const FunctionCallbackInfo<Value>& args) {
530568}
531569
532570void CheckIP (const FunctionCallbackInfo<Value>& args) {
533- CheckX509Subject (args,
534- [](X509View& cert,
535- std::string_view subject,
536- uint32_t flags,
537- DataPointer*) { return cert.checkIp (subject, flags); });
571+ CheckX509Subject (
572+ args,
573+ [](X509View& cert,
574+ std::string_view subject,
575+ uint32_t flags,
576+ DataPointer*) {
577+ if (subject.find (' \0 ' ) != std::string_view::npos ||
578+ subject.find (' %' ) != std::string_view::npos) {
579+ return X509View::CheckMatch::INVALID_NAME ;
580+ }
581+
582+ const std::string input (subject);
583+ std::array<unsigned char , 16 > address{};
584+ size_t length = 0 ;
585+ if (uv_inet_pton (AF_INET , input.c_str (), address.data ()) == 0 ) {
586+ length = 4 ;
587+ } else if (uv_inet_pton (AF_INET6 , input.c_str (), address.data ()) == 0 ) {
588+ length = 16 ;
589+ } else {
590+ return X509View::CheckMatch::INVALID_NAME ;
591+ }
592+ return cert.checkIp ({.data = address.data (), .len = length}, flags);
593+ });
538594}
539595
540596void GetIssuerCert (const FunctionCallbackInfo<Value>& args) {
@@ -619,9 +675,20 @@ static MaybeLocal<Value> GetX509NameObject(Environment* env,
619675 Object::New (env->isolate (), Null (env->isolate ()), nullptr , nullptr , 0 );
620676 if (result.IsEmpty ()) return {};
621677
622- for (auto i : name) {
623- if (!ToV8Value (env, i.first ).ToLocal (&v8_name) ||
624- !ToV8Value (env, i.second ).ToLocal (&v8_value)) {
678+ for (auto entry : name) {
679+ if (!entry.has_value ()) [[unlikely]] {
680+ THROW_ERR_CRYPTO_OPERATION_FAILED (env,
681+ " Failed to convert X509 name to UTF-8" );
682+ return {};
683+ }
684+
685+ const auto & [key, value] = *entry;
686+ // The legacy object can only represent string values. Preserve valid
687+ // sibling attributes and omit values that cannot be converted to UTF-8.
688+ if (!value.has_value ()) continue ;
689+
690+ if (!ToV8Value (env, key).ToLocal (&v8_name) ||
691+ !ToV8Value (env, *value).ToLocal (&v8_value)) {
625692 return {};
626693 }
627694
@@ -682,11 +749,13 @@ MaybeLocal<Value> GetModulusString(Environment* env, const BIGNUM* n) {
682749
683750MaybeLocal<Value> GetExponentString (Environment* env, const BIGNUM * e) {
684751 if (e == nullptr ) return Null (env->isolate ());
752+ MarkPopErrorOnReturn mark_pop_error_on_return;
685753 auto bio = BIOPointer::NewMem ();
686754 if (!bio) [[unlikely]]
687755 return {};
688- BIO_puts (bio.get (), " 0x" );
689- BN_print (bio.get (), e);
756+ if (!BIOPointer::WriteAll (bio, " 0x" ) || BN_print (bio.get (), e) != 1 ) {
757+ return {};
758+ }
690759 return ToV8Value (env->context (), bio);
691760}
692761
@@ -712,7 +781,9 @@ MaybeLocal<Value> GetECGroupBits(Environment* env, const EC_GROUP* group) {
712781
713782template <const char * (*nid2string)(int nid)>
714783MaybeLocal<Value> GetCurveName (Environment* env, const int nid) {
715- std::string_view name = nid2string (nid);
784+ const char * const curve_name = nid2string (nid);
785+ if (curve_name == nullptr ) return Undefined (env->isolate ());
786+ const std::string_view name = curve_name;
716787 return name.size () ? MaybeLocal<Value>(OneByteString (env->isolate (), name))
717788 : MaybeLocal<Value>(Undefined (env->isolate ()));
718789}
@@ -747,10 +818,21 @@ MaybeLocal<Object> X509ToObject(Environment* env, const X509View& cert) {
747818 env->set_x509_dictionary_template (tmpl);
748819 }
749820
821+ Local<Value> subject;
822+ if (!GetX509NameObject (env, cert.getSubjectName ()).ToLocal (&subject)) {
823+ return {};
824+ }
825+
826+ Local<Value> issuer;
827+ if (!GetX509NameObject (env, cert.getIssuerName ()).ToLocal (&issuer)) {
828+ return {};
829+ }
830+
750831 MaybeLocal<Value> values[] = {
751- GetX509NameObject (env, cert.getSubjectName ()),
752- GetX509NameObject (env, cert.getIssuerName ()),
753- GetSubjectAltNameString (env, cert),
832+ subject,
833+ issuer,
834+ GetSubjectAltNameString (
835+ env, cert, MalformedSubjectAltNameMode::kReturnLegacySentinel ),
754836 GetInfoAccessString (env, cert),
755837 Boolean::New (env->isolate (), cert.isCA ()),
756838 Undefined (env->isolate ()), // modulus
@@ -769,6 +851,19 @@ MaybeLocal<Object> X509ToObject(Environment* env, const X509View& cert) {
769851 Undefined (env->isolate ()), // nistcurve
770852 };
771853
854+ // X509_get0_pubkey() fails when the linked OpenSSL cannot decode the
855+ // certificate's public key (e.g. an unsupported curve in FIPS builds).
856+ // ifRsa()/ifEc() assume a decodable key, so leave the key-specific
857+ // fields undefined in that case.
858+ const bool has_public_key = [&] {
859+ MarkPopErrorOnReturn mark_pop_error_on_return;
860+ return X509_get0_pubkey (cert.get ()) != nullptr ;
861+ }();
862+ if (!has_public_key) {
863+ return scope.EscapeMaybe (
864+ NewDictionaryInstance (env->context (), tmpl, values));
865+ }
866+
772867 cert.ifRsa ([&](const ncrypto::Rsa& rsa) {
773868 auto pub_key = rsa.getPublicKey ();
774869 values[5 ] = GetModulusString (env, pub_key.n ); // modulus
0 commit comments