use crate::bytes::{be_u16, be_u32, bytes_n, cstr};
use crate::error::{LuksError, Result};
pub const LUKS_MAGIC: [u8; 6] = [b'L', b'U', b'K', b'S', 0xba, 0xbe];
pub const LUKS1_PHDR_LEN: usize = 592;
pub const LUKS_NUM_KEYS: usize = 8;
pub const KEYSLOT_LEN: usize = 48;
pub const MK_DIGEST_LEN: usize = 20;
pub const SALT_LEN: usize = 32;
pub const KEY_ENABLED: u32 = 0x00AC_71F3;
pub const KEY_DISABLED: u32 = 0x0000_DEAD;
pub const SECTOR: u64 = 512;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Keyslot {
pub active: u32,
pub iterations: u32,
pub salt: [u8; SALT_LEN],
pub key_material_offset: u32,
pub stripes: u32,
}
impl Keyslot {
#[must_use]
pub fn is_active(&self) -> bool {
self.active == KEY_ENABLED
}
#[must_use]
pub fn is_disabled(&self) -> bool {
self.active == KEY_DISABLED
}
}
#[derive(Debug, Clone)]
pub struct Luks1Header {
pub version: u16,
pub cipher_name: String,
pub cipher_mode: String,
pub hash_spec: String,
pub payload_offset: u32,
pub key_bytes: u32,
pub mk_digest: [u8; MK_DIGEST_LEN],
pub mk_digest_salt: [u8; SALT_LEN],
pub mk_digest_iter: u32,
pub uuid: String,
pub keyslots: [Keyslot; LUKS_NUM_KEYS],
}
impl Luks1Header {
pub fn parse(data: &[u8]) -> Result<Self> {
let magic = bytes_n::<6>(data, 0);
if magic != LUKS_MAGIC {
return Err(LuksError::NotLuks { found: magic });
}
if data.len() < LUKS1_PHDR_LEN {
return Err(LuksError::MalformedHeader {
what: "phdr",
need: LUKS1_PHDR_LEN,
got: data.len(),
});
}
let version = be_u16(data, 6);
if version != 1 {
return Err(LuksError::UnsupportedVersion { version });
}
let keyslots = std::array::from_fn(|i| {
let base = 208 + i * KEYSLOT_LEN;
Keyslot {
active: be_u32(data, base),
iterations: be_u32(data, base + 4),
salt: bytes_n::<SALT_LEN>(data, base + 8),
key_material_offset: be_u32(data, base + 40),
stripes: be_u32(data, base + 44),
}
});
Ok(Luks1Header {
version,
cipher_name: cstr(data, 8, 32),
cipher_mode: cstr(data, 40, 32),
hash_spec: cstr(data, 72, 32),
payload_offset: be_u32(data, 104),
key_bytes: be_u32(data, 108),
mk_digest: bytes_n::<MK_DIGEST_LEN>(data, 112),
mk_digest_salt: bytes_n::<SALT_LEN>(data, 132),
mk_digest_iter: be_u32(data, 164),
uuid: cstr(data, 168, 40),
keyslots,
})
}
pub fn active_keyslots(&self) -> impl Iterator<Item = &Keyslot> {
self.keyslots.iter().filter(|k| k.is_active())
}
#[must_use]
pub fn payload_byte_offset(&self) -> u64 {
u64::from(self.payload_offset) * SECTOR
}
}
#[cfg(test)]
mod tests {
use super::*;
fn build_header() -> Vec<u8> {
let mut h = vec![0u8; LUKS1_PHDR_LEN];
h[0..6].copy_from_slice(&LUKS_MAGIC);
h[6..8].copy_from_slice(&1u16.to_be_bytes());
h[8..11].copy_from_slice(b"aes");
h[40..51].copy_from_slice(b"xts-plain64");
h[72..78].copy_from_slice(b"sha256");
h[104..108].copy_from_slice(&4096u32.to_be_bytes()); h[108..112].copy_from_slice(&64u32.to_be_bytes()); h[112..132].copy_from_slice(&[0xAB; 20]); h[132..164].copy_from_slice(&[0xCD; 32]); h[164..168].copy_from_slice(&1000u32.to_be_bytes()); h[168..204].copy_from_slice(b"b22690e1-a392-4ecc-83b1-c1cf21200116");
let b = 208;
h[b..b + 4].copy_from_slice(&KEY_ENABLED.to_be_bytes());
h[b + 4..b + 8].copy_from_slice(&50000u32.to_be_bytes());
h[b + 8..b + 40].copy_from_slice(&[0x11; 32]);
h[b + 40..b + 44].copy_from_slice(&8u32.to_be_bytes());
h[b + 44..b + 48].copy_from_slice(&4000u32.to_be_bytes());
let b1 = 208 + KEYSLOT_LEN;
h[b1..b1 + 4].copy_from_slice(&KEY_DISABLED.to_be_bytes());
h
}
#[test]
fn parses_luks1_header_fields() {
let h = Luks1Header::parse(&build_header()).unwrap();
assert_eq!(h.version, 1);
assert_eq!(h.cipher_name, "aes");
assert_eq!(h.cipher_mode, "xts-plain64");
assert_eq!(h.hash_spec, "sha256");
assert_eq!(h.payload_offset, 4096);
assert_eq!(h.payload_byte_offset(), 4096 * 512);
assert_eq!(h.key_bytes, 64);
assert_eq!(h.mk_digest, [0xAB; 20]);
assert_eq!(h.mk_digest_salt, [0xCD; 32]);
assert_eq!(h.mk_digest_iter, 1000);
assert_eq!(h.uuid, "b22690e1-a392-4ecc-83b1-c1cf21200116");
}
#[test]
fn parses_keyslots_and_active_filter() {
let h = Luks1Header::parse(&build_header()).unwrap();
assert!(h.keyslots[0].is_active());
assert_eq!(h.keyslots[0].iterations, 50000);
assert_eq!(h.keyslots[0].salt, [0x11; 32]);
assert_eq!(h.keyslots[0].key_material_offset, 8);
assert_eq!(h.keyslots[0].stripes, 4000);
assert!(!h.keyslots[1].is_active());
assert!(h.keyslots[1].is_disabled());
assert_eq!(h.active_keyslots().count(), 1);
}
#[test]
fn rejects_non_luks() {
let err = Luks1Header::parse(&[0u8; LUKS1_PHDR_LEN]).unwrap_err();
assert!(matches!(err, LuksError::NotLuks { .. }));
}
#[test]
fn rejects_bad_version() {
let mut h = build_header();
h[6..8].copy_from_slice(&9u16.to_be_bytes());
assert!(matches!(
Luks1Header::parse(&h).unwrap_err(),
LuksError::UnsupportedVersion { version: 9 }
));
}
#[test]
fn rejects_truncated() {
let mut h = build_header();
h.truncate(100);
assert!(matches!(
Luks1Header::parse(&h).unwrap_err(),
LuksError::MalformedHeader { .. }
));
}
}