Skip to content
Ownfold
Esc
navigateopen⌘Jpreview

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.

Last updated on August 4, 2026

Was this page helpful?