use std::{
collections::HashMap,
ops::{Deref, DerefMut},
};
use crate::{
backup::util::tlv::tlv_blocks,
error::{BackupError, Result},
};
#[derive(Debug, Clone)]
pub(crate) struct KeyRing {
pub bag_type: u32,
pub uuid: Vec<u8>,
pub wrap: Vec<u8>,
pub dpsl: Vec<u8>,
pub dpic: u32,
pub salt: Vec<u8>,
pub iter: u32,
pub(crate) attrs: HashMap<[u8; 4], Vec<u8>>,
pub(crate) class_keys: HashMap<u32, ClassKeyData>,
}
impl KeyRing {
pub(crate) fn from_bytes(blob: &[u8]) -> Result<KeyRing> {
let mut bag = KeyRing {
bag_type: 0,
uuid: Vec::new(),
wrap: Vec::new(),
dpsl: Vec::new(),
salt: Vec::new(),
dpic: 0,
iter: 0,
attrs: HashMap::new(),
class_keys: HashMap::new(),
};
let mut current: Option<HashMap<[u8; 4], Vec<u8>>> = None;
for item in tlv_blocks(blob) {
let (tag, data) = item?;
if data.len() == 4 {
let v = u32::from_be_bytes(
data.as_slice()
.try_into()
.map_err(BackupError::ConversionFailed)?,
);
if &tag == b"TYPE" {
bag.bag_type = v;
continue;
}
}
match &tag {
b"UUID" if bag.uuid.is_empty() => bag.uuid = data,
b"WRAP" if bag.wrap.is_empty() => bag.wrap = data,
b"DPSL" if bag.dpsl.is_empty() => bag.dpsl = data,
b"SALT" if bag.salt.is_empty() => bag.salt = data,
b"DPIC" if bag.dpic == 0 => {
bag.dpic = u32::from_be_bytes(
data.as_slice()
.try_into()
.map_err(BackupError::ConversionFailed)?,
);
}
b"ITER" if bag.iter == 0 => {
bag.iter = u32::from_be_bytes(
data.as_slice()
.try_into()
.map_err(BackupError::ConversionFailed)?,
);
}
b"UUID" => {
if let Some(cur) = current.take() {
let class_id = u32::from_be_bytes(
cur[b"CLAS"][..]
.try_into()
.map_err(BackupError::ConversionFailed)?,
);
bag.class_keys
.insert(class_id, ClassKeyData::from_map(&cur));
}
let mut map = HashMap::new();
map.insert(tag, data);
current = Some(map);
}
t if current.is_some()
&& (t == b"CLAS"
|| b"WPKY".as_ref() == &t[..]
|| b"PBKY".as_ref() == &t[..]
|| b"KTYP".as_ref() == &t[..]
|| b"WRAP" == &t[..]) =>
{
if let Some(curr) = &mut current {
curr.insert(tag, data);
}
}
_ => {
bag.attrs.insert(tag, data);
}
}
}
if let Some(cur) = current {
let class_id = u32::from_be_bytes(
cur[b"CLAS"][..]
.try_into()
.map_err(BackupError::ConversionFailed)?,
);
bag.class_keys
.insert(class_id, ClassKeyData::from_map(&cur));
}
Ok(bag)
}
}
#[derive(Debug, Clone)]
pub(crate) struct ClassKeyData {
pub wpky: Option<Vec<u8>>,
pub wrap: Option<Vec<u8>>,
pub _uuid: Option<Vec<u8>>,
}
impl ClassKeyData {
#[must_use]
pub(crate) fn from_map(map: &HashMap<[u8; 4], Vec<u8>>) -> ClassKeyData {
let wpky = map
.get(b"WPKY")
.cloned()
.or_else(|| map.get(b"PBKY").cloned());
ClassKeyData {
wpky,
wrap: map.get(b"WRAP").cloned(),
_uuid: map.get(b"UUID").cloned(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EncryptionKey(Vec<u8>);
impl AsRef<[u8]> for EncryptionKey {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
impl From<Vec<u8>> for EncryptionKey {
fn from(v: Vec<u8>) -> EncryptionKey {
EncryptionKey(v)
}
}
impl Deref for EncryptionKey {
type Target = Vec<u8>;
fn deref(&self) -> &Vec<u8> {
&self.0
}
}
impl DerefMut for EncryptionKey {
fn deref_mut(&mut self) -> &mut Vec<u8> {
&mut self.0
}
}
#[derive(Debug, Clone)]
pub struct ProtectionClassKey {
pub class_id: u32,
pub key: EncryptionKey,
}
#[cfg(test)]
mod tests_types {
use std::collections::HashMap;
use crate::backup::models::keyring::{ClassKeyData, KeyRing};
#[test]
fn test_backup_key_ring_from_bytes_basic() {
let mut blob = Vec::new();
blob.extend(b"TYPE");
blob.extend(&4u32.to_be_bytes());
blob.extend(&1u32.to_be_bytes());
blob.extend(b"DPSL");
blob.extend(&2u32.to_be_bytes());
blob.extend(b"aa");
blob.extend(b"DPIC");
blob.extend(&4u32.to_be_bytes());
blob.extend(&2u32.to_be_bytes());
blob.extend(b"SALT");
blob.extend(&2u32.to_be_bytes());
blob.extend(b"bb");
blob.extend(b"ITER");
blob.extend(&4u32.to_be_bytes());
blob.extend(&3u32.to_be_bytes());
let bag = KeyRing::from_bytes(&blob).unwrap();
assert_eq!(bag.bag_type, 1);
assert_eq!(bag.dpsl, b"aa");
assert_eq!(bag.dpic, 2);
assert_eq!(bag.salt, b"bb");
assert_eq!(bag.iter, 3);
assert!(bag.class_keys.is_empty());
}
#[test]
fn test_class_key_data_prefer_wpky() {
let mut map: HashMap<[u8; 4], Vec<u8>> = HashMap::new();
map.insert(*b"PBKY", b"pb".to_vec());
map.insert(*b"WPKY", b"wp".to_vec());
map.insert(*b"WRAP", b"wr".to_vec());
map.insert(*b"UUID", b"id".to_vec());
let ck = ClassKeyData::from_map(&map);
assert_eq!(ck.wpky.unwrap(), b"wp".to_vec());
assert_eq!(ck.wrap.unwrap(), b"wr".to_vec());
assert_eq!(ck._uuid.unwrap(), b"id".to_vec());
}
}