我正在研究一个用例,我需要通过 OpenSSL 中的 SSL_CTX_load_verify_locations 函数注入自定义证书来覆盖 TLS 连接期间使用的证书。我已在 Python 应用程序 中成功实现了此解决方案,使用 LD_PRELOAD 在运行时覆盖 OpenSSL 函数。
但是,同样的方法不适用于 Node.js。经过一番研究,我发现 Node.js在内部嵌入了 OpenSSL ,而不是依赖于动态链接的 OpenSSL 库。结果看来我的LD_PRELOAD技巧没有效果。我正在寻找一种方法来强制 Node.js 使用系统安装的 OpenSSL(共享库)而不是嵌入式的,而无需从源代码重建 Node.js。
以下是我的设置的详细信息以及我迄今为止所尝试的内容。覆盖 SSL_CTX_load_verify_locations 的 C 代码
#define _GNU_SOURCE
#include <dlfcn.h>
#include <openssl/ssl.h>
#include <stdio.h>
// Function pointer to hold the original SSL_CTX_load_verify_locations function
int (*original_SSL_CTX_load_verify_locations)(SSL_CTX *ctx, const char *CAfile, const char *CApath) = NULL;
// Custom SSL_CTX_load_verify_locations function
int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile, const char *CApath) {
// Load the original function if it hasn't been loaded yet
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, "Error loading original 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\nOverriding CAfile with %s\n\n", my_CAfile);
// Call the original function with the overridden 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 certificate validation failed',
details: error.message
});
}
});
const PORT = 8080;
app.listen(PORT, () => {
console.log(`Server is running on 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
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 -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
如何强制 Node.js 使用系统安装的 OpenSSL 共享库(如 libssl.so.3 和 libcrypto.so.3)而不是嵌入式版本?
➜ 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_EXTRA_CA_CERTS
环境变量:
`NODE_EXTRA_CA_CERTS=/path/to/custom-ca.crt node your-app.js`
This will just extend the list of trusted certificates you can use
in your app.
2 对 tls 方法进行猴子修补:
const tls = require('tls');
const fs = require('fs');
// Read your custom CA certificate
const customCa = fs.readFileSync('/path/to/custom-ca.crt');
// Store the original method
const originalCreateSecureContext = tls.createSecureContext;
// Override the method
tls.createSecureContext = function (options = {}) {
// Ensure 'ca' is an array and include the custom certificate
options.ca = options.ca || [];
if (!Array.isArray(options.ca)) {
options.ca = [options.ca];
}
options.ca.push(customCa);
// Call the original method with modified options
return originalCreateSecureContext.call(tls, options);
};
3 以编程方式将其注入 https lib
const https = require('https');
const fs = require('fs');
// Read your custom CA certificate
const customCa = fs.readFileSync('/path/to/custom-ca.crt');
// Create an HTTPS agent that includes your custom CA
const httpsAgent = new https.Agent({
ca: customCa,
});
// Use the custom agent in your HTTPS request
https.get('https://example.com', { agent: httpsAgent }, (res) => {
// Handle the response
res.on('data', (d) => {
process.stdout.write(d);
});
}).on('error', (e) => {
console.error(e);
});