aeat.adapters.persistence.storage.master_key._master_key module

Master-key acquisition for the at-rest crypto stack.

Three concrete providers implement the MasterKeyProvider protocol:

  • KeyringMasterKeyProvider — backed by the keyring package (Windows Credential Manager, macOS Keychain, Linux Secret Service via libsecret). The master key is stored under a fixed service name and account; explicit enrollment mints a 32-byte random key and persists it.

  • FileFallbackMasterKeyProvider — backed by a passphrase- derived KEK (Argon2id) wrapping an AES-256-GCM master key. The per-store random salt is carried inside master.kdf (salt_b64).

  • EphemeralMasterKeyProvider — an in-memory provider used exclusively by tests; the key vanishes when the provider object is garbage-collected.

The get_master_key_provider() factory selects a provider per Settings.aeat_secret_store_backend. The auto backend tries the OS keychain and falls back to the file backend only when the keychain is unusable. The keyring backend refuses to fall back; the file backend never consults the keychain.

The on-disk file backend persists two artefacts in Settings.aeat_secret_store_dir:

  • master.key — the AES-256-GCM ciphertext of the master key, plus its 12-byte nonce, plus the 16-byte tag, base64-encoded.

  • master.kdf — a small JSON document carrying the Argon2id parameters (including the per-store random salt_b64) used to derive the KEK from the operator’s passphrase. This file is human-readable; only master.key is sensitive.

Passphrase resolution: AEAT_SECRET_PASSPHRASE env var is consulted first; absent that, the passphrase is prompted interactively via getpass.getpass().

NIST_PASSPHRASE_MIN_LENGTH: Final[int]

NIST SP 800-63B §5.1.1.1 verifier-side minimum passphrase length.

KEYRING_SERVICE: Final[str]

Stable service identifier under which the keyring backend stores the key.

KEYRING_USERNAME: Final[str]

Account identifier for the master-key entry in the OS keychain.

class MasterKeyProvider(*args, **kwargs)[source]

Bases: Protocol

Source of the master key used by every at-rest crypto consumer.

Providers are context managers: entering activates the backend’s session (idle-timeout guard, in-memory key cache) and exiting tears it down. Every concrete provider implements the protocol verbatim.

The _session / _activation_cm slots are the bookkeeping the shared enter/exit machinery binds onto: entering stores the opened BucketSession and its activation context manager, exiting tears both down. Every concrete provider declares them in __init__.

get_master_key()[source]

Return the 32-byte AES-256 master key.

Return type:

bytes

Returns:

The 32-byte AES-256 master key for the active session.

provision_master_key()[source]

Mint and persist the 32-byte AES-256 master key during explicit enrollment.

Return type:

bytes

class KeyringClient(*args, **kwargs)[source]

Bases: Protocol

Injection seam for the OS-keychain operations the master-key provider depends on.

The real implementation wraps the third-party keyring module’s get_password / set_password calls plus the backend probe that rejects fail.Keyring and null.Keyring. Tests inject a real in-memory implementation rather than mutating the third-party module at runtime.

probe_backend()[source]

Raise KeyringUnavailableError when the active backend cannot persist a master key.

No-op fail / null backends trigger this error.

Return type:

None

get_password(service, username)[source]

Return the persisted password for (service, username), or None when absent.

Return type:

str | None

Parameters:
  • service (str)

  • username (str)

set_password(service, username, password)[source]

Persist password under (service, username).

Return type:

None

Parameters:
  • service (str)

  • username (str)

  • password (str)

class KeyringMasterKeyProvider(*, service='aeat:secure-persistence', username='master', client=None)[source]

Bases: object

OS-keychain-backed master-key provider.

The provider lazily imports the keyring package and lazily queries the active backend. Before any read or write, the active keyring backend is inspected; the no-op fail.Keyring and null.Keyring backends raise KeyringUnavailableError so the auto fallback can route to the file backend without silently dropping the master key into a sink.

Older builds kept an in-process key cache keyed by (service, username). That cache has retired in favour of BucketSession; this provider resolves through the keyring on each call.

The optional client argument injects a KeyringClient implementation so tests exercise the provider’s contract against a real in-memory implementation rather than mutating the third-party keyring module at runtime.

Parameters:
get_master_key()[source]

Fetch the master key via the OS keychain.

Resolves on every call: process-global caching has retired in favour of BucketSession instance state. Production consumers should activate a session via activate_session() and read through get_active_master_key() rather than call this method in a tight loop.

Absent key material is a provisioning error, not permission to create storage implicitly. Explicit enrollment calls provision_master_key().

Return type:

bytes

provision_master_key()[source]

Mint and persist a new keychain master key for explicit enrollment.

Return type:

bytes

class FileFallbackMasterKeyProvider(*, store_dir, passphrase_callback=None)[source]

Bases: object

Encrypted-file-backed master-key provider.

Persists master.key (plus a human-readable master.kdf parameters document carrying the per-store salt_b64) under Settings.aeat_secret_store_dir. The KEK is derived from a passphrase via Argon2id and wraps the master key with AES-256-GCM.

Parameters:
get_master_key()[source]

Unwrap and return the 32-byte master key from the encrypted file store.

