Compare commits

...

2 Commits

Author SHA1 Message Date
pfgithub
58baddf875 Sync Node.js tests with upstream 2025-05-30 00:57:24 +00:00
pfg
c724b2d8bd feat(tls): implement secureProtocol support 2025-05-29 17:56:13 -07:00
9 changed files with 157 additions and 5 deletions

View File

@@ -770,8 +770,15 @@ create_ssl_context_from_options(struct us_socket_context_options_t options) {
* buffer allocated in a different address */
SSL_CTX_set_mode(ssl_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
/* Anything below TLS 1.2 is disabled */
SSL_CTX_set_min_proto_version(ssl_context, TLS1_2_VERSION);
if (options.min_version) {
SSL_CTX_set_min_proto_version(ssl_context, options.min_version);
} else {
/* Anything below TLS 1.2 is disabled */
SSL_CTX_set_min_proto_version(ssl_context, TLS1_2_VERSION);
}
if (options.max_version) {
SSL_CTX_set_max_proto_version(ssl_context, options.max_version);
}
/* The following are helpers. You may easily implement whatever you want by
* using the native handle directly */
@@ -1146,8 +1153,14 @@ SSL_CTX *create_ssl_context_from_bun_options(
* buffer allocated in a different address */
SSL_CTX_set_mode(ssl_context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
/* Anything below TLS 1.2 is disabled */
SSL_CTX_set_min_proto_version(ssl_context, TLS1_2_VERSION);
if (options.min_version) {
SSL_CTX_set_min_proto_version(ssl_context, options.min_version);
} else {
SSL_CTX_set_min_proto_version(ssl_context, TLS1_2_VERSION);
}
if (options.max_version) {
SSL_CTX_set_max_proto_version(ssl_context, options.max_version);
}
/* The following are helpers. You may easily implement whatever you want by
* using the native handle directly */

View File

@@ -233,6 +233,8 @@ struct us_bun_socket_context_options_t {
const char **ca;
unsigned int ca_count;
unsigned int secure_options;
unsigned int min_version;
unsigned int max_version;
int reject_unauthorized;
int request_cert;
unsigned int client_renegotiation_limit;

View File

@@ -74,6 +74,8 @@ namespace uWS {
const char **ca = nullptr;
unsigned int ca_count = 0;
unsigned int secure_options = 0;
unsigned int min_version = 0;
unsigned int max_version = 0;
int reject_unauthorized = 0;
int request_cert = 0;
unsigned int client_renegotiation_limit = 3;

View File

@@ -41,6 +41,14 @@ const MimeType = HTTP.MimeType;
const Blob = JSC.WebCore.Blob;
const BoringSSL = bun.BoringSSL.c;
const Arena = @import("../../allocators/mimalloc_arena.zig").Arena;
fn parseTLSVersion(str: []const u8) ?u16 {
if (strings.eqlComptime(str, "TLSv1")) return BoringSSL.TLS1_VERSION;
if (strings.eqlComptime(str, "TLSv1.1")) return BoringSSL.TLS1_1_VERSION;
if (strings.eqlComptime(str, "TLSv1.2")) return BoringSSL.TLS1_2_VERSION;
if (strings.eqlComptime(str, "TLSv1.3")) return BoringSSL.TLS1_3_VERSION;
return null;
}
const SendfileContext = struct {
fd: bun.FileDescriptor,
socket_fd: bun.FileDescriptor = bun.invalid_fd,
@@ -661,6 +669,8 @@ pub const ServerConfig = struct {
ca_count: u32 = 0,
secure_options: u32 = 0,
min_version: u16 = 0,
max_version: u16 = 0,
request_cert: i32 = 0,
reject_unauthorized: i32 = 0,
ssl_ciphers: ?[*:0]const u8 = null,
@@ -704,6 +714,8 @@ pub const ServerConfig = struct {
}
ctx_opts.request_cert = this.request_cert;
ctx_opts.reject_unauthorized = this.reject_unauthorized;
ctx_opts.min_version = this.min_version;
ctx_opts.max_version = this.max_version;
return ctx_opts;
}
@@ -1177,6 +1189,22 @@ pub const ServerConfig = struct {
}
}
if (try obj.getStringish(global, "minVersion")) |minv| {
var slice = try minv.toSlice(global, bun.default_allocator);
defer slice.deinit();
if (parseTLSVersion(slice.slice())) |ver| {
result.min_version = ver;
}
}
if (try obj.getStringish(global, "maxVersion")) |maxv| {
var slice = try maxv.toSlice(global, bun.default_allocator);
defer slice.deinit();
if (parseTLSVersion(slice.slice())) |ver| {
result.max_version = ver;
}
}
if (try obj.getTruthy(global, "clientRenegotiationLimit")) |client_renegotiation_limit| {
if (client_renegotiation_limit.isNumber()) {
result.client_renegotiation_limit = client_renegotiation_limit.toU32();

View File

@@ -2554,6 +2554,8 @@ pub const us_bun_socket_context_options_t = extern struct {
ca: ?[*]?[*:0]const u8 = null,
ca_count: u32 = 0,
secure_options: u32 = 0,
min_version: u32 = 0,
max_version: u32 = 0,
reject_unauthorized: i32 = 0,
request_cert: i32 = 0,
client_renegotiation_limit: u32 = 3,

View File

@@ -690,6 +690,8 @@ fn NewHTTPContext(comptime ssl: bool) type {
.ca_count = @intCast(init_opts.ca.len),
.ca_file_name = if (init_opts.abs_ca_file_name.len > 0) init_opts.abs_ca_file_name else null,
.request_cert = 1,
.min_version = 0,
.max_version = 0,
};
try this.initWithOpts(&opts);
@@ -702,6 +704,8 @@ fn NewHTTPContext(comptime ssl: bool) type {
.request_cert = 1,
// we manually abort the connection if the hostname doesn't match
.reject_unauthorized = 0,
.min_version = 0,
.max_version = 0,
};
var err: uws.create_bun_socket_error_t = .none;
this.us_socket_context = uws.us_create_bun_ssl_socket_context(http_thread.loop.loop, @sizeOf(usize), opts, &err).?;

View File

@@ -202,6 +202,8 @@ var InternalSecureContext = class SecureContext {
passphrase;
servername;
secureOptions;
minVersion;
maxVersion;
constructor(options) {
const context = {};
@@ -244,6 +246,9 @@ var InternalSecureContext = class SecureContext {
this.secureOptions = secureOptions;
if (options.minVersion !== undefined) this.minVersion = options.minVersion;
if (options.maxVersion !== undefined) this.maxVersion = options.maxVersion;
if (!$isUndefinedOrNull(options.privateKeyIdentifier)) {
if ($isUndefinedOrNull(options.privateKeyEngine)) {
// prettier-ignore
@@ -311,6 +316,8 @@ function TLSSocket(socket?, options?) {
options = isNetSocketOrDuplex ? { ...options, allowHalfOpen: false } : options || socket || {};
applySecureProtocol(options);
NetSocket.$call(this, options);
this.ciphers = options.ciphers;
@@ -508,6 +515,8 @@ function Server(options, secureConnectionListener): void {
this.ca = undefined;
this.passphrase = undefined;
this.secureOptions = undefined;
this.minVersion = undefined;
this.maxVersion = undefined;
this._rejectUnauthorized = rejectUnauthorizedDefault;
this._requestCert = undefined;
this.servername = undefined;
@@ -535,6 +544,7 @@ function Server(options, secureConnectionListener): void {
options = options.context;
}
if (options) {
applySecureProtocol(options);
const { ALPNProtocols } = options;
if (ALPNProtocols) {
@@ -577,6 +587,9 @@ function Server(options, secureConnectionListener): void {
}
this.secureOptions = secureOptions;
if (options.minVersion !== undefined) this.minVersion = options.minVersion;
if (options.maxVersion !== undefined) this.maxVersion = options.maxVersion;
const requestCert = options.requestCert || false;
if (requestCert) this._requestCert = requestCert;
@@ -617,6 +630,8 @@ function Server(options, secureConnectionListener): void {
ca: this.ca,
passphrase: this.passphrase,
secureOptions: this.secureOptions,
minVersion: this.minVersion,
maxVersion: this.maxVersion,
rejectUnauthorized: this._rejectUnauthorized,
requestCert: isClient ? true : this._requestCert,
ALPNProtocols: this.ALPNProtocols,
@@ -640,6 +655,32 @@ const DEFAULT_ECDH_CURVE = "auto",
DEFAULT_MIN_VERSION = "TLSv1.2",
DEFAULT_MAX_VERSION = "TLSv1.3";
function applySecureProtocol(opts) {
const proto = opts && opts.secureProtocol;
if (typeof proto !== "string") return;
switch (proto) {
case "TLSv1_method":
opts.minVersion = "TLSv1";
opts.maxVersion = "TLSv1";
break;
case "TLSv1_1_method":
opts.minVersion = "TLSv1.1";
opts.maxVersion = "TLSv1.1";
break;
case "TLSv1_2_method":
opts.minVersion = "TLSv1.2";
opts.maxVersion = "TLSv1.2";
break;
case "TLSv1_3_method":
opts.minVersion = "TLSv1.3";
opts.maxVersion = "TLSv1.3";
break;
default:
// TLS_method or unknown methods fall back to defaults
break;
}
}
function normalizeConnectArgs(listArgs) {
const args = net._normalizeArgs(listArgs);
$assert($isObject(args[0]));

View File

@@ -543,7 +543,7 @@ pub const JSValkeyClient = struct {
vm.rareData().valkey_context.tls orelse brk_ctx: {
// TLS socket, default config
var err: uws.create_bun_socket_error_t = .none;
const ctx_ = uws.us_create_bun_ssl_socket_context(vm.uwsLoop(), @sizeOf(*JSValkeyClient), uws.us_bun_socket_context_options_t{}, &err).?;
const ctx_ = uws.us_create_bun_ssl_socket_context(vm.uwsLoop(), @sizeOf(*JSValkeyClient), uws.us_bun_socket_context_options_t{ .min_version = 0, .max_version = 0 }, &err).?;
uws.NewSocketHandler(true).configure(ctx_, true, *JSValkeyClient, SocketHandler(true));
vm.rareData().valkey_context.tls = ctx_;
break :brk_ctx ctx_;

View File

@@ -0,0 +1,60 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto)
common.skip('missing crypto');
const { hasOpenSSL } = require('../common/crypto');
// This test ensures that `getProtocol` returns the right protocol
// from a TLS connection
const assert = require('assert');
const tls = require('tls');
const fixtures = require('../common/fixtures');
const clientConfigs = [
{
secureProtocol: 'TLSv1_method',
version: 'TLSv1',
ciphers: (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT')
}, {
secureProtocol: 'TLSv1_1_method',
version: 'TLSv1.1',
ciphers: (hasOpenSSL(3, 1) ? 'DEFAULT:@SECLEVEL=0' : 'DEFAULT')
}, {
secureProtocol: 'TLSv1_2_method',
version: 'TLSv1.2'
},
];
const serverConfig = {
secureProtocol: 'TLS_method',
key: fixtures.readKey('agent2-key.pem'),
cert: fixtures.readKey('agent2-cert.pem')
};
if (!process.features.openssl_is_boringssl) {
serverConfig.ciphers = 'RSA@SECLEVEL=0';
}
const server = tls.createServer(serverConfig, common.mustCall(clientConfigs.length))
.listen(0, common.localhostIPv4, function() {
let connected = 0;
for (const v of clientConfigs) {
tls.connect({
host: common.localhostIPv4,
port: server.address().port,
ciphers: v.ciphers,
rejectUnauthorized: false,
secureProtocol: v.secureProtocol
}, common.mustCall(function() {
assert.strictEqual(this.getProtocol(), v.version);
this.on('end', common.mustCall());
this.on('close', common.mustCall(function() {
assert.strictEqual(this.getProtocol(), null);
})).end();
if (++connected === clientConfigs.length)
server.close();
}));
}
});