mirror of
https://github.com/openai/codex.git
synced 2026-05-29 23:40:29 +00:00
Address remaining MITM CA trust feedback
This commit is contained in:
3
codex-rs/Cargo.lock
generated
3
codex-rs/Cargo.lock
generated
@@ -3330,6 +3330,7 @@ dependencies = [
|
||||
"codex-utils-home-dir",
|
||||
"codex-utils-rustls-provider",
|
||||
"globset",
|
||||
"openssl-probe 0.2.1",
|
||||
"pretty_assertions",
|
||||
"rama-core",
|
||||
"rama-http",
|
||||
@@ -3340,6 +3341,8 @@ dependencies = [
|
||||
"rama-tls-rustls",
|
||||
"rama-unix",
|
||||
"rustls-native-certs",
|
||||
"schannel",
|
||||
"security-framework 3.5.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
|
||||
@@ -315,6 +315,7 @@ multimap = "0.10.0"
|
||||
notify = "8.2.0"
|
||||
nucleo = { git = "https://github.com/helix-editor/nucleo.git", rev = "4253de9faabb4e5c6d81d946a5e35a90f87347ee" }
|
||||
once_cell = "1.20.2"
|
||||
openssl-probe = "0.2.1"
|
||||
openssl-sys = "*"
|
||||
opentelemetry = "0.31.0"
|
||||
opentelemetry-appender-tracing = "0.31.0"
|
||||
@@ -348,8 +349,10 @@ rustls = { version = "0.23", default-features = false, features = [
|
||||
] }
|
||||
rustls-native-certs = "0.8.3"
|
||||
rustls-pki-types = "1.14.0"
|
||||
schannel = "0.1.28"
|
||||
schemars = "0.8.22"
|
||||
seccompiler = "0.5.0"
|
||||
security-framework = "3.5.1"
|
||||
semver = "1.0"
|
||||
sentry = "0.46.0"
|
||||
serde = "1"
|
||||
|
||||
@@ -268,6 +268,7 @@ fn managed_proxy_preflight_argv_is_wrapped_for_full_access_policy() {
|
||||
Path::new("/"),
|
||||
&FileSystemSandboxPolicy::unrestricted(),
|
||||
mode,
|
||||
/*mitm_ca_cert_path*/ None,
|
||||
)
|
||||
.expect("build preflight argv")
|
||||
.args;
|
||||
|
||||
@@ -44,3 +44,12 @@ tempfile = { workspace = true }
|
||||
|
||||
[target.'cfg(target_family = "unix")'.dependencies]
|
||||
rama-unix = { version = "=0.3.0-alpha.4" }
|
||||
|
||||
[target.'cfg(all(unix, not(target_os = "macos")))'.dependencies]
|
||||
openssl-probe = { workspace = true }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
security-framework = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
schannel = { workspace = true }
|
||||
|
||||
@@ -20,7 +20,19 @@ use rama_tls_rustls::dep::rcgen::PKCS_ECDSA_P256_SHA256;
|
||||
use rama_tls_rustls::dep::rcgen::SanType;
|
||||
use rama_tls_rustls::dep::rustls;
|
||||
use rama_tls_rustls::server::TlsAcceptorData;
|
||||
#[cfg(windows)]
|
||||
use schannel::cert_context::ValidUses;
|
||||
#[cfg(windows)]
|
||||
use schannel::cert_store::CertStore;
|
||||
#[cfg(target_os = "macos")]
|
||||
use security_framework::trust_settings::Domain;
|
||||
#[cfg(target_os = "macos")]
|
||||
use security_framework::trust_settings::TrustSettings;
|
||||
#[cfg(target_os = "macos")]
|
||||
use security_framework::trust_settings::TrustSettingsForCertificate;
|
||||
use std::collections::HashMap;
|
||||
#[cfg(any(target_os = "macos", windows))]
|
||||
use std::error::Error as StdError;
|
||||
use std::fs;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
@@ -156,8 +168,7 @@ fn build_managed_ca_trust_bundle(
|
||||
env: &HashMap<String, String>,
|
||||
) -> Result<String> {
|
||||
let mut trust_bundle = String::new();
|
||||
let rustls_native_certs::CertificateResult { certs, errors, .. } =
|
||||
rustls_native_certs::load_native_certs();
|
||||
let rustls_native_certs::CertificateResult { certs, errors, .. } = load_platform_native_certs();
|
||||
if !errors.is_empty() {
|
||||
warn!(
|
||||
native_root_error_count = errors.len(),
|
||||
@@ -194,6 +205,205 @@ fn build_managed_ca_trust_bundle(
|
||||
Ok(trust_bundle)
|
||||
}
|
||||
|
||||
// Match rustls-native-certs' platform loaders without honoring SSL_CERT_FILE / SSL_CERT_DIR.
|
||||
// Those inherited values are child custom roots, so append their bundle paths separately below.
|
||||
fn load_platform_native_certs() -> rustls_native_certs::CertificateResult {
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
{
|
||||
let cert_file = NATIVE_CERTIFICATE_FILE_NAMES
|
||||
.iter()
|
||||
.copied()
|
||||
.find_map(|path| Path::new(path).exists().then(|| PathBuf::from(path)));
|
||||
let mut result = rustls_native_certs::load_certs_from_paths(cert_file.as_deref(), None);
|
||||
for certs_dir in openssl_probe::candidate_cert_dirs() {
|
||||
let dir_result = rustls_native_certs::load_certs_from_paths(None, Some(certs_dir));
|
||||
result.certs.extend(dir_result.certs);
|
||||
result.errors.extend(dir_result.errors);
|
||||
}
|
||||
result.certs.sort_unstable_by(|left, right| left.cmp(right));
|
||||
result.certs.dedup();
|
||||
return result;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let mut result = rustls_native_certs::CertificateResult::default();
|
||||
let mut all_certs = HashMap::new();
|
||||
for domain in &[Domain::User, Domain::Admin, Domain::System] {
|
||||
let trust_settings = TrustSettings::new(*domain);
|
||||
let iter = match trust_settings.iter() {
|
||||
Ok(iter) => iter,
|
||||
Err(err) => {
|
||||
push_native_cert_os_error(
|
||||
&mut result,
|
||||
err.into(),
|
||||
match domain {
|
||||
Domain::User => "failed to load user trust settings",
|
||||
Domain::Admin => "failed to load admin trust settings",
|
||||
Domain::System => "failed to load system trust settings",
|
||||
},
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
for cert in iter {
|
||||
let der = cert.to_der();
|
||||
let trusted = match trust_settings.tls_trust_settings_for_certificate(&cert) {
|
||||
Ok(trusted) => trusted.unwrap_or(TrustSettingsForCertificate::TrustRoot),
|
||||
Err(err) => {
|
||||
push_native_cert_os_error(
|
||||
&mut result,
|
||||
err.into(),
|
||||
"certificate not trusted",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
all_certs.entry(der).or_insert(trusted);
|
||||
}
|
||||
}
|
||||
|
||||
for (der, trusted) in all_certs {
|
||||
if matches!(
|
||||
trusted,
|
||||
TrustSettingsForCertificate::TrustRoot | TrustSettingsForCertificate::TrustAsRoot
|
||||
) {
|
||||
result.certs.push(CertificateDer::from(der));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
{
|
||||
let mut result = rustls_native_certs::CertificateResult::default();
|
||||
let current_user_store = match CertStore::open_current_user("ROOT") {
|
||||
Ok(store) => store,
|
||||
Err(err) => {
|
||||
push_native_cert_os_error(
|
||||
&mut result,
|
||||
err.into(),
|
||||
"failed to open current user certificate store",
|
||||
);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
for cert in current_user_store.certs() {
|
||||
let valid_uses = match cert.valid_uses() {
|
||||
Ok(valid_uses) => valid_uses,
|
||||
Err(err) => {
|
||||
push_native_cert_os_error(
|
||||
&mut result,
|
||||
err.into(),
|
||||
"failed to read certificate valid uses",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let is_time_valid = match cert.is_time_valid() {
|
||||
Ok(is_time_valid) => is_time_valid,
|
||||
Err(err) => {
|
||||
push_native_cert_os_error(
|
||||
&mut result,
|
||||
err.into(),
|
||||
"failed to read certificate validity",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if usable_for_rustls(valid_uses) && is_time_valid {
|
||||
result
|
||||
.certs
|
||||
.push(CertificateDer::from(cert.to_der().to_vec()));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
rustls_native_certs::load_native_certs()
|
||||
}
|
||||
|
||||
#[cfg(any(target_os = "macos", windows))]
|
||||
fn push_native_cert_os_error(
|
||||
result: &mut rustls_native_certs::CertificateResult,
|
||||
err: Box<dyn StdError + Send + Sync + 'static>,
|
||||
context: &'static str,
|
||||
) {
|
||||
result.errors.push(rustls_native_certs::Error {
|
||||
context,
|
||||
kind: rustls_native_certs::ErrorKind::Os(err),
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
fn usable_for_rustls(uses: ValidUses) -> bool {
|
||||
match uses {
|
||||
ValidUses::All => true,
|
||||
ValidUses::Oids(oids) => oids.iter().any(|oid| oid == PKIX_SERVER_AUTH),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
const PKIX_SERVER_AUTH: &str = "1.3.6.1.5.5.7.3.1";
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "linux"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] = &[
|
||||
"/etc/ssl/certs/ca-certificates.crt",
|
||||
"/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem",
|
||||
"/etc/pki/tls/certs/ca-bundle.crt",
|
||||
"/etc/ssl/ca-bundle.pem",
|
||||
"/etc/pki/tls/cacert.pem",
|
||||
"/etc/ssl/cert.pem",
|
||||
"/opt/etc/ssl/certs/ca-certificates.crt",
|
||||
"/etc/ssl/certs/cacert.pem",
|
||||
];
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "freebsd"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] = &["/usr/local/etc/ssl/cert.pem"];
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "dragonfly"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] = &["/usr/local/share/certs/ca-root-nss.crt"];
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "netbsd"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] = &["/etc/openssl/certs/ca-certificates.crt"];
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "openbsd"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] = &["/etc/ssl/cert.pem"];
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "solaris"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] = &["/etc/certs/ca-certificates.crt"];
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "illumos"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] =
|
||||
&["/etc/ssl/cacert.pem", "/etc/certs/ca-certificates.crt"];
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "android"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] =
|
||||
&["/data/data/com.termux/files/usr/etc/tls/cert.pem"];
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos"), target_os = "haiku"))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] = &["/boot/system/data/ssl/CARootCertificates.pem"];
|
||||
|
||||
#[cfg(all(
|
||||
unix,
|
||||
not(target_os = "macos"),
|
||||
not(any(
|
||||
target_os = "linux",
|
||||
target_os = "freebsd",
|
||||
target_os = "dragonfly",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "solaris",
|
||||
target_os = "illumos",
|
||||
target_os = "android",
|
||||
target_os = "haiku",
|
||||
))
|
||||
))]
|
||||
const NATIVE_CERTIFICATE_FILE_NAMES: &[&str] = &["/etc/ssl/certs/ca-certificates.crt"];
|
||||
|
||||
fn append_pem_file(bundle: &mut String, path: &Path) -> Result<()> {
|
||||
if !bundle.ends_with('\n') {
|
||||
bundle.push('\n');
|
||||
@@ -500,6 +710,26 @@ mod tests {
|
||||
assert!(!trust_bundle.contains("stale managed bundle"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_managed_ca_trust_bundle_appends_inherited_bundle() {
|
||||
let dir = tempdir().unwrap();
|
||||
let managed_ca_cert_path = dir.path().join("ca.pem");
|
||||
let trust_bundle_path = dir.path().join("ca-bundle.pem");
|
||||
let inherited_bundle_path = dir.path().join("inherited.pem");
|
||||
fs::write(&managed_ca_cert_path, "managed ca\n").unwrap();
|
||||
fs::write(&inherited_bundle_path, "inherited ca\n").unwrap();
|
||||
let env = HashMap::from([(
|
||||
"SSL_CERT_FILE".to_string(),
|
||||
inherited_bundle_path.display().to_string(),
|
||||
)]);
|
||||
|
||||
let trust_bundle =
|
||||
build_managed_ca_trust_bundle(&managed_ca_cert_path, &trust_bundle_path, &env).unwrap();
|
||||
|
||||
assert!(trust_bundle.contains("inherited ca"));
|
||||
assert!(trust_bundle.contains("managed ca"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn validate_existing_ca_key_file_rejects_group_world_permissions() {
|
||||
|
||||
@@ -627,6 +627,19 @@ impl NetworkProxy {
|
||||
|
||||
pub fn apply_to_env(&self, env: &mut HashMap<String, String>) {
|
||||
let runtime_settings = self.runtime_settings();
|
||||
// Fold command-level CA overrides into our replacement bundle before overwriting them.
|
||||
let mitm_ca_trust_bundle_path =
|
||||
runtime_settings
|
||||
.mitm_ca_trust_bundle_path
|
||||
.as_ref()
|
||||
.map(|fallback_path| {
|
||||
crate::certs::managed_ca_trust_bundle_path(env).unwrap_or_else(|err| {
|
||||
warn!(
|
||||
"failed to refresh managed MITM CA trust bundle from child env; using startup bundle: {err}"
|
||||
);
|
||||
fallback_path.clone()
|
||||
})
|
||||
});
|
||||
// Enforce proxying for child processes. We intentionally override existing values so
|
||||
// command-level environment cannot bypass the managed proxy endpoint.
|
||||
apply_proxy_env_overrides(
|
||||
@@ -635,7 +648,7 @@ impl NetworkProxy {
|
||||
self.socks_addr,
|
||||
self.socks_enabled,
|
||||
runtime_settings.allow_local_binding,
|
||||
runtime_settings.mitm_ca_trust_bundle_path.as_deref(),
|
||||
mitm_ca_trust_bundle_path.as_deref(),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user