You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让Node.js使用系统OpenSSL而非内置版本(无需源码编译)

问题描述

我需要通过OpenSSL的SSL_CTX_load_verify_locations函数注入自定义证书,覆盖TLS连接使用的证书。该方案在Python应用中通过LD_PRELOAD运行时覆盖OpenSSL函数已成功实现,但在Node.js中无效——原因是Node.js内置OpenSSL而非依赖动态链接库,导致LD_PRELOAD方法失效。

用于覆盖SSL_CTX_load_verify_locations的C代码

#define _GNU_SOURCE
#include <dlfcn.h>
#include <openssl/ssl.h>
#include <stdio.h>

// 保存原函数指针
int (*original_SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath) = NULL;

// 自定义覆盖函数
int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath) {
    // 加载原函数
    if (!original_SSL_CTX_load_verify_locations) {
        original_SSL_CTX_load_verify_locations = (int (*)(SSL_CTX *, const char *, const char *))
            dlsym(RTLD_NEXT, "SSL_CTX_load_verify_locations");

        const char *error = dlerror();
        if (error != NULL) {
            fprintf(stderr, "加载原SSL_CTX_load_verify_locations函数失败: %s\n", error);
            return -1;
        }
    }

    const char *my_CAfile = "/Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/sample-apps/keploy.crt";
    printf("\n\n替换CAfile为 %s\n\n", my_CAfile);

    // 调用原函数并传入自定义证书
    return original_SSL_CTX_load_verify_locations(ctx, my_CAfile, CApath);
}

编译命令

clang-14 -I/usr/include/openssl -L/usr/lib/aarch64-linux-gnu -fPIC -shared -o ssl_override.so ssl_override.c -lssl -lcrypto

Node.js测试代码

const express = require('express');
const axios = require('axios');
const fs = require('fs');
const https = require('https');

const app = express();
const CERTIFICATE_PATH = '/Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/sample-apps/ninja.crt';

const httpsAgent = new https.Agent({
  ca: fs.readFileSync(CERTIFICATE_PATH), 
  keepAlive: false,
});

app.get('/catFact', async (req, res) => {
  try {
    const response = await axios.get('https://catfact.ninja/fact', { httpsAgent });
    res.status(200).json(response.data);
  } catch (error) {
    res.status(500).json({
      error: 'SSL证书验证失败',
      details: error.message
    });
  }
});

const PORT = 8080;
app.listen(PORT, () => {
  console.log(`服务器运行在 http://0.0.0.0:${PORT}`);
});

Node.js运行命令

LD_PRELOAD=/Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/tls-change-cert/ssl_override.so node index.js

已尝试的解决方法

  • LD_PRELOAD加载多库:同时指定系统OpenSSL库和自定义覆盖库
    export LD_LIBRARY_PATH=/usr/lib/aarch64-linux-gnu:$LD_LIBRARY_PATH
    
    export LD_PRELOAD="/usr/lib/aarch64-linux-gnu/libssl.so.3 \
    /usr/lib/aarch64-linux-gnu/libcrypto.so.3 \
    /Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/tls-change-cert/ssl_override.so"
    
    node index.js
    
  • 用lsof验证库加载:确认自定义覆盖库和系统OpenSSL库已加载到Node.js进程
    ➜  lsof -p $(pidof node) | grep ssl
    node  507762 gouravkumar  mem REG 0,40  70040 /path/to/ssl_override.so
    node  507762 gouravkumar  mem REG 254,1 737192 /usr/lib/aarch64-linux-gnu/libssl.so.3
    
  • 结果:虽然库已加载,但覆盖函数中的printf语句未触发,说明Node.js仍在使用内置OpenSSL实现。

求助问题

  1. 如何强制Node.js使用系统安装的OpenSSL共享库(如libssl.so.3和libcrypto.so.3)而非内置版本?
  2. 是否无需重新编译Node.js源码,就能让其动态加载系统OpenSSL?
  3. 是否有已知的workaround或配置(如设置特定环境变量)可实现该需求?

Node.js版本信息

➜  tls-support git:(main) ✗ node -p "process.versions"        
{
  node: '18.20.4',
  acorn: '8.11.3',
  ada: '2.7.8',
  ares: '1.28.1',
  base64: '0.5.2',
  brotli: '1.0.9',
  cjs_module_lexer: '1.2.2',
  cldr: '44.1',
  icu: '74.2',
  llhttp: '6.1.1',
  modules: '108',
  napi: '9',
  nghttp2: '1.61.0',
  nghttp3: '0.7.0',
  ngtcp2: '1.3.0',
  openssl: '3.0.13+quic',
  simdutf: '5.2.4',
  tz: '2024a',
  undici: '5.28.4',
  unicode: '15.1',
  uv: '1.44.2',
  uvwasi: '0.0.19',
  v8: '10.2.154.26-node.37',
  zlib: '1.3.0.1-motley'
}

解决方案

核心结论

官方预编译的Node.js版本是静态链接内置OpenSSL的,无法通过LD_PRELOAD强制切换到系统OpenSSL,必须通过重新编译Node.js实现动态链接系统库。不过有两种替代方案可以绕开编译需求:

方案1:利用Node.js内置配置实现证书注入

不需要Hook OpenSSL函数,直接通过Node.js的原生机制覆盖CA证书:

  • 全局生效:设置环境变量NODE_EXTRA_CA_CERTS指向自定义证书文件,所有HTTPS请求会自动加载该证书进行验证:
    NODE_EXTRA_CA_CERTS=/Users/gouravkumar/Desktop/Keploy/Lima-workspace/gk_workspace/tls-ebpf/sample-apps/keploy.crt node index.js
    
  • 细粒度控制:如果只需要针对特定请求生效,可在https.Agent或axios配置中指定ca参数,就像你代码中已经实现的逻辑。

方案2:使用动态链接系统OpenSSL的Node.js版本

如果一定要用LD_PRELOAD的Hook方案,需要使用动态链接版本的Node.js:

  • 通过包管理器安装:部分Linux发行版(如Debian、Ubuntu)的官方包仓库提供的Node.js是动态链接系统OpenSSL的,直接通过sudo apt install nodejs安装即可。
  • 用nvm编译安装指定版本:如果需要特定版本的Node.js,可使用nvm的--shared-openssl参数从源码编译:
    nvm install 18.20.4 --shared-openssl
    
    编译完成后,Node.js会动态链接系统的OpenSSL库,此时LD_PRELOAD的Hook就能正常拦截SSL_CTX_load_verify_locations调用。

为什么之前的LD_PRELOAD尝试失败?

官方预编译的Node.js将OpenSSL代码直接静态编译进了二进制文件,进程中加载的系统libssl.so.3只是被你的Hook库引用,Node.js本身不会调用动态库中的OpenSSL函数,因此覆盖函数的printf不会触发。只有当Node.js动态链接系统OpenSSL时,相关函数才会从动态库加载,LD_PRELOAD的Hook才能生效。


内容的提问来源于stack exchange,提问作者Gourav Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 23:53:10