Api Key Exposure in Actix with Mutual Tls
Api Key Exposure in Actix with Mutual TLS — how this specific combination creates or exposes the vulnerability
Actix Web is a popular Rust framework for building HTTP services. When mutual TLS (mTLS) is used, the server requests a client certificate to authenticate the connecting peer. Developers may assume that mTLS alone is sufficient to secure an endpoint and may neglect additional authorization checks, such as validating an API key or token carried in headers. This assumption creates a risk where an authenticated mTLS connection is treated as implicitly authorized to perform actions or access data it should not.
Consider an Actix handler that retrieves an API key from a request header for downstream routing or auditing but does not verify whether the caller is permitted to use that specific key. If mTLS ensures transport-level identity but the application conflates identity with authorization, an attacker who possesses a valid client certificate can enumerate or misuse keys they should not see. This often manifests in two ways: (1) keys are returned in responses or logs when they should be masked, and (2) keys are accepted from headers without verifying the caller’s entitlement to use them.
Another common pattern is binding an API key to a certificate fingerprint or subject without enforcing strict scope checks. If the Actix service uses the certificate to identify the client but does not cross-check the key against the certificate’s attributes, a compromised or improperly issued certificate can lead to exposure of keys belonging to other clients. This is especially risky when keys are included in error messages or debug endpoints, which may be inadvertently exposed in production builds.
The combination of mTLS and API keys should be treated as two independent security layers. mTLS provides identity assurance at the transport layer, while API keys represent application-level credentials. Leaking keys through logs, error responses, or inconsistent authorization logic undermines this separation. For example, an endpoint that echoes a key in a JSON response during a diagnostic call can expose the key to anyone who can present a valid certificate, which aligns with findings seen in improper secret handling (e.g., references to CVE patterns involving unintended data exposure).
middleBrick scans identify scenarios where authenticated endpoints return sensitive values or fail to enforce per-client authorization. The scanner checks whether responses inadvertently expose keys, whether authorization is applied consistently across methods, and whether certificate-bound identities are properly mapped to allowed operations. By testing unauthenticated attack surfaces and authenticated contexts where mTLS is enforced, it can surface misconfigurations where identity and authorization are incorrectly assumed to be equivalent.
Mutual TLS-Specific Remediation in Actix — concrete code fixes
To secure an Actix service using mTLS, treat client certificates as identity signals and enforce explicit authorization for every operation. Do not rely on the presence of a certificate to imply permission to access specific API keys or resources. Below are focused, practical corrections and code examples.
1. Enforce mTLS and map certificate fields to application roles
Configure Actix to require client certificates and extract identity details from the certificate. Use these fields for access control, not for granting implicit permissions to keys.
use actix_web::{web, App, HttpServer, Responder, HttpResponse};
use actix_web::dev::ServiceRequest;
use actix_web::middleware::Logger;
use std::sync::Arc;
use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod};
fn create_ssl_acceptor() -> 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_client_ca_file("ca.pem").unwrap();
builder.set_verify(openssl::ssl::SslVerifyMode::PEER | openssl::ssl::SslVerifyMode::FAIL_IF_NO_PEER_CERT, verify_callback);
builder.build()
}
fn verify_callback(
preverify_ok: bool,
ctx: &mut openssl::x509::X509StoreContextRef,
) -> bool {
// You can inspect certificate fields here if needed
preverify_ok
}
async fn index(req: ServiceRequest) -> impl Responder {
// Extract subject or other fields from peer certificate via request extensions
// This is illustrative; actual extraction depends on your middleware setup
HttpResponse::Ok().body("mTLS authenticated")
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let ssl = create_ssl_acceptor();
HttpServer::new(|| {
App::new()
.wrap(Logger::default())
.wrap(mtls_auth_middleware())
.route("/", web::get().to(index))
})
.bind_openssl("127.0.0.1:8443", ssl)?
.run()
.await
}
The above sets up mTLS with peer verification. The next step is to enforce authorization at the handler or via a guard.
2. Validate API keys independently and avoid exposing them
If your API uses API keys for downstream routing, validate them separately from identity and never return them in responses or logs. Use Actix extractors to inspect headers and ensure the caller is permitted to use the requested key.
use actix_web::{web, Error, HttpRequest, HttpResponse};
use actix_web::http::header::HeaderValue;
async fn proxied_service(
req: HttpRequest,
key: web::Header<ApiKey>
) -> Result<HttpResponse, Error> {
// Perform authorization: ensure this key is allowed for the authenticated principal
let principal = req.extensions().get::()
.ok_or_else(|| actix_web::error::ErrorForbidden("missing identity"))?;
if !principal.allowed_keys().contains(&key.0) {
return Ok(HttpResponse::Forbidden().body("key not authorized"));
}
// Forward request with key handled securely; do not echo key in responses
Ok(HttpResponse::Ok().body("operation authorized"))
}
struct ApiKey(String);
impl std::convert::FromRequest for ApiKey {
type Error = Error;
type Future = std::future::Ready<Result<Self, Self::Error>>;
fn from_request(req: &HttpRequest, _: &mut actix_web::dev::Payload) -> Self::Future {
let key = req.headers().get("X-API-Key")
.and_then(HeaderValue::to_str)
.map(|s| ApiKey(s.to_string()))
.unwrap_or_else(|| ApiKey(String::new()));
std::future::ready(Ok(key))
}
}
This pattern separates identity (from mTLS) from authorization (API key checks). It prevents accidental key exposure by ensuring keys are only used for routing decisions and never echoed back.
3. Centralize authorization and avoid per-handler inconsistencies
Apply a guard or middleware that checks mTLS identity against a permissions store and validates any provided API key before reaching business logic. This reduces the chance of forgetting checks in individual handlers.
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
use actix_web::Error;
use std::future::{ready, Ready};
pub struct AuthzMiddleware;
impl Transform<S, ServiceRequest> for AuthzMiddleware
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>
{
type Response = ServiceResponse<B>;
type Error = Error;
type InitError = ();
type Transform = AuthzMiddlewareImpl<S>;
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(AuthzMiddlewareImpl { service }))
}
}
pub struct AuthzMiddlewareImpl<S> {
service: S,
}
impl<S, B> Service<ServiceRequest> for AuthzMiddlewareImpl<S>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>
{
type Response = ServiceResponse<B>;
type Error = Error;
type Future = S::Future;
fn poll_ready(&self, cx: &mut std::task::Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
self.service.poll_ready(cx)
}
fn call(&self, req: ServiceRequest) -> Self::Future {
// Perform mTLS identity checks and API key authorization here
// Reject or transform request as needed
self.service.call(req)
}
}
With these changes, mTLS provides strong client authentication while explicit checks ensure API keys are accessed only by authorized principals. This reduces the likelihood of key exposure through logs, errors, or misconfigured permissions.