srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/internal/ipc/server.go
diff options
context:
space:
mode:
authorsrdusr <[email protected]>2026-06-16 22:57:00 +0200
committersrdusr <[email protected]>2026-06-16 22:57:00 +0200
commit23c8ab359c2108654d57176e233d2c099b398f31 (patch)
tree3e2d1c66d987b4b771da69e67bd8a0c9ad2fc3db /internal/ipc/server.go
parent6114567258bcad0517a0d881168711aaacdba5d5 (diff)
downloadmitmux-23c8ab359c2108654d57176e233d2c099b398f31.tar.gz
mitmux-23c8ab359c2108654d57176e233d2c099b398f31.zip
Client (mutual-TLS) certificates
Adds internal/clientcert: a cert/key pair matched to hosts by the same substring-or-regex pattern model as scope.Rule, so mitmux can present a client certificate on an upstream TLS handshake that requires one - the previous behavior was a hard handshake failure with no way to authenticate. Wired into both places mitmux dials an https:// upstream over its own TLS client connection: proxy.go's handleConnect (live proxied traffic) and repeat.go's dialForRepeat (Repeater/Intruder resends), both through a new Server.clientCertFor(host) helper. Stored in a new client_certs table, mirroring the existing scope_rules persistence pattern. The TUI (`t` from history) is add-only like scope, for the same reason: delete and re-add covers changing anything, and it's a rarely-touched, low-cardinality list. The add form takes cert/key file paths and reads them once at save time - PEM content, not the path, is what's stored and later presented, so a cert keeps working even if the original file moves afterward. Verified live against a real mutual-TLS-requiring origin server: without a matching cert the handshake correctly fails; with one configured, the origin receives it and the request succeeds; toggling it off reproduces the failure, confirming the enable/disable path works end to end.
Diffstat (limited to 'internal/ipc/server.go')
-rw-r--r--internal/ipc/server.go47
1 files changed, 47 insertions, 0 deletions
diff --git a/internal/ipc/server.go b/internal/ipc/server.go
index c890a54..4db27b9 100644
--- a/internal/ipc/server.go
+++ b/internal/ipc/server.go
@@ -9,6 +9,7 @@ import (
"regexp"
"sync"
+ "mitmux/internal/clientcert"
"mitmux/internal/proxy"
"mitmux/internal/rules"
"mitmux/internal/scope"
@@ -347,6 +348,52 @@ func (s *Server) handleConn(conn net.Conn) {
}
enc.Encode(Response{Type: "scope_rules"})
+ case "clientcert_list":
+ certs, err := s.db.ListClientCerts()
+ if err != nil {
+ enc.Encode(Response{Type: "error", Error: err.Error()})
+ continue
+ }
+ enc.Encode(Response{Type: "client_certs", ClientCerts: certs})
+
+ case "clientcert_add":
+ if req.ClientCert == nil {
+ enc.Encode(Response{Type: "error", Error: "clientcert_add: missing certificate"})
+ continue
+ }
+ cert := *req.ClientCert
+ if cert.IsRegex {
+ if _, err := regexp.Compile(cert.Pattern); err != nil {
+ enc.Encode(Response{Type: "error", Error: "invalid regex: " + err.Error()})
+ continue
+ }
+ }
+ if _, err := cert.TLSCertificate(); err != nil {
+ enc.Encode(Response{Type: "error", Error: err.Error()})
+ continue
+ }
+ id, err := s.db.AddClientCert(cert)
+ if err != nil {
+ enc.Encode(Response{Type: "error", Error: err.Error()})
+ continue
+ }
+ cert.ID = id
+ enc.Encode(Response{Type: "client_certs", ClientCerts: []clientcert.Cert{cert}})
+
+ case "clientcert_delete":
+ if err := s.db.DeleteClientCert(req.ClientCertID); err != nil {
+ enc.Encode(Response{Type: "error", Error: err.Error()})
+ continue
+ }
+ enc.Encode(Response{Type: "client_certs"})
+
+ case "clientcert_toggle":
+ if err := s.db.SetClientCertEnabled(req.ClientCertID, req.RuleEnabled); err != nil {
+ enc.Encode(Response{Type: "error", Error: err.Error()})
+ continue
+ }
+ enc.Encode(Response{Type: "client_certs"})
+
case "subscribe":
sub := s.hub.subscribe()
defer s.hub.unsubscribe(sub)