Compare commits

...

1 Commits

Author SHA1 Message Date
pfg
de2747b408 Implement allowPartialTrustChain 2025-06-20 16:32:52 -07:00
7 changed files with 76 additions and 0 deletions

View File

@@ -36,6 +36,7 @@ void *sni_find(void *sni, const char *hostname);
#include <openssl/dh.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <openssl/x509_vfy.h>
#elif LIBUS_USE_WOLFSSL
#include <wolfssl/openssl/bio.h>
#include <wolfssl/openssl/dh.h>
@@ -1320,6 +1321,12 @@ SSL_CTX *create_ssl_context_from_bun_options(
SSL_CTX_set_options(ssl_context, options.secure_options);
}
// After setting up the cert store (after all SSL_CTX_set_cert_store calls):
if (options.allow_partial_trust_chain) {
X509_STORE *cert_store = SSL_CTX_get_cert_store(ssl_context);
X509_STORE_set_flags(cert_store, X509_V_FLAG_PARTIAL_CHAIN);
}
/* This must be free'd with free_ssl_context, not SSL_CTX_free */
return ssl_context;
}

View File

@@ -237,6 +237,7 @@ struct us_bun_socket_context_options_t {
int request_cert;
unsigned int client_renegotiation_limit;
unsigned int client_renegotiation_window;
int allow_partial_trust_chain;
};
/* Return 15-bit timestamp for this context */

View File

@@ -78,6 +78,7 @@ namespace uWS {
int request_cert = 0;
unsigned int client_renegotiation_limit = 3;
unsigned int client_renegotiation_window = 600;
int allow_partial_trust_chain = 0;
/* Conversion operator used internally */
operator struct us_bun_socket_context_options_t() const {

View File

@@ -29,6 +29,7 @@ protos: ?[*:0]const u8 = null,
protos_len: usize = 0,
client_renegotiation_limit: u32 = 0,
client_renegotiation_window: u32 = 0,
allow_partial_trust_chain: i32 = 0,
const BlobFileContentResult = struct {
data: [:0]const u8,
@@ -91,6 +92,7 @@ pub fn asUSockets(this: SSLConfig) uws.SocketContext.BunSocketContextOptions {
}
ctx_opts.request_cert = this.request_cert;
ctx_opts.reject_unauthorized = this.reject_unauthorized;
ctx_opts.allow_partial_trust_chain = this.allow_partial_trust_chain;
return ctx_opts;
}
@@ -447,6 +449,11 @@ pub fn fromJS(vm: *JSC.VirtualMachine, global: *JSC.JSGlobalObject, obj: JSC.JSV
any = true;
}
if (try obj.getBooleanStrict(global, "allowPartialTrustChain")) |allow_partial_trust_chain| {
result.allow_partial_trust_chain = @intFromBool(allow_partial_trust_chain);
any = true;
}
if (try obj.getTruthy(global, "ciphers")) |ssl_ciphers| {
var sliced = try ssl_ciphers.toSlice(global, bun.default_allocator);
defer sliced.deinit();

View File

@@ -240,6 +240,7 @@ pub const SocketContext = opaque {
request_cert: i32 = 0,
client_renegotiation_limit: u32 = 3,
client_renegotiation_window: u32 = 600,
allow_partial_trust_chain: i32 = 0,
pub fn createSSLContext(options: BunSocketContextOptions, err: *uws.create_bun_socket_error_t) ?*BoringSSL.SSL_CTX {
return c.create_ssl_context_from_bun_options(options, err);

View File

@@ -208,6 +208,7 @@ var InternalSecureContext = class SecureContext {
passphrase;
servername;
secureOptions;
allowPartialTrustChain;
constructor(options) {
const context = {};
@@ -250,6 +251,10 @@ var InternalSecureContext = class SecureContext {
this.secureOptions = secureOptions;
if (options.allowPartialTrustChain !== undefined) {
this.allowPartialTrustChain = !!options.allowPartialTrustChain;
}
if (!$isUndefinedOrNull(options.privateKeyIdentifier)) {
if ($isUndefinedOrNull(options.privateKeyEngine)) {
// prettier-ignore
@@ -495,6 +500,7 @@ TLSSocket.prototype[buntls] = function (port, host) {
rejectUnauthorized: this._rejectUnauthorized,
requestCert: this._requestCert,
ciphers: this.ciphers,
allowPartialTrustChain: this[ksecureContext]?.allowPartialTrustChain,
...this[ksecureContext],
};
};

View File

@@ -0,0 +1,53 @@
'use strict';
const common = require('../common');
if (!common.hasCrypto) { common.skip('missing crypto'); };
const assert = require('assert');
const { once } = require('events');
const fixtures = require('../common/fixtures');
// agent6-cert.pem is signed by intermediate cert of ca3.
// The server has a cert chain of agent6->ca3->ca1(root).
const { it, beforeEach, afterEach, describe } = require('node:test');
describe('allowPartialTrustChain', { skip: !common.hasCrypto }, function() {
const tls = require('tls');
let server;
let client;
let opts;
beforeEach(async function() {
server = tls.createServer({
ca: fixtures.readKey('ca3-cert.pem'),
key: fixtures.readKey('agent6-key.pem'),
cert: fixtures.readKey('agent6-cert.pem'),
}, (socket) => socket.resume());
server.listen(0);
await once(server, 'listening');
opts = {
port: server.address().port,
ca: fixtures.readKey('ca3-cert.pem'),
checkServerIdentity() {}
};
});
afterEach(async function() {
client?.destroy();
server?.close();
});
it('can connect successfully with allowPartialTrustChain: true', async function() {
client = tls.connect({ ...opts, allowPartialTrustChain: true });
await once(client, 'secureConnect'); // Should not throw
});
it('fails without with allowPartialTrustChain: true for an intermediate cert in the CA', async function() {
// Consistency check: Connecting fails without allowPartialTrustChain: true
await assert.rejects(async () => {
const client = tls.connect(opts);
await once(client, 'secureConnect');
}, { code: 'UNABLE_TO_GET_ISSUER_CERT' });
});
});