pub mod af;
pub mod error;
pub mod password;
use aes::{Aes128, Aes256, NewBlockCipher};
use bincode::Options;
use hmac::Hmac;
use self::error::{LuksError, ParseError};
use serde::{Deserialize, Serialize, de::{self, Deserializer}};
use sha2::Sha256;
use std::{cmp::{max, min}, collections::HashMap, fmt::{Debug, Display}, io::{self, Cursor, ErrorKind, Read, Seek, SeekFrom}, str::FromStr};
use xts_mode::{Xts128, get_tweak_default};
#[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(Debug)]
pub struct LuksDevice<T: Read + Seek> {
device: T,
master_key: Vec<u8>,
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) -> Vec<u8> {
self.master_key.clone()
}
fn decrypt_master_key(password: &[u8], json: &LuksJson, device: &mut T, sector_size: usize) -> Result<Vec<u8>, 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<Vec<u8>, 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);
}
}
match area.key_size() {
32 => {
let key1 = Aes128::new_varkey(&pw_hash[..16]).unwrap();
let key2 = Aes128::new_varkey(&pw_hash[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_varkey(&pw_hash[..32]).unwrap();
let key2 = Aes256::new_varkey(&pw_hash[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 master_key = af::merge(&k, 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, &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;
}
let sector_num = min(sector_num, max_sector);
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.len() {
32 => {
let key1 = Aes128::new_varkey(&self.master_key[..16]).unwrap();
let key2 = Aes128::new_varkey(&self.master_key[16..]).unwrap();
let xts = Xts128::<Aes128>::new(key1, key2);
xts.decrypt_sector(&mut sector, iv);
},
64 => {
let key1 = Aes256::new_varkey(&self.master_key[..32]).unwrap();
let key2 = Aes256::new_varkey(&self.master_key[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(())
}
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
}
})
}
}
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())
}
}