Compare commits

...

5 Commits

Author SHA1 Message Date
autofix-ci[bot]
8712fce75f [autofix.ci] apply automated fixes 2025-07-15 23:05:44 +00:00
Claude Bot
05b5fce694 Fix CA loading order to match Node.js behavior (additive, not replacement)
The system CA implementation now correctly follows Node.js certificate
loading order instead of replacing bundled certificates:

Node.js loading order:
1. Default root certs (bundled Mozilla certificates)
2. System certs (when --use-system-ca is enabled)
3. Extra certs (NODE_EXTRA_CA_CERTS)

Key changes:
- Always load bundled root certificates first
- Add system CAs as additional certificates when --use-system-ca is set
- Keep NODE_EXTRA_CA_CERTS loading last
- System CAs are now additive, not replacement
- Updated tests to reflect additive behavior

This matches PR #21092 behavior where system CAs supplement rather than
replace the bundled certificate store, providing both comprehensive
coverage and system-specific trust settings.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 23:01:08 +00:00
Claude Bot
4777992fe7 Remove BUN_USE_SYSTEM_CA environment variable support
Make --use-system-ca CLI flag the only way to enable system CA loading.
This simplifies the implementation and removes potential confusion
between environment variables and CLI flags.

Changes:
- Remove environment variable fallback from Bun__useSystemCA()
- Update tests to only test CLI flag functionality
- Add tests for default behavior and flag position independence
- Update comments to clarify CLI flag only support

The implementation now only respects the --use-system-ca CLI flag,
making the behavior more predictable and consistent with other
Bun CLI features.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 22:52:43 +00:00
Claude Bot
8b950ca95a Update macOS system CA implementation to match PR #21092 with dynamic loading
This comprehensive update adapts the implementation from PR #21092 to use
dynamic loading with bun.sys.dlopen instead of static linking.

Key changes:
- Complete Security framework function coverage with dynamic loading
- Comprehensive trust evaluation logic matching PR #21092
- CLI flag support: --use-system-ca (primary) + env var fallback
- Advanced certificate validation using SecTrustEvaluateWithError
- Trust settings evaluation across user and admin domains
- SSL policy-specific certificate filtering
- Proper SecItemCopyMatching implementation for keychain access
- Enhanced error handling and warning system

Architecture:
- Zig: Dynamic framework loading + function pointer management
- C++: Trust evaluation logic + certificate processing
- CLI: --use-system-ca flag with precedence over env vars
- Backward compatibility with BUN_USE_SYSTEM_CA environment variable

Benefits over static linking:
- No build system changes required
- Runtime-only dependency on Security framework
- Follows existing Bun patterns (fs_events.zig)
- Graceful degradation if frameworks unavailable

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 22:50:05 +00:00
Claude Bot
448b9c7ff2 Add macOS system CA loading support
This implementation uses Zig's bun.sys.dlopen to dynamically load
the macOS Security framework and CoreFoundation functions, then
implements the CA loading logic in C++ for better integration
with the existing OpenSSL certificate store.

Key features:
- Uses BUN_USE_SYSTEM_CA environment variable to enable feature
- Dynamically loads Security.framework without static linking
- Integrates system CAs with existing bundled and extra CAs
- Follows existing Bun patterns for framework loading
- Platform-specific code only runs on macOS

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-15 22:19:27 +00:00
9 changed files with 670 additions and 6 deletions

View File

@@ -236,6 +236,7 @@ src/bun.js/ResolveMessage.zig
src/bun.js/RuntimeTranspilerCache.zig
src/bun.js/SavedSourceMap.zig
src/bun.js/Strong.zig
src/bun.js/system_ca.zig
src/bun.js/test/diff_format.zig
src/bun.js/test/expect.zig
src/bun.js/test/jest.zig

View File

