Codex Handbook
codex-client/src/custom_ca.rs 795 lines
//! Custom CA handling for Codex outbound HTTP and websocket clients.//!//! Codex constructs outbound reqwest clients and secure websocket connections in a few crates, but//! they all need the same trust-store policy when enterprise proxies or gateways intercept TLS.//! This module centralizes that policy so callers can start from an ordinary//! `reqwest::ClientBuilder` or rustls client config, layer in custom CA support, and either get//! back a configured transport or a user-facing error that explains how to fix a misconfigured CA//! bundle.//!//! The module intentionally has a narrow responsibility://!//! - read CA material from `CODEX_CA_CERTIFICATE`, falling back to `SSL_CERT_FILE`//! - normalize PEM variants that show up in real deployments, including OpenSSL-style//!   `TRUSTED CERTIFICATE` labels and bundles that also contain CRLs//! - return user-facing errors that explain how to fix misconfigured CA files//!//! Its production contract is narrow: produce a transport configuration whose root store contains//! every parseable certificate block from the configured PEM bundle, or fail early with a precise//! error before the caller starts network traffic.//!//! In this module's test setup, a hermetic test is one whose result depends only on the CA file//! and environment variables that the test chose for itself. That matters here because the normal//! reqwest client-construction path is not hermetic enough for environment-sensitive tests://!//! - on macOS seatbelt runs, `reqwest::Client::builder().build()` can panic inside//!   `system-configuration` while probing platform proxy settings, which means the process can die//!   before the custom-CA code reports success or a structured error. That matters in practice//!   because Codex itself commonly runs spawned test processes under seatbelt, so this is not just//!   a hypothetical CI edge case.//! - child processes inherit CA-related environment variables by default, which lets developer//!   shell state or CI configuration affect a test unless the test scrubs those variables first//!//! The tests in this crate therefore stay split across two layers://!//! - unit tests in this module cover env-selection logic without constructing a real client//! - subprocess integration tests under `tests/` cover real client construction through//!   [`build_reqwest_client_for_subprocess_tests`], which disables reqwest proxy autodetection so//!   the tests can observe custom-CA success and failure directly, including one TLS handshake//!   through a local HTTPS server//! - those subprocess tests also scrub inherited CA environment variables before launch so their//!   result depends only on the test fixtures and env vars set by the test itselfuse std::env;use std::fs;use std::io;use std::path::Path;use std::path::PathBuf;use std::sync::Arc;use codex_utils_rustls_provider::ensure_rustls_crypto_provider;use rustls::ClientConfig;use rustls::RootCertStore;use rustls_pki_types::CertificateDer;use rustls_pki_types::pem::PemObject;use rustls_pki_types::pem::SectionKind;use rustls_pki_types::pem::{self};use thiserror::Error;use tracing::info;use tracing::warn;pub const CODEX_CA_CERT_ENV: &str = "CODEX_CA_CERTIFICATE";pub const SSL_CERT_FILE_ENV: &str = "SSL_CERT_FILE";const CA_CERT_HINT: &str = "If you set CODEX_CA_CERTIFICATE or SSL_CERT_FILE, ensure it points to a PEM file containing one or more CERTIFICATE blocks, or unset it to use system roots.";type PemSection = (SectionKind, Vec<u8>);/// Describes why a transport using shared custom CA support could not be constructed.////// These failure modes apply to both reqwest client construction and websocket TLS/// configuration. A build can fail because the configured CA file could not be read, could not be/// parsed as certificates, contained certs that the target TLS stack refused to register, or/// because the final reqwest client builder failed. Callers that do not care about the/// distinction can rely on the `From<BuildCustomCaTransportError> for io::Error` conversion.#[derive(Debug, Error)]pub enum BuildCustomCaTransportError {    /// Reading the selected CA file from disk failed before any PEM parsing could happen.    #[error(        "Failed to read CA certificate file {} selected by {}: {source}. {hint}",        path.display(),        source_env,        hint = CA_CERT_HINT    )]    ReadCaFile {        source_env: &'static str,        path: PathBuf,        source: io::Error,    },    /// The selected CA file was readable, but did not produce usable certificate material.    #[error(        "Failed to load CA certificates from {} selected by {}: {detail}. {hint}",        path.display(),        source_env,        hint = CA_CERT_HINT    )]    InvalidCaFile {        source_env: &'static str,        path: PathBuf,        detail: String,    },    /// One parsed certificate block could not be registered with the reqwest client builder.    #[error(        "Failed to parse certificate #{certificate_index} from {} selected by {}: {source}. {hint}",        path.display(),        source_env,        hint = CA_CERT_HINT    )]    RegisterCertificate {        source_env: &'static str,        path: PathBuf,        certificate_index: usize,        source: reqwest::Error,    },    /// Reqwest rejected the final client configuration after a custom CA bundle was loaded.    #[error(        "Failed to build HTTP client while using CA bundle from {} ({}): {source}",        source_env,        path.display()    )]    BuildClientWithCustomCa {        source_env: &'static str,        path: PathBuf,        #[source]        source: reqwest::Error,    },    /// Reqwest rejected the final client configuration while using only system roots.    #[error("Failed to build HTTP client while using system root certificates: {0}")]    BuildClientWithSystemRoots(#[source] reqwest::Error),    /// One parsed certificate block could not be registered with the websocket TLS root store.    #[error(        "Failed to register certificate #{certificate_index} from {} selected by {} in rustls root store: {source}. {hint}",        path.display(),        source_env,        hint = CA_CERT_HINT    )]    RegisterRustlsCertificate {        source_env: &'static str,        path: PathBuf,        certificate_index: usize,        source: rustls::Error,    },}impl From<BuildCustomCaTransportError> for io::Error {    fn from(error: BuildCustomCaTransportError) -> Self {        match error {            BuildCustomCaTransportError::ReadCaFile { ref source, .. } => {                io::Error::new(source.kind(), error)            }            BuildCustomCaTransportError::InvalidCaFile { .. }            | BuildCustomCaTransportError::RegisterCertificate { .. }            | BuildCustomCaTransportError::RegisterRustlsCertificate { .. } => {                io::Error::new(io::ErrorKind::InvalidData, error)            }            BuildCustomCaTransportError::BuildClientWithCustomCa { .. }            | BuildCustomCaTransportError::BuildClientWithSystemRoots(_) => io::Error::other(error),        }    }}/// Builds a reqwest client that honors Codex custom CA environment variables.////// Callers supply the baseline builder configuration they need, and this helper layers in custom/// CA handling before finally constructing the client. `CODEX_CA_CERTIFICATE` takes precedence/// over `SSL_CERT_FILE`, and empty values for either are treated as unset so callers do not/// accidentally turn `VAR=""` into a bogus path lookup.////// Callers that build a raw `reqwest::Client` directly bypass this policy entirely. That is an/// easy mistake to make when adding a new outbound Codex HTTP path, and the resulting bug only/// shows up in environments where a proxy or gateway requires a custom root CA.////// # Errors////// Returns a [`BuildCustomCaTransportError`] when the configured CA file is unreadable,/// malformed, or contains a certificate block that `reqwest` cannot register as a root.pub fn build_reqwest_client_with_custom_ca(    builder: reqwest::ClientBuilder,) -> Result<reqwest::Client, BuildCustomCaTransportError> {    build_reqwest_client_with_env(&ProcessEnv, builder)}/// Builds a rustls client config when a Codex custom CA bundle is configured.////// This is the websocket-facing sibling of [`build_reqwest_client_with_custom_ca`]. When/// `CODEX_CA_CERTIFICATE` or `SSL_CERT_FILE` selects a CA bundle, the returned config starts from/// the platform native roots and then adds the configured custom CA certificates. When no custom/// CA env var is set, this returns `Ok(None)` so websocket callers can keep using their ordinary/// default connector path.////// Callers that let tungstenite build its default TLS connector directly bypass this policy/// entirely. That bug only shows up in environments where secure websocket traffic needs the same/// enterprise root CA bundle as HTTPS traffic.pub fn maybe_build_rustls_client_config_with_custom_ca()-> Result<Option<Arc<ClientConfig>>, BuildCustomCaTransportError> {    maybe_build_rustls_client_config_with_env(&ProcessEnv)}/// Builds a reqwest client for spawned subprocess tests that exercise CA behavior.////// This is the test-only client-construction path used by the subprocess coverage in `tests/`./// The module-level docs explain the hermeticity problem in full; this helper only addresses the/// reqwest proxy-discovery panic side of that problem by disabling proxy autodetection. The tests/// still scrub inherited CA environment variables themselves. Normal production callers should use/// [`build_reqwest_client_with_custom_ca`] so test-only proxy behavior does not leak into/// ordinary client construction.pub fn build_reqwest_client_for_subprocess_tests(    builder: reqwest::ClientBuilder,) -> Result<reqwest::Client, BuildCustomCaTransportError> {    build_reqwest_client_with_env(&ProcessEnv, builder.no_proxy())}fn maybe_build_rustls_client_config_with_env(    env_source: &dyn EnvSource,) -> Result<Option<Arc<ClientConfig>>, BuildCustomCaTransportError> {    let Some(bundle) = env_source.configured_ca_bundle() else {        return Ok(None);    };    ensure_rustls_crypto_provider();    // Start from the platform roots so websocket callers keep the same baseline trust behavior    // they would get from tungstenite's default rustls connector, then layer in the Codex custom    // CA bundle on top when configured.    let mut root_store = RootCertStore::empty();    let rustls_native_certs::CertificateResult { certs, errors, .. } =        rustls_native_certs::load_native_certs();    if !errors.is_empty() {        warn!(            native_root_error_count = errors.len(),            "encountered errors while loading native root certificates"        );    }    let _ = root_store.add_parsable_certificates(certs);    let certificates = bundle.load_certificates()?;    for (idx, cert) in certificates.into_iter().enumerate() {        if let Err(source) = root_store.add(cert) {            warn!(                source_env = bundle.source_env,                ca_path = %bundle.path.display(),                certificate_index = idx + 1,                error = %source,                "failed to register CA certificate in rustls root store"            );            return Err(BuildCustomCaTransportError::RegisterRustlsCertificate {                source_env: bundle.source_env,                path: bundle.path.clone(),                certificate_index: idx + 1,                source,            });        }    }    Ok(Some(Arc::new(        ClientConfig::builder()            .with_root_certificates(root_store)            .with_no_client_auth(),    )))}/// Builds a reqwest client using an injected environment source and reqwest builder.////// This exists so tests can exercise precedence behavior deterministically without mutating the/// real process environment. It selects the CA bundle, delegates file parsing to/// [`ConfiguredCaBundle::load_certificates`], preserves the caller's chosen `reqwest` builder/// configuration, forces rustls when a custom CA is configured, and finally registers each parsed/// certificate with that builder.fn build_reqwest_client_with_env(    env_source: &dyn EnvSource,    mut builder: reqwest::ClientBuilder,) -> Result<reqwest::Client, BuildCustomCaTransportError> {    if let Some(bundle) = env_source.configured_ca_bundle() {        ensure_rustls_crypto_provider();        info!(            source_env = bundle.source_env,            ca_path = %bundle.path.display(),            "building HTTP client with rustls backend for custom CA bundle"        );        builder = builder.use_rustls_tls();        let certificates = bundle.load_certificates()?;        for (idx, cert) in certificates.iter().enumerate() {            let certificate = match reqwest::Certificate::from_der(cert.as_ref()) {                Ok(certificate) => certificate,                Err(source) => {                    warn!(                        source_env = bundle.source_env,                        ca_path = %bundle.path.display(),                        certificate_index = idx + 1,                        error = %source,                        "failed to register CA certificate"                    );                    return Err(BuildCustomCaTransportError::RegisterCertificate {                        source_env: bundle.source_env,                        path: bundle.path.clone(),                        certificate_index: idx + 1,                        source,                    });                }            };            builder = builder.add_root_certificate(certificate);        }        return match builder.build() {            Ok(client) => Ok(client),            Err(source) => {                warn!(                    source_env = bundle.source_env,                    ca_path = %bundle.path.display(),                    error = %source,                    "failed to build client after loading custom CA bundle"                );                Err(BuildCustomCaTransportError::BuildClientWithCustomCa {                    source_env: bundle.source_env,                    path: bundle.path.clone(),                    source,                })            }        };    }    info!(        codex_ca_certificate_configured = false,        ssl_cert_file_configured = false,        "using system root certificates because no CA override environment variable was selected"    );    match builder.build() {        Ok(client) => Ok(client),        Err(source) => {            warn!(                error = %source,                "failed to build client while using system root certificates"            );            Err(BuildCustomCaTransportError::BuildClientWithSystemRoots(                source,            ))        }    }}/// Abstracts environment access so tests can cover precedence rules without mutating process-wide/// variables.trait EnvSource {    /// Returns the environment variable value for `key`, if this source considers it set.    ///    /// Implementations should return `None` for absent values and may also collapse unreadable    /// process-environment states into `None`, because the custom CA logic treats both cases as    /// "no override configured". Callers build precedence and empty-string handling on top of this    /// method, so implementations should not trim or normalize the returned string.    fn var(&self, key: &str) -> Option<String>;    /// Returns a non-empty environment variable value interpreted as a filesystem path.    ///    /// Empty strings are treated as unset because presence here acts as a boolean "custom CA    /// override requested" signal. This keeps the precedence logic from treating `VAR=""` as an    /// attempt to open the current working directory or some other platform-specific oddity once    /// it is converted into a path.    fn non_empty_path(&self, key: &str) -> Option<PathBuf> {        self.var(key)            .filter(|value| !value.is_empty())            .map(PathBuf::from)    }    /// Returns the configured CA bundle and which environment variable selected it.    ///    /// `CODEX_CA_CERTIFICATE` wins over `SSL_CERT_FILE` because it is the Codex-specific override.    /// Keeping the winning variable name with the path lets later logging explain not only which    /// file was used but also why that file was chosen.    fn configured_ca_bundle(&self) -> Option<ConfiguredCaBundle> {        self.non_empty_path(CODEX_CA_CERT_ENV)            .map(|path| ConfiguredCaBundle {                source_env: CODEX_CA_CERT_ENV,                path,            })            .or_else(|| {                self.non_empty_path(SSL_CERT_FILE_ENV)                    .map(|path| ConfiguredCaBundle {                        source_env: SSL_CERT_FILE_ENV,                        path,                    })            })    }}/// Reads CA configuration from the real process environment.////// This is the production `EnvSource` implementation used by/// [`build_reqwest_client_with_custom_ca`]. Tests substitute in-memory env maps so they can/// exercise precedence and empty-value behavior without mutating process-global variables.struct ProcessEnv;impl EnvSource for ProcessEnv {    fn var(&self, key: &str) -> Option<String> {        env::var(key).ok()    }}/// Identifies the CA bundle selected for a client and the policy decision that selected it.////// This is the concrete output of the environment-precedence logic. Callers use `source_env` for/// logging and diagnostics, while `path` is the bundle that will actually be loaded.struct ConfiguredCaBundle {    /// The environment variable that won the precedence check for this bundle.    source_env: &'static str,    /// The filesystem path that should be read as PEM certificate input.    path: PathBuf,}impl ConfiguredCaBundle {    /// Loads certificates from this selected CA bundle.    ///    /// The bundle already represents the output of environment-precedence selection, so this is    /// the natural point where the file-loading phase begins. The method owns the high-level    /// success/failure logs for that phase and keeps the source env and path together for lower-    /// level parsing and error shaping.    fn load_certificates(        &self,    ) -> Result<Vec<CertificateDer<'static>>, BuildCustomCaTransportError> {        match self.parse_certificates() {            Ok(certificates) => {                info!(                    source_env = self.source_env,                    ca_path = %self.path.display(),                    certificate_count = certificates.len(),                    "loaded certificates from custom CA bundle"                );                Ok(certificates)            }            Err(error) => {                warn!(                    source_env = self.source_env,                    ca_path = %self.path.display(),                    error = %error,                    "failed to load custom CA bundle"                );                Err(error)            }        }    }    /// Loads every certificate block from a PEM file intended for Codex CA overrides.    ///    /// This accepts a few common real-world variants so Codex behaves like other CA-aware tooling:    /// leading comments are preserved, `TRUSTED CERTIFICATE` labels are normalized to standard    /// certificate labels, and embedded CRLs are ignored when they are well-formed enough for the    /// section iterator to classify them.    fn parse_certificates(        &self,    ) -> Result<Vec<CertificateDer<'static>>, BuildCustomCaTransportError> {        let pem_data = self.read_pem_data()?;        let normalized_pem = NormalizedPem::from_pem_data(self.source_env, &self.path, &pem_data);        let mut certificates = Vec::new();        let mut logged_crl_presence = false;        for section_result in normalized_pem.sections() {            // Known limitation: if `rustls-pki-types` fails while parsing a malformed CRL section,            // that error is reported here before we can classify the block as ignorable. A bundle            // containing valid certificates plus a malformed `X509 CRL` therefore still fails to            // load today, even though well-formed CRLs are ignored.            let (section_kind, der) = match section_result {                Ok(section) => section,                Err(error) => return Err(self.pem_parse_error(&error)),            };            match section_kind {                SectionKind::Certificate => {                    // Standard CERTIFICATE blocks already decode to the exact DER bytes reqwest                    // wants. Only OpenSSL TRUSTED CERTIFICATE blocks need trimming to drop any                    // trailing X509_AUX trust metadata before registration.                    let cert_der = normalized_pem.certificate_der(&der).ok_or_else(|| {                        self.invalid_ca_file(                            "failed to extract certificate data from TRUSTED CERTIFICATE: invalid DER length",                        )                    })?;                    certificates.push(CertificateDer::from(cert_der.to_vec()));                }                SectionKind::Crl if !logged_crl_presence => {                    info!(                        source_env = self.source_env,                        ca_path = %self.path.display(),                        "ignoring X509 CRL entries found in custom CA bundle"                    );                    logged_crl_presence = true;                }                _ => {}            }        }        if certificates.is_empty() {            return Err(self.pem_parse_error(&pem::Error::NoItemsFound));        }        Ok(certificates)    }    /// Reads the CA bundle bytes while preserving the original filesystem error kind.    ///    /// The caller wants a user-facing error that includes the bundle path and remediation hint, but    /// higher-level surfaces still benefit from distinguishing "not found" from other I/O    /// failures. This helper keeps both pieces together.    fn read_pem_data(&self) -> Result<Vec<u8>, BuildCustomCaTransportError> {        fs::read(&self.path).map_err(|source| BuildCustomCaTransportError::ReadCaFile {            source_env: self.source_env,            path: self.path.clone(),            source,        })    }    /// Rewrites PEM parsing failures into user-facing configuration errors.    ///    /// The underlying parser knows whether the file was empty, malformed, or contained unsupported    /// PEM content, but callers need a message that also points them back to the relevant    /// environment variables and the expected remediation.    fn pem_parse_error(&self, error: &pem::Error) -> BuildCustomCaTransportError {        let detail = match error {            pem::Error::NoItemsFound => "no certificates found in PEM file".to_string(),            _ => format!("failed to parse PEM file: {error}"),        };        self.invalid_ca_file(detail)    }    /// Creates an invalid-CA error tied to this file path.    ///    /// Most parse-time failures in this module eventually collapse to "the configured CA bundle is    /// not usable", but the detailed reason still matters for operator debugging. Centralizing that    /// formatting keeps the path and hint text consistent across the different parser branches.    fn invalid_ca_file(&self, detail: impl std::fmt::Display) -> BuildCustomCaTransportError {        BuildCustomCaTransportError::InvalidCaFile {            source_env: self.source_env,            path: self.path.clone(),            detail: detail.to_string(),        }    }}/// The PEM text shape after OpenSSL compatibility normalization.////// `Standard` means the input already used ordinary PEM certificate labels. `TrustedCertificate`/// means the input used OpenSSL's `TRUSTED CERTIFICATE` labels, so callers must also be prepared/// to trim trailing `X509_AUX` bytes from decoded certificate sections.enum NormalizedPem {    /// PEM contents that already used ordinary `CERTIFICATE` labels.    Standard(String),    /// PEM contents rewritten from OpenSSL `TRUSTED CERTIFICATE` labels to `CERTIFICATE`.    TrustedCertificate(String),}impl NormalizedPem {    /// Normalizes PEM text from a CA bundle into the label shape this module expects.    ///    /// Codex only needs certificate DER bytes to seed `reqwest`'s root store, but operators may    /// point it at CA files that came from OpenSSL tooling rather than from a minimal certificate    /// bundle. OpenSSL's `TRUSTED CERTIFICATE` form is one such variant: it is still certificate    /// material, but it uses a different PEM label and may carry auxiliary trust metadata that    /// this crate does not consume. This constructor rewrites only the PEM labels so the mixed-    /// section parser can keep treating the file as certificate input. The rustls ecosystem does    /// not currently accept `TRUSTED CERTIFICATE` as a standard certificate label upstream, so    /// this remains a local compatibility shim rather than behavior delegated to    /// `rustls-pki-types`.    ///    /// See also:    /// - rustls/pemfile issue #52, closed as not planned, documenting that    ///   `BEGIN TRUSTED CERTIFICATE` blocks are ignored upstream:    ///   <https://github.com/rustls/pemfile/issues/52>    /// - OpenSSL `x509 -trustout`, which emits `TRUSTED CERTIFICATE` PEM blocks:    ///   <https://docs.openssl.org/master/man1/openssl-x509/>    /// - OpenSSL PEM readers, which document that plain `PEM_read_bio_X509()` discards auxiliary    ///   trust settings:    ///   <https://docs.openssl.org/master/man3/PEM_read_bio_PrivateKey/>    /// - `openssl s_server`, a real OpenSSL-based server/test tool that operates in this    ///   ecosystem:    ///   <https://docs.openssl.org/master/man1/openssl-s_server/>    fn from_pem_data(source_env: &'static str, path: &Path, pem_data: &[u8]) -> Self {        let pem = String::from_utf8_lossy(pem_data);        if pem.contains("TRUSTED CERTIFICATE") {            info!(                source_env,                ca_path = %path.display(),                "normalizing OpenSSL TRUSTED CERTIFICATE labels in custom CA bundle"            );            Self::TrustedCertificate(                pem.replace("BEGIN TRUSTED CERTIFICATE", "BEGIN CERTIFICATE")                    .replace("END TRUSTED CERTIFICATE", "END CERTIFICATE"),            )        } else {            Self::Standard(pem.into_owned())        }    }    /// Returns the normalized PEM contents regardless of the label shape that produced them.    fn contents(&self) -> &str {        match self {            Self::Standard(contents) | Self::TrustedCertificate(contents) => contents,        }    }    /// Iterates over every recognized PEM section in this normalized PEM text.    ///    /// `rustls-pki-types` exposes mixed-section parsing through a `PemObject` implementation on the    /// `(SectionKind, Vec<u8>)` tuple. Keeping that type-directed API here lets callers iterate in    /// terms of normalized sections rather than trait plumbing.    fn sections(&self) -> impl Iterator<Item = Result<PemSection, pem::Error>> + '_ {        PemSection::pem_slice_iter(self.contents().as_bytes())    }    /// Returns the certificate DER bytes for one parsed PEM certificate section.    ///    /// Standard PEM certificates already decode to the exact DER bytes `reqwest` wants. OpenSSL    /// `TRUSTED CERTIFICATE` sections may append `X509_AUX` bytes after the certificate, so those    /// sections need to be trimmed down to their first DER object before registration.    fn certificate_der<'a>(&self, der: &'a [u8]) -> Option<&'a [u8]> {        match self {            Self::Standard(_) => Some(der),            Self::TrustedCertificate(_) => first_der_item(der),        }    }}/// Returns the first DER-encoded ASN.1 object in `der`, ignoring any trailing OpenSSL metadata.////// A PEM `CERTIFICATE` block usually decodes to exactly one DER blob: the certificate itself./// OpenSSL's `TRUSTED CERTIFICATE` variant is different. It starts with that same certificate/// blob, but may append extra `X509_AUX` bytes after it to describe OpenSSL-specific trust/// settings. `reqwest::Certificate::from_der` only understands the certificate object, not those/// trailing OpenSSL extensions.////// This helper therefore asks a narrower question than "is this a valid certificate?": where does/// the first top-level DER object end? If that boundary can be found, the caller keeps only that/// prefix and discards the trailing trust metadata. If it cannot be found, the input is treated as/// malformed CA data.fn first_der_item(der: &[u8]) -> Option<&[u8]> {    der_item_length(der).map(|length| &der[..length])}/// Returns the byte length of the first DER item in `der`.////// DER is a binary encoding for ASN.1 objects. Each object begins with:////// - a tag byte describing what kind of object follows/// - one or more length bytes describing how many content bytes belong to that object/// - the content bytes themselves////// For this module, the important fact is that a certificate is stored as one complete top-level/// DER object. Once we know that object's declared length, we know exactly where the certificate/// ends and where any trailing OpenSSL `X509_AUX` data begins.////// This helper intentionally parses only that outer length field. It does not validate the inner/// certificate structure, the meaning of the tag, or every nested ASN.1 value. That narrower scope/// is deliberate: the caller only needs a safe slice boundary for the leading certificate object/// before handing those bytes to `reqwest`, which performs the real certificate parsing.////// The implementation supports the DER length forms needed here:////// - short form, where the length is stored directly in the second byte/// - long form, where the second byte says how many following bytes make up the length value////// Indefinite lengths are rejected because DER does not permit them, and any declared length that/// would run past the end of the input is treated as malformed.fn der_item_length(der: &[u8]) -> Option<usize> {    let &length_octet = der.get(1)?;    if length_octet & 0x80 == 0 {        return Some(2 + usize::from(length_octet)).filter(|length| *length <= der.len());    }    let length_octets = usize::from(length_octet & 0x7f);    if length_octets == 0 {        return None;    }    let length_start = 2usize;    let length_end = length_start.checked_add(length_octets)?;    let length_bytes = der.get(length_start..length_end)?;    let mut content_length = 0usize;    for &byte in length_bytes {        content_length = content_length            .checked_mul(256)?            .checked_add(usize::from(byte))?;    }    length_end        .checked_add(content_length)        .filter(|length| *length <= der.len())}#[cfg(test)]mod tests {    use std::collections::HashMap;    use std::fs;    use std::path::PathBuf;    use pretty_assertions::assert_eq;    use tempfile::TempDir;    use super::BuildCustomCaTransportError;    use super::CODEX_CA_CERT_ENV;    use super::EnvSource;    use super::SSL_CERT_FILE_ENV;    use super::maybe_build_rustls_client_config_with_env;    const TEST_CERT: &str = include_str!("../tests/fixtures/test-ca.pem");    struct MapEnv {        values: HashMap<String, String>,    }    impl EnvSource for MapEnv {        fn var(&self, key: &str) -> Option<String> {            self.values.get(key).cloned()        }    }    fn map_env(pairs: &[(&str, &str)]) -> MapEnv {        MapEnv {            values: pairs                .iter()                .map(|(key, value)| ((*key).to_string(), (*value).to_string()))                .collect(),        }    }    fn write_cert_file(temp_dir: &TempDir, name: &str, contents: &str) -> PathBuf {        let path = temp_dir.path().join(name);        fs::write(&path, contents).unwrap_or_else(|error| {            panic!("write cert fixture failed for {}: {error}", path.display())        });        path    }    #[test]    fn ca_path_prefers_codex_env() {        let env = map_env(&[            (CODEX_CA_CERT_ENV, "/tmp/codex.pem"),            (SSL_CERT_FILE_ENV, "/tmp/fallback.pem"),        ]);        assert_eq!(            env.configured_ca_bundle().map(|bundle| bundle.path),            Some(PathBuf::from("/tmp/codex.pem"))        );    }    #[test]    fn ca_path_falls_back_to_ssl_cert_file() {        let env = map_env(&[(SSL_CERT_FILE_ENV, "/tmp/fallback.pem")]);        assert_eq!(            env.configured_ca_bundle().map(|bundle| bundle.path),            Some(PathBuf::from("/tmp/fallback.pem"))        );    }    #[test]    fn ca_path_ignores_empty_values() {        let env = map_env(&[            (CODEX_CA_CERT_ENV, ""),            (SSL_CERT_FILE_ENV, "/tmp/fallback.pem"),        ]);        assert_eq!(            env.configured_ca_bundle().map(|bundle| bundle.path),            Some(PathBuf::from("/tmp/fallback.pem"))        );    }    #[test]    fn rustls_config_uses_custom_ca_bundle_when_configured() {        let temp_dir = TempDir::new().expect("tempdir");        let cert_path = write_cert_file(&temp_dir, "ca.pem", TEST_CERT);        let env = map_env(&[(CODEX_CA_CERT_ENV, cert_path.to_string_lossy().as_ref())]);        let config = maybe_build_rustls_client_config_with_env(&env)            .expect("rustls config")            .expect("custom CA config should be present");        assert!(config.enable_sni);    }    #[test]    fn rustls_config_reports_invalid_ca_file() {        let temp_dir = TempDir::new().expect("tempdir");        let cert_path = write_cert_file(&temp_dir, "empty.pem", "");        let env = map_env(&[(CODEX_CA_CERT_ENV, cert_path.to_string_lossy().as_ref())]);        let error = maybe_build_rustls_client_config_with_env(&env).expect_err("invalid CA");        assert!(matches!(            error,            BuildCustomCaTransportError::InvalidCaFile { .. }        ));    }}