diff options
| author | srdusr <[email protected]> | 2026-06-05 23:30:00 +0200 |
|---|---|---|
| committer | srdusr <[email protected]> | 2026-06-05 23:30:00 +0200 |
| commit | f3558f18ecabba983aba001468b06394471e6d8b (patch) | |
| tree | 49bc1c3843945e4d98a85e9129f34b4739a081b9 /internal/proxy/proxy.go | |
| parent | ec42c6652eec8030b3710a804d02ff954d253720 (diff) | |
| download | mitmux-f3558f18ecabba983aba001468b06394471e6d8b.tar.gz mitmux-f3558f18ecabba983aba001468b06394471e6d8b.zip | |
Serve the CA certificate for browsers/mobile at http://mitmux.cert/
Browser/mobile setup previously meant "find ca.pem on disk and import
it manually" - awkward on a phone or tablet especially, which has no
convenient way to get a file onto the device at all short of emailing
it to yourself or similar. Any client already configured to proxy
through mitmux can now just visit http://mitmux.cert/ and get the cert
directly, with Content-Type: application/x-x509-ca-cert triggering
iOS/Android's native "install this certificate" prompt.
Same idea as mitmproxy's own http://mitm.it/, arrived at independently
rather than reusing their domain - mitmux.cert isn't a registered TLD,
so it can never collide with a real site someone meant to visit.
Deliberately HTTP-only: fetching it over HTTPS would require the client
to already trust mitmux's CA to MITM that very connection, the exact
chicken-and-egg problem this exists to solve, so it's not attempted on
the CONNECT/TLS path at all.
internal/proxy/proxy.go: isCertDownloadHost matches the hostname
case-insensitively regardless of port; handleHTTP checks it before
ever dialing upstream and answers directly via serveCACert, using the
CA's own CertPEM bytes already held in memory. Short-circuits before
record() is ever reached, so the download itself never pollutes
history.
Verified live: fetched http://mitmux.cert/ through a real running
proxy and confirmed the downloaded bytes are byte-identical to the
actual ca.pem on disk (diff, not just "the request succeeded");
confirmed a request with an explicit port and a path still matches;
confirmed normal proxying to an unrelated host is completely
unaffected; confirmed via direct SQLite query that the cert-download
requests never appear in history while a normal request in the same
session does.
Also documented (no code needed): any standard proxy-switcher extension
(FoxyProxy, etc.) or a phone/tablet's own Wi-Fi proxy setting already
works with mitmux exactly like it would with Burp/ZAP/Caido, since it's
a normal forward proxy speaking the standard protocol. This was true
before but never actually spelled out in the README for the phone/
tablet case specifically, which is a real, common daily workflow.
go build/vet/gofmt/test/mod tidy all clean.
Diffstat (limited to 'internal/proxy/proxy.go')
| -rw-r--r-- | internal/proxy/proxy.go | 39 |
1 files changed, 39 insertions, 0 deletions
diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index 2e80ed2..523ca01 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -31,6 +31,7 @@ import ( "net" "net/http" "net/url" + "strings" "sync" "time" @@ -611,6 +612,10 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, "mitmux: request must use absolute-form URI (configure as a proxy, not a target)", http.StatusBadRequest) return } + if isCertDownloadHost(r.URL.Host) { + s.serveCACert(w) + return + } host := r.URL.Host dial := func(ctx context.Context) (net.Conn, string, error) { return dialUpstreamPlain(ctx, host, s.UpstreamProxy) @@ -618,6 +623,40 @@ func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) { s.forward(dial, r.URL.Scheme, r.URL.Host, w, r) } +// certDownloadHost is a magic hostname mitmux intercepts and answers +// itself, serving its own CA certificate - reachable over plain HTTP +// from any client configured to use mitmux as its proxy, including a +// mobile browser, which otherwise has no easy way to get a file onto +// the device to trust as a CA at all. Deliberately not a real, +// resolvable domain (".cert" isn't a registered TLD) so it can never +// collide with an actual site someone meant to visit - the same idea +// as mitmproxy's own http://mitm.it/, arrived at independently rather +// than reusing their domain. HTTP only, on purpose: fetching this over +// HTTPS would require the client to already trust mitmux's CA to MITM +// that very connection - exactly the chicken-and-egg problem this page +// exists to solve, so intercepting it on the CONNECT/TLS path wouldn't +// make sense and isn't attempted. +const certDownloadHost = "mitmux.cert" + +func isCertDownloadHost(hostPort string) bool { + host := hostPort + if h, _, err := net.SplitHostPort(hostPort); err == nil { + host = h + } + return strings.EqualFold(host, certDownloadHost) +} + +// serveCACert answers with the CA certificate as a download. The +// content type (application/x-x509-ca-cert) is what makes iOS and +// Android offer to install it as a trusted certificate directly from +// the browser's download prompt, rather than just saving a plain file. +func (s *Server) serveCACert(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/x-x509-ca-cert") + w.Header().Set("Content-Disposition", `attachment; filename="mitmux-ca.pem"`) + w.WriteHeader(http.StatusOK) + w.Write(s.ca.CertPEM) +} + func stripHopByHop(h http.Header) { for _, k := range hopByHopHeaders { h.Del(k) |