-
Type:
Bug
-
Resolution: Unresolved
-
Priority:
Trivial - P5
-
None
-
Affects Version/s: None
-
Component/s: None
-
Monguard
-
ALL
-
None
-
None
-
None
-
None
-
None
-
None
-
None
Summary
An empty or non-certificate CA file quietly turns off client certificate checks instead of failing, but only the operator can put that file there.
Impact
Client certificate verification and identity propagation are silently disabled despite the operator configuring a CA; because PEER (without FAIL_IF_NO_PEER_CERT) already admits cert-less clients at :214, the practical attacker-facing delta is negligible — the harm is operational (operator believes mTLS is on, legitimate clients' subject_dn is no longer forwarded to the upstream).
Severity: low
Exploitability: theoretical — Requires an operator-side mistake (empty/non-PEM CA file, or a truncation/rotation race observed at startup or SIGHUP); a remote attacker cannot induce the condition, and even when fired cert-less clients were already accepted, so no new attacker capability is gained.
Location
- monguard/src/tls/handshake/openssl/mod.rs:75 in X509PkiCtx::new
- monguard/src/tls/handshake/openssl/mod.rs:204 in apply_pki
- monguard/src/tls/handshake/openssl/mod.rs:218 in apply_pki
- monguard/src/main.rs:267 in Monguard::run (startup PKI build)
- monguard/src/main.rs:279 in reload callback
- monguard/src/config/resolver.rs:496 in resolve_tls_config
Reproduction / trigger path
Analyzed trigger path from static analysis — not an executed PoC. Confirm with a concrete repro before handing off.
Attacker controls: Operator/deployment tooling controls the bytes at the configured ca path. A remote network client controls nothing about this branch.
- (source) monguard/src/config/raw.rs:258 in RawTlsConfig.ca — Operator sets listeners.tls_config.ca: <path> in YAML
- (hop) monguard/src/config/resolver.rs:489 in resolve_tls_config — resolve_required_file confirms the path is an existing file (no content check)
- (hop) monguard/src/config/resolver.rs:496 in resolve_tls_config — X509PkiCtx::new called for upfront validation — succeeds with empty ca_certs, so no config error is raised
- (hop) monguard/src/main.rs:267 in run — Startup builds X509PkiCtx from entry.ca; same on hot-reload at :279
- (hop) monguard/src/tls/handshake/openssl/mod.rs:73 in X509PkiCtx::new — std::fs::read reads CA file bytes from disk
- (hop) monguard/src/tls/handshake/openssl/mod.rs:75 in X509PkiCtx::new — X509::stack_from_pem returns Ok(empty Vec) when no PEM blocks; no emptiness guard
- (hop) monguard/src/tls/handshake/openssl/mod.rs:172 in certificate_callback — Per-handshake callback loads pki snapshot and calls apply_pki
- (sink) monguard/src/tls/handshake/openssl/mod.rs:218 in apply_pki — ca_certs.is_empty() → else branch → ssl.set_verify(SslVerifyMode::NONE); no client cert requested or verified
Root cause
- monguard/src/tls/handshake/openssl/mod.rs:75 — when ca_file is Some, X509::stack_from_pem on the file bytes returns Ok(Vec::new()) for input with no PEM blocks; the result is assigned to ca_certs with no `.is_empty()` guard (contrast cert_chain at :58 which bails on empty).
- monguard/src/tls/handshake/openssl/mod.rs:100 — the possibly-empty ca_certs is stored on X509PkiCtx without further validation.
- monguard/src/tls/handshake/openssl/mod.rs:204-218 — apply_pki branches solely on ca_certs.is_empty(); the configured-but-empty case is indistinguishable from 'no CA configured' and takes the else branch that calls ssl.set_verify(SslVerifyMode::NONE).
- monguard/src/config/resolver.rs:496 — the config-time validation also calls X509PkiCtx::new and therefore inherits the same blind spot, so neither startup nor hot-reload (main.rs:264, main.rs:279) reports an error.
Filed from Aegis finding 96bb8ce60b36 (scan scan-3c7821728fe9) · primitive: Configured-but-certless CA bundle yields an empty trust store and falls through to the no-verification branch instead of erroring. · categories: Certificate Validation Bypass, Other