From bcf7d2e79d702b2c3867818e3b07410761d556d8 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 17 Dec 2025 13:53:35 +0100 Subject: [PATCH 1/2] Allow no client auth --- src/lib.rs | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d0a703d..f8300c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -204,7 +204,7 @@ impl ProxyServer { None, // context )?; - let input_data = compute_report_input(&cert_chain, exporter)?; + let input_data = compute_report_input(Some(&cert_chain), exporter)?; // Get the TLS certficate chain of the client, if there is one let remote_cert_chain = connection.peer_certificates().map(|c| c.to_owned()); @@ -234,10 +234,7 @@ impl ProxyServer { // If we expect an attestaion from the client, verify it and get measurements let measurements = if attestation_verifier.has_remote_attestion() { - let remote_input_data = compute_report_input( - &remote_cert_chain.ok_or(ProxyError::NoClientAuth)?, - exporter, - )?; + let remote_input_data = compute_report_input(remote_cert_chain.as_deref(), exporter)?; attestation_verifier .verify_attestation(remote_attestation_message, remote_input_data) @@ -620,7 +617,7 @@ impl ProxyClient { .ok_or(ProxyError::NoCertificate)? .to_owned(); - let remote_input_data = compute_report_input(&remote_cert_chain, exporter)?; + let remote_input_data = compute_report_input(Some(&remote_cert_chain), exporter)?; // Read a length prefixed attestation from the proxy-server let mut length_bytes = [0; 4]; @@ -640,8 +637,7 @@ impl ProxyClient { // If we are in a CVM, provide an attestation let attestation = if attestation_generator.attestation_type != AttestationType::None { - let local_input_data = - compute_report_input(&cert_chain.ok_or(ProxyError::NoClientAuth)?, exporter)?; + let local_input_data = compute_report_input(cert_chain.as_deref(), exporter)?; attestation_generator .generate_attestation(local_input_data) .await? @@ -731,7 +727,7 @@ async fn get_tls_cert_with_config( let remote_attestation_message = AttestationExchangeMessage::decode(&mut &buf[..])?; - let remote_input_data = compute_report_input(&remote_cert_chain, exporter)?; + let remote_input_data = compute_report_input(Some(&remote_cert_chain), exporter)?; let _measurements = attestation_verifier .verify_attestation(remote_attestation_message, remote_input_data) @@ -743,12 +739,14 @@ async fn get_tls_cert_with_config( /// Given a certificate chain and an exporter (session key material), build the quote input value /// SHA256(pki) || exporter pub fn compute_report_input( - cert_chain: &[CertificateDer<'_>], + cert_chain: Option<&[CertificateDer<'_>]>, exporter: [u8; 32], ) -> Result<[u8; 64], AttestationError> { let mut quote_input = [0u8; 64]; - let pki_hash = get_pki_hash_from_certificate_chain(cert_chain)?; - quote_input[..32].copy_from_slice(&pki_hash); + if let Some(cert_chain) = cert_chain { + let pki_hash = get_pki_hash_from_certificate_chain(cert_chain)?; + quote_input[..32].copy_from_slice(&pki_hash); + } quote_input[32..].copy_from_slice(&exporter); Ok(quote_input) } From 3d1ca62c7ae74c28b2d8a19f83bb58878758fe51 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 17 Dec 2025 14:03:07 +0100 Subject: [PATCH 2/2] Remove check enforcing client auth with client attestation --- src/lib.rs | 83 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index f8300c3..5fbf859 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,10 +90,6 @@ impl ProxyServer { attestation_verifier: AttestationVerifier, client_auth: bool, ) -> Result { - if attestation_verifier.has_remote_attestion() && !client_auth { - return Err(ProxyError::NoClientAuth); - } - let mut server_config = if client_auth { let root_store = RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned()); @@ -349,12 +345,6 @@ impl ProxyClient { attestation_verifier: AttestationVerifier, remote_certificate: Option>, ) -> Result { - // If we will provide attestation, we must also use client auth - if attestation_generator.attestation_type != AttestationType::None && cert_and_key.is_none() - { - return Err(ProxyError::NoClientAuth); - } - // If a remote CA cert was given, use it as the root store, otherwise use webpki_roots let root_store = match remote_certificate { Some(remote_certificate) => { @@ -768,8 +758,6 @@ fn get_pki_hash_from_certificate_chain( /// An error when running a proxy client or server #[derive(Error, Debug)] pub enum ProxyError { - #[error("Client auth is required when the client is running in a CVM")] - NoClientAuth, #[error("Failed to get server ceritifcate")] NoCertificate, #[error("TLS: {0}")] @@ -1009,6 +997,77 @@ mod tests { assert_eq!(measurements, mock_dcap_measurements()); } + // Server has no attestation, client has mock DCAP but no client auth + #[tokio::test] + async fn http_proxy_client_attestation_no_client_auth() { + let target_addr = example_http_service().await; + + let (server_cert_chain, server_private_key) = + generate_certificate_chain("127.0.0.1".parse().unwrap()); + let (server_config, client_config) = + generate_tls_config(server_cert_chain.clone(), server_private_key); + + let proxy_server = ProxyServer::new_with_tls_config( + server_cert_chain, + server_config, + "127.0.0.1:0", + target_addr, + AttestationGenerator::with_no_attestation(), + AttestationVerifier::mock(), + ) + .await + .unwrap(); + + let proxy_addr = proxy_server.local_addr().unwrap(); + + tokio::spawn(async move { + // Accept one connection, then finish + proxy_server.accept().await.unwrap(); + }); + + let proxy_client = ProxyClient::new_with_tls_config( + client_config, + "127.0.0.1:0", + proxy_addr.to_string(), + AttestationGenerator::new_not_dummy(AttestationType::DcapTdx).unwrap(), + AttestationVerifier::expect_none(), + None, + ) + .await + .unwrap(); + + let proxy_client_addr = proxy_client.local_addr().unwrap(); + + tokio::spawn(async move { + // Accept two connections, then finish + proxy_client.accept().await.unwrap(); + proxy_client.accept().await.unwrap(); + }); + + let res = reqwest::get(format!("http://{}", proxy_client_addr.to_string())) + .await + .unwrap(); + + // We expect no measurements from the server + let headers = res.headers(); + assert!(headers.get(MEASUREMENT_HEADER).is_none()); + + let attestation_type = headers + .get(ATTESTATION_TYPE_HEADER) + .unwrap() + .to_str() + .unwrap(); + assert_eq!(attestation_type, AttestationType::None.as_str()); + + let res_body = res.text().await.unwrap(); + + // The response body shows us what was in the request header (as the test http server + // handler puts them there) + let measurements = + MultiMeasurements::from_header_format(&res_body, AttestationType::DcapTdx).unwrap(); + assert_eq!(measurements, mock_dcap_measurements()); + } + // Server has mock DCAP, client has mock DCAP and client auth #[tokio::test] async fn http_proxy_mutual_attestation() {