1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
//! Error type for LUKS parsing and unlocking.
use std::io;
/// Result alias for `luks-core`.
pub type Result<T> = std::result::Result<T, LuksError>;
/// A LUKS parse or unlock failure. Every variant names the offending value so an
/// investigator can act on it (never a bare "invalid").
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum LuksError {
/// The `LUKS\xba\xbe` magic is absent — not a LUKS container.
#[error("not a LUKS container: magic is {found:02x?}, expected 4c554b53babe")]
NotLuks {
/// The first six bytes actually found.
found: [u8; 6],
},
/// The header version is neither 1 nor 2.
#[error("unsupported LUKS version {version} (only 1 and 2 are supported)")]
UnsupportedVersion {
/// The version field value.
version: u16,
},
/// The cipher/mode/hash combination has no validated decrypt path.
#[error("unsupported {what}: {value:?}")]
Unsupported {
/// Which axis is unsupported (cipher, mode, hash).
what: &'static str,
/// The offending value verbatim.
value: String,
},
/// The keyslot demands more key-derivation work than the wall-clock budget
/// allows. The iteration count comes from the header, so a crafted container
/// can ask for billions of rounds; refusing is the only way the tool stays
/// responsive on hostile input.
///
/// A *refusal*, never a silent reduction: deriving with fewer rounds than the
/// header specifies produces a different key, which would report a wrong
/// passphrase for a container that would in fact have opened.
#[error(
"key derivation would take about {projected_secs}s for {iterations} iterations, \
over the {budget_secs}s budget — refusing rather than clamping, because a \
reduced iteration count derives a different key"
)]
DerivationBudgetExceeded {
/// The iteration count the header asked for, verbatim.
iterations: u32,
/// Projected cost on this machine, measured from a calibration run.
projected_secs: u64,
/// The budget that was exceeded.
budget_secs: u64,
},
/// The keyslot's anti-forensic stripe count is not plausible.
///
/// `stripes` is a header field, so the container chooses it. It multiplies
/// the key size to give the key-material length the caller then allocates,
/// and it is the trip count of the AF merge loop — so an unbounded value is
/// both an allocation and a spin, on the container's say-so.
///
/// LUKS1 fixes the count at 4000 and cryptsetup writes the same for LUKS2,
/// so the ceiling sits far above anything genuine.
#[error(
"keyslot declares {stripes} anti-forensic stripes, over the {max} ceiling \
— refusing before sizing a {} byte buffer",
.stripes.saturating_mul(*.block_size)
)]
ImplausibleStripes {
/// The stripe count the header asked for, verbatim.
stripes: usize,
/// The ceiling that was exceeded.
max: usize,
/// The key size the count would have been multiplied by.
block_size: usize,
},
/// The Argon2 keyslot demands more memory than the ceiling allows.
///
/// Separate from [`Self::DerivationBudgetExceeded`] because memory cannot be
/// bounded the same way: a time cost can be measured and projected, but
/// *attempting* an oversized allocation is itself the harm — the process is
/// killed by the OS before any deadline could fire. `argon2::Params` caps
/// `m_cost` at `u32::MAX` 1 KiB blocks (4 TiB) and the header chooses the
/// value, so the ceiling has to be ours.
#[error(
"argon2 keyslot asks for {requested_kib} KiB of memory, over the \
{max_kib} KiB ceiling — refusing before allocating"
)]
DerivationMemoryExceeded {
/// The memory cost the header asked for, verbatim, in KiB blocks.
requested_kib: u32,
/// The ceiling that was exceeded, in KiB blocks.
max_kib: u32,
},
/// The header is structurally malformed (a field runs past the buffer).
#[error("malformed LUKS header: {what} (need {need} bytes, have {got})")]
MalformedHeader {
/// What was being read.
what: &'static str,
/// Bytes needed.
need: usize,
/// Bytes available.
got: usize,
},
/// No keyslot could be unlocked with the supplied passphrase, or the derived
/// master key failed the mk-digest check (wrong passphrase).
#[error("authentication failed: no keyslot matched the passphrase")]
AuthenticationFailed,
/// The container carries no active (enabled) keyslot.
#[error("no active keyslot in the LUKS header")]
NoActiveKeyslot,
/// An I/O error reading the container.
#[error("I/O error: {0}")]
Io(#[from] io::Error),
}