ADR 0005: Device identity and root-key envelopes
Encrypt X25519 device private keys under non-extractable AES-GCM browser keys and use authenticated per-device root-key envelopes.
Status: accepted for the v0.3 recovery-enrollment slice.
Each registered browser generates a random X25519 key pair with libsodium. The 32-byte private key
is immediately encrypted with AES-256-GCM under a non-extractable Web Crypto key and its plaintext
buffer is zeroed on a best-effort basis. IndexedDB stores the protected private-key object and the
structured-clone AES CryptoKey; it never stores the raw X25519 private key. Only the X25519 public
key, encrypted root-key envelope, label, and lifecycle metadata leave the browser.
Directly persisting a non-extractable X25519 CryptoKey works in Chromium and Firefox but WebKit
may write an unreadable value. Non-extractable AES-GCM keys are persistable across the supported
engines. The protected local format is versioned as ownfold.protected-device-private-key V1 and
authenticates its format, version, algorithm, public key, and nonce. Existing direct X25519
CryptoKey records remain readable so upgrading does not strand enrolled browsers.
To protect the root key for a device, Ownfold generates an ephemeral X25519 key pair, derives 256 bits with the recipient public key, and runs the result through HKDF-SHA-256 with a random 32-byte salt. The derived AES-256-GCM key encrypts the 32-byte root key with a random 12-byte nonce. Format, version, suite, device ID, vault ID, key version, creation time, both public keys, KDF parameters, and encryption parameters are canonicalized and supplied as authenticated additional data.
The recipient private key, ephemeral private key, shared secret, derived wrapping key, plaintext
root key, and recovery password are never sent to the application backend. Private-key and
shared-secret byte buffers are zeroed on a best-effort basis after use. Web Crypto CryptoKey
objects cannot be explicitly zeroed.
Recovery-based enrollment opens the Recovery Kit locally, creates a new device identity, encrypts the root key for that identity, and registers the public metadata through the configured transport. Registration is idempotent by device ID. Revocation uses optimistic concurrency and prevents that device from being accepted during later client initialization or unlock attempts.
Successful verified initialization and unlock send only the current device and vault identifiers
to an authenticated activity endpoint. The server supplies lastActiveAt; client clocks cannot
forge it. This operational metadata is not a security-state mutation, so activity writes do not
increment revision or create spurious conflicts with revocation and key rotation.
Revocation cannot erase plaintext or root-key material already available to a compromised device. It also cannot protect against malicious same-origin JavaScript while a vault is unlocked. Password Recovery-password unlock remains available when a local Recovery Kit exists. Paired browsers use their device envelope because the pairing protocol deliberately does not transfer a Recovery Kit. That device-key path is a logical application lock, not OS-backed user-presence authentication.
Web Crypto AES-GCM and IndexedDB CryptoKey structured cloning are browser capability requirements
for device enrollment. Unsupported browsers receive a typed device-key-generation or storage
error. Existing-device pairing reuses this envelope format and binds the recipient key through the
exact, expiring offer described in ADR 0006.