use crate::crypto::{self, AuthKey};
use crate::kdf;
use crate::qt::{QReader, QWriter};
use crate::tdata::{FillRandom, random_bytes};
use crate::{Error, KeyDataFailure, Result};
pub const DEFAULT_KEY_FILE: &str = "data";
pub const KEY_DATA_MAGIC: u32 = 0x4B44_3200;
pub const KEY_DATA_FORMAT_VERSION: u32 = 2;
pub const MAX_WRAP_COUNT: u32 = 2;
pub const MAX_NEWER_FORMAT_WRAP_COUNT: u32 = 16;
pub const LOCAL_ENCRYPT_SALT_SIZE: usize = 32;
pub const KDF_PBKDF2: u32 = 0;
pub const KDF_ARGON2ID: u32 = 1;
pub const KDF_SCRYPT: u32 = 2;
pub const ARGON2_MEMORY_KIB: u32 = 65_536;
pub const ARGON2_TIME: u32 = 8;
pub const ARGON2_LANES: u32 = 1;
pub const DEFAULT_WRAP_GENERATION: u32 = 1;
#[must_use]
pub fn file_name(key_file: &str) -> String {
format!("key_{key_file}")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PasscodeKdf {
pub kind: u32,
pub generation: u32,
pub memory: u32,
pub time: u32,
pub parallelism: u32,
}
impl PasscodeKdf {
#[must_use]
pub fn valid(&self) -> bool {
if self.time == 0 || self.parallelism == 0 {
return false;
}
match self.kind {
KDF_ARGON2ID => u64::from(self.memory) >= 8 * u64::from(self.parallelism),
KDF_SCRYPT => self.memory >= 2 && self.memory.is_power_of_two(),
_ => false,
}
}
#[must_use]
pub fn cost_within_limits(&self) -> bool {
match self.kind {
KDF_ARGON2ID => {
self.memory <= kdf::ARGON2_MAX_MEMORY_KIB
&& self.time <= kdf::ARGON2_MAX_TIME
&& self.parallelism <= kdf::ARGON2_MAX_LANES
}
KDF_SCRYPT => {
self.time <= kdf::SCRYPT_MAX_R
&& self.parallelism <= kdf::SCRYPT_MAX_P
&& self.memory <= kdf::SCRYPT_MAX_N
}
_ => true,
}
}
}
#[must_use]
pub fn default_passcode_kdf() -> PasscodeKdf {
PasscodeKdf {
kind: KDF_ARGON2ID,
generation: DEFAULT_WRAP_GENERATION,
memory: ARGON2_MEMORY_KIB,
time: ARGON2_TIME,
parallelism: ARGON2_LANES,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PasscodeWrap {
pub kdf: PasscodeKdf,
pub salt: Vec<u8>,
pub key_encrypted: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyDataTrailer {
pub committed: u32,
pub wraps: Vec<PasscodeWrap>,
}
impl KeyDataTrailer {
pub fn parse(blob: &[u8]) -> Result<Self> {
let mut reader = QReader::new(blob);
let [magic, format, committed, count] = streamed(
(|| {
Ok([
reader.read_u32()?,
reader.read_u32()?,
reader.read_u32()?,
reader.read_u32()?,
])
})(),
TRAILER_HEADER_STREAM_FAILED,
)?;
if magic != KEY_DATA_MAGIC {
return Err(invalid(KeyDataFailure::BadTrailerMagic));
}
let limit = if format > KEY_DATA_FORMAT_VERSION {
MAX_NEWER_FORMAT_WRAP_COUNT
} else {
MAX_WRAP_COUNT
};
if count > limit {
return Err(invalid(KeyDataFailure::BadWrapCount { count }));
}
let mut wraps: Vec<PasscodeWrap> = Vec::new();
for _ in 0..count {
let (kdf, salt, key_encrypted) = streamed(
(|| {
let kdf = PasscodeKdf {
kind: reader.read_u32()?,
generation: reader.read_u32()?,
memory: reader.read_u32()?,
time: reader.read_u32()?,
parallelism: reader.read_u32()?,
};
let salt = reader.read_bytearray()?.unwrap_or_default().to_vec();
let key_encrypted = reader.read_bytearray()?.unwrap_or_default().to_vec();
Ok((kdf, salt, key_encrypted))
})(),
WRAP_STREAM_FAILED,
)?;
if kdf.cost_within_limits() && !kdf.valid() {
return Err(invalid(KeyDataFailure::BadKdfFamily { kind: kdf.kind }));
}
if salt.len() < kdf::PASSCODE_SALT_MIN_SIZE {
return Err(invalid(KeyDataFailure::BadWrapSaltSize {
size: salt.len(),
}));
}
if wraps.iter().any(|w| w.kdf.generation == kdf.generation) {
return Err(invalid(KeyDataFailure::DuplicateWrapGeneration {
generation: kdf.generation,
}));
}
wraps.push(PasscodeWrap {
kdf,
salt,
key_encrypted,
});
}
Ok(Self { committed, wraps })
}
pub fn encode(&self) -> Result<Vec<u8>> {
let count = u32::try_from(self.wraps.len())
.map_err(|_| Error::QDataStreamFailed { context: None })?;
let mut writer = QWriter::new();
writer.write_u32(KEY_DATA_MAGIC);
writer.write_u32(KEY_DATA_FORMAT_VERSION);
writer.write_u32(self.committed);
writer.write_u32(count);
for wrap in &self.wraps {
writer.write_u32(wrap.kdf.kind);
writer.write_u32(wrap.kdf.generation);
writer.write_u32(wrap.kdf.memory);
writer.write_u32(wrap.kdf.time);
writer.write_u32(wrap.kdf.parallelism);
writer.write_bytearray(Some(&wrap.salt))?;
writer.write_bytearray(Some(&wrap.key_encrypted))?;
}
Ok(writer.into_bytes())
}
pub fn live_wraps(&self) -> impl Iterator<Item = &PasscodeWrap> {
self.wraps
.iter()
.filter(move |w| w.kdf.generation == self.committed)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KeyData {
pub salt: Vec<u8>,
pub key_encrypted: Option<Vec<u8>>,
pub info_encrypted: Vec<u8>,
pub trailer: Option<KeyDataTrailer>,
}
impl KeyData {
pub fn parse(payload: &[u8]) -> Result<Self> {
let mut reader = QReader::new(payload);
let (salt, key_encrypted, info_encrypted) = streamed(
(|| {
let salt = reader.read_bytearray()?.unwrap_or_default().to_vec();
let key_encrypted = reader.read_bytearray()?.map(<[u8]>::to_vec);
let info_encrypted = reader.read_bytearray()?.unwrap_or_default().to_vec();
Ok((salt, key_encrypted, info_encrypted))
})(),
KEY_DATA_STREAM_FAILED,
)?;
if salt.len() != LOCAL_ENCRYPT_SALT_SIZE {
return Err(invalid(KeyDataFailure::BadSalt { size: salt.len() }));
}
let trailer = if reader.is_empty() {
None
} else {
let blob =
streamed(reader.read_bytearray(), TRAILER_STREAM_FAILED)?.unwrap_or_default();
Some(KeyDataTrailer::parse(blob)?)
};
Ok(Self {
salt,
key_encrypted,
info_encrypted,
trailer,
})
}
pub fn encode(&self) -> Result<Vec<u8>> {
let mut writer = QWriter::new();
writer.write_bytearray(Some(&self.salt))?;
writer.write_bytearray(self.key_encrypted.as_deref())?;
writer.write_bytearray(Some(&self.info_encrypted))?;
if let Some(trailer) = &self.trailer {
writer.write_bytearray(Some(&trailer.encode()?))?;
}
Ok(writer.into_bytes())
}
#[must_use]
pub fn live_wrap_count(&self) -> usize {
self.trailer
.as_ref()
.map_or(0, |trailer| trailer.live_wraps().count())
}
}
#[derive(Debug, Clone)]
pub struct OpenedKey {
pub local_key: AuthKey,
pub passcode_key: AuthKey,
}
pub fn create_passcode_key(passcode: &[u8], salt: &[u8], params: &PasscodeKdf) -> Result<AuthKey> {
if passcode.is_empty()
|| salt.len() < kdf::PASSCODE_SALT_MIN_SIZE
|| !params.cost_within_limits()
|| !params.valid()
{
return Err(invalid(KeyDataFailure::UnusableWrapParameters {
kind: params.kind,
memory: params.memory,
time: params.time,
parallelism: params.parallelism,
}));
}
let derived = if params.kind == KDF_ARGON2ID {
kdf::argon2id(
passcode,
salt,
params.memory,
params.time,
params.parallelism,
)
} else {
kdf::scrypt(
passcode,
salt,
params.memory,
params.time,
params.parallelism,
)
};
derived.map(AuthKey::new).map_err(|_| Error::BadDecryptKey)
}
pub fn wrap_local_key(local_key: &AuthKey, wrap_key: &AuthKey, padding: &[u8]) -> Result<Vec<u8>> {
crypto::encrypt_local(local_key.as_bytes(), wrap_key, |_| padding.to_vec())
}
pub fn unwrap_local_key(key_encrypted: &[u8], wrap_key: &AuthKey) -> Result<AuthKey> {
let inner = crypto::decrypt_local(key_encrypted, wrap_key).map_err(|_| Error::BadDecryptKey)?;
AuthKey::from_slice(&inner).map_err(|_| Error::BadDecryptKey)
}
pub fn open_local_key(data: &KeyData, passcode: &[u8]) -> Result<OpenedKey> {
let open_wrap = data.key_encrypted.as_deref().unwrap_or_default();
let Some(trailer) = &data.trailer else {
let wrap_key = kdf::create_local_key(passcode, &data.salt);
return opened(open_wrap, wrap_key);
};
if trailer.wraps.iter().any(|w| !w.kdf.cost_within_limits()) {
return Err(Error::BadDecryptKey);
}
if passcode.is_empty() {
if open_wrap.is_empty() {
return Err(Error::BadDecryptKey);
}
return opened(open_wrap, kdf::create_local_key(&[], &data.salt));
}
let mut live = trailer.live_wraps();
let (Some(wrap), None) = (live.next(), live.next()) else {
return Err(Error::BadDecryptKey);
};
let wrap_key = create_passcode_key(passcode, &wrap.salt, &wrap.kdf)?;
opened(&wrap.key_encrypted, wrap_key)
}
pub(crate) fn install_draws(has_passcode: bool) -> Vec<usize> {
let padding = crypto::local_padding_len(AuthKey::SIZE);
if has_passcode {
vec![LOCAL_ENCRYPT_SALT_SIZE, LOCAL_ENCRYPT_SALT_SIZE, padding]
} else {
vec![LOCAL_ENCRYPT_SALT_SIZE, padding]
}
}
pub(crate) fn install(
local_key: &AuthKey,
passcode: &[u8],
rng: &mut FillRandom<'_>,
) -> Result<(KeyData, AuthKey)> {
let salt = random_bytes(rng, LOCAL_ENCRYPT_SALT_SIZE)?;
let padding_len = crypto::local_padding_len(AuthKey::SIZE);
if passcode.is_empty() {
let wrap_key = kdf::create_local_key(&[], &salt);
let padding = random_bytes(rng, padding_len)?;
let data = KeyData {
key_encrypted: Some(wrap_local_key(local_key, &wrap_key, &padding)?),
salt,
info_encrypted: Vec::new(),
trailer: Some(KeyDataTrailer {
committed: 0,
wraps: Vec::new(),
}),
};
return Ok((data, wrap_key));
}
let params = default_passcode_kdf();
let wrap_salt = random_bytes(rng, LOCAL_ENCRYPT_SALT_SIZE)?;
let wrap_key = create_passcode_key(passcode, &wrap_salt, ¶ms)?;
let padding = random_bytes(rng, padding_len)?;
let wrap = PasscodeWrap {
kdf: params,
salt: wrap_salt,
key_encrypted: wrap_local_key(local_key, &wrap_key, &padding)?,
};
let data = KeyData {
salt,
key_encrypted: None,
info_encrypted: Vec::new(),
trailer: Some(KeyDataTrailer {
committed: params.generation,
wraps: vec![wrap],
}),
};
Ok((data, wrap_key))
}
fn opened(key_encrypted: &[u8], wrap_key: AuthKey) -> Result<OpenedKey> {
Ok(OpenedKey {
local_key: unwrap_local_key(key_encrypted, &wrap_key)?,
passcode_key: wrap_key,
})
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AccountsInfo {
pub indices: Vec<i32>,
pub active_index: i32,
}
impl AccountsInfo {
pub fn parse(data: &[u8]) -> Result<Self> {
let mut reader = QReader::new(data);
let count = reader.read_i32()?;
if count <= 0 {
return Err(invalid(KeyDataFailure::BadAccountsCount { count }));
}
let mut indices = Vec::new();
for _ in 0..count {
indices.push(reader.read_i32()?);
}
let active_index = if reader.is_empty() {
0
} else {
reader.read_i32()?
};
Ok(Self {
indices,
active_index,
})
}
pub fn encode(&self) -> Result<Vec<u8>> {
let count = i32::try_from(self.indices.len())
.map_err(|_| Error::QDataStreamFailed { context: None })?;
let mut writer = QWriter::new();
writer.write_i32(count);
for &index in &self.indices {
writer.write_i32(index);
}
writer.write_i32(self.active_index);
Ok(writer.into_bytes())
}
}
pub fn decrypt_accounts_info(
data: &KeyData,
local_key: &AuthKey,
passcode: &[u8],
) -> Result<AccountsInfo> {
let info = crypto::decrypt_local(&data.info_encrypted, local_key).map_err(|_| {
if passcode.is_empty() && data.live_wrap_count() == 1 {
Error::BadDecryptKey
} else {
invalid(KeyDataFailure::AccountsInfoDecrypt)
}
})?;
AccountsInfo::parse(&info)
}
const KEY_DATA_STREAM_FAILED: &str = "Could not stream the key data";
const TRAILER_STREAM_FAILED: &str = "Could not stream the key data trailer";
const TRAILER_HEADER_STREAM_FAILED: &str = "Could not stream the key data trailer header";
const WRAP_STREAM_FAILED: &str = "Could not stream a key data wrap";
fn streamed<T>(result: Result<T>, context: &'static str) -> Result<T> {
result.map_err(|error| match error {
Error::QDataStreamFailed { context: None } => Error::QDataStreamFailed {
context: Some(context),
},
other => other,
})
}
const fn invalid(context: KeyDataFailure) -> Error {
Error::KeyDataInvalid { context }
}