Hello,

Please find attached a patch to build lang/node against OpenBSD's
base-system LibreSSL instead of OpenSSL from ports.

The patch removes the dependency on security/openssl and adds the
necessary compatibility changes to Node.js.

The main changes are:

- Use the base-system libssl and libcrypto.
- Add LibreSSL compatibility code for ncrypto.
- Adapt TLS and cryptographic operations to LibreSSL.
- Reject unsupported cryptographic features explicitly.
- Adjust JavaScript feature detection to reflect LibreSSL's
  capabilities.
- Report the actual LibreSSL version through process.versions.openssl.
- Remove the external OpenSSL library paths and related build settings.

The goal is to make Node.js work with OpenBSD's native TLS and
cryptographic libraries without requiring a separate OpenSSL
installation.

I would appreciate review of the compatibility patches.

Thanks,
David.
? lang/node/README
Index: lang/node/Makefile
===================================================================
RCS file: /cvs/ports/lang/node/Makefile,v
diff -u -p -u -p -r1.176 Makefile
--- lang/node/Makefile	28 Oct 2026 22:43:42 -0000	1.176
+++ lang/node/Makefile	1 Nov 2026 15:29:34 -0000
@@ -13,6 +13,7 @@ DIST_TUPLE =		codeberg qbit node-pledge 
 DISTNAME =		node-${NODE_VERSION}
 PKGNAME =		${DISTNAME:S/v//g}
 EPOCH =			0
+REVISION =		1
 
 CATEGORIES =		lang devel
 
@@ -26,9 +27,8 @@ SITES =			${HOMEPAGE}dist/${NODE_VERSION
 
 MODULES =		lang/python
 
-WANTLIB += ${COMPILER_LIBCXX} c cares execinfo icudata icui18n
-WANTLIB += icuuc m nghttp2 nghttp3 simdutf uv z
-WANTLIB += lib/${EOPENSSL_VSN}/ssl lib/${EOPENSSL_VSN}/crypto
+WANTLIB += ${COMPILER_LIBCXX} c cares crypto execinfo icudata icui18n
+WANTLIB += icuuc m nghttp2 nghttp3 simdutf ssl uv z
 
 COMPILER =		base-clang ports-gcc
 
@@ -50,32 +50,22 @@ CONFIGURE_ARGS =	--prefix=${PREFIX} \
 			--shared-nghttp3 \
 			--shared-simdutf \
 			--shared-zlib \
-			--shared-openssl \
-			--shared-openssl-libpath=${EOPENSSL_LIB} \
-			--shared-openssl-includes=${EOPENSSL_INC}
+			--shared-openssl
 
 CFLAGS +=		-idirafter ${LOCALBASE}/include
 
 SUBST_VARS +=		DISTNAME
 SUBST_VARS +=		WRKDIST
 SUBST_VARS +=		NODE_VERSION
-SUBST_VARS +=		EOPENSSL_LIB
 
-# uses a wide range of OpenSSL API and only really supports boring/openssl
 LIB_DEPENDS +=		\
 			devel/libuv>=1.51.0p0 \
 			net/libcares \
-			security/openssl/${OPENSSL_VSN} \
 			textproc/icu4c \
 			textproc/simdutf \
 			www/nghttp2 \
 			www/nghttp3
 
-OPENSSL_VSN =		3.5
-EOPENSSL_VSN =		eopenssl${OPENSSL_VSN:C/\.//}
-EOPENSSL_LIB =		${LOCALBASE}/lib/${EOPENSSL_VSN}
-EOPENSSL_INC =		${LOCALBASE}/include/${EOPENSSL_VSN}
-
 DEBUG_PACKAGES =	${BUILD_PACKAGES}
 
 # Run default tests, without linters or building the docs.
@@ -101,9 +91,6 @@ post-install:
 		mv $$x ../..; \
 		ln -s ../../$$x $$x; \
 	done
-
-pre-test:
-	@ln -sf ${LOCALBASE}/bin/${EOPENSSL_VSN} ${WRKDIR}/bin/openssl
 
 # Includes a lot of networking tests that will fail when running as _pbuild
 do-test:
Index: lang/node/patches/patch-common_gypi
===================================================================
RCS file: /cvs/ports/lang/node/patches/patch-common_gypi,v
diff -u -p -u -p -r1.44 patch-common_gypi
--- lang/node/patches/patch-common_gypi	21 Oct 2026 16:04:35 -0000	1.44
+++ lang/node/patches/patch-common_gypi	1 Nov 2026 15:29:34 -0000
@@ -1,3 +1,7 @@
+Enable CET/IBT on OpenBSD, use -idirafter for local includes, and
+keep W^X.  The external OpenSSL rpath is no longer needed because
+this port uses the base-system LibreSSL.
+
 Index: common.gypi
 --- common.gypi.orig
 +++ common.gypi
@@ -19,16 +23,14 @@ Index: common.gypi
          'conditions': [
            ['enable_lto=="true"', {
              'cflags': ['<(lto)'],
-@@ -596,8 +598,10 @@
+@@ -596,8 +598,8 @@
              'standalone_static_library': 1,
            }],
            ['OS=="openbsd"', {
 -            'cflags': [ '-I/usr/local/include' ],
 -            'ldflags': [ '-Wl,-z,wxneeded' ],
 +            'cflags': [ '-idirafter /usr/local/include' ],
-+            'ldflags': [ '-Wl,-z,wxneeded'
-+                , '-Wl,-rpath,${EOPENSSL_LIB}'
-+            ],
++            'ldflags': [ '-Wl,-z,wxneeded' ],
            }],
            ['_toolset=="host"', {
              'conditions': [
Index: lang/node/patches/patch-deps_ncrypto_libressl_compat_h
===================================================================
RCS file: lang/node/patches/patch-deps_ncrypto_libressl_compat_h
diff -N lang/node/patches/patch-deps_ncrypto_libressl_compat_h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-deps_ncrypto_libressl_compat_h	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,212 @@
+Add LibreSSL compatibility helpers for ncrypto.
+Map missing allocation, secure-heap and key-check helpers onto
+the primitives LibreSSL actually provides.
+
+Index: deps/ncrypto/libressl_compat.h
+--- /dev/null
++++ deps/ncrypto/libressl_compat.h
+@@ -0,0 +1,204 @@
++#pragma once
++
++// Compatibility helpers for building ncrypto against LibreSSL.
++//
++// LibreSSL implements most of the OpenSSL 1.1 API but intentionally does not
++// provide the OpenSSL 3 provider API.  It also omits a number of allocation
++// and validation helpers that Node's legacy OpenSSL backend still expects.
++// The helpers below map those interfaces onto the equivalent LibreSSL
++// primitives.  They are only compiled when LibreSSL is detected.
++
++#ifdef LIBRESSL_VERSION_NUMBER
++
++#include <openssl/bio.h>
++#include <openssl/bn.h>
++#include <openssl/crypto.h>
++#include <openssl/dh.h>
++#include <openssl/dsa.h>
++#include <openssl/ec.h>
++#include <openssl/evp.h>
++#include <openssl/rsa.h>
++
++#include <cstddef>
++#include <cstring>
++
++// LibreSSL defines OPENSSL_NO_DEPRECATED unconditionally but still exports
++// the low-level key objects used by Node's legacy backend.
++#ifndef OPENSSL_NO_OCB
++// LibreSSL does not define the OCB mode identifiers even though its
++// opensslfeatures.h leaves OPENSSL_NO_OCB unset.  Do not advertise a mode
++// that cannot be selected.
++#define OPENSSL_NO_OCB 1
++#endif
++
++#ifndef OPENSSL_NO_SM2
++// LibreSSL has no SM2 NIDs or EVP_PKEY identifier.
++#define OPENSSL_NO_SM2 1
++#endif
++
++static inline void* OPENSSL_zalloc(size_t size) {
++  void* ptr = OPENSSL_malloc(size);
++  if (ptr != nullptr) std::memset(ptr, 0, size);
++  return ptr;
++}
++
++static inline void* OPENSSL_memdup(const void* data, size_t size) {
++  if (data == nullptr) return nullptr;
++  void* ptr = OPENSSL_malloc(size);
++  if (ptr != nullptr) std::memcpy(ptr, data, size);
++  return ptr;
++}
++
++static inline void OPENSSL_clear_free(void* ptr, size_t size) {
++  if (ptr == nullptr) return;
++  if (size != 0) OPENSSL_cleanse(ptr, size);
++  OPENSSL_free(ptr);
++}
++
++// LibreSSL has no secure heap implementation.  These helpers preserve the
++// documented OpenSSL fallback used when the secure heap has not been
++// initialized: allocate ordinary memory and report it as non-secure.
++// Node refuses to start when --secure-heap is requested on such a build.
++static inline void* OPENSSL_secure_zalloc(size_t size) {
++  return OPENSSL_zalloc(size);
++}
++
++static inline void OPENSSL_secure_clear_free(void* ptr, size_t size) {
++  OPENSSL_clear_free(ptr, size);
++}
++
++static inline int CRYPTO_secure_allocated(const void* ptr) {
++  (void)ptr;
++  return 0;
++}
++
++static inline int CRYPTO_secure_malloc_initialized(void) { return 0; }
++
++static inline size_t CRYPTO_secure_used(void) { return 0; }
++
++static inline BIGNUM* BN_secure_new(void) { return BN_new(); }
++
++static inline const BIO_METHOD* BIO_s_secmem(void) { return BIO_s_mem(); }
++
++// LibreSSL has no generic EVP_PKEY_check/public_check entry points.  Validate
++// the key through the low-level primitives that are available.  Returning 0
++// here rejects the key; it never silently accepts malformed key material.
++static inline int ncrypto_evp_pkey_public_check(EVP_PKEY_CTX* ctx) {
++  EVP_PKEY* pkey = ctx == nullptr ? nullptr : EVP_PKEY_CTX_get0_pkey(ctx);
++  if (pkey == nullptr) return 0;
++
++  switch (EVP_PKEY_id(pkey)) {
++    case EVP_PKEY_RSA:
++    case EVP_PKEY_RSA_PSS: {
++      const RSA* rsa = EVP_PKEY_get0_RSA(pkey);
++      const BIGNUM* n = nullptr;
++      const BIGNUM* e = nullptr;
++      if (rsa == nullptr) return 0;
++      RSA_get0_key(rsa, &n, &e, nullptr);
++      if (n == nullptr || e == nullptr) return 0;
++      return BN_num_bits(n) >= 512 && BN_is_odd(e) ? 1 : 0;
++    }
++    case EVP_PKEY_EC: {
++      const EC_KEY* key = EVP_PKEY_get0_EC_KEY(pkey);
++      return key != nullptr && EC_KEY_check_key(key) == 1 ? 1 : 0;
++    }
++    case EVP_PKEY_DSA: {
++      const DSA* dsa = EVP_PKEY_get0_DSA(pkey);
++      const BIGNUM* p = nullptr;
++      const BIGNUM* q = nullptr;
++      const BIGNUM* g = nullptr;
++      const BIGNUM* pub = nullptr;
++      if (dsa == nullptr) return 0;
++      DSA_get0_pqg(dsa, &p, &q, &g);
++      DSA_get0_key(dsa, &pub, nullptr);
++      if (p == nullptr || q == nullptr || g == nullptr || pub == nullptr)
++        return 0;
++      if (BN_is_negative(pub) || BN_is_zero(pub) || BN_cmp(pub, p) >= 0)
++        return 0;
++      return 1;
++    }
++    case EVP_PKEY_DH: {
++      const DH* dh = EVP_PKEY_get0_DH(pkey);
++      const BIGNUM* p = nullptr;
++      const BIGNUM* q = nullptr;
++      const BIGNUM* g = nullptr;
++      const BIGNUM* pub = nullptr;
++      int codes = 0;
++      if (dh == nullptr) return 0;
++      DH_get0_pqg(dh, &p, &q, &g);
++      DH_get0_key(dh, &pub, nullptr);
++      if (p == nullptr || g == nullptr || pub == nullptr) return 0;
++      if (DH_check_pub_key(dh, pub, &codes) != 1) return 0;
++      if (codes & (DH_CHECK_PUBKEY_TOO_SMALL | DH_CHECK_PUBKEY_TOO_LARGE))
++        return 0;
++      return 1;
++    }
++    case EVP_PKEY_ED25519:
++    case EVP_PKEY_X25519:
++      // Raw keys have no structural parameters to validate.
++      return 1;
++    default:
++      // Refuse unknown key types rather than claiming they are valid.
++      return 0;
++  }
++}
++
++static inline int ncrypto_evp_pkey_private_check(EVP_PKEY_CTX* ctx) {
++  EVP_PKEY* pkey = ctx == nullptr ? nullptr : EVP_PKEY_CTX_get0_pkey(ctx);
++  if (pkey == nullptr) return 0;
++
++  switch (EVP_PKEY_id(pkey)) {
++    case EVP_PKEY_RSA:
++    case EVP_PKEY_RSA_PSS: {
++      const RSA* rsa = EVP_PKEY_get0_RSA(pkey);
++      return rsa != nullptr && RSA_check_key(rsa) == 1 ? 1 : 0;
++    }
++    case EVP_PKEY_EC: {
++      const EC_KEY* key = EVP_PKEY_get0_EC_KEY(pkey);
++      return key != nullptr && EC_KEY_check_key(key) == 1 ? 1 : 0;
++    }
++    case EVP_PKEY_DSA: {
++      const DSA* dsa = EVP_PKEY_get0_DSA(pkey);
++      const BIGNUM* p = nullptr;
++      const BIGNUM* q = nullptr;
++      const BIGNUM* g = nullptr;
++      const BIGNUM* pub = nullptr;
++      const BIGNUM* priv = nullptr;
++      if (dsa == nullptr) return 0;
++      DSA_get0_pqg(dsa, &p, &q, &g);
++      DSA_get0_key(dsa, &pub, &priv);
++      if (p == nullptr || q == nullptr || g == nullptr || priv == nullptr)
++        return 0;
++      if (BN_is_negative(priv) || BN_is_zero(priv) || BN_cmp(priv, q) >= 0)
++        return 0;
++      if (pub != nullptr &&
++          (BN_is_negative(pub) || BN_is_zero(pub) || BN_cmp(pub, p) >= 0))
++        return 0;
++      return 1;
++    }
++    case EVP_PKEY_DH: {
++      const DH* dh = EVP_PKEY_get0_DH(pkey);
++      const BIGNUM* p = nullptr;
++      const BIGNUM* q = nullptr;
++      const BIGNUM* g = nullptr;
++      const BIGNUM* pub = nullptr;
++      const BIGNUM* priv = nullptr;
++      int codes = 0;
++      if (dh == nullptr) return 0;
++      DH_get0_pqg(dh, &p, &q, &g);
++      DH_get0_key(dh, &pub, &priv);
++      if (p == nullptr || g == nullptr || priv == nullptr) return 0;
++      if (DH_check(dh, &codes) != 1) return 0;
++      if (codes != 0) return 0;
++      if (BN_is_negative(priv) || BN_is_zero(priv)) return 0;
++      return 1;
++    }
++    case EVP_PKEY_ED25519:
++    case EVP_PKEY_X25519:
++      return 1;
++    default:
++      return 0;
++  }
++}
++
++#endif  // LIBRESSL_VERSION_NUMBER
Index: lang/node/patches/patch-deps_ncrypto_ncrypto_cc
===================================================================
RCS file: lang/node/patches/patch-deps_ncrypto_ncrypto_cc
diff -N lang/node/patches/patch-deps_ncrypto_ncrypto_cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-deps_ncrypto_ncrypto_cc	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,274 @@
+Adapt ncrypto to legacy APIs that LibreSSL does not provide.
+Keep unsupported features failing closed instead of returning
+fabricated success.
+
+Index: deps/ncrypto/ncrypto.cc
+--- deps/ncrypto/ncrypto.cc.orig
++++ deps/ncrypto/ncrypto.cc
+@@ -33,7 +33,8 @@
+ #endif
+ #endif
+ // EVP_PKEY_CTX_set_dsa_paramgen_q_bits was added in OpenSSL 1.1.1e.
+-#if OPENSSL_VERSION_NUMBER < 0x1010105fL
++// LibreSSL reports an OpenSSL 2.0 version number but does not expose it.
++#if OPENSSL_VERSION_NUMBER < 0x1010105fL || defined(LIBRESSL_VERSION_NUMBER)
+ #define EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits)                       \
+   EVP_PKEY_CTX_ctrl((ctx),                                                     \
+                     EVP_PKEY_DSA,                                              \
+@@ -422,6 +423,13 @@ size_t DataPointer::GetSecureHeapUsed()
+ 
+ DataPointer::InitSecureHeapResult DataPointer::TryInitSecureHeap(size_t amount,
+                                                                  size_t min) {
++#ifdef LIBRESSL_VERSION_NUMBER
++  // LibreSSL does not implement OpenSSL's secure heap.  Fail closed so that
++  // --secure-heap is rejected instead of silently using ordinary memory.
++  (void)amount;
++  (void)min;
++  return InitSecureHeapResult::FAILED;
++#else
+   switch (CRYPTO_secure_malloc_init(amount, min)) {
+     case 0:
+       return InitSecureHeapResult::FAILED;
+@@ -432,6 +440,7 @@ DataPointer::InitSecureHeapResult DataPo
+     default:
+       return InitSecureHeapResult::FAILED;
+   }
++#endif  // LIBRESSL_VERSION_NUMBER
+ }
+ 
+ DataPointer DataPointer::Copy(const Buffer<const void>& buffer) {
+@@ -499,9 +508,17 @@ DataPointer DataPointer::resize(size_t l
+   auto buf = release();
+   if (actual_len == 0) return DataPointer(buf.data, actual_len);
+ 
+-  buf.data = OPENSSL_realloc(buf.data, actual_len);
+-  buf.len = actual_len;
+-  return DataPointer(buf);
++  // LibreSSL does not expose OPENSSL_realloc.  Allocate a fresh buffer and
++  // copy the retained prefix so the pointer stays compatible with
++  // OPENSSL_free/OPENSSL_clear_free.
++  void* resized = OPENSSL_malloc(actual_len);
++  if (resized == nullptr) {
++    OPENSSL_clear_free(buf.data, buf.len);
++    return {};
++  }
++  std::memcpy(resized, buf.data, actual_len);
++  OPENSSL_clear_free(buf.data, buf.len);
++  return DataPointer(resized, actual_len);
+ }
+ 
+ // ============================================================================
+@@ -2942,7 +2959,15 @@ bool ScryptDerive(const Buffer<const cha
+ #endif
+ 
+ bool checkScryptParams(uint64_t N, uint64_t r, uint64_t p, uint64_t maxmem) {
+-#if NCRYPTO_USE_OPENSSL3_PROVIDER
++#ifdef OPENSSL_NO_SCRYPT
++  // LibreSSL does not provide a scrypt implementation.  Report the
++  // parameters as invalid so callers fail cleanly instead of deriving a key.
++  (void)N;
++  (void)r;
++  (void)p;
++  (void)maxmem;
++  return false;
++#elif NCRYPTO_USE_OPENSSL3_PROVIDER
+   // A null output validates the parameters without deriving a key.
+   return ScryptDerive({nullptr, 0}, {nullptr, 0}, N, r, p, maxmem, nullptr, 0);
+ #else
+@@ -2958,6 +2983,16 @@ DataPointer scrypt(const Buffer<const ch
+                    uint64_t p,
+                    uint64_t maxmem,
+                    size_t length) {
++#ifdef OPENSSL_NO_SCRYPT
++  (void)pass;
++  (void)salt;
++  (void)N;
++  (void)r;
++  (void)p;
++  (void)maxmem;
++  (void)length;
++  return {};
++#else
+   if (pass.len > INT_MAX || salt.len > INT_MAX) {
+     return {};
+   }
+@@ -2982,6 +3017,7 @@ DataPointer scrypt(const Buffer<const ch
+   }
+ 
+   return {};
++#endif  // OPENSSL_NO_SCRYPT
+ }
+ 
+ DataPointer pbkdf2(const Digest& md,
+@@ -3309,8 +3345,10 @@ const LegacyKeyAlgorithm kLegacyKeyAlgor
+     {KeyAlgorithm::X25519.name(), EVP_PKEY_X25519},
+ #endif
+     {"HKDF", EVP_PKEY_HKDF},
++#ifndef LIBRESSL_VERSION_NUMBER
+     {KeyAlgorithm::ED448.name(), EVP_PKEY_ED448},
+     {KeyAlgorithm::X448.name(), EVP_PKEY_X448},
++#endif
+ #ifndef OPENSSL_NO_SM2
+     {KeyAlgorithm::SM2.name(), EVP_PKEY_SM2},
+ #endif
+@@ -4922,7 +4960,13 @@ bool SSLPointer::setSniContext(const SSL
+     // SSL_use_certificate replaces only the certificate matching the key
+     // type. Clear all existing certificates so credentials from the default
+     // context cannot be selected for a different key type.
++    //
++    // LibreSSL keeps a single certificate and private key per SSL object and
++    // SSL_use_certificate() replaces them, so there is no per-key-type set to
++    // clear.
++#ifndef LIBRESSL_VERSION_NUMBER
+     SSL_certs_clear(get());
++#endif
+     err = SSL_use_certificate(get(), x509);
+   }
+   if (err == 1) err = SSL_use_PrivateKey(get(), pkey);
+@@ -4941,9 +4985,12 @@ std::optional<uint32_t> SSLPointer::veri
+   // Allow no-cert for PSK authentication in TLS1.2 and lower.
+   // In TLS1.3 check that session was reused because TLS1.3 PSK
+   // looks like session resumption.
+-  if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk ||
+-      (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION &&
+-       SSL_session_reused(get()))) {
++#ifndef OPENSSL_NO_PSK
++  if (SSL_CIPHER_get_auth_nid(curr_cipher) == NID_auth_psk)
++    return X509_V_OK;
++#endif
++  if (SSL_SESSION_get_protocol_version(sess) == TLS1_3_VERSION &&
++      SSL_session_reused(get())) {
+     return X509_V_OK;
+   }
+ 
+@@ -5016,7 +5063,13 @@ std::optional<std::string_view> SSLPoint
+ std::optional<std::string_view> SSLPointer::getCipherStandardName() const {
+   auto cipher = getCipher();
+   if (cipher == nullptr) return std::nullopt;
++#ifdef LIBRESSL_VERSION_NUMBER
++  // LibreSSL does not implement the IANA cipher-name mapping.  Fall back to
++  // the OpenSSL name rather than inventing a standard name.
++  return SSL_CIPHER_get_name(cipher);
++#else
+   return SSL_CIPHER_standard_name(cipher);
++#endif
+ }
+ 
+ std::optional<std::string_view> SSLPointer::getCipherVersion() const {
+@@ -5461,7 +5514,7 @@ const Cipher& Cipher::AES_256_KW() {
+   return GetPredefinedCipher<NID_id_aes256_wrap>();
+ }
+ 
+-#ifndef OPENSSL_IS_BORINGSSL
++#if !defined(OPENSSL_IS_BORINGSSL) && !defined(OPENSSL_NO_OCB)
+ const Cipher& Cipher::AES_128_OCB() {
+   return GetPredefinedCipher<NID_aes_128_ocb>();
+ }
+@@ -5509,8 +5562,12 @@ bool Cipher::isCtsMode() const {
+ }
+ 
+ bool Cipher::isOcbMode() const {
++#ifdef OPENSSL_NO_OCB
++  return false;
++#else
+   if (!cipher_) return false;
+   return getMode() == EVP_CIPH_OCB_MODE;
++#endif
+ }
+ 
+ bool Cipher::isSivMode() const {
+@@ -5581,8 +5638,10 @@ std::string_view Cipher::getModeLabel()
+       return "ecb";
+     case EVP_CIPH_GCM_MODE:
+       return "gcm";
++#ifndef OPENSSL_NO_OCB
+     case EVP_CIPH_OCB_MODE:
+       return "ocb";
++#endif
+     case EVP_CIPH_OFB_MODE:
+       return "ofb";
+ #if OPENSSL_WITH_AES_SIV
+@@ -5776,8 +5835,12 @@ bool CipherCtxPointer::isGcmMode() const
+ }
+ 
+ bool CipherCtxPointer::isOcbMode() const {
++#ifdef OPENSSL_NO_OCB
++  return false;
++#else
+   if (!ctx_) return false;
+   return getMode() == EVP_CIPH_OCB_MODE;
++#endif
+ }
+ 
+ bool CipherCtxPointer::isCcmMode() const {
+@@ -6695,7 +6758,9 @@ EVPKeyPointer EVPKeyCtxPointer::paramgen
+ bool EVPKeyCtxPointer::publicCheck() const {
+   if (!ctx_) return false;
+ #ifndef OPENSSL_IS_BORINGSSL
+-#if OPENSSL_VERSION_MAJOR >= 3
++#if defined(LIBRESSL_VERSION_NUMBER)
++  return ncrypto_evp_pkey_public_check(ctx_.get()) == 1;
++#elif OPENSSL_VERSION_MAJOR >= 3
+   return EVP_PKEY_public_check_quick(ctx_.get()) == 1;
+ #else
+   return EVP_PKEY_public_check(ctx_.get()) == 1;
+@@ -6711,7 +6776,11 @@ bool EVPKeyCtxPointer::publicCheck() con
+ bool EVPKeyCtxPointer::privateCheck() const {
+   if (!ctx_) return false;
+ #ifndef OPENSSL_IS_BORINGSSL
++#if defined(LIBRESSL_VERSION_NUMBER)
++  return ncrypto_evp_pkey_private_check(ctx_.get()) == 1;
++#else
+   return EVP_PKEY_check(ctx_.get()) == 1;
++#endif
+ #else
+   // Boringssl appears not to support this operation.
+   // TODO(jasnell): Is there an alternative approach that Boringssl does
+@@ -7054,7 +7123,7 @@ const Rsa::OtherPrimeInfos Rsa::getOther
+   for (const auto& info : other_prime_infos_) {
+     infos.push_back({info.r.get(), info.d.get(), info.t.get()});
+   }
+-#elif NCRYPTO_USE_LEGACY_OPENSSL
++#elif NCRYPTO_USE_LEGACY_OPENSSL && !defined(LIBRESSL_VERSION_NUMBER)
+   if (rsa_ == nullptr) return infos;
+   const int count = RSA_get_multi_prime_extra_count(rsa_);
+   if (count <= 0) return infos;
+@@ -7233,7 +7302,7 @@ bool Rsa::setPrivateKey(BignumPointer&&
+   dq.release();
+   qi.release();
+ 
+-#if NCRYPTO_USE_LEGACY_OPENSSL
++#if NCRYPTO_USE_LEGACY_OPENSSL && !defined(LIBRESSL_VERSION_NUMBER)
+   if (!other_prime_infos.empty()) {
+     std::vector<BIGNUM*> factors;
+     std::vector<BIGNUM*> exponents;
+@@ -7797,9 +7866,16 @@ bool EVPMDCtxPointer::digestFinalInto(Bu
+ 
+   auto ptr = static_cast<unsigned char*>(buf->data);
+ 
++#ifdef LIBRESSL_VERSION_NUMBER
++  // LibreSSL has no XOF finalisation primitive.  Only fixed-size digests can
++  // be returned; callers requesting a longer output must fail.
++  if (buf->len != getExpectedSize()) return false;
++  int ret = EVP_DigestFinal_ex(ctx_.get(), ptr, nullptr);
++#else
+   int ret = (buf->len == getExpectedSize())
+                 ? EVP_DigestFinal_ex(ctx_.get(), ptr, nullptr)
+                 : EVP_DigestFinalXOF(ctx_.get(), ptr, buf->len);
++#endif
+ 
+   if (ret != 1) [[unlikely]]
+     return false;
+@@ -7822,8 +7898,12 @@ const EVP_MD* EVPMDCtxPointer::getDigest
+ }
+ 
+ bool EVPMDCtxPointer::hasXofFlag() const {
++#ifdef LIBRESSL_VERSION_NUMBER
++  return false;
++#else
+   if (!ctx_) return false;
+   return (EVP_MD_flags(getDigest()) & EVP_MD_FLAG_XOF) == EVP_MD_FLAG_XOF;
++#endif
+ }
+ 
+ bool EVPMDCtxPointer::copyTo(const EVPMDCtxPointer& other) const {
Index: lang/node/patches/patch-deps_ncrypto_ncrypto_h
===================================================================
RCS file: lang/node/patches/patch-deps_ncrypto_ncrypto_h
diff -N lang/node/patches/patch-deps_ncrypto_ncrypto_h
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-deps_ncrypto_ncrypto_h	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,15 @@
+Include the LibreSSL compatibility header from ncrypto.
+
+Index: deps/ncrypto/ncrypto.h
+--- deps/ncrypto/ncrypto.h.orig
++++ deps/ncrypto/ncrypto.h
+@@ -12,6 +12,9 @@
+ #include <openssl/rsa.h>
+ #include <openssl/ssl.h>
+ #include <openssl/x509.h>
++#ifdef LIBRESSL_VERSION_NUMBER
++#include "libressl_compat.h"
++#endif
+ #include <array>
+ #include <cstddef>
+ #include <cstdint>
Index: lang/node/patches/patch-lib_internal_crypto_util_js
===================================================================
RCS file: lang/node/patches/patch-lib_internal_crypto_util_js
diff -N lang/node/patches/patch-lib_internal_crypto_util_js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-lib_internal_crypto_util_js	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,63 @@
+Do not advertise algorithms that LibreSSL does not implement.
+
+Index: lib/internal/crypto/util.js
+--- lib/internal/crypto/util.js.orig
++++ lib/internal/crypto/util.js
+@@ -22,6 +22,7 @@ const {
+   PromiseWithResolvers,
+   SafeMap,
+   SafeSet,
++  StringPrototypeIncludes,
+   StringPrototypeToUpperCase,
+   Symbol,
+   TypedArrayPrototypeGetBuffer,
+@@ -488,24 +489,28 @@ const kAlgorithmDefinitions = {
+ };
+ 
+ // Conditionally supported algorithms
++const isLibreSSL =
++  StringPrototypeIncludes(process.versions.openssl, 'LibreSSL');
+ const pqcKeyTypes = getPqcKeyTypes();
+ 
+ const conditionalAlgorithms = {
+-  'AES-OCB': !!hasAesOcbMode,
++  'AES-OCB': !isLibreSSL && !!hasAesOcbMode,
+   'Argon2d': !!Argon2Job,
+   'Argon2i': !!Argon2Job,
+   'Argon2id': !!Argon2Job,
+   'ChaCha20-Poly1305': process.features.openssl_is_boringssl ||
+     ArrayPrototypeIncludes(getCiphers(), 'chacha20-poly1305'),
+-  'cSHAKE128': !process.features.openssl_is_boringssl ||
+-    ArrayPrototypeIncludes(getHashes(), 'shake128'),
+-  'cSHAKE256': !process.features.openssl_is_boringssl ||
+-    ArrayPrototypeIncludes(getHashes(), 'shake256'),
+-  'Ed448': !process.features.openssl_is_boringssl,
++  'cSHAKE128': !isLibreSSL &&
++    (!process.features.openssl_is_boringssl ||
++     ArrayPrototypeIncludes(getHashes(), 'shake128')),
++  'cSHAKE256': !isLibreSSL &&
++    (!process.features.openssl_is_boringssl ||
++     ArrayPrototypeIncludes(getHashes(), 'shake256')),
++  'Ed448': !isLibreSSL && !process.features.openssl_is_boringssl,
+   'KMAC128': !!KmacJob,
+   'KMAC256': !!KmacJob,
+-  'KT128': !isFips,
+-  'KT256': !isFips,
++  'KT128': !isLibreSSL && !isFips,
++  'KT256': !isLibreSSL && !isFips,
+   'ML-DSA-44': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-44'),
+   'ML-DSA-65': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-65'),
+   'ML-DSA-87': ArrayPrototypeIncludes(pqcKeyTypes, 'ML-DSA-87'),
+@@ -530,9 +535,9 @@ const conditionalAlgorithms = {
+     ArrayPrototypeIncludes(getHashes(), 'sha3-384'),
+   'SHA3-512': !process.features.openssl_is_boringssl ||
+     ArrayPrototypeIncludes(getHashes(), 'sha3-512'),
+-  'TurboSHAKE128': !isFips,
+-  'TurboSHAKE256': !isFips,
+-  'X448': !process.features.openssl_is_boringssl,
++  'TurboSHAKE128': !isLibreSSL && !isFips,
++  'TurboSHAKE256': !isLibreSSL && !isFips,
++  'X448': !isLibreSSL && !process.features.openssl_is_boringssl,
+ };
+ 
+ // Experimental algorithms
Index: lang/node/patches/patch-lib_internal_crypto_webidl_js
===================================================================
RCS file: lang/node/patches/patch-lib_internal_crypto_webidl_js
diff -N lang/node/patches/patch-lib_internal_crypto_webidl_js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-lib_internal_crypto_webidl_js	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,28 @@
+Do not accept cSHAKE context parameters on LibreSSL.
+
+Index: lib/internal/crypto/webidl.js
+--- lib/internal/crypto/webidl.js.orig
++++ lib/internal/crypto/webidl.js
+@@ -6,6 +6,7 @@ const {
+   NumberParseInt,
+   ObjectPrototypeHasOwnProperty,
+   StringPrototypeCharCodeAt,
++  StringPrototypeIncludes,
+   StringPrototypeSplit,
+   StringPrototypeStartsWith,
+   StringPrototypeToLowerCase,
+@@ -686,6 +687,14 @@ converters.ContextParams = createDiction
+ 
+           if (process.features.openssl_is_boringssl) {
+             this.validator = validateLength;
++          } else if (StringPrototypeIncludes(process.versions.openssl,
++                                             'LibreSSL')) {
++            // LibreSSL does not implement cSHAKE context parameters.
++            const validateEmpty = validateZeroLength('ContextParams.context');
++            this.validator = (V, dict) => {
++              validateLength(V);
++              validateEmpty(V, dict);
++            };
+           } else {
+             let { 0: major, 1: minor } =
+               StringPrototypeSplit(process.versions.openssl, '.');
Index: lang/node/patches/patch-lib_internal_tls_wrap_js
===================================================================
RCS file: lang/node/patches/patch-lib_internal_tls_wrap_js
diff -N lang/node/patches/patch-lib_internal_tls_wrap_js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-lib_internal_tls_wrap_js	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,20 @@
+Reject pskCallback when LibreSSL has no PSK support.
+
+Index: lib/internal/tls/wrap.js
+--- lib/internal/tls/wrap.js.orig
++++ lib/internal/tls/wrap.js
+@@ -1043,6 +1043,14 @@ TLSSocket.prototype._init = function(soc
+   if (options.ALPNProtocols)
+     ssl.setALPNProtocols(options.ALPNProtocols);
+ 
++  if (options.pskCallback && !ssl.enablePskCallback) {
++    // LibreSSL is built without PSK support.  Reject the option instead of
++    // silently negotiating without the requested authentication mode.
++    throw new ERR_INVALID_ARG_VALUE(
++      'options.pskCallback', options.pskCallback,
++      'is not supported with LibreSSL');
++  }
++
+   if (options.pskCallback && ssl.enablePskCallback) {
+     validateFunction(options.pskCallback, 'pskCallback');
+ 
Index: lang/node/patches/patch-src_crypto_crypto_context_cc
===================================================================
RCS file: lang/node/patches/patch-src_crypto_crypto_context_cc
diff -N lang/node/patches/patch-src_crypto_crypto_context_cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-src_crypto_crypto_context_cc	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,33 @@
+Disable TLS interfaces that LibreSSL does not implement.
+setSigalgs now reports an unsupported operation.
+
+Index: src/crypto/crypto_context.cc
+--- src/crypto/crypto_context.cc.orig
++++ src/crypto/crypto_context.cc
+@@ -1951,6 +1951,11 @@ void SecureContext::SetNewSessionCallbac
+ void SecureContext::SetClientHelloCallback(ClientHelloCb cb) {
+ #ifdef OPENSSL_IS_BORINGSSL
+   SSL_CTX_set_select_certificate_cb(ctx_.get(), cb);
++#elif defined(LIBRESSL_VERSION_NUMBER)
++  // LibreSSL has no ClientHello callback.  The early ClientHello hook is only
++  // used for server-side resumeSession bookkeeping; session tickets continue
++  // to work through LibreSSL's own session handling.
++  (void)cb;
+ #else
+   SSL_CTX_set_client_hello_cb(ctx_.get(), cb, nullptr);
+ #endif
+@@ -2027,8 +2032,14 @@ void SecureContext::SetSigalgs(const Fun
+ 
+   const Utf8Value sigalgs(env->isolate(), args[0]);
+ 
++#ifdef LIBRESSL_VERSION_NUMBER
++  // LibreSSL does not expose signature algorithm list configuration.
++  return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(
++      env, "setSigalgs is not supported with LibreSSL");
++#else
+   if (!SSL_CTX_set1_sigalgs_list(sc->ctx_.get(), *sigalgs))
+     return ThrowCryptoError(env, ERR_get_error());
++#endif
+ }
+ 
+ #ifndef OPENSSL_NO_ENGINE
Index: lang/node/patches/patch-src_crypto_crypto_dh_cc
===================================================================
RCS file: lang/node/patches/patch-src_crypto_crypto_dh_cc
diff -N lang/node/patches/patch-src_crypto_crypto_dh_cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-src_crypto_crypto_dh_cc	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,14 @@
+Do not include the OpenSSL-only bnerr.h with LibreSSL.
+
+Index: src/crypto/crypto_dh.cc
+--- src/crypto/crypto_dh.cc.orig
++++ src/crypto/crypto_dh.cc
+@@ -7,7 +7,7 @@
+ #include "memory_tracker-inl.h"
+ #include "ncrypto.h"
+ #include "node_errors.h"
+-#ifndef OPENSSL_IS_BORINGSSL
++#if !defined(OPENSSL_IS_BORINGSSL) && !defined(LIBRESSL_VERSION_NUMBER)
+ #include "openssl/bnerr.h"
+ #endif
+ #include "openssl/dh.h"
Index: lang/node/patches/patch-src_crypto_crypto_hash_cc
===================================================================
RCS file: lang/node/patches/patch-src_crypto_crypto_hash_cc
diff -N lang/node/patches/patch-src_crypto_crypto_hash_cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-src_crypto_crypto_hash_cc	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,47 @@
+Handle the absence of XOF digests in LibreSSL.
+
+Index: src/crypto/crypto_hash.cc
+--- src/crypto/crypto_hash.cc.orig
++++ src/crypto/crypto_hash.cc
+@@ -360,6 +360,10 @@ const EVP_MD* GetDigestImplementation(
+ void MarkInvalidXofLength() {
+ #if NCRYPTO_USE_OPENSSL3_PROVIDER
+   ERR_raise(ERR_LIB_EVP, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
++#elif defined(LIBRESSL_VERSION_NUMBER)
++  // LibreSSL has no XOF implementation or matching reason code.
++  ERR_put_error(
++      ERR_LIB_EVP, 0, EVP_R_INVALID_DIGEST, __FILE__, __LINE__);
+ #else
+   EVPerr(EVP_F_EVP_DIGESTFINALXOF, EVP_R_NOT_XOF_OR_INVALID_LENGTH);
+ #endif
+@@ -379,7 +383,12 @@ void OneShotDigestWithMD(Environment* en
+ 
+   enum encoding output_enc = ParseEncoding(isolate, args[4], args[5], HEX);
+ 
++#ifdef LIBRESSL_VERSION_NUMBER
++  // LibreSSL does not implement XOF digests.
++  bool is_xof = false;
++#else
+   bool is_xof = (EVP_MD_flags(md) & EVP_MD_FLAG_XOF) != 0;
++#endif
+   int output_length = EVP_MD_size(md);
+ 
+   // This is to cause hash() to fail when an incorrect
+@@ -833,11 +842,17 @@ Maybe<void> HashTraits::AdditionalConfig
+         static_cast<uint32_t>(args[offset + 2].As<Uint32>()->Value()) /
+         CHAR_BIT;
+     if (params->length != expected) {
++#ifdef LIBRESSL_VERSION_NUMBER
++      // No XOF digests are available with LibreSSL.
++      THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
++      return Nothing<void>();
++#else
+       if ((EVP_MD_flags(params->digest.get()) & EVP_MD_FLAG_XOF) == 0)
+           [[unlikely]] {
+         THROW_ERR_CRYPTO_INVALID_DIGEST(env, "Digest method not supported");
+         return Nothing<void>();
+       }
++#endif
+     }
+   }
+ 
Index: lang/node/patches/patch-src_crypto_crypto_tls_cc
===================================================================
RCS file: lang/node/patches/patch-src_crypto_crypto_tls_cc
diff -N lang/node/patches/patch-src_crypto_crypto_tls_cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-src_crypto_crypto_tls_cc	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,172 @@
+Adapt TLS callbacks, tickets and reads to LibreSSL.
+Drain application data left buffered after a TLS 1.3 handshake.
+
+Index: src/crypto/crypto_tls.cc
+--- src/crypto/crypto_tls.cc.orig
++++ src/crypto/crypto_tls.cc
+@@ -120,7 +120,7 @@ ssl_select_cert_result_t EarlyClientHell
+              ? ssl_select_cert_success
+              : ssl_select_cert_retry;
+ }
+-#else
++#elif !defined(LIBRESSL_VERSION_NUMBER)
+ int EarlyClientHelloCallback(SSL* s, int* al, void* arg) {
+   TLSWrap* w = static_cast<TLSWrap*>(SSL_get_app_data(s));
+   if (!w->should_suspend_for_client_hello()) return SSL_CLIENT_HELLO_SUCCESS;
+@@ -415,7 +415,9 @@ TLSWrap::TLSWrap(Environment* env,
+   ssl_ = sc_->CreateSSL();
+   CHECK(ssl_);
+ 
++#ifndef LIBRESSL_VERSION_NUMBER
+   sc_->SetClientHelloCallback(EarlyClientHelloCallback);
++#endif
+   sc_->SetGetSessionCallback(GetSessionCallback);
+   sc_->SetNewSessionCallback(NewSessionCallback);
+ 
+@@ -589,7 +591,13 @@ void TLSWrap::InitSSL() {
+ 
+   ConfigureSecureContext(sc_.get());
+ 
++#ifndef LIBRESSL_VERSION_NUMBER
+   SSL_set_cert_cb(ssl_.get(), SSLCertCallback, this);
++#else
++  // LibreSSL has no per-connection certificate callback.  Static
++  // certificates and SSL_CTX-based SNI selection still work; the asynchronous
++  // 'cert'/'OCSPRequest' callbacks are rejected by EnableCertCb() below.
++#endif
+ 
+   if (is_server()) {
+     SSL_set_accept_state(ssl_.get());
+@@ -872,6 +880,7 @@ void TLSWrap::ClearOut() {
+ 
+   char out[kClearOutChunkSize];
+   int read;
++drain:
+   for (;;) {
+     {
+       SSLLibraryCallScope ssl_library_call_scope(this);
+@@ -965,6 +974,15 @@ void TLSWrap::ClearOut() {
+         }
+         break;
+ 
++      case SSL_ERROR_WANT_READ:
++      case SSL_ERROR_WANT_WRITE:
++        // LibreSSL can complete a TLS 1.3 handshake and leave already
++        // decrypted application data buffered inside the SSL object while
++        // SSL_read() reports WANT_READ.  Drain that data before waiting for
++        // more socket input.
++        if (SSL_pending(ssl_.get()) > 0) goto drain;
++        return;
++
+       default:
+         return;
+     }
+@@ -1200,6 +1218,18 @@ int TLSWrap::DoWrite(WriteWrap* w,
+       SSLLibraryCallScope ssl_library_call_scope(this);
+       written = SSL_write(ssl_.get(), bs->Data(), length);
+     }
++    if (written == -1 && ssl_ != nullptr) {
++      int err = SSL_get_error(ssl_.get(), written);
++      if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
++        // LibreSSL can require pending handshake data to be consumed before
++        // it accepts the first application record after a TLS 1.3 handshake.
++        ClearOut();
++        if (ssl_ != nullptr) {
++          SSLLibraryCallScope ssl_library_call_scope(this);
++          written = SSL_write(ssl_.get(), bs->Data(), length);
++        }
++      }
++    }
+   } else {
+     // Only one buffer: try to write directly, only store if it fails
+     uv_buf_t* buf = &bufs[nonempty_i];
+@@ -1209,6 +1239,17 @@ int TLSWrap::DoWrite(WriteWrap* w,
+       written = SSL_write(ssl_.get(), buf->base, buf->len);
+     }
+ 
++    if (written == -1 && ssl_ != nullptr) {
++      int err = SSL_get_error(ssl_.get(), written);
++      if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) {
++        ClearOut();
++        if (ssl_ != nullptr) {
++          SSLLibraryCallScope ssl_library_call_scope(this);
++          written = SSL_write(ssl_.get(), buf->base, buf->len);
++        }
++      }
++    }
++
+     if (written == -1) {
+       bs = ArrayBuffer::NewBackingStore(
+           env()->isolate(),
+@@ -1462,7 +1503,15 @@ void TLSWrap::Destroy() {
+ void TLSWrap::EnableCertCb(const FunctionCallbackInfo<Value>& args) {
+   TLSWrap* wrap;
+   ASSIGN_OR_RETURN_UNWRAP(&wrap, args.This());
++#ifdef LIBRESSL_VERSION_NUMBER
++  // LibreSSL cannot suspend a handshake for an asynchronous certificate or
++  // OCSP callback.  Fail instead of silently ignoring the callback.
++  return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(
++      wrap->env(),
++      "asynchronous certificate callbacks are not supported with LibreSSL");
++#else
+   wrap->WaitForCertCb(ResumeAfterCertCb, wrap);
++#endif
+ }
+ 
+ void TLSWrap::WaitForCertCb(CertCb cb, void* arg) {
+@@ -1553,10 +1602,15 @@ int TLSWrap::SelectSNIContextCallback(SS
+ }
+ 
+ int TLSWrap::SetCACerts(SecureContext* sc) {
++#ifndef LIBRESSL_VERSION_NUMBER
+   int err = SSL_set1_verify_cert_store(ssl_.get(),
+                                        SSL_CTX_get_cert_store(sc->ctx().get()));
+   if (err != 1)
+     return err;
++#else
++  // LibreSSL has no per-SSL certificate store setter.  SSL_set_SSL_CTX()
++  // already switched the connection to the SNI context, including its store.
++#endif
+ 
+   STACK_OF(X509_NAME)* list =
+       SSL_dup_CA_list(SSL_CTX_get_client_CA_list(sc->ctx().get()));
+@@ -2052,6 +2106,10 @@ void TLSWrap::GetSharedSigalgs(const Fun
+   TLSWrap* w;
+   ASSIGN_OR_RETURN_UNWRAP(&w, args.This());
+ 
++#ifdef LIBRESSL_VERSION_NUMBER
++  return THROW_ERR_CRYPTO_UNSUPPORTED_OPERATION(
++      env, "getSharedSigalgs is not supported with LibreSSL");
++#else
+   SSL* ssl = w->ssl_.get();
+   int nsig = SSL_get_shared_sigalgs(ssl, 0, nullptr, nullptr, nullptr, nullptr,
+                                     nullptr);
+@@ -2124,6 +2182,7 @@ void TLSWrap::GetSharedSigalgs(const Fun
+ 
+   args.GetReturnValue().Set(
+                  Array::New(env->isolate(), ret_arr.out(), ret_arr.length()));
++#endif  // LIBRESSL_VERSION_NUMBER
+ }
+ 
+ void TLSWrap::ExportKeyingMaterial(const FunctionCallbackInfo<Value>& args) {
+@@ -2190,6 +2249,12 @@ void TLSWrap::GetTLSTicket(const Functio
+   if (sess == nullptr)
+     return;
+ 
++#ifdef LIBRESSL_VERSION_NUMBER
++  // LibreSSL does not expose the raw TLS ticket bytes.
++  (void)env;
++  (void)sess;
++  return;
++#else
+   const unsigned char* ticket;
+   size_t length;
+   SSL_SESSION_get0_ticket(sess, &ticket, &length);
+@@ -2199,6 +2264,7 @@ void TLSWrap::GetTLSTicket(const Functio
+         Buffer::Copy(env, reinterpret_cast<const char*>(ticket), length)
+             .FromMaybe(Local<Object>()));
+   }
++#endif  // LIBRESSL_VERSION_NUMBER
+ }
+ 
+ void TLSWrap::NewSessionDone(const FunctionCallbackInfo<Value>& args) {
Index: lang/node/patches/patch-src_crypto_crypto_util_cc
===================================================================
RCS file: lang/node/patches/patch-src_crypto_crypto_util_cc
diff -N lang/node/patches/patch-src_crypto_crypto_util_cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-src_crypto_crypto_util_cc	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,78 @@
+Initialise LibreSSL without OPENSSL_INIT_SETTINGS and map its
+available error libraries.  Reject --secure-heap fail-closed.
+
+Index: src/crypto/crypto_util.cc
+--- src/crypto/crypto_util.cc.orig
++++ src/crypto/crypto_util.cc
+@@ -23,6 +23,11 @@
+ #include "openssl/provider.h"
+ #endif
+ 
++#ifdef LIBRESSL_VERSION_NUMBER
++#include <openssl/conf.h>
++#include <cstdlib>
++#endif
++
+ #if !defined(OPENSSL_IS_BORINGSSL) && OPENSSL_VERSION_PREREQ(3, 4)
+ #include <openssl/indicator.h>
+ #endif
+@@ -504,6 +509,14 @@ void InitCryptoOnce() {
+   Mutex::ScopedLock lock(per_process::cli_options_mutex);
+   Mutex::ScopedLock fips_lock(fips_mutex);
+ #ifndef OPENSSL_IS_BORINGSSL
++#ifdef LIBRESSL_VERSION_NUMBER
++  // LibreSSL has no OPENSSL_INIT_SETTINGS object.  Apply --openssl-config
++  // through OPENSSL_config() and initialise the library with no settings.
++  if (!per_process::cli_options->openssl_config.empty()) {
++    OPENSSL_config(per_process::cli_options->openssl_config.c_str());
++  }
++  OPENSSL_init_ssl(0, nullptr);
++#else
+   OPENSSL_INIT_SETTINGS* settings = OPENSSL_INIT_new();
+   CHECK_NOT_NULL(settings);
+ 
+@@ -526,11 +539,14 @@ void InitCryptoOnce() {
+ #endif
+ 
+   OPENSSL_init_ssl(0, settings);
++#endif
+   InstallFipsIndicatorCallback();
+ 
+   ncrypto::ConfigurePqcEncoding();
++#ifndef LIBRESSL_VERSION_NUMBER
+   OPENSSL_INIT_free(settings);
+   settings = nullptr;
++#endif
+ 
+ #ifndef _WIN32
+   if (per_process::cli_options->secure_heap != 0) {
+@@ -539,6 +555,12 @@ void InitCryptoOnce() {
+         per_process::cli_options->secure_heap_min)) {
+       case DataPointer::InitSecureHeapResult::FAILED:
+         fprintf(stderr, "Unable to initialize openssl secure heap.\n");
++#ifdef LIBRESSL_VERSION_NUMBER
++        // LibreSSL has no secure heap.  Refuse to run rather than silently
++        // keeping supposedly protected secrets on the ordinary heap.
++        fflush(stderr);
++        exit(1);
++#endif
+         break;
+       case DataPointer::InitSecureHeapResult::UNABLE_TO_MEMORY_MAP:
+         // Not a fatal error but worthy of a warning.
+@@ -939,6 +961,16 @@ Maybe<void> Decorate(Environment* env,
+ 
+ #ifdef OPENSSL_IS_BORINGSSL
+ #define OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V)
++#elif defined(LIBRESSL_VERSION_NUMBER)
++// LibreSSL has no OSSL_STORE, ASYNC, or SM2 error library identifiers.
++#define OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V)                                   \
++  V(PKCS12)                                                                    \
++  V(DSO)                                                                       \
++  V(STORE)                                                                     \
++  V(FIPS)                                                                      \
++  V(TS)                                                                        \
++  V(CT)                                                                        \
++  V(KDF)
+ #else
+ #define OSSL_ERROR_CODES_MAP_OPENSSL_ONLY(V)                                   \
+   V(PKCS12)                                                                    \
Index: lang/node/patches/patch-src_node_metadata_cc
===================================================================
RCS file: lang/node/patches/patch-src_node_metadata_cc
diff -N lang/node/patches/patch-src_node_metadata_cc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-src_node_metadata_cc	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,25 @@
+Report LibreSSL in process.versions.openssl.
+
+Index: src/node_metadata.cc
+--- src/node_metadata.cc.orig
++++ src/node_metadata.cc
+@@ -63,6 +63,11 @@ static inline std::string GetOpenSSLVers
+   // sample openssl version string format
+   // for reference: "OpenSSL 1.1.0i 14 Aug 2018"
+   const char* version = OpenSSL_version(OPENSSL_VERSION);
++#ifdef LIBRESSL_VERSION_NUMBER
++  // Report the library and version truthfully instead of presenting LibreSSL
++  // as an OpenSSL release.
++  return std::string(version);
++#else
+   const size_t first_space = search(version, ' ');
+ 
+   // When Node.js is linked to an alternative library implementing the
+@@ -75,6 +80,7 @@ static inline std::string GetOpenSSLVers
+   const size_t start = first_space + 1;
+   const size_t len = search(&version[start], ' ');
+   return std::string(version, start, len);
++#endif  // LIBRESSL_VERSION_NUMBER
+ }
+ #endif  // HAVE_OPENSSL
+ 
Index: lang/node/patches/patch-test_common_crypto_js
===================================================================
RCS file: lang/node/patches/patch-test_common_crypto_js
diff -N lang/node/patches/patch-test_common_crypto_js
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ lang/node/patches/patch-test_common_crypto_js	1 Nov 2026 15:29:34 -0000
@@ -0,0 +1,26 @@
+Treat LibreSSL as not being an OpenSSL release for feature tests.
+
+Index: test/common/crypto.js
+--- test/common/crypto.js.orig
++++ test/common/crypto.js
+@@ -112,9 +112,12 @@ const opensslVersionNumber = (major = 0,
+   return (major << 28) | (minor << 20) | (patch << 4);
+ };
+ 
++const isLibreSSL = /LibreSSL/.test(process.versions.openssl);
+ let OPENSSL_VERSION_NUMBER;
+ const hasOpenSSL = (major = 0, minor = 0, patch = 0) => {
+   if (!common.hasCrypto) return false;
++  // LibreSSL is not an OpenSSL release; never satisfy OpenSSL feature gates.
++  if (isLibreSSL) return false;
+   if (OPENSSL_VERSION_NUMBER === undefined) {
+     const regexp = /(?<m>\d+)\.(?<n>\d+)\.(?<p>\d+)/;
+     const { m, n, p } = process.versions.openssl.match(regexp).groups;
+@@ -147,6 +150,7 @@ module.exports = {
+   hasOpenSSL,
+   hasFIPS,
+   isBoringSSL,
++  isLibreSSL,
+   get hasOpenSSL3() {
+     return hasOpenSSL(3);
+   },

Reply via email to