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

nordisk/edda

crates/nornir-vault/src/error.rs

//! The one error type for the vault.
//!
//! Every variant is deliberately *coarse* and carries **no secret material** and
//! no oracle: an unlock that fails because the PIN was wrong and an unlock that
//! fails because the ciphertext was corrupted both surface as
//! [`VaultError::Unlock`] — the caller learns "it did not open", never *why* in a
//! way that could be turned into a guessing oracle. The `Debug`/`Display` of this
//! type is safe to log.

use crate::provider::AuthMethod;

/// The result alias used across the crate.
pub type Result<T> = core::result::Result<T, VaultError>;

/// Everything that can go wrong opening, unlocking, or mutating a vault.
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum VaultError {
    /// The on-disk vault file could not be read/written, or its directory could
    /// not be created / `chmod`ed.
    #[error("vault i/o: {0}")]
    Io(#[from] std::io::Error),

    /// The header could not be (de)serialised — a truncated or foreign file. This
    /// is a *format* failure, distinct from an authentication failure.
    #[error("vault header is not valid JSON / not a vault file")]
    Format(#[from] serde_json::Error),

    /// A base64 blob in the header did not decode.
    #[error("vault header contains a malformed binary field")]
    Base64,

    /// Unlock failed. Wrong credential OR tampered/corrupt ciphertext — the
    /// variant is intentionally indistinguishable (no guessing oracle). The
    /// attached counter is how many attempts remain before the vault locks; `0`
    /// means this failure just tripped the lockout.
    #[error("vault did not unlock ({remaining} attempt(s) remaining before lockout)")]
    Unlock {
        /// Attempts left before [`VaultError::LockedOut`]. Never leaks the reason.
        remaining: u32,
    },

    /// The vault is locked out: too many failed unlock attempts. It will not
    /// accept another attempt until the lockout is reset out-of-band (a fresh
    /// vault / operator reset). This is the low-entropy-PIN backstop.
    #[error("vault is locked out after too many failed attempts")]
    LockedOut,

    /// A read/write of secret entries was attempted while the vault is still
    /// locked (no DEK in memory). Call `unlock` first.
    #[error("vault is locked — unlock before reading or writing entries")]
    Locked,

    /// The provider handed to `unlock`/`create` does not match how this vault was
    /// provisioned (e.g. a `PinProvider` against an `OsKeyring` header).
    #[error("auth-method mismatch: vault is {vault:?}, provider offered {provider:?}")]
    MethodMismatch {
        /// The method the vault header was provisioned with.
        vault: AuthMethod,
        /// The method the supplied provider speaks.
        provider: AuthMethod,
    },

    /// A design-only provider (OIDC→OpenBao, TPM, FIDO2) was invoked. These are
    /// declared so they *slot in* to the trait, but their key material flow is
    /// not implemented — see `provider.rs`.
    #[error("key provider for {0:?} is not implemented yet (design-only stub)")]
    ProviderUnimplemented(AuthMethod),

    /// A key-provider-specific failure that is not itself a secret (e.g. the OS
    /// keyring is unavailable, or Argon2 rejected the parameters).
    #[error("key provider error: {0}")]
    Provider(String),
}