diff --git a/isideload/src/anisette/data.rs b/isideload/src/anisette/data.rs new file mode 100644 index 0000000..fbd6f4f --- /dev/null +++ b/isideload/src/anisette/data.rs @@ -0,0 +1,147 @@ +use crate::{ + anisette::{AnisetteProvider, AnisetteProviderConfig, remote_v3::state::AnisetteState}, + auth::grandslam::GrandSlam, +}; +use plist::Dictionary; +use plist_macro::plist; +use reqwest::header::HeaderMap; +use rootcause::prelude::*; +use serde::Deserialize; +use std::{collections::HashMap, time::SystemTime}; + +#[derive(Deserialize, Debug, Clone)] +pub struct AnisetteClientInfo { + pub client_info: String, + pub user_agent: String, +} + +#[derive(Debug, Clone)] +pub struct AnisetteData { + pub routing_info: String, + pub machine_id: String, + pub one_time_password: String, + pub device_description: String, + pub device_unique_identifier: String, + pub local_user_id: String, + pub generated_at: SystemTime, +} + +// Some headers don't seem to be required. I guess not including them is technically more efficient soooo +impl AnisetteData { + pub fn get_headers(&self) -> HashMap { + //let dt: DateTime = Utc::now().round_subsecs(0); + + HashMap::from_iter( + [ + // ( + // "X-Apple-I-Client-Time".to_string(), + // dt.format("%+").to_string().replace("+00:00", "Z"), + // ), + // ("X-Apple-I-SRL-NO".to_string(), serial), + // ("X-Apple-I-TimeZone".to_string(), "UTC".to_string()), + // ("X-Apple-Locale".to_string(), "en_US".to_string()), + // ("X-Apple-I-MD-RINFO".to_string(), self.routing_info.clone()), + // ("X-Apple-I-MD-LU".to_string(), self.local_user_id.clone()), + ( + "X-Mme-Device-Id".to_string(), + self.device_unique_identifier.clone(), + ), + ("X-Apple-I-MD".to_string(), self.one_time_password.clone()), + ("X-Apple-I-MD-M".to_string(), self.machine_id.clone()), + // ( + // "X-Mme-Client-Info".to_string(), + // self.device_description.clone(), + // ), + ] + .into_iter(), + ) + } + + pub fn get_header_map(&self) -> HeaderMap { + let headers_map = self.get_headers(); + let mut header_map = HeaderMap::new(); + + for (key, value) in headers_map { + header_map.insert( + reqwest::header::HeaderName::from_bytes(key.as_bytes()).unwrap(), + reqwest::header::HeaderValue::from_str(&value).unwrap(), + ); + } + + header_map + } + + pub fn get_client_provided_data(&self) -> Dictionary { + let headers = self.get_headers(); + + let mut cpd = plist!(dict { + "bootstrap": "true", + "icscrec": "true", + "loc": "en_US", + "pbe": "false", + "prkgen": "true", + "svct": "iCloud" + }); + + for (key, value) in headers { + cpd.insert(key.to_string(), plist::Value::String(value)); + } + + cpd + } + + pub fn needs_refresh(&self) -> bool { + let elapsed = self.generated_at.elapsed().unwrap(); + elapsed.as_secs() > 60 + } +} + +pub struct RenewableAnisetteData { + config: AnisetteProviderConfig, + anisette_data: Option, + client_info: Option, + state: Option, +} + +impl RenewableAnisetteData { + pub fn new(config: AnisetteProviderConfig) -> Self { + RenewableAnisetteData { + config, + anisette_data: None, + client_info: None, + state: None, + } + } + + pub async fn get_anisette_data(&mut self, gs: &mut GrandSlam) -> Result<&AnisetteData, Report> { + if self + .anisette_data + .as_ref() + .map_or(true, |data| data.needs_refresh()) + { + if self.client_info.is_none() || self.state.is_none() { + let mut provider = self.config.get_provider(self.client_info.clone()); + let client_info = provider.get_client_info().await?; + self.client_info = Some(client_info); + let data = provider.get_anisette_data(gs).await?; + self.anisette_data = Some(data); + } else { + } + } + + Ok(self.anisette_data.as_ref().unwrap()) + } + + pub async fn get_client_info( + &mut self, + gs: &mut GrandSlam, + ) -> Result { + self.get_anisette_data(gs).await?; + + if let Some(client_info) = &self.client_info { + return Ok(client_info.clone()); + } else { + bail!("Anisette client info not available"); + } + } +} diff --git a/isideload/src/auth/apple_account.rs b/isideload/src/auth/apple_account.rs index 4f60d9f..5dffd84 100644 --- a/isideload/src/auth/apple_account.rs +++ b/isideload/src/auth/apple_account.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, Mutex}; + use crate::{ anisette::{AnisetteData, AnisetteProvider}, auth::{ @@ -28,7 +30,7 @@ use tracing::{debug, info, warn}; pub struct AppleAccount { pub email: String, pub spd: Option, - pub anisette_provider: Box, + pub anisette_provider: Arc>, pub anisette_data: AnisetteData, pub grandslam_client: GrandSlam, login_state: LoginState, @@ -62,7 +64,7 @@ impl AppleAccount { /// - `debug`: DANGER, If true, accept invalid certificates and enable verbose connection pub async fn new( email: &str, - mut anisette_provider: Box, + mut anisette_provider: Arc>, debug: bool, ) -> Result { info!("Initializing apple account"); @@ -71,6 +73,8 @@ impl AppleAccount { } let client_info = anisette_provider + .lock() + .unwrap() .get_client_info() .await .context("Failed to get anisette client info")?; diff --git a/isideload/src/dev/developer_session.rs b/isideload/src/dev/developer_session.rs index 1ecce74..0c80303 100644 --- a/isideload/src/dev/developer_session.rs +++ b/isideload/src/dev/developer_session.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, Mutex}; + use plist::Dictionary; use plist_macro::{plist, plist_to_xml_string}; use rootcause::prelude::*; @@ -6,7 +8,7 @@ use tracing::{error, warn}; use uuid::Uuid; use crate::{ - anisette::AnisetteData, + anisette::{AnisetteData, AnisetteProvider}, auth::{ apple_account::{AppToken, AppleAccount}, grandslam::GrandSlam, @@ -25,7 +27,7 @@ pub struct DeveloperSession<'a> { token: AppToken, adsid: String, client: &'a GrandSlam, - anisette_data: &'a AnisetteData, + anisette_provider: Arc>, } impl<'a> DeveloperSession<'a> { @@ -33,13 +35,13 @@ impl<'a> DeveloperSession<'a> { token: AppToken, adsid: String, client: &'a GrandSlam, - anisette_data: &'a AnisetteData, + anisette_provider: Arc>, ) -> Self { DeveloperSession { token, adsid, client, - anisette_data, + anisette_provider, } }