Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/edda

.nornir/vault-design.md

nornir-vault — design

Class: design · The client-side, at-rest-encrypted secrets vault for the nordisk thin clients (korp first). States what is built; the why and the korp-wiring plan live in workspace_nordisk/.nornir/korp-client-vault-design-2026-07-30.md. Sibling: vault-guide.md.

What it is

nornir-vault is a local, at-rest-encrypted store a thin client keeps its own credentials in — an mTLS client cert+key, a Spark token. It is the sibling of the nornir-auth-* leaves and is distinct from the server-side OpenBao (that is infra); this is the client's local locked store. OpenBao may later be one remote key-provider, but the vault does not depend on it.

Threat model

The korp thin client runs inside an RDP-open container an operator watches from Windows; anyone at that session can read the container filesystem. The vault keeps those credentials encrypted at rest so an idle/naive RDP viewer cannot lift them off disk. It is a "not too open" measure, not a fortress.

Envelope encryption

  PIN ──Argon2id(salt)──▶ KEK ──unwraps──▶ DEK ──AES-256-GCM──▶ entries
  (or OS keyring ─fetch─▶ KEK)             (random, per-vault)   (cert/key, token)

Providers

AuthMethod Provider KEK source Status
PinCode PinProvider Argon2id(pin, salt) (m=19 MiB, t=2, p=1) implemented
OsKeyring OsKeyringProvider random secret in the OS keyring (Secret Service) implemented, feature os-keyring (backend + live test are the consumer's; live test #[ignore]d)
Oidc OidcOpenBaoProvider OIDC login → OpenBao releases the key design-only stub
(TPM) TpmProvider TPM-sealed KEK design-only stub
(FIDO2) Fido2Provider FIDO2 hmac-secret → KEK design-only stub
Mtls, StaticToken — these are stored credential kinds, not unlock methods n/a

Stubs return VaultError::ProviderUnimplemented — the trait admits them; no crypto is faked.

The invariants

On-disk format

A single JSON header (written 0600, temp-file + atomic rename) holding only non-secret metadata + ciphertext: version, method, provider params (Argon2 salt+cost, or keyring coordinates), the wrapped DEK, the lockout counter, and each entry's kind + sealed blob. A test asserts the file never contains a secret's plaintext bytes.

Entry types (extensible)

MtlsClientIdentity { cert_pem, key_pem }, SparkToken(String), and Opaque(Vec<u8>) (the extension point — e.g. a nornir-auth-client TokenStore bundle, which the vault is designed to back).