Skip to content
Closed
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions packages/bun-usockets/src/crypto/openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,43 @@ static int us_ssl_ctx_use_privatekey_content(SSL_CTX *ctx, const char *content,
return ret;
}

/* node:tls `crl`: parse each X509 CRL block in `content` into `store` and
* enable CRL checking on it (Node's SecureContext::AddCRL). Returns the number
* of CRLs added, 0 when no CRL could be parsed or any block failed. */
static int add_crl_to_ctx_store(const char *content, X509_STORE *store) {
int count = 0;
X509_CRL *crl = NULL;
ERR_clear_error();
if (content == NULL) return 0;
BIO *in = BIO_new_mem_buf(content, strlen(content));
if (in == NULL) {
OPENSSL_PUT_ERROR(SSL, ERR_R_BUF_LIB);
return 0;
}
while ((crl = PEM_read_bio_X509_CRL(in, NULL, NULL, NULL))) {
int added = X509_STORE_add_crl(store, crl);
X509_CRL_free(crl);
if (!added) {
BIO_free(in);
return 0;
}
count++;
}
BIO_free(in);
if (count > 0) {
/* PEM_R_NO_START_LINE terminates the loop when the BIO is exhausted; any
* other error means a later block was malformed. */
unsigned long pem_err = ERR_peek_last_error();
if (pem_err != 0 && !(ERR_GET_LIB(pem_err) == ERR_LIB_PEM &&
ERR_GET_REASON(pem_err) == PEM_R_NO_START_LINE)) {
return 0;
}
X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
ERR_clear_error();
}
return count;
}

Comment thread
coderabbitai[bot] marked this conversation as resolved.
static int add_ca_cert_to_ctx_store(SSL_CTX *ctx, const char *content, X509_STORE *store) {
X509 *x = NULL;
ERR_clear_error();
Expand Down Expand Up @@ -1030,6 +1067,43 @@ SSL_CTX *us_ssl_ctx_build_raw(struct us_bun_socket_context_options_t options,
us_verify_callback);
}

if (options.crl && options.crl_count > 0) {
X509_STORE *store = SSL_CTX_get_cert_store(ssl_context);
/* Clone-on-write: a CRL must not be attached to the process-wide default
* root store (every other context would see it and start failing with
* UNABLE_TO_GET_CRL). Same check Node's SecureContext::AddCRL performs.
* A default context built without ca/requestCert still has the empty store
* from SSL_CTX_new(); give it its own default-roots copy so the CRL has a
* chain to check against and the per-socket client attach does not replace
* it with the shared store (which would drop the CRL flags). */
X509_STORE *shared = us_get_shared_default_ca_store();
int store_is_shared = store && store == shared;
X509_STORE_free(shared);
int store_is_empty = 0;
if (store && !store_is_shared) {
const STACK_OF(X509_OBJECT) *objs = X509_STORE_get0_objects(store);
store_is_empty = objs == NULL || sk_X509_OBJECT_num(objs) == 0;
}
if (store_is_shared || store_is_empty) {
X509_STORE *own = us_get_default_ca_store();
if (!own) {
ssl_ctx_build_fail(ssl_context);
return NULL;
}
SSL_CTX_set_cert_store(ssl_context, own);
store = own;
}
us_ex_idx_ensure();
SSL_CTX_set_ex_data(ssl_context, us_ctx_user_ca_ex_idx, (void *)1);
for (unsigned int i = 0; i < options.crl_count; i++) {
if (add_crl_to_ctx_store(options.crl[i], store) == 0) {
*err = CREATE_BUN_SOCKET_ERROR_INVALID_CRL;
ssl_ctx_build_fail(ssl_context);
return NULL;
}
}
}
Comment thread
robobun marked this conversation as resolved.

