pub mod af;
pub mod error;
pub mod password;
use self::error::{LuksError, ParseError};
use aes::{Aes128, Aes256, NewBlockCipher};
use bincode::Options;
use hmac::Hmac;
use secrecy::{CloneableSecret, DebugSecret, ExposeSecret, Secret, Zeroize};
use serde::{
de::{self, Deserializer},
Deserialize, Serialize,
};
use sha2::Sha256;
use std::{
cmp::max,
collections::HashMap,
fmt::{Debug, Display},
io::{self, Cursor, ErrorKind, Read, Seek, SeekFrom},
str::FromStr,
};
use xts_mode::{get_tweak_default, Xts128};
#[macro_use]
extern crate serde_big_array;
big_array! {
BigArray;
+184, 7*512
}
#[derive(Deserialize, PartialEq, Serialize)]
pub struct LuksHeader {
pub magic: [u8; 6],
pub version: u16,
pub hdr_size: u64,
pub seqid: u64,
#[serde(with = "BigArray")]
pub label: [u8; 48],
pub csum_alg: [u8; 32],
#[serde(with = "BigArray")]
pub salt: [u8; 64],
#[serde(with = "BigArray")]
pub uuid: [u8; 40],
#[serde(with = "BigArray")]
pub subsystem: [u8; 48],
pub hdr_offset: u64,
#[serde(with = "BigArray")]
_padding: [u8; 184],
#[serde(with = "BigArray")]
pub csum: [u8; 64],
#[serde(with = "BigArray")]
_padding4069: [u8; 7 * 512],
}
impl LuksHeader {
pub fn read_from<R: Read>(mut reader: &mut R) -> Result<Self, ParseError> {
let options = bincode::options().with_big_endian().with_fixint_encoding();
let h: Self = options.deserialize_from(&mut reader)?;
if (h.magic != [0x4c, 0x55, 0x4b, 0x53, 0xba, 0xbe])
&& (h.magic != [0x53, 0x4b, 0x55, 0x4c, 0xba, 0xbe])
{
return Err(ParseError::InvalidHeaderMagic);
}
if h.version != 2 {
return Err(ParseError::InvalidHeaderVersion(h.version));
}
Ok(h)
}
}
impl Debug for LuksHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
format!(
"LuksHeader {{ magic: {:?}, version: {:?}, hdr_size: {:?}, seqid: {:?}, \
label: {:?}, csum_alg: {:?}, salt: {:?}, uuid: {:?}, subsystem: {:?}, \
hdr_offset: {:?}, csum: {:?} }}",
self.magic,
self.version,
self.hdr_size,
self.seqid,
self.label,
self.csum_alg,
self.salt,
self.uuid,
self.subsystem,
self.hdr_offset,
self.csum
)
)
}
}
impl Display for LuksHeader {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
fn bytes_to_str<'a>(empty_text: &'static str, bytes: &'a [u8]) -> &'a str {
if bytes.iter().all(|el| *el == 0) {
empty_text
} else {
match std::str::from_utf8(bytes) {
Ok(s) => s,
Err(_) => "<decoding error>",
}
}
}
fn bytes_to_hex_string(bytes: &[u8]) -> String {
let mut s = String::with_capacity(bytes.len());
for b in bytes {
s += format!("{:02X}", b).as_str()
}
s
}
let mut magic = String::from_utf8_lossy(&self.magic[..4]).to_string();
magic += format!("\\x{:x}\\x{:x}", self.magic[4], self.magic[5]).as_str();
write!(
f,
"{}",
format!(
"LuksHeader {{\n\
\tmagic: {},\n\
\tversion: {},\n\
\theader size: {},\n\
\tsequence id: {},\n\
\tlabel: {},\n\
\tchecksum algorithm: {},\n\
\tsalt: {},\n\
\tuuid: {},\n\
\tsubsystem label: {},\n\
\theader offset: {},\n\
\tchecksum: {}\n\
}}",
magic,
self.version,
self.hdr_size,
self.seqid,
bytes_to_str("<no label>", &self.label),
bytes_to_str("<no checksum algorithm>", &self.csum_alg),
bytes_to_hex_string(&self.salt),
bytes_to_str("<no uuid>", &self.uuid),
bytes_to_str("<no subsystem label>", &self.subsystem),
self.hdr_offset,
bytes_to_hex_string(&self.csum)
)
)
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type")]
#[allow(non_camel_case_types)]
pub enum LuksArea {
raw {
encryption: String,
key_size: u32,
#[serde(deserialize_with = "from_str")]
offset: u64,
#[serde(deserialize_with = "from_str")]
size: u64,
},
}
impl LuksArea {
pub fn encryption(&self) -> &String {
match self {
LuksArea::raw { encryption, .. } => encryption,
}
}
pub fn key_size(&self) -> u32 {
match self {
LuksArea::raw { key_size, .. } => *key_size,
}
}
pub fn offset(&self) -> u64 {
match self {
LuksArea::raw { offset, .. } => *offset,
}
}
pub fn size(&self) -> u64 {
match self {
LuksArea::raw { size, .. } => *size,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type")]
#[allow(non_camel_case_types)]
pub enum LuksAf {
luks1 {
stripes: u16, hash: String,
},
}
impl LuksAf {
pub fn stripes(&self) -> u16 {
match self {
LuksAf::luks1 { stripes, .. } => *stripes,
}
}
pub fn hash(&self) -> &String {
match self {
LuksAf::luks1 { hash, .. } => hash,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type")]
#[allow(non_camel_case_types)]
pub enum LuksKdf {
pbkdf2 {
salt: String,
hash: String,
iterations: u32,
},
argon2i {
salt: String,
time: u32,
memory: u32,
cpus: u32,
},
argon2id {
salt: String,
time: u32,
memory: u32,
cpus: u32,
},
}
impl LuksKdf {
pub fn salt(&self) -> &String {
match self {
LuksKdf::pbkdf2 { salt, .. } => salt,
LuksKdf::argon2i { salt, .. } => salt,
LuksKdf::argon2id { salt, .. } => salt,
}
}
}
#[derive(Debug, Deserialize, Eq, PartialEq, PartialOrd, Ord, Serialize)]
#[allow(non_camel_case_types)]
pub enum LuksPriority {
ignore,
normal,
high,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type")]
#[allow(non_camel_case_types)]
pub enum LuksKeyslot {
luks2 {
key_size: u16,
area: LuksArea,
kdf: LuksKdf,
af: LuksAf,
#[serde(deserialize_with = "deserialize_priority")]
#[serde(default)]
priority: Option<LuksPriority>,
},
}
impl LuksKeyslot {
pub fn key_size(&self) -> u16 {
match self {
LuksKeyslot::luks2 { key_size, .. } => *key_size,
}
}
pub fn area(&self) -> &LuksArea {
match self {
LuksKeyslot::luks2 { area, .. } => area,
}
}
pub fn kdf(&self) -> &LuksKdf {
match self {
LuksKeyslot::luks2 { kdf, .. } => kdf,
}
}
pub fn af(&self) -> &LuksAf {
match self {
LuksKeyslot::luks2 { af, .. } => af,
}
}
pub fn priority(&self) -> Option<&LuksPriority> {
match self {
LuksKeyslot::luks2 { priority, .. } => priority.as_ref(),
}
}
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct LuksIntegrity {
#[serde(rename(deserialize = "type"))]
pub integrity_type: String,
pub journal_encryption: String,
pub journal_integrity: String,
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[allow(non_camel_case_types)]
pub enum LuksSegmentSize {
dynamic,
fixed(u64),
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type")]
#[allow(non_camel_case_types)]
pub enum LuksSegment {
crypt {
#[serde(deserialize_with = "from_str")]
offset: u64,
#[serde(deserialize_with = "deserialize_segment_size")]
size: LuksSegmentSize,
#[serde(deserialize_with = "from_str")]
iv_tweak: u64,
encryption: String,
sector_size: u16,
#[serde(default)]
integrity: Option<LuksIntegrity>,
#[serde(default)]
flags: Option<Vec<String>>,
},
}
impl LuksSegment {
pub fn offset(&self) -> u64 {
match self {
LuksSegment::crypt { offset, .. } => *offset,
}
}
pub fn size(&self) -> &LuksSegmentSize {
match self {
LuksSegment::crypt { size, .. } => size,
}
}
pub fn iv_tweak(&self) -> u64 {
match self {
LuksSegment::crypt { iv_tweak, .. } => *iv_tweak,
}
}
pub fn encryption(&self) -> &String {
match self {
LuksSegment::crypt { encryption, .. } => encryption,
}
}
pub fn sector_size(&self) -> u16 {
match self {
LuksSegment::crypt { sector_size, .. } => *sector_size,
}
}
pub fn integrity(&self) -> Option<&LuksIntegrity> {
match self {
LuksSegment::crypt { integrity, .. } => integrity.as_ref(),
}
}
pub fn flags(&self) -> Option<&Vec<String>> {
match self {
LuksSegment::crypt { flags, .. } => flags.as_ref(),
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(tag = "type")]
#[allow(non_camel_case_types)]
pub enum LuksDigest {
pbkdf2 {
#[serde(deserialize_with = "vec_from_str")]
keyslots: Vec<u8>,
#[serde(deserialize_with = "vec_from_str")]
segments: Vec<u8>,
salt: String,
digest: String,
hash: String,
iterations: u32,
},
}
impl LuksDigest {
pub fn keyslots(&self) -> &Vec<u8> {
match self {
LuksDigest::pbkdf2 { keyslots, .. } => keyslots,
}
}
pub fn segments(&self) -> &Vec<u8> {
match self {
LuksDigest::pbkdf2 { segments, .. } => segments,
}
}
pub fn salt(&self) -> &String {
match self {
LuksDigest::pbkdf2 { salt, .. } => salt,
}
}
pub fn digest(&self) -> &String {
match self {
LuksDigest::pbkdf2 { digest, .. } => digest,
}
}
pub fn hash(&self) -> &String {
match self {
LuksDigest::pbkdf2 { hash, .. } => hash,
}
}
pub fn iterations(&self) -> u32 {
match self {
LuksDigest::pbkdf2 { iterations, .. } => *iterations,
}
}
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct LuksConfig {
#[serde(deserialize_with = "from_str")]
pub json_size: u64,
#[serde(deserialize_with = "from_str")]
pub keyslots_size: u64,
#[serde(default)]
pub flags: Option<Vec<String>>,
#[serde(default)]
pub requirements: Option<Vec<String>>,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct LuksToken {}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
pub struct LuksJson {
pub keyslots: HashMap<u8, LuksKeyslot>,
pub tokens: HashMap<u8, LuksToken>,
pub segments: HashMap<u8, LuksSegment>,
pub digests: HashMap<u8, LuksDigest>,
pub config: LuksConfig,
}
impl LuksJson {
pub fn read_from<R: Read>(mut reader: &mut R) -> Result<Self, ParseError> {
let j: Self = serde_json::from_reader(&mut reader)?;
let stripes_ok = j.keyslots.iter().all(|(_, k)| k.af().stripes() == 4000u16);
if !stripes_ok {
return Err(ParseError::InvalidStripes);
}
let sector_sizes_valid = j
.segments
.iter()
.all(|(_, s)| vec![512, 1024, 2048, 4096].contains(&s.sector_size()));
if !sector_sizes_valid {
return Err(ParseError::InvalidSectorSize);
}
if (j.config.keyslots_size % 4096) != 0 {
return Err(ParseError::KeyslotNotAligned);
}
let refs_valid = j.digests.iter().all(|(_, d)| {
d.keyslots().iter().all(|k| j.keyslots.contains_key(k))
&& d.segments().iter().all(|s| j.keyslots.contains_key(s))
});
if !refs_valid {
return Err(ParseError::InvalidReference);
}
Ok(j)
}
}
fn from_str<'de, T, D>(deserializer: D) -> Result<T, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
T::from_str(&s).map_err(de::Error::custom)
}
fn vec_from_str<'de, T, D>(deserializer: D) -> Result<Vec<T>, D::Error>
where
T: FromStr,
T::Err: Display,
D: Deserializer<'de>,
{
let v = Vec::<String>::deserialize(deserializer)?;
let mut res = Vec::with_capacity(v.len());
for s in v {
res.push(T::from_str(&s).map_err(de::Error::custom)?);
}
Ok(res)
}
fn deserialize_priority<'de, D>(deserializer: D) -> Result<Option<LuksPriority>, D::Error>
where
D: Deserializer<'de>,
{
let p = match Option::<i32>::deserialize(deserializer)? {
Some(pr) => pr,
None => return Ok(None),
};
match p {
0 => Ok(Some(LuksPriority::ignore)),
1 => Ok(Some(LuksPriority::normal)),
2 => Ok(Some(LuksPriority::high)),
_ => Err(de::Error::custom(format!("invalid priority {}", p))),
}
}
fn deserialize_segment_size<'de, D>(deserializer: D) -> Result<LuksSegmentSize, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
match s.as_str() {
"dynamic" => Ok(LuksSegmentSize::dynamic),
x => Ok(LuksSegmentSize::fixed(
u64::from_str(x).map_err(de::Error::custom)?,
)),
}
}
#[derive(Clone)]
pub struct MasterKey(Vec<u8>);
impl Zeroize for MasterKey {
fn zeroize(&mut self) {
self.0.zeroize()
}
}
impl DebugSecret for MasterKey {}
impl CloneableSecret for MasterKey {}
pub type SecretMasterKey = Secret<MasterKey>;
#[derive(Debug)]
pub struct LuksDevice<T: Read + Seek> {
device: T,
master_key: SecretMasterKey,
current_sector: Cursor<Vec<u8>>,
current_sector_num: u64,
pub header: LuksHeader,
pub json: LuksJson,
pub sector_size: usize,
pub active_segment: LuksSegment,
}
impl<T: Read + Seek> LuksDevice<T> {
pub fn from_device(
mut device: T,
password: &[u8],
sector_size: usize,
) -> Result<Self, LuksError> {
let mut h = vec![0; 4096];
device.read_exact(&mut h)?;
let header = LuksHeader::read_from(&mut Cursor::new(&h))?;
let mut j = vec![0; (header.hdr_size - 4096) as usize];
device.read_exact(&mut j)?;
let j: Vec<u8> = j.iter().map(|b| *b).filter(|b| *b != 0).collect();
let json = LuksJson::read_from(&mut Cursor::new(&j))?;
let master_key = Self::decrypt_master_key(password, &json, &mut device, sector_size)?;
let active_segment = json.segments[&0].clone();
let mut d = LuksDevice {
device,
master_key,
current_sector: Cursor::new(vec![0; 256]),
current_sector_num: u64::MAX,
header,
json,
sector_size,
active_segment,
};
d.seek(SeekFrom::Start(0))?;
Ok(d)
}
pub fn master_key(&self) -> SecretMasterKey {
self.master_key.clone()
}
pub fn active_segment_size(&mut self) -> io::Result<u64> {
Ok(match self.active_segment.size() {
LuksSegmentSize::fixed(s) => *s,
LuksSegmentSize::dynamic => {
let pos_before = self.device.seek(SeekFrom::Current(0))?;
let end = self.device.seek(SeekFrom::End(0))?;
self.device.seek(SeekFrom::Start(pos_before))?;
end - self.active_segment.offset()
}
})
}
fn decrypt_master_key(
password: &[u8],
json: &LuksJson,
device: &mut T,
sector_size: usize,
) -> Result<SecretMasterKey, LuksError>
where
T: Read + Seek,
{
let mut keyslots: Vec<&LuksKeyslot> = json.keyslots.values().collect();
keyslots.sort_by_key(|&ks| ks.priority().unwrap_or(&LuksPriority::normal));
for &ks in keyslots.iter().rev() {
match Self::decrypt_keyslot(password, ks, json, device, sector_size) {
Ok(mk) => return Ok(mk),
Err(e) => match e {
LuksError::InvalidPassword => {}
_ => return Err(e),
},
}
}
Err(LuksError::InvalidPassword)
}
fn decrypt_keyslot(
password: &[u8],
keyslot: &LuksKeyslot,
json: &LuksJson,
device: &mut T,
sector_size: usize,
) -> Result<SecretMasterKey, LuksError>
where
T: Read + Seek,
{
let area = keyslot.area();
let af = keyslot.af();
if af.hash() != "sha256" {
return Err(LuksError::UnsupportedAfHash(af.hash().to_string()));
}
let mut k = vec![0; keyslot.key_size() as usize * af.stripes() as usize];
device.seek(SeekFrom::Start(area.offset()))?;
device.read_exact(&mut k)?;
let mut pw_hash = vec![0; area.key_size() as usize];
match keyslot.kdf() {
LuksKdf::argon2i {
salt,
time,
memory,
cpus,
}
| LuksKdf::argon2id {
salt,
time,
memory,
cpus,
} => {
let variant = if let LuksKdf::argon2i { .. } = keyslot.kdf() {
argon2::Variant::Argon2i
} else {
argon2::Variant::Argon2id
};
let config = argon2::Config {
variant,
mem_cost: *memory,
time_cost: *time,
lanes: *cpus,
thread_mode: argon2::ThreadMode::Parallel,
hash_length: area.key_size(),
..argon2::Config::default()
};
let salt = base64::decode(&salt)?;
pw_hash = argon2::hash_raw(password, &salt, &config)?;
}
LuksKdf::pbkdf2 {
salt,
hash,
iterations,
} => {
assert_eq!(hash, "sha256");
let salt = base64::decode(salt)?;
pbkdf2::pbkdf2::<Hmac<Sha256>>(password, &salt, *iterations, &mut pw_hash);
}
}
let pw_hash = Secret::new(pw_hash);
match area.key_size() {
32 => {
let key1 = Aes128::new_from_slice(&pw_hash.expose_secret()[..16]).unwrap();
let key2 = Aes128::new_from_slice(&pw_hash.expose_secret()[16..]).unwrap();
let xts = Xts128::<Aes128>::new(key1, key2);
xts.decrypt_area(&mut k, sector_size, 0, get_tweak_default);
}
64 => {
let key1 = Aes256::new_from_slice(&pw_hash.expose_secret()[..32]).unwrap();
let key2 = Aes256::new_from_slice(&pw_hash.expose_secret()[32..]).unwrap();
let xts = Xts128::<Aes256>::new(key1, key2);
xts.decrypt_area(&mut k, sector_size, 0, get_tweak_default);
}
x => return Err(LuksError::UnsupportedKeySize(x)),
}
let k = Secret::new(k);
let master_key = Secret::new(MasterKey(af::merge(
&k.expose_secret(),
keyslot.key_size() as usize,
af.stripes() as usize,
)));
let digest_actual = base64::decode(json.digests[&0].digest())?;
let mut digest_computed = vec![0; digest_actual.len()];
let salt = base64::decode(json.digests[&0].salt())?;
pbkdf2::pbkdf2::<Hmac<Sha256>>(
&master_key.expose_secret().0,
&salt,
json.digests[&0].iterations(),
&mut digest_computed,
);
if digest_computed == digest_actual {
Ok(master_key)
} else {
Err(LuksError::InvalidPassword)
}
}
fn go_to_sector(&mut self, sector_num: u64) -> io::Result<()> {
if sector_num == self.current_sector_num {
return Ok(());
} else if sector_num
< (self.active_segment.offset() / self.active_segment.sector_size() as u64)
{
return Err(io::Error::new(
ErrorKind::InvalidInput,
"tried to seek to position before active segment",
));
}
let sector_size = self.active_segment.sector_size() as u64;
let mut max_sector = self.active_segment_size()? / sector_size;
if (self.active_segment_size()? % sector_size) != 0 {
max_sector += 1;
}
if sector_num > max_sector {
return Ok(());
}
let sector_pos = SeekFrom::Start(sector_num * sector_size);
self.device.seek(sector_pos)?;
let mut sector = vec![0; sector_size as usize];
if let Err(e) = self.device.read_exact(&mut sector) {
match e.kind() {
ErrorKind::UnexpectedEof => {
self.device.seek(sector_pos)?;
sector = vec![0; sector_size as usize];
let bytes_read = self.device.read(&mut sector)?;
sector.resize(bytes_read, 0);
}
_ => return Err(e),
}
}
if sector.len() != 0 {
let iv = sector_num
- (self.active_segment.offset() / self.active_segment.sector_size() as u64);
let iv = get_tweak_default((iv + self.active_segment.iv_tweak()) as u128);
match self.master_key.expose_secret().0.len() {
32 => {
let key1 =
Aes128::new_from_slice(&self.master_key.expose_secret().0[..16]).unwrap();
let key2 =
Aes128::new_from_slice(&self.master_key.expose_secret().0[16..]).unwrap();
let xts = Xts128::<Aes128>::new(key1, key2);
xts.decrypt_sector(&mut sector, iv);
}
64 => {
let key1 =
Aes256::new_from_slice(&self.master_key.expose_secret().0[..32]).unwrap();
let key2 =
Aes256::new_from_slice(&self.master_key.expose_secret().0[32..]).unwrap();
let xts = Xts128::<Aes256>::new(key1, key2);
xts.decrypt_sector(&mut sector, iv);
}
x => {
return Err(io::Error::new(
ErrorKind::InvalidInput,
format!("Unsupported key size: {}", x),
))
}
}
}
self.current_sector = Cursor::new(sector);
self.current_sector_num = sector_num;
Ok(())
}
}
impl<T: Read + Seek> Read for LuksDevice<T> {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.current_sector.position() == self.active_segment.sector_size() as u64 {
self.go_to_sector(self.current_sector_num + 1)?;
}
self.current_sector.read(buf)
}
}
impl<T: Read + Seek> Seek for LuksDevice<T> {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
match pos {
SeekFrom::Start(p) => {
let sector_size = self.active_segment.sector_size() as u64;
let p = p + self.active_segment.offset();
let sector = p / sector_size;
self.go_to_sector(sector)?;
self.current_sector.seek(SeekFrom::Start(p % sector_size))?;
}
SeekFrom::End(p) => {
let sector_size = self.active_segment.sector_size() as i128;
let p = max(0, p); let end = self.active_segment_size()? as i128;
let sector = (end + p as i128) / sector_size;
if sector < 0 {
return Err(io::Error::new(
ErrorKind::InvalidInput,
"tried to seek to negative sector",
));
}
self.go_to_sector(sector as u64)?;
let target_pos = (end + p as i128) - sector * sector_size;
self.current_sector
.seek(SeekFrom::Start(target_pos as u64))?;
}
SeekFrom::Current(p) => {
let sector_size = self.active_segment.sector_size() as i128;
let current = self.current_sector_num as i128 * sector_size
+ self.current_sector.position() as i128;
let sector = (current + p as i128) / sector_size;
if sector < 0 {
return Err(io::Error::new(
ErrorKind::InvalidInput,
"tried to seek to negative sector",
));
}
self.go_to_sector(sector as u64)?;
let target_pos = (current + p as i128) - sector * sector_size;
self.current_sector
.seek(SeekFrom::Start(target_pos as u64))?;
}
}
Ok(
self.current_sector_num * self.active_segment.sector_size() as u64
+ self.current_sector.position(),
)
}
}