diff options
Diffstat (limited to 'internal/proxy/proxy.go')
| -rw-r--r-- | internal/proxy/proxy.go | 43 |
1 files changed, 38 insertions, 5 deletions
diff --git a/internal/proxy/proxy.go b/internal/proxy/proxy.go index aec724a..bc22bcf 100644 --- a/internal/proxy/proxy.go +++ b/internal/proxy/proxy.go @@ -39,6 +39,7 @@ import ( "golang.org/x/net/http2" "mitmux/internal/ca" + "mitmux/internal/clientcert" "mitmux/internal/rules" "mitmux/internal/scope" "mitmux/internal/store" @@ -228,7 +229,7 @@ func (s *Server) handleConnect(w http.ResponseWriter, r *http.Request) { client.SetDeadline(time.Time{}) dial := func(ctx context.Context) (net.Conn, string, error) { - return dialUpstreamTLS(ctx, hostPort, hostname, s.UpstreamProxy) + return dialUpstreamTLS(ctx, hostPort, hostname, s.UpstreamProxy, s.clientCertFor(hostname)) } handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { s.forward(dial, "https", hostname, w, r) @@ -256,16 +257,22 @@ func (s *Server) handleConnect(w http.ResponseWriter, r *http.Request) { // HTTP/2 and HTTP/1.1 over ALPN and letting the server pick. Chaining // through another proxy is transparent to everything from here on: once // the CONNECT tunnel is up, TLS and the request/response on top of it -// look identical to a direct connection. -func dialUpstreamTLS(ctx context.Context, hostPort, sni, upstreamProxy string) (net.Conn, string, error) { +// look identical to a direct connection. cert, if non-nil, is presented +// during the handshake for servers that require mutual TLS - see +// clientCertFor. +func dialUpstreamTLS(ctx context.Context, hostPort, sni, upstreamProxy string, cert *tls.Certificate) (net.Conn, string, error) { raw, err := dialViaProxy(ctx, hostPort, upstreamProxy) if err != nil { return nil, "", err } - conn := tls.Client(raw, &tls.Config{ + cfg := &tls.Config{ ServerName: sni, NextProtos: []string{http2.NextProtoTLS, "http/1.1"}, - }) + } + if cert != nil { + cfg.Certificates = []tls.Certificate{*cert} + } + conn := tls.Client(raw, cfg) if err := conn.HandshakeContext(ctx); err != nil { raw.Close() return nil, "", err @@ -273,6 +280,32 @@ func dialUpstreamTLS(ctx context.Context, hostPort, sni, upstreamProxy string) ( return conn, conn.ConnectionState().NegotiatedProtocol, nil } +// clientCertFor returns the client certificate configured for host, if +// any - see clientcert.FindFor. Errors (a bad DB read, an unparseable +// PEM pair) are logged and treated as "no certificate configured" rather +// than failing the connection outright: a broken client-cert config +// shouldn't take down otherwise-working proxying for that host. +func (s *Server) clientCertFor(host string) *tls.Certificate { + if s.store == nil { + return nil + } + certs, err := s.store.ListClientCerts() + if err != nil { + log.Printf("list client certs: %v", err) + return nil + } + c := clientcert.FindFor(certs, host) + if c == nil { + return nil + } + tc, err := c.TLSCertificate() + if err != nil { + log.Printf("client cert %q: %v", c.Name, err) + return nil + } + return &tc +} + // dialUpstreamPlain connects to a plain (non-TLS) upstream for the // non-CONNECT proxy path, which is always HTTP/1.1. Unlike the TLS/ // CONNECT path, chaining here means dialing the upstream proxy's own |