feat: encrypt the WiFi transport with cert-pinned mutual TLS 1.3 (#16) - #151
Open
gcobc12677 wants to merge 4 commits into
Open
feat: encrypt the WiFi transport with cert-pinned mutual TLS 1.3 (#16)#151gcobc12677 wants to merge 4 commits into
gcobc12677 wants to merge 4 commits into
Conversation
…tual TLS 1.3 Adds an authenticated, encrypted WiFi path so screen/input traffic is no longer sent in the clear on the local network (issue peetzweg#16). Trust model — no pairing code; the USB cable is the physical trust anchor: - First time a device is cabled, the Mac and iPad exchange self-signed P-256 identities over a loopback-only usbmux channel (:9010); the iPad shows a one-time confirmation with the key fingerprint. Both persist the peer's public key (SPKI) in the data-protection Keychain. - WiFi then uses mutual TLS 1.3 (:9001) with those pinned identities — no low-entropy secret, immune to offline attack, MITM-resistant. - The USB bootstrap is self-verifying: it runs once per USB session and the iPad decides (silent auto-accept on an exact pin match, prompt otherwise), so re-pairing works even when one side has forgotten the other. - Forget/reset revokes symmetrically over the wire and drops the live session. Transport is TLS-only over WiFi (no plaintext fallback); USB streaming stays plaintext on a loopback-bound port (physical channel). Discovery reuses the existing _opensidecar._tcp Bonjour type (no new type → no Local Network re-grant on upgrade). Adds apple/swift-certificates + swift-asn1. New: Shared/{TrustStore,TLSConfigurator}.swift, Mac/TrustBootstrapClient.swift. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses review of peetzweg#151. - USB no longer gated on the WiFi protocol floor. minSupportedPeer returns to 1 (USB is plaintext over a physical loopback channel, and iOS/Mac update independently, so a raised floor would strand working USB setups mid-rollout). WiFi stays cert-pinned-TLS-only by construction — a pre-TLS peer can't pair or establish a WiFi session regardless of the floor. Add minWiFiPeer (3) as an informational constant driving a soft "update to use Wi-Fi" hint, never a connection gate. - Rewrite code comments that referenced an internal design doc (removed SEV-N / plan §N / wifi-tls-pairing-plan pointers) to be self-contained; kept real references (peetzweg#16, peetzweg#52, peetzweg#132, TN3137). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review nits from peetzweg#151 (comment-only, no behavior change): - Document that the phone's wire identity is PhoneReceiver.installID (UserDefaults) and it deliberately never uses TrustStore.installID() (that Keychain-backed id is the caller's own self-id, read only by the Mac). - Refresh three "next milestone" comments that describe already-wired behavior (reinstall-cleanup flag and the Paired Macs UI are implemented on this branch). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1. autoConnect resolves the WiFi peer via the installIDByWifiName fallback (like connect()), so a paired device whose browse result lacks the TXT id still auto-reconnects over WiFi instead of being wrongly told to connect via USB (peetzweg#2). 2. resolvePeerID now reads the in-memory snapshot via a new peerID(forSPKI:) lookup instead of N+1 Keychain reads on the video queue; the snapshot carries (peerID, SPKI) pairs and allPinnedPeerSPKIs maps to the SPKIs the verify block already used (peetzweg#4). 3. Clear the stale "connect via USB once" caption when a USB bootstrap pins a device (peetzweg#5). 4. Document that a loopback (usbmux) connection intentionally preempts WiFi TLS and that on-device loopback callers are out of scope — comment-only, no behavior change (peetzweg#1/peetzweg#3). Co-Authored-By: Claude Opus 4.8 (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.
Encrypt the WiFi transport: USB-bootstrapped trust + cert-pinned mutual TLS 1.3
Addresses #16.
Motivation
Today WiFi mode streams H.264 in the clear on the local network and accepts any Mac that connects — anyone on the same Wi-Fi can passively record the screen or inject input. This PR makes WiFi private and authenticated before it's promoted to a first-class mode. USB stays point-to-point and unchanged.
What it does
:9010). The iPad shows a one-time confirmation displaying the key fingerprint (Signal-style grouped digits). On accept, each side pins the peer's public key (SPKI) in the data-protection Keychain.:9001) authenticated by those pinned identities. No low-entropy secret is ever on the wire, so there's nothing to brute-force; an unpinned Mac fails the handshake.Relationship to #16
The goal and acceptance criteria of #16 are met: WiFi is encrypted and authenticated, and a third machine on the LAN can neither view the stream nor connect.
The approach differs deliberately from the one sketched in the issue:
NWProtocolTLSwith a PSKTradeoff to flag: because pairing is USB-first, a device that is never cabled to a given Mac cannot pair over WiFi. If cable-less pairing is desired, a numeric-code path would need a real PAKE (e.g. SPAKE2) — happy to do it as a follow-up if you'd prefer to keep #16's original code-based flow.
Security properties
127.0.0.1(usbmux only, unreachable from the LAN) and needs no Local Network permission.installID+ SPKI match; a new or changed identity always requires on-screen confirmation.Threat model is the local network. A malicious app already running on the iPad can reach the loopback USB ports (
127.0.0.1:9000/:9010) — but on-device compromise is out of scope (such an app could screen-record by other means), and even then it can only DoS or spoof the local display, never read the Mac's screen or decrypt the WiFi session. The USB video channel stays plaintext by design: the cable + Apple's "Trust This Computer" is the physical trust boundary.Compatibility & rollout
:9000listener just stops advertising on the LAN. A version-mismatched (pre-TLS) peer still streams over USB — only WiFi requires the newer protocol._opensidecar._tcpBonjour type — no new service type is declared, so upgrading installs never need to re-grant Local Network permission (macOS snapshots declared Bonjour types at grant time). The TLS listener advertises on that type; the Mac dials the discovered endpoint (which resolves to the TLS port).Dependencies
Adds
apple/swift-certificatesandapple/swift-asn1(both targets) for minting/serializing the self-signed identities.New files
Shared/TrustStore.swift— identity generation, Keychain-backed pins, in-memory pin snapshot, fingerprint.Shared/TLSConfigurator.swift— pinned mutual-TLS 1.3NWProtocolTLS.Options.Mac/TrustBootstrapClient.swift— the USB:9010identity exchange.Testing
:9001confirms TLS application-data records (0x17 0x03 …) with no plaintext H.264/JSON on the wire; seamless USB ⇄ WiFi transport switching; Forget / Reset identity revoke and re-pair correctly (both sides, connected and disconnected) while routine re-plugs don't re-prompt.Note: on-device runtime was exercised on macOS 26 / iPadOS 26; the self-signed
SecIdentityformation path is compile-checked against the 14/17 SDK settings but hasn't been runtime-verified on 14/17 hardware.Not included
Signing.xcconfig) and any personal bundle identifier are intentionally kept out of this PR.🤖 Generated with Claude Code