Skip to main content

Cryptographic inventory

What Lonca uses cryptography for, where each key lives, who can read it and how it is replaced (ASVS 5.0 V11.1.1). Keep it current: a change to any row changes this file in the same pull request. The threat model and docs/security/owasp.md point here.

Algorithms​

UseAlgorithmLibraryWhere
Daemon signature on every log entryEd25519, verify_stricted25519-dalek 3crates/lonca-core/src/keys.rs, oplog.rs
Human signatures, file keys (development)Ed25519, verify_stricted25519-dalek 3keys.rs, crates/lonca-policy/src/human.rs
Human signatures, Secure Enclave keysECDSA P-256 with SHA-256Security framework (lonca-se), p256 0.14 to verifypackaging/macos/lonca-se, human.rs
Human signatures, FIDO security keysECDSA P-256 with SHA-256 or Ed25519 (verify_strict), in OpenSSH's SSHSIG form over SHA-512 or SHA-256 of the messagethe device and ssh-keygen to sign; p256, ed25519-dalek, sha2 to verifyhuman.rs (sk)
Log entry digests, hash chainSHA-256 over canonical JSONsha2 0.11crates/lonca-core/src/canonical.rs, oplog.rs
Session tokens at restSHA-256 of the tokensha2crates/lonca-daemon/src/home.rs (Tokens)
Tokens, nonces, ids, new keys32 (tokens, keys) or 8 to 16 bytes from the OSgetrandom 0.4canonical.rs (random_bytes)
Object ids of Git trees and commitsSHA-1 (Git's own format)gixcrates/lonca-core/src/store.rs
Advisor calls to the Claude APITLS (rustls with ring, webpki roots)ureq 3crates/lonca-cli/src/advise.rs
Git bridge, importswhatever git uses (HTTPS, SSH)the system gitcrates/lonca-daemon/src/service.rs

Notes:

  • What a signature covers. The daemon signs the SHA-256 digest of an entry's canonical bytes, and that digest names the previous entry's, so the chain and every entry are covered. A human signs the canonical bytes of the intent: the repository, the operation, a nonce and issued_ms. The daemon refuses a nonce it has seen and an issued_ms more than 10 minutes old or 1 minute ahead (human.rs, MAX_AGE_MS, MAX_SKEW_MS).
  • SHA-1 is Git's, not Lonca's. Git object ids name trees and commits, and a collision would let two trees share a name. The log's integrity rests on SHA-256 and Ed25519; the auditor recomputes merges and facts from the objects it is given. A move to SHA-256 Git repositories is open.
  • No custom constructions. No algorithm or mode is implemented in Lonca; each comes from the library in the table.

Keys and secrets​

SecretWhereReadable byCreatedReplaced
Daemon signing key<home>/daemon.key, hex, mode 0600the daemon's user (_lonca / lonca when hardened)first startlonca daemon-key rotate (daemon.rotate); a lost key: lonca daemon-key recover (daemon.recover, signed by a human)
Key of a rotation in progress<home>/daemon.next.key, mode 0600the daemon's userdaemon.rotatebecomes daemon.key once every repository has switched; the old secret is deleted
Human file key~/.config/lonca/humans/<name>.key ($LONCA_HUMANS), mode 0600the developer's accountlonca human keygenlonca human rotate (human.rotate)
Human Secure Enclave key<humans>/<name>.se: a key blob only the Secure Enclave of that Mac can use; every signature needs Touch ID or the passwordnobody: the private key never leaves the enclavelonca human keygen --secure-enclavehuman.rotate; a lost key: the recovery key (human.recover; on a lost Mac, with the daemon key gone too, a daemon.recover it signs: Recovery)
Human security key<humans>/<name>.sk: the handle of a credential on a FIDO security key, made for the application ssh:lonca; every signature needs a touchnobody: the private key never leaves the device, and the handle signs nothing without itlonca human keygen --security-keyhuman.rotate; a lost device: the recovery key (human.recover)
Recovery key--out path, then off the machine (password manager, USB stick)whoever holds the copylonca human recovery-keyspent by lonca human recover (human.recover, or daemon.recover with recover on a lost Mac); set a new one after
Agent session tokensgiven once to the harness (session file in the workspace, MCP), stored in <home>/tokens.json only as SHA-256the agent's process; the daemon keeps no copysession.startend, revocation (cascades), expiry (#53)
Human session tokens<humans>/<name>.session, mode 0600the developer's accountlonca human login (presence)expiry, lonca human logout
Anthropic credentialsANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN in the environment, or ant auththe developer's accountoutside Loncaoutside Lonca

How keys are held:

  • Loading checks the file. A key file must be a regular file (a link is not followed), owned by the uid of the process that loads it, with no access for group or others; otherwise the load fails and says what to do (check_private in keys.rs). The one exception is the recovery key, which is often carried on a file system that shows every file as readable: only its owner is checked, and others being able to read it is a warning.
  • The daemon home must belong to the daemon. Home::ensure refuses a home owned by another uid and sets it to mode 0700.
  • Secrets are wiped. KeyPair shares one SigningKey through an Arc, so a clone copies no secret; SigningKey zeroizes itself when the last clone is dropped. The hex text read from disk, the decoded bytes and the text written for a new key are Zeroizing buffers.
  • Agents hold no long-lived keys. An agent has a session token, which ends with the session; nothing an agent holds can sign a log entry or a human operation.
  • Hardened mode. The daemon home, its key and tokens.json belong to the daemon's own system user, mode 0700, where the developer's account can not read or write them (packaging/*/install-hardened.sh).

Not done​

  • The daemon key is a file. Keeping it in the System keychain or the Secure Enclave on macOS goes with #5.
  • No hardware key for the daemon on Linux (TPM).
  • Session tokens are bearer tokens: nothing binds one to the process it was given to, apart from the sandbox check of isolated sessions.