Time Of Check Time Of Use in Actix with Mutual Tls
Time Of Check Time Of Use in Actix with Mutual Tls — how this specific combination creates or exposes the vulnerability
Time Of Check Time Of Use (TOCTOU) occurs when the outcome of a security check depends on the timing between the check and the subsequent use of a resource. In Actix with Mutual TLS, this can manifest when authorization checks are performed on certificate metadata (such as subject or SAN) before the application uses the peer identity to route or process a request. Because TLS handshakes validate the client certificate once per connection, an attacker can change the effective identity used after the check if the application does not re-validate or bind the certificate state to the request context. This gap between check and use can allow elevation or confused deputy scenarios, especially when access control decisions are based on certificate claims that are not re-verified at the point of use.
Mutual TLS adds authentication of both client and server, but it does not automatically prevent TOCTOU. The certificate is typically inspected once during the handshake, and the application may cache the principal or roles derived from certificate fields. If the application later uses those cached values without ensuring the certificate remains valid and bound to the request (for example, after connection pooling, multiplexing, or when delegating to internal services), it may act on stale or elevated identity information. This is particularly relevant in Actix-web where handlers often rely on extractor patterns that pull identity from request extensions set during an earlier middleware check.
Consider an API where a middleware verifies that the presented client certificate contains an email SAN matching an allowed list, then stores the email in request extensions for downstream handlers to authorize access to specific resources. If downstream handlers assume the stored email cannot change for the lifetime of the request, but an attacker can influence routing or handler selection (for example, through path-based handler dispatch or feature toggles), they might exploit ordering or branching logic to use a different logical identity at the point of access. The check (middleware) and the use (handler) are not atomic with respect to identity binding, creating a TOCTOU window.
In distributed contexts, TOCTOU can be amplified when Actix services call other services using the same client certificate. The calling service may check scopes or roles from its local cache of certificate claims before issuing a request, while the remote service re-validates the certificate. If the local cache is not tightly bound to the request lifecycle and can be influenced by untrusted inputs (such as query parameters or headers that affect which certificate is selected from a pool), the remote call may execute with a mismatched authorization context, effectively bypassing intended access controls.
Real-world analogs include scenarios where authorization is tied to certificate-based RBAC or ABAC attributes without per-request re-verification. OWASP API Security Top 10 references authorization flaws that arise from trusting identity claims without ensuring integrity and freshness at the point of use. PCI-DSS and SOC2 controls similarly require that access decisions be enforced consistently at the time of access, not just at connection establishment. Mitigating TOCTOU in Actix with Mutual TLS therefore requires binding certificate-derived identity to the request lifecycle and revalidating critical attributes at the moment of use.
Mutual Tls-Specific Remediation in Actix — concrete code fixes
To mitigate TOCTOU in Actix with Mutual TLS, tie certificate-derived identity directly to the request and re-validate or re-extract critical attributes at the point of use. Avoid caching authorization decisions in mutable global or static state, and prefer extractor patterns that recompute or re-verify identity within the handler or via tightly scoped request extensions.
Example: enforce that the client certificate is verified and bound to the request in middleware, then pass a minimal, signed representation to handlers while still validating required attributes immediately before use.
use actix_web::{web, App, HttpServer, HttpRequest, middleware::Logger};
use actix_web::http::header::HeaderValue;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
fn config_ssl() -> SslAcceptor {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder.set_private_key_file("key.pem", SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file("cert.pem").unwrap();
builder.set_verify(openssl::ssl::SslVerifyMode::PEER | openssl::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT,
|_, _| true // custom verification can add additional checks here
);
builder.build()
}
// Middleware that extracts and binds certificate identity to the request
async fn require_mtls_identity(req: HttpRequest, payload: web::Payload) -> Result {
// In practice, use actix_web::middleware::from_fn with proper error handling
// This snippet illustrates the concept
let peer_cert = req.connection_info().peer_certificate().map(|p| p.to_vec());
match peer_cert {
Some(cert) => {
// Perform checks here (e.g., SAN, CN, OCSP) and store minimal claims
// Bind identity to request extensions for this request only
let req = req.into_inner();
// Example: store claims in extensions
let (req, payload) = actix_web::dev::ServiceRequest::from_parts(req, payload);
// extensions.insert(CertificateIdentity::from_cert(&cert)?);
Ok(req)
}
None => Err(actix_web::error::ErrorUnauthorized("Client certificate required")),
}
}
// Handler that re-validates critical attributes before use
async fn sensitive_operation(req: HttpRequest) -> String {
// Re-extract or re-verify attributes required for this operation
// Do not rely on values set earlier in middleware unless they are immutable and bound to this request
let allowed_email = match get_email_from_cert(&req) {
Ok(email) if is_allowed_email(&email) => email,
_ => return String::from("Forbidden"),
};
format!("Processing for {}", allowed_email)
}
fn get_email_from_cert(req: &HttpRequest) -> Result {
// Parse certificate from request extensions or connection info
// Return the email claim if present and valid
Ok(String::from("user@example.com"))
}
fn is_allowed_email(email: &str) -> bool {
// Check against allowlist or policy
email.ends_with("@example.com")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(move || {
App::new()
.wrap(Logger::default())
.route("/sensitive", web::get().to(sensitive_operation))
})
.bind_openssl("127.0.0.1:8443", config_ssl())?
.run()
.await
}
Key remediation points:
- Bind identity to the request lifecycle: store certificate-derived claims in request extensions rather than global caches.
- Re-validate critical attributes at the point of use inside handlers, especially when authorization depends on time-sensitive or mutable properties (e.g., revocation, scope changes).
- Minimize the data carried forward from middleware checks; pass only what is necessary and verify it again before performing security-sensitive operations.
- Use immutable request-local data where possible, and avoid mutating identity information after the initial check.
- For inter-service calls, re-verify the certificate on the server side rather than relying on the client’s cached authorization decision.
These practices reduce the TOCTOU window by ensuring authorization checks occur close to the action and that identity assertions are verified immediately before use.