Files
codex/codex-rs/http-client/src/client_tls.rs
T
efrazer-oai f496b09bd1 Retry proxy CONNECT challenges with Windows authentication (#56000)
## Why

HTTPS requests and WebSocket connections on Windows need to authenticate when a proxy rejects `CONNECT` with status `407`.

## What changed

- Retry proxy challenges through the existing Windows authentication helper, using the signed-in user's Negotiate or NTLM credentials. Treat an origin `407` as a normal HTTP response.
- Bridge authenticated tunnels to the HTTP client through destination-specific named pipes, retaining redirect checks and TLS backend fallback without caching those tunnels across destinations.
- Carry explicit CA certificates and client identities into HTTPS proxy TLS configuration, and apply the configured connect timeout when opening authenticated HTTP tunnels.

GitOrigin-RevId: ec7d9168972a7a640350e5348cfca40307390fbf
2026-10-21 22:26:16 +00:00

114 lines
3.9 KiB
Rust

//! Validated TLS material for strict, route-aware HTTP clients.
//! Explicit roots replace shared environment roots; PEM identities require HTTPS and rustls.
use std::fmt;
#[cfg(windows)]
use std::io;
#[cfg(windows)]
use std::sync::Arc;
#[cfg(windows)]
use codex_utils_rustls_provider::ensure_rustls_crypto_provider;
#[cfg(windows)]
use rustls::ClientConfig;
#[cfg(windows)]
use rustls::RootCertStore;
#[cfg(windows)]
use rustls_pki_types::CertificateDer;
#[cfg(windows)]
use rustls_pki_types::PrivateKeyDer;
#[cfg(windows)]
use rustls_pki_types::pem::PemObject;
use crate::HttpError;
#[cfg(windows)]
use crate::WindowsProxyTls;
/// TLS configuration consumed by [`crate::HttpClientBuilder::build_with_tls`].
/// Keeping this separate from the builder prevents combining it with legacy fallback APIs.
#[derive(Clone, Default)]
pub struct HttpClientTlsConfig {
pub(crate) root_certificate: Option<TlsMaterial<reqwest::Certificate>>,
pub(crate) client_identity: Option<TlsMaterial<reqwest::Identity>>,
}
#[derive(Clone)]
pub(crate) struct TlsMaterial<T> {
pub(crate) reqwest: T,
// Reqwest does not expose its TLS material to the Windows proxy connector.
#[cfg(windows)]
pem: Arc<[u8]>,
}
impl<T> TlsMaterial<T> {
fn from_pem(
pem: &[u8],
parse: impl FnOnce(&[u8]) -> Result<T, reqwest::Error>,
) -> Result<Self, HttpError> {
Ok(Self {
reqwest: parse(pem)?,
#[cfg(windows)]
pem: pem.into(),
})
}
}
impl fmt::Debug for HttpClientTlsConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("HttpClientTlsConfig")
.field("explicit_root", &self.root_certificate.is_some())
.field("client_identity", &self.client_identity.is_some())
.finish()
}
}
impl HttpClientTlsConfig {
/// Trusts this PEM certificate instead of system or environment-provided roots.
pub fn with_root_certificate_pem(mut self, pem: &[u8]) -> Result<Self, HttpError> {
self.root_certificate = Some(TlsMaterial::from_pem(pem, reqwest::Certificate::from_pem)?);
Ok(self)
}
/// Installs a PEM certificate chain and private key and requires HTTPS requests.
pub fn with_client_identity_pem(mut self, pem: &[u8]) -> Result<Self, HttpError> {
self.client_identity = Some(TlsMaterial::from_pem(pem, reqwest::Identity::from_pem)?);
Ok(self)
}
#[cfg(windows)]
pub(crate) fn windows_proxy_tls(&self) -> io::Result<WindowsProxyTls> {
if self.root_certificate.is_none() && self.client_identity.is_none() {
return Ok(WindowsProxyTls::Default);
}
let roots = if let Some(root) = &self.root_certificate {
let mut roots = RootCertStore::empty();
for certificate in CertificateDer::pem_slice_iter(&root.pem) {
roots
.add(certificate.map_err(io::Error::other)?)
.map_err(io::Error::other)?;
}
roots
} else {
crate::custom_ca::build_rustls_root_store_with_custom_ca().map_err(io::Error::other)?
};
ensure_rustls_crypto_provider();
let builder = ClientConfig::builder().with_root_certificates(roots);
let config = if let Some(identity) = &self.client_identity {
let certificates = CertificateDer::pem_slice_iter(&identity.pem)
.collect::<Result<Vec<_>, _>>()
.map_err(io::Error::other)?;
let key = PrivateKeyDer::pem_slice_iter(&identity.pem)
.last()
.transpose()
.map_err(io::Error::other)?
.ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "missing private key"))?;
builder
.with_client_auth_cert(certificates, key)
.map_err(io::Error::other)?
} else {
builder.with_no_client_auth()
};
Ok(WindowsProxyTls::Custom(Arc::new(config)))
}
}