srdusr
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--PLAN.md58
-rw-r--r--README.md22
-rw-r--r--internal/proxy/certdownload_test.go24
-rw-r--r--internal/proxy/proxy.go39
4 files changed, 142 insertions, 1 deletions
diff --git a/PLAN.md b/PLAN.md
index f113bd8..0d1d52e 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -378,3 +378,61 @@ Skipped deliberately (from the research, matches this tool's stated
scope): active/passive vulnerability scanning, plugin marketplace,
Collaborator/OAST, team collaboration, CI integration, client TLS
(mutual-TLS) certs, invisible/non-proxy-aware proxying.
+
+## Licensing, packaging, and browser/mobile support
+
+Licensed GPL-3.0 (see `LICENSE`) - deliberate for a security tool
+specifically: keeps derivatives open, a common and well-regarded choice
+in that community, and doesn't foreclose the author dual-licensing the
+code commercially later (remains available as sole copyright holder,
+independent of the public license) or relicensing outright in the
+future (unconstrained for as long as the codebase has no outside
+contributors - the harder case only starts once other people's
+copyrighted changes are in it).
+
+Added a `-version` flag to both binaries (`internal/version`, set via
+`-ldflags` at build time, defaulting to "dev" otherwise) and a
+`Makefile` (`make build`/`make test`/`make release`/`make install`).
+Cross-platform support turned out to already be ~95% there by accident
+of earlier choices - pure-Go SQLite (no CGO), `os.UserConfigDir()`
+instead of a hardcoded XDG path, nothing Linux-specific anywhere in the
+codebase - so making it explicit was verification and packaging work,
+not a rewrite: `make release` was run for real and produced correctly-
+formatted binaries (confirmed with `file`, not just an exit code) for
+linux/darwin/windows/freebsd across amd64+arm64 where applicable, all
+`CGO_ENABLED=0`. Linux remains the only platform actually run during
+development, though - macOS/Windows/FreeBSD compile clean and pass `go
+vet` but haven't touched real hardware, documented honestly as such in
+the README's Platforms section rather than as a tested claim.
+
+CA certificate distribution for browsers/mobile: `mitmuxd` now
+recognizes the magic hostname `mitmux.cert` on its plain-HTTP proxy
+path and serves its own CA certificate as a download
+(`internal/proxy/proxy.go`'s `serveCACert`/`isCertDownloadHost`) -
+`http://mitmux.cert/` from any client already configured to proxy
+through mitmux gets the cert with `Content-Type:
+application/x-x509-ca-cert`, which triggers iOS/Android's native
+"install this certificate" prompt directly. Same idea as mitmproxy's
+own `http://mitm.it/`, arrived at independently rather than reusing
+their domain - `.cert` isn't a registered TLD, so it can never collide
+with a real site. Deliberately HTTP-only: fetching it over HTTPS would
+require the client to already trust mitmux's CA to MITM that
+connection, the exact chicken-and-egg problem this exists to solve.
+This matters most for mobile devices, which otherwise have no
+convenient way to get a certificate file onto the device at all short
+of emailing it to yourself or similar. Verified live: fetched
+`http://mitmux.cert/` through a real running proxy, confirmed the
+downloaded bytes are byte-identical to the actual `ca.pem` on disk,
+confirmed a request with an explicit port and path still matches,
+confirmed normal proxying to an unrelated host is unaffected, and
+confirmed the cert-download request itself never gets recorded to
+history (it's answered before `record()` is ever reached).
+
+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 - mitmux is a normal forward proxy
+speaking the standard protocol, nothing proxy-switcher-specific to
+support. No code needed here, just documented clearly in the README's
+Quick start (previously this was implied but never actually spelled
+out for a phone/tablet setup, which is a real, common daily workflow
+this tool hadn't explicitly walked through before).
diff --git a/README.md b/README.md
index 393b0bc..dfa9f87 100644
--- a/README.md
+++ b/README.md
@@ -144,13 +144,33 @@ shorter `-socket /tmp/mitmux.sock` (and the matching `-socket` to
trust store itself. Installing a root CA is a system-wide trust
change, so you run the printed command yourself.
+ For a phone, tablet, or any other device where copying a file over
+ and importing it manually is awkward - the actual common case for
+ mobile testing - point the device's browser at
+ **`http://mitmux.cert/`** once it's configured to proxy through
+ mitmux (step 3). mitmuxd recognizes that hostname specifically and
+ serves its own CA certificate as a download, the same trick
+ [mitmproxy's `mitm.it`](https://mitm.it) uses: no DNS lookup, no
+ real domain, works the moment traffic is flowing through the proxy
+ at all - the browser's install-certificate prompt handles the rest.
+ Plain `http://`, not `https://`: fetching it over TLS would need the
+ device to already trust mitmux's CA to intercept that very
+ connection, which is the exact problem this page solves.
+
3. **Point a client at the proxy.** e.g.:
```sh
curl -x http://127.0.0.1:8080 --cacert ~/.config/mitmux/ca.pem https://example.com/
```
- Or configure your browser's proxy settings to `127.0.0.1:8080`.
+ Or configure your browser's proxy settings to `127.0.0.1:8080` -
+ directly, or via a proxy-switcher extension like
+ [FoxyProxy](https://getfoxyproxy.org/): add a new proxy profile
+ pointing at `127.0.0.1:8080` (HTTP, and reused for HTTPS - mitmux
+ handles both on the same listener), no mitmux-specific setup needed
+ since it's a standard forward proxy speaking the same protocol Burp/
+ ZAP/Caido all do. Same story for a phone or tablet: set its Wi-Fi
+ proxy to your machine's LAN address and the daemon's port.
4. **Open the TUI** (in another terminal - the daemon keeps running
independently):
diff --git a/internal/proxy/certdownload_test.go b/internal/proxy/certdownload_test.go
new file mode 100644
index 0000000..aae5f48
--- /dev/null
+++ b/internal/proxy/certdownload_test.go
@@ -0,0 +1,24 @@
+package proxy
+
+import "testing"
+
+func TestIsCertDownloadHost(t *testing.T) {
+ tests := []struct {
+ host string
+ want bool
+ }{
+ {"mitmux.cert", true},
+ {"mitmux.cert:80", true},
+ {"MITMUX.CERT", true},
+ {"Mitmux.Cert:8080", true},
+ {"example.com", false},
+ {"notmitmux.cert", false},
+ {"mitmux.cert.evil.com", false},
+ {"", false},
+ }
+ for _, tt := range tests {
+ if got := isCertDownloadHost(tt.host); got != tt.want {
+ t.Errorf("isCertDownloadHost(%q) = %v, want %v", tt.host, got, tt.want)
+ }
+ }
+}
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)