From 99c80e3a45eb8ac25c904dc699e9289bfabbd02f Mon Sep 17 00:00:00 2001 From: James M Snell Date: Sun, 27 Jul 2025 14:34:12 -0700 Subject: [PATCH] quic: update the guard to check openssl version Since we need to be able to use the openssl adapter provided by the ngtcp2 library, and because that adapter does not include any compile guards to ensure that OpenSSL 3.5 is being used and that the APIs are actually available, we need to add a compile time check for the openssl version in order to conditionally include the adapter to avoid build errors when using a shared openssl library that is not OpenSSL 3.5. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/59249 Reviewed-By: Matteo Collina --- configure.py | 61 ++++++++++++++++++++++++++++++++++++++++++ deps/ngtcp2/ngtcp2.gyp | 31 ++++++++++++++++++++- src/quic/guard.h | 10 +------ 3 files changed, 92 insertions(+), 10 deletions(-) diff --git a/configure.py b/configure.py index 79d2e6709c9..c60dc30188a 100755 --- a/configure.py +++ b/configure.py @@ -1205,6 +1205,65 @@ def get_gas_version(cc): warn(f'Could not recognize `gas`: {gas_ret}') return '0.0' +def get_openssl_version(): + """Parse OpenSSL version from opensslv.h header file. + + Returns the version as a number matching OPENSSL_VERSION_NUMBER format: + 0xMNN00PPSL where M=major, NN=minor, PP=patch, S=status(0xf=release,0x0=pre), L=0 + """ + + try: + # Use the C compiler to extract preprocessor macros from opensslv.h + args = ['-E', '-dM', '-include', 'openssl/opensslv.h', '-'] + if not options.shared_openssl: + args = ['-I', 'deps/openssl/openssl/include'] + args + elif options.shared_openssl_includes: + args = ['-I', options.shared_openssl_includes] + args + + proc = subprocess.Popen( + shlex.split(CC) + args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE + ) + with proc: + proc.stdin.write(b'\n') + out = to_utf8(proc.communicate()[0]) + + if proc.returncode != 0: + warn('Failed to extract OpenSSL version from opensslv.h header') + return 0 + + # Parse the macro definitions + macros = {} + for line in out.split('\n'): + if line.startswith('#define OPENSSL_VERSION_'): + parts = line.split() + if len(parts) >= 3: + macro_name = parts[1] + macro_value = parts[2] + macros[macro_name] = macro_value + + # Extract version components + major = int(macros.get('OPENSSL_VERSION_MAJOR', '0')) + minor = int(macros.get('OPENSSL_VERSION_MINOR', '0')) + patch = int(macros.get('OPENSSL_VERSION_PATCH', '0')) + + # Check if it's a pre-release (has non-empty PRE_RELEASE string) + pre_release = macros.get('OPENSSL_VERSION_PRE_RELEASE', '""').strip('"') + status = 0x0 if pre_release else 0xf + # Construct version number: 0xMNN00PPSL + version_number = ((major << 28) | + (minor << 20) | + (patch << 4) | + status) + + return version_number + + except (OSError, ValueError, subprocess.SubprocessError) as e: + warn(f'Failed to determine OpenSSL version from header: {e}') + return 0 + # Note: Apple clang self-reports as clang 4.2.0 and gcc 4.2.1. It passes # the version check more by accident than anything else but a more rigorous # check involves checking the build number against an allowlist. I'm not @@ -1828,6 +1887,8 @@ def configure_openssl(o): if options.quic: o['defines'] += ['NODE_OPENSSL_HAS_QUIC'] + o['variables']['openssl_version'] = get_openssl_version() + configure_library('openssl', o) def configure_sqlite(o): diff --git a/deps/ngtcp2/ngtcp2.gyp b/deps/ngtcp2/ngtcp2.gyp index e166d94534d..2901eff484a 100644 --- a/deps/ngtcp2/ngtcp2.gyp +++ b/deps/ngtcp2/ngtcp2.gyp @@ -129,6 +129,36 @@ 'HAVE_NETINET_IN_H', ], }], + # TODO: Support OpenSSL 3.5 shared library builds. + # The complexity here is that we need to use the ngtcp2 ossl + # adapter, which does not include any conditional checks to + # see if the version of OpenSSL used has the necessary QUIC + # APIs, so we need to ensure that we conditionally enable use + # of the adapter only when we know that the OpenSSL version we + # are compiling against has the necessary APIs. We can do that + # by checkig the OpenSSL version number but, currently, the + # code that does so checks only the VERSION.dat file that is + # bundled with the openssl dependency. We'll need to update + # that to support the shared library case, where the version + # of the shared library needs to be determined. + # + # TODO: Support Boringssl here also. ngtcp2 provides an adapter + # for Boringssl. If we can detect that boringssl is being used + # here then we can use that adapter and also set the + # QUIC_NGTCP2_USE_BORINGSSL define (the guard in quic/guard.h + # would need to be updated to check for this define). + ['node_shared_openssl=="false" and openssl_version >= 0x3050001f', { + 'sources': [ + '<@(ngtcp2_sources_ossl)', + ], + 'direct_dependent_settings': { + 'defines': [ + # Tells us that we are using the OpenSSL 3.5 adapter + # that is provided by ngtcp2. + 'QUIC_NGTCP2_USE_OPENSSL_3_5', + ], + }, + }] ], 'direct_dependent_settings': { 'defines': [ @@ -143,7 +173,6 @@ }, 'sources': [ '<@(ngtcp2_sources)', - '<@(ngtcp2_sources_ossl)', ] }, { diff --git a/src/quic/guard.h b/src/quic/guard.h index 2e7bd441057..c0635034522 100644 --- a/src/quic/guard.h +++ b/src/quic/guard.h @@ -1,13 +1,5 @@ #pragma once -#if HAVE_OPENSSL -#include -// QUIC is only available in Openssl 3.5.x and later. It was not introduced in -// Node.js until 3.5.1... prior to that we will not compile any of the QUIC -// related code. -#if OPENSSL_VERSION_NUMBER < 0x30500010 || OPENSSL_IS_BORINGSSL -#define OPENSSL_NO_QUIC = 1 -#endif -#else +#ifndef QUIC_NGTCP2_USE_OPENSSL_3_5 #define OPENSSL_NO_QUIC = 1 #endif