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);
+ },