1use crate::bytes::{be_u16, be_u32, bytes_n, cstr};
17use crate::error::{LuksError, Result};
18
19pub const LUKS_MAGIC: [u8; 6] = [b'L', b'U', b'K', b'S', 0xba, 0xbe];
21pub const LUKS1_PHDR_LEN: usize = 592;
23pub const LUKS_NUM_KEYS: usize = 8;
25pub const KEYSLOT_LEN: usize = 48;
27pub const MK_DIGEST_LEN: usize = 20;
29pub const SALT_LEN: usize = 32;
31pub const KEY_ENABLED: u32 = 0x00AC_71F3;
33pub const KEY_DISABLED: u32 = 0x0000_DEAD;
35pub const SECTOR: u64 = 512;
37
38#[derive(Debug, Clone, PartialEq, Eq)]
40pub struct Keyslot {
41 pub active: u32,
43 pub iterations: u32,
45 pub salt: [u8; SALT_LEN],
47 pub key_material_offset: u32,
49 pub stripes: u32,
51}
52
53impl Keyslot {
54 #[must_use]
56 pub fn is_active(&self) -> bool {
57 self.active == KEY_ENABLED
58 }
59
60 #[must_use]
63 pub fn is_disabled(&self) -> bool {
64 self.active == KEY_DISABLED
65 }
66}
67
68#[derive(Debug, Clone)]
70pub struct Luks1Header {
71 pub version: u16,
73 pub cipher_name: String,
75 pub cipher_mode: String,
77 pub hash_spec: String,
79 pub payload_offset: u32,
81 pub key_bytes: u32,
83 pub mk_digest: [u8; MK_DIGEST_LEN],
85 pub mk_digest_salt: [u8; SALT_LEN],
87 pub mk_digest_iter: u32,
89 pub uuid: String,
91 pub keyslots: [Keyslot; LUKS_NUM_KEYS],
93}
94
95impl Luks1Header {
96 pub fn parse(data: &[u8]) -> Result<Self> {
102 let magic = bytes_n::<6>(data, 0);
103 if magic != LUKS_MAGIC {
104 return Err(LuksError::NotLuks { found: magic });
105 }
106 if data.len() < LUKS1_PHDR_LEN {
107 return Err(LuksError::MalformedHeader {
108 what: "phdr",
109 need: LUKS1_PHDR_LEN,
110 got: data.len(),
111 });
112 }
113 let version = be_u16(data, 6);
114 if version != 1 {
115 return Err(LuksError::UnsupportedVersion { version });
116 }
117
118 let keyslots = std::array::from_fn(|i| {
119 let base = 208 + i * KEYSLOT_LEN;
120 Keyslot {
121 active: be_u32(data, base),
122 iterations: be_u32(data, base + 4),
123 salt: bytes_n::<SALT_LEN>(data, base + 8),
124 key_material_offset: be_u32(data, base + 40),
125 stripes: be_u32(data, base + 44),
126 }
127 });
128
129 Ok(Luks1Header {
130 version,
131 cipher_name: cstr(data, 8, 32),
132 cipher_mode: cstr(data, 40, 32),
133 hash_spec: cstr(data, 72, 32),
134 payload_offset: be_u32(data, 104),
135 key_bytes: be_u32(data, 108),
136 mk_digest: bytes_n::<MK_DIGEST_LEN>(data, 112),
137 mk_digest_salt: bytes_n::<SALT_LEN>(data, 132),
138 mk_digest_iter: be_u32(data, 164),
139 uuid: cstr(data, 168, 40),
140 keyslots,
141 })
142 }
143
144 pub fn active_keyslots(&self) -> impl Iterator<Item = &Keyslot> {
146 self.keyslots.iter().filter(|k| k.is_active())
147 }
148
149 #[must_use]
151 pub fn payload_byte_offset(&self) -> u64 {
152 u64::from(self.payload_offset) * SECTOR
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 fn build_header() -> Vec<u8> {
162 let mut h = vec![0u8; LUKS1_PHDR_LEN];
163 h[0..6].copy_from_slice(&LUKS_MAGIC);
164 h[6..8].copy_from_slice(&1u16.to_be_bytes());
165 h[8..11].copy_from_slice(b"aes");
166 h[40..51].copy_from_slice(b"xts-plain64");
167 h[72..78].copy_from_slice(b"sha256");
168 h[104..108].copy_from_slice(&4096u32.to_be_bytes()); h[108..112].copy_from_slice(&64u32.to_be_bytes()); h[112..132].copy_from_slice(&[0xAB; 20]); h[132..164].copy_from_slice(&[0xCD; 32]); h[164..168].copy_from_slice(&1000u32.to_be_bytes()); h[168..204].copy_from_slice(b"b22690e1-a392-4ecc-83b1-c1cf21200116");
174 let b = 208;
176 h[b..b + 4].copy_from_slice(&KEY_ENABLED.to_be_bytes());
177 h[b + 4..b + 8].copy_from_slice(&50000u32.to_be_bytes());
178 h[b + 8..b + 40].copy_from_slice(&[0x11; 32]);
179 h[b + 40..b + 44].copy_from_slice(&8u32.to_be_bytes());
180 h[b + 44..b + 48].copy_from_slice(&4000u32.to_be_bytes());
181 let b1 = 208 + KEYSLOT_LEN;
183 h[b1..b1 + 4].copy_from_slice(&KEY_DISABLED.to_be_bytes());
184 h
185 }
186
187 #[test]
188 fn parses_luks1_header_fields() {
189 let h = Luks1Header::parse(&build_header()).unwrap();
190 assert_eq!(h.version, 1);
191 assert_eq!(h.cipher_name, "aes");
192 assert_eq!(h.cipher_mode, "xts-plain64");
193 assert_eq!(h.hash_spec, "sha256");
194 assert_eq!(h.payload_offset, 4096);
195 assert_eq!(h.payload_byte_offset(), 4096 * 512);
196 assert_eq!(h.key_bytes, 64);
197 assert_eq!(h.mk_digest, [0xAB; 20]);
198 assert_eq!(h.mk_digest_salt, [0xCD; 32]);
199 assert_eq!(h.mk_digest_iter, 1000);
200 assert_eq!(h.uuid, "b22690e1-a392-4ecc-83b1-c1cf21200116");
201 }
202
203 #[test]
204 fn parses_keyslots_and_active_filter() {
205 let h = Luks1Header::parse(&build_header()).unwrap();
206 assert!(h.keyslots[0].is_active());
207 assert_eq!(h.keyslots[0].iterations, 50000);
208 assert_eq!(h.keyslots[0].salt, [0x11; 32]);
209 assert_eq!(h.keyslots[0].key_material_offset, 8);
210 assert_eq!(h.keyslots[0].stripes, 4000);
211 assert!(!h.keyslots[1].is_active());
212 assert!(h.keyslots[1].is_disabled());
213 assert_eq!(h.active_keyslots().count(), 1);
214 }
215
216 #[test]
217 fn rejects_non_luks() {
218 let err = Luks1Header::parse(&[0u8; LUKS1_PHDR_LEN]).unwrap_err();
219 assert!(matches!(err, LuksError::NotLuks { .. }));
220 }
221
222 #[test]
223 fn rejects_bad_version() {
224 let mut h = build_header();
225 h[6..8].copy_from_slice(&9u16.to_be_bytes());
226 assert!(matches!(
227 Luks1Header::parse(&h).unwrap_err(),
228 LuksError::UnsupportedVersion { version: 9 }
229 ));
230 }
231
232 #[test]
233 fn rejects_truncated() {
234 let mut h = build_header();
235 h.truncate(100);
236 assert!(matches!(
237 Luks1Header::parse(&h).unwrap_err(),
238 LuksError::MalformedHeader { .. }
239 ));
240 }
241}