Skip to content

Commit 162ed26

Browse files
Retire fix-macos.md; fold findings into THREAT_MODEL (#67)
fix-macos.md's implementation plan is complete: - Steps 1-6 (Path 2: AES-GCM-wrapped .handle + keychain-held wrapping key) are shipped in PR #65. - Steps 7-8 (Path 1: entitled SecKeyCreateRandomKey with kSecAttrTokenIDSecureEnclave) are blocked on a provisioning profile, not deferred work. The required keychain-access-groups entitlement is AMFI-restricted and unavailable to Homebrew / cargo-install distribution, so these steps will not land under the current distribution model. The still-useful content from the plan doc has been migrated into THREAT_MODEL's macOS platform-specific notes: - Full prompt-behavior matrix (ad-hoc vs self-signed vs trusted-cert, first run / rebuild / different path) - Deny / Always Allow / upgrade-transition behavior - -34018 finding explaining why the legacy keychain is used - Explicit note that the entitled SE path is blocked on provisioning fix-macos.md is deleted. Co-authored-by: Jay Gowdy <jay@gowdy.me>
1 parent 9489c3e commit 162ed26

2 files changed

Lines changed: 19 additions & 291 deletions

File tree

THREAT_MODEL.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,25 @@ The cache header (version, magic, timestamps, risk level) in `crates/enclaveapp-
162162

163163
## Platform-specific notes
164164

165-
**macOS Keychain prompts:** On unsigned builds, the Keychain prompts once per binary hash change (e.g., after `brew upgrade`). Signed builds avoid this. This is the Keychain enforcing its ACL, not a vulnerability.
165+
**macOS Keychain prompts.** The Keychain scopes access to `kSecClassGenericPassword` items by the calling binary's code-signing identity. The prompt behavior is load-bearing for the wrapping-key threat model (items above): it is exactly what blocks a different binary from reading the wrapping key.
166+
167+
Observed behavior by signing scenario:
168+
169+
| Scenario | First run | Rebuild at same path | Different path |
170+
|----------|-----------|----------------------|----------------|
171+
| Ad-hoc signed (`swiftc` / `rustc` default, Homebrew source builds) | no prompt | **prompt** (code hash changed) | **prompt** |
172+
| Untrusted self-signed cert | no prompt | **prompt** (code hash changed) | **prompt** |
173+
| Trusted signing identity (Apple Development / Developer ID) | no prompt | no prompt (identity unchanged) | **prompt** |
174+
175+
Additional behavior:
176+
- "Deny" is not permanent: operations fail with `errSecUserCanceled` (`-128`), but the next invocation prompts again. The user is never locked out.
177+
- "Always Allow" persists until the binary is replaced. After `brew upgrade`, one new prompt appears on first use of the upgraded binary.
178+
- Ad-hoc → trusted-cert transition: one prompt on the first signed run; after "Always Allow," subsequent runs with the same identity are silent.
179+
- Trusting a self-signed cert requires a `security add-trusted-cert` system password dialog and cannot be automated silently in a Homebrew formula.
180+
181+
**Data Protection Keychain not used.** `kSecUseDataProtectionKeychain: true` fails with `errSecMissingEntitlement` (`-34018`) on unsigned / ad-hoc-signed builds. The implementation uses the legacy (file-based) login keychain with `kSecAttrAccessibleWhenUnlockedThisDeviceOnly`.
182+
183+
**Entitled Secure Enclave path not enabled.** Storing SE keys directly as Keychain items via `SecKeyCreateRandomKey` + `kSecAttrTokenIDSecureEnclave` + `kSecAttrIsPermanent: true` would remove the `.handle` file entirely. It requires `keychain-access-groups`, which is an AMFI-restricted entitlement that needs a provisioning profile — even a valid Apple Development cert without a matching profile causes AMFI to kill the binary with error `-413`. This path is only viable for App Store / Enterprise / Xcode-provisioned distribution and is not available for Homebrew or `cargo install` builds. The current Path-2 implementation (AES-GCM-wrapped `.handle` + Keychain-held wrapping key) is the hardening target for unsigned distribution.
166184

167185
**WSL bridge:** Communicates over stdin/stdout of a child process. The client (`crates/enclaveapp-bridge/src/client.rs`) discovers the bridge only from a fixed-path list under `/mnt/c/Program Files/<app>/` and `/mnt/c/ProgramData/<app>/`. Replacing the binary at those paths requires Windows admin rights. The previous `which`-based PATH fallback was removed — a user-writable `$PATH` entry on the WSL side could otherwise substitute a malicious bridge binary. Request/response size is capped at 64 KB, the child is reaped via `BridgeSession::Drop`, and reads are bounded. There is still no Authenticode / `WinVerifyTrust` check on the resolved bridge binary; adding one is a tracked hardening gap for environments where the Windows host itself is semi-trusted.
168186

fix-macos.md

Lines changed: 0 additions & 290 deletions
This file was deleted.

0 commit comments

Comments
 (0)