Skip to main content

Module userkey

Module userkey 

Source
Expand description

The user’s encryption key: one key pair per user, the same for every repository, opened by any one of the user’s unlock methods.

unlock methods (each is one age file on the server, wrapping the SAME user key)
  SSH key file ──────────────┐  age ssh-ed25519 / ssh-rsa stanza      (CLI)
  security key (PRF secret) ─┤  v2: HKDF-SHA256 → XChaCha20-Poly1305    (browser + CLI)
                             │  (v1, still opened: HKDF → X25519 → age X25519)
  age1pq1… key file ─────────┤  age mlkem768x25519 stanza              (CLI)
  printed recovery code ─────┘  age scrypt stanza                      (CLI + browser)
         │
         ▼
user key (AGE-SECRET-KEY-PQ-1…, quantum-safe: the default; or AGE-SECRET-KEY-1…, classic)
         │ age X25519 (or mlkem768x25519) stanza: one per member in .gunnar-crypt/keys
         ▼
repository key ring (id key + epochs)
  • A repository names only user public keys (alice age1… in .gunnar-crypt/members). Adding or removing an unlock method changes one wrap of the user key and no repository at all.
  • The server keeps the user’s public key and the wraps, which are ciphertext to it. It never sees a method’s secret: an SSH private key, the security key’s PRF output or the recovery code.
  • A security key’s recipient is fixed per credential: the PRF output (WebAuthn prf, FIDO2 hmac-secret under the CLI) is evaluated on the fixed security_key::PRF_INPUT, and HKDF-SHA256 with a fixed label and the credential id as context makes the X25519 secret. The same key and credential always give the same age1… recipient, in the browser and in the CLI.
  • No primitive is ours: age’s X25519, SSH, scrypt and (feature pq) hybrid stanzas do every wrap; RustCrypto hkdf and sha2 do the derivation.
  • A new key is quantum-safe (Level::NEW, CRYPT-PQ-DEFAULT 2026-10-30). Every method opens it. Each method’s OWN wrap is as strong as the method (wrap_is_quantum_safe): the age1pq1… key file (hybrid stanza), the recovery code (scrypt, symmetric) and the security key’s v2 wrap (symmetric, CRYPT-PQ-SK) are quantum-safe; the SSH key’s wrap (X25519 or RSA-OAEP) is not, and the CLI and the console say so (SSH_WRAP_IS_CLASSICAL). A security key’s v1 wrap (X25519) still opens and is re-wrapped to v2 on the next unlock.

Modules§

recovery_code
A printed recovery code as one more unlock method: 32 characters of Crockford’s base32 (160 random bits), in groups of four.
security_key
A security key as an unlock method: WebAuthn prf in the browser, FIDO2 hmac-secret in the CLI, one secret.

Enums§

SecurityKeyWrap
Which of a security key’s wraps opened (open_with_security_key).
UserKey
One user’s encryption key (the private half).
UserKeyError
Why a user key could not be wrapped or opened. Every refusal has a name.

Constants§

MAX_WRAP
The largest wrap anyone parses (a hybrid stanza is ~1.6 KB).
PAYLOAD_TAG
The first line of a wrapped user key’s payload: a type tag, so a wrap that opens to anything else is refused.
SSH_WRAP_IS_CLASSICAL
What the CLI and the console say when an SSH key opens a quantum-safe key. The key stays quantum-safe and so do the repositories; this one wrap does not, and nothing falls back to a classic key.

Functions§

open
Open a stored wrap with one method’s age identity, and check the key inside against the account’s published public key (expected_public).
open_with_recovery_code
Open a stored wrap with a printed recovery code.
open_with_security_key
Open a security key’s wrap (v2, or a v1 left from before CRYPT-PQ-SK) with the credential’s PRF / hmac-secret output, and check the key inside against the account’s published public half. Says which wrap it was: a v1 one is re-wrapped to v2 by the caller.
wrap_is_quantum_safe
Is an unlock method’s wrap of the key quantum-safe? By the method’s kind on the wire and, for a security key, the wrap itself: age-pq (age’s hybrid ML-KEM-768 + X25519 stanza), recovery-code (age’s scrypt) and a security key’s v2 wrap (crate::skwrap: symmetric, HKDF over the PRF output, XChaCha20-Poly1305) are, since a quantum computer only halves a symmetric key; ssh (X25519 or RSA-OAEP) and a security key’s v1 wrap (an X25519 stanza) are not, and neither is any kind this version does not know.