What macOS Keychain actually is, what's open vs closed, and the full set of options for giving NextBSD/gershwin a local secret store with Apple's SecItem API — from porting Apple's Security.framework wholesale, to wrapping gnome-keyring, to a clean-room secd-equivalent. Companion to the WiFi management, PAM port, and configd plans.
Security.framework) talking over Mach IPC to a launchd-managed daemon (securityd / secd) that owns the encrypted keychain files, with the hardware root of trust in the Secure Enclave (SEP) on modern Macs.corecrypto (restrictive license) and SEP firmware that is closed and hardware-specific, and carries decades of CDSA/iCloud baggage we don't want. It's a reference, not a vendorable codebase.SecItem (and legacy SecKeychain) C API, so Apple-derived apps and our own daemons (wifid-fbsd, configd, a future mail/browser) have somewhere safe to put passwords and keys.secd-equivalent. A launchd-managed Mach/DO daemon implementing the SecItem facade over an encrypted file store, using base crypto (libcrypto / libsodium), optionally TPM2-backed. Per the shipped system's convention it keeps Apple's own name — launchd job label and Mach service both com.apple.securityd (modern alt com.apple.secd), exactly like the running com.apple.configd / com.apple.notifyd — see §5.1. Reuses the project's existing launchd + CoreFoundation + Mach substrate. Estimated ~2–3 person-months for a usable per-user subset; more for full ACL/access-group fidelity.Security.framework port (closed crypto + SEP + enormous); wrapping gnome-keyring/libsecret (drags in D-Bus, LGPL, and a non-Apple API shape); KWallet (Qt/KDE weight); pass/gpg-agent (no daemon API, CLI-shaped); plaintext Network.plist (status quo, no protection).Network.plist (the WiFi plan's Q2). This plan exists so the option space is settled and we don't relitigate.Four layers, only the top two of which are app-visible:
| Layer | Role | launchd? |
|---|---|---|
Security.framework (library) |
The in-process API apps link against: modern SecItem* (SecItemAdd/CopyMatching/Update/Delete) and legacy SecKeychain*. Does no storage itself — marshals every request over Mach IPC to the daemon. |
No — loaded into each client process |
securityd (legacy) / secd (modern data-protection keychain) / trustd |
The daemon that owns the keychain files, holds unlocked keys in memory, enforces access control, and (on modern Macs) talks to the SEP. trustd handles certificate trust evaluation (a related but separable job). |
Yes — launchd jobs advertising Mach service names; started on demand when a client does a bootstrap lookup |
| Keychain files | On-disk encrypted databases. Legacy: ~/Library/Keychains/login.keychain-db (per-user, unlocked by login password) and /Library/Keychains/System.keychain (system-wide, e.g. WiFi). Modern: SQLite-backed data-protection keychain. |
n/a |
| Secure Enclave (SEP) | Hardware coprocessor (T2 / Apple Silicon) that wraps the keychain's master keys and gates them on biometrics/passcode. The actual root of trust on modern hardware. | n/a — closed firmware |
Critically for us, the daemon half is a textbook launchd-on-demand Mach service, the same pattern as configd / notifyd: a .plist declares a Mach service name, the library does a bootstrap_look_up, launchd starts the daemon on first use. That maps directly onto the infrastructure the launchd and libxpc/DO work already provides.
Each item has a class (kSecClassGenericPassword, kSecClassInternetPassword, kSecClassCertificate, kSecClassKey, kSecClassIdentity), a set of attributes (account, service, server, protocol, label, …) used as search keys, an encrypted secret payload, and an access-control policy (which apps may read it, whether unlock/biometric is required). WiFi passwords, for example, are kSecClassGenericPassword / kSecClassInternetPassword items in the System keychain. Replicating this data model is the real work — the crypto is the easy part.
| Component | Status | Notes |
|---|---|---|
| Security | Source published | The framework: SecItem / SecKeychain APIs, on-disk format logic, ACL logic. Does not build standalone — Apple's own README notes missing internal headers since ~2016. |
| securityd | Source published | The daemon + SecurityTool. Mach/MIG IPC server. Same build caveat. |
corecrypto |
Effectively closed | Apple's FIPS-validated crypto core. Source is published but under a restrictive license that bars free reuse — not vendorable. Security.framework's crypto bottoms out here. |
| Secure Enclave firmware | Closed | Hardware root of trust. Unportable by definition; FreeBSD has no SEP. |
| iCloud Keychain (sync/escrow server) | Closed | The CloudKit sync, SOS circle, and escrow mechanisms. We don't want this anyway. |
CommonCrypto, libDER, CoreFoundation |
Open / available | CommonCrypto is a thin API (can sit on libcrypto); libDER is open; CoreFoundation we already have. |
corecrypto and the SEP — are exactly the parts you cannot take. So "port Apple's keychain" really means "port Apple's keychain data model and API, and supply your own crypto + (optional) hardware backing." Once that's accepted, a from-scratch daemon stops looking so different from a port.| Feature | Need it? | Why |
|---|---|---|
Local secret storage + SecItem CRUD | Yes | The whole point — give apps/daemons a place to keep secrets behind a known API. |
| Per-user keychain unlocked by login password | Yes | Matches user expectation; integrates with the PAM port for unlock-at-login. |
| System keychain (for WiFi etc.) | Yes | wifid-fbsd needs a root-owned store for network creds available before login. |
| Per-app / access-group access control | Partial | A simplified uid/path-based policy is enough initially; full code-signing-identity ACLs are a later fidelity bump. |
| iCloud Keychain sync | No | No Apple-ID infra; out of scope permanently. |
| Secure Enclave / biometric gating | No | No SEP hardware. TPM2 is the only analog and it's optional (see §6). |
Certificate trust evaluation (trustd) | No (separate) | Different problem; the base system / OpenSSL already do TLS trust. Don't conflate. |
CDSA / legacy SecKeychain full fidelity | Shim only | Provide thin legacy shims mapping onto the modern store; don't reimplement CDSA. |
Seven options, from most-Apple to least-effort. Verdicts summarized in §4.8.
Security.framework + securityd wholesaleVendor the published source, reconstruct the missing build glue, swap corecrypto for libcrypto, stub the SEP.
SecItem/SecKeychain caller works unchanged; closest to "it's literally macOS."corecrypto is unusable and threaded throughout; SEP paths must be torn out; decades of CDSA/iCloud/code-signing code comes along for the ride; deep CoreFoundation + Security-Transforms + MIG coupling. Realistically a 6–12+ person-month slog with permanent maintenance of a non-buildable upstream. You'd end up rewriting the crypto and storage layers anyway — i.e. most of option B — while carrying all of Apple's legacy.Rejected Highest fidelity, worst effort/maintenance. The closed crypto + SEP mean you can't actually ship Apple's security guarantees regardless.
secd-equivalent recommendedWrite a new launchd-managed Mach/DO daemon (com.apple.securityd) exposing the public SecItem API, backed by our own encrypted store and base crypto. Implement the published API contract, not Apple's implementation.
SecItem attribute vocabulary carefully for compat; no hardware backing by default.Chosen Best balance. Detail in §5.
gnome-keyring / libsecret (freedesktop Secret Service)Run gnome-keyring-daemon as the storage backend; put a SecItem→Secret-Service translation shim in our Security-shim library.
Fallback only Reasonable if a working store is needed fast and D-Bus is already present; otherwise the dependency cost outweighs the saved daemon work.
Rejected Strictly worse fit than C for this project.
pass / gpg-agent backendRejected Wrong shape — can't present SecItem semantics.
p11-kit + SoftHSM / OpenSC)kSecClassKey/Identity classes.GenericPassword items; another semantic translation layer. Better seen as an optional backend for the key classes under option B than as the store itself.Complementary Not a standalone answer; a possible plug-in backend for cert/key items later.
Network.plist / flat filesInterim The honest current state until a consumer justifies B.
| Option | Apple API | License fit | IPC fit | Effort | Verdict |
|---|---|---|---|---|---|
| A. Port Security.framework | Exact | corecrypto blocked | Mach | 6–12+ mo | Rejected |
| B. Clean-room secd | Public API | BSD | Mach/DO | 2–3 mo | Chosen |
| C. gnome-keyring/libsecret | Shimmed | LGPL | D-Bus | 3–5 wk | Fallback |
| D. KWallet | Shimmed | LGPL | D-Bus/Qt | 4–6 wk | Rejected |
| E. pass / gpg-agent | None | GPL tool | CLI | days | Rejected |
| F. PKCS#11 | Keys only | BSD/LGPL | lib | varies | Backend |
| G. plaintext plist | None | — | — | 0 | Interim |
com.apple.securitydSecItem keychain API, backed by an encrypted per-user (and per-system) file store, with crypto from base libcrypto/libsodium and an optional TPM2 key-wrapping backend. The Security-shim library does a bootstrap_look_up and forwards calls — identical client/daemon pattern to configd and notifyd.com.apple.securityd (file com.apple.securityd.plist) — the same string for the job Label and the advertised Mach service (legacy alias com.apple.SecurityServer); started on demand. Per-user instances in the user's launchd domain (keychains are per-user); one system instance (root) for the System keychain. Mirrors the per-user/per-session domain handling the launchd port already supports.Security-compatible shim exporting SecItemAdd/CopyMatching/Update/Delete (+ legacy SecKeychain* shims) that marshal CFDictionary queries over DO/Mach to secd. CoreFoundation types are already available.~/Library/Keychains/login.keychain-db and /Library/Keychains/System.keychain, matching Apple paths so muscle memory / tooling line up. Items carry class + attributes (searchable, plaintext) + encrypted secret blob + access policy.pam_secd, analog of pam_keychain/pam_gnome_keyring) unlocks the login keychain at login using the same password — direct tie-in to the just-completed PAM port. Manual lock/unlock via a CLI (security(1)-equivalent) and an Agent callback for GUI prompts when an app needs an item from a locked keychain.This is settled by what the running image actually does, not by what earlier draft plans guessed. A live launchctl list on a NextBSD box shows the system daemons keeping Apple's exact labels — the job Label, the .plist filename, and the advertised Mach service are all the one com.apple.* string:
launchd% list
PID Status Label
54 - com.apple.syslogd
53 - com.apple.notifyd
52 - com.apple.mDNSResponder
51 - com.apple.kextd
50 - com.apple.hostnamed
48 - com.apple.configd
47 - com.apple.aslmanager
46 - com.apple.IPConfiguration
45 - com.apple.DiskArbitration
55 - com.openssh.sshd # third-party → upstream's own reverse-DNS
So a daemon that models or replaces an Apple service takes Apple's own name outright. For the keychain that is com.apple.securityd (modern data-protection alt: com.apple.secd; legacy bootstrap alias: com.apple.SecurityServer). There is no separate "our identity" label — that was a wrong inference; the shipped daemons don't do it. A clean-room Security.framework has bootstrap_look_up("com.apple.securityd") baked in, and using Apple's label for the job too keeps everything aligned with the rest of the running stack.
| Layer | Namespace | Examples |
|---|---|---|
| Userland daemon modeling/replacing an Apple service | com.apple.* (Apple's exact label; job = service) | com.apple.configd, com.apple.notifyd, com.apple.IPConfiguration, com.apple.DiskArbitration, com.apple.securityd |
| Third-party daemon | upstream's own reverse-DNS | com.openssh.sshd |
Kernel kexts (loaded by com.apple.kextd) | org.nextbsd.* | org.nextbsd.driver.*, org.nextbsd.kpi.*, org.nextbsd.filesystems.* |
| Desktop / GUI layer | org.gershwin.* | org.gershwin.loginwindow, org.gershwin.dshelper |
org.nextbsd.secd (that namespace is kernel-space only) and not org.freebsd.secd (the org.freebsd.* labels in older plan drafts don't match the shipped image, which uses com.apple.* for these daemons — see the audit note in the WiFi/Bluetooth companions).corecrypto. Use base libcrypto (OpenSSL) or libsodium — AES-256-GCM for item secrets, Argon2id/scrypt for password-derived keys, HKDF for subkeys.secd's (ideally mlock'd, non-swappable) memory while unlocked. This is the same guarantee gnome-keyring/KWallet give on commodity Linux — root, or a debugger on the unlocked process, can read live secrets. State this plainly; don't imply Apple-equivalent hardware protection.tpm(4) and security/tpm2-tss in ports. The keychain master key can be sealed to the TPM (optionally to PCR state), so the on-disk DB can't be decrypted off-box without the TPM. This is the closest analog to SEP key-wrapping and is the recommended hardening once the core works — but strictly optional and gated on TPM presence./* Modern data-protection API — the priority surface */
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result);
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result);
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);
OSStatus SecItemDelete(CFDictionaryRef query);
/* Classes to support first */
kSecClassGenericPassword // app/daemon secrets, WiFi PSKs
kSecClassInternetPassword // server creds (has protocol/server/port attrs)
kSecClassKey / kSecClassCertificate / kSecClassIdentity // later; PKCS#11 backend option
/* Common attributes (search keys) */
kSecAttrAccount kSecAttrService kSecAttrServer kSecAttrProtocol
kSecAttrLabel kSecAttrAccessGroup kSecAttrAccessible
/* Legacy shims (thin, mapped onto the modern store) */
SecKeychainAddGenericPassword(...);
SecKeychainFindGenericPassword(...);
SecKeychainAddInternetPassword(...);
Getting GenericPassword + InternetPassword CRUD correct covers WiFi creds, network shares, and the vast majority of app needs. Key/cert/identity classes and a possible PKCS#11 (§4.F) backend come later.
wifid-fbsd network creds, a future mail/browser, or network-share mounts. Until then, plaintext Network.plist stands (WiFi plan Q2).wifid-fbsd would need first; the per-user login keychain is what desktop apps need. Probably ship the system instance first since WiFi is the nearer consumer..keychain-db format is undocumented/SEP-entangled in modern form. Inventing a clean format (encrypted SQLite) is simpler and we control it; Apple-path filenames give familiarity without binary-format compatibility (which buys us nothing without macOS interop).man 4 tpm