50 lines
3.1 KiB
Markdown
50 lines
3.1 KiB
Markdown
# PortalCam for macOS (`mac2`)
|
|||
|
|
|
||
|
|
Native macOS companion app (`PortalCam.app`) and CoreMediaIO Camera Extension (`CamExtension`) for streaming high-definition video and audio from Portal TV.
|
||
|
|
|
||
|
|
## Security Architecture
|
||
|
|
|
||
|
|
PortalCam uses a channel-bound, zero-knowledge authentication scheme to pair with Portal TV without sending secrets over the network or exposing connections to Man-in-the-Middle (MITM) proxies:
|
||
|
|
|
||
|
|
1. **Self-Signed ECDSA P-256 TLS**:
|
||
|
|
* Portal TV serves all endpoints over HTTPS via an on-device generated ECDSA NIST P-256 (`secp256r1`) certificate.
|
||
|
|
2. **Channel-Bound SRP-6a Handshake (RFC 5054)**:
|
||
|
|
* **Initiation**: User clicks **Pair** in the app. The app calls `POST https://<portal-ip>:5654/auth/srp/init` over an ephemeral TLS session.
|
||
|
|
* **Channel Binding**: The app extracts the presented leaf TLS certificate DER and computes:
|
||
|
|
$$\mathbf{tls\_hash} = \text{SHA256}(\text{cert\_der})$$
|
||
|
|
* **Proof $M_1$ Generation**: The user enters the 6-digit PIN displayed on the TV. The client computes:
|
||
|
|
$$M_1 = \text{SHA256}(\text{pad256}(A) \parallel \text{pad256}(B) \parallel K \parallel s \parallel \mathbf{tls\_hash})$$
|
||
|
|
and submits it to `POST https://<portal-ip>:5654/auth/srp/verify`.
|
||
|
|
* **Mutual Proof $M_2$ Verification**: The Portal TV verifies $M_1$ against its own certificate hash. If verified, it returns server proof $M_2$:
|
||
|
|
$$M_2 = \text{SHA256}(\text{pad256}(A) \parallel M_1 \parallel K \parallel \mathbf{tls\_hash})$$
|
||
|
|
along with a random 256-bit bearer token.
|
||
|
|
* **Client Verification**: The app verifies $M_2$ to confirm the Portal independently knows the shared secret before trusting the server.
|
||
|
|
|
||
|
|
### Why Active MITM Attacks Cannot Succeed
|
||
|
|
|
||
|
|
If an attacker deploys a rogue proxy:
|
||
|
|
1. The rogue proxy presents a substitute TLS certificate $C_{\text{rogue}}$ to the Mac.
|
||
|
|
2. The Mac computes $M_1$ using $\text{SHA256}(C_{\text{rogue}})$.
|
||
|
|
3. The Portal verifies $M_1$ using $\text{SHA256}(C_{\text{portal}})$.
|
||
|
|
4. Because the certificate fingerprints differ, $M_1$ verification fails on the Portal TV.
|
||
|
|
5. The attacker cannot forge $M_1$ or $M_2$ without knowing the ephemeral PIN, which is displayed solely on the physical TV screen and never leaves the room.
|
||
|
|
|
||
|
|
### Certificate Pinning & Keychain Storage
|
||
|
|
|
||
|
|
Once pairing succeeds:
|
||
|
|
* The 256-bit bearer token and the certificate's SHA-256 fingerprint are saved in the macOS Data Protection Keychain under access group `ENT9X9U544.com.kovtash.portalcam`:
|
||
|
|
* `portalAuthToken` — Bearer auth token
|
||
|
|
* `portalPinnedCertSha256` — Hex SHA-256 digest of the server's ECDSA certificate
|
||
|
|
* A fallback copy is mirrored into the shared App Group `UserDefaults` (`ENT9X9U544.com.kovtash.portalcam`) for seamless access by the sandboxed camera extension.
|
||
|
|
* `PortalPinnedSessionDelegate` evaluates all subsequent HTTPS requests (`/video.h264`, `/audio.aac`, `/control/*`). Any TLS certificate mismatch aborts the connection immediately.
|
||
|
|
|
||
|
|
## Building and Installing
|
||
|
|
|
||
|
|
```sh
|
||
|
|
cd mac2/PortalCam
|
||
|
|
xcodebuild -scheme PortalCam -configuration Debug build -derivedDataPath ./build
|
||
|
|
killall PortalCam 2>/dev/null || true
|
||
|
|
cp -R ./build/Build/Products/Debug/PortalCam.app /Applications/
|
||
|
|
open /Applications/PortalCam.app
|
||
|
|
```
|