[draft] mitm-df: outbound (TLS termination, fronts table, audit log)#263
Draft
garmr-ulfr wants to merge 1 commit into
Draft
[draft] mitm-df: outbound (TLS termination, fronts table, audit log)#263garmr-ulfr wants to merge 1 commit into
garmr-ulfr wants to merge 1 commit into
Conversation
Adds a TCP outbound that terminates the user's TLS handshake with a
leaf cert minted by a per-device, name-constrained CA, then re-encrypts
to a configured fronting target:
- DialContext returns the user side of an in-memory pipe (no listening
port); the proxy side runs tls.Server with a GetCertificate callback
driven by the actual ClientHello SNI.
- The signing CA (internal/mitmca) carries an RFC 5280 NameConstraints
extension (PermittedDNSDomainsCritical, MaxPathLen 0). Generated
per-device on first boot with the key persisted in PKCS#8 mode 0600
via atomic temp+rename. Even with the key in hand, an attacker
cannot mint leaves outside the configured permitted_domains.
- Fronts table maps user SNI -> fronted_sni / verify_san /
redirect_addr. A deny list short-circuits GetCertificate before any
signature is produced; every decision is recorded to a JSONL audit
log.
- Egress uses uTLS HelloCustom + ApplyPreset so the user's negotiated
ALPN is the only one offered to the front. Named uTLS presets bake
an ALPN extension into the replayed ClientHello that overrides
Config.NextProtos; HelloCustom is the only path that inherits
cleanly.
Unit tests cover the CA, fronts matcher, deny list, concurrent
audit-log writes, and uTLS preset mapping. The e2e suite spins up an
in-process fronting server and exercises the full pipeline plus a
deny-list-blocked variant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
myleshorton
added a commit
that referenced
this pull request
May 15, 2026
PR #263 (mitmfront-outbound) lands the actual outbound under protocol/mitmdf/ rather than internal/mitmdf/ — sibling to the existing protocol/ transports (algeneva, lanturn, samizdat, unbounded, water). Update the architecture doc to match: - Component diagram: MITM box now labeled protocol/mitmdf - Package layout block: mitmdf/ moves out from under internal/ to protocol/, with a one-line note explaining why mitmca stays under internal/ (private helper, consumed only by mitmdf) - Implementation-order list: the three mitmdf paths now reference protocol/mitmdf/... - Drop session.go from the layout (its responsibilities fold into outbound.go's serve goroutine — matches what #263 actually shipped) - Add utls_preset.go to the layout with a note about the HelloCustom + ApplyPreset ALPN-inheritance detail that PR #263 caught - Add test/e2e/mitmdf_test.go to the layout (#263 ships it; the previous draft of this doc deferred it) No code changes; doc-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Stacked on top of #262. Once #262 lands, this PR introduces the actual
mitm-dfoutbound that uses the foundation's name-constrained CA."mitm-df".DialContextreturns the user side of an in-memorynet.Pipe— no listening port.tls.ServerwithGetCertificatecallback driven by the actual ClientHello SNI (not the router-supplied destination), so a sniff/routing mismatch can't mint the wrong leaf. Callback runs: deny-list check → fronts lookup → audit record →mitmca.CA.SignLeaf(sni).fronted_sni/verify_san/ optionalredirect_addr. Match semantics: exact-or-strict-subdomain (vercel.appmatches*.vercel.appbut notnotvercel.apporvercel.app.evil.com).allow,deny,no-match,error) with{ts, sni, fronted_sni, decision, reason}. Emptyaudit_log_pathdisables.HelloCustom+ApplyPresetso the user's negotiated ALPN is the only one offered to the front. Named uTLS presets (HelloChrome_Auto, etc.) bake an ALPN extension into the replayed ClientHello that overridesConfig.NextProtos;HelloCustomis the only path that inherits cleanly. Without this, every fronted flow would leak[h2, http/1.1]regardless of the inner request shape — breaks h1-only flows likegooglevideo/alive.github.com.ca.cert_path/ca.key_pathif absent (delegates tomitmca.GenerateCA); reloaded on subsequent boots viamitmca.LoadCAwhich refuses certs withoutNameConstraints.Files
option/mitmdf.go—MITMDFOutboundOptions+ nestedMITMDFCAOptions/MITMDFFrontEntry.protocol/mitmdf/outbound.go— registration,NewOutbound,DialContext, theservegoroutine, theGetCertificatecallback, CA bootstrap,verifyFrontSANcallback for the egress.fronts.go—FrontEntryparsing, suffix-aware matcher, deny-list match.audit.go— append-only JSONL withsync.Mutex, nil-safe.utls_preset.go— preset map +HelloCustom/ApplyPresetALPN-rewrite helper.fronts_test.go,audit_test.go— unit coverage.test/e2e/mitmdf_test.go—TestMITMDFE2E(full pipeline through an in-process fronting server: leaf chains to local CA, captured egress SNI = front, captured egress ALPN =[h2], bytes round-trip, auditallow) +TestMITMDFDenyList(deny-list path: no mint, auditdeny).testdata/options/mitmdf_client.json— example config; passeslantern-box check.constant/proxy.go,protocol/register.go,go.mod— type constant, registration,utlspromoted to direct dep.Out of scope (follow-ups)
KeyStore(keystore_macos.go,keystore_android.go, …) — theKeyStoreinterface is already in place on the foundation branch; per-platform variants behind build tags.pinner.goper architecture doc).Test plan
go build ./...go vet ./...go test ./protocol/mitmdf/... ./internal/mitmca/...go test ./test/e2e/ -run TestMITMDF -vlantern-box check --config testdata/options/mitmdf_client.json— generates a CA at the configured paths withPermittedDNSDomainsCritical: true,CA:TRUE pathlen:0, key mode 0600.🤖 Generated with Claude Code