keyring#
OS credential storage for small secrets.
Overview#
keyring stores, retrieves and deletes small secrets (API tokens,
passphrases, passwords) in the operating system's credential store,
keyed by a service name and an account name. Secrets live in wipeable
byte buffers, never in immutable strings, and pretty-printing is always
redacted.
Backends are explicit. There is no automatic fallback: a platform
without a usable credential store reports Unavailable, and the caller
decides what to do about it. The macOS backend talks to Keychain
Services through native Security.framework calls, so authorization
attaches to the calling application rather than to a helper executable,
and no secret ever crosses a subprocess pipe.
Usage#
Every backend is used through the same three operations:
# let keyring = Keyring.Memory.v ()
val keyring : Keyring.t = <abstr>
# Keyring.set keyring ~service:"example.org" ~account:"alice"
(Keyring.Secret.of_string "hunter2")
- : (unit, Keyring.Error.t) result = Ok ()
# (match Keyring.get keyring ~service:"example.org" ~account:"alice" with
| Ok secret -> Keyring.Secret.use secret Bytes.to_string
| Error _ -> assert false)
- : string = "hunter2"
# Keyring.delete keyring ~service:"example.org" ~account:"alice"
- : (unit, Keyring.Error.t) result = Ok ()
# Keyring.get keyring ~service:"example.org" ~account:"alice"
- : (Keyring.Secret.t, Keyring.Error.t) result = Error Keyring.Error.Missing
On macOS, Keyring.Macos.v () is the user's login keychain; secrets
are generic-password items (kSecAttrService / kSecAttrAccount).
Keyring.Macos.available says whether the build links against
Security.framework, so portable callers can pick a backend at run
time.
Custom backends implement Keyring.BACKEND and lift through
Keyring.Make.
Secrets#
Keyring.Secret.t wraps a mutable buffer:
useborrows the buffer without copying; the callback must not retain it.wipezero-fills the buffer; later uses raise.equalcompares contents in constant time (viaeqaf).ppprints<secret:N>, never the contents.
Copies the process cannot wipe still exist transiently (CoreFoundation buffers, IPC serialization); the guarantee is that the library never creates long-lived immutable copies on the OCaml heap.
Errors#
Keyring.Error.t distinguishes Missing, Locked,
Interaction_required, Denied, Unavailable and Backend of string, so callers can react (prompt, skip, fall back) instead of
parsing message strings. Keychain calls can block on securityd IPC
and authorization prompts; run them away from a latency-sensitive
event loop.
Related Work#
- Rust keyring, Python keyring and Go 99designs/keyring define the cross-platform get/set/delete-by-service-and-account shape this library follows.
- osx-keychain binds
the full
SecItem*attribute surface (internet passwords, attribute enumeration) for macOS only; its stubs informed the query-dictionary construction and OSStatus handling here. This library instead keeps a minimal cross-platform facade with wipeable secret buffers. - The
freedesktop Secret Service specification
is the target of a future Linux backend; the
LockedandInteraction_requirederrors exist in the vocabulary because that protocol has explicit locking, sessions and prompts.
License#
ISC License. See LICENSE.md for details.