kc-cli 0.6.0

CLI for reading and writing macOS keychain files without the Security framework
kc-cli-0.6.0 is not a library.
Visit the last successful build: kc-cli-0.1.0

kc-cli

kc creates, queries, and updates macOS .keychain-db files directly. It does not call the Security framework, require entitlements, or depend on a running securityd. The files remain interoperable with Keychain Access and /usr/bin/security.

The Rust library is keychain-rs. This crate provides the kc command.

Install

cargo install kc-cli

Keychain selection

Every command that accepts --keychain resolves keychains in this order:

  1. explicit --keychain KEYCHAIN or a command's explicit keychain argument;
  2. KC_DEFAULT_KEYCHAIN;
  3. keychains.default in ~/.config/keychain.kdl;
  4. login.

Bare names are searched under ~/Library/Keychains and then under configured search paths. machina therefore resolves to ~/Library/Keychains/machina.keychain-db; system is the system keychain. Paths containing / are treated as paths.

kc config set keychains.default machina
kc config set search.paths "path1" "path2"
kc config append search.paths "path3"
kc config prepend search.paths "preferred"
kc config show

export KC_DEFAULT_KEYCHAIN="$PWD/secrets.keychain-db"

config show reports both the saved default and the effective default with its source.

Create and add

Creation always requires an output path or name:

kc create ./secrets.keychain-db
kc create --password-gen --password-policy secure ./secrets.keychain-db

Password items use one assignment grammar:

kc add \
  class=generic \
  label=machina-vpn-operator \
  account=machina \
  service=cloudflare \
  kind="api key" \
  comment="a vpn operator" \
  --keychain ./secrets.keychain-db

kc add \
  class=internet \
  label="Gmail Account" \
  account=bryan@example.com \
  server=imap.gmail.com \
  port=443 \
  kind=password

Supported classes are generic, internet, and appleshare. Friendly field names and native aliases are interchangeable: account/acct, service/svce, comment/icmt, and so on. Class-invalid assignments are errors.

When -w, --secret is absent, the item secret is read from stdin or prompted for on a terminal.

Identities retain a purpose-built form because their inputs are files:

kc add identity -c certificate.pem -k private-key.der -l "MDM Push" login
kc add identity --pkcs12 identity.p12 login
kc import identity combined.pem login

Certificate and private-key inputs accept PEM or DER by content, regardless of file extension. Private keys may be PKCS#8 or RSA PKCS#1. PKCS#12/PFX accepts DER or PEM and has independent --pkcs12-password* inputs.

Get

get is the only item read command. With no predicates it returns all queryable password, certificate, public-key, private-key, and item-key records.

kc get
kc get class:internet label:"Gmail Account" kind:"api key"
kc get \
  'class:internet label:"Gmail Account" kind:"api key" icmt:%2026%'
kc get class:internet label:"Gmail Account" 'icmt:%2026%'

Predicates are ANDed. The canonical grammar is:

FIELD:VALUE
FIELD:>=VALUE
FIELD[MODIFIERS]:PATTERN

Comparisons are =, !=, <, <=, >, and >=. A value containing % or _ uses SQL-LIKE semantics: % matches any sequence and _ matches one character. Backslash escapes a wildcard. [c] is case-insensitive, [d] is diacritic-insensitive, and [cd] enables both.

Examples:

kc get \
  'cdat:<20260515074219Z' \
  'mdat:>=20250515074219Z' \
  'icmt:%2026%' \
  'label[cd]:com.%'

Dates accept Keychain UTC timestamps (YYYYMMDDhhmmssZ) and RFC 3339 UTC timestamps. Numeric and boolean fields are compared as their actual types. Unknown modifiers, malformed comparisons, invalid typed values, and unknown exact class names are errors.

Projection

-o, --output selects ordered fields:

kc get \
  class:generic account:machina service:vpn \
  -o label,kind,account,service

kc get class:internet -o '*'
kc --json get class:internet -o account,server,port

-u, --distinct emits each projected tuple once while preserving first-seen order. Deduplication uses typed projected values before text or JSON rendering:

# Unique non-empty service values
kc get 'service:_%' -o service --distinct

# Unique account/service pairs
kc get 'service:_%' -o account,service -u

