1#[cfg(feature = "_write")]
2use byteorder::WriteBytesExt;
3use byteorder::{BigEndian, ReadBytesExt};
4use serde::{Deserialize, Serialize};
5use sha2::{Digest, Sha256};
6use std::collections::HashMap;
7use std::fmt;
8use std::io::{Read, Seek, Write};
9use std::str::FromStr;
10use thiserror::Error;
11
12#[cfg(feature = "_challenge_response")]
13pub mod challenge_response;
14
15#[cfg(feature = "_challenge_response")]
16use crate::challenge_response::ChallengeResponseSlot;
17
18use aes::cipher::KeyInit;
19use base64::Engine as _;
20#[cfg(feature = "_write")]
21use rand::RngExt;
22use sha2::Sha512;
23use std::io::SeekFrom;
24use std::path::Path;
25use std::process::{Command, Stdio};
26use xts_mode::{Xts128, get_tweak_default};
27
28pub mod af;
29pub mod hash;
30pub mod kdf;
31pub mod key;
32pub mod keyslot;
33
34pub use af::{LUKS1_AF_STRIPES, Luks2Af, Luks2AfType};
35pub use hash::{
36 HASH_SHA256, HASH_SHA512, LUKS2_CHECKSUM_ALG_ID_LEN, Luks2HashAlg, SHA256_DIGEST_SIZE, SHA512_DIGEST_SIZE,
37};
38pub use kdf::Luks2Kdf;
39pub use key::{UnlockKey, VolumeKey};
40pub use keyslot::{
41 KeySlotId, Luks2Area, Luks2AreaEncryption, Luks2KeySize, Luks2Keyslot, Luks2KeyslotPriority,
42 Luks2ReencryptDirection, Luks2ReencryptMode,
43};
44
45#[derive(Serialize, Deserialize)]
49pub struct LuksDevice {
50 pub header: LuksHeader,
52 pub keyslots: HashMap<KeySlotId, Vec<u8>>,
54 #[serde(skip)]
56 pub unlocked_key: Option<VolumeKey>,
57}
58
59impl LuksDevice {
60 #[cfg(feature = "_challenge_response")]
62 pub fn get_challenge_response_keyslots(&self) -> Vec<(KeySlotId, Option<u32>, ChallengeResponseSlot)> {
63 let mut results = Vec::new();
64 match &self.header {
65 LuksHeader::V1 => {}
66 LuksHeader::V2(h) => {
67 for token in h.metadata.tokens.values() {
68 if let Luks2Token::ChallengeResponse {
69 keyslots,
70 serial,
71 slot,
72 } = token
73 {
74 for id in keyslots {
75 results.push((id.clone(), *serial, slot.clone()));
76 }
77 }
78 }
79 }
80 }
81 results
82 }
83
84 pub fn unlock(&mut self, keyslot_id: &KeySlotId, key: &UnlockKey) -> Result<(), LuksError> {
86 let volume_key = self.get_volume_key(keyslot_id, key)?;
87 self.unlocked_key = Some(volume_key);
88
89 if self.verify(keyslot_id)? {
90 Ok(())
91 } else {
92 self.unlocked_key = None;
93 Err(LuksError::Kdf("Passphrase verification failed".to_string()))
94 }
95 }
96
97 #[cfg(feature = "_write")]
99 pub fn to_writer<W: Write + Seek>(&self, mut writer: W) -> Result<(), LuksError> {
100 self.header.to_writer(&mut writer)?;
101
102 match &self.header {
103 LuksHeader::V1 => return Err(LuksError::UnsupportedVersion(1)),
104 LuksHeader::V2(h) => {
105 for (id, slot) in &h.metadata.keyslots {
106 let area = slot.area();
107 let data = self.keyslots.get(id).ok_or_else(|| {
108 LuksError::InvalidHeader(format!("Data for keyslot {} not found", id))
109 })?;
110 writer.seek(std::io::SeekFrom::Start(area.offset()))?;
111 writer.write_all(data)?;
112 }
113 }
114 }
115
116 Ok(())
117 }
118
119 pub fn verify(&self, keyslot_id: &KeySlotId) -> Result<bool, LuksError> {
125 let volume_key = self.unlocked_key.as_ref().ok_or(LuksError::Locked)?;
126
127 let h2 = match &self.header {
128 LuksHeader::V1 => return Err(LuksError::UnsupportedVersion(1)),
129 LuksHeader::V2(h) => h,
130 };
131
132 let (_digest_id, digest) = h2
134 .metadata
135 .digests
136 .iter()
137 .find(|(_, d)| match d {
138 Luks2Digest::Pbkdf2 { keyslots, .. } => keyslots.contains(keyslot_id),
139 })
140 .ok_or_else(|| LuksError::InvalidHeader(format!("No digest found for keyslot {}", keyslot_id)))?;
141
142 let (digest_hash, digest_salt, expected_digest, digest_iterations) = match digest {
143 Luks2Digest::Pbkdf2 {
144 hash,
145 salt,
146 digest,
147 iterations,
148 ..
149 } => (hash, salt, digest, iterations),
150 };
151
152 let kdf_digest_salt = base64::engine::general_purpose::STANDARD
154 .decode(digest_salt)
155 .map_err(|e| LuksError::Kdf(format!("Invalid salt base64 in digest: {}", e)))?;
156
157 let expected_bytes = base64::engine::general_purpose::STANDARD
158 .decode(expected_digest)
159 .map_err(|e| LuksError::Kdf(format!("Invalid digest base64 in digest: {}", e)))?;
160
161 let mut verification_output = vec![0u8; expected_bytes.len()];
162 if digest_hash == &Luks2HashAlg::Sha256 {
163 pbkdf2::pbkdf2::<hmac::Hmac<Sha256>>(
164 volume_key.expose_bytes(),
165 &kdf_digest_salt,
166 *digest_iterations,
167 &mut verification_output,
168 )
169 .map_err(|e| LuksError::Kdf(format!("PBKDF2 SHA256 error: {}", e)))?;
170 } else if digest_hash == &Luks2HashAlg::Sha512 {
171 pbkdf2::pbkdf2::<hmac::Hmac<Sha512>>(
172 volume_key.expose_bytes(),
173 &kdf_digest_salt,
174 *digest_iterations,
175 &mut verification_output,
176 )
177 .map_err(|e| LuksError::Kdf(format!("PBKDF2 SHA512 error: {}", e)))?;
178 } else {
179 return Err(LuksError::UnsupportedChecksumAlg(digest_hash.to_string()));
180 }
181
182 Ok(verification_output == expected_bytes)
183 }
184
185 pub fn get_volume_key(&self, keyslot_id: &KeySlotId, key: &UnlockKey) -> Result<VolumeKey, LuksError> {
187 let h2 = match &self.header {
188 LuksHeader::V1 => return Err(LuksError::UnsupportedVersion(1)),
189 LuksHeader::V2(h) => h,
190 };
191
192 let keyslot = h2
193 .metadata
194 .keyslots
195 .get(keyslot_id)
196 .ok_or_else(|| LuksError::InvalidHeader(format!("Keyslot {} not found", keyslot_id)))?;
197
198 let (kdf, key_size, area, af) = match keyslot {
199 Luks2Keyslot::Luks2 {
200 kdf,
201 key_size,
202 area,
203 af,
204 ..
205 } => (kdf, u64::from(*key_size), area, af),
206 Luks2Keyslot::Reencrypt { .. } => {
207 return Err(LuksError::Kdf(
208 "Verification for reencrypt keyslots not yet supported".to_string(),
209 ));
210 }
211 };
212
213 let Luks2Area::Raw { encryption, .. } = area else {
214 return Err(LuksError::InvalidHeader(
215 "LUKS2 keyslot must have area type 'raw'".to_string(),
216 ));
217 };
218
219 let keyslot_key = kdf.derive_key(key, &h2.salt, key_size as usize)?;
221 let encrypted_data = self
223 .keyslots
224 .get(keyslot_id)
225 .ok_or_else(|| LuksError::InvalidHeader(format!("Data for keyslot {} not captured", keyslot_id)))?;
226
227 if *encryption != Luks2AreaEncryption::AesXtsPlain64 {
229 return Err(LuksError::UnsupportedChecksumAlg(format!(
230 "Area encryption {} is not supported",
231 encryption
232 )));
233 }
234
235 let mut decrypted_data = encrypted_data.clone();
236 if key_size == (AES128_KEY_SIZE as u64 * 2) {
237 let cipher_1 = aes::Aes128::new_from_slice(&keyslot_key[0..AES128_KEY_SIZE])
238 .map_err(|e| LuksError::Kdf(format!("Cipher error: {}", e)))?;
239 let cipher_2 = aes::Aes128::new_from_slice(&keyslot_key[AES128_KEY_SIZE..AES128_KEY_SIZE * 2])
240 .map_err(|e| LuksError::Kdf(format!("Cipher error: {}", e)))?;
241 let xts = Xts128::new(cipher_1, cipher_2);
242
243 for (i, chunk) in decrypted_data.chunks_mut(SECTOR_SIZE).enumerate() {
244 xts.decrypt_area(chunk, SECTOR_SIZE, (i as u64).into(), |t| get_tweak_default(t));
245 }
246 } else if key_size == (AES256_KEY_SIZE as u64 * 2) {
247 let cipher_1 = aes::Aes256::new_from_slice(&keyslot_key[0..AES256_KEY_SIZE])
248 .map_err(|e| LuksError::Kdf(format!("Cipher error: {}", e)))?;
249 let cipher_2 = aes::Aes256::new_from_slice(&keyslot_key[AES256_KEY_SIZE..AES256_KEY_SIZE * 2])
250 .map_err(|e| LuksError::Kdf(format!("Cipher error: {}", e)))?;
251 let xts = Xts128::new(cipher_1, cipher_2);
252
253 for (i, chunk) in decrypted_data.chunks_mut(SECTOR_SIZE).enumerate() {
254 xts.decrypt_area(chunk, SECTOR_SIZE, (i as u64).into(), |t| get_tweak_default(t));
255 }
256 } else {
257 return Err(LuksError::Kdf(format!(
258 "Unsupported key size {} for AES-XTS",
259 key_size
260 )));
261 }
262
263 let volume_key_bytes = crate::af::merge(
265 &decrypted_data[0..(key_size as u64 * af.stripes as u64) as usize],
266 &af.hash,
267 af.stripes,
268 key_size as usize,
269 )?;
270 VolumeKey::new(volume_key_bytes)
271 }
272
273 pub fn map_with_dmsetup(&self, name: &str, backing_device: &Path) -> Result<(), LuksError> {
275 let volume_key = self.unlocked_key.as_ref().ok_or(LuksError::Locked)?;
276
277 let h2 = match &self.header {
278 LuksHeader::V1 => return Err(LuksError::UnsupportedVersion(1)),
279 LuksHeader::V2(h) => h,
280 };
281
282 let segment = h2
284 .metadata
285 .segments
286 .iter()
287 .find_map(|(_, s)| match s {
288 Luks2Segment::Crypt { .. } => Some(s),
289 })
290 .ok_or_else(|| LuksError::InvalidHeader("No crypt segment found".to_string()))?;
291
292 let Luks2Segment::Crypt {
293 offset,
294 iv_tweak,
295 size,
296 encryption,
297 ..
298 } = segment;
299
300 let num_sectors = match size {
302 Luks2SegmentSize::U64(s) => s / SECTOR_SIZE as u64,
303 Luks2SegmentSize::Dynamic => {
304 let mut file = std::fs::File::open(backing_device)?;
305 let total_size = file.seek(SeekFrom::End(0))?;
306 (total_size - offset.0) / SECTOR_SIZE as u64
307 }
308 };
309
310 let table = format!(
313 "0 {} crypt {} {} {} {} {}\n",
314 num_sectors,
315 encryption,
316 to_hex(volume_key.expose_bytes()),
317 iv_tweak.0,
318 backing_device.to_string_lossy(),
319 offset.0 / SECTOR_SIZE as u64
320 );
321
322 let mut child = Command::new("dmsetup")
324 .arg("create")
325 .arg(name)
326 .stdin(Stdio::piped())
327 .spawn()?;
328
329 if let Some(mut child_stdin) = child.stdin.take() {
331 child_stdin.write_all(table.as_bytes())?;
332 }
333
334 let status = child.wait()?;
335 if !status.success() {
336 return Err(LuksError::DmSetup(format!("exit code {}", status)));
337 }
338
339 Ok(())
340 }
341
342 #[cfg(feature = "_write")]
346 pub fn change_unlock_key(
347 &mut self,
348 keyslot_id: &KeySlotId,
349 old_key: &UnlockKey,
350 new_key: &UnlockKey,
351 ) -> Result<(), LuksError> {
352 let volume_key = self.get_volume_key(keyslot_id, old_key)?;
353 self.update_keyslot(keyslot_id, new_key, &volume_key)
354 }
355
356 #[cfg(feature = "_write")]
357 fn update_keyslot(
358 &mut self,
359 keyslot_id: &KeySlotId,
360 key: &UnlockKey,
361 volume_key: &VolumeKey,
362 ) -> Result<(), LuksError> {
363 let h2 = match &mut self.header {
364 LuksHeader::V1 => return Err(LuksError::UnsupportedVersion(1)),
365 LuksHeader::V2(h) => h,
366 };
367
368 let keyslot = h2
369 .metadata
370 .keyslots
371 .get_mut(keyslot_id)
372 .ok_or_else(|| LuksError::InvalidHeader(format!("Keyslot {} not found", keyslot_id)))?;
373
374 let (kdf, key_size, area, af) = match keyslot {
375 Luks2Keyslot::Luks2 {
376 kdf,
377 key_size,
378 area,
379 af,
380 ..
381 } => (kdf, u64::from(*key_size), area, af),
382 Luks2Keyslot::Reencrypt { .. } => {
383 return Err(LuksError::Kdf(
384 "Changing passphrase for reencrypt keyslots not supported".to_string(),
385 ));
386 }
387 };
388
389 let Luks2Area::Raw { encryption, .. } = area else {
390 return Err(LuksError::InvalidHeader(
391 "LUKS2 keyslot must have area type 'raw'".to_string(),
392 ));
393 };
394
395 let mut new_salt = vec![0u8; KDF_SALT_SIZE];
397 rand::rng().fill(&mut new_salt[..]);
398 let new_salt_b64 = base64::engine::general_purpose::STANDARD.encode(new_salt);
399
400 match kdf {
402 Luks2Kdf::Argon2i { salt, .. } => *salt = new_salt_b64,
403 Luks2Kdf::Argon2id { salt, .. } => *salt = new_salt_b64,
404 Luks2Kdf::Pbkdf2 { salt, .. } => *salt = new_salt_b64,
405 }
406
407 let keyslot_key = kdf.derive_key(key, &h2.salt, key_size as usize)?;
409 let mut random_stripes =
411 vec![0u8; (volume_key.expose_bytes().len() as u32 * (af.stripes - 1)) as usize];
412 rand::rng().fill(&mut random_stripes[..]);
413 let split_data = crate::af::split(
414 volume_key.expose_bytes(),
415 &af.hash,
416 af.stripes,
417 volume_key.expose_bytes().len(),
418 random_stripes,
419 )?;
420
421 if *encryption != Luks2AreaEncryption::AesXtsPlain64 {
423 return Err(LuksError::UnsupportedChecksumAlg(format!(
424 "Area encryption {} is not supported",
425 encryption
426 )));
427 }
428
429 let mut encrypted_data = split_data.clone();
430 if key_size == (AES128_KEY_SIZE as u64 * 2) {
431 let cipher_1 = aes::Aes128::new_from_slice(&keyslot_key[0..AES128_KEY_SIZE])
432 .map_err(|e| LuksError::Kdf(format!("Cipher error: {}", e)))?;
433 let cipher_2 = aes::Aes128::new_from_slice(&keyslot_key[AES128_KEY_SIZE..AES128_KEY_SIZE * 2])
434 .map_err(|e| LuksError::Kdf(format!("Cipher error: {}", e)))?;
435 let xts = Xts128::new(cipher_1, cipher_2);
436
437 for (i, chunk) in encrypted_data.chunks_mut(SECTOR_SIZE).enumerate() {
438 xts.encrypt_area(chunk, SECTOR_SIZE, (i as u64).into(), |t| get_tweak_default(t));
439 }
440 } else if key_size == (AES256_KEY_SIZE as u64 * 2) {
441 let cipher_1 = aes::Aes256::new_from_slice(&keyslot_key[0..AES256_KEY_SIZE])
442 .map_err(|e| LuksError::Kdf(format!("Cipher error: {}", e)))?;
443 let cipher_2 = aes::Aes256::new_from_slice(&keyslot_key[AES256_KEY_SIZE..AES256_KEY_SIZE * 2])
444 .map_err(|e| LuksError::Kdf(format!("Cipher error: {}", e)))?;
445 let xts = Xts128::new(cipher_1, cipher_2);
446
447 for (i, chunk) in encrypted_data.chunks_mut(SECTOR_SIZE).enumerate() {
448 xts.encrypt_area(chunk, SECTOR_SIZE, (i as u64).into(), |t| get_tweak_default(t));
449 }
450 } else {
451 return Err(LuksError::Kdf(format!(
452 "Unsupported key size {} for AES-XTS",
453 key_size
454 )));
455 }
456
457 self.keyslots.insert(keyslot_id.clone(), encrypted_data);
459
460 #[cfg(feature = "_challenge_response")]
462 {
463 if let Some(cr) = key.challenge_response() {
464 let h2 = match &mut self.header {
466 LuksHeader::V2(h) => h,
467 _ => unreachable!(),
468 };
469
470 let mut found = false;
471 for token in h2.metadata.tokens.values_mut() {
472 if let Luks2Token::ChallengeResponse {
473 keyslots,
474 serial: _,
475 slot: _,
476 } = token
477 {
478 if keyslots.contains(keyslot_id) {
479 found = true;
480 break;
481 }
482 }
483 }
484
485 if !found {
486 let mut next_id = 0;
488 while h2.metadata.tokens.contains_key(&next_id.to_string()) {
489 next_id += 1;
490 }
491
492 match cr {
493 crate::key::ChallengeResponseKey::Hardware { serial, slot } => {
494 h2.metadata.tokens.insert(
495 next_id.to_string(),
496 Luks2Token::ChallengeResponse {
497 keyslots: vec![keyslot_id.clone()],
498 serial: *serial,
499 slot: slot.clone(),
500 },
501 );
502 }
503 crate::key::ChallengeResponseKey::Software { .. } => {
504 h2.metadata.tokens.insert(
505 next_id.to_string(),
506 Luks2Token::ChallengeResponse {
507 keyslots: vec![keyslot_id.clone()],
508 serial: None,
509 slot: ChallengeResponseSlot::Slot1, },
511 );
512 }
513 }
514 }
515 } else {
516 let h2 = match &mut self.header {
518 LuksHeader::V2(h) => h,
519 _ => unreachable!(),
520 };
521
522 let mut token_to_remove = None;
523 for (id, token) in &mut h2.metadata.tokens {
524 if let Luks2Token::ChallengeResponse {
525 keyslots,
526 serial: _,
527 slot: _,
528 } = token
529 {
530 if keyslots.contains(keyslot_id) {
531 keyslots.retain(|k| k != keyslot_id);
532 if keyslots.is_empty() {
533 token_to_remove = Some(id.clone());
534 }
535 }
536 }
537 }
538 if let Some(id) = token_to_remove {
539 h2.metadata.tokens.remove(&id);
540 }
541 }
542 }
543
544 Ok(())
545 }
546}
547
548pub fn is_luks_device<P: AsRef<Path>>(path: P) -> Result<bool, LuksError> {
552 let mut file = std::fs::File::open(path)?;
553 let mut buffer = [0u8; LUKS_MAGIC_SIZE];
554 match file.read_exact(&mut buffer) {
555 Ok(_) => Ok(buffer == LUKS_MAGIC),
556 Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(false),
557 Err(e) => Err(LuksError::Io(e)),
558 }
559}
560
561pub const LUKS_MAGIC: [u8; 6] = *b"LUKS\xBA\xBE";
563
564pub const LUKS_MAGIC_SIZE: usize = 6;
566pub const LUKS_VERSION_SIZE: usize = 2;
568
569pub const LUKS2_LABEL_SIZE: usize = 48;
571pub const LUKS2_SALT_SIZE: usize = 64;
573pub const LUKS2_UUID_SIZE: usize = 40;
575pub const LUKS2_SUBSYSTEM_SIZE: usize = 48;
577pub const LUKS2_CHECKSUM_SIZE: usize = 64;
579
580pub const SECTOR_SIZE: usize = 512;
582
583pub const KDF_SALT_SIZE: usize = 32;
585
586pub const LUKS2_CHECKSUM_OFFSET: usize = 448;
588pub const LUKS2_BINARY_HEADER_SIZE: usize = 4096;
590
591pub const AES_BLOCK_SIZE: usize = 16;
593pub const AES128_KEY_SIZE: usize = 16;
595pub const AES256_KEY_SIZE: usize = 32;
597
598pub const LUKS2_DEFAULT_JSON_SIZE: u64 = 12288;
603pub const LUKS2_DEFAULT_KEYSLOTS_SIZE: u64 = 4161536;
605
606#[derive(Error, Debug)]
608pub enum LuksError {
609 #[error("IO error: {0}")]
611 Io(#[from] std::io::Error),
612 #[error("Invalid LUKS magic: {0:?}")]
614 InvalidMagic([u8; LUKS_MAGIC_SIZE]),
615 #[error("Unsupported LUKS version: {0}")]
617 UnsupportedVersion(u16),
618 #[error("JSON error: {0}")]
620 Json(#[from] serde_json::Error),
621 #[error("Invalid LUKS2 header: {0}")]
623 InvalidHeader(String),
624 #[error("Checksum verification failed: expected {expected}, got {actual}")]
626 InvalidChecksum { expected: String, actual: String },
627 #[error("Unsupported checksum algorithm: {0}")]
629 UnsupportedChecksumAlg(String),
630 #[error("KDF error: {0}")]
632 Kdf(String),
633 #[error("dmsetup failed: {0}")]
635 DmSetup(String),
636 #[error("Device is locked")]
638 Locked,
639 #[cfg(feature = "_challenge_response")]
640 #[error("Challenge-response error: {0}")]
642 ChallengeResponse(String),
643}
644
645#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
652pub struct Luks2U64(pub u64);
653
654impl Serialize for Luks2U64 {
655 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
656 where
657 S: serde::Serializer,
658 {
659 serializer.serialize_str(&self.0.to_string())
660 }
661}
662
663impl<'de> Deserialize<'de> for Luks2U64 {
664 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
665 where
666 D: serde::Deserializer<'de>,
667 {
668 let s = String::deserialize(deserializer)?;
669 s.parse::<u64>().map(Luks2U64).map_err(serde::de::Error::custom)
670 }
671}
672
673#[derive(Debug, Clone, Serialize, Deserialize)]
675#[serde(tag = "type", rename_all = "lowercase")]
676pub enum Luks2Token {
677 #[serde(rename = "luks2-keyring")]
679 Keyring {
680 keyslots: Vec<KeySlotId>,
682 key_description: String,
684 },
685 #[serde(rename = "luks-rs-keyring")]
687 LuksRsKeyring {
688 keyslots: Vec<KeySlotId>,
690 key_description: String,
692 },
693 #[cfg(feature = "_challenge_response")]
694 #[serde(rename = "luks2-challenge-response")]
696 ChallengeResponse {
697 keyslots: Vec<KeySlotId>,
699 serial: Option<u32>,
701 slot: ChallengeResponseSlot,
703 },
704}
705
706#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum Luks2SegmentSize {
709 U64(u64),
711 Dynamic,
713}
714
715impl Serialize for Luks2SegmentSize {
716 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
717 where
718 S: serde::Serializer,
719 {
720 match self {
721 Luks2SegmentSize::U64(v) => serializer.serialize_str(&v.to_string()),
722 Luks2SegmentSize::Dynamic => serializer.serialize_str("dynamic"),
723 }
724 }
725}
726
727impl<'de> Deserialize<'de> for Luks2SegmentSize {
728 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
729 where
730 D: serde::Deserializer<'de>,
731 {
732 let s = String::deserialize(deserializer)?;
733 if s == "dynamic" {
734 Ok(Luks2SegmentSize::Dynamic)
735 } else {
736 s.parse::<u64>()
737 .map(Luks2SegmentSize::U64)
738 .map_err(serde::de::Error::custom)
739 }
740 }
741}
742
743#[derive(Debug, Clone, Serialize, Deserialize)]
745#[serde(tag = "type", rename_all = "lowercase")]
746pub enum Luks2Segment {
747 Crypt {
749 offset: Luks2U64,
751 iv_tweak: Luks2U64,
753 size: Luks2SegmentSize,
755 encryption: String,
757 sector_size: u32,
759 },
760}
761
762#[derive(Debug, Clone, Serialize, Deserialize)]
764#[serde(tag = "type", rename_all = "lowercase")]
765pub enum Luks2Digest {
766 Pbkdf2 {
768 keyslots: Vec<KeySlotId>,
770 segments: Vec<String>,
772 hash: Luks2HashAlg,
774 iterations: u32,
776 salt: String,
778 digest: String,
780 },
781}
782
783#[derive(Debug, Clone, Serialize, Deserialize)]
785pub struct Luks2Config {
786 pub json_size: Luks2U64,
788 pub keyslots_size: Luks2U64,
790 pub flags: Option<Vec<String>>,
792}
793
794#[derive(Debug, Clone, Serialize, Deserialize)]
796pub struct Luks2Metadata {
797 #[serde(deserialize_with = "crate::keyslot::deserialize_and_validate_keyslots")]
799 pub keyslots: HashMap<KeySlotId, Luks2Keyslot>,
800 pub tokens: HashMap<String, Luks2Token>,
802 pub segments: HashMap<String, Luks2Segment>,
804 pub digests: HashMap<String, Luks2Digest>,
806 pub config: Luks2Config,
808}
809
810#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
812#[serde(transparent)]
813pub struct LuksDeviceUuid(String);
814
815impl LuksDeviceUuid {
816 pub fn as_str(&self) -> &str {
818 &self.0
819 }
820}
821
822impl FromStr for LuksDeviceUuid {
823 type Err = LuksError;
824
825 fn from_str(s: &str) -> Result<Self, Self::Err> {
826 let s = s.trim_matches('\0');
827 if s.is_empty() {
828 return Err(LuksError::InvalidHeader("UUID is empty".to_string()));
829 }
830 if s.len() >= LUKS2_UUID_SIZE {
831 return Err(LuksError::InvalidHeader(format!(
832 "UUID too long: {} (max {})",
833 s.len(),
834 LUKS2_UUID_SIZE - 1
835 )));
836 }
837 if !s.chars().all(|c| c.is_ascii_hexdigit() || c == '-') {
838 return Err(LuksError::InvalidHeader(format!(
839 "UUID contains invalid characters: {}",
840 s
841 )));
842 }
843 Ok(LuksDeviceUuid(s.to_string()))
844 }
845}
846
847impl fmt::Display for LuksDeviceUuid {
848 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
849 f.write_str(&self.0)
850 }
851}
852
853impl PartialEq<&str> for LuksDeviceUuid {
854 fn eq(&self, other: &&str) -> bool {
855 &self.0 == *other
856 }
857}
858
859#[derive(Debug, Clone, Serialize, Deserialize)]
861pub struct Luks2Header {
862 pub version: u16,
864 pub hdr_size: u64,
866 pub seqid: u64,
868 pub label: String,
870 pub checksum_alg: Luks2HashAlg,
872 #[serde(with = "serde_arrays")]
874 pub salt: [u8; LUKS2_SALT_SIZE],
875 pub uuid: LuksDeviceUuid,
877 pub subsystem: String,
879 pub hdr_offset: u64,
881 #[serde(with = "serde_arrays")]
883 pub checksum: [u8; LUKS2_CHECKSUM_SIZE],
884 pub metadata: Luks2Metadata,
886}
887
888mod serde_arrays {
889 use serde::{Deserialize, Deserializer, Serialize, Serializer};
890
891 pub fn serialize<S>(bytes: &[u8; 64], serializer: S) -> Result<S::Ok, S::Error>
892 where
893 S: Serializer,
894 {
895 bytes.as_slice().serialize(serializer)
896 }
897
898 pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 64], D::Error>
899 where
900 D: Deserializer<'de>,
901 {
902 let v: Vec<u8> = Vec::deserialize(deserializer)?;
903 v.try_into()
904 .map_err(|_| serde::de::Error::custom("expected array of length 64"))
905 }
906}
907
908impl Luks2Header {
909 pub fn num_keyslots(&self) -> usize {
911 self.metadata.keyslots.len()
912 }
913
914 #[cfg(feature = "_write")]
916 pub fn to_writer<W: Write + Seek>(&self, mut writer: W) -> Result<(), LuksError> {
917 let json_str = serde_json::to_string(&self.metadata)?;
919 let json_bytes = json_str.as_bytes();
920 let json_size = json_bytes.len() as u64;
921
922 let binary_header_size = LUKS2_BINARY_HEADER_SIZE as u64;
924
925 let mut binary_header = vec![0u8; LUKS2_BINARY_HEADER_SIZE];
927 let mut cursor = std::io::Cursor::new(&mut binary_header);
928
929 cursor.write_all(&LUKS_MAGIC)?;
931
932 cursor.write_u16::<BigEndian>(self.version)?;
934
935 cursor.write_u64::<BigEndian>(binary_header_size + json_size)?;
937
938 cursor.write_u64::<BigEndian>(self.seqid)?;
940
941 let mut label_buf = [0u8; LUKS2_LABEL_SIZE];
943 let label_bytes = self.label.as_bytes();
944 let label_len = std::cmp::min(label_bytes.len(), LUKS2_LABEL_SIZE);
945 label_buf[..label_len].copy_from_slice(&label_bytes[..label_len]);
946 cursor.write_all(&label_buf)?;
947
948 cursor.write_all(&self.checksum_alg.to_bytes())?;
950
951 cursor.write_all(&self.salt)?;
953
954 let mut uuid_buf = [0u8; LUKS2_UUID_SIZE];
956 let uuid_bytes = self.uuid.as_str().as_bytes();
957 let uuid_len = std::cmp::min(uuid_bytes.len(), LUKS2_UUID_SIZE);
958 uuid_buf[..uuid_len].copy_from_slice(&uuid_bytes[..uuid_len]);
959 cursor.write_all(&uuid_buf)?;
960
961 let mut subsystem_buf = [0u8; LUKS2_SUBSYSTEM_SIZE];
963 let subsystem_bytes = self.subsystem.as_bytes();
964 let subsystem_len = std::cmp::min(subsystem_bytes.len(), LUKS2_SUBSYSTEM_SIZE);
965 subsystem_buf[..subsystem_len].copy_from_slice(&subsystem_bytes[..subsystem_len]);
966 cursor.write_all(&subsystem_buf)?;
967
968 cursor.write_u64::<BigEndian>(self.hdr_offset)?;
970
971 if self.checksum_alg == Luks2HashAlg::Sha256 {
973 let mut hasher = Sha256::new();
974 hasher.update(&binary_header);
977 hasher.update(json_bytes);
978 let calculated = hasher.finalize();
979
980 binary_header[LUKS2_CHECKSUM_OFFSET..LUKS2_CHECKSUM_OFFSET + SHA256_DIGEST_SIZE]
982 .copy_from_slice(calculated.as_slice());
983 } else {
984 return Err(LuksError::UnsupportedChecksumAlg(self.checksum_alg.to_string()));
985 }
986
987 writer.seek(std::io::SeekFrom::Start(self.hdr_offset))?;
989 writer.write_all(&binary_header)?;
990
991 writer.write_all(json_bytes)?;
993
994 Ok(())
995 }
996}
997
998#[derive(Debug, Clone, Serialize, Deserialize)]
1000pub enum LuksHeader {
1001 V1,
1003 V2(Luks2Header),
1005}
1006
1007impl LuksHeader {
1008 pub fn num_keyslots(&self) -> usize {
1010 match self {
1011 LuksHeader::V1 => 8, LuksHeader::V2(h) => h.num_keyslots(),
1013 }
1014 }
1015
1016 #[cfg(feature = "_write")]
1018 pub fn to_writer<W: Write + Seek>(&self, writer: W) -> Result<(), LuksError> {
1019 match self {
1020 LuksHeader::V1 => Err(LuksError::UnsupportedVersion(1)),
1021 LuksHeader::V2(h) => h.to_writer(writer),
1022 }
1023 }
1024
1025 pub fn open<R: Read + Seek>(mut reader: R) -> Result<LuksDevice, LuksError> {
1029 let header = Self::from_reader(&mut reader)?;
1030 let mut keyslots = HashMap::new();
1031
1032 match &header {
1033 LuksHeader::V1 => return Err(LuksError::UnsupportedVersion(1)),
1034 LuksHeader::V2(h) => {
1035 for (id, slot) in &h.metadata.keyslots {
1036 let area = slot.area();
1037 let mut data = vec![0u8; area.size() as usize];
1038 reader.seek(SeekFrom::Start(area.offset()))?;
1039 reader.read_exact(&mut data)?;
1040 keyslots.insert(id.clone(), data);
1041 }
1042 }
1043 }
1044
1045 Ok(LuksDevice {
1046 header,
1047 keyslots,
1048 unlocked_key: None,
1049 })
1050 }
1051
1052 pub fn from_reader<R: Read + Seek>(mut reader: R) -> Result<Self, LuksError> {
1056 let mut magic = [0u8; LUKS_MAGIC_SIZE];
1057 reader.read_exact(&mut magic)?;
1058
1059 if magic != LUKS_MAGIC {
1060 return Err(LuksError::InvalidMagic(magic));
1061 }
1062
1063 let version = reader.read_u16::<BigEndian>()?;
1064 match version {
1065 1 => Ok(LuksHeader::V1),
1066 2 => {
1067 let mut binary_header = vec![0u8; LUKS2_BINARY_HEADER_SIZE];
1069 binary_header[0..LUKS_MAGIC_SIZE].copy_from_slice(&magic);
1070 binary_header[LUKS_MAGIC_SIZE..LUKS_MAGIC_SIZE + LUKS_VERSION_SIZE]
1071 .copy_from_slice(&version.to_be_bytes());
1072
1073 reader.read_exact(&mut binary_header[LUKS_MAGIC_SIZE + LUKS_VERSION_SIZE..])?;
1074
1075 let mut cursor = std::io::Cursor::new(&binary_header[LUKS_MAGIC_SIZE + LUKS_VERSION_SIZE..]);
1076
1077 let hdr_size = cursor.read_u64::<BigEndian>()?;
1078 let seqid = cursor.read_u64::<BigEndian>()?;
1079
1080 let mut label_buf = [0u8; LUKS2_LABEL_SIZE];
1081 cursor.read_exact(&mut label_buf)?;
1082 let label = String::from_utf8_lossy(&label_buf).trim_matches('\0').to_string();
1083
1084 let mut checksum_alg_buf = [0u8; LUKS2_CHECKSUM_ALG_ID_LEN];
1085 cursor.read_exact(&mut checksum_alg_buf)?;
1086 let checksum_alg = Luks2HashAlg::from_str(&String::from_utf8_lossy(&checksum_alg_buf))?;
1087
1088 let mut salt = [0u8; LUKS2_SALT_SIZE];
1089 cursor.read_exact(&mut salt)?;
1090
1091 let mut uuid_buf = [0u8; LUKS2_UUID_SIZE];
1092 cursor.read_exact(&mut uuid_buf)?;
1093 let uuid = LuksDeviceUuid::from_str(&String::from_utf8_lossy(&uuid_buf))?;
1094
1095 let mut subsystem_buf = [0u8; LUKS2_SUBSYSTEM_SIZE];
1096 cursor.read_exact(&mut subsystem_buf)?;
1097 let subsystem = String::from_utf8_lossy(&subsystem_buf)
1098 .trim_matches('\0')
1099 .to_string();
1100
1101 let hdr_offset = cursor.read_u64::<BigEndian>()?;
1105
1106 let mut checksum = [0u8; LUKS2_CHECKSUM_SIZE];
1107 checksum.copy_from_slice(
1108 &binary_header[LUKS2_CHECKSUM_OFFSET..LUKS2_CHECKSUM_OFFSET + LUKS2_CHECKSUM_SIZE],
1109 );
1110
1111 if hdr_size < LUKS2_BINARY_HEADER_SIZE as u64 {
1113 return Err(LuksError::InvalidHeader(format!(
1114 "Header size {} is too small",
1115 hdr_size
1116 )));
1117 }
1118 let json_size = hdr_size - LUKS2_BINARY_HEADER_SIZE as u64;
1119 let mut json_buf = vec![0u8; json_size as usize];
1120 reader.read_exact(&mut json_buf)?;
1121
1122 if checksum_alg == Luks2HashAlg::Sha256 {
1124 let mut hasher = Sha256::new();
1125
1126 let mut csum_buf = binary_header.clone();
1128 for i in 0..LUKS2_CHECKSUM_SIZE {
1129 csum_buf[LUKS2_CHECKSUM_OFFSET + i] = 0;
1130 }
1131 hasher.update(&csum_buf);
1132 hasher.update(&json_buf);
1133
1134 let calculated = hasher.finalize();
1135 if calculated.as_slice() != &checksum[0..SHA256_DIGEST_SIZE] {
1137 return Err(LuksError::InvalidChecksum {
1138 expected: to_hex(&checksum[0..SHA256_DIGEST_SIZE]),
1139 actual: to_hex(calculated.as_slice()),
1140 });
1141 }
1142 } else {
1143 return Err(LuksError::UnsupportedChecksumAlg(checksum_alg.to_string()));
1144 }
1145
1146 let json_str = String::from_utf8_lossy(&json_buf).trim_matches('\0').to_string();
1148 let metadata: Luks2Metadata = serde_json::from_str(&json_str)?;
1149
1150 Ok(LuksHeader::V2(Luks2Header {
1151 version,
1152 hdr_size,
1153 seqid,
1154 label,
1155 checksum_alg,
1156 salt,
1157 uuid,
1158 subsystem,
1159 hdr_offset,
1160 checksum,
1161 metadata,
1162 }))
1163 }
1164 _ => Err(LuksError::UnsupportedVersion(version)),
1165 }
1166 }
1167}
1168
1169fn to_hex(bytes: &[u8]) -> String {
1170 bytes.iter().map(|b| format!("{:02x}", b)).collect()
1171}
1172
1173#[cfg(test)]
1174mod tests {
1175 use super::*;
1176 #[cfg(feature = "_write")]
1177 use byteorder::BigEndian;
1178 #[cfg(feature = "_write")]
1179 use byteorder::WriteBytesExt;
1180 #[cfg(feature = "_write")]
1181 use std::io::{Cursor, Write};
1182
1183 #[test]
1184 #[cfg(feature = "_write")]
1185 fn test_detect_luks2_with_checksum() {
1186 let mut binary_header = vec![0u8; LUKS2_BINARY_HEADER_SIZE];
1187 let json_data = format!(
1188 r#"{{
1189 "keyslots": {{}},
1190 "tokens": {{}},
1191 "segments": {{}},
1192 "digests": {{}},
1193 "config": {{
1194 "json_size": "{}",
1195 "keyslots_size": "{}"
1196 }}
1197 }}"#,
1198 LUKS2_DEFAULT_JSON_SIZE, LUKS2_DEFAULT_KEYSLOTS_SIZE
1199 );
1200 let hdr_size = LUKS2_BINARY_HEADER_SIZE as u64 + json_data.len() as u64;
1201
1202 {
1203 let mut cursor = Cursor::new(&mut binary_header);
1204 cursor.write_all(&LUKS_MAGIC).unwrap();
1205 cursor.write_u16::<BigEndian>(2).unwrap();
1206 cursor.write_u64::<BigEndian>(hdr_size).unwrap();
1207 cursor.write_u64::<BigEndian>(1).unwrap(); let mut label = [0u8; LUKS2_LABEL_SIZE];
1210 label[..4].copy_from_slice(b"test");
1211 cursor.write_all(&label).unwrap();
1212
1213 cursor.write_all(&Luks2HashAlg::Sha256.to_bytes()).unwrap();
1214
1215 cursor.write_all(&[0u8; LUKS2_SALT_SIZE]).unwrap();
1216
1217 let mut uuid = [0u8; LUKS2_UUID_SIZE];
1218 uuid[..4].copy_from_slice(b"abcd");
1219 cursor.write_all(&uuid).unwrap();
1220
1221 let subsystem = [0u8; LUKS2_SUBSYSTEM_SIZE];
1222 cursor.write_all(&subsystem).unwrap();
1223
1224 cursor.write_u64::<BigEndian>(0).unwrap(); }
1226
1227 let mut hasher = Sha256::new();
1229 hasher.update(&binary_header);
1230 hasher.update(json_data.as_bytes());
1231 let result = hasher.finalize();
1232
1233 binary_header[LUKS2_CHECKSUM_OFFSET..LUKS2_CHECKSUM_OFFSET + SHA256_DIGEST_SIZE]
1235 .copy_from_slice(&result);
1236
1237 let mut buf = binary_header;
1238 buf.extend_from_slice(json_data.as_bytes());
1239
1240 let cursor = Cursor::new(buf);
1241 let header = LuksHeader::from_reader(cursor).unwrap();
1242
1243 if let LuksHeader::V2(h) = header {
1244 assert_eq!(h.version, 2);
1245 assert_eq!(h.label, "test");
1246 assert_eq!(h.uuid, "abcd");
1247 } else {
1248 panic!("Expected LUKS2 header");
1249 }
1250 }
1251
1252 #[test]
1253 #[cfg(feature = "_write")]
1254 fn test_device_roundtrip() {
1255 use aes::cipher::KeyInit;
1256 use base64::Engine;
1257 use rand::RngExt;
1258 use xts_mode::{Xts128, get_tweak_default};
1259 let mut rng = rand::rng();
1260
1261 let mut salt = [0u8; LUKS2_SALT_SIZE];
1263 rng.fill(&mut salt);
1264
1265 let volume_key_size = AES128_KEY_SIZE * 2;
1266 let volume_key = vec![0x42u8; volume_key_size];
1267 let passphrase = "correct horse battery staple";
1268 let unlock_key = UnlockKey::from(passphrase);
1269
1270 let mut keyslot_salt = [0u8; 32];
1272 rng.fill(&mut keyslot_salt);
1273 let keyslot_salt_b64 = base64::engine::general_purpose::STANDARD.encode(keyslot_salt);
1274
1275 let kdf = Luks2Kdf::Pbkdf2 {
1276 hash: Luks2HashAlg::Sha256,
1277 iterations: 1000,
1278 salt: keyslot_salt_b64,
1279 };
1280
1281 let keyslot_key = kdf.derive_key(&unlock_key, &salt, volume_key_size).unwrap();
1282
1283 let mut random_stripes = vec![0u8; volume_key_size * (LUKS1_AF_STRIPES - 1) as usize];
1285 rng.fill(&mut random_stripes[..]);
1286 let encrypted_keyslot_data = crate::af::split(
1287 &volume_key,
1288 &Luks2HashAlg::Sha256,
1289 LUKS1_AF_STRIPES,
1290 volume_key_size,
1291 random_stripes,
1292 )
1293 .unwrap();
1294
1295 let mut encrypted_data = encrypted_keyslot_data.clone();
1297 let cipher_1 = aes::Aes128::new_from_slice(&keyslot_key[0..AES128_KEY_SIZE]).unwrap();
1298 let cipher_2 = aes::Aes128::new_from_slice(&keyslot_key[AES128_KEY_SIZE..AES128_KEY_SIZE * 2]).unwrap();
1299 let xts = Xts128::new(cipher_1, cipher_2);
1300 for (i, chunk) in encrypted_data.chunks_mut(SECTOR_SIZE).enumerate() {
1301 xts.encrypt_area(chunk, SECTOR_SIZE, (i as u64).into(), |t| get_tweak_default(t));
1302 }
1303
1304 let keyslot = Luks2Keyslot::Luks2 {
1305 key_size: Luks2KeySize::Size32,
1306 priority: Some(Luks2KeyslotPriority::Normal),
1307 af: Luks2Af {
1308 af_type: Luks2AfType::Luks1,
1309 stripes: LUKS1_AF_STRIPES,
1310 hash: Luks2HashAlg::Sha256,
1311 },
1312 area: Luks2Area::Raw {
1313 encryption: Luks2AreaEncryption::AesXtsPlain64,
1314 key_size: Luks2KeySize::Size32,
1315 offset: Luks2U64(32768),
1316 size: Luks2U64(encrypted_data.len() as u64),
1317 },
1318 kdf,
1319 };
1320
1321 let mut digest_salt = [0u8; 32];
1323 rng.fill(&mut digest_salt);
1324 let digest_salt_b64 = base64::engine::general_purpose::STANDARD.encode(digest_salt);
1325
1326 let mut expected_digest = vec![0u8; 32];
1327 pbkdf2::pbkdf2::<hmac::Hmac<Sha256>>(&volume_key, &digest_salt, 1000, &mut expected_digest).unwrap();
1328 let expected_digest_b64 = base64::engine::general_purpose::STANDARD.encode(expected_digest);
1329
1330 let digest = Luks2Digest::Pbkdf2 {
1331 keyslots: vec![KeySlotId::new("0")],
1332 segments: vec!["0".to_string()],
1333 hash: Luks2HashAlg::Sha256,
1334 iterations: 1000,
1335 salt: digest_salt_b64,
1336 digest: expected_digest_b64,
1337 };
1338
1339 let segment = Luks2Segment::Crypt {
1340 offset: Luks2U64(16777216), iv_tweak: Luks2U64(0),
1342 size: Luks2SegmentSize::Dynamic,
1343 encryption: "aes-xts-plain64".to_string(),
1344 sector_size: 512,
1345 };
1346
1347 let mut keyslots = HashMap::new();
1348 keyslots.insert(KeySlotId::new("0"), keyslot);
1349
1350 let mut digests = HashMap::new();
1351 digests.insert("0".to_string(), digest);
1352
1353 let mut segments = HashMap::new();
1354 segments.insert("0".to_string(), segment);
1355
1356 let header = Luks2Header {
1357 version: 2,
1358 hdr_size: 0, seqid: 1,
1360 label: "test".to_string(),
1361 checksum_alg: Luks2HashAlg::Sha256,
1362 salt,
1363 uuid: LuksDeviceUuid::from_str("00000000-0000-0000-0000-000000000000").unwrap(),
1364 subsystem: "test".to_string(),
1365 hdr_offset: 0,
1366 checksum: [0u8; LUKS2_CHECKSUM_SIZE],
1367 metadata: Luks2Metadata {
1368 keyslots,
1369 tokens: HashMap::new(),
1370 segments,
1371 digests,
1372 config: Luks2Config {
1373 json_size: Luks2U64(LUKS2_DEFAULT_JSON_SIZE),
1374 keyslots_size: Luks2U64(LUKS2_DEFAULT_KEYSLOTS_SIZE),
1375 flags: None,
1376 },
1377 },
1378 };
1379
1380 let mut captured_keyslots = HashMap::new();
1381 captured_keyslots.insert(KeySlotId::new("0"), encrypted_data);
1382
1383 let device = LuksDevice {
1384 header: LuksHeader::V2(header),
1385 keyslots: captured_keyslots,
1386 unlocked_key: None,
1387 };
1388
1389 let mut buf = Cursor::new(Vec::new());
1391 device.to_writer(&mut buf).expect("to_writer failed");
1392
1393 buf.set_position(0);
1395 let mut read_device = LuksHeader::open(&mut buf).expect("open failed");
1396
1397 let key = UnlockKey::from(passphrase);
1398 read_device
1399 .unlock(&KeySlotId::new("0"), &key)
1400 .expect("unlock failed");
1401 assert!(read_device.verify(&KeySlotId::new("0")).expect("verify failed"));
1402 }
1403
1404 #[test]
1405 #[cfg(feature = "_write")]
1406 fn test_change_unlock_key() {
1407 use aes::cipher::KeyInit;
1408 use base64::Engine;
1409 use rand::RngExt;
1410 use xts_mode::{Xts128, get_tweak_default};
1411 let mut rng = rand::rng();
1412
1413 const TEST_ITERATIONS: u32 = 1000;
1414
1415 let mut salt = [0u8; LUKS2_SALT_SIZE];
1417 rng.fill(&mut salt);
1418
1419 let volume_key_size = AES128_KEY_SIZE * 2;
1420 let volume_key = vec![0x42u8; volume_key_size];
1421 let old_passphrase = "old passphrase";
1422 let new_passphrase = "new passphrase";
1423 let old_key = UnlockKey::from(old_passphrase);
1424 let new_key = UnlockKey::from(new_passphrase);
1425
1426 let mut keyslot_salt = [0u8; KDF_SALT_SIZE];
1428 rng.fill(&mut keyslot_salt);
1429 let keyslot_salt_b64 = base64::engine::general_purpose::STANDARD.encode(keyslot_salt);
1430
1431 let kdf = Luks2Kdf::Pbkdf2 {
1432 hash: Luks2HashAlg::Sha256,
1433 iterations: TEST_ITERATIONS,
1434 salt: keyslot_salt_b64,
1435 };
1436
1437 let keyslot_key = kdf.derive_key(&old_key, &salt, volume_key_size).unwrap();
1438
1439 let mut random_stripes = vec![0u8; volume_key_size * (LUKS1_AF_STRIPES - 1) as usize];
1441 rng.fill(&mut random_stripes[..]);
1442 let encrypted_keyslot_data = crate::af::split(
1443 &volume_key,
1444 &Luks2HashAlg::Sha256,
1445 LUKS1_AF_STRIPES,
1446 volume_key_size,
1447 random_stripes,
1448 )
1449 .unwrap();
1450
1451 let mut encrypted_data = encrypted_keyslot_data.clone();
1453 let cipher_1 = aes::Aes128::new_from_slice(&keyslot_key[0..AES128_KEY_SIZE]).unwrap();
1454 let cipher_2 = aes::Aes128::new_from_slice(&keyslot_key[AES128_KEY_SIZE..AES128_KEY_SIZE * 2]).unwrap();
1455 let xts = Xts128::new(cipher_1, cipher_2);
1456 for (i, chunk) in encrypted_data.chunks_mut(SECTOR_SIZE).enumerate() {
1457 xts.encrypt_area(chunk, SECTOR_SIZE, (i as u64).into(), |t| get_tweak_default(t));
1458 }
1459
1460 let keyslot = Luks2Keyslot::Luks2 {
1461 key_size: Luks2KeySize::Size32,
1462 priority: Some(Luks2KeyslotPriority::Normal),
1463 af: Luks2Af {
1464 af_type: Luks2AfType::Luks1,
1465 stripes: LUKS1_AF_STRIPES,
1466 hash: Luks2HashAlg::Sha256,
1467 },
1468 area: Luks2Area::Raw {
1469 encryption: Luks2AreaEncryption::AesXtsPlain64,
1470 key_size: Luks2KeySize::Size32,
1471 offset: Luks2U64(32768),
1472 size: Luks2U64(encrypted_data.len() as u64),
1473 },
1474 kdf,
1475 };
1476
1477 let mut digest_salt = [0u8; KDF_SALT_SIZE];
1479 rng.fill(&mut digest_salt);
1480 let digest_salt_b64 = base64::engine::general_purpose::STANDARD.encode(digest_salt);
1481
1482 let mut expected_digest = vec![0u8; SHA256_DIGEST_SIZE];
1483 pbkdf2::pbkdf2::<hmac::Hmac<Sha256>>(&volume_key, &digest_salt, TEST_ITERATIONS, &mut expected_digest)
1484 .unwrap();
1485 let expected_digest_b64 = base64::engine::general_purpose::STANDARD.encode(expected_digest);
1486
1487 let ks0 = KeySlotId::new("0");
1488 let digest = Luks2Digest::Pbkdf2 {
1489 keyslots: vec![ks0.clone()],
1490 segments: vec!["0".to_string()],
1491 hash: Luks2HashAlg::Sha256,
1492 iterations: TEST_ITERATIONS,
1493 salt: digest_salt_b64,
1494 digest: expected_digest_b64,
1495 };
1496
1497 let mut keyslots = HashMap::new();
1498 keyslots.insert(ks0.clone(), keyslot);
1499
1500 let mut digests = HashMap::new();
1501 digests.insert("0".to_string(), digest);
1502
1503 let header = Luks2Header {
1504 version: 2,
1505 hdr_size: 0,
1506 seqid: 1,
1507 label: "test".to_string(),
1508 checksum_alg: Luks2HashAlg::Sha256,
1509 salt,
1510 uuid: LuksDeviceUuid::from_str("00000000-0000-0000-0000-000000000000").unwrap(),
1511 subsystem: "test".to_string(),
1512 hdr_offset: 0,
1513 checksum: [0u8; LUKS2_CHECKSUM_SIZE],
1514 metadata: Luks2Metadata {
1515 keyslots,
1516 tokens: HashMap::new(),
1517 segments: HashMap::new(),
1518 digests,
1519 config: Luks2Config {
1520 json_size: Luks2U64(LUKS2_DEFAULT_JSON_SIZE),
1521 keyslots_size: Luks2U64(LUKS2_DEFAULT_KEYSLOTS_SIZE),
1522 flags: None,
1523 },
1524 },
1525 };
1526
1527 let mut captured_keyslots = HashMap::new();
1528 captured_keyslots.insert(ks0.clone(), encrypted_data);
1529
1530 let mut device = LuksDevice {
1531 header: LuksHeader::V2(header),
1532 keyslots: captured_keyslots,
1533 unlocked_key: None,
1534 };
1535
1536 device
1538 .change_unlock_key(&ks0, &old_key, &new_key)
1539 .expect("change_unlock_key failed");
1540
1541 device.unlock(&ks0, &new_key).expect("unlock failed");
1543 assert!(device.verify(&ks0).expect("verify with new passphrase failed"));
1544
1545 device
1546 .unlock(&ks0, &old_key)
1547 .expect_err("unlock with old passphrase should fail");
1548
1549 let derived_volume_key = device
1551 .get_volume_key(&ks0, &new_key)
1552 .expect("get_volume_key failed");
1553 assert_eq!(volume_key, derived_volume_key.expose_bytes());
1554 }
1555
1556 #[test]
1557 #[cfg(feature = "_write")]
1558 fn test_unlock() {
1559 use aes::cipher::KeyInit;
1560 use base64::Engine;
1561 use rand::RngExt;
1562 use xts_mode::{Xts128, get_tweak_default};
1563 let mut rng = rand::rng();
1564
1565 let mut salt = [0u8; LUKS2_SALT_SIZE];
1567 rng.fill(&mut salt);
1568
1569 let volume_key_size = AES128_KEY_SIZE * 2;
1570 let volume_key = vec![0x42u8; volume_key_size];
1571 let passphrase = "correct horse battery staple";
1572 let key = UnlockKey::from(passphrase);
1573
1574 let mut keyslot_salt = [0u8; KDF_SALT_SIZE];
1576 rng.fill(&mut keyslot_salt);
1577 let keyslot_salt_b64 = base64::engine::general_purpose::STANDARD.encode(keyslot_salt);
1578
1579 let kdf = Luks2Kdf::Pbkdf2 {
1580 hash: Luks2HashAlg::Sha256,
1581 iterations: 1000,
1582 salt: keyslot_salt_b64,
1583 };
1584
1585 let keyslot_key = kdf.derive_key(&key, &salt, volume_key_size).unwrap();
1586
1587 let mut random_stripes = vec![0u8; volume_key_size * (LUKS1_AF_STRIPES - 1) as usize];
1589 rng.fill(&mut random_stripes[..]);
1590 let encrypted_keyslot_data = crate::af::split(
1591 &volume_key,
1592 &Luks2HashAlg::Sha256,
1593 LUKS1_AF_STRIPES,
1594 volume_key_size,
1595 random_stripes,
1596 )
1597 .unwrap();
1598
1599 let mut encrypted_data = encrypted_keyslot_data.clone();
1601 let cipher_1 = aes::Aes128::new_from_slice(&keyslot_key[0..AES128_KEY_SIZE]).unwrap();
1602 let cipher_2 = aes::Aes128::new_from_slice(&keyslot_key[AES128_KEY_SIZE..AES128_KEY_SIZE * 2]).unwrap();
1603 let xts = Xts128::new(cipher_1, cipher_2);
1604 for (i, chunk) in encrypted_data.chunks_mut(SECTOR_SIZE).enumerate() {
1605 xts.encrypt_area(chunk, SECTOR_SIZE, (i as u64).into(), |t| get_tweak_default(t));
1606 }
1607
1608 let keyslot = Luks2Keyslot::Luks2 {
1609 key_size: Luks2KeySize::Size32,
1610 priority: Some(Luks2KeyslotPriority::Normal),
1611 af: Luks2Af {
1612 af_type: Luks2AfType::Luks1,
1613 stripes: LUKS1_AF_STRIPES,
1614 hash: Luks2HashAlg::Sha256,
1615 },
1616 area: Luks2Area::Raw {
1617 encryption: Luks2AreaEncryption::AesXtsPlain64,
1618 key_size: Luks2KeySize::Size32,
1619 offset: Luks2U64(32768),
1620 size: Luks2U64(encrypted_data.len() as u64),
1621 },
1622 kdf,
1623 };
1624
1625 let mut digest_salt = [0u8; KDF_SALT_SIZE];
1627 rng.fill(&mut digest_salt);
1628 let digest_salt_b64 = base64::engine::general_purpose::STANDARD.encode(digest_salt);
1629
1630 let mut expected_digest = vec![0u8; SHA256_DIGEST_SIZE];
1631 pbkdf2::pbkdf2::<hmac::Hmac<Sha256>>(&volume_key, &digest_salt, 1000, &mut expected_digest).unwrap();
1632 let expected_digest_b64 = base64::engine::general_purpose::STANDARD.encode(expected_digest);
1633
1634 let ks0 = KeySlotId::new("0");
1635 let digest = Luks2Digest::Pbkdf2 {
1636 keyslots: vec![ks0.clone()],
1637 segments: vec!["0".to_string()],
1638 hash: Luks2HashAlg::Sha256,
1639 iterations: 1000,
1640 salt: digest_salt_b64,
1641 digest: expected_digest_b64,
1642 };
1643
1644 let mut keyslots = HashMap::new();
1645 keyslots.insert(ks0.clone(), keyslot);
1646
1647 let mut digests = HashMap::new();
1648 digests.insert("0".to_string(), digest);
1649
1650 let header = Luks2Header {
1651 version: 2,
1652 hdr_size: 0,
1653 seqid: 1,
1654 label: "test".to_string(),
1655 checksum_alg: Luks2HashAlg::Sha256,
1656 salt,
1657 uuid: LuksDeviceUuid::from_str("00000000-0000-0000-0000-000000000000").unwrap(),
1658 subsystem: "test".to_string(),
1659 hdr_offset: 0,
1660 checksum: [0u8; LUKS2_CHECKSUM_SIZE],
1661 metadata: Luks2Metadata {
1662 keyslots,
1663 tokens: HashMap::new(),
1664 segments: HashMap::new(),
1665 digests,
1666 config: Luks2Config {
1667 json_size: Luks2U64(LUKS2_DEFAULT_JSON_SIZE),
1668 keyslots_size: Luks2U64(LUKS2_DEFAULT_KEYSLOTS_SIZE),
1669 flags: None,
1670 },
1671 },
1672 };
1673
1674 let mut captured_keyslots = HashMap::new();
1675 captured_keyslots.insert(ks0.clone(), encrypted_data);
1676
1677 let mut device = LuksDevice {
1678 header: LuksHeader::V2(header),
1679 keyslots: captured_keyslots,
1680 unlocked_key: None,
1681 };
1682
1683 device.unlock(&ks0, &key).expect("unlock failed");
1685
1686 assert!(device.unlocked_key.is_some());
1688 assert_eq!(volume_key, device.unlocked_key.as_ref().unwrap().expose_bytes());
1689 }
1690
1691 #[test]
1692 #[cfg(feature = "_write")]
1693 fn test_header_roundtrip() {
1694 let mut salt = [0u8; LUKS2_SALT_SIZE];
1695 salt[0..4].copy_from_slice(b"salt");
1696 let header = Luks2Header {
1697 version: 2,
1698 hdr_size: 16384, seqid: 1,
1700 label: "test".to_string(),
1701 checksum_alg: Luks2HashAlg::Sha256,
1702 salt,
1703 uuid: LuksDeviceUuid::from_str("00000000-0000-0000-0000-000000000000").unwrap(),
1704 subsystem: "test".to_string(),
1705 hdr_offset: 0,
1706 checksum: [0u8; LUKS2_CHECKSUM_SIZE], metadata: Luks2Metadata {
1708 keyslots: HashMap::new(),
1709 tokens: HashMap::new(),
1710 segments: HashMap::new(),
1711 digests: HashMap::new(),
1712 config: Luks2Config {
1713 json_size: Luks2U64(LUKS2_DEFAULT_JSON_SIZE),
1714 keyslots_size: Luks2U64(LUKS2_DEFAULT_KEYSLOTS_SIZE),
1715 flags: None,
1716 },
1717 },
1718 };
1719
1720 let mut buf = Cursor::new(Vec::new());
1721 header.to_writer(&mut buf).expect("to_writer failed");
1722
1723 buf.set_position(0);
1724 let read_header = LuksHeader::from_reader(&mut buf).expect("from_reader failed");
1725
1726 if let LuksHeader::V2(h) = read_header {
1727 assert_eq!(h.version, header.version);
1728 assert_eq!(h.label, header.label);
1729 assert_eq!(h.uuid, header.uuid);
1730 assert_eq!(h.subsystem, header.subsystem);
1731 assert_eq!(h.checksum_alg, header.checksum_alg);
1732 assert_eq!(h.salt, header.salt);
1733 assert_eq!(h.hdr_offset, header.hdr_offset);
1734 assert!(h.hdr_size >= LUKS2_BINARY_HEADER_SIZE as u64);
1736 } else {
1737 panic!("Expected LUKS2 header");
1738 }
1739 }
1740
1741 #[test]
1742 #[cfg(feature = "_write")]
1743 fn test_num_keyslots() {
1744 let mut binary_header = vec![0u8; LUKS2_BINARY_HEADER_SIZE];
1745 let json_data = format!(
1746 r#"{{
1747 "keyslots": {{
1748 "0": {{
1749 "type": "luks2",
1750 "key_size": 64,
1751 "priority": 1,
1752 "af": {{ "type": "luks1", "stripes": 4000, "hash": "{}" }},
1753 "area": {{ "type": "raw", "encryption": "aes-xts-plain64", "key_size": 64, "offset": "32768", "size": "131072" }},
1754 "kdf": {{ "type": "argon2i", "time": 4, "memory": 235980, "cpus": 2, "salt": "z6vz4xK7cjan92rDA5JF8O6Jk2HouV0O8DMB6GlztVk=" }}
1755 }},
1756 "1": {{
1757 "type": "luks2",
1758 "key_size": 64,
1759 "priority": 1,
1760 "af": {{ "type": "luks1", "stripes": 4000, "hash": "{}" }},
1761 "area": {{ "type": "raw", "encryption": "aes-xts-plain64", "key_size": 64, "offset": "163840", "size": "131072" }},
1762 "kdf": {{ "type": "pbkdf2", "hash": "sha256", "iterations": 1774240, "salt": "vWcwY3rx2fKpXW2Q6oSCNf8j5bvdJyEzB6BNXECGDsI=" }}
1763 }}
1764 }},
1765 "tokens": {{}},
1766 "segments": {{}},
1767 "digests": {{}},
1768 "config": {{
1769 "json_size": "{}",
1770 "keyslots_size": "{}"
1771 }}
1772 }}"#,
1773 HASH_SHA256, HASH_SHA256, LUKS2_DEFAULT_JSON_SIZE, LUKS2_DEFAULT_KEYSLOTS_SIZE
1774 );
1775 let hdr_size = LUKS2_BINARY_HEADER_SIZE as u64 + json_data.len() as u64;
1776
1777 {
1778 let mut cursor = Cursor::new(&mut binary_header);
1779 cursor.write_all(&LUKS_MAGIC).unwrap();
1780 cursor.write_u16::<BigEndian>(2).unwrap();
1781 cursor.write_u64::<BigEndian>(hdr_size).unwrap();
1782 cursor.write_u64::<BigEndian>(1).unwrap();
1783
1784 let label = [0u8; LUKS2_LABEL_SIZE];
1785 cursor.write_all(&label).unwrap();
1786
1787 cursor.write_all(&Luks2HashAlg::Sha256.to_bytes()).unwrap();
1788
1789 cursor.write_all(&[0u8; LUKS2_SALT_SIZE]).unwrap();
1790
1791 let mut uuid = [0u8; LUKS2_UUID_SIZE];
1792 uuid[..4].copy_from_slice(b"abcd");
1793 cursor.write_all(&uuid).unwrap();
1794
1795 let subsystem = [0u8; LUKS2_SUBSYSTEM_SIZE];
1796 cursor.write_all(&subsystem).unwrap();
1797
1798 cursor.write_u64::<BigEndian>(0).unwrap();
1799 }
1800
1801 let mut hasher = Sha256::new();
1802 hasher.update(&binary_header);
1803 hasher.update(json_data.as_bytes());
1804 let result = hasher.finalize();
1805
1806 binary_header[LUKS2_CHECKSUM_OFFSET..LUKS2_CHECKSUM_OFFSET + SHA256_DIGEST_SIZE]
1807 .copy_from_slice(&result);
1808
1809 let mut buf = binary_header;
1810 buf.extend_from_slice(json_data.as_bytes());
1811
1812 let cursor = Cursor::new(buf);
1813 let header = LuksHeader::from_reader(cursor).unwrap();
1814
1815 assert_eq!(header.num_keyslots(), 2);
1816 }
1817
1818 #[test]
1819 fn test_luks_uuid_parsing() {
1820 assert!(LuksDeviceUuid::from_str("550e8400-e29b-41d4-a716-446655440000").is_ok());
1821 assert!(LuksDeviceUuid::from_str("abcd").is_ok());
1822 assert!(LuksDeviceUuid::from_str("").is_err());
1823 assert!(LuksDeviceUuid::from_str("invalid-char!").is_err());
1824 assert!(LuksDeviceUuid::from_str(&"a".repeat(40)).is_err());
1825 }
1826
1827 #[test]
1828 fn test_parse_full_example_json() {
1829 let json_data = r#"{
1830 "keyslots": {
1831 "0": {
1832 "type": "luks2",
1833 "key_size": 32,
1834 "af": {
1835 "type": "luks1",
1836 "stripes": 4000,
1837 "hash": "sha256"
1838 },
1839 "area": {
1840 "type": "raw",
1841 "encryption": "aes-xts-plain64",
1842 "key_size": 32,
1843 "offset": "32768",
1844 "size": "131072"
1845 },
1846 "kdf": {
1847 "type": "argon2i",
1848 "time": 4,
1849 "memory": 235980,
1850 "cpus": 2,
1851 "salt": "z6vz4xK7cjan92rDA5JF8O6Jk2HouV0O8DMB6GlztVk="
1852 }
1853 },
1854 "1": {
1855 "type": "luks2",
1856 "key_size": 32,
1857 "af": {
1858 "type": "luks1",
1859 "stripes": 4000,
1860 "hash": "sha256"
1861 },
1862 "area": {
1863 "type": "raw",
1864 "encryption": "aes-xts-plain64",
1865 "key_size": 32,
1866 "offset": "163840",
1867 "size": "131072"
1868 },
1869 "kdf": {
1870 "type": "pbkdf2",
1871 "hash": "sha256",
1872 "iterations": 1774240,
1873 "salt": "vWcwY3rx2fKpXW2Q6oSCNf8j5bvdJyEzB6BNXECGDsI="
1874 }
1875 }
1876 },
1877 "tokens": {
1878 "0": {
1879 "type": "luks2-keyring",
1880 "keyslots": [
1881 "1"
1882 ],
1883 "key_description": "MyKeyringKeyID"
1884 }
1885 },
1886 "segments": {
1887 "0": {
1888 "type": "crypt",
1889 "offset": "4194304",
1890 "iv_tweak": "0",
1891 "size": "dynamic",
1892 "encryption": "aes-xts-plain64",
1893 "sector_size": 512
1894 }
1895 },
1896 "digests": {
1897 "0": {
1898 "type": "pbkdf2",
1899 "keyslots": [
1900 "0",
1901 "1"
1902 ],
1903 "segments": [
1904 "0"
1905 ],
1906 "hash": "sha256",
1907 "iterations": 110890,
1908 "salt": "G8gqtKhS96IbogHyJLO+t9kmjLkx+DM3HHJqQtgc2Dk=",
1909 "digest": "C9JWko5m+oYmjg6R0t/98cGGzLr/4UaG3hImSJMivfc="
1910 }
1911 },
1912 "config": {
1913 "json_size": "12288",
1914 "keyslots_size": "4161536",
1915 "flags": [
1916 "allow-discards"
1917 ]
1918 }
1919 }"#;
1920 let metadata: Luks2Metadata = serde_json::from_str(json_data).unwrap();
1921 assert_eq!(metadata.keyslots.len(), 2);
1922 assert_eq!(metadata.tokens.len(), 1);
1923 assert_eq!(metadata.segments.len(), 1);
1924 assert_eq!(metadata.digests.len(), 1);
1925 assert_eq!(metadata.config.json_size, Luks2U64(12288));
1926
1927 let ks0 = metadata.keyslots.get(&KeySlotId::new("0")).unwrap();
1928 let Luks2Keyslot::Luks2 { key_size, kdf, .. } = ks0 else {
1929 panic!("Expected Luks2 keyslot")
1930 };
1931 assert_eq!(*key_size, Luks2KeySize::Size32);
1932 assert!(matches!(kdf, Luks2Kdf::Argon2i { .. }));
1933
1934 let ks1 = metadata.keyslots.get(&KeySlotId::new("1")).unwrap();
1935 let Luks2Keyslot::Luks2 { kdf, .. } = ks1 else {
1936 panic!("Expected Luks2 keyslot")
1937 };
1938 assert!(matches!(kdf, Luks2Kdf::Pbkdf2 { .. }));
1939
1940 let token0 = metadata.tokens.get("0").unwrap();
1941 assert!(matches!(token0, Luks2Token::Keyring { .. }));
1942
1943 let segment0 = metadata.segments.get("0").unwrap();
1944 let Luks2Segment::Crypt { size, .. } = segment0;
1945 assert_eq!(size, &Luks2SegmentSize::Dynamic);
1946
1947 let digest0 = metadata.digests.get("0").unwrap();
1948 assert!(matches!(digest0, Luks2Digest::Pbkdf2 { .. }));
1949 }
1950
1951 #[test]
1952 fn test_parse_luks_rs_keyring_token() {
1953 let json_data = r#"{
1954 "keyslots": {},
1955 "tokens": {
1956 "0": {
1957 "type": "luks-rs-keyring",
1958 "keyslots": ["0"],
1959 "key_description": "MyLuksRsKey"
1960 }
1961 },
1962 "segments": {},
1963 "digests": {},
1964 "config": { "json_size": "12288", "keyslots_size": "4161536" }
1965 }"#;
1966 let metadata: Luks2Metadata = serde_json::from_str(json_data).unwrap();
1967 let token = metadata.tokens.get("0").unwrap();
1968 if let Luks2Token::LuksRsKeyring {
1969 keyslots,
1970 key_description,
1971 } = token
1972 {
1973 assert_eq!(keyslots, &vec![KeySlotId::new("0")]);
1974 assert_eq!(key_description, "MyLuksRsKey");
1975 } else {
1976 panic!("Expected LuksRsKeyring token");
1977 }
1978 }
1979
1980 #[test]
1981 fn test_invalid_keyslot_area_type() {
1982 let json_data = r#"{
1983 "keyslots": {
1984 "0": {
1985 "type": "luks2",
1986 "key_size": 32,
1987 "af": { "type": "luks1", "stripes": 4000, "hash": "sha256" },
1988 "area": { "type": "none", "encryption": "aes-xts-plain64", "key_size": 32, "offset": "32768", "size": "131072" },
1989 "kdf": { "type": "argon2i", "time": 4, "memory": 235980, "cpus": 2, "salt": "salt" }
1990 }
1991 },
1992 "tokens": {},
1993 "segments": {},
1994 "digests": {},
1995 "config": { "json_size": "12288", "keyslots_size": "4161536" }
1996 }"#;
1997 let result: Result<Luks2Metadata, _> = serde_json::from_str(json_data);
1998 assert!(result.is_err());
1999 assert!(
2000 result
2001 .unwrap_err()
2002 .to_string()
2003 .contains("LUKS2 keyslot must have area type 'raw'")
2004 );
2005 }
2006
2007 #[test]
2008 fn test_parse_reencrypt_keyslot() {
2009 let json_data = r#"{
2010 "keyslots": {
2011 "0": {
2012 "type": "reencrypt",
2013 "mode": "reencrypt",
2014 "direction": "forward",
2015 "key_size": "1",
2016 "priority": 1,
2017 "af": { "type": "luks1", "stripes": 4000, "hash": "sha256" },
2018 "area": { "type": "none", "encryption": "aes-xts-plain64", "key_size": 32, "offset": "32768", "size": "131072" },
2019 "kdf": { "type": "argon2i", "time": 4, "memory": 235980, "cpus": 2, "salt": "salt" }
2020 }
2021 },
2022 "tokens": {},
2023 "segments": {},
2024 "digests": {},
2025 "config": { "json_size": "12288", "keyslots_size": "4161536" }
2026 }"#;
2027 let metadata: Luks2Metadata = serde_json::from_str(json_data).unwrap();
2028 let slot = metadata.keyslots.get(&KeySlotId::new("0")).unwrap();
2029 let Luks2Keyslot::Reencrypt {
2030 mode,
2031 direction,
2032 key_size,
2033 ..
2034 } = slot
2035 else {
2036 panic!("Expected Reencrypt keyslot")
2037 };
2038 assert_eq!(*mode, Luks2ReencryptMode::Reencrypt);
2039 assert_eq!(*direction, Luks2ReencryptDirection::Forward);
2040 assert_eq!(key_size, "1");
2041 }
2042
2043 #[test]
2044 fn test_parse_reencrypt_area_types() {
2045 let json_data = r#"{
2046 "keyslots": {
2047 "checksum_slot": {
2048 "type": "reencrypt",
2049 "mode": "reencrypt",
2050 "direction": "forward",
2051 "key_size": "1",
2052 "af": { "type": "luks1", "stripes": 4000, "hash": "sha256" },
2053 "area": { "type": "checksum", "hash": "sha256", "sector_size": 512, "offset": "32768", "size": "131072" },
2054 "kdf": { "type": "argon2i", "time": 4, "memory": 235980, "cpus": 2, "salt": "salt" }
2055 },
2056 "datashift_slot": {
2057 "type": "reencrypt",
2058 "mode": "reencrypt",
2059 "direction": "forward",
2060 "key_size": "1",
2061 "af": { "type": "luks1", "stripes": 4000, "hash": "sha256" },
2062 "area": { "type": "datashift", "shift_size": "4096", "offset": "32768", "size": "131072" },
2063 "kdf": { "type": "argon2i", "time": 4, "memory": 235980, "cpus": 2, "salt": "salt" }
2064 },
2065 "datashift_checksum_slot": {
2066 "type": "reencrypt",
2067 "mode": "reencrypt",
2068 "direction": "forward",
2069 "key_size": "1",
2070 "af": { "type": "luks1", "stripes": 4000, "hash": "sha256" },
2071 "area": { "type": "datashift-checksum", "hash": "sha256", "sector_size": 512, "shift_size": "4096", "offset": "32768", "size": "131072" },
2072 "kdf": { "type": "argon2i", "time": 4, "memory": 235980, "cpus": 2, "salt": "salt" }
2073 }
2074 },
2075 "tokens": {},
2076 "segments": {},
2077 "digests": {},
2078 "config": { "json_size": "12288", "keyslots_size": "4161536" }
2079 }"#;
2080 let metadata: Luks2Metadata = serde_json::from_str(json_data).unwrap();
2081
2082 let checksum_slot = metadata.keyslots.get(&KeySlotId::new("checksum_slot")).unwrap();
2083 if let Luks2Keyslot::Reencrypt { area, .. } = checksum_slot {
2084 assert!(matches!(area, Luks2Area::Checksum { .. }));
2085 } else {
2086 panic!("Expected Reencrypt keyslot")
2087 }
2088
2089 let datashift_slot = metadata.keyslots.get(&KeySlotId::new("datashift_slot")).unwrap();
2090 if let Luks2Keyslot::Reencrypt { area, .. } = datashift_slot {
2091 assert!(matches!(area, Luks2Area::Datashift { .. }));
2092 } else {
2093 panic!("Expected Reencrypt keyslot")
2094 }
2095
2096 let datashift_checksum_slot = metadata
2097 .keyslots
2098 .get(&KeySlotId::new("datashift_checksum_slot"))
2099 .unwrap();
2100 if let Luks2Keyslot::Reencrypt { area, .. } = datashift_checksum_slot {
2101 assert!(matches!(area, Luks2Area::DatashiftChecksum { .. }));
2102 } else {
2103 panic!("Expected Reencrypt keyslot")
2104 }
2105 }
2106
2107 #[test]
2108 fn test_parse_argon2id_kdf() {
2109 let json_data = r#"{
2110 "keyslots": {
2111 "0": {
2112 "type": "luks2",
2113 "key_size": 32,
2114 "af": { "type": "luks1", "stripes": 4000, "hash": "sha256" },
2115 "area": { "type": "raw", "encryption": "aes-xts-plain64", "key_size": 32, "offset": "32768", "size": "131072" },
2116 "kdf": { "type": "argon2id", "time": 4, "memory": 235980, "cpus": 2, "salt": "salt" }
2117 }
2118 },
2119 "tokens": {},
2120 "segments": {},
2121 "digests": {},
2122 "config": { "json_size": "12288", "keyslots_size": "4161536" }
2123 }"#;
2124 let metadata: Luks2Metadata = serde_json::from_str(json_data).unwrap();
2125 let slot = metadata.keyslots.get(&KeySlotId::new("0")).unwrap();
2126 let Luks2Keyslot::Luks2 { kdf, .. } = slot else {
2127 panic!("Expected Luks2 keyslot")
2128 };
2129 assert!(matches!(
2130 kdf,
2131 Luks2Kdf::Argon2id {
2132 time: 4,
2133 memory: 235980,
2134 cpus: 2,
2135 ..
2136 }
2137 ));
2138 }
2139
2140 #[test]
2141 fn test_is_luks_device() {
2142 let path = std::env::temp_dir().join("test_luks_magic");
2143 std::fs::write(&path, &LUKS_MAGIC).unwrap();
2144 assert!(is_luks_device(&path).unwrap());
2145 std::fs::remove_file(&path).unwrap();
2146
2147 let path = std::env::temp_dir().join("test_not_luks_magic");
2148 std::fs::write(&path, b"NOTLUK").unwrap();
2149 assert!(!is_luks_device(&path).unwrap());
2150 std::fs::remove_file(&path).unwrap();
2151
2152 let path = std::env::temp_dir().join("test_short_luks_magic");
2153 std::fs::write(&path, b"LUKS").unwrap();
2154 assert!(!is_luks_device(&path).unwrap());
2155 std::fs::remove_file(&path).unwrap();
2156 }
2157
2158 #[test]
2159 #[cfg(feature = "_challenge_response")]
2160 fn test_challenge_response_e2e() {
2161 use crate::hash::SHA256_DIGEST_SIZE;
2162 use std::collections::HashMap;
2163
2164 const TEST_KEY_SIZE: usize = 64;
2165 const TEST_AREA_SIZE: usize = 131072;
2166 const TEST_ITERATIONS: u32 = 1000;
2167 const TEST_VOL_KEY_SIZE: usize = 64;
2168 const TEST_CR_SECRET: &[u8] = &[0x01, 0x02, 0x03, 0x04];
2169
2170 let json_metadata = format!(
2172 r#"{{
2173 "keyslots": {{
2174 "0": {{
2175 "type": "luks2",
2176 "key_size": {key_size},
2177 "af": {{ "type": "luks1", "stripes": 4000, "hash": "sha256" }},
2178 "area": {{ "type": "raw", "encryption": "aes-xts-plain64", "key_size": {key_size}, "offset": "{area_offset}", "size": "{area_size}" }},
2179 "kdf": {{ "type": "argon2id", "time": 1, "memory": 1024, "cpus": 1, "salt": "c2FsdA==" }}
2180 }}
2181 }},
2182 "tokens": {{}},
2183 "segments": {{}},
2184 "digests": {{
2185 "0": {{
2186 "type": "pbkdf2",
2187 "keyslots": ["0"],
2188 "segments": [],
2189 "hash": "sha256",
2190 "iterations": {iterations},
2191 "salt": "c2FsdA==",
2192 "digest": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
2193 }}
2194 }},
2195 "config": {{ "json_size": "{json_size}", "keyslots_size": "{keyslots_size}" }}
2196 }}"#,
2197 key_size = TEST_KEY_SIZE,
2198 area_offset = LUKS2_BINARY_HEADER_SIZE * 8,
2199 area_size = TEST_AREA_SIZE,
2200 iterations = TEST_ITERATIONS,
2201 json_size = LUKS2_DEFAULT_JSON_SIZE,
2202 keyslots_size = LUKS2_DEFAULT_KEYSLOTS_SIZE
2203 );
2204
2205 let metadata: Luks2Metadata = serde_json::from_str(&json_metadata).unwrap();
2206 let header = LuksHeader::V2(Luks2Header {
2207 version: 2,
2208 hdr_size: (LUKS2_BINARY_HEADER_SIZE * 4) as u64,
2209 seqid: 1,
2210 label: "test".to_string(),
2211 checksum_alg: Luks2HashAlg::Sha256,
2212 salt: [0u8; LUKS2_SALT_SIZE],
2213 uuid: LuksDeviceUuid::from_str("00000000-0000-0000-0000-000000000000").unwrap(),
2214 subsystem: "".to_string(),
2215 hdr_offset: 0,
2216 checksum: [0u8; LUKS2_CHECKSUM_SIZE],
2217 metadata,
2218 });
2219
2220 let mut keyslots = HashMap::new();
2222 let slot0 = KeySlotId::from("0");
2223 keyslots.insert(slot0.clone(), vec![0u8; TEST_AREA_SIZE]);
2224
2225 let mut device = LuksDevice {
2226 header,
2227 keyslots: keyslots.clone(),
2228 unlocked_key: None,
2229 };
2230
2231 let volume_key_bytes = vec![0x42u8; TEST_VOL_KEY_SIZE];
2235 let volume_key = VolumeKey::new(volume_key_bytes.clone()).unwrap();
2236
2237 if let LuksHeader::V2(ref mut h) = device.header {
2239 if let Some(crate::Luks2Digest::Pbkdf2 { digest, salt, .. }) = h.metadata.digests.get_mut("0") {
2240 let salt_bytes = base64::engine::general_purpose::STANDARD.decode(&salt).unwrap();
2241 let mut expected_digest = vec![0u8; SHA256_DIGEST_SIZE];
2242 pbkdf2::pbkdf2::<hmac::Hmac<sha2::Sha256>>(
2243 &volume_key_bytes,
2244 &salt_bytes,
2245 TEST_ITERATIONS,
2246 &mut expected_digest,
2247 )
2248 .unwrap();
2249 *digest = base64::engine::general_purpose::STANDARD.encode(expected_digest);
2250 }
2251 }
2252
2253 let old_password = "old-password".to_string();
2254 let old_key = UnlockKey::from_passphrase(old_password);
2255
2256 device.update_keyslot(&slot0, &old_key, &volume_key).unwrap();
2259
2260 let new_password = "new-password".to_string();
2262 let new_key =
2263 UnlockKey::from_passphrase(new_password).with_software_challenge_response(TEST_CR_SECRET.to_vec());
2264
2265 device.change_unlock_key(&slot0, &old_key, &new_key).unwrap();
2266
2267 let mut device_to_unlock = device;
2269
2270 let cr_slots = device_to_unlock.get_challenge_response_keyslots();
2272 assert_eq!(cr_slots.len(), 1, "Token should be automatically created");
2273 let (slot_id, _serial, _slot) = &cr_slots[0];
2274 assert_eq!(slot_id, &slot0);
2275
2276 device_to_unlock
2277 .unlock(&slot0, &new_key)
2278 .expect("Should unlock with CR");
2279 assert!(device_to_unlock.unlocked_key.is_some());
2280
2281 let password_only_key = UnlockKey::from_passphrase("simple-password".to_string());
2283 device_to_unlock
2284 .change_unlock_key(&slot0, &new_key, &password_only_key)
2285 .unwrap();
2286 assert_eq!(
2287 device_to_unlock.get_challenge_response_keyslots().len(),
2288 0,
2289 "Token should be removed"
2290 );
2291
2292 device_to_unlock
2294 .change_unlock_key(&slot0, &password_only_key, &new_key)
2295 .unwrap();
2296 let wrong_cr_key = UnlockKey::from_passphrase("new-password".to_string())
2297 .with_software_challenge_response(vec![0x00, 0x00, 0x00, 0x00]);
2298 let result = device_to_unlock.get_volume_key(&slot0, &wrong_cr_key);
2299 let vk = result.unwrap();
2300 device_to_unlock.unlocked_key = Some(vk);
2301 assert!(!device_to_unlock.verify(&slot0).unwrap());
2302 }
2303}