1use std::io::{Read, Seek, SeekFrom};
9
10use crate::af;
11use crate::crypto::{derive_key, derive_key_argon2, xts_decrypt_area, Argon2Params};
12use crate::error::{LuksError, Result};
13use crate::header::{Luks1Header, LUKS1_PHDR_LEN, MK_DIGEST_LEN, SECTOR};
14use crate::header2::{Luks2Header, Luks2Kdf, LUKS2_BIN_HDR_LEN};
15
16pub struct LuksVolume;
18
19#[derive(Debug, Clone)]
21pub struct VolumeInfo {
22 pub version: u16,
24 pub cipher_name: String,
26 pub cipher_mode: String,
28 pub key_bytes: u32,
30 pub sector_size: usize,
32}
33
34fn split_encryption(enc: &str) -> (String, String) {
37 match enc.split_once('-') {
38 Some((name, mode)) => (name.to_string(), mode.to_string()),
39 None => (enc.to_string(), String::new()),
40 }
41}
42
43impl LuksVolume {
44 pub fn unlock_with_passphrase<R: Read + Seek>(
50 mut reader: R,
51 passphrase: &[u8],
52 ) -> Result<DecryptedPayload<R>> {
53 let mut ver = [0u8; 8];
54 reader.seek(SeekFrom::Start(0))?;
55 read_fill(&mut reader, &mut ver)?;
56 reader.seek(SeekFrom::Start(0))?;
57 match crate::bytes::be_u16(&ver, 6) {
58 1 => Self::unlock1_with_passphrase(reader, passphrase),
59 2 => Self::unlock2_with_passphrase(reader, passphrase),
60 other => {
61 if ver.starts_with(&crate::header::LUKS_MAGIC) {
62 Err(LuksError::UnsupportedVersion { version: other })
63 } else {
64 Err(LuksError::NotLuks {
65 found: crate::bytes::bytes_n::<6>(&ver, 0),
66 })
67 }
68 }
69 }
70 }
71
72 pub fn unlock1_with_passphrase<R: Read + Seek>(
78 mut reader: R,
79 passphrase: &[u8],
80 ) -> Result<DecryptedPayload<R>> {
81 let mut hdr_buf = vec![0u8; LUKS1_PHDR_LEN];
82 reader.seek(SeekFrom::Start(0))?;
83 read_fill(&mut reader, &mut hdr_buf)?;
84 let header = Luks1Header::parse(&hdr_buf)?;
85 if header.active_keyslots().next().is_none() {
86 return Err(LuksError::NoActiveKeyslot);
87 }
88 let key_bytes = header.key_bytes as usize;
89 let master_key = recover_master_key1(&mut reader, &header, passphrase, key_bytes)?;
90 let total_size = reader.seek(SeekFrom::End(0))?;
91 Ok(DecryptedPayload {
92 reader,
93 master_key,
94 cipher_mode: header.cipher_mode.clone(),
95 payload_offset: header.payload_byte_offset(),
96 sector_size: SECTOR as usize,
97 iv_tweak: 0,
98 total_size,
99 position: 0,
100 info: VolumeInfo {
101 version: 1,
102 cipher_name: header.cipher_name.clone(),
103 cipher_mode: header.cipher_mode.clone(),
104 key_bytes: header.key_bytes,
105 sector_size: SECTOR as usize,
106 },
107 })
108 }
109
110 pub fn unlock2_with_passphrase<R: Read + Seek>(
116 mut reader: R,
117 passphrase: &[u8],
118 ) -> Result<DecryptedPayload<R>> {
119 let mut bin = vec![0u8; LUKS2_BIN_HDR_LEN];
121 reader.seek(SeekFrom::Start(0))?;
122 read_fill(&mut reader, &mut bin)?;
123 let hdr_size = crate::bytes::be_u64(&bin, 8).max(LUKS2_BIN_HDR_LEN as u64) as usize;
124 let mut full = vec![0u8; hdr_size];
125 reader.seek(SeekFrom::Start(0))?;
126 read_available(&mut reader, &mut full)?;
127 let header = Luks2Header::parse(&full)?;
128
129 let segment = header
130 .crypt_segment()
131 .cloned()
132 .ok_or(LuksError::NoActiveKeyslot)?;
133 if header.keyslots.is_empty() {
134 return Err(LuksError::NoActiveKeyslot);
135 }
136
137 let master_key = recover_master_key2(&mut reader, &header, passphrase)?;
138 let total_size = reader.seek(SeekFrom::End(0))?;
139 let (cipher_name, cipher_mode) = split_encryption(&segment.encryption);
140 let key_bytes = master_key.len() as u32;
141 Ok(DecryptedPayload {
142 reader,
143 master_key,
144 cipher_mode: cipher_mode.clone(),
145 payload_offset: segment.offset,
146 sector_size: segment.sector_size.max(512),
147 iv_tweak: segment.iv_tweak,
148 total_size,
149 position: 0,
150 info: VolumeInfo {
151 version: 2,
152 cipher_name,
153 cipher_mode,
154 key_bytes,
155 sector_size: segment.sector_size.max(512),
156 },
157 })
158 }
159}
160
161fn recover_master_key1<R: Read + Seek>(
164 reader: &mut R,
165 header: &Luks1Header,
166 passphrase: &[u8],
167 key_bytes: usize,
168) -> Result<Vec<u8>> {
169 for slot in header.active_keyslots() {
170 let slot_key = derive_key(
171 &header.hash_spec,
172 passphrase,
173 &slot.salt,
174 slot.iterations,
175 key_bytes,
176 )?;
177 let material_len = af::material_len(key_bytes, slot.stripes as usize);
178 let mut material = vec![0u8; material_len];
179 reader.seek(SeekFrom::Start(
180 u64::from(slot.key_material_offset) * SECTOR,
181 ))?;
182 if read_available(reader, &mut material)? < material_len {
183 continue;
184 }
185 xts_decrypt_area(&header.cipher_mode, &slot_key, &mut material, 512, 0)?;
186 let candidate = af::merge(
187 &header.hash_spec,
188 &material,
189 key_bytes,
190 slot.stripes as usize,
191 )?; let digest = derive_key(
193 &header.hash_spec,
194 &candidate,
195 &header.mk_digest_salt,
196 header.mk_digest_iter,
197 MK_DIGEST_LEN,
198 )?; if digest == header.mk_digest {
200 return Ok(candidate);
201 }
202 }
203 Err(LuksError::AuthenticationFailed)
204}
205
206fn recover_master_key2<R: Read + Seek>(
209 reader: &mut R,
210 header: &Luks2Header,
211 passphrase: &[u8],
212) -> Result<Vec<u8>> {
213 for slot in &header.keyslots {
214 let slot_key = match &slot.kdf {
215 Luks2Kdf::Argon2 {
216 kind,
217 time,
218 memory,
219 cpus,
220 salt,
221 } => derive_key_argon2(
222 &Argon2Params {
223 kind,
224 time: *time,
225 memory: *memory,
226 cpus: *cpus,
227 salt,
228 },
229 passphrase,
230 slot.key_size,
231 )?,
232 Luks2Kdf::Pbkdf2 {
233 hash,
234 iterations,
235 salt,
236 } => derive_key(hash, passphrase, salt, *iterations, slot.key_size)?,
237 };
238
239 let (_, area_mode) = split_encryption(&slot.area_encryption);
240 let material_len = af::material_len(slot.key_size, slot.af_stripes as usize);
241 let mut material = vec![0u8; material_len];
242 reader.seek(SeekFrom::Start(slot.area_offset))?;
243 if read_available(reader, &mut material)? < material_len {
244 continue;
245 }
246 xts_decrypt_area(&area_mode, &slot_key, &mut material, 512, 0)?;
247 let candidate = af::merge(
248 &slot.af_hash,
249 &material,
250 slot.key_size,
251 slot.af_stripes as usize,
252 )?;
253
254 for dig in &header.digests {
255 if dig.kind == "pbkdf2" && dig.keyslots.iter().any(|k| k == &slot.id) {
256 let d = derive_key(
257 &dig.hash,
258 &candidate,
259 &dig.salt,
260 dig.iterations,
261 dig.digest.len(),
262 )?;
263 if d == dig.digest {
264 return Ok(candidate);
265 }
266 }
267 }
268 }
269 Err(LuksError::AuthenticationFailed)
270}
271
272pub struct DecryptedPayload<R> {
274 reader: R,
275 master_key: Vec<u8>,
276 cipher_mode: String,
277 payload_offset: u64,
278 sector_size: usize,
279 iv_tweak: u128,
280 total_size: u64,
281 position: u64,
282 info: VolumeInfo,
283}
284
285impl<R: Read + Seek> DecryptedPayload<R> {
286 #[must_use]
288 pub fn info(&self) -> &VolumeInfo {
289 &self.info
290 }
291
292 #[must_use]
294 pub fn master_key(&self) -> &[u8] {
295 &self.master_key
296 }
297
298 #[must_use]
300 pub fn payload_size(&self) -> u64 {
301 self.total_size.saturating_sub(self.payload_offset)
302 }
303
304 pub fn read_at(&mut self, offset: u64, buf: &mut [u8]) -> Result<()> {
310 let ss = self.sector_size as u64;
311 let step = (self.sector_size / 512).max(1) as u128;
312 let mut done = 0usize;
313 while done < buf.len() {
314 let pos = offset + done as u64;
315 let unit = pos / ss;
316 let within = (pos % ss) as usize;
317 let physical = self.payload_offset + unit * ss;
318
319 let mut ct = vec![0u8; self.sector_size];
320 self.reader.seek(SeekFrom::Start(physical))?;
321 read_available(&mut self.reader, &mut ct)?;
322 let tweak = self.iv_tweak + u128::from(unit) * step;
323 xts_decrypt_area(
324 &self.cipher_mode,
325 &self.master_key,
326 &mut ct,
327 self.sector_size,
328 tweak,
329 )?; let take = (self.sector_size - within).min(buf.len() - done);
332 buf[done..done + take].copy_from_slice(&ct[within..within + take]);
333 done += take;
334 }
335 Ok(())
336 }
337}
338
339impl<R: Read + Seek> Read for DecryptedPayload<R> {
340 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
341 let size = self.payload_size();
342 if self.position >= size {
343 return Ok(0);
344 }
345 let n = (buf.len() as u64).min(size - self.position) as usize;
346 self.read_at(self.position, &mut buf[..n])
347 .map_err(|e| std::io::Error::other(e.to_string()))?;
348 self.position += n as u64;
349 Ok(n)
350 }
351}
352
353impl<R: Read + Seek> Seek for DecryptedPayload<R> {
354 fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
355 let size = self.payload_size();
356 let new = match pos {
357 SeekFrom::Start(o) => i128::from(o),
358 SeekFrom::End(o) => i128::from(size) + i128::from(o),
359 SeekFrom::Current(o) => i128::from(self.position) + i128::from(o),
360 };
361 if new < 0 {
362 return Err(std::io::Error::new(
363 std::io::ErrorKind::InvalidInput,
364 "seek before start",
365 ));
366 }
367 self.position = new as u64;
368 Ok(self.position)
369 }
370}
371
372fn read_fill<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<()> {
374 reader.read_exact(buf)?;
375 Ok(())
376}
377
378fn read_available<R: Read>(reader: &mut R, buf: &mut [u8]) -> Result<usize> {
381 let mut filled = 0;
382 while filled < buf.len() {
383 match reader.read(&mut buf[filled..]) {
384 Ok(0) => break,
385 Ok(n) => filled += n,
386 Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => {}
387 Err(e) => return Err(e.into()),
388 }
389 }
390 for b in &mut buf[filled..] {
391 *b = 0;
392 }
393 Ok(filled)
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
404 use crate::af;
405 use crate::crypto::{derive_key, derive_key_argon2, Argon2Params};
406 use crate::header::{
407 KEYSLOT_LEN, KEY_DISABLED, KEY_ENABLED, LUKS1_PHDR_LEN, LUKS_MAGIC, LUKS_NUM_KEYS,
408 };
409 use crate::header2::LUKS2_BIN_HDR_LEN;
410 use aes::cipher::KeyInit;
411 use aes::Aes256;
412 use base64::Engine;
413 use std::io::Cursor;
414 use xts_mode::Xts128;
415
416 const PASS: &[u8] = b"luks-TEST";
417
418 fn xts_encrypt(key: &[u8], buf: &mut [u8], unit_size: usize, base: u128) {
423 let step = (unit_size / 512).max(1) as u128;
424 let (k1, k2) = key.split_at(32);
425 let xts = Xts128::<Aes256>::new(Aes256::new(k1.into()), Aes256::new(k2.into()));
426 for (u, chunk) in buf.chunks_mut(unit_size).enumerate() {
427 let tweak = (base + u as u128 * step).to_le_bytes();
428 xts.encrypt_sector(chunk, tweak);
429 }
430 }
431
432 fn pbkdf2_sha256(pw: &[u8], salt: &[u8], iters: u32, len: usize) -> Vec<u8> {
433 let mut out = vec![0u8; len];
434 pbkdf2::pbkdf2_hmac::<sha2::Sha256>(pw, salt, iters, &mut out);
435 out
436 }
437
438 fn known_plain(len: usize) -> Vec<u8> {
439 (0..len).map(|i| (i as u8) ^ 0x3c).collect()
440 }
441
442 const L1_KM_OFF: u32 = 2; const L1_PAYLOAD_OFF: u32 = 4; const L1_STRIPES: u32 = 8;
447 const L1_KEY_BYTES: u32 = 64;
448 const L1_ITERS: u32 = 5;
449 const L1_MK_ITER: u32 = 7;
450
451 fn build_luks1(master: &[u8]) -> (Vec<u8>, Vec<u8>) {
454 let salt = [0x11u8; 32];
455 let mk_salt = [0x22u8; 32];
456 let slot_key = pbkdf2_sha256(PASS, &salt, L1_ITERS, L1_KEY_BYTES as usize);
457
458 let mut material =
459 af::af_split::<sha2::Sha256>(master, L1_KEY_BYTES as usize, L1_STRIPES as usize, 0x5a);
460 xts_encrypt(&slot_key, &mut material, 512, 0);
461
462 let mk_digest = pbkdf2_sha256(master, &mk_salt, L1_MK_ITER, 20);
463
464 let mut payload = known_plain(3 * 512);
465 let plain = payload.clone();
466 xts_encrypt(master, &mut payload, 512, 0);
467
468 let payload_byte = L1_PAYLOAD_OFF as usize * 512;
469 let mut img = vec![0u8; payload_byte + payload.len()];
470 img[0..6].copy_from_slice(&LUKS_MAGIC);
471 img[6..8].copy_from_slice(&1u16.to_be_bytes());
472 img[8..11].copy_from_slice(b"aes");
473 img[40..51].copy_from_slice(b"xts-plain64");
474 img[72..78].copy_from_slice(b"sha256");
475 img[104..108].copy_from_slice(&L1_PAYLOAD_OFF.to_be_bytes());
476 img[108..112].copy_from_slice(&L1_KEY_BYTES.to_be_bytes());
477 img[112..132].copy_from_slice(&mk_digest);
478 img[132..164].copy_from_slice(&mk_salt);
479 img[164..168].copy_from_slice(&L1_MK_ITER.to_be_bytes());
480 img[168..204].copy_from_slice(b"b22690e1-a392-4ecc-83b1-c1cf21200116");
481
482 for i in 0..LUKS_NUM_KEYS {
483 let base = 208 + i * KEYSLOT_LEN;
484 if i == 0 {
485 img[base..base + 4].copy_from_slice(&KEY_ENABLED.to_be_bytes());
486 img[base + 4..base + 8].copy_from_slice(&L1_ITERS.to_be_bytes());
487 img[base + 8..base + 40].copy_from_slice(&salt);
488 img[base + 40..base + 44].copy_from_slice(&L1_KM_OFF.to_be_bytes());
489 img[base + 44..base + 48].copy_from_slice(&L1_STRIPES.to_be_bytes());
490 } else {
491 img[base..base + 4].copy_from_slice(&KEY_DISABLED.to_be_bytes());
492 }
493 }
494
495 let km_byte = L1_KM_OFF as usize * 512;
496 img[km_byte..km_byte + material.len()].copy_from_slice(&material);
497 img[payload_byte..payload_byte + payload.len()].copy_from_slice(&payload);
498 assert!(
499 LUKS1_PHDR_LEN <= km_byte,
500 "phdr must not overlap key material"
501 );
502 (img, plain)
503 }
504
505 #[test]
506 fn luks1_roundtrip_via_autodetect() {
507 let master: Vec<u8> = (0..64u8)
508 .map(|x| x.wrapping_mul(7).wrapping_add(3))
509 .collect();
510 let (img, plain) = build_luks1(&master);
511 let mut vol = LuksVolume::unlock_with_passphrase(Cursor::new(img), PASS).unwrap();
512
513 assert_eq!(vol.info().version, 1);
514 assert_eq!(vol.info().cipher_name, "aes");
515 assert_eq!(vol.info().cipher_mode, "xts-plain64");
516 assert_eq!(vol.info().key_bytes, 64);
517 assert_eq!(vol.info().sector_size, 512);
518 assert_eq!(vol.master_key(), &master[..]);
519 assert_eq!(vol.payload_size(), 3 * 512);
520
521 for lba in 0..3u64 {
522 let mut buf = [0u8; 512];
523 vol.read_at(lba * 512, &mut buf).unwrap();
524 assert_eq!(
525 &buf[..],
526 &plain[lba as usize * 512..lba as usize * 512 + 512]
527 );
528 }
529 }
530
531 #[test]
532 fn luks1_read_seek_traits() {
533 let master = vec![0xABu8; 64];
534 let (img, plain) = build_luks1(&master);
535 let mut vol = LuksVolume::unlock1_with_passphrase(Cursor::new(img), PASS).unwrap();
536
537 assert_eq!(vol.seek(SeekFrom::Start(512)).unwrap(), 512);
539 assert_eq!(vol.seek(SeekFrom::Current(-256)).unwrap(), 256);
540 let end = vol.seek(SeekFrom::End(0)).unwrap();
541 assert_eq!(end, 3 * 512);
542 assert!(vol.seek(SeekFrom::End(-1)).is_ok());
543 assert!(vol.seek(SeekFrom::Start(0)).is_ok());
544
545 let mut got = Vec::new();
547 std::io::Read::read_to_end(&mut vol, &mut got).unwrap();
548 assert_eq!(got, plain);
549 let mut z = [0u8; 16];
551 assert_eq!(std::io::Read::read(&mut vol, &mut z).unwrap(), 0);
552 }
553
554 #[test]
555 fn luks1_seek_before_start_errors() {
556 let master = vec![0x01u8; 64];
557 let (img, _) = build_luks1(&master);
558 let mut vol = LuksVolume::unlock1_with_passphrase(Cursor::new(img), PASS).unwrap();
559 assert!(vol.seek(SeekFrom::Start(0)).is_ok());
560 assert!(vol.seek(SeekFrom::Current(-1)).is_err());
561 }
562
563 #[test]
564 fn luks1_wrong_passphrase() {
565 let master = vec![0x5Cu8; 64];
566 let (img, _) = build_luks1(&master);
567 assert!(matches!(
568 LuksVolume::unlock1_with_passphrase(Cursor::new(img), b"wrong"),
569 Err(LuksError::AuthenticationFailed)
570 ));
571 }
572
573 #[test]
574 fn luks1_no_active_keyslot() {
575 let master = vec![0x5Cu8; 64];
576 let (mut img, _) = build_luks1(&master);
577 img[208..212].copy_from_slice(&KEY_DISABLED.to_be_bytes());
579 assert!(matches!(
580 LuksVolume::unlock1_with_passphrase(Cursor::new(img), PASS),
581 Err(LuksError::NoActiveKeyslot)
582 ));
583 }
584
585 #[test]
586 fn luks1_short_key_material_is_skipped() {
587 let mut img = vec![0u8; LUKS1_PHDR_LEN];
590 img[0..6].copy_from_slice(&LUKS_MAGIC);
591 img[6..8].copy_from_slice(&1u16.to_be_bytes());
592 img[8..11].copy_from_slice(b"aes");
593 img[40..51].copy_from_slice(b"xts-plain64");
594 img[72..78].copy_from_slice(b"sha256");
595 img[104..108].copy_from_slice(&4u32.to_be_bytes());
596 img[108..112].copy_from_slice(&64u32.to_be_bytes());
597 let base = 208;
598 img[base..base + 4].copy_from_slice(&KEY_ENABLED.to_be_bytes());
599 img[base + 4..base + 8].copy_from_slice(&5u32.to_be_bytes());
600 img[base + 40..base + 44].copy_from_slice(&1000u32.to_be_bytes()); img[base + 44..base + 48].copy_from_slice(&8u32.to_be_bytes());
602 assert!(matches!(
603 LuksVolume::unlock1_with_passphrase(Cursor::new(img), PASS),
604 Err(LuksError::AuthenticationFailed)
605 ));
606 }
607
608 #[test]
611 fn not_a_luks_container() {
612 assert!(matches!(
613 LuksVolume::unlock_with_passphrase(Cursor::new(vec![0u8; 8]), PASS),
614 Err(LuksError::NotLuks { .. })
615 ));
616 }
617
618 #[test]
619 fn unsupported_version() {
620 let mut img = vec![0u8; 8];
621 img[0..6].copy_from_slice(&LUKS_MAGIC);
622 img[6..8].copy_from_slice(&3u16.to_be_bytes());
623 assert!(matches!(
624 LuksVolume::unlock_with_passphrase(Cursor::new(img), PASS),
625 Err(LuksError::UnsupportedVersion { version: 3 })
626 ));
627 }
628
629 #[test]
630 fn split_encryption_without_dash() {
631 assert_eq!(split_encryption("aes"), ("aes".to_string(), String::new()));
632 assert_eq!(
633 split_encryption("aes-xts-plain64"),
634 ("aes".to_string(), "xts-plain64".to_string())
635 );
636 }
637
638 const L2_AREA_OFF: u64 = 8192;
641 const L2_SEG_OFF: u64 = 12288;
642 const L2_STRIPES: usize = 8;
643 const L2_DIG_ITER: u32 = 5;
644
645 fn build_luks2(
648 master: &[u8],
649 slot_key: &[u8],
650 kdf_json: &str,
651 sector_size: usize,
652 ) -> (Vec<u8>, Vec<u8>) {
653 let b64 = base64::engine::general_purpose::STANDARD;
654 let dig_salt = [0x22u8; 16];
655 let digest = pbkdf2_sha256(master, &dig_salt, L2_DIG_ITER, 32);
656
657 let mut material = af::af_split::<sha2::Sha256>(master, 64, L2_STRIPES, 0x5a);
658 xts_encrypt(slot_key, &mut material, 512, 0);
659
660 let mut payload = known_plain(sector_size); let plain = payload.clone();
662 xts_encrypt(master, &mut payload, sector_size, 0);
663
664 let json = format!(
665 concat!(
666 "{{\"keyslots\":{{\"0\":{{\"key_size\":64,",
667 "\"af\":{{\"type\":\"luks1\",\"stripes\":{stripes},\"hash\":\"sha256\"}},",
668 "\"area\":{{\"type\":\"raw\",\"offset\":\"{area}\",\"size\":\"512\",\"encryption\":\"aes-xts-plain64\",\"key_size\":64}},",
669 "\"kdf\":{kdf}}}}},",
670 "\"segments\":{{\"0\":{{\"type\":\"crypt\",\"offset\":\"{seg}\",\"size\":\"dynamic\",\"iv_tweak\":\"0\",\"encryption\":\"aes-xts-plain64\",\"sector_size\":{ss}}}}},",
671 "\"digests\":{{\"0\":{{\"type\":\"pbkdf2\",\"keyslots\":[\"0\"],\"segments\":[\"0\"],\"hash\":\"sha256\",\"iterations\":{diter},\"salt\":\"{dsalt}\",\"digest\":\"{dig}\"}}}}}}"
672 ),
673 stripes = L2_STRIPES,
674 area = L2_AREA_OFF,
675 kdf = kdf_json,
676 seg = L2_SEG_OFF,
677 ss = sector_size,
678 diter = L2_DIG_ITER,
679 dsalt = b64.encode(dig_salt),
680 dig = b64.encode(&digest),
681 );
682
683 let hdr_size = LUKS2_BIN_HDR_LEN + 4096; assert!(json.len() < 4096);
685 let total = L2_SEG_OFF as usize + payload.len();
686 let mut img = vec![0u8; total];
687 img[0..6].copy_from_slice(&LUKS_MAGIC);
688 img[6..8].copy_from_slice(&2u16.to_be_bytes());
689 img[8..16].copy_from_slice(&(hdr_size as u64).to_be_bytes());
690 img[LUKS2_BIN_HDR_LEN..LUKS2_BIN_HDR_LEN + json.len()].copy_from_slice(json.as_bytes());
691 let a = L2_AREA_OFF as usize;
692 img[a..a + material.len()].copy_from_slice(&material);
693 let s = L2_SEG_OFF as usize;
694 img[s..s + payload.len()].copy_from_slice(&payload);
695 (img, plain)
696 }
697
698 #[test]
699 fn luks2_argon2id_roundtrip() {
700 let master = vec![0xC3u8; 64];
701 let salt = [0x77u8; 16];
702 let slot_key = derive_key_argon2(
703 &Argon2Params {
704 kind: "argon2id",
705 time: 1,
706 memory: 32,
707 cpus: 1,
708 salt: &salt,
709 },
710 PASS,
711 64,
712 )
713 .unwrap();
714 let b64 = base64::engine::general_purpose::STANDARD;
715 let kdf = format!(
716 "{{\"type\":\"argon2id\",\"time\":1,\"memory\":32,\"cpus\":1,\"salt\":\"{}\"}}",
717 b64.encode(salt)
718 );
719 let (img, plain) = build_luks2(&master, &slot_key, &kdf, 4096);
720
721 let mut vol = LuksVolume::unlock_with_passphrase(Cursor::new(img), PASS).unwrap();
722 assert_eq!(vol.info().version, 2);
723 assert_eq!(vol.info().cipher_name, "aes");
724 assert_eq!(vol.info().cipher_mode, "xts-plain64");
725 assert_eq!(vol.info().sector_size, 4096);
726 assert_eq!(vol.info().key_bytes, 64);
727 assert_eq!(vol.master_key(), &master[..]);
728
729 let mut buf = [0u8; 512];
730 vol.read_at(0, &mut buf).unwrap();
731 assert_eq!(&buf[..], &plain[..512]);
732 }
733
734 #[test]
735 fn luks2_pbkdf2_roundtrip() {
736 let master = vec![0x1Eu8; 64];
737 let salt = [0x88u8; 16];
738 let slot_key = derive_key("sha256", PASS, &salt, 5, 64).unwrap();
739 let b64 = base64::engine::general_purpose::STANDARD;
740 let kdf = format!(
741 "{{\"type\":\"pbkdf2\",\"hash\":\"sha256\",\"iterations\":5,\"salt\":\"{}\"}}",
742 b64.encode(salt)
743 );
744 let (img, plain) = build_luks2(&master, &slot_key, &kdf, 512);
745
746 let mut vol = LuksVolume::unlock2_with_passphrase(Cursor::new(img), PASS).unwrap();
747 assert_eq!(vol.info().sector_size, 512);
748 let mut buf = [0u8; 512];
749 vol.read_at(0, &mut buf).unwrap();
750 assert_eq!(&buf[..], &plain[..512]);
751 }
752
753 #[test]
754 fn luks2_no_crypt_segment() {
755 let json = r#"{"keyslots":{"0":{"key_size":64,
756 "af":{"stripes":8,"hash":"sha256"},
757 "area":{"offset":"8192","size":"512","encryption":"aes-xts-plain64"},
758 "kdf":{"type":"pbkdf2","hash":"sha256","iterations":5,"salt":"AAAA"}}},
759 "segments":{},"digests":{}}"#;
760 let img = build_luks2_json(json);
761 assert!(matches!(
762 LuksVolume::unlock2_with_passphrase(Cursor::new(img), PASS),
763 Err(LuksError::NoActiveKeyslot)
764 ));
765 }
766
767 #[test]
768 fn luks2_no_keyslots() {
769 let json = r#"{"keyslots":{},
770 "segments":{"0":{"type":"crypt","offset":"12288","encryption":"aes-xts-plain64","sector_size":512}},
771 "digests":{}}"#;
772 let img = build_luks2_json(json);
773 assert!(matches!(
774 LuksVolume::unlock2_with_passphrase(Cursor::new(img), PASS),
775 Err(LuksError::NoActiveKeyslot)
776 ));
777 }
778
779 #[test]
780 fn luks2_short_key_material_is_skipped() {
781 let json = r#"{"keyslots":{"0":{"key_size":64,
783 "af":{"stripes":8,"hash":"sha256"},
784 "area":{"offset":"9999999","size":"512","encryption":"aes-xts-plain64"},
785 "kdf":{"type":"pbkdf2","hash":"sha256","iterations":5,"salt":"AAAA"}}},
786 "segments":{"0":{"type":"crypt","offset":"12288","encryption":"aes-xts-plain64","sector_size":512}},
787 "digests":{"0":{"type":"pbkdf2","keyslots":["0"],"hash":"sha256","iterations":5,"salt":"AAAA","digest":"AAAA"}}}"#;
788 let img = build_luks2_json(json);
789 assert!(matches!(
790 LuksVolume::unlock2_with_passphrase(Cursor::new(img), PASS),
791 Err(LuksError::AuthenticationFailed)
792 ));
793 }
794
795 fn build_luks2_json(json: &str) -> Vec<u8> {
798 let hdr_size = LUKS2_BIN_HDR_LEN + 4096;
799 let total = 16384usize;
800 let mut img = vec![0u8; total];
801 img[0..6].copy_from_slice(&LUKS_MAGIC);
802 img[6..8].copy_from_slice(&2u16.to_be_bytes());
803 img[8..16].copy_from_slice(&(hdr_size as u64).to_be_bytes());
804 img[LUKS2_BIN_HDR_LEN..LUKS2_BIN_HDR_LEN + json.len()].copy_from_slice(json.as_bytes());
805 img
806 }
807
808 #[test]
811 fn luks1_unsupported_hash_errors() {
812 let mut img = vec![0u8; 4096];
815 img[0..6].copy_from_slice(&LUKS_MAGIC);
816 img[6..8].copy_from_slice(&1u16.to_be_bytes());
817 img[8..11].copy_from_slice(b"aes");
818 img[40..51].copy_from_slice(b"xts-plain64");
819 img[72..75].copy_from_slice(b"md5");
820 img[104..108].copy_from_slice(&4u32.to_be_bytes());
821 img[108..112].copy_from_slice(&64u32.to_be_bytes());
822 let base = 208;
823 img[base..base + 4].copy_from_slice(&KEY_ENABLED.to_be_bytes());
824 img[base + 4..base + 8].copy_from_slice(&5u32.to_be_bytes());
825 img[base + 40..base + 44].copy_from_slice(&1u32.to_be_bytes());
826 img[base + 44..base + 48].copy_from_slice(&8u32.to_be_bytes());
827 assert!(matches!(
828 LuksVolume::unlock1_with_passphrase(Cursor::new(img), PASS),
829 Err(LuksError::Unsupported { what: "hash", .. })
830 ));
831 }
832
833 #[test]
834 fn luks2_bad_argon2_params_errors() {
835 let json = r#"{"keyslots":{"0":{"key_size":64,
837 "af":{"stripes":8,"hash":"sha256"},
838 "area":{"offset":"8192","size":"512","encryption":"aes-xts-plain64"},
839 "kdf":{"type":"argon2id","time":1,"memory":0,"cpus":1,"salt":"AAAAAAAAAAA="}}},
840 "segments":{"0":{"type":"crypt","offset":"12288","encryption":"aes-xts-plain64","sector_size":512}},
841 "digests":{}}"#;
842 let img = build_luks2_json(json);
843 assert!(matches!(
844 LuksVolume::unlock2_with_passphrase(Cursor::new(img), PASS),
845 Err(LuksError::Unsupported { .. })
846 ));
847 }
848
849 #[test]
850 fn luks2_unsupported_af_hash_errors() {
851 let json = r#"{"keyslots":{"0":{"key_size":64,
854 "af":{"stripes":8,"hash":"md5"},
855 "area":{"offset":"8192","size":"512","encryption":"aes-xts-plain64"},
856 "kdf":{"type":"pbkdf2","hash":"sha256","iterations":5,"salt":"AAAA"}}},
857 "segments":{"0":{"type":"crypt","offset":"12288","encryption":"aes-xts-plain64","sector_size":512}},
858 "digests":{}}"#;
859 let img = build_luks2_json(json);
860 assert!(matches!(
861 LuksVolume::unlock2_with_passphrase(Cursor::new(img), PASS),
862 Err(LuksError::Unsupported { what: "hash", .. })
863 ));
864 }
865
866 #[test]
867 fn luks2_unsupported_digest_hash_errors() {
868 let json = r#"{"keyslots":{"0":{"key_size":64,
870 "af":{"stripes":8,"hash":"sha256"},
871 "area":{"offset":"8192","size":"512","encryption":"aes-xts-plain64"},
872 "kdf":{"type":"pbkdf2","hash":"sha256","iterations":5,"salt":"AAAA"}}},
873 "segments":{"0":{"type":"crypt","offset":"12288","encryption":"aes-xts-plain64","sector_size":512}},
874 "digests":{"0":{"type":"pbkdf2","keyslots":["0"],"hash":"md5","iterations":5,"salt":"AAAA","digest":"AAAA"}}}"#;
875 let img = build_luks2_json(json);
876 assert!(matches!(
877 LuksVolume::unlock2_with_passphrase(Cursor::new(img), PASS),
878 Err(LuksError::Unsupported { what: "hash", .. })
879 ));
880 }
881
882 #[test]
883 fn luks2_digest_mismatch_falls_through() {
884 let json = r#"{"keyslots":{"0":{"key_size":64,
889 "af":{"stripes":8,"hash":"sha256"},
890 "area":{"offset":"8192","size":"512","encryption":"aes-xts-plain64"},
891 "kdf":{"type":"pbkdf2","hash":"sha256","iterations":5,"salt":"AAAA"}}},
892 "segments":{"0":{"type":"crypt","offset":"12288","encryption":"aes-xts-plain64","sector_size":512}},
893 "digests":{"0":{"type":"pbkdf2","keyslots":["0"],"hash":"sha256","iterations":5,"salt":"AAAA","digest":"AAAA"},
894 "1":{"type":"pbkdf2","keyslots":["9"],"hash":"sha256","iterations":5,"salt":"AAAA","digest":"AAAA"}}}"#;
895 let img = build_luks2_json(json);
896 assert!(matches!(
897 LuksVolume::unlock2_with_passphrase(Cursor::new(img), PASS),
898 Err(LuksError::AuthenticationFailed)
899 ));
900 }
901
902 struct InterruptOnce(bool);
905 impl Read for InterruptOnce {
906 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
907 if self.0 {
908 Ok(0)
909 } else {
910 self.0 = true;
911 Err(std::io::Error::from(std::io::ErrorKind::Interrupted))
912 }
913 }
914 }
915
916 struct AlwaysError;
917 impl Read for AlwaysError {
918 fn read(&mut self, _buf: &mut [u8]) -> std::io::Result<usize> {
919 Err(std::io::Error::other("boom"))
920 }
921 }
922
923 #[test]
924 fn read_available_retries_on_interrupted() {
925 let mut r = InterruptOnce(false);
926 let mut buf = [0xFFu8; 4];
927 assert_eq!(read_available(&mut r, &mut buf).unwrap(), 0);
928 assert_eq!(buf, [0u8; 4]); }
930
931 #[test]
932 fn read_available_propagates_hard_error() {
933 let mut r = AlwaysError;
934 let mut buf = [0u8; 4];
935 assert!(read_available(&mut r, &mut buf).is_err());
936 }
937}