#[non_exhaustive]pub enum CryptoError {
Show 41 variants
Io(Error),
InputPath,
InvalidInput(String),
InvalidFormat(FormatDefect),
UnsupportedVersion(UnsupportedVersion),
InvalidKdfParams(InvalidKdfParams),
KdfResourceCapExceeded {
mem_cost_kib: u32,
local_cap_kib: u32,
},
KdfTimeCostCapExceeded {
time_cost: u32,
local_cap: u32,
},
KdfLanesCapExceeded {
lanes: u32,
local_cap: u32,
},
KdfBelowWriteFloor {
mem_cost_kib: u32,
floor_kib: u32,
},
HeaderLenCapExceeded {
header_len: u32,
local_cap: u32,
},
RecipientCountCapExceeded {
count: u16,
local_cap: u16,
},
RecipientBodyCapExceeded {
body_len: u32,
local_cap: u32,
},
RecipientStringCapExceeded {
input_chars: u32,
local_cap: u32,
},
PrivateKeyWrappedSecretCapExceeded {
wrapped_secret_len: u32,
local_cap: u32,
},
KeyFileUnlockFailed,
HeaderTampered,
HeaderMacFailedAfterUnwrap {
type_name: String,
},
RecipientUnwrapFailed {
type_name: String,
},
UnknownCriticalRecipient {
type_name: String,
},
NoSupportedRecipient,
DecryptorModeMismatch {
expected: UnauthenticatedRecipientMode,
found: UnauthenticatedRecipientMode,
},
EmptyRecipientList,
IncompatibleRecipients {
type_name: String,
policy: MixingPolicy,
},
PayloadTampered,
PayloadTruncated,
ExtraDataAfterPayload,
PayloadChunkCountExceeded,
MalformedArchive {
reason: &'static str,
},
UnsafeArchivePath {
path: String,
reason: &'static str,
},
InvalidArchiveTree {
path: String,
reason: &'static str,
},
ArchiveEntryCountCapExceeded {
entry_count: u32,
local_cap: u32,
},
ArchiveTotalBytesCapExceeded {
total_bytes: u64,
local_cap: u64,
},
ArchiveManifestLenCapExceeded {
manifest_len: u64,
local_cap: u32,
},
ArchivePathBytesCapExceeded {
path_bytes: u32,
local_cap: u32,
path: Option<String>,
},
ArchivePathDepthCapExceeded {
depth: u32,
local_cap: u32,
path: String,
},
ArchiveExtLenCapExceeded {
ext_len: u64,
local_cap: u32,
},
ArchiveEntryExtLenCapExceeded {
ext_len: u64,
local_cap: u32,
path: Option<String>,
},
ArchiveTotalEntryExtCapExceeded {
total_ext_bytes: u64,
local_cap: u64,
},
InternalInvariant(&'static str),
InternalCryptoFailure(&'static str),
}Expand description
Errors that can occur during key generation, encryption, or decryption.
All Display messages are short, user-facing, and free of internal
type names so that consumers can surface them directly without
additional mapping.
§Design: identity-only where possible
Most variants are identity-only: they carry no per-operation context (no paths, no byte offsets, no wrapped error text), because that context belongs at the caller, not inside the error. A CLI frontend can prepend the file path if it wants to; a GUI can elide it; a server can log structured fields. The library stays agnostic.
Variants that do carry data carry typed structured data, not heap-allocated strings:
CryptoError::InvalidFormatcarries aFormatDefectCryptoError::UnsupportedVersioncarries anUnsupportedVersionCryptoError::InvalidKdfParamscarries anInvalidKdfParamsCryptoError::InternalInvariantandCryptoError::InternalCryptoFailurecarry a&'static strmarker (no heap allocation)- The
*CapExceededvariants (CryptoError::HeaderLenCapExceeded,CryptoError::RecipientCountCapExceeded,CryptoError::RecipientBodyCapExceeded,CryptoError::RecipientStringCapExceeded,CryptoError::KdfResourceCapExceeded,CryptoError::KdfTimeCostCapExceeded,CryptoError::KdfLanesCapExceeded,CryptoError::PrivateKeyWrappedSecretCapExceeded, and theArchive*CapExceededfamily for theFORMAT.md§9.12 caps) each carry the offending value plus the configured local cap as named integer fields, matching the “distinct resource-cap error” classes thatFORMAT.md§3.2 / §12 enumerate - The archive defect variants (
CryptoError::MalformedArchive,CryptoError::UnsafeArchivePath,CryptoError::InvalidArchiveTree) carry a staticreasondescribing the violated rule; the path-carrying ones also carry the offending entry path, sanitized for display (control and non-ASCII characters escaped, long paths truncated), because an attacker-crafted archive can hold thousands of entries and the path is the only way to locate the bad one - The multi-recipient diagnostics (
CryptoError::RecipientUnwrapFailed,CryptoError::HeaderMacFailedAfterUnwrap,CryptoError::UnknownCriticalRecipient,CryptoError::IncompatibleRecipients) each carry thetype_nameso callers can tell which recipient slot raised them
Consumers can pattern-match on these shapes without substring comparisons.
§The one escape hatch: CryptoError::InvalidInput
One variant — CryptoError::InvalidInput — carries a free-form
String. It is the designated heterogeneous caller-input
bucket for fail-closed rejections whose only useful context is
a path or short token that has to be echoed back to the user.
Concretely it covers:
- encrypt-side source-tree problems: “Input is a symlink:
path”, “Source is no longer a regular file:name”, “Unsupported file type:path”. The source tree belongs to the caller’s environment, so these are caller-input rejections, not format defects. Every embedded path or name — caller-supplied top-level paths included — is sanitized viasanitize_for_display/sanitize_path_for_displaybefore embedding. - Bech32 recipient parser: reports a malformed recipient string, unexpected recipient prefix, or uppercase input. Callers pass recipient strings through as opaque values, so the parser echoes sanitized, truncated input to help the user spot a typo.
- Caller-invocation path conflicts and shape rejections:
“Output already exists:
path”, “Key file already exists:path”, “Invalid recipient public key”. These surface which user-supplied path or value triggered the rejection so operators can fix it without extra debugging. The conflict messages render the parent directory raw (it is the caller’s trust boundary) and escape the final component, which can be attacker-influenced. - Caller-supplied config values outside the valid range:
“KDF memory limit overflow:
NMiB”, “Passphrase must not be empty”.
Rejections of a parsed archive payload — a malicious or corrupt
FCA inside the decrypted stream — are not InvalidInput: they
surface as CryptoError::MalformedArchive,
CryptoError::UnsafeArchivePath,
CryptoError::InvalidArchiveTree, or the Archive*CapExceeded
family, because they describe the file, not the caller.
Library consumers treat InvalidInput as an opaque string and
surface it via Display; the CLI and desktop frontends do exactly
that.
Variants (Non-exhaustive)§
This enum is marked as non-exhaustive
Io(Error)
Filesystem or stream I/O failure.
InputPath
Input file or directory does not exist.
InvalidInput(String)
Invalid caller input with a human-readable explanation. See the type-level docs for the design rationale.
InvalidFormat(FormatDefect)
Encrypted file or key-file structure is invalid, truncated, or corrupted at the format level (not a crypto authentication issue).
UnsupportedVersion(UnsupportedVersion)
Encrypted file or key-file version is outside the range this release can read.
InvalidKdfParams(InvalidKdfParams)
KDF parameters read from an untrusted header are outside safe structural bounds.
KdfResourceCapExceeded
Argon2id memory cost from a header exceeds the caller-configured
local resource cap. Per FORMAT.md §3.2, exceeding a local cap
produces a distinct resource-cap error rather than a generic
malformed-file error. Distinct from
InvalidKdfParams (structurally invalid params): here the params
are well-formed but cost more than the caller is willing to
spend.
Fields
KdfTimeCostCapExceeded
Argon2id time cost (iteration count) from a header exceeds the
caller-configured local cap. The time-dimension counterpart of
Self::KdfResourceCapExceeded: the value is structurally valid
(within the v1 maximum) but asks for more iterations than the policy
allows. The default cap is the v1 maximum, so this variant is returned
only when a caller tightens KdfLimit below that maximum.
Fields
KdfLanesCapExceeded
Argon2id lane count (parallelism) from a header exceeds the
caller-configured local cap. The parallelism-dimension counterpart of
Self::KdfResourceCapExceeded; like Self::KdfTimeCostCapExceeded,
the default cap is the v1 maximum, so this variant is returned only when
a caller tightens KdfLimit below that maximum.
Fields
KdfBelowWriteFloor
Writer-side Argon2id memory cost is below the production floor.
Raised only when writing a .fcr or private.key (passphrase
encryption / key sealing), so a caller cannot accidentally seal an
artefact with weak Argon2id memory. The floor is a writer policy
only: readers accept any structurally valid, within-cap parameters,
so a file written before the floor existed always decrypts.
Fields
HeaderLenCapExceeded
header_len exceeds the caller-configured local cap. The
structural max (HEADER_LEN_MAX = 16 MiB per FORMAT.md §3.1)
is much higher; this fires when the header would exceed the
caller’s resource policy. Distinct from
FormatDefect::OversizedHeader (above structural max) per
FORMAT.md §3.2.
Fields
RecipientCountCapExceeded
recipient_count exceeds the caller-configured local cap. The
structural range (1..=4096 per FORMAT.md §3.2) is much
wider; this fires when the count would exceed the caller’s
resource policy. Distinct from
FormatDefect::RecipientCountOutOfRange (above structural
max).
Fields
RecipientBodyCapExceeded
A recipient entry’s body_len exceeds the local resource cap.
The structural max (BODY_LEN_MAX = 16 MiB per FORMAT.md
§3.3) is much higher; this fires when the body would exceed the
caller-configured local cap (FORMAT.md §3.2 recommends 8 KiB
for untrusted input). Distinct from
FormatDefect::MalformedRecipientEntry: the file is
structurally valid; the reader’s resource policy is the
constraint, and callers may raise the cap for trusted input.
Fields
RecipientStringCapExceeded
Bech32 recipient string exceeds the caller-configured local length cap.
Distinct from malformed public-key input: the string may be
structurally valid, but the reader’s resource policy rejected it.
The v1 structural ceiling is 20,000 ASCII characters (FORMAT.md
§7); the recommended local default is smaller. For valid recipient
strings, byte length and character count are the same because the
encoding is ASCII.
Fields
PrivateKeyWrappedSecretCapExceeded
A private.key file’s wrapped_secret_len exceeds the local
resource cap. The structural max (16 MiB per FORMAT.md §8) is
much higher; this fires when the wrapped secret would exceed the
reader’s resource policy (4 KiB by default — every v1 native key
type needs only 48 bytes). Distinct from
FormatDefect::MalformedPrivateKey: the file may be
structurally valid for a future key type; the local policy is
the constraint.
Fields
KeyFileUnlockFailed
Unlocking the private.key file failed AEAD authentication. The
key file is structurally valid, but either the supplied
passphrase does not decrypt it, or its cleartext fields have
been tampered with after the file was written. The AEAD
primitive cannot distinguish the two cases — the associated-data
binding introduced in the v1 private.key format catches tampering
cryptographically, but both failure modes surface as the same
error by design. The Display wording reflects both causes.
HeaderTampered
The header MAC failed after the passphrase recipient unwrapped
a candidate file_key.
Per FORMAT.md §3.7, a recipient unwrap is not accepted until
the candidate key verifies the header MAC. The failed MAC shows
the MAC-covered bytes changed after the file was written, but
cannot distinguish deliberate tampering from storage corruption.
This variant is the passphrase-mode verdict; the public-key
decrypt path reports the same condition as
Self::HeaderMacFailedAfterUnwrap, whatever the recipient
count.
HeaderMacFailedAfterUnwrap
In a public-key decrypt, a recipient slot unwrapped a
file_key, but the resulting header_key did not verify the
header MAC.
The unwrap is not final until the MAC verifies. The decrypt loop
still visits every supported slot; when at least one slot
unwrapped and none verified, this variant is the final verdict —
including for a single-recipient public-key file. The passphrase
counterpart is Self::HeaderTampered, and both render the same
message. The type_name field records which recipient type produced
the failed candidate; it is not shown in the message.
RecipientUnwrapFailed
No supported recipient slot opened with the supplied credential.
This means the credential is wrong or a recipient body was modified;
the AEAD result cannot distinguish those cases. This is the
FORMAT.md §12 wrong-passphrase/key class, separate from
Self::NoSupportedRecipient (a file with no supported recipient
type at all). An opened slot is not final until the header MAC verifies.
The rendered message follows type_name: wrong passphrase for
argon2id; for public-key recipients, no matching recipient or a
modified file. The type_name is not shown.
UnknownCriticalRecipient
The recipient list contains a recipient_flags.critical = 1
entry whose type_name is unknown to this implementation. Per
FORMAT.md §3.4 unknown critical entries must cause file
rejection (vs unknown non-critical, which are skipped).
NoSupportedRecipient
The file holds no recipient of a type this build can process — only
unknown non-critical entries. Detected while classifying the recipient
list, before any unwrap. A supported recipient whose unwrap fails is
Self::RecipientUnwrapFailed instead; FORMAT.md §12 lists the two
as separate failure classes.
DecryptorModeMismatch
The decryptor variant the caller chose does not match the file’s recipient mode (e.g. a passphrase decryptor invoked against a file sealed to public-key recipients, or vice versa).
The public API routes through crate::Decryptor::open, which
inspects the file structurally and hands back the matching variant —
so callers using the public surface cannot reach this error. It is
reserved for internal callers and any future plugin-style API
where a caller drives protocol::decrypt directly with a chosen
credential scheme.
expected is the mode the decryptor expected (its credential-scheme
mode); found is the mode classified from the file’s recipient
list. Distinct from Self::NoSupportedRecipient, which means
“the file’s recipient list contains no entry I can unlock,”
not “I’m the wrong tool for this file.”
Fields
expected: UnauthenticatedRecipientModeDecryptor mode selected by the caller.
found: UnauthenticatedRecipientModeRecipient mode classified from the .fcr header.
EmptyRecipientList
The caller provided no encryption recipients.
Encryptor::with_public_keys requires at least one public recipient;
otherwise there is no recipient entry to wrap the per-file file_key.
IncompatibleRecipients
The recipient list contains an entry whose mixing rule forbids
the combination. The most common v1 trigger is an
MixingPolicy::Exclusive native type (today only argon2id)
sharing a file with any other entry — per FORMAT.md §4.1 such
types must appear alone, and readers must reject the mix
structurally before running any KDF.
type_name identifies which entry triggered the rejection;
policy carries the MixingPolicy projection the offending
rule declared, so callers can pattern-match without parsing the
message. Future native types whose compatibility class differs
from the two fixed shorthand classes surface as
MixingPolicy::Custom { compatibility_class },
with the class identifier preserved in the variant payload. The
same variant surfaces from both the decrypt-side mixing
enforcement (run before any KDF) and the encrypt-side preflight
(run before any output bytes are written), with identical
wording in both directions.
Fields
policy: MixingPolicyMixing policy associated with the offending recipient type.
PayloadTampered
An encrypted payload chunk failed AEAD authentication during streaming decryption. The ciphertext was modified or corrupted after the header was authenticated.
PayloadTruncated
The encrypted stream ends before the final-flag chunk. Usually caused by a truncated file or an aborted download.
ExtraDataAfterPayload
Bytes remain after the final-flag chunk has been successfully decrypted. The file has unexpected trailing data.
PayloadChunkCountExceeded
The encrypted payload exceeds the 2^32-chunk cap mandated by
FORMAT.md §5. Surfaces from the writer-side cap (refuses to
emit the over-cap chunk) and the reader-side cap (refuses to
consume one).
MalformedArchive
Inner FCA archive header or manifest is structurally invalid:
bad magic, reserved flags set, no entries declared, truncated or
over-declared regions, a length-field overflow, trailing
manifest or content bytes, an unknown entry kind, a directory
entry declaring a size, an out-of-range mode word, a non-UTF-8
or empty entry path, or a declared total that does not match the
entry sizes. Fires inside the encrypted payload after the outer
container is accepted, and from the writer gate when a caller
hands the archive writer data it could never read back. The
reason names the violated rule. Per FORMAT.md §9.
UnsafeArchivePath
An archive entry path violates the FORMAT.md §9.6 grammar
(absolute, traversal, separator abuse, control bytes,
Windows-reserved names, and similar path-safety violations).
Fires on read for a malicious or corrupt archive and on write for a
source tree whose names FCA cannot represent.
Fields
InvalidArchiveTree
The archive manifest violates the FORMAT.md §9.7/§9.8 tree
rules: duplicate entries (exact or ASCII-case-insensitive),
multiple top-level roots, a missing parent or root entry, or a
child under a file path.
Fields
ArchiveEntryCountCapExceeded
Archive entry count exceeds the configured
ArchiveLimits::max_entry_count cap
(FORMAT.md §9.12). Like every Archive*CapExceeded variant,
this is a resource-policy rejection, not a format defect, and is
enforced identically on encrypt and decrypt.
Fields
ArchiveTotalBytesCapExceeded
Total plaintext bytes exceed the configured
ArchiveLimits::max_total_plaintext_bytes
cap (FORMAT.md §9.12).
Fields
ArchiveManifestLenCapExceeded
Serialized manifest length exceeds the configured
ArchiveLimits::max_manifest_bytes cap
(FORMAT.md §9.12).
Fields
ArchivePathBytesCapExceeded
An entry path’s UTF-8 byte length exceeds the configured
ArchiveLimits::max_path_bytes cap
(FORMAT.md §9.12). path is None when the cap fires on the
declared length before the path bytes have been parsed.
Fields
ArchivePathDepthCapExceeded
An entry path’s component depth exceeds the configured
ArchiveLimits::max_path_depth cap
(FORMAT.md §9.12).
Fields
ArchiveExtLenCapExceeded
The archive-level extension region exceeds the configured
ArchiveLimits::max_archive_ext_bytes
cap (FORMAT.md §9.12).
Fields
ArchiveEntryExtLenCapExceeded
A per-entry extension region exceeds the configured
ArchiveLimits::max_entry_ext_bytes cap
(FORMAT.md §9.12). path is None when the cap fires on the
declared length before the entry’s path has been parsed.
Fields
ArchiveTotalEntryExtCapExceeded
The summed per-entry extension regions exceed the configured
ArchiveLimits::max_total_entry_ext_bytes
cap (FORMAT.md §9.12).
Fields
InternalInvariant(&'static str)
A non-cryptographic invariant that should hold by construction did not hold. Triggered by state-machine misuse (e.g. using a stream after it was finalized), impossible-size checks, or internal encoding failures. If this fires, it indicates a library bug.
InternalCryptoFailure(&'static str)
A cryptographic primitive (AEAD encryption, HKDF expansion) returned an error even though the inputs were well-formed. Unreachable in practice for valid data; indicates either a library bug or a very rare underlying-crate failure.
Trait Implementations§
Source§impl Debug for CryptoError
impl Debug for CryptoError
Source§impl Display for CryptoError
impl Display for CryptoError
Source§impl Error for CryptoError
impl Error for CryptoError
Source§fn source(&self) -> Option<&(dyn Error + 'static)>
fn source(&self) -> Option<&(dyn Error + 'static)>
1.0.0 · Source§fn description(&self) -> &str
fn description(&self) -> &str
use the Display impl or to_string()