@@ -8,6 +8,97 @@
static const int root_certs_size = sizeof(root_certs) / sizeof(root_certs[0]);
extern "C" void BUN__warn__extra_ca_load_failed(const char* filename, const char* error_msg);
extern "C" void BUN__warn__system_ca_load_failed(const char* error_msg);
// macOS Security framework types and constants
#ifdef __APPLE__
typedef long CFIndex;
typedef unsigned char Boolean;
typedef int OSStatus;
typedef void* CFTypeRef;
typedef void* CFArrayRef;
typedef void* CFDataRef;
typedef void* CFStringRef;
typedef void* CFDictionaryRef;
typedef void* CFErrorRef;
typedef void* CFAllocatorRef;
typedef void* SecCertificateRef;
typedef void* SecTrustRef;
typedef void* SecPolicyRef;
// Trust settings domains
enum {
kSecTrustSettingsDomainUser = 0,
kSecTrustSettingsDomainAdmin = 1,
kSecTrustSettingsDomainSystem = 2
};
// Trust results
enum {
kSecTrustSettingsResultInvalid = 0,
kSecTrustSettingsResultTrustRoot = 1,
kSecTrustSettingsResultTrustAsRoot = 2,
kSecTrustSettingsResultDeny = 3,
kSecTrustSettingsResultUnspecified = 4
};
// CFNumber types
enum {
kCFNumberSInt32Type = 3
};
// CFString encoding
enum {
kCFStringEncodingUTF8 = 0x08000100
};
// Function pointers structure - matches Zig MacOSCAFunctions
struct MacOSCAFunctions {
// Security framework functions
OSStatus (*SecTrustCopyAnchorCertificates)(CFArrayRef* anchor_certs);
CFDataRef (*SecCertificateCopyData)(SecCertificateRef cert_ref);
OSStatus (*SecItemCopyMatching)(CFDictionaryRef query, CFTypeRef* result);
OSStatus (*SecTrustSettingsCopyTrustSettings)(SecCertificateRef cert_ref, int domain);
SecPolicyRef (*SecPolicyCreateSSL)(Boolean server, CFStringRef hostname);
OSStatus (*SecTrustCreateWithCertificates)(CFTypeRef certificates, CFTypeRef policies, SecTrustRef* trust);
Boolean (*SecTrustEvaluateWithError)(SecTrustRef trust, CFErrorRef* error);
CFDictionaryRef (*SecPolicyCopyProperties)(SecPolicyRef policy);
// CoreFoundation functions
CFIndex (*CFArrayGetCount)(CFArrayRef array);
CFTypeRef (*CFArrayGetValueAtIndex)(CFArrayRef array, CFIndex index);
const unsigned char* (*CFDataGetBytePtr)(CFDataRef data);
CFIndex (*CFDataGetLength)(CFDataRef data);
void (*CFRelease)(CFTypeRef ref);
CFDictionaryRef (*CFDictionaryCreate)(CFAllocatorRef allocator, CFTypeRef* keys, CFTypeRef* values, CFIndex num_values, void* key_callbacks, void* value_callbacks);
CFStringRef (*CFStringCreateWithCString)(CFAllocatorRef allocator, const char* c_str, unsigned int encoding);
CFArrayRef (*CFArrayCreate)(CFAllocatorRef allocator, CFTypeRef* values, CFIndex num_values, void* callbacks);
CFTypeRef (*CFDictionaryGetValue)(CFDictionaryRef dictionary, CFTypeRef key);
unsigned long (*CFGetTypeID)(CFTypeRef ref);
unsigned long (*CFStringGetTypeID)(void);
unsigned long (*CFNumberGetTypeID)(void);
Boolean (*CFStringGetCString)(CFStringRef str, char* buffer, CFIndex buffer_size, unsigned int encoding);
Boolean (*CFNumberGetValue)(CFTypeRef number, int type, void* value_ptr);
// Constants
CFStringRef* kSecClass;
CFStringRef* kSecClassCertificate;
CFStringRef* kSecMatchLimit;
CFStringRef* kSecMatchLimitAll;
CFStringRef* kSecReturnRef;
CFStringRef* kCFBooleanTrue;
CFStringRef* kSecTrustSettingsResult;
CFStringRef* kSecTrustSettingsPolicy;
CFStringRef* kSecTrustSettingsPolicyString;
CFStringRef* kSecTrustSettingsApplication;
CFStringRef* kSecPolicyOid;
CFStringRef* kSecPolicyAppleSSL;
};
extern "C" MacOSCAFunctions* Bun__getMacOSCAFunctions();
#endif
extern "C" int Bun__useSystemCA();
// This callback is used to avoid the default passphrase callback in OpenSSL
// which will typically prompt for the passphrase. The prompting is designed
@@ -40,6 +131,256 @@ us_ssl_ctx_get_X509_without_callback_from(struct us_cert_string_t content) {
return x;
}
#ifdef __APPLE__
// Helper function to check if trust settings indicate the certificate is trusted for SSL policy
static bool us_internal_is_trust_settings_trusted_for_policy(MacOSCAFunctions* ca_funcs, CFArrayRef trust_settings, SecPolicyRef ssl_policy) {
if (!trust_settings || !ca_funcs) {
// Empty trust settings means "use system defaults"
return true;
}
CFIndex count = ca_funcs->CFArrayGetCount(trust_settings);
if (count == 0) {
// Empty trust settings array means "use system defaults"
return true;
}
CFDictionaryRef ssl_policy_properties = ca_funcs->SecPolicyCopyProperties(ssl_policy);
CFStringRef ssl_policy_oid = NULL;
if (ssl_policy_properties && ca_funcs->kSecPolicyOid && *(ca_funcs->kSecPolicyOid)) {
ssl_policy_oid = (CFStringRef)ca_funcs->CFDictionaryGetValue(ssl_policy_properties, *(ca_funcs->kSecPolicyOid));
}
bool is_trusted = true; // Default to trusted
for (CFIndex i = 0; i < count; i++) {
CFDictionaryRef trust_dict = (CFDictionaryRef)ca_funcs->CFArrayGetValueAtIndex(trust_settings, i);
if (!trust_dict) continue;
// Check if this trust setting applies to SSL policy
if (ca_funcs->kSecTrustSettingsPolicy && *(ca_funcs->kSecTrustSettingsPolicy)) {
CFTypeRef policy = ca_funcs->CFDictionaryGetValue(trust_dict, *(ca_funcs->kSecTrustSettingsPolicy));
if (policy) {
// If policy is specified, check if it matches SSL
CFStringRef policy_string = NULL;
if (ca_funcs->kSecTrustSettingsPolicyString && *(ca_funcs->kSecTrustSettingsPolicyString)) {
policy_string = (CFStringRef)ca_funcs->CFDictionaryGetValue(trust_dict, *(ca_funcs->kSecTrustSettingsPolicyString));
}
bool policy_matches = false;
if (policy_string && ssl_policy_oid) {
char policy_str[256];
char ssl_oid_str[256];
if (ca_funcs->CFStringGetCString(policy_string, policy_str, sizeof(policy_str), kCFStringEncodingUTF8) &&
ca_funcs->CFStringGetCString(ssl_policy_oid, ssl_oid_str, sizeof(ssl_oid_str), kCFStringEncodingUTF8)) {
policy_matches = (strcmp(policy_str, ssl_oid_str) == 0);
}
}
if (!policy_matches) {
continue; // This trust setting doesn't apply to SSL
}
}
}
// Check if application is specified
if (ca_funcs->kSecTrustSettingsApplication && *(ca_funcs->kSecTrustSettingsApplication)) {
CFTypeRef application = ca_funcs->CFDictionaryGetValue(trust_dict, *(ca_funcs->kSecTrustSettingsApplication));
if (application) {
// If application is specified, this trust setting only applies to that app
continue;
}
}
// Get the trust result
if (ca_funcs->kSecTrustSettingsResult && *(ca_funcs->kSecTrustSettingsResult)) {
CFTypeRef result_ref = ca_funcs->CFDictionaryGetValue(trust_dict, *(ca_funcs->kSecTrustSettingsResult));
if (result_ref) {
if (ca_funcs->CFGetTypeID(result_ref) == ca_funcs->CFNumberGetTypeID()) {
int result_value;
if (ca_funcs->CFNumberGetValue(result_ref, kCFNumberSInt32Type, &result_value)) {
switch (result_value) {
case kSecTrustSettingsResultDeny:
is_trusted = false;
break;
case kSecTrustSettingsResultTrustRoot:
case kSecTrustSettingsResultTrustAsRoot:
is_trusted = true;
break;
case kSecTrustSettingsResultUnspecified:
default:
// Use system default
break;
}
}
}
}
}
}
if (ssl_policy_properties) {
ca_funcs->CFRelease(ssl_policy_properties);
}
return is_trusted;
}
// Check if a certificate is trusted for the SSL policy
static bool us_internal_is_certificate_trusted_for_policy(MacOSCAFunctions* ca_funcs, SecCertificateRef cert) {
if (!ca_funcs || !cert) {
return false;
}
// Create SSL policy
SecPolicyRef ssl_policy = ca_funcs->SecPolicyCreateSSL(false, NULL); // Client SSL policy
if (!ssl_policy) {
return false;
}
bool is_trusted = false;
// Check trust settings in user and admin domains
for (int domain : {kSecTrustSettingsDomainUser, kSecTrustSettingsDomainAdmin}) {
CFArrayRef trust_settings = NULL;
OSStatus status = ca_funcs->SecTrustSettingsCopyTrustSettings(cert, domain);
if (status == 0 && trust_settings) {
if (us_internal_is_trust_settings_trusted_for_policy(ca_funcs, trust_settings, ssl_policy)) {
is_trusted = true;
ca_funcs->CFRelease(trust_settings);
break;
}
ca_funcs->CFRelease(trust_settings);
} else if (status == -25263) { // errSecItemNotFound
// No explicit trust settings - use system default evaluation
CFArrayRef cert_array = ca_funcs->CFArrayCreate(NULL, (CFTypeRef*)&cert, 1, NULL);
if (cert_array) {
SecTrustRef trust = NULL;
CFArrayRef policies = ca_funcs->CFArrayCreate(NULL, (CFTypeRef*)&ssl_policy, 1, NULL);
if (policies) {
if (ca_funcs->SecTrustCreateWithCertificates(cert_array, policies, &trust) == 0) {
CFErrorRef error = NULL;
if (ca_funcs->SecTrustEvaluateWithError(trust, &error)) {
is_trusted = true;
}
if (error) {
ca_funcs->CFRelease(error);
}
ca_funcs->CFRelease(trust);
}
ca_funcs->CFRelease(policies);
}
ca_funcs->CFRelease(cert_array);
}
break;
}
}
ca_funcs->CFRelease(ssl_policy);
return is_trusted;
}
// Load system certificates from macOS keychain using comprehensive trust evaluation
static STACK_OF(X509) *us_internal_init_system_certs_from_macos_keychain() {
MacOSCAFunctions* ca_funcs = Bun__getMacOSCAFunctions();
if (!ca_funcs) {
BUN__warn__system_ca_load_failed("Could not load macOS Security framework");
return NULL;
}
// Verify we have all required constants
if (!ca_funcs->kSecClass || !*(ca_funcs->kSecClass) ||
!ca_funcs->kSecClassCertificate || !*(ca_funcs->kSecClassCertificate) ||
!ca_funcs->kSecMatchLimit || !*(ca_funcs->kSecMatchLimit) ||
!ca_funcs->kSecMatchLimitAll || !*(ca_funcs->kSecMatchLimitAll) ||
!ca_funcs->kSecReturnRef || !*(ca_funcs->kSecReturnRef) ||
!ca_funcs->kCFBooleanTrue || !*(ca_funcs->kCFBooleanTrue)) {
BUN__warn__system_ca_load_failed("Required Security framework constants not available");
return NULL;
}
// Create query dictionary to get all certificates
CFTypeRef keys[3] = {
*(ca_funcs->kSecClass),
*(ca_funcs->kSecMatchLimit),
*(ca_funcs->kSecReturnRef)
};
CFTypeRef values[3] = {
*(ca_funcs->kSecClassCertificate),
*(ca_funcs->kSecMatchLimitAll),
*(ca_funcs->kCFBooleanTrue)
};
CFDictionaryRef query = ca_funcs->CFDictionaryCreate(NULL, keys, values, 3, NULL, NULL);
if (!query) {
BUN__warn__system_ca_load_failed("Could not create certificate query");
return NULL;
}
CFTypeRef result = NULL;
OSStatus status = ca_funcs->SecItemCopyMatching(query, &result);
ca_funcs->CFRelease(query);
if (status != 0 || !result) {
BUN__warn__system_ca_load_failed("Could not retrieve system certificates");
return NULL;
}
CFArrayRef cert_array = (CFArrayRef)result;
CFIndex cert_count = ca_funcs->CFArrayGetCount(cert_array);
STACK_OF(X509) *stack = sk_X509_new_null();
if (!stack) {
ca_funcs->CFRelease(cert_array);
BUN__warn__system_ca_load_failed("Could not create certificate stack");
return NULL;
}
int loaded_count = 0;
for (CFIndex i = 0; i < cert_count; i++) {
SecCertificateRef cert_ref = (SecCertificateRef)ca_funcs->CFArrayGetValueAtIndex(cert_array, i);
if (!cert_ref) continue;
// Check if certificate is trusted for SSL
if (!us_internal_is_certificate_trusted_for_policy(ca_funcs, cert_ref)) {
continue;
}
// Get certificate data
CFDataRef cert_data = ca_funcs->SecCertificateCopyData(cert_ref);
if (!cert_data) continue;
const unsigned char* data_ptr = ca_funcs->CFDataGetBytePtr(cert_data);
CFIndex data_len = ca_funcs->CFDataGetLength(cert_data);
if (data_len > 0) {
const unsigned char* data_ptr_copy = data_ptr;
X509* x509 = d2i_X509(NULL, &data_ptr_copy, (long)data_len);
if (x509) {
if (sk_X509_push(stack, x509)) {
loaded_count++;
} else {
X509_free(x509);
}
}
}
ca_funcs->CFRelease(cert_data);
}
ca_funcs->CFRelease(cert_array);
if (loaded_count == 0) {
sk_X509_pop_free(stack, X509_free);
BUN__warn__system_ca_load_failed("No trusted system certificates found");
return NULL;
}
return stack;
}
#endif
static STACK_OF(X509) *us_ssl_ctx_load_all_certs_from_file(const char *filename) {
BIO *in = NULL;
STACK_OF(X509) *certs = NULL;
@@ -100,7 +441,8 @@ end:
static void us_internal_init_root_certs(
X509 *root_cert_instances[root_certs_size],
STACK_OF(X509) *&root_extra_cert_instances) {
STACK_OF(X509) *&root_extra_cert_instances,
STACK_OF(X509) *&system_cert_instances) {
static std::atomic_flag root_cert_instances_lock = ATOMIC_FLAG_INIT;
static std::atomic_bool root_cert_instances_initialized = 0;
@@ -122,6 +464,13 @@ static void us_internal_init_root_certs(
if (extra_certs && extra_certs[0]) {
root_extra_cert_instances = us_ssl_ctx_load_all_certs_from_file(extra_certs);
}
// Load system certificates if enabled
if (Bun__useSystemCA()) {
#ifdef __APPLE__
system_cert_instances = us_internal_init_system_certs_from_macos_keychain();
#endif
}
}
atomic_flag_clear_explicit(&root_cert_instances_lock,
@@ -136,12 +485,15 @@ extern "C" int us_internal_raw_root_certs(struct us_cert_string_t **out) {
struct us_default_ca_certificates {
X509 *root_cert_instances[root_certs_size];
STACK_OF(X509) *root_extra_cert_instances;
STACK_OF(X509) *system_cert_instances;
};
us_default_ca_certificates* us_get_default_ca_certificates() {
static us_default_ca_certificates default_ca_certificates = {{NULL}, NULL};
static us_default_ca_certificates default_ca_certificates = {{NULL}, NULL, NULL};
us_internal_init_root_certs(default_ca_certificates.root_cert_instances, default_ca_certificates.root_extra_cert_instances);
us_internal_init_root_certs(default_ca_certificates.root_cert_instances,
default_ca_certificates.root_extra_cert_instances,
default_ca_certificates.system_cert_instances);
return &default_ca_certificates;
}
@@ -150,6 +502,10 @@ STACK_OF(X509) *us_get_root_extra_cert_instances() {
return us_get_default_ca_certificates()->root_extra_cert_instances;
}
STACK_OF(X509) *us_get_system_cert_instances() {
return us_get_default_ca_certificates()->system_cert_instances;
}
extern "C" X509_STORE *us_get_default_ca_store() {
X509_STORE *store = X509_STORE_new();
if (store == NULL) {
@@ -164,8 +520,14 @@ extern "C" X509_STORE *us_get_default_ca_store() {
us_default_ca_certificates *default_ca_certificates = us_get_default_ca_certificates();
X509** root_cert_instances = default_ca_certificates->root_cert_instances;
STACK_OF(X509) *root_extra_cert_instances = default_ca_certificates->root_extra_cert_instances;
STACK_OF(X509) *system_cert_instances = default_ca_certificates->system_cert_instances;
// load all root_cert_instances on the default ca store
// Node.js loads certificates in this order:
// 1. Default root certs (bundled Mozilla certs)
// 2. System certs (when --use-system-ca is enabled)
// 3. Extra certs (NODE_EXTRA_CA_CERTS)
// 1. Always load bundled root certificates first
for (size_t i = 0; i < root_certs_size; i++) {
X509 *cert = root_cert_instances[i];
if (cert == NULL)
@@ -174,6 +536,16 @@ extern "C" X509_STORE *us_get_default_ca_store() {
X509_STORE_add_cert(store, cert);
}
// 2. Add system certificates when --use-system-ca flag is enabled
if (Bun__useSystemCA() && system_cert_instances) {
for (int i = 0; i < sk_X509_num(system_cert_instances); i++) {
X509 *cert = sk_X509_value(system_cert_instances, i);
X509_up_ref(cert);
X509_STORE_add_cert(store, cert);
}
}
// 3. Always include extra CAs from NODE_EXTRA_CA_CERTS last
if (root_extra_cert_instances) {
for (int i = 0; i < sk_X509_num(root_extra_cert_instances); i++) {
X509 *cert = sk_X509_value(root_extra_cert_instances, i);
@@ -183,4 +555,4 @@ extern "C" X509_STORE *us_get_default_ca_store() {
}
return store;
}
}

200
src/bun.js/system_ca.zig Normal file
View File

@@ -0,0 +1,200 @@
const std = @import("std");
const bun = @import("root").bun;
const Environment = bun.Environment;
// macOS Security framework types
pub const OSStatus = c_int;
pub const CFIndex = c_long;
pub const Boolean = c_uchar;
// Opaque types
pub const CFTypeRef = ?*anyopaque;
pub const CFArrayRef = ?*anyopaque;
pub const CFDataRef = ?*anyopaque;
pub const CFStringRef = ?*anyopaque;
pub const CFDictionaryRef = ?*anyopaque;
pub const CFErrorRef = ?*anyopaque;
pub const CFAllocatorRef = ?*anyopaque;
pub const SecCertificateRef = ?*anyopaque;
pub const SecTrustRef = ?*anyopaque;
pub const SecPolicyRef = ?*anyopaque;
// Security framework function pointers
pub const SecTrustCopyAnchorCertificatesFunc = *const fn (*?CFArrayRef) callconv(.C) OSStatus;
pub const SecCertificateCopyDataFunc = *const fn (SecCertificateRef) callconv(.C) CFDataRef;
pub const SecItemCopyMatchingFunc = *const fn (CFDictionaryRef, *?CFTypeRef) callconv(.C) OSStatus;
pub const SecTrustSettingsCopyTrustSettingsFunc = *const fn (SecCertificateRef, c_int) callconv(.C) OSStatus;
pub const SecPolicyCreateSSLFunc = *const fn (Boolean, CFStringRef) callconv(.C) SecPolicyRef;
pub const SecTrustCreateWithCertificatesFunc = *const fn (CFTypeRef, CFTypeRef, *?SecTrustRef) callconv(.C) OSStatus;
pub const SecTrustEvaluateWithErrorFunc = *const fn (SecTrustRef, *?CFErrorRef) callconv(.C) Boolean;
pub const SecPolicyCopyPropertiesFunc = *const fn (SecPolicyRef) callconv(.C) CFDictionaryRef;
// CoreFoundation function pointers
pub const CFArrayGetCountFunc = *const fn (CFArrayRef) callconv(.C) CFIndex;
pub const CFArrayGetValueAtIndexFunc = *const fn (CFArrayRef, CFIndex) callconv(.C) CFTypeRef;
pub const CFDataGetBytePtrFunc = *const fn (CFDataRef) callconv(.C) [*]const u8;
pub const CFDataGetLengthFunc = *const fn (CFDataRef) callconv(.C) CFIndex;
pub const CFReleaseFunc = *const fn (CFTypeRef) callconv(.C) void;
pub const CFDictionaryCreateFunc = *const fn (CFAllocatorRef, [*]CFTypeRef, [*]CFTypeRef, CFIndex, ?*anyopaque, ?*anyopaque) callconv(.C) CFDictionaryRef;
pub const CFStringCreateWithCStringFunc = *const fn (CFAllocatorRef, [*:0]const u8, c_uint) callconv(.C) CFStringRef;
pub const CFArrayCreateFunc = *const fn (CFAllocatorRef, [*]CFTypeRef, CFIndex, ?*anyopaque) callconv(.C) CFArrayRef;
pub const CFDictionaryGetValueFunc = *const fn (CFDictionaryRef, CFTypeRef) callconv(.C) CFTypeRef;
pub const CFGetTypeIDFunc = *const fn (CFTypeRef) callconv(.C) c_ulong;
pub const CFStringGetTypeIDFunc = *const fn () callconv(.C) c_ulong;
pub const CFNumberGetTypeIDFunc = *const fn () callconv(.C) c_ulong;
pub const CFStringGetCStringFunc = *const fn (CFStringRef, [*]u8, CFIndex, c_uint) callconv(.C) Boolean;
pub const CFNumberGetValueFunc = *const fn (CFTypeRef, c_int, *anyopaque) callconv(.C) Boolean;
// Constants that need to be resolved dynamically
pub const ConstantRef = ?*CFStringRef;
// Structure to hold all the function pointers and constants
pub const MacOSCAFunctions = extern struct {
// Security framework functions
SecTrustCopyAnchorCertificates: ?SecTrustCopyAnchorCertificatesFunc,
SecCertificateCopyData: ?SecCertificateCopyDataFunc,
SecItemCopyMatching: ?SecItemCopyMatchingFunc,
SecTrustSettingsCopyTrustSettings: ?SecTrustSettingsCopyTrustSettingsFunc,
SecPolicyCreateSSL: ?SecPolicyCreateSSLFunc,
SecTrustCreateWithCertificates: ?SecTrustCreateWithCertificatesFunc,
SecTrustEvaluateWithError: ?SecTrustEvaluateWithErrorFunc,
SecPolicyCopyProperties: ?SecPolicyCopyPropertiesFunc,
// CoreFoundation functions
CFArrayGetCount: ?CFArrayGetCountFunc,
CFArrayGetValueAtIndex: ?CFArrayGetValueAtIndexFunc,
CFDataGetBytePtr: ?CFDataGetBytePtrFunc,
CFDataGetLength: ?CFDataGetLengthFunc,
CFRelease: ?CFReleaseFunc,
CFDictionaryCreate: ?CFDictionaryCreateFunc,
CFStringCreateWithCString: ?CFStringCreateWithCStringFunc,
CFArrayCreate: ?CFArrayCreateFunc,
CFDictionaryGetValue: ?CFDictionaryGetValueFunc,
CFGetTypeID: ?CFGetTypeIDFunc,
CFStringGetTypeID: ?CFStringGetTypeIDFunc,
CFNumberGetTypeID: ?CFNumberGetTypeIDFunc,
CFStringGetCString: ?CFStringGetCStringFunc,
CFNumberGetValue: ?CFNumberGetValueFunc,
// Constants
kSecClass: ConstantRef,
kSecClassCertificate: ConstantRef,
kSecMatchLimit: ConstantRef,
kSecMatchLimitAll: ConstantRef,
kSecReturnRef: ConstantRef,
kCFBooleanTrue: ConstantRef,
kSecTrustSettingsResult: ConstantRef,
kSecTrustSettingsPolicy: ConstantRef,
kSecTrustSettingsPolicyString: ConstantRef,
kSecTrustSettingsApplication: ConstantRef,
kSecPolicyOid: ConstantRef,
kSecPolicyAppleSSL: ConstantRef,
};
var ca_functions: ?MacOSCAFunctions = null;
var init_mutex: std.Thread.Mutex = .{};
fn dlsym(handle: ?*anyopaque, comptime Type: type, comptime symbol: [:0]const u8) ?Type {
if (std.c.dlsym(handle, symbol)) |ptr| {
return bun.cast(Type, ptr);
}
return null;
}
fn dlsym_constant(handle: ?*anyopaque, comptime symbol: [:0]const u8) ConstantRef {
if (std.c.dlsym(handle, symbol)) |ptr| {
return bun.cast(ConstantRef, ptr);
}
return null;
}
fn initMacOSCAFunctions() bool {
if (ca_functions != null) return true;
init_mutex.lock();
defer init_mutex.unlock();
if (ca_functions != null) return true;
if (!Environment.isMac) return false;
// Load Security framework
const security_handle = bun.sys.dlopen("/System/Library/Frameworks/Security.framework/Security", .{ .LAZY = true, .LOCAL = true });
if (security_handle == null) return false;
// Load CoreFoundation framework
const cf_handle = bun.sys.dlopen("/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation", .{ .LAZY = true, .LOCAL = true });
if (cf_handle == null) return false;
ca_functions = MacOSCAFunctions{
// Security framework functions
.SecTrustCopyAnchorCertificates = dlsym(security_handle, SecTrustCopyAnchorCertificatesFunc, "SecTrustCopyAnchorCertificates"),
.SecCertificateCopyData = dlsym(security_handle, SecCertificateCopyDataFunc, "SecCertificateCopyData"),
.SecItemCopyMatching = dlsym(security_handle, SecItemCopyMatchingFunc, "SecItemCopyMatching"),
.SecTrustSettingsCopyTrustSettings = dlsym(security_handle, SecTrustSettingsCopyTrustSettingsFunc, "SecTrustSettingsCopyTrustSettings"),
.SecPolicyCreateSSL = dlsym(security_handle, SecPolicyCreateSSLFunc, "SecPolicyCreateSSL"),
.SecTrustCreateWithCertificates = dlsym(security_handle, SecTrustCreateWithCertificatesFunc, "SecTrustCreateWithCertificates"),
.SecTrustEvaluateWithError = dlsym(security_handle, SecTrustEvaluateWithErrorFunc, "SecTrustEvaluateWithError"),
.SecPolicyCopyProperties = dlsym(security_handle, SecPolicyCopyPropertiesFunc, "SecPolicyCopyProperties"),
// CoreFoundation functions
.CFArrayGetCount = dlsym(cf_handle, CFArrayGetCountFunc, "CFArrayGetCount"),
.CFArrayGetValueAtIndex = dlsym(cf_handle, CFArrayGetValueAtIndexFunc, "CFArrayGetValueAtIndex"),
.CFDataGetBytePtr = dlsym(cf_handle, CFDataGetBytePtrFunc, "CFDataGetBytePtr"),
.CFDataGetLength = dlsym(cf_handle, CFDataGetLengthFunc, "CFDataGetLength"),
.CFRelease = dlsym(cf_handle, CFReleaseFunc, "CFRelease"),
.CFDictionaryCreate = dlsym(cf_handle, CFDictionaryCreateFunc, "CFDictionaryCreate"),
.CFStringCreateWithCString = dlsym(cf_handle, CFStringCreateWithCStringFunc, "CFStringCreateWithCString"),
.CFArrayCreate = dlsym(cf_handle, CFArrayCreateFunc, "CFArrayCreate"),
.CFDictionaryGetValue = dlsym(cf_handle, CFDictionaryGetValueFunc, "CFDictionaryGetValue"),
.CFGetTypeID = dlsym(cf_handle, CFGetTypeIDFunc, "CFGetTypeID"),
.CFStringGetTypeID = dlsym(cf_handle, CFStringGetTypeIDFunc, "CFStringGetTypeID"),
.CFNumberGetTypeID = dlsym(cf_handle, CFNumberGetTypeIDFunc, "CFNumberGetTypeID"),
.CFStringGetCString = dlsym(cf_handle, CFStringGetCStringFunc, "CFStringGetCString"),
.CFNumberGetValue = dlsym(cf_handle, CFNumberGetValueFunc, "CFNumberGetValue"),
// Constants
.kSecClass = dlsym_constant(security_handle, "kSecClass"),
.kSecClassCertificate = dlsym_constant(security_handle, "kSecClassCertificate"),
.kSecMatchLimit = dlsym_constant(security_handle, "kSecMatchLimit"),
.kSecMatchLimitAll = dlsym_constant(security_handle, "kSecMatchLimitAll"),
.kSecReturnRef = dlsym_constant(security_handle, "kSecReturnRef"),
.kCFBooleanTrue = dlsym_constant(cf_handle, "kCFBooleanTrue"),
.kSecTrustSettingsResult = dlsym_constant(security_handle, "kSecTrustSettingsResult"),
.kSecTrustSettingsPolicy = dlsym_constant(security_handle, "kSecTrustSettingsPolicy"),
.kSecTrustSettingsPolicyString = dlsym_constant(security_handle, "kSecTrustSettingsPolicyString"),
.kSecTrustSettingsApplication = dlsym_constant(security_handle, "kSecTrustSettingsApplication"),
.kSecPolicyOid = dlsym_constant(security_handle, "kSecPolicyOid"),
.kSecPolicyAppleSSL = dlsym_constant(security_handle, "kSecPolicyAppleSSL"),
};
// Verify critical functions were loaded
if (ca_functions.?.SecCertificateCopyData == null or
ca_functions.?.CFArrayGetCount == null or
ca_functions.?.CFArrayGetValueAtIndex == null or
ca_functions.?.CFDataGetBytePtr == null or
ca_functions.?.CFDataGetLength == null or
ca_functions.?.CFRelease == null)
{
ca_functions = null;
return false;
}
return true;
}
// Export function to get the CA functions for C++
export fn Bun__getMacOSCAFunctions() ?*MacOSCAFunctions {
if (initMacOSCAFunctions()) {
return &ca_functions.?;
}
return null;
}
// External function to check CLI flag
extern "C" fn Bun__useSystemCAFromCLI() c_int;
// Check if system CA loading is enabled via CLI flag
export fn Bun__useSystemCA() c_int {
return Bun__useSystemCAFromCLI();
}

View File

@@ -3749,6 +3749,8 @@ pub fn freeSensitive(allocator: std.mem.Allocator, slice: anytype) void {
}
pub const server = @import("./bun.js/api/server.zig");
pub const system_ca = @import("./bun.js/system_ca.zig");
pub const macho = @import("./macho.zig");
pub const valkey = @import("./valkey/index.zig");
pub const highway = @import("./highway.zig");

View File

@@ -389,6 +389,7 @@ pub const Command = struct {
expose_gc: bool = false,
preserve_symlinks_main: bool = false,
console_depth: ?u16 = null,
use_system_ca: bool = false,
};
var global_cli_ctx: Context = undefined;
@@ -1656,3 +1657,11 @@ pub fn printRevisionAndExit() noreturn {
Output.writer().writeAll(Global.package_json_version_with_revision ++ "\n") catch {};
Global.exit(0);
}
// Export function to check if --use-system-ca CLI flag is set
export fn Bun__useSystemCAFromCLI() c_int {
if (global_cli_ctx.runtime_options.use_system_ca) {
return 1;
}
return 0;
}

View File

@@ -95,6 +95,7 @@ pub const runtime_params_ = [_]ParamType{
clap.parseParam("--prefer-offline Skip staleness checks for packages in the Bun runtime and resolve from disk") catch unreachable,
clap.parseParam("--prefer-latest Use the latest matching versions of packages in the Bun runtime, always checking npm") catch unreachable,
clap.parseParam("--port <STR> Set the default port for Bun.serve") catch unreachable,
clap.parseParam("--use-system-ca Use the system CA store instead of the embedded certificates") catch unreachable,
clap.parseParam("-u, --origin <STR>") catch unreachable,
clap.parseParam("--conditions <STR>... Pass custom conditions to resolve") catch unreachable,
clap.parseParam("--fetch-preconnect <STR>... Preconnect to a URL while code is loading") catch unreachable,
@@ -668,6 +669,7 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
ctx.runtime_options.smol = args.flag("--smol");
ctx.runtime_options.preconnect = args.options("--fetch-preconnect");
ctx.runtime_options.expose_gc = args.flag("--expose-gc");
ctx.runtime_options.use_system_ca = args.flag("--use-system-ca");
if (args.option("--console-depth")) |depth_str| {
const depth = std.fmt.parseInt(u16, depth_str, 10) catch {

View File

@@ -139,6 +139,10 @@ export fn BUN__warn__extra_ca_load_failed(filename: [*c]const u8, error_msg: [*c
bun.Output.warn("ignoring extra certs from {s}, load failed: {s}", .{ filename, error_msg });
}
export fn BUN__warn__system_ca_load_failed(error_msg: [*c]const u8) void {
bun.Output.warn("ignoring system CAs, load failed: {s}", .{error_msg});
}
pub const LIBUS_SOCKET_DESCRIPTOR = switch (bun.Environment.isWindows) {
true => *anyopaque,
false => i32,

View File

@@ -82,7 +82,7 @@
"tsyringe": "4.8.0",
"type-graphql": "2.0.0-rc.2",
"typeorm": "0.3.20",
"typescript": "^5.8.3",
"typescript": "5.8.3",
"undici": "5.20.0",
"unzipper": "0.12.3",
"uuid": "11.1.0",

View File

@@ -0,0 +1,74 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe } from "harness";
test("system CA loading can be enabled via CLI flag (additive to bundled CAs)", async () => {
// Test that the CLI flag is recognized and adds system CAs to bundled CAs
const { stdout, stderr, exitCode } = await new Bun.subprocess({
cmd: [bunExe(), "--use-system-ca", "-e", "console.log('--use-system-ca flag works')"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
}).spawn();
const output = await new Response(stdout).text();
const error = await new Response(stderr).text();
expect(exitCode).toBe(0);
expect(output.trim()).toBe("--use-system-ca flag works");
});
test("system CA loading is disabled by default", async () => {
// Test that system CA loading is not enabled without the flag
const { stdout, stderr, exitCode } = await new Bun.subprocess({
cmd: [bunExe(), "-e", "console.log('default behavior works')"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
}).spawn();
const output = await new Response(stdout).text();
expect(exitCode).toBe(0);
expect(output.trim()).toBe("default behavior works");
});
test("CLI flag position independence", async () => {
// Test that CLI flag works regardless of position
const { stdout, stderr, exitCode } = await new Bun.subprocess({
cmd: [bunExe(), "-e", "console.log('flag position test')", "--use-system-ca"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
}).spawn();
const output = await new Response(stdout).text();
expect(exitCode).toBe(0);
expect(output.trim()).toBe("flag position test");
});
// Only run this test on macOS since system CA loading is macOS-specific
test.skipIf(process.platform !== "darwin")("macOS system CAs are added to bundled CAs", async () => {
// Test that system CA functionality works and is additive to bundled CAs
const { stdout, stderr, exitCode } = await new Bun.subprocess({
cmd: [
bunExe(),
"--use-system-ca",
"-e",
`
// This tests that system CA functionality works alongside bundled CAs
// The system loads: bundled CAs + system CAs + NODE_EXTRA_CA_CERTS
console.log("macOS system CA additive test passed");
`,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
}).spawn();
const output = await new Response(stdout).text();
const error = await new Response(stderr).text();
expect(exitCode).toBe(0);
expect(output.trim()).toBe("macOS system CA additive test passed");
});