mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 23:48:52 +00:00
Compare commits
5 Commits
dylan/pyth
...
claude/loa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8712fce75f | ||
|
|
05b5fce694 | ||
|
|
4777992fe7 | ||
|
|
8b950ca95a | ||
|
|
448b9c7ff2 |
@@ -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
|
||||
|
||||
@@ -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
200
src/bun.js/system_ca.zig
Normal 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();
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
74
test/regression/issue/system-ca-test.test.ts
Normal file
74
test/regression/issue/system-ca-test.test.ts
Normal 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");
|
||||
});
|
||||
Reference in New Issue
Block a user