Timing Attack in Axum with Mutual Tls
Timing Attack in Axum with Mutual Tls — how this specific combination creates or exposes the vulnerability
A timing attack in Axum with Mutual TLS (mTLS) arises when the server’s behavior depends on sensitive data—such as a token or password—execution time, even when the transport is encrypted. In mTLS, the client presents a certificate and the server validates it before application logic proceeds. If certificate validation or subsequent authorization checks are performed in a way that short-circuits on the first mismatch, an attacker can infer validity by measuring request latency.
Consider a scenario where an API endpoint accepts client certificates and also an API key or JWT contained in headers. If the server validates the certificate and then compares the provided key in a non-constant-time manner, the total response time will vary based on how early the comparison diverges. Because mTLS ensures channel identity, an attacker may not learn whether a certificate is valid, but they can still probe timing differences to learn about the presence or correctness of secondary authorization data. This is especially relevant when the same endpoint behaves differently depending on whether credentials are accepted early or late in the processing pipeline.
Axum does not prescribe a specific order for mTLS verification and business logic; how you wire your middleware determines the exposure. For example, extracting a token from headers and comparing it before checking certificate metadata can create a measurable difference compared to rejecting invalid certificates first. An attacker can send many requests and observe small timing drifts to infer which components fail, even when TLS terminates the channel. The attack does not require breaking encryption; it leverages observable differences in milliseconds that can be amplified with statistical analysis.
Real-world analogies include variations caused by early exit in conditional checks or branching on sensitive strings. In Axum, if your handler or a custom layer validates a JSON Web Token’s signature or claims after the TLS handshake completes but before rejecting obviously malformed inputs quickly, you introduce a gradient that can be exploited. Even with mTLS ensuring peer authenticity, supplementary checks must be designed to take constant time and avoid branching on secret material to mitigate timing-based inference.
To assess such risks, tools like middleBrick can scan an Axum service protected by mTLS, checking for inconsistent timing behavior across authenticated and unauthenticated paths. While middleBrick does not fix implementation details, its findings can highlight inconsistencies in how credentials are processed and guide developers toward safer patterns.
Mutual Tls-Specific Remediation in Axum — concrete code fixes
Remediation focuses on ensuring that validation work does not depend on secret or sensitive data and that processing paths remain consistent regardless of input correctness. In Axum, this means designing your middleware or handler logic so that certificate checks, token validation, and business rules execute in a uniform sequence with constant-time comparisons where applicable.
First, structure your Axum application so that mTLS verification is handled by the server or reverse proxy (e.g., via hyper with rustls) and ensure Axum only receives requests with valid client certificates. This way, Axum logic does not need to re-validate certificate details, reducing branching based on sensitive validation outcomes.
When you must inspect credentials inside Axum, use constant-time comparisons for any secret-bearing data. For example, when comparing a provided API key or a JWT’s critical claim, avoid early returns based on string equality that short-circuits on the first mismatch. Instead, process all inputs and normalize outcomes before sending a response.
Below are concrete Axum code examples that demonstrate secure handling with mTLS. The first shows a minimal Axum server with TLS configured at the hyper layer, ensuring client certificates are verified before requests reach your routes. The second illustrates constant-time comparison logic within an Axum handler.
Example 1: Axum server with mTLS via hyper and rustls
use axum::{routing::get, Router};
use hyper::server::conn::http1;
use hyper::service::service_fn;
use hyper_util::rt::TokioIo;
use rustls::{Certificate, ServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use std::{fs::File, io::BufReader, sync::Arc};
use tokio::net::TcpListener;
async fn run_axum_with_mtls() -> Result<(), Box> {
let addr = "127.0.0.1:3000".parse()?;
let listener = TcpListener::bind(addr).await?;
// Load server certificate and private key
let cert_file = &mut BufReader::new(File::open("server.crt")?);
let key_file = &mut BufReader::new(File::open("server.key")?);
let cert_chain = certs(cert_file).collect::, _>>()?;
let mut keys = pkcs8_private_keys(key_file).collect::, _>>()?;
let mut server_config = ServerConfig::builder()
.with_no_client_auth() // For mTLS, use `with_client_auth_verifier`
.with_single_cert(cert_chain, keys.remove(0))?;
// Configure client authentication for mTLS:
// let client_ca = rustls::ClientCertVerifier::...;
// server_config.client_auth_verifier = Arc::new(client_ca);
let io = TokioIo::new(listener.accept().await?.1);
http1::Builder::new()
.serve_connection(io, service_fn(|_req| async { Ok::<_, std::convert::Infallible>(axum::response::Response::new(())) }))
.with_tls_config(Arc::new(server_config))?;
Ok(())
}
Example 2: Constant-time comparison inside an Axum handler
use axum::{Extension, Json};
use serde::Deserialize;
use subtle::ConstantTimeEq;
#[derive(Deserialize)]
struct ApiKeyInput {
key: String,
}
// Simulated stored key (in practice, use a secure secret store)
const STORED_API_KEY: &[u8] = b"supersecretapikey123456";
async fn validate_key(Json(payload): Json) -> &'static str {
let input = payload.key.as_bytes();
// Ensure lengths are checked in constant time to avoid branching
let lengths_eq = STORED_API_KEY.len().ct_eq(&input.len());
let key_eq = STORED_API_KEY.ct_eq(input);
let result = lengths_eq & key_eq;
if result.into() {
"authorized"
} else {
"unauthorized"
}
}
// In your router:
// let app = Router::new().route("/check", post(validate_key));
These examples illustrate how to align Axum with mTLS and secure coding practices. The first example shows server-side TLS configuration that can enforce mTLS, while the second demonstrates how to avoid timing leaks in credential checks using constant-time comparisons. By combining transport security with consistent validation logic, you reduce the risk of timing-based inference even in complex Axum services.