mirror of
https://github.com/jkcoxson/idevice.git
synced 2026-03-02 06:26:15 +01:00
Implement TSS support
This commit is contained in:
@@ -22,7 +22,7 @@ log = { version = "0.4" }
|
|||||||
env_logger = { version = "0.11" }
|
env_logger = { version = "0.11" }
|
||||||
|
|
||||||
indexmap = { version = "2.7", features = ["serde"], optional = true }
|
indexmap = { version = "2.7", features = ["serde"], optional = true }
|
||||||
uuid = { version = "1.12", features = ["serde"], optional = true }
|
uuid = { version = "1.12", features = ["serde", "v4"], optional = true }
|
||||||
async-recursion = { version = "1.1", optional = true }
|
async-recursion = { version = "1.1", optional = true }
|
||||||
base64 = { version = "0.22", optional = true }
|
base64 = { version = "0.22", optional = true }
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ serde_json = { version = "1", optional = true }
|
|||||||
json = { version = "0.12", optional = true }
|
json = { version = "0.12", optional = true }
|
||||||
byteorder = { version = "1.5", optional = true }
|
byteorder = { version = "1.5", optional = true }
|
||||||
|
|
||||||
|
reqwest = { version = "0.12", features = ["json"], optional = true }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
core_device_proxy = ["dep:serde_json", "dep:json", "dep:byteorder"]
|
||||||
@@ -38,6 +40,7 @@ installation_proxy = []
|
|||||||
mounter = []
|
mounter = []
|
||||||
usbmuxd = []
|
usbmuxd = []
|
||||||
tcp = ["tokio/net"]
|
tcp = ["tokio/net"]
|
||||||
|
tss = ["dep:uuid", "dep:reqwest"]
|
||||||
xpc = [
|
xpc = [
|
||||||
"tokio/full",
|
"tokio/full",
|
||||||
"dep:indexmap",
|
"dep:indexmap",
|
||||||
@@ -53,6 +56,7 @@ full = [
|
|||||||
"usbmuxd",
|
"usbmuxd",
|
||||||
"xpc",
|
"xpc",
|
||||||
"tcp",
|
"tcp",
|
||||||
|
"tss",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Why: https://github.com/rust-lang/cargo/issues/1197
|
# Why: https://github.com/rust-lang/cargo/issues/1197
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ pub mod lockdownd;
|
|||||||
pub mod mounter;
|
pub mod mounter;
|
||||||
pub mod pairing_file;
|
pub mod pairing_file;
|
||||||
pub mod provider;
|
pub mod provider;
|
||||||
|
#[cfg(feature = "tss")]
|
||||||
|
pub mod tss;
|
||||||
#[cfg(feature = "usbmuxd")]
|
#[cfg(feature = "usbmuxd")]
|
||||||
pub mod usbmuxd;
|
pub mod usbmuxd;
|
||||||
|
mod util;
|
||||||
#[cfg(feature = "xpc")]
|
#[cfg(feature = "xpc")]
|
||||||
pub mod xpc;
|
pub mod xpc;
|
||||||
|
|
||||||
@@ -216,6 +219,13 @@ pub enum IdeviceError {
|
|||||||
#[error("usb bad version")]
|
#[error("usb bad version")]
|
||||||
UsbBadVersion,
|
UsbBadVersion,
|
||||||
|
|
||||||
|
#[error("bad build manifest")]
|
||||||
|
BadBuildManifest,
|
||||||
|
|
||||||
|
#[cfg(feature = "tss")]
|
||||||
|
#[error("http reqwest error")]
|
||||||
|
Reqwest(#[from] reqwest::Error),
|
||||||
|
|
||||||
#[error("unknown error `{0}` returned from device")]
|
#[error("unknown error `{0}` returned from device")]
|
||||||
UnknownErrorType(String),
|
UnknownErrorType(String),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Abstractions for lockdownd
|
// Abstractions for lockdownd
|
||||||
|
|
||||||
use log::error;
|
use log::error;
|
||||||
|
use plist::Value;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{pairing_file, Idevice, IdeviceError, IdeviceService};
|
use crate::{pairing_file, Idevice, IdeviceError, IdeviceService};
|
||||||
@@ -37,7 +38,7 @@ impl LockdowndClient {
|
|||||||
pub fn new(idevice: Idevice) -> Self {
|
pub fn new(idevice: Idevice) -> Self {
|
||||||
Self { idevice }
|
Self { idevice }
|
||||||
}
|
}
|
||||||
pub async fn get_value(&mut self, value: impl Into<String>) -> Result<String, IdeviceError> {
|
pub async fn get_value(&mut self, value: impl Into<String>) -> Result<Value, IdeviceError> {
|
||||||
let req = LockdowndRequest {
|
let req = LockdowndRequest {
|
||||||
label: self.idevice.label.clone(),
|
label: self.idevice.label.clone(),
|
||||||
key: Some(value.into()),
|
key: Some(value.into()),
|
||||||
@@ -47,7 +48,7 @@ impl LockdowndClient {
|
|||||||
self.idevice.send_plist(message).await?;
|
self.idevice.send_plist(message).await?;
|
||||||
let message: plist::Dictionary = self.idevice.read_plist().await?;
|
let message: plist::Dictionary = self.idevice.read_plist().await?;
|
||||||
match message.get("Value") {
|
match message.get("Value") {
|
||||||
Some(m) => Ok(plist::from_value(m)?),
|
Some(m) => Ok(m.to_owned()),
|
||||||
None => Err(IdeviceError::UnexpectedResponse),
|
None => Err(IdeviceError::UnexpectedResponse),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
use crate::{lockdownd::LockdowndClient, Idevice, IdeviceError, IdeviceService};
|
use crate::{
|
||||||
|
lockdownd::LockdowndClient, tss::TSSRequest, util::hashmap_to_dictionary, Idevice,
|
||||||
|
IdeviceError, IdeviceService,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct ImageMounter {
|
pub struct ImageMounter {
|
||||||
idevice: Idevice,
|
idevice: Idevice,
|
||||||
@@ -51,6 +54,26 @@ impl ImageMounter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Looks up an image and returns the signature
|
||||||
|
pub async fn lookup_image(
|
||||||
|
&mut self,
|
||||||
|
image_type: impl Into<String>,
|
||||||
|
) -> Result<Vec<u8>, IdeviceError> {
|
||||||
|
let image_type = image_type.into();
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("Command".into(), "LookupImage".into());
|
||||||
|
req.insert("ImageType".into(), image_type.into());
|
||||||
|
self.idevice
|
||||||
|
.send_plist(plist::Value::Dictionary(req))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res = self.idevice.read_plist().await?;
|
||||||
|
match res.get("ImageSignature") {
|
||||||
|
Some(plist::Value::Data(signature)) => Ok(signature.clone()),
|
||||||
|
_ => Err(IdeviceError::NotFound),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn upload_image(
|
pub async fn upload_image(
|
||||||
&mut self,
|
&mut self,
|
||||||
image_type: impl Into<String>,
|
image_type: impl Into<String>,
|
||||||
@@ -129,6 +152,28 @@ impl ImageMounter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Unmounts an image at a specified path.
|
||||||
|
/// Use ``/Developer`` for pre-iOS 17 developer images.
|
||||||
|
/// Use ``/System/Developer`` for personalized images.
|
||||||
|
pub async fn unmount_image(
|
||||||
|
&mut self,
|
||||||
|
mount_path: impl Into<String>,
|
||||||
|
) -> Result<(), IdeviceError> {
|
||||||
|
let mount_path = mount_path.into();
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("Command".into(), "UnmountImage".into());
|
||||||
|
req.insert("MountPath".into(), mount_path.into());
|
||||||
|
self.idevice
|
||||||
|
.send_plist(plist::Value::Dictionary(req))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res = self.idevice.read_plist().await?;
|
||||||
|
match res.get("Status") {
|
||||||
|
Some(plist::Value::String(s)) if s.as_str() == "Complete" => Ok(()),
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Queries the personalization manifest from the device.
|
/// Queries the personalization manifest from the device.
|
||||||
/// On failure, the socket must be closed and reestablished.
|
/// On failure, the socket must be closed and reestablished.
|
||||||
pub async fn query_personalization_manifest(
|
pub async fn query_personalization_manifest(
|
||||||
@@ -153,4 +198,334 @@ impl ImageMounter {
|
|||||||
_ => Err(IdeviceError::NotFound),
|
_ => Err(IdeviceError::NotFound),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn query_developer_mode_status(&mut self) -> Result<bool, IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("Command".into(), "QueryDeveloperModeStatus".into());
|
||||||
|
self.idevice
|
||||||
|
.send_plist(plist::Value::Dictionary(req))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res = self.idevice.read_plist().await?;
|
||||||
|
match res.get("DeveloperModeStatus") {
|
||||||
|
Some(plist::Value::Boolean(status)) => Ok(*status),
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_nonce(
|
||||||
|
&mut self,
|
||||||
|
personalized_image_type: Option<String>,
|
||||||
|
) -> Result<Vec<u8>, IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("Command".into(), "QueryNonce".into());
|
||||||
|
if let Some(image_type) = personalized_image_type {
|
||||||
|
req.insert("PersonalizedImageType".into(), image_type.into());
|
||||||
|
}
|
||||||
|
self.idevice
|
||||||
|
.send_plist(plist::Value::Dictionary(req))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res = self.idevice.read_plist().await?;
|
||||||
|
match res.get("PersonalizationNonce") {
|
||||||
|
Some(plist::Value::Data(nonce)) => Ok(nonce.clone()),
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_personalization_identifiers(
|
||||||
|
&mut self,
|
||||||
|
image_type: Option<String>,
|
||||||
|
) -> Result<plist::Dictionary, IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("Command".into(), "QueryPersonalizationIdentifiers".into());
|
||||||
|
if let Some(image_type) = image_type {
|
||||||
|
req.insert("PersonalizedImageType".into(), image_type.into());
|
||||||
|
}
|
||||||
|
self.idevice
|
||||||
|
.send_plist(plist::Value::Dictionary(req))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let res = self.idevice.read_plist().await?;
|
||||||
|
match res.get("PersonalizationIdentifiers") {
|
||||||
|
Some(plist::Value::Dictionary(identifiers)) => Ok(identifiers.clone()),
|
||||||
|
_ => Err(IdeviceError::UnexpectedResponse),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn roll_personalization_nonce(&mut self) -> Result<(), IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("Command".into(), "RollPersonalizationNonce".into());
|
||||||
|
self.idevice
|
||||||
|
.send_plist(plist::Value::Dictionary(req))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn roll_cryptex_nonce(&mut self) -> Result<(), IdeviceError> {
|
||||||
|
let mut req = plist::Dictionary::new();
|
||||||
|
req.insert("Command".into(), "RollCryptexNonce".into());
|
||||||
|
self.idevice
|
||||||
|
.send_plist(plist::Value::Dictionary(req))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mount_developer(
|
||||||
|
&mut self,
|
||||||
|
image: &[u8],
|
||||||
|
signature: Vec<u8>,
|
||||||
|
) -> Result<(), IdeviceError> {
|
||||||
|
self.upload_image("Developer", &image, signature.clone())
|
||||||
|
.await?;
|
||||||
|
self.mount_image(
|
||||||
|
"Developer",
|
||||||
|
signature,
|
||||||
|
Vec::new(),
|
||||||
|
plist::Value::Dictionary(plist::Dictionary::new()),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn mount_personalized(
|
||||||
|
&mut self,
|
||||||
|
image: Vec<u8>,
|
||||||
|
trust_cache: Vec<u8>,
|
||||||
|
build_manifest: &[u8],
|
||||||
|
info_plist: Option<plist::Value>,
|
||||||
|
unique_chip_id: u64,
|
||||||
|
) -> Result<(), IdeviceError> {
|
||||||
|
// Try to fetch personalization manifest
|
||||||
|
let manifest = match self
|
||||||
|
.query_personalization_manifest("DeveloperDiskImage", image.clone()) // TODO:
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(manifest) => manifest,
|
||||||
|
Err(IdeviceError::NotFound) => {
|
||||||
|
// Get manifest from TSS
|
||||||
|
let manifest_dict: plist::Dictionary = plist::from_bytes(build_manifest)?;
|
||||||
|
self.get_manifest_from_tss(&manifest_dict, unique_chip_id)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
|
Err(e) => return Err(e),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.upload_image("Personalized", &image, manifest.clone())
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut extras = plist::Dictionary::new();
|
||||||
|
if let Some(info) = info_plist {
|
||||||
|
extras.insert("ImageInfoPlist".into(), info);
|
||||||
|
}
|
||||||
|
extras.insert(
|
||||||
|
"ImageTrustCache".into(),
|
||||||
|
plist::Value::Data(trust_cache.clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
self.mount_image(
|
||||||
|
"Personalized",
|
||||||
|
manifest,
|
||||||
|
trust_cache,
|
||||||
|
plist::Value::Dictionary(extras),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "tss")]
|
||||||
|
pub async fn get_manifest_from_tss(
|
||||||
|
&mut self,
|
||||||
|
build_manifest: &plist::Dictionary,
|
||||||
|
unique_chip_id: u64,
|
||||||
|
) -> Result<Vec<u8>, IdeviceError> {
|
||||||
|
use log::{debug, warn};
|
||||||
|
|
||||||
|
let mut request = TSSRequest::new();
|
||||||
|
|
||||||
|
let personalization_identifiers = self.query_personalization_identifiers(None).await?;
|
||||||
|
for (key, val) in &personalization_identifiers {
|
||||||
|
if key.starts_with("Ap,") {
|
||||||
|
request.insert(key, val.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let board_id = match personalization_identifiers.get("BoardId") {
|
||||||
|
Some(plist::Value::Integer(b)) => match b.as_unsigned() {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return Err(IdeviceError::UnexpectedResponse),
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let chip_id = match personalization_identifiers.get("ChipID") {
|
||||||
|
Some(plist::Value::Integer(b)) => match b.as_unsigned() {
|
||||||
|
Some(b) => b,
|
||||||
|
None => return Err(IdeviceError::UnexpectedResponse),
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
request.insert("@ApImg4Ticket", true);
|
||||||
|
request.insert("@BBTicket", true);
|
||||||
|
request.insert("ApBoardID", board_id);
|
||||||
|
request.insert("ApChipID", chip_id);
|
||||||
|
request.insert("ApECID", unique_chip_id);
|
||||||
|
request.insert(
|
||||||
|
"ApNonce",
|
||||||
|
plist::Value::Data(
|
||||||
|
self.query_nonce(Some("DeveloperDiskImage".to_string()))
|
||||||
|
.await?,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
request.insert("ApProductionMode", true);
|
||||||
|
request.insert("ApSecurityDomain", 1);
|
||||||
|
request.insert("ApSecurityMode", true);
|
||||||
|
request.insert("SepNonce", plist::Value::Data(vec![0; 20]));
|
||||||
|
request.insert("UID_MODE", false);
|
||||||
|
|
||||||
|
let identities = match build_manifest.get("BuildIdentities") {
|
||||||
|
Some(plist::Value::Array(i)) => i,
|
||||||
|
_ => {
|
||||||
|
return Err(IdeviceError::BadBuildManifest);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut build_identity = None;
|
||||||
|
for id in identities {
|
||||||
|
let id = match id {
|
||||||
|
plist::Value::Dictionary(id) => id,
|
||||||
|
_ => {
|
||||||
|
debug!("build identity wasn't a dictionary");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let ap_board_id = match id.get("ApBoardID") {
|
||||||
|
Some(plist::Value::String(a)) => a,
|
||||||
|
_ => {
|
||||||
|
debug!("Build identity contained no ApBoardID");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let ap_board_id = match u64::from_str_radix(ap_board_id.trim_start_matches("0x"), 16) {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(_) => {
|
||||||
|
debug!("Could not parse {ap_board_id} as usize");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if ap_board_id != board_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ap_chip_id = match id.get("ApChipID") {
|
||||||
|
Some(plist::Value::String(a)) => a,
|
||||||
|
_ => {
|
||||||
|
debug!("Build identity contained no ApChipID");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let ap_chip_id = match u64::from_str_radix(ap_chip_id.trim_start_matches("0x"), 16) {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(_) => {
|
||||||
|
debug!("Could not parse {ap_board_id} as usize");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if ap_chip_id != chip_id {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
build_identity = Some(id.to_owned());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let build_identity = match build_identity {
|
||||||
|
Some(b) => b,
|
||||||
|
None => {
|
||||||
|
return Err(IdeviceError::BadBuildManifest);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let manifest = match build_identity.get("Manifest") {
|
||||||
|
Some(plist::Value::Dictionary(m)) => m,
|
||||||
|
_ => {
|
||||||
|
return Err(IdeviceError::BadBuildManifest);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut parameters = plist::Dictionary::new();
|
||||||
|
parameters.insert("ApProductionMode".into(), true.into());
|
||||||
|
parameters.insert("ApSecurityDomain".into(), 1.into());
|
||||||
|
parameters.insert("ApSecurityMode".into(), true.into());
|
||||||
|
parameters.insert("ApSupportsImg4".into(), true.into());
|
||||||
|
|
||||||
|
for (key, manifest_item) in manifest {
|
||||||
|
let manifest_item = match manifest_item {
|
||||||
|
plist::Value::Dictionary(m) => m,
|
||||||
|
_ => {
|
||||||
|
debug!("Manifest item wasn't a dictionary");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let info = match manifest_item.get("Info") {
|
||||||
|
Some(plist::Value::Dictionary(i)) => i,
|
||||||
|
_ => {
|
||||||
|
debug!("Manifest item didn't contain info");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match info.get("Trusted") {
|
||||||
|
Some(plist::Value::Boolean(t)) => {
|
||||||
|
if !t {
|
||||||
|
debug!("Info item isn't trusted");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!("Info didn't contain trusted bool");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut tss_entry = manifest_item.clone();
|
||||||
|
tss_entry.remove("Info");
|
||||||
|
|
||||||
|
if let Some(plist::Value::Dictionary(l)) = manifest.get("LoadableTrustCache") {
|
||||||
|
if let Some(plist::Value::Dictionary(i)) = l.get("Info") {
|
||||||
|
if let Some(plist::Value::Array(rules)) = i.get("RestoreRequestRules") {
|
||||||
|
crate::tss::apply_restore_request_rules(&mut tss_entry, ¶meters, rules);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if manifest_item.get("Digest").is_none() {
|
||||||
|
tss_entry.insert("Digest".into(), plist::Value::Data(vec![]));
|
||||||
|
}
|
||||||
|
|
||||||
|
request.insert(key, tss_entry);
|
||||||
|
}
|
||||||
|
let res = request.send().await?;
|
||||||
|
let mut res = match res {
|
||||||
|
plist::Value::Dictionary(r) => r,
|
||||||
|
_ => {
|
||||||
|
warn!("Apple returned a non-dictionary plist");
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match res.remove("ApImg4Ticket") {
|
||||||
|
Some(plist::Value::Data(d)) => Ok(d),
|
||||||
|
_ => {
|
||||||
|
warn!("TSS response didn't contain ApImg4Ticket data");
|
||||||
|
Err(IdeviceError::UnexpectedResponse)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
140
idevice/src/tss.rs
Normal file
140
idevice/src/tss.rs
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
// Thanks pymobiledevice3
|
||||||
|
|
||||||
|
use log::{debug, warn};
|
||||||
|
use plist::Value;
|
||||||
|
|
||||||
|
use crate::{util::plist_to_bytes, IdeviceError};
|
||||||
|
|
||||||
|
const TSS_CLIENT_VERSION_STRING: &str = "libauthinstall-1033.0.2";
|
||||||
|
const TSS_CONTROLLER_ACTION_URL: &str = "http://gs.apple.com/TSS/controller?action=2";
|
||||||
|
|
||||||
|
pub struct TSSRequest {
|
||||||
|
inner: plist::Dictionary,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TSSRequest {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut inner = plist::Dictionary::new();
|
||||||
|
inner.insert("@HostPlatformInfo".into(), "mac".into());
|
||||||
|
inner.insert("@VersionInfo".into(), TSS_CLIENT_VERSION_STRING.into());
|
||||||
|
inner.insert(
|
||||||
|
"@UUID".into(),
|
||||||
|
uuid::Uuid::new_v4().to_string().to_uppercase().into(),
|
||||||
|
);
|
||||||
|
Self {
|
||||||
|
inner: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&mut self, key: impl Into<String>, val: impl Into<Value>) {
|
||||||
|
let key = key.into();
|
||||||
|
let val = val.into();
|
||||||
|
self.inner.insert(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send(&self) -> Result<plist::Value, IdeviceError> {
|
||||||
|
let client = reqwest::Client::new();
|
||||||
|
|
||||||
|
let res = client
|
||||||
|
.post(TSS_CONTROLLER_ACTION_URL)
|
||||||
|
.header("Cache-Control", "no-cache")
|
||||||
|
.header("Content-type", "text/xml; charset=\"utf-8\"")
|
||||||
|
.header("User-Agent", "InetURL/1.0")
|
||||||
|
.header("Expect", "")
|
||||||
|
.body(plist_to_bytes(&self.inner))
|
||||||
|
.send()
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
debug!("Apple responeded with {res}");
|
||||||
|
let res = res.trim_start_matches("MESSAGE=");
|
||||||
|
if !res.starts_with("SUCCESS") {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
let res = res.split("REQUEST_STRING=").collect::<Vec<&str>>();
|
||||||
|
if res.len() < 2 {
|
||||||
|
return Err(IdeviceError::UnexpectedResponse);
|
||||||
|
}
|
||||||
|
Ok(plist::from_bytes(res[1].as_bytes())?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for TSSRequest {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_restore_request_rules(
|
||||||
|
input: &mut plist::Dictionary,
|
||||||
|
parameters: &plist::Dictionary,
|
||||||
|
rules: &Vec<plist::Value>,
|
||||||
|
) {
|
||||||
|
for rule in rules {
|
||||||
|
if let plist::Value::Dictionary(rule) = rule {
|
||||||
|
let mut conditions_fulfulled = true;
|
||||||
|
let conditions = match rule.get("Conditions") {
|
||||||
|
Some(plist::Value::Dictionary(c)) => c,
|
||||||
|
_ => {
|
||||||
|
warn!("Conditions doesn't exist or wasn't a dictionary!!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (key, value) in conditions {
|
||||||
|
let value2 = match key.as_str() {
|
||||||
|
"ApRawProductionMode" => parameters.get("ApProductionMode"),
|
||||||
|
"ApCurrentProductionMode" => parameters.get("ApProductionMode"),
|
||||||
|
"ApRawSecurityMode" => parameters.get("ApSecurityMode"),
|
||||||
|
"ApRequiresImage4" => parameters.get("ApSupportsImg4"),
|
||||||
|
"ApDemotionPolicyOverride" => parameters.get("DemotionPolicy"),
|
||||||
|
"ApInRomDFU" => parameters.get("ApInRomDFU"),
|
||||||
|
_ => {
|
||||||
|
warn!("Unhandled key {key}");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
conditions_fulfulled = match value2 {
|
||||||
|
Some(value2) => value == value2,
|
||||||
|
None => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if !conditions_fulfulled {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !conditions_fulfulled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let actions = match rule.get("Actions") {
|
||||||
|
Some(plist::Value::Dictionary(a)) => a,
|
||||||
|
_ => {
|
||||||
|
warn!("Actions doesn't exist or wasn't a dictionary!!");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (key, value) in actions {
|
||||||
|
if let Some(i) = value.as_unsigned_integer() {
|
||||||
|
if i == 255 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(i) = value.as_signed_integer() {
|
||||||
|
if i == 255 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input.insert(key.to_owned(), value.to_owned());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
warn!("Rule wasn't a dictionary");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
// Jackson Coxson
|
// Jackson Coxson
|
||||||
|
|
||||||
|
use crate::util::plist_to_bytes;
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -11,14 +12,6 @@ pub struct RawPacket {
|
|||||||
pub plist: plist::Dictionary,
|
pub plist: plist::Dictionary,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn plist_to_bytes(p: &plist::Dictionary) -> Vec<u8> {
|
|
||||||
let buf = Vec::new();
|
|
||||||
let mut writer = std::io::BufWriter::new(buf);
|
|
||||||
plist::to_writer_xml(&mut writer, &p).unwrap();
|
|
||||||
|
|
||||||
writer.into_inner().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawPacket {
|
impl RawPacket {
|
||||||
pub fn new(plist: plist::Dictionary, version: u32, message: u32, tag: u32) -> RawPacket {
|
pub fn new(plist: plist::Dictionary, version: u32, message: u32, tag: u32) -> RawPacket {
|
||||||
let plist_bytes = plist_to_bytes(&plist);
|
let plist_bytes = plist_to_bytes(&plist);
|
||||||
|
|||||||
9
idevice/src/util.rs
Normal file
9
idevice/src/util.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// Jackson Coxson
|
||||||
|
|
||||||
|
pub fn plist_to_bytes(p: &plist::Dictionary) -> Vec<u8> {
|
||||||
|
let buf = Vec::new();
|
||||||
|
let mut writer = std::io::BufWriter::new(buf);
|
||||||
|
plist::to_writer_xml(&mut writer, &p).unwrap();
|
||||||
|
|
||||||
|
writer.into_inner().unwrap()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user