use std::{collections::HashMap, fs::File, path::Path};
use plist::Value;
use crate::{
Authentication,
backup::{
crypto::{derive_key_from_password, unlock_keys_from_manifest},
models::{
keyring::{EncryptionKey, KeyRing, ProtectionClassKey},
manifest::lockdown::ManifestLockdownInfo,
},
util::{
hex::hex_decode,
plist::{as_dictionary, get_key_as_boolean, get_key_as_data},
},
},
error::{BackupError, Result},
};
use super::app::Application;
#[derive(Debug, Clone)]
pub struct Manifest {
pub manifest_data: ManifestData,
pub main_decryption_key: Option<EncryptionKey>,
pub unlocked_class_keys: Option<HashMap<u32, ProtectionClassKey>>,
}
impl Manifest {
pub fn from_manifest_data(manifest_data: ManifestData, auth: &Authentication) -> Result<Self> {
let (main_decryption_key, unlocked_class_keys) = match (manifest_data.is_encrypted, auth) {
(true, Authentication::Password(password)) => {
let backup_key_ring = manifest_data.key_ring.as_ref().ok_or_else(|| {
BackupError::MissingPlistKey(
"BackupKeyBag (required for encrypted backup)".to_string(),
)
})?;
let master_key = derive_key_from_password(
password.as_bytes(),
&backup_key_ring.dpsl,
backup_key_ring.dpic,
&backup_key_ring.salt,
backup_key_ring.iter,
)?;
let unlocked_keys_map = unlock_keys_from_manifest(&master_key, &manifest_data)
.map_err(|_| BackupError::PasswordOrKeyIncorrect)?;
(Some(master_key), Some(unlocked_keys_map))
}
(true, Authentication::DerivedKey(key_hex)) => {
manifest_data.key_ring.as_ref().ok_or_else(|| {
BackupError::MissingPlistKey(
"BackupKeyBag (required for encrypted backup)".to_string(),
)
})?;
let master_key = hex_decode(key_hex)?.into();
let unlocked_keys_map = unlock_keys_from_manifest(&master_key, &manifest_data)
.map_err(|_| BackupError::PasswordOrKeyIncorrect)?;
(Some(master_key), Some(unlocked_keys_map))
}
(true, Authentication::None) => return Err(BackupError::PasswordOrKeyRequired),
(false, Authentication::None) => (None, None),
(false, _) => return Err(BackupError::NotEncrypted),
};
Ok(Self {
manifest_data,
main_decryption_key,
unlocked_class_keys,
})
}
pub fn get_class_key(&self, protection_class: u32) -> Result<&ProtectionClassKey> {
if !self.manifest_data.is_encrypted {
return Err(BackupError::NotEncrypted);
}
self.unlocked_class_keys
.as_ref()
.and_then(|keys| keys.get(&protection_class))
.ok_or_else(|| BackupError::Crypto(format!("Class {protection_class} key not found!",)))
}
pub fn keys(&self) -> Result<&HashMap<u32, ProtectionClassKey>> {
if !self.manifest_data.is_encrypted {
return Err(BackupError::NotEncrypted);
}
self.unlocked_class_keys.as_ref().ok_or_else(|| {
BackupError::Crypto("Missing class keys for encrypted backup".to_string())
})
}
}
#[derive(Debug, Clone)]
pub struct ManifestData {
pub(crate) key_ring: Option<KeyRing>,
pub is_encrypted: bool,
pub lockdown: ManifestLockdownInfo,
pub manifest_key: Option<EncryptionKey>,
pub applications: Vec<Application>,
}
impl ManifestData {
pub fn from_plist<P: AsRef<Path>>(path: P) -> Result<Self> {
let path_ref = path.as_ref();
let file = File::open(path_ref)
.map_err(|_| BackupError::ManifestPlistNotFound(path_ref.display().to_string()))?;
let plist = Value::from_reader(file).map_err(|_| {
BackupError::PlistParseError("Top-level plist is not a dictionary".into())
})?;
let dict = as_dictionary(&plist)?;
let is_encrypted = get_key_as_boolean(dict, "IsEncrypted").unwrap_or(false);
let backup_key_ring = if is_encrypted {
let data = get_key_as_data(dict, "BackupKeyBag")?;
Some(KeyRing::from_bytes(&data)?)
} else {
None
};
let manifest_key = if is_encrypted {
let data = get_key_as_data(dict, "ManifestKey")?;
Some(data.clone().into())
} else {
None
};
let lockdown_val = dict
.get("Lockdown")
.ok_or_else(|| BackupError::MissingPlistKey("Lockdown".into()))?;
let lockdown = ManifestLockdownInfo::from_plist(lockdown_val)?;
let app_dict = as_dictionary(
dict.get("Applications")
.ok_or_else(|| BackupError::MissingPlistKey("Applications".into()))?,
)?;
let applications = app_dict
.iter()
.filter_map(|(bundle_id, plist_data)| {
Application::from_plist(bundle_id, plist_data).ok()
})
.collect::<Vec<Application>>();
Ok(ManifestData {
key_ring: backup_key_ring,
is_encrypted,
lockdown,
manifest_key,
applications,
})
}
}