service:% also matches an empty service; service:_% requires at least one character. The query language intentionally uses % rather than shell-glob *.

* emits full record detail. secret decrypts the selected item. More than one secret requires --all; non-secret queries intentionally allow multiple rows.

kc get class:generic account:machina -o secret
kc --format secret get class:generic account:machina
kc get class:generic -o secret --all

@ref emits opaque, revision-bound item references for mutation pipelines:

kc get class:internet 'account[c]:bryan%' -o @ref |
  kc set label=updated --for -

References carry their keychain and database revision. set validates the complete stream before changing memory and saves once. A malformed, duplicate, wrong-keychain, missing, or stale reference aborts the operation without writing. Human-readable and JSON output are never reparsed as selectors.

Set

set applies assignments to every item matched by --for:

kc set label=updated comment="rotated in 2026" \
  --for 'class:internet account[c]:bryan%'

The query must be non-empty. class, record, secret, creation time, and modification time cannot be changed. Attributes that define a relation's unique identity are rejected by the library; replacing those means deleting and adding an item.

Updates are collected first, applied in memory, and saved once. A failure before the save leaves the file unchanged.

Password input

All keychain-password consumers use the same sources:

-E, --password-env [ENV_VAR]       default ENV_VAR: KC_PASSWORD
-F, --password-file FILE           use - for stdin
    --password-gen[=TEMPLATE]       creation/change only; default: sha1
-P, --password PASSWORD
    --password-policy POLICY        alphanumeric|mixed-case|symbol|secure

When no source is set, kc reads the password from stdin or prompts on a terminal. Environment variables and files avoid exposing secrets in process arguments. If -P is useful for shell composition, prefer a shell expression:

kc get class:generic account:alice -o secret \
  -P "$(secret-tool lookup kc login)"

Access policy and native ACLs

New keychains default to a saved hybrid policy whose direct and native decision is prompt. Direct secret access therefore requires --interactive; native ACLs pre-authorize no caller, allowing securityd to show its normal authorization UI.

kc access show machina
kc access set --mode hybrid --default prompt machina
kc access apply --to securityd machina
kc access audit --against securityd machina

extended enforces the kc policy only, native delegates to item ACLs, and hybrid does both. Keychain-wide policy is projected onto items when they are written and can be reapplied to existing items. Native Keychain ACLs are still item-level data; the saved keychain policy is the template and enforcement model for all items.

For individual legacy operations, kc trust supports:

kc trust -a alice -T /Applications/MyApp.app login
kc trust -a alice --trust-requirement \
  /Applications/MyApp.app=requirement.bin login
kc trust -a alice --prompt login
kc trust -a alice -A login

--trust-requirement PATH=FILE stores the compiled code requirement while the legacy cdhash remains optional. This preserves Security.framework interoperability while allowing stronger application identity.

Identity import and export

kc export cert -l "MDM Push" -o certificate.pem login
kc export key -l "MDM Push" -o private-key.pem login
kc export identity -l "MDM Push" -o identity.pem login
kc export identity -l "MDM Push" --pkcs12 -o identity.p12 login
kc export identity -l "MDM Push" --pkcs12 --pem -o identity.p12.pem login

An explicit output path is recommended for exported secret material. PKCS#12 exports are encrypted with the selected PKCS#12 password.

Other commands

kc info [KEYCHAIN]
kc verify [KEYCHAIN]
kc rm item SELECTOR [--all] [KEYCHAIN]
kc rm identity [-l LABEL | --hash HEX] [KEYCHAIN]
kc cp SELECTOR SOURCE DESTINATION
kc passwd [KEYCHAIN]
kc settings [OPTIONS] [KEYCHAIN]
kc completions SHELL

show, find, and ls were removed in 0.6.0. Their behavior is represented by get, projections, and an empty query.

Output and exit status

Text is the default. --json is shorthand for --format json; --format secret emits only secret bytes suitable for a pipeline.

Common exit statuses are stable:

0   success
44  no item matched
45  incorrect keychain password
46  duplicate item
2   command-line or other operational error

Development

cargo test --workspace --all-targets
cargo clippy --workspace --all-targets --all-features -- -D warnings
mise run verify

The release order is keychain-rs first, then kc-cli.

License

MIT