1use base64::Engine;
10use serde_json::Value;
11
12use crate::bytes::{be_u16, be_u64, bytes_n};
13use crate::error::{LuksError, Result};
14use crate::header::LUKS_MAGIC;
15
16pub const LUKS2_BIN_HDR_LEN: usize = 4096;
18
19#[derive(Debug, Clone, PartialEq, Eq)]
21pub enum Luks2Kdf {
22 Argon2 {
24 kind: String,
26 time: u32,
28 memory: u32,
30 cpus: u32,
32 salt: Vec<u8>,
34 },
35 Pbkdf2 {
37 hash: String,
39 iterations: u32,
41 salt: Vec<u8>,
43 },
44}
45
46#[derive(Debug, Clone, PartialEq, Eq)]
48pub struct Luks2Keyslot {
49 pub id: String,
51 pub key_size: usize,
53 pub af_stripes: u32,
55 pub af_hash: String,
57 pub area_offset: u64,
59 pub area_size: u64,
61 pub area_encryption: String,
63 pub kdf: Luks2Kdf,
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct Luks2Segment {
70 pub id: String,
72 pub offset: u64,
74 pub encryption: String,
76 pub sector_size: usize,
78 pub iv_tweak: u128,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct Luks2Digest {
85 pub kind: String,
87 pub hash: String,
89 pub iterations: u32,
91 pub salt: Vec<u8>,
93 pub digest: Vec<u8>,
95 pub keyslots: Vec<String>,
97}
98
99#[derive(Debug, Clone)]
101pub struct Luks2Header {
102 pub version: u16,
104 pub hdr_size: u64,
106 pub keyslots: Vec<Luks2Keyslot>,
108 pub segments: Vec<Luks2Segment>,
110 pub digests: Vec<Luks2Digest>,
112}
113
114fn as_u64(v: &Value) -> u64 {
116 match v {
117 Value::String(s) => s.parse().unwrap_or(0),
118 Value::Number(n) => n.as_u64().unwrap_or(0),
119 _ => 0,
120 }
121}
122
123fn b64(v: &Value) -> Vec<u8> {
124 v.as_str()
125 .and_then(|s| base64::engine::general_purpose::STANDARD.decode(s).ok())
126 .unwrap_or_default()
127}
128
129impl Luks2Header {
130 pub fn parse(data: &[u8]) -> Result<Self> {
137 let magic = bytes_n::<6>(data, 0);
138 if magic != LUKS_MAGIC {
139 return Err(LuksError::NotLuks { found: magic });
140 }
141 let version = be_u16(data, 6);
142 if version != 2 {
143 return Err(LuksError::UnsupportedVersion { version });
144 }
145 let hdr_size = be_u64(data, 8);
146
147 let end = (hdr_size as usize).min(data.len());
148 let json_area = data
149 .get(LUKS2_BIN_HDR_LEN..end)
150 .ok_or(LuksError::MalformedHeader {
151 what: "json area",
152 need: LUKS2_BIN_HDR_LEN,
153 got: data.len(),
154 })?;
155 let json_end = json_area
156 .iter()
157 .position(|&b| b == 0)
158 .unwrap_or(json_area.len());
159 let root: Value = serde_json::from_slice(&json_area[..json_end]).map_err(|_| {
160 LuksError::MalformedHeader {
161 what: "json parse",
162 need: json_end,
163 got: json_area.len(),
164 }
165 })?;
166
167 let keyslots = root
168 .get("keyslots")
169 .and_then(Value::as_object)
170 .map(|m| {
171 m.iter()
172 .filter_map(|(id, v)| parse_keyslot(id, v))
173 .collect()
174 })
175 .unwrap_or_default();
176 let segments = root
177 .get("segments")
178 .and_then(Value::as_object)
179 .map(|m| {
180 m.iter()
181 .filter_map(|(id, v)| parse_segment(id, v))
182 .collect()
183 })
184 .unwrap_or_default();
185 let digests = root
186 .get("digests")
187 .and_then(Value::as_object)
188 .map(|m| m.values().filter_map(parse_digest).collect())
189 .unwrap_or_default();
190
191 Ok(Luks2Header {
192 version,
193 hdr_size,
194 keyslots,
195 segments,
196 digests,
197 })
198 }
199
200 #[must_use]
202 pub fn crypt_segment(&self) -> Option<&Luks2Segment> {
203 self.segments.iter().find(|s| !s.encryption.is_empty())
204 }
205}
206
207fn parse_keyslot(id: &str, v: &Value) -> Option<Luks2Keyslot> {
208 let af = v.get("af")?;
209 let area = v.get("area")?;
210 let kdf_v = v.get("kdf")?;
211 let kdf = match kdf_v.get("type").and_then(Value::as_str)? {
212 "pbkdf2" => Luks2Kdf::Pbkdf2 {
213 hash: kdf_v
214 .get("hash")
215 .and_then(Value::as_str)
216 .unwrap_or("sha256")
217 .to_string(),
218 iterations: as_u64(kdf_v.get("iterations")?) as u32,
219 salt: b64(kdf_v.get("salt")?),
220 },
221 kind @ ("argon2i" | "argon2id") => Luks2Kdf::Argon2 {
222 kind: kind.to_string(),
223 time: as_u64(kdf_v.get("time")?) as u32,
224 memory: as_u64(kdf_v.get("memory")?) as u32,
225 cpus: as_u64(kdf_v.get("cpus")?) as u32,
226 salt: b64(kdf_v.get("salt")?),
227 },
228 _ => return None,
229 };
230 Some(Luks2Keyslot {
231 id: id.to_string(),
232 key_size: as_u64(v.get("key_size")?) as usize,
233 af_stripes: as_u64(af.get("stripes")?) as u32,
234 af_hash: af
235 .get("hash")
236 .and_then(Value::as_str)
237 .unwrap_or("sha256")
238 .to_string(),
239 area_offset: as_u64(area.get("offset")?),
240 area_size: as_u64(area.get("size")?),
241 area_encryption: area.get("encryption").and_then(Value::as_str)?.to_string(),
242 kdf,
243 })
244}
245
246fn parse_segment(id: &str, v: &Value) -> Option<Luks2Segment> {
247 Some(Luks2Segment {
248 id: id.to_string(),
249 offset: as_u64(v.get("offset")?),
250 encryption: v.get("encryption").and_then(Value::as_str)?.to_string(),
251 sector_size: as_u64(v.get("sector_size")?) as usize,
252 iv_tweak: u128::from(as_u64(v.get("iv_tweak").unwrap_or(&Value::Null))),
253 })
254}
255
256fn parse_digest(v: &Value) -> Option<Luks2Digest> {
257 Some(Luks2Digest {
258 kind: v.get("type").and_then(Value::as_str)?.to_string(),
259 hash: v
260 .get("hash")
261 .and_then(Value::as_str)
262 .unwrap_or("sha256")
263 .to_string(),
264 iterations: as_u64(v.get("iterations").unwrap_or(&Value::Null)) as u32,
265 salt: b64(v.get("salt")?),
266 digest: b64(v.get("digest")?),
267 keyslots: v
268 .get("keyslots")
269 .and_then(Value::as_array)
270 .map(|a| {
271 a.iter()
272 .filter_map(|x| x.as_str().map(str::to_string))
273 .collect()
274 })
275 .unwrap_or_default(),
276 })
277}
278
279#[cfg(test)]
280mod tests {
281 use super::*;
282
283 fn build_luks2(json: &str) -> Vec<u8> {
284 let hdr_size = LUKS2_BIN_HDR_LEN + 12288;
285 let mut buf = vec![0u8; hdr_size];
286 buf[0..6].copy_from_slice(&LUKS_MAGIC);
287 buf[6..8].copy_from_slice(&2u16.to_be_bytes());
288 buf[8..16].copy_from_slice(&(hdr_size as u64).to_be_bytes());
289 buf[LUKS2_BIN_HDR_LEN..LUKS2_BIN_HDR_LEN + json.len()].copy_from_slice(json.as_bytes());
290 buf
291 }
292
293 const ORACLE_JSON: &str = r#"{
294 "keyslots":{"0":{"type":"luks2","key_size":64,
295 "af":{"type":"luks1","stripes":4000,"hash":"sha256"},
296 "area":{"type":"raw","offset":"32768","size":"258048","encryption":"aes-xts-plain64","key_size":64},
297 "kdf":{"type":"argon2id","time":4,"memory":32,"cpus":1,"salt":"pUcL4RKUCh/iGgNoo6PW95eKTZm6RsS4Ds7Kn4gCdVs="}}},
298 "segments":{"0":{"type":"crypt","offset":"16777216","size":"dynamic","iv_tweak":"0","encryption":"aes-xts-plain64","sector_size":4096}},
299 "digests":{"0":{"type":"pbkdf2","keyslots":["0"],"segments":["0"],"hash":"sha256","iterations":1000,
300 "salt":"IUaJeXA4vB2CruGetgh6GOlfgvB2WoD2UzWGb2K6KLc=","digest":"yqVonIxK8BFwHFIsUaacNsXcz65r6EnYBVgF2fDPgps="}}}"#;
301
302 #[test]
303 fn parses_luks2_keyslot_segment_digest() {
304 let h = Luks2Header::parse(&build_luks2(ORACLE_JSON)).unwrap();
305 assert_eq!(h.version, 2);
306 assert_eq!(h.keyslots.len(), 1);
307 let k = &h.keyslots[0];
308 assert_eq!(k.key_size, 64);
309 assert_eq!(k.af_stripes, 4000);
310 assert_eq!(k.area_offset, 32768);
311 assert_eq!(k.area_encryption, "aes-xts-plain64");
312 match &k.kdf {
313 Luks2Kdf::Argon2 {
314 kind,
315 time,
316 memory,
317 cpus,
318 salt,
319 } => {
320 assert_eq!(kind, "argon2id");
321 assert_eq!((*time, *memory, *cpus), (4, 32, 1));
322 assert_eq!(salt.len(), 32);
323 }
324 Luks2Kdf::Pbkdf2 { .. } => panic!("expected argon2id"), }
326 let seg = h.crypt_segment().unwrap();
327 assert_eq!(seg.offset, 16_777_216);
328 assert_eq!(seg.sector_size, 4096);
329 assert_eq!(seg.iv_tweak, 0);
330 assert_eq!(h.digests.len(), 1);
331 assert_eq!(h.digests[0].kind, "pbkdf2");
332 assert_eq!(h.digests[0].digest.len(), 32);
333 assert_eq!(h.digests[0].keyslots, vec!["0".to_string()]);
334 }
335
336 #[test]
337 fn rejects_non_luks2() {
338 assert!(matches!(
339 Luks2Header::parse(&[0u8; 5000]).unwrap_err(),
340 LuksError::NotLuks { .. }
341 ));
342 }
343
344 #[test]
345 fn rejects_wrong_version() {
346 let mut b = build_luks2(ORACLE_JSON);
347 b[6..8].copy_from_slice(&1u16.to_be_bytes());
348 assert!(matches!(
349 Luks2Header::parse(&b).unwrap_err(),
350 LuksError::UnsupportedVersion { version: 1 }
351 ));
352 }
353
354 #[test]
355 fn rejects_bad_json() {
356 let b = build_luks2("{not valid json");
357 assert!(matches!(
358 Luks2Header::parse(&b).unwrap_err(),
359 LuksError::MalformedHeader { .. }
360 ));
361 }
362
363 #[test]
364 fn parses_pbkdf2_keyslot_variant() {
365 let json = r#"{"keyslots":{"0":{"key_size":32,
366 "af":{"stripes":4000,"hash":"sha1"},
367 "area":{"offset":"32768","size":"128000","encryption":"aes-xts-plain64"},
368 "kdf":{"type":"pbkdf2","hash":"sha256","iterations":50000,"salt":"IUaJeXA4vB2CruGetgh6GOlfgvB2WoD2UzWGb2K6KLc="}}},
369 "segments":{},"digests":{}}"#;
370 let h = Luks2Header::parse(&build_luks2(json)).unwrap();
371 match &h.keyslots[0].kdf {
372 Luks2Kdf::Pbkdf2 {
373 hash,
374 iterations,
375 salt,
376 } => {
377 assert_eq!(hash, "sha256");
378 assert_eq!(*iterations, 50000);
379 assert_eq!(salt.len(), 32);
380 }
381 Luks2Kdf::Argon2 { .. } => panic!("expected pbkdf2"), }
383 }
384
385 #[test]
386 fn as_u64_non_numeric_field_defaults_zero() {
387 let json = r#"{"keyslots":{"0":{"key_size":true,
389 "af":{"stripes":4000,"hash":"sha256"},
390 "area":{"offset":"32768","size":"128000","encryption":"aes-xts-plain64"},
391 "kdf":{"type":"pbkdf2","hash":"sha256","iterations":5,"salt":"AAAA"}}},
392 "segments":{},"digests":{}}"#;
393 let h = Luks2Header::parse(&build_luks2(json)).unwrap();
394 assert_eq!(h.keyslots.len(), 1);
395 assert_eq!(h.keyslots[0].key_size, 0);
396 }
397
398 #[test]
399 fn unknown_kdf_type_drops_keyslot() {
400 let json = r#"{"keyslots":{"0":{"key_size":64,
403 "af":{"stripes":4000,"hash":"sha256"},
404 "area":{"offset":"32768","size":"128000","encryption":"aes-xts-plain64"},
405 "kdf":{"type":"scrypt","salt":"AAAA"}}},
406 "segments":{},"digests":{}}"#;
407 let h = Luks2Header::parse(&build_luks2(json)).unwrap();
408 assert!(h.keyslots.is_empty());
409 }
410}