Commit 1c92657
authored
feat(desktop): OS trust store and mTLS client certs for native HTTP (#1225)
* feat(desktop): OS trust store and mTLS client certs for native HTTP
The desktop app's native Rust HTTP client (guarded_http_client) trusted only
the bundled Mozilla roots and could not present a client certificate, so remote
fetches (tile/URL resolution, OGC GetCapabilities) to enterprise endpoints
behind a private CA or mutual TLS failed, while the WebView fetch path succeeded
via the OS trust store and an interactive cert prompt.
- Trust the OS/system certificate store in addition to the bundled roots
(rustls-tls-native-roots), so enterprise-CA-signed servers work with no
configuration.
- Present a client certificate for mutual TLS when configured via environment
variables: GEOLIBRE_HTTP_CLIENT_CERT (PEM or PKCS#12), an optional
GEOLIBRE_HTTP_CLIENT_CERT_PASSWORD, and GEOLIBRE_HTTP_CA_CERT for a private CA.
PEM identities use the default rustls backend; PKCS#12 (with passphrase)
switches that one client to the platform native-tls backend.
The interactive OS certificate prompt the WebView shows is not reachable from
reqwest, so the native path uses this config-based mechanism instead.
Verified end to end against a local mTLS server: both the PEM (rustls) and
PKCS#12-with-passphrase (native-tls) client-cert paths complete the handshake,
a client without the cert is rejected, and a wrong passphrase fails to load.
Refs #1220
* perf(desktop): cache the guarded HTTP client; error on stray mTLS passphrase
Address review feedback on the native mTLS change:
- Build the SSRF-guarded HTTP client once and cache it behind a OnceLock instead
of rebuilding it (re-reading and re-parsing the CA bundle and client
certificate from disk) on every native fetch. Callers now set their own
per-request deadline with RequestBuilder::timeout(), so the shared client keeps
its connection pool and parsed TLS material across fetch and URL-resolve calls.
- Surface GEOLIBRE_HTTP_CLIENT_CERT_PASSWORD set without GEOLIBRE_HTTP_CLIENT_CERT
as a clear error rather than silently discarding the passphrase.
- Note in guarded_http_client that the SSRF guard is applied independent of the
TLS backend, so it holds on the native-tls (PKCS#12) path too.
Adds a unit test for the stray-passphrase guard.
* fix(desktop): treat an empty mTLS passphrase as unset
Env interpolation in Docker/K8s/.env tooling (e.g. PASSWORD=${SECRET:-})
commonly yields an empty string rather than leaving the variable unset. An empty
GEOLIBRE_HTTP_CLIENT_CERT_PASSWORD now counts as no passphrase, so it neither
forces the PKCS#12 code path for a PEM certificate nor trips the
stray-passphrase error.
* fix(desktop): treat empty cert paths as unset; error on non-UTF-8 passphrase
More review follow-ups on the native mTLS config:
- Filter set-but-empty GEOLIBRE_HTTP_CA_CERT and GEOLIBRE_HTTP_CLIENT_CERT the
same way as the passphrase, so ${SECRET:-}-style empty values are treated as
unset instead of read as the path "" (which would fail on fs::read and, since
the guarded client is cached, permanently break every native fetch).
- Surface a non-UTF-8 GEOLIBRE_HTTP_CLIENT_CERT_PASSWORD as a clear error rather
than silently dropping it, since the PKCS#12 loader takes a &str passphrase.
- Document that the configured client certificate is held on the shared native
HTTP client and is presented to any host that requests one during the TLS
handshake, so it should only be configured when the app's hosts are trusted.1 parent c4617c7 commit 1c92657
4 files changed
Lines changed: 383 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | | - | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
43 | 46 | | |
44 | 47 | | |
45 | 48 | | |
| |||
0 commit comments