if (options.dh_params_file_name) {
DH *dh_2048 = NULL;
FILE *paramfile = fopen(options.dh_params_file_name, "r");
Expand Down
3 changes: 3 additions & 0 deletions packages/bun-usockets/src/libusockets.h
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,8 @@ struct us_bun_socket_context_options_t {
unsigned int cert_count;
const char * const *ca;
unsigned int ca_count;
const char * const *crl;
unsigned int crl_count;
unsigned int secure_options;
// Minimum/maximum TLS protocol version (TLS1_VERSION..TLS1_3_VERSION); 0 = unset/default.
int ssl_min_version;
Expand All @@ -441,6 +443,7 @@ enum create_bun_socket_error_t {
CREATE_BUN_SOCKET_ERROR_INVALID_CA_FILE,
CREATE_BUN_SOCKET_ERROR_INVALID_CA,
CREATE_BUN_SOCKET_ERROR_INVALID_CIPHERS,
CREATE_BUN_SOCKET_ERROR_INVALID_CRL,
};

/* Build an SSL_CTX from options. Returns the BoringSSL SSL_CTX*; caller owns
Expand Down
2 changes: 2 additions & 0 deletions packages/bun-uws/src/App.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ namespace uWS {
unsigned int cert_count = 0;
const char **ca = nullptr;
unsigned int ca_count = 0;
const char **crl = nullptr;
unsigned int crl_count = 0;
unsigned int secure_options = 0;
int ssl_min_version = 0;
int ssl_max_version = 0;
Expand Down
10 changes: 10 additions & 0 deletions src/http/ssl_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub struct SSLConfig {
pub key: CStrSlice,
pub cert: CStrSlice,
pub ca: CStrSlice,
pub crl: CStrSlice,

pub secure_options: u32,
/// Minimum/maximum TLS protocol version (TLS1_VERSION..TLS1_3_VERSION); 0 = unset/default.
Expand Down Expand Up @@ -109,6 +110,7 @@ impl SSLConfig {
key: None,
cert: None,
ca: None,
crl: None,
secure_options: 0,
ssl_min_version: 0,
ssl_max_version: 0,
Expand Down Expand Up @@ -203,6 +205,10 @@ impl SSLConfig {
ctx_opts.ca = ca.as_ptr();
ctx_opts.ca_count = ca.len() as u32;
}
if let Some(crl) = &self.crl {
ctx_opts.crl = crl.as_ptr();
ctx_opts.crl_count = crl.len() as u32;
}

if !self.ssl_ciphers.is_null() {
ctx_opts.ssl_ciphers = self.ssl_ciphers;
Expand Down Expand Up @@ -275,6 +281,7 @@ impl SSLConfig {
eq_slice!(key);
eq_slice!(cert);
eq_slice!(ca);
eq_slice!(crl);
if self.secure_options != other.secure_options {
return false;
}
Expand Down Expand Up @@ -347,6 +354,7 @@ impl SSLConfig {
hash_slice!(key);
hash_slice!(cert);
hash_slice!(ca);
hash_slice!(crl);
hasher.update(&self.secure_options.to_ne_bytes());
hasher.update(&self.ssl_min_version.to_ne_bytes());
hasher.update(&self.ssl_max_version.to_ne_bytes());
Expand Down Expand Up @@ -386,6 +394,7 @@ impl SSLConfig {
free_strings(&mut self.key);
free_strings(&mut self.cert);
free_strings(&mut self.ca);
free_strings(&mut self.crl);
free_string(&mut self.ssl_ciphers);
free_string(&mut self.protos);
}
Expand Down Expand Up @@ -438,6 +447,7 @@ impl Clone for SSLConfig {
key: clone_strings(&self.key),
cert: clone_strings(&self.cert),
ca: clone_strings(&self.ca),
crl: clone_strings(&self.crl),
secure_options: self.secure_options,
ssl_min_version: self.ssl_min_version,
ssl_max_version: self.ssl_max_version,
Expand Down
10 changes: 10 additions & 0 deletions src/js/node/tls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,8 @@ var InternalSecureContext = class SecureContext {
if (key) throwOnInvalidTLSArray("options.key", key);
const ca = options.ca;
if (ca) throwOnInvalidTLSArray("options.ca", ca);
const crl = options.crl;
if (crl) throwOnInvalidTLSArray("options.crl", crl);
if (options.servername != null && typeof options.servername !== "string")
throw new TypeError("servername argument must be an string");
if (options.secureOptions != null && typeof options.secureOptions !== "number")
Expand Down Expand Up @@ -1245,6 +1247,7 @@ function Server(options, secureConnectionListener): void {
this.key = undefined;
this.cert = undefined;
this.ca = undefined;
this.crl = undefined;
this.passphrase = undefined;
this.secureOptions = undefined;
this._rejectUnauthorized = rejectUnauthorizedDefault();
Expand Down Expand Up @@ -1361,6 +1364,12 @@ function Server(options, secureConnectionListener): void {
}
this.ca = ca;

let crl = options.crl;
if (crl) {
throwOnInvalidTLSArray("options.crl", crl);
}
this.crl = crl;

let passphrase = options.passphrase;
if (passphrase && typeof passphrase !== "string") {
throw $ERR_INVALID_ARG_TYPE("options.passphrase", "string", passphrase);
Expand Down Expand Up @@ -1438,6 +1447,7 @@ function Server(options, secureConnectionListener): void {
key: this.key,
cert: this.cert,
ca: this.ca,
crl: this.crl,
passphrase: this.passphrase,
secureOptions: this.secureOptions,
rejectUnauthorized: this._rejectUnauthorized,
Expand Down
5 changes: 4 additions & 1 deletion src/jsc/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ pub struct SSLConfig {
pub ca: SSLConfigFile,
pub cert: SSLConfigFile,
pub key: SSLConfigFile,
pub crl: SSLConfigFile,
pub key_file: GenOpt<GenString>,
pub cert_file: GenOpt<GenString>,
pub ca_file: GenOpt<GenString>,
Expand Down Expand Up @@ -405,7 +406,7 @@ impl Drop for SSLConfig {
release_gen_opt_string(&self.passphrase);
release_gen_opt_string(&self.dh_params_file);
release_gen_opt_string(&self.server_name);
// `ca` / `cert` / `key`: `SSLConfigFile` — released by its own `Drop`.
// `ca` / `cert` / `key` / `crl`: `SSLConfigFile` — released by its own `Drop`.
release_gen_opt_string(&self.key_file);
release_gen_opt_string(&self.cert_file);
release_gen_opt_string(&self.ca_file);
Expand Down Expand Up @@ -550,6 +551,7 @@ struct ExternSSLConfig {
ca: ExternSSLConfigFile,
cert: ExternSSLConfigFile,
key: ExternSSLConfigFile,
crl: ExternSSLConfigFile,
secure_options: u32,
ssl_min_version: i32,
ssl_max_version: i32,
Expand Down Expand Up @@ -584,6 +586,7 @@ impl SSLConfig {
ca: SSLConfigFile::convert_from_extern(ext.ca),
cert: SSLConfigFile::convert_from_extern(ext.cert),
key: SSLConfigFile::convert_from_extern(ext.key),
crl: SSLConfigFile::convert_from_extern(ext.crl),
secure_options: ext.secure_options,
ssl_min_version: ext.ssl_min_version,
ssl_max_version: ext.ssl_max_version,
Expand Down
1 change: 1 addition & 0 deletions src/runtime/socket/SSLConfig.bindv2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const SSLConfig = b.dictionary(
ca: SSLConfigFile,
cert: SSLConfigFile,
key: SSLConfigFile,
crl: SSLConfigFile,
secureOptions: {
type: b.u32,
default: 0,
Expand Down
4 changes: 3 additions & 1 deletion src/runtime/socket/SSLConfig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,12 @@ impl SSLConfigFromJs for SSLConfig {
result.ca = handle_file_for_field(global, "ca", &generated.ca)?;
result.cert = handle_file_for_field(global, "cert", &generated.cert)?;
result.key = handle_file_for_field(global, "key", &generated.key)?;
result.crl = handle_file_for_field(global, "crl", &generated.crl)?;
result.requires_custom_request_ctx = result.requires_custom_request_ctx
|| result.ca.is_some()
|| result.cert.is_some()
|| result.key.is_some();
|| result.key.is_some()
|| result.crl.is_some();

if let Some(key_file) = generated.key_file.get() {
result.key_file_name = handle_path(global, "keyFile", &key_file)?;
Expand Down
6 changes: 6 additions & 0 deletions src/runtime/socket/uws_jsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ pub fn create_bun_socket_error_to_js(
format_args!("Invalid ciphers"),
)
.to_js(),
create_bun_socket_error_t::invalid_crl => global_object
.err(
bun_jsc::ErrorCode::ERR_CRYPTO_OPERATION_FAILED,
format_args!("Failed to parse CRL"),
)
.to_js(),
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/sql_jsc/jsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ pub(crate) fn create_bun_socket_error_to_js(
E::invalid_ciphers => global
.err(ErrorCode::BORINGSSL, format_args!("Invalid ciphers"))
.to_js(),
E::invalid_crl => global
.err(
ErrorCode::ERR_CRYPTO_OPERATION_FAILED,
format_args!("Failed to parse CRL"),
)
.to_js(),
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/uws_sys/SocketContext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ pub struct BunSocketContextOptions {
pub cert_count: u32,
pub ca: *const *const c_char,
pub ca_count: u32,
pub crl: *const *const c_char,
pub crl_count: u32,
pub secure_options: u32,
pub ssl_min_version: i32,
pub ssl_max_version: i32,
Expand All @@ -136,6 +138,8 @@ impl Default for BunSocketContextOptions {
cert_count: 0,
ca: ptr::null(),
ca_count: 0,
crl: ptr::null(),
crl_count: 0,
secure_options: 0,
ssl_min_version: 0,
ssl_max_version: 0,
Expand Down Expand Up @@ -236,6 +240,7 @@ impl BunSocketContextOptions {
feed_arr(&mut h, self.key, self.key_count);
feed_arr(&mut h, self.cert, self.cert_count);
feed_arr(&mut h, self.ca, self.ca_count);
feed_arr(&mut h, self.crl, self.crl_count);
h.update(bun_core::bytes_of(&self.secure_options));
h.update(bun_core::bytes_of(&self.ssl_min_version));
h.update(bun_core::bytes_of(&self.ssl_max_version));
Expand Down Expand Up @@ -268,6 +273,7 @@ impl BunSocketContextOptions {
sum(self.key, self.key_count, &mut n);
sum(self.cert, self.cert_count, &mut n);
sum(self.ca, self.ca_count, &mut n);
sum(self.crl, self.crl_count, &mut n);
n
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/uws_sys/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ pub enum create_bun_socket_error_t {
invalid_ca_file,
invalid_ca,
invalid_ciphers,
invalid_crl,
}

impl create_bun_socket_error_t {
Expand All @@ -118,6 +119,7 @@ impl create_bun_socket_error_t {
Self::invalid_ca_file => Some(b"Invalid CA file"),
Self::invalid_ca => Some(b"Invalid CA"),
Self::invalid_ciphers => Some(b"Invalid ciphers"),
Self::invalid_crl => Some(b"Failed to parse CRL"),
}
}
}
Expand Down
Loading
Loading