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;
@@ -65,12 +70,20 @@ ManagedX509& ManagedX509::operator=(const ManagedX509& that) {
6570
6671void ManagedX509::MemoryInfo (MemoryTracker* tracker) const {
6772 if (!cert_) return ;
73+ MarkPopErrorOnReturn mark_pop_error_on_return;
6874 // This is an approximation based on the der encoding size.
69- int size = i2d_X509 (cert_.get (), nullptr );
70- tracker->TrackFieldWithSize (" cert" , size);
75+ const int size = i2d_X509 (cert_.get (), nullptr );
76+ if (size > 0 ) tracker->TrackFieldWithSize (" cert" , size);
7177}
7278
7379namespace {
80+ enum class MalformedSubjectAltNameMode {
81+ kThrow ,
82+ kReturnLegacySentinel ,
83+ };
84+
85+ constexpr std::string_view kMalformedSubjectAltName = " DNS:" ;
86+
7487MaybeLocal<Value> GetFingerprintDigest (Environment* env,
7588 const Digest& method,
7689 const X509View& cert) {
@@ -167,12 +180,29 @@ MaybeLocal<Value> GetDer(Environment* env, const X509View& view) {
167180 return ret;
168181}
169182
170- MaybeLocal<Value> GetSubjectAltNameString (Environment* env,
171- const X509View& view) {
183+ MaybeLocal<Value> GetSubjectAltNameString (
184+ Environment* env,
185+ const X509View& view,
186+ MalformedSubjectAltNameMode malformed_name_mode) {
172187 Local<Value> ret;
173- auto bio = view.getSubjectAltName ();
174- if (!bio) [[unlikely]]
175- return Undefined (env->isolate ());
188+ bool has_extension = false ;
189+ auto bio = view.getSubjectAltName (&has_extension);
190+ if (!bio) [[unlikely]] {
191+ // An absent SAN maps to undefined. A malformed, present SAN throws for
192+ // direct X509Certificate inspection, while the legacy TLS object uses a
193+ // deliberately nonmatching value to prevent commonName fallback.
194+ if (!has_extension) return Undefined (env->isolate ());
195+ if (malformed_name_mode ==
196+ MalformedSubjectAltNameMode::kReturnLegacySentinel ) {
197+ // The legacy object is used by TLS hostname verification. Return an
198+ // empty DNS name so verification cannot fall back to the common name,
199+ // while keeping getPeerCertificate() usable for malformed certificates.
200+ return OneByteString (env->isolate (), kMalformedSubjectAltName );
201+ }
202+ THROW_ERR_CRYPTO_OPERATION_FAILED (
203+ env, " Failed to format subject alternative name" );
204+ return {};
205+ }
176206 if (!ToV8Value (env->context (), bio).ToLocal (&ret)) return {};
177207 return ret;
178208}
@@ -211,13 +241,19 @@ MaybeLocal<Value> GetValidTo(Environment* env, const X509View& view) {
211241}
212242
213243MaybeLocal<Value> GetValidFromDate (Environment* env, const X509View& view) {
214- int64_t validFromTime = view.getValidFromTime ();
215- return Date::New (env->context (), validFromTime * 1000 .);
244+ const auto valid_from_time = view.getValidFromTime ();
245+ const double milliseconds = valid_from_time.has_value ()
246+ ? valid_from_time.value () * 1000 .
247+ : std::numeric_limits<double >::quiet_NaN ();
248+ return Date::New (env->context (), milliseconds);
216249}
217250
218251MaybeLocal<Value> GetValidToDate (Environment* env, const X509View& view) {
219- int64_t validToTime = view.getValidToTime ();
220- return Date::New (env->context (), validToTime * 1000 .);
252+ const auto valid_to_time = view.getValidToTime ();
253+ const double milliseconds = valid_to_time.has_value ()
254+ ? valid_to_time.value () * 1000 .
255+ : std::numeric_limits<double >::quiet_NaN ();
256+ return Date::New (env->context (), milliseconds);
221257}
222258
223259MaybeLocal<Value> GetSignatureAlgorithm (Environment* env,
@@ -296,7 +332,9 @@ void SubjectAltName(const FunctionCallbackInfo<Value>& args) {
296332 X509Certificate* cert;
297333 ASSIGN_OR_RETURN_UNWRAP (&cert, args.This ());
298334 Local<Value> ret;
299- if (GetSubjectAltNameString (env, cert->view ()).ToLocal (&ret)) {
335+ if (GetSubjectAltNameString (
336+ env, cert->view (), MalformedSubjectAltNameMode::kThrow )
337+ .ToLocal (&ret)) {
300338 args.GetReturnValue ().Set (ret);
301339 }
302340}
@@ -517,11 +555,29 @@ void CheckEmail(const FunctionCallbackInfo<Value>& args) {
517555}
518556
519557void CheckIP (const FunctionCallbackInfo<Value>& args) {
520- CheckX509Subject (args,
521- [](X509View& cert,
522- std::string_view subject,
523- uint32_t flags,
524- DataPointer*) { return cert.checkIp (subject, flags); });
558+ CheckX509Subject (
559+ args,
560+ [](X509View& cert,
561+ std::string_view subject,
562+ uint32_t flags,
563+ DataPointer*) {
564+ if (subject.find (' \0 ' ) != std::string_view::npos ||
565+ subject.find (' %' ) != std::string_view::npos) {
566+ return X509View::CheckMatch::INVALID_NAME ;
567+ }
568+
569+ const std::string input (subject);
570+ std::array<unsigned char , 16 > address{};
571+ size_t length = 0 ;
572+ if (uv_inet_pton (AF_INET , input.c_str (), address.data ()) == 0 ) {
573+ length = 4 ;
574+ } else if (uv_inet_pton (AF_INET6 , input.c_str (), address.data ()) == 0 ) {
575+ length = 16 ;
576+ } else {
577+ return X509View::CheckMatch::INVALID_NAME ;
578+ }
579+ return cert.checkIp ({.data = address.data (), .len = length}, flags);
580+ });
525581}
526582
527583void GetIssuerCert (const FunctionCallbackInfo<Value>& args) {
@@ -606,9 +662,20 @@ static MaybeLocal<Value> GetX509NameObject(Environment* env,
606662 Object::New (env->isolate (), Null (env->isolate ()), nullptr , nullptr , 0 );
607663 if (result.IsEmpty ()) return {};
608664
609- for (auto i : name) {
610- if (!ToV8Value (env, i.first ).ToLocal (&v8_name) ||
611- !ToV8Value (env, i.second ).ToLocal (&v8_value)) {
665+ for (auto entry : name) {
666+ if (!entry.has_value ()) [[unlikely]] {
667+ THROW_ERR_CRYPTO_OPERATION_FAILED (env,
668+ " Failed to convert X509 name to UTF-8" );
669+ return {};
670+ }
671+
672+ const auto & [key, value] = *entry;
673+ // The legacy object can only represent string values. Preserve valid
674+ // sibling attributes and omit values that cannot be converted to UTF-8.
675+ if (!value.has_value ()) continue ;
676+
677+ if (!ToV8Value (env, key).ToLocal (&v8_name) ||
678+ !ToV8Value (env, *value).ToLocal (&v8_value)) {
612679 return {};
613680 }
614681
@@ -669,11 +736,13 @@ MaybeLocal<Value> GetModulusString(Environment* env, const BIGNUM* n) {
669736
670737MaybeLocal<Value> GetExponentString (Environment* env, const BIGNUM * e) {
671738 if (e == nullptr ) return Null (env->isolate ());
739+ MarkPopErrorOnReturn mark_pop_error_on_return;
672740 auto bio = BIOPointer::NewMem ();
673741 if (!bio) [[unlikely]]
674742 return {};
675- BIO_puts (bio.get (), " 0x" );
676- BN_print (bio.get (), e);
743+ if (!BIOPointer::WriteAll (bio, " 0x" ) || BN_print (bio.get (), e) != 1 ) {
744+ return {};
745+ }
677746 return ToV8Value (env->context (), bio);
678747}
679748
@@ -699,7 +768,9 @@ MaybeLocal<Value> GetECGroupBits(Environment* env, const EC_GROUP* group) {
699768
700769template <const char * (*nid2string)(int nid)>
701770MaybeLocal<Value> GetCurveName (Environment* env, const int nid) {
702- std::string_view name = nid2string (nid);
771+ const char * const curve_name = nid2string (nid);
772+ if (curve_name == nullptr ) return Undefined (env->isolate ());
773+ const std::string_view name = curve_name;
703774 return name.size () ? MaybeLocal<Value>(OneByteString (env->isolate (), name))
704775 : MaybeLocal<Value>(Undefined (env->isolate ()));
705776}
@@ -734,10 +805,21 @@ MaybeLocal<Object> X509ToObject(Environment* env, const X509View& cert) {
734805 env->set_x509_dictionary_template (tmpl);
735806 }
736807
808+ Local<Value> subject;
809+ if (!GetX509NameObject (env, cert.getSubjectName ()).ToLocal (&subject)) {
810+ return {};
811+ }
812+
813+ Local<Value> issuer;
814+ if (!GetX509NameObject (env, cert.getIssuerName ()).ToLocal (&issuer)) {
815+ return {};
816+ }
817+
737818 MaybeLocal<Value> values[] = {
738- GetX509NameObject (env, cert.getSubjectName ()),
739- GetX509NameObject (env, cert.getIssuerName ()),
740- GetSubjectAltNameString (env, cert),
819+ subject,
820+ issuer,
821+ GetSubjectAltNameString (
822+ env, cert, MalformedSubjectAltNameMode::kReturnLegacySentinel ),
741823 GetInfoAccessString (env, cert),
742824 Boolean::New (env->isolate (), cert.isCA ()),
743825 Undefined (env->isolate ()), // modulus
@@ -756,6 +838,19 @@ MaybeLocal<Object> X509ToObject(Environment* env, const X509View& cert) {
756838 Undefined (env->isolate ()), // nistcurve
757839 };
758840
841+ // X509_get0_pubkey() fails when the linked OpenSSL cannot decode the
842+ // certificate's public key (e.g. an unsupported curve in FIPS builds).
843+ // ifRsa()/ifEc() assume a decodable key, so leave the key-specific
844+ // fields undefined in that case.
845+ const bool has_public_key = [&] {
846+ MarkPopErrorOnReturn mark_pop_error_on_return;
847+ return X509_get0_pubkey (cert.get ()) != nullptr ;
848+ }();
849+ if (!has_public_key) {
850+ return scope.EscapeMaybe (
851+ NewDictionaryInstance (env->context (), tmpl, values));
852+ }
853+
759854 cert.ifRsa ([&](const ncrypto::Rsa& rsa) {
760855 auto pub_key = rsa.getPublicKey ();
761856 values[5 ] = GetModulusString (env, pub_key.n ); // modulus
0 commit comments