Resolves the operator passphrase, then serialises the unwrap-or-refuse decision under an exclusive master.lock so two first-time callers cannot race-mint conflicting master.key / master.kdf pairs. When both artefacts (master.key, master.kdf) are present, derives the Argon2id key-encryption key (KEK) from the passphrase and uses it to unwrap the wrapped master key. A partial artefact set is a torn install – a prior mint or recovery crashed mid-write – and is refused rather than silently re-minted, which would orphan records encrypted under the lost key.

Return type:

bytes

Returns:

The 32-byte AES-256 master key.

Raises:
provision_master_key(*, force=False)[source]

Mint the file-fallback master key for explicit enrollment.

Parameters:

force (bool) – When True, replace complete existing material. Reserved for explicit re-provision flows; normal enrollment leaves it False.

Return type:

bytes

Returns:

The newly minted 32-byte master key.

Raises:
complete_recovery(master_key)[source]

Re-mint the file-fallback artefacts under recovered key bytes.

Writes master.kdf and master.key for the operator’s current passphrase (via the configured callback), wrapping master_key under a freshly-derived Argon2id KEK. Both artefacts are written via the atomic tempfile-and-replace pattern so a crash between writes leaves the existing on-disk state untouched.

Use after a recovery-key unwrap (unwrap_master_key) to bind the recovered master-key bytes to a new passphrase. The substrate’s in-process cache is invalidated so subsequent get_master_key() calls re-read the freshly-written artefacts under the new passphrase.

Parameters:

master_key (bytes) – The 32-byte recovered master-key value.

Raises:

SecretStoreError – When the master key has the wrong length, the resolved passphrase is empty, or the target directory is not writable.

Return type:

None

activate_master_key_provider(provider, *, fallback_bucket_id=None, allow_bucket_dek_enrollment=False)[source]

Activate provider for encrypted storage within the current block.

fallback_bucket_id is used by bootstrap flows such as profile creation, where the command knows the bucket being provisioned but the active-profile pointer does not exist until the transaction completes.

Parameters:
  • provider (MasterKeyProvider) – The MasterKeyProvider to activate.

  • fallback_bucket_id (str | None) – Optional bucket identifier used when no active profile pointer is present (bootstrap flows only).

  • allow_bucket_dek_enrollment (bool) – When True, a missing per-bucket DEK file is minted on first activation rather than raising.

Return type:

Iterator[object]

class UnsecuredMasterKeyProvider[source]

Bases: object

Master-key provider for testing / throwaway scenarios.

Returns a published deterministic 32-byte master key. The substrate’s encryption pipeline is unchanged; only the wrapping key is publicly known. Provides ZERO confidentiality.

Activation requires both signals:

  • AEAT_ALLOW_UNENCRYPTED=1 environment variable (the hostile- named opt-out gate).

  • aeat_secret_store_backend=unsecured setting (or equivalent explicit backend selection at the substrate boundary).

Refused at profile-load time when the operator profile carries a valid NIF/NIE/CIF (NIF-canary) — see refuse_unsecured_with_real_nif() in the consumer modules. Real tax data is incompatible with a published deterministic master key.

get_master_key()[source]

Return the published deterministic master key for unsecured mode.

The returned bytes are publicly known by design, so the wrapping key provides ZERO confidentiality; the substrate’s encryption pipeline is otherwise intact. Intended only for testing, tutorial, and throwaway scenarios that are fenced off from real tax data by the NIF-canary at the profile-load boundary.

Return type:

bytes

Returns:

The 32-byte published deterministic master key.

provision_master_key()[source]

Return the published deterministic key without minting material.

There is nothing to provision for the unsecured backend: the key is a fixed published constant, so enrollment and retrieval return the same bytes. Provides ZERO confidentiality – see get_master_key.

Return type:

bytes

Returns:

The 32-byte published deterministic master key.

refuse_unsecured_with_real_nif(tax_id, *, provider)[source]

Refuse the unsecured backend when the operator profile is real.

Called at the profile-load / profile-write boundary. When the active master-key provider is UnsecuredMasterKeyProvider AND the profile’s tax id parses as a real NIF / NIE / CIF (per looks_like_real_tax_id()), raises UnsecuredModeRefusedError. No-op when the provider is any other class.

Parameters:
Raises:

UnsecuredModeRefusedError – When the unsecured backend is active and the tax id is real.

Return type:

None

get_master_key_provider(*, backend=None, settings_override=None, passphrase_callback=None, keyring_client=None)[source]

Resolve the active MasterKeyProvider per project settings.

Parameters:
  • backend (str | None) – Optional explicit backend selector (auto / keyring / file). Overrides the value resolved from settings.

  • settings_override (Settings | None) – Optional pre-built settings instance. Tests inject a settings object bound to tmp_path so the file backend writes inside the test sandbox.

  • passphrase_callback (Callable[[], str] | None) – Optional override for passphrase resolution; only consulted by the file backend.

  • keyring_client (KeyringClient | None) – Optional KeyringClient implementation threaded into any constructed KeyringMasterKeyProvider. Tests inject a real fake type rather than patching the third-party keyring module.

Return type:

MasterKeyProvider

Returns:

A live provider instance honouring the resolved backend.

Raises: