From f2f0a2135a202e3e15d2a8cbfbd791aad9b04f3a Mon Sep 17 00:00:00 2001 From: srdusr <99972264+srdusr@users.noreply.github.com> Date: Sat, 27 Jan 2024 21:47:00 +0200 Subject: TLS interception: per-host leaf certs, terminate-and-resign MITM, native HTTP/2 Implements build-order step 2. CA gains LeafFor(host), signing and caching per-host leaf certificates on demand. The proxy's CONNECT handler now terminates TLS with the client using a matching leaf cert instead of tunneling raw bytes, and forwards each request upstream over its own independently negotiated TLS connection. Client-side and upstream-side ALPN are negotiated separately rather than one being forced to mirror the other: an http.Transport configured via http2.ConfigureTransport auto-bridges HTTP/1.1 and HTTP/2 on each side independently, so e.g. an HTTP/1.1-only client reaching an HTTP/2-preferring origin still works instead of failing the handshake (caught by testing curl --http1.1 against example.com before this fix). Verified live: plain HTTP passthrough, HTTPS with default (H2) and forced HTTP/1.1 clients, and that requests without the mitmux CA trusted are correctly rejected. --- internal/ca/ca.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) (limited to 'internal/ca') diff --git a/internal/ca/ca.go b/internal/ca/ca.go index 949c2fd..fc227a5 100644 --- a/internal/ca/ca.go +++ b/internal/ca/ca.go @@ -1,20 +1,23 @@ -// Package ca manages mitmux's root CA: generating it on first run and -// loading it on subsequent runs. Leaf certificate signing for TLS -// interception is added in a later build step. +// Package ca manages mitmux's root CA: generating it on first run, +// loading it on subsequent runs, and signing per-host leaf certificates +// on demand for TLS interception. package ca import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "errors" "fmt" "math/big" + "net" "os" "path/filepath" + "sync" "time" ) @@ -30,6 +33,82 @@ type CA struct { Key *ecdsa.PrivateKey CertPEM []byte KeyPEM []byte + + leafMu sync.Mutex + leafCache map[string]*tls.Certificate +} + +// leafLifetime is kept well under the ~398 day limit modern browsers +// enforce on leaf certificates. +const leafLifetime = 300 * 24 * time.Hour + +// LeafFor returns a TLS certificate for host (a DNS name or IP address, +// no port), signed by the CA. Certificates are generated once and cached +// in memory for the life of the process. +func (c *CA) LeafFor(host string) (*tls.Certificate, error) { + c.leafMu.Lock() + defer c.leafMu.Unlock() + + if c.leafCache == nil { + c.leafCache = make(map[string]*tls.Certificate) + } + if cert, ok := c.leafCache[host]; ok { + return cert, nil + } + + cert, err := c.signLeaf(host) + if err != nil { + return nil, err + } + c.leafCache[host] = cert + return cert, nil +} + +func (c *CA) signLeaf(host string) (*tls.Certificate, error) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, err + } + + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return nil, err + } + + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{ + CommonName: host, + Organization: []string{"mitmux"}, + }, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(leafLifetime), + KeyUsage: x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + IsCA: false, + } + if ip := net.ParseIP(host); ip != nil { + tmpl.IPAddresses = []net.IP{ip} + } else { + tmpl.DNSNames = []string{host} + } + + der, err := x509.CreateCertificate(rand.Reader, tmpl, c.Cert, &key.PublicKey, c.Key) + if err != nil { + return nil, err + } + + leaf, err := x509.ParseCertificate(der) + if err != nil { + return nil, err + } + + return &tls.Certificate{ + Certificate: [][]byte{der, c.Cert.Raw}, + PrivateKey: key, + Leaf: leaf, + }, nil } // Dir returns the directory mitmux stores its CA material in -- cgit v1.2.3