From 2a5710380da85e7c0106995d6f44f05e91369705 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Mon, 1 Apr 2024 19:57:58 -0600 Subject: [PATCH 01/19] feat(icloud-auth): convert to async and integrate anisette-v3 via omnisette --- apple-dev-apis/Cargo.toml | 3 ++ apple-dev-apis/tests/xcsession.rs | 6 +-- icloud-auth/Cargo.toml | 5 +- icloud-auth/src/anisette.rs | 7 +-- icloud-auth/src/client.rs | 50 +++++++++--------- icloud-auth/tests/gsa_auth.rs | 11 ++-- omnisette/Cargo.toml | 3 +- omnisette/src/lib.rs | 9 +++- omnisette/src/remote_anisette_v3.rs | 79 ++++++++++++++++++++++------- 9 files changed, 118 insertions(+), 55 deletions(-) diff --git a/apple-dev-apis/Cargo.toml b/apple-dev-apis/Cargo.toml index 8aab7e3..187193b 100644 --- a/apple-dev-apis/Cargo.toml +++ b/apple-dev-apis/Cargo.toml @@ -14,3 +14,6 @@ omnisette = {path = "../omnisette"} icloud_auth = {path = "../icloud-auth"} hmac = "0.12.1" sha2 = "0.10.6" + +[dev-dependencies] +tokio = { version = "1", features = ["rt", "macros"] } diff --git a/apple-dev-apis/tests/xcsession.rs b/apple-dev-apis/tests/xcsession.rs index 58b03e1..24483c4 100644 --- a/apple-dev-apis/tests/xcsession.rs +++ b/apple-dev-apis/tests/xcsession.rs @@ -3,8 +3,8 @@ mod tests { use apple_dev_apis::XcodeSession; use icloud_auth::*; - #[test] - fn xcsession_test() { + #[tokio::test] + async fn xcsession_test() { println!("gsa auth test"); let email = std::env::var("apple_email").unwrap_or_else(|_| { @@ -29,7 +29,7 @@ mod tests { std::io::stdin().read_line(&mut input).unwrap(); input.trim().to_string() }; - let acc = AppleAccount::login(appleid_closure, tfa_closure); + let acc = AppleAccount::login(appleid_closure, tfa_closure).await; let session = XcodeSession::with(&acc.unwrap()); } } diff --git a/icloud-auth/Cargo.toml b/icloud-auth/Cargo.toml index 530ba8f..bccadec 100644 --- a/icloud-auth/Cargo.toml +++ b/icloud-auth/Cargo.toml @@ -22,4 +22,7 @@ cbc = { version = "0.1.2", features = ["std"] } aes = "0.8.2" pkcs7 = "0.3.0" reqwest = { version = "0.11.14", features = ["blocking", "json", "default-tls"] } -omnisette = {path = "../omnisette"} +omnisette = {path = "../omnisette", features = ["remote-anisette-v3"]} + +[dev-dependencies] +tokio = { version = "1", features = ["rt", "macros"] } diff --git a/icloud-auth/src/anisette.rs b/icloud-auth/src/anisette.rs index bd37b40..b5e3550 100644 --- a/icloud-auth/src/anisette.rs +++ b/icloud-auth/src/anisette.rs @@ -9,13 +9,13 @@ pub struct AnisetteData { impl AnisetteData { /// Fetches the data at an anisette server - pub fn new() -> Result { + pub async fn new() -> Result { let base_headers = match AnisetteHeaders::get_anisette_headers_provider( AnisetteConfiguration::new() .set_configuration_path(PathBuf::new().join("anisette_test")) - .set_anisette_url("https://ani.f1sh.me".to_string()), + .set_anisette_url("https://ani.sidestore.io/".to_string()), ) { - Ok(mut b) => match b.get_authentication_headers() { + Ok(mut b) => match b.provider.get_authentication_headers().await { Ok(b) => b, Err(_) => return Err(Error::ErrorGettingAnisette), }, @@ -34,6 +34,7 @@ impl AnisetteData { app_info: bool, ) -> HashMap { let mut headers = self.base_headers.clone(); + println!("headers {:?}", headers); let old_client_info = headers.remove("X-Mme-Client-Info"); if client_info { let client_info = match old_client_info { diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 5525523..7d7b15a 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -6,7 +6,7 @@ use aes::cipher::block_padding::Pkcs7; use cbc::cipher::{BlockDecryptMut, KeyIvInit}; use hmac::{Hmac, Mac}; use reqwest::{ - blocking::{Client, ClientBuilder, Response}, + Client, ClientBuilder, Response, header::{HeaderMap, HeaderName, HeaderValue}, Certificate, }; @@ -123,8 +123,8 @@ pub enum LoginResponse { // } // } -fn parse_response(res: Result) -> plist::Dictionary { - let res = res.unwrap().text().unwrap(); +async fn parse_response(res: Result) -> plist::Dictionary { + let res = res.unwrap().text().await.unwrap(); println!("{:?}", res); let res: plist::Dictionary = plist::from_bytes(res.as_bytes()).unwrap(); let res: plist::Value = res.get("Response").unwrap().to_owned(); @@ -135,8 +135,8 @@ fn parse_response(res: Result) -> plist::Dictionary { } impl AppleAccount { - pub fn new() -> Self { - let anisette = AnisetteData::new(); + pub async fn new() -> Self { + let anisette = AnisetteData::new().await; Self::new_with_anisette(anisette.unwrap()) } @@ -155,15 +155,15 @@ impl AppleAccount { } } - pub fn login( + pub async fn login( appleid_closure: impl Fn() -> (String, String), tfa_closure: impl Fn() -> String, ) -> Result { let anisette = AnisetteData::new(); - AppleAccount::login_with_anisette(appleid_closure, tfa_closure, anisette.unwrap()) + AppleAccount::login_with_anisette(appleid_closure, tfa_closure, anisette.await.unwrap()).await } - pub fn get_app_token(&self, app_name: &str) -> Result { + pub async fn get_app_token(&self, app_name: &str) -> Result { let spd = self.spd.as_ref().unwrap(); // println!("spd: {:#?}", spd); let dsid = spd.get("adsid").unwrap().as_string().unwrap(); @@ -224,8 +224,8 @@ impl AppleAccount { .post(GSA_ENDPOINT) .headers(gsa_headers.clone()) .body(buffer) - .send(); - let res = parse_response(res); + .send().await; + let res = parse_response(res).await; let err_check = Self::check_error(&res); if err_check.is_err() { return Err(err_check.err().unwrap()); @@ -265,22 +265,22 @@ impl AppleAccount { /// ``` /// Note: You would not provide the 2FA code like this, you would have to actually ask input for it. //TODO: add login_with_anisette and login, where login autodetcts anisette - pub fn login_with_anisette (String, String), G: Fn() -> String>( + pub async fn login_with_anisette (String, String), G: Fn() -> String>( appleid_closure: F, tfa_closure: G, anisette: AnisetteData, ) -> Result { let mut _self = AppleAccount::new_with_anisette(anisette); let (username, password) = appleid_closure(); - let mut response = _self.login_email_pass(username.clone(), password.clone())?; + let mut response = _self.login_email_pass(username.clone(), password.clone()).await?; loop { match response { - LoginResponse::NeedsDevice2FA() => response = _self.send_2fa_to_devices().unwrap(), + LoginResponse::NeedsDevice2FA() => response = _self.send_2fa_to_devices().await.unwrap(), LoginResponse::Needs2FAVerification() => { - response = _self.verify_2fa(tfa_closure()).unwrap() + response = _self.verify_2fa(tfa_closure()).await.unwrap() } LoginResponse::NeedsLogin() => { - response = _self.login_email_pass(username.clone(), password.clone())? + response = _self.login_email_pass(username.clone(), password.clone()).await? } LoginResponse::LoggedIn(ac) => return Ok(ac), LoginResponse::Failed(e) => return Err(e), @@ -288,7 +288,7 @@ impl AppleAccount { } } - pub fn login_email_pass( + pub async fn login_email_pass( &mut self, username: String, password: String, @@ -340,9 +340,9 @@ impl AppleAccount { .post(GSA_ENDPOINT) .headers(gsa_headers.clone()) .body(buffer) - .send(); + .send().await; - let res = parse_response(res); + let res = parse_response(res).await; let err_check = Self::check_error(&res); if err_check.is_err() { return Err(err_check.err().unwrap()); @@ -393,9 +393,9 @@ impl AppleAccount { .post(GSA_ENDPOINT) .headers(gsa_headers.clone()) .body(buffer) - .send(); + .send().await; - let res = parse_response(res); + let res = parse_response(res).await; let err_check = Self::check_error(&res); if err_check.is_err() { return Err(err_check.err().unwrap()); @@ -453,14 +453,14 @@ impl AppleAccount { .unwrap() } - pub fn send_2fa_to_devices(&self) -> Result { + pub async fn send_2fa_to_devices(&self) -> Result { let headers = self.build_2fa_headers(); let res = self .client .get("https://gsa.apple.com/auth/verify/trusteddevice") .headers(headers) - .send(); + .send().await; if !res.as_ref().unwrap().status().is_success() { return Err(Error::AuthSrp); @@ -468,7 +468,7 @@ impl AppleAccount { return Ok(LoginResponse::Needs2FAVerification()); } - pub fn verify_2fa(&self, code: String) -> Result { + pub async fn verify_2fa(&self, code: String) -> Result { let headers = self.build_2fa_headers(); println!("Recieved code: {}", code); let res = self @@ -479,10 +479,10 @@ impl AppleAccount { HeaderName::from_str("security-code").unwrap(), HeaderValue::from_str(&code).unwrap(), ) - .send(); + .send().await; let res: plist::Dictionary = - plist::from_bytes(res.unwrap().text().unwrap().as_bytes()).unwrap(); + plist::from_bytes(res.unwrap().text().await.unwrap().as_bytes()).unwrap(); let err_check = Self::check_error(&res); if err_check.is_err() { diff --git a/icloud-auth/tests/gsa_auth.rs b/icloud-auth/tests/gsa_auth.rs index 63773d7..24de02c 100644 --- a/icloud-auth/tests/gsa_auth.rs +++ b/icloud-auth/tests/gsa_auth.rs @@ -2,8 +2,8 @@ mod tests { use icloud_auth::*; - #[test] - fn gsa_auth() { + #[tokio::test] + async fn gsa_auth() { println!("gsa auth test"); let email = std::env::var("apple_email").unwrap_or_else(|_| { println!("Enter Apple email: "); @@ -27,7 +27,10 @@ mod tests { std::io::stdin().read_line(&mut input).unwrap(); input.trim().to_string() }; - let acc = AppleAccount::login(appleid_closure, tfa_closure); + let acc = AppleAccount::login(appleid_closure, tfa_closure).await; + + println!("here"); + return; let account = acc.unwrap(); let spd_plist = account.clone().spd.unwrap(); // turn plist::dictonary into json @@ -35,7 +38,7 @@ mod tests { println!("{:?}", spd_json); - let auth_token = account.clone().get_app_token("com.apple.gs.xcode.auth"); + let auth_token = account.clone().get_app_token("com.apple.gs.xcode.auth").await; println!("auth_token: {:?}", auth_token.unwrap().auth_token); println!("gsa auth test done"); } diff --git a/omnisette/Cargo.toml b/omnisette/Cargo.toml index 4194367..a19de32 100644 --- a/omnisette/Cargo.toml +++ b/omnisette/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" remote-anisette = [] async = ["dep:async-trait"] default = ["remote-anisette", "dep:remove-async-await"] -remote-anisette-v3 = ["async", "dep:serde", "dep:serde_json", "dep:tokio-tungstenite", "dep:futures-util"] +remote-anisette-v3 = ["async", "dep:serde", "dep:serde_json", "dep:tokio-tungstenite", "dep:futures-util", "dep:chrono"] [dependencies] anyhow = "1.0" @@ -27,6 +27,7 @@ serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0.115", optional = true } tokio-tungstenite = { version = "0.20.1", optional = true, features = ["rustls-tls-native-roots"] } futures-util = { version = "0.3.28", optional = true } +chrono = { version = "0.4.37", optional = true } [target.'cfg(target_os = "macos")'.dependencies] dlopen2 = "0.4" diff --git a/omnisette/src/lib.rs b/omnisette/src/lib.rs index 3c95bc0..bd83db9 100644 --- a/omnisette/src/lib.rs +++ b/omnisette/src/lib.rs @@ -6,6 +6,7 @@ use crate::adi_proxy::{ADIProxyAnisetteProvider, ConfigurableADIProxy}; use crate::anisette_headers_provider::AnisetteHeadersProvider; +use crate::remote_anisette_v3::AnisetteState; use anyhow::Result; use std::fmt::Formatter; use std::path::PathBuf; @@ -44,12 +45,12 @@ impl std::error::Error for AnisetteMetaError {} pub const DEFAULT_ANISETTE_URL: &str = "https://ani.f1sh.me/"; -#[cfg(feature = "remote-anisette-v3")] pub const DEFAULT_ANISETTE_URL_V3: &str = "https://ani.sidestore.io"; #[derive(Clone)] pub struct AnisetteConfiguration { anisette_url: String, + anisette_url_v3: String, configuration_path: PathBuf, } @@ -63,6 +64,7 @@ impl AnisetteConfiguration { pub fn new() -> AnisetteConfiguration { AnisetteConfiguration { anisette_url: DEFAULT_ANISETTE_URL.to_string(), + anisette_url_v3: DEFAULT_ANISETTE_URL_V3.to_string(), configuration_path: PathBuf::new(), } } @@ -128,6 +130,11 @@ impl AnisetteHeaders { return Ok(ssc_anisette_headers_provider); } + #[cfg(feature = "remote-anisette-v3")] + return Ok(AnisetteHeadersProviderRes::remote(Box::new( + remote_anisette_v3::RemoteAnisetteProviderV3::new(configuration.anisette_url_v3, configuration.configuration_path.clone()), + ))); + #[cfg(feature = "remote-anisette")] return Ok(AnisetteHeadersProviderRes::remote(Box::new( remote_anisette::RemoteAnisetteProvider::new(configuration.anisette_url), diff --git a/omnisette/src/remote_anisette_v3.rs b/omnisette/src/remote_anisette_v3.rs index 470f0e2..c420437 100644 --- a/omnisette/src/remote_anisette_v3.rs +++ b/omnisette/src/remote_anisette_v3.rs @@ -1,10 +1,11 @@ // Implementing the SideStore Anisette v3 protocol -use std::{collections::HashMap, io::Cursor}; +use std::{collections::HashMap, fs, io::Cursor, path::PathBuf}; use anyhow::{anyhow, Result}; use base64::engine::general_purpose; +use chrono::{DateTime, SubsecRound, Utc}; use log::debug; use plist::{Data, Dictionary}; use reqwest::{Client, ClientBuilder, RequestBuilder}; @@ -146,8 +147,14 @@ pub struct AnisetteData { impl AnisetteData { pub fn get_headers(&self) -> HashMap { - // TODO time headers + let dt: DateTime = Utc::now().round_subsecs(0); + println!("here {}", dt.format("%+").to_string()); + 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(), "0".to_string() /* TODO */), + ("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()), @@ -179,12 +186,16 @@ impl AnisetteClient { } fn build_apple_request(&self, state: &AnisetteState, builder: RequestBuilder) -> RequestBuilder { - // TODO time headers + let dt: DateTime = Utc::now().round_subsecs(0); + builder.header("X-Mme-Client-Info", &self.client_info.client_info) .header("User-Agent", &self.client_info.user_agent) .header("Content-Type", "text/x-xml-plist") .header("X-Apple-I-MD-LU", encode_hex(&state.md_lu())) .header("X-Mme-Device-Id", state.device_id()) + .header("X-Apple-I-Client-Time", dt.format("%+").to_string()) + .header("X-Apple-I-TimeZone", "UTC") + .header("X-Apple-Locale", "en_US") } pub async fn get_headers(&self, state: &AnisetteState) -> Result { @@ -235,7 +246,7 @@ impl AnisetteClient { one_time_password, routing_info, device_description: self.client_info.client_info.clone(), - local_user_id: base64_encode(&state.md_lu()), + local_user_id: encode_hex(&state.md_lu()), device_unique_identifier: state.device_id() }) } @@ -353,20 +364,20 @@ impl AnisetteClient { pub struct RemoteAnisetteProviderV3 { - client: AnisetteClient, - pub state: AnisetteState, + client_url: String, + client: Option, + pub state: Option, + configuration_path: PathBuf } impl RemoteAnisetteProviderV3 { - pub async fn new(url: String, mut state: AnisetteState) -> Result { - let client = AnisetteClient::new(url).await?; - if !state.is_provisioned() { - client.provision(&mut state).await?; + pub fn new(url: String, configuration_path: PathBuf) -> RemoteAnisetteProviderV3 { + RemoteAnisetteProviderV3 { + client_url: url, + client: None, + state: None, + configuration_path } - Ok(RemoteAnisetteProviderV3 { - client, - state - }) } } @@ -376,7 +387,41 @@ impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 { &mut self, _skip_provisioning: bool, ) -> Result> { - let data = self.client.get_headers(&self.state).await?; + if self.client.is_none() { + self.client = Some(AnisetteClient::new(self.client_url.clone()).await?); + } + let client = self.client.as_ref().unwrap(); + + fs::create_dir_all(&self.configuration_path)?; + + let config_path = self.configuration_path.join("state.plist"); + if self.state.is_none() { + self.state = Some(if let Ok(text) = plist::from_file(&config_path) { + text + } else { + AnisetteState::new() + }); + } + + let state = self.state.as_mut().unwrap(); + if !state.is_provisioned() { + client.provision(state).await?; + plist::to_file_xml(&config_path, state)?; + } + let data = match client.get_headers(&state).await { + Ok(data) => data, + Err(err) => { + if let Some(message) = err.downcast_ref::() { + // retry provisioning + if message == "AnisetteNotProvisioned" { + state.adi_pb = None; + client.provision(state).await?; + plist::to_file_xml(config_path, state)?; + client.get_headers(&state).await? + } else { panic!() } + } else { panic!() } + }, + }; Ok(data.get_headers()) } } @@ -384,7 +429,7 @@ impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 { #[cfg(test)] mod tests { use crate::anisette_headers_provider::AnisetteHeadersProvider; - use crate::remote_anisette_v3::{AnisetteState, RemoteAnisetteProviderV3}; + use crate::remote_anisette_v3::RemoteAnisetteProviderV3; use crate::DEFAULT_ANISETTE_URL_V3; use anyhow::Result; use log::info; @@ -393,7 +438,7 @@ mod tests { async fn fetch_anisette_remote_v3() -> Result<()> { crate::tests::init_logger(); - let mut provider = RemoteAnisetteProviderV3::new(DEFAULT_ANISETTE_URL_V3.to_string(), AnisetteState::new()).await?; + let mut provider = RemoteAnisetteProviderV3::new(DEFAULT_ANISETTE_URL_V3.to_string(), "anisette_test".into()); info!( "Remote headers: {:?}", (&mut provider as &mut dyn AnisetteHeadersProvider).get_authentication_headers().await? From 0989820826268bb70a79f8d203dde2c4c873551f Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Mon, 1 Apr 2024 20:46:24 -0600 Subject: [PATCH 02/19] feat(icloud-auth): support sms auth --- icloud-auth/src/client.rs | 116 +++++++++++++++++++++++++++------- icloud-auth/tests/gsa_auth.rs | 13 +--- 2 files changed, 94 insertions(+), 35 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 7d7b15a..16f35bb 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -107,10 +107,30 @@ pub enum LoginResponse { // NeedsSMS2FASent(Send2FAToDevices), NeedsDevice2FA(), Needs2FAVerification(), + NeedsSMS2FA(), + NeedsSMS2FAVerification(VerifyBody), NeedsLogin(), Failed(Error), } +#[derive(Serialize)] +struct VerifyCode { + code: String, +} + +#[derive(Serialize)] +struct PhoneNumber { + id: u32 +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct VerifyBody { + phone_number: PhoneNumber, + mode: String, + security_code: Option +} + // impl Send2FAToDevices { // pub fn send_2fa_to_devices(&self) -> LoginResponse { // self.account.send_2fa_to_devices().unwrap() @@ -279,6 +299,12 @@ impl AppleAccount { LoginResponse::Needs2FAVerification() => { response = _self.verify_2fa(tfa_closure()).await.unwrap() } + LoginResponse::NeedsSMS2FA() => { + response = _self.send_sms_2fa_to_devices().await.unwrap() + } + LoginResponse::NeedsSMS2FAVerification(body) => { + response = _self.verify_sms_2fa(tfa_closure(), body).await.unwrap() + } LoginResponse::NeedsLogin() => { response = _self.login_email_pass(username.clone(), password.clone()).await? } @@ -288,6 +314,11 @@ impl AppleAccount { } } + pub fn get_pet(&self) -> String { + self.spd.as_ref().unwrap().get("t").unwrap().as_dictionary().unwrap().get("com.apple.gs.idms.pet") + .unwrap().as_dictionary().unwrap().get("token").unwrap().as_string().unwrap().to_string() + } + pub async fn login_email_pass( &mut self, username: String, @@ -410,24 +441,14 @@ impl AppleAccount { let status = res.get("Status").unwrap().as_dictionary().unwrap(); - let needs2fa = match status.get("au") { - Some(plist::Value::String(s)) => { - if s == "trustedDeviceSecondaryAuth" { - println!("Trusted device authentication required"); - true - } else { - println!("Unknown auth value {}", s); - // PHONE AUTH WILL CAUSE ERRORS! - false - } - } - _ => false, - }; - self.spd = Some(decoded_spd); - if needs2fa { - return Ok(LoginResponse::NeedsDevice2FA()); + if let Some(plist::Value::String(s)) = status.get("au") { + return match s.as_str() { + "trustedDeviceSecondaryAuth" => Ok(LoginResponse::NeedsDevice2FA()), + "secondaryAuth" => Ok(LoginResponse::NeedsSMS2FA()), + _unk => panic!("Unknown auth value {}", _unk) + } } Ok(LoginResponse::LoggedIn(self.clone().to_owned())) @@ -454,7 +475,7 @@ impl AppleAccount { } pub async fn send_2fa_to_devices(&self) -> Result { - let headers = self.build_2fa_headers(); + let headers = self.build_2fa_headers(false); let res = self .client @@ -468,8 +489,33 @@ impl AppleAccount { return Ok(LoginResponse::Needs2FAVerification()); } + + pub async fn send_sms_2fa_to_devices(&self) -> Result { + let headers = self.build_2fa_headers(true); + + let body = VerifyBody { + phone_number: PhoneNumber { + id: 1 + }, + mode: "sms".to_string(), + security_code: None + }; + + let res = self + .client + .put("https://gsa.apple.com/auth/verify/phone/") + .headers(headers) + .json(&body) + .send().await; + + if !res.as_ref().unwrap().status().is_success() { + return Err(Error::AuthSrp); + } + + return Ok(LoginResponse::NeedsSMS2FAVerification(body)); + } pub async fn verify_2fa(&self, code: String) -> Result { - let headers = self.build_2fa_headers(); + let headers = self.build_2fa_headers(false); println!("Recieved code: {}", code); let res = self .client @@ -492,6 +538,26 @@ impl AppleAccount { Ok(LoginResponse::LoggedIn(self.clone())) } + pub async fn verify_sms_2fa(&self, code: String, mut body: VerifyBody) -> Result { + let headers = self.build_2fa_headers(true); + println!("Recieved code: {}", code); + + body.security_code = Some(VerifyCode { code }); + + let res = self + .client + .post("https://gsa.apple.com/auth/verify/phone/securitycode") + .headers(headers) + .json(&body) + .send().await.unwrap(); + + if res.status() != 200 { + return Err(Error::AuthSrp); + } + + Ok(LoginResponse::LoggedIn(self.clone())) + } + fn check_error(res: &plist::Dictionary) -> Result<(), Error> { let res = match res.get("Status") { Some(plist::Value::Dictionary(d)) => d, @@ -508,7 +574,7 @@ impl AppleAccount { Ok(()) } - fn build_2fa_headers(&self) -> HeaderMap { + fn build_2fa_headers(&self, sms: bool) -> HeaderMap { let spd = self.spd.as_ref().unwrap(); let dsid = spd.get("adsid").unwrap().as_string().unwrap(); let token = spd.get("GsIdmsToken").unwrap().as_string().unwrap(); @@ -526,11 +592,13 @@ impl AppleAccount { ); }); - headers.insert( - "Content-Type", - HeaderValue::from_str("text/x-xml-plist").unwrap(), - ); - headers.insert("Accept", HeaderValue::from_str("text/x-xml-plist").unwrap()); + if !sms { + headers.insert( + "Content-Type", + HeaderValue::from_str("text/x-xml-plist").unwrap(), + ); + headers.insert("Accept", HeaderValue::from_str("text/x-xml-plist").unwrap()); + } headers.insert("User-Agent", HeaderValue::from_str("Xcode").unwrap()); headers.insert("Accept-Language", HeaderValue::from_str("en-us").unwrap()); headers.append( diff --git a/icloud-auth/tests/gsa_auth.rs b/icloud-auth/tests/gsa_auth.rs index 24de02c..a3e0420 100644 --- a/icloud-auth/tests/gsa_auth.rs +++ b/icloud-auth/tests/gsa_auth.rs @@ -29,17 +29,8 @@ mod tests { }; let acc = AppleAccount::login(appleid_closure, tfa_closure).await; - println!("here"); - return; let account = acc.unwrap(); - let spd_plist = account.clone().spd.unwrap(); - // turn plist::dictonary into json - let spd_json = serde_json::to_string(&spd_plist).unwrap(); - - println!("{:?}", spd_json); - - let auth_token = account.clone().get_app_token("com.apple.gs.xcode.auth").await; - println!("auth_token: {:?}", auth_token.unwrap().auth_token); - println!("gsa auth test done"); + println!("PET: {}", account.get_pet()); + return; } } From 645930bb5e0a7442678f68fef5b4493a4735cd2f Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Mon, 1 Apr 2024 21:28:16 -0600 Subject: [PATCH 03/19] feat(icloud-auth): support passing anisette configuration directly, and support passing serial number --- icloud-auth/src/anisette.rs | 8 ++------ icloud-auth/src/client.rs | 10 +++++++--- icloud-auth/tests/gsa_auth.rs | 6 +++++- omnisette/src/lib.rs | 9 ++++++++- omnisette/src/remote_anisette_v3.rs | 16 +++++++++------- 5 files changed, 31 insertions(+), 18 deletions(-) diff --git a/icloud-auth/src/anisette.rs b/icloud-auth/src/anisette.rs index b5e3550..f926e40 100644 --- a/icloud-auth/src/anisette.rs +++ b/icloud-auth/src/anisette.rs @@ -9,12 +9,8 @@ pub struct AnisetteData { impl AnisetteData { /// Fetches the data at an anisette server - pub async fn new() -> Result { - let base_headers = match AnisetteHeaders::get_anisette_headers_provider( - AnisetteConfiguration::new() - .set_configuration_path(PathBuf::new().join("anisette_test")) - .set_anisette_url("https://ani.sidestore.io/".to_string()), - ) { + pub async fn new(config: AnisetteConfiguration) -> Result { + let base_headers = match AnisetteHeaders::get_anisette_headers_provider(config) { Ok(mut b) => match b.provider.get_authentication_headers().await { Ok(b) => b, Err(_) => return Err(Error::ErrorGettingAnisette), diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 16f35bb..1e63f1a 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -5,6 +5,7 @@ use crate::{anisette::AnisetteData, Error}; use aes::cipher::block_padding::Pkcs7; use cbc::cipher::{BlockDecryptMut, KeyIvInit}; use hmac::{Hmac, Mac}; +use omnisette::AnisetteConfiguration; use reqwest::{ Client, ClientBuilder, Response, header::{HeaderMap, HeaderName, HeaderValue}, @@ -155,8 +156,8 @@ async fn parse_response(res: Result) -> plist::Diction } impl AppleAccount { - pub async fn new() -> Self { - let anisette = AnisetteData::new().await; + pub async fn new(config: AnisetteConfiguration) -> Self { + let anisette = AnisetteData::new(config).await; Self::new_with_anisette(anisette.unwrap()) } @@ -178,8 +179,9 @@ impl AppleAccount { pub async fn login( appleid_closure: impl Fn() -> (String, String), tfa_closure: impl Fn() -> String, + config: AnisetteConfiguration, ) -> Result { - let anisette = AnisetteData::new(); + let anisette = AnisetteData::new(config); AppleAccount::login_with_anisette(appleid_closure, tfa_closure, anisette.await.unwrap()).await } @@ -555,6 +557,8 @@ impl AppleAccount { return Err(Error::AuthSrp); } + println!("res {}", res.text().await.unwrap()); + Ok(LoginResponse::LoggedIn(self.clone())) } diff --git a/icloud-auth/tests/gsa_auth.rs b/icloud-auth/tests/gsa_auth.rs index a3e0420..91c4e47 100644 --- a/icloud-auth/tests/gsa_auth.rs +++ b/icloud-auth/tests/gsa_auth.rs @@ -1,6 +1,9 @@ #[cfg(test)] mod tests { + use std::{path::PathBuf, str::FromStr}; + use icloud_auth::*; + use omnisette::AnisetteConfiguration; #[tokio::test] async fn gsa_auth() { @@ -27,7 +30,8 @@ mod tests { std::io::stdin().read_line(&mut input).unwrap(); input.trim().to_string() }; - let acc = AppleAccount::login(appleid_closure, tfa_closure).await; + let acc = AppleAccount::login(appleid_closure, tfa_closure, AnisetteConfiguration::new() + .set_configuration_path(PathBuf::from_str("anisette_test").unwrap())).await; let account = acc.unwrap(); println!("PET: {}", account.get_pet()); diff --git a/omnisette/src/lib.rs b/omnisette/src/lib.rs index bd83db9..375789c 100644 --- a/omnisette/src/lib.rs +++ b/omnisette/src/lib.rs @@ -52,6 +52,7 @@ pub struct AnisetteConfiguration { anisette_url: String, anisette_url_v3: String, configuration_path: PathBuf, + macos_serial: String, } impl Default for AnisetteConfiguration { @@ -66,6 +67,7 @@ impl AnisetteConfiguration { anisette_url: DEFAULT_ANISETTE_URL.to_string(), anisette_url_v3: DEFAULT_ANISETTE_URL_V3.to_string(), configuration_path: PathBuf::new(), + macos_serial: "0".to_string() } } @@ -82,6 +84,11 @@ impl AnisetteConfiguration { self } + pub fn set_macos_serial(mut self, macos_serial: String) -> AnisetteConfiguration { + self.macos_serial = macos_serial; + self + } + pub fn set_configuration_path(mut self, configuration_path: PathBuf) -> AnisetteConfiguration { self.configuration_path = configuration_path; self @@ -132,7 +139,7 @@ impl AnisetteHeaders { #[cfg(feature = "remote-anisette-v3")] return Ok(AnisetteHeadersProviderRes::remote(Box::new( - remote_anisette_v3::RemoteAnisetteProviderV3::new(configuration.anisette_url_v3, configuration.configuration_path.clone()), + remote_anisette_v3::RemoteAnisetteProviderV3::new(configuration.anisette_url_v3, configuration.configuration_path.clone(), configuration.macos_serial.clone()), ))); #[cfg(feature = "remote-anisette")] diff --git a/omnisette/src/remote_anisette_v3.rs b/omnisette/src/remote_anisette_v3.rs index c420437..8ad25de 100644 --- a/omnisette/src/remote_anisette_v3.rs +++ b/omnisette/src/remote_anisette_v3.rs @@ -146,13 +146,13 @@ pub struct AnisetteData { } impl AnisetteData { - pub fn get_headers(&self) -> HashMap { + pub fn get_headers(&self, serial: String) -> HashMap { let dt: DateTime = Utc::now().round_subsecs(0); println!("here {}", dt.format("%+").to_string()); 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(), "0".to_string() /* TODO */), + ("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()), @@ -367,16 +367,18 @@ pub struct RemoteAnisetteProviderV3 { client_url: String, client: Option, pub state: Option, - configuration_path: PathBuf + configuration_path: PathBuf, + serial: String } impl RemoteAnisetteProviderV3 { - pub fn new(url: String, configuration_path: PathBuf) -> RemoteAnisetteProviderV3 { + pub fn new(url: String, configuration_path: PathBuf, serial: String) -> RemoteAnisetteProviderV3 { RemoteAnisetteProviderV3 { client_url: url, client: None, state: None, - configuration_path + configuration_path, + serial } } } @@ -422,7 +424,7 @@ impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 { } else { panic!() } }, }; - Ok(data.get_headers()) + Ok(data.get_headers(self.serial.clone())) } } @@ -438,7 +440,7 @@ mod tests { async fn fetch_anisette_remote_v3() -> Result<()> { crate::tests::init_logger(); - let mut provider = RemoteAnisetteProviderV3::new(DEFAULT_ANISETTE_URL_V3.to_string(), "anisette_test".into()); + let mut provider = RemoteAnisetteProviderV3::new(DEFAULT_ANISETTE_URL_V3.to_string(), "anisette_test".into(), "0".to_string()); info!( "Remote headers: {:?}", (&mut provider as &mut dyn AnisetteHeadersProvider).get_authentication_headers().await? From 5a36972eaf634c4043d8b854e43cfcab945da350 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Mon, 1 Apr 2024 21:35:57 -0600 Subject: [PATCH 04/19] fix(icloud-auth): Re-login after 2fa to get account data including PET --- icloud-auth/src/client.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 1e63f1a..7f99f4d 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -537,7 +537,7 @@ impl AppleAccount { return Err(err_check.err().unwrap()); } - Ok(LoginResponse::LoggedIn(self.clone())) + Ok(LoginResponse::NeedsLogin()) } pub async fn verify_sms_2fa(&self, code: String, mut body: VerifyBody) -> Result { @@ -557,9 +557,7 @@ impl AppleAccount { return Err(Error::AuthSrp); } - println!("res {}", res.text().await.unwrap()); - - Ok(LoginResponse::LoggedIn(self.clone())) + Ok(LoginResponse::NeedsLogin()) } fn check_error(res: &plist::Dictionary) -> Result<(), Error> { From fd9c0352225187ac9928dab7e84677c670feacc9 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Tue, 2 Apr 2024 13:44:08 -0600 Subject: [PATCH 05/19] feat(icloud-auth): support number retrieval, header access, and extra step exposure --- icloud-auth/src/client.rs | 45 ++++++++++++++++++++++++++++++++++----- icloud-auth/src/lib.rs | 1 + 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 7f99f4d..5e52e9c 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -110,6 +110,7 @@ pub enum LoginResponse { Needs2FAVerification(), NeedsSMS2FA(), NeedsSMS2FAVerification(VerifyBody), + NeedsExtraStep(String), NeedsLogin(), Failed(Error), } @@ -132,6 +133,27 @@ pub struct VerifyBody { security_code: Option } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TrustedPhoneNumber { + pub number_with_dial_code: String, + pub last_two_digits: String, + pub push_mode: String, + pub id: u32 +} + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthenticationExtras { + pub trusted_phone_numbers: Vec, + pub recovery_url: Option, + pub cant_use_phone_number_url: Option, + pub dont_have_access_url: Option, + pub recovery_web_url: Option, + pub repair_phone_number_url: Option, + pub repair_phone_number_web_url: Option, +} + // impl Send2FAToDevices { // pub fn send_2fa_to_devices(&self) -> LoginResponse { // self.account.send_2fa_to_devices().unwrap() @@ -302,7 +324,7 @@ impl AppleAccount { response = _self.verify_2fa(tfa_closure()).await.unwrap() } LoginResponse::NeedsSMS2FA() => { - response = _self.send_sms_2fa_to_devices().await.unwrap() + response = _self.send_sms_2fa_to_devices(1).await.unwrap() } LoginResponse::NeedsSMS2FAVerification(body) => { response = _self.verify_sms_2fa(tfa_closure(), body).await.unwrap() @@ -312,6 +334,7 @@ impl AppleAccount { } LoginResponse::LoggedIn(ac) => return Ok(ac), LoginResponse::Failed(e) => return Err(e), + LoginResponse::NeedsExtraStep(step) => return Err(Error::ExtraStep(step)) } } } @@ -449,7 +472,7 @@ impl AppleAccount { return match s.as_str() { "trustedDeviceSecondaryAuth" => Ok(LoginResponse::NeedsDevice2FA()), "secondaryAuth" => Ok(LoginResponse::NeedsSMS2FA()), - _unk => panic!("Unknown auth value {}", _unk) + _unk => Ok(LoginResponse::NeedsExtraStep(_unk.to_string())) } } @@ -492,12 +515,12 @@ impl AppleAccount { return Ok(LoginResponse::Needs2FAVerification()); } - pub async fn send_sms_2fa_to_devices(&self) -> Result { + pub async fn send_sms_2fa_to_devices(&self, phone_id: u32) -> Result { let headers = self.build_2fa_headers(true); let body = VerifyBody { phone_number: PhoneNumber { - id: 1 + id: phone_id }, mode: "sms".to_string(), security_code: None @@ -516,6 +539,18 @@ impl AppleAccount { return Ok(LoginResponse::NeedsSMS2FAVerification(body)); } + + pub async fn get_auth_extras(&self) -> Result { + let headers = self.build_2fa_headers(true); + + Ok(self.client + .get("https://gsa.apple.com/auth") + .headers(headers) + .header("Accept", "application/json") + .send().await.unwrap() + .json::().await.unwrap()) + } + pub async fn verify_2fa(&self, code: String) -> Result { let headers = self.build_2fa_headers(false); println!("Recieved code: {}", code); @@ -576,7 +611,7 @@ impl AppleAccount { Ok(()) } - fn build_2fa_headers(&self, sms: bool) -> HeaderMap { + pub fn build_2fa_headers(&self, sms: bool) -> HeaderMap { let spd = self.spd.as_ref().unwrap(); let dsid = spd.get("adsid").unwrap().as_string().unwrap(); let token = spd.get("GsIdmsToken").unwrap().as_string().unwrap(); diff --git a/icloud-auth/src/lib.rs b/icloud-auth/src/lib.rs index 7422929..3525159 100644 --- a/icloud-auth/src/lib.rs +++ b/icloud-auth/src/lib.rs @@ -8,4 +8,5 @@ pub enum Error { ErrorGettingAnisette, AuthSrp, AuthSrpWithMessage(i64, String), + ExtraStep(String), } From b87bda0f1fffacdef92f68dfd4b0f3a6f7796288 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Tue, 2 Apr 2024 14:38:48 -0600 Subject: [PATCH 06/19] feat: convert to `thiserror`and fix error handling --- icloud-auth/Cargo.toml | 1 + icloud-auth/src/anisette.rs | 14 +--- icloud-auth/src/client.rs | 77 ++++++++++------------ icloud-auth/src/lib.rs | 17 ++++- omnisette/Cargo.toml | 3 +- omnisette/src/adi_proxy.rs | 43 ++++++------ omnisette/src/anisette_headers_provider.rs | 8 ++- omnisette/src/lib.rs | 33 ++++++---- omnisette/src/remote_anisette.rs | 8 +-- omnisette/src/remote_anisette_v3.rs | 35 +++++----- omnisette/src/store_services_core.rs | 60 +++++++---------- 11 files changed, 147 insertions(+), 152 deletions(-) diff --git a/icloud-auth/Cargo.toml b/icloud-auth/Cargo.toml index bccadec..4e3bc35 100644 --- a/icloud-auth/Cargo.toml +++ b/icloud-auth/Cargo.toml @@ -23,6 +23,7 @@ aes = "0.8.2" pkcs7 = "0.3.0" reqwest = { version = "0.11.14", features = ["blocking", "json", "default-tls"] } omnisette = {path = "../omnisette", features = ["remote-anisette-v3"]} +thiserror = "1.0.58" [dev-dependencies] tokio = { version = "1", features = ["rt", "macros"] } diff --git a/icloud-auth/src/anisette.rs b/icloud-auth/src/anisette.rs index f926e40..1055101 100644 --- a/icloud-auth/src/anisette.rs +++ b/icloud-auth/src/anisette.rs @@ -1,6 +1,6 @@ use crate::Error; use omnisette::{AnisetteConfiguration, AnisetteHeaders}; -use std::{collections::HashMap, path::PathBuf}; +use std::collections::HashMap; #[derive(Debug, Clone)] pub struct AnisetteData { @@ -10,15 +10,8 @@ pub struct AnisetteData { impl AnisetteData { /// Fetches the data at an anisette server pub async fn new(config: AnisetteConfiguration) -> Result { - let base_headers = match AnisetteHeaders::get_anisette_headers_provider(config) { - Ok(mut b) => match b.provider.get_authentication_headers().await { - Ok(b) => b, - Err(_) => return Err(Error::ErrorGettingAnisette), - }, - Err(_) => { - return Err(Error::HttpRequest); - } - }; + let mut b = AnisetteHeaders::get_anisette_headers_provider(config)?; + let base_headers = b.provider.get_authentication_headers().await?; Ok(AnisetteData { base_headers }) } @@ -30,7 +23,6 @@ impl AnisetteData { app_info: bool, ) -> HashMap { let mut headers = self.base_headers.clone(); - println!("headers {:?}", headers); let old_client_info = headers.remove("X-Mme-Client-Info"); if client_info { let client_info = match old_client_info { diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 5e52e9c..cb6cdd8 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -166,36 +166,34 @@ pub struct AuthenticationExtras { // } // } -async fn parse_response(res: Result) -> plist::Dictionary { - let res = res.unwrap().text().await.unwrap(); - println!("{:?}", res); - let res: plist::Dictionary = plist::from_bytes(res.as_bytes()).unwrap(); +async fn parse_response(res: Result) -> Result { + let res = res?.text().await?; + let res: plist::Dictionary = plist::from_bytes(res.as_bytes())?; let res: plist::Value = res.get("Response").unwrap().to_owned(); match res { - plist::Value::Dictionary(dict) => dict, - _ => panic!("Invalid response"), + plist::Value::Dictionary(dict) => Ok(dict), + _ => Err(crate::Error::Parse), } } impl AppleAccount { - pub async fn new(config: AnisetteConfiguration) -> Self { - let anisette = AnisetteData::new(config).await; - Self::new_with_anisette(anisette.unwrap()) + pub async fn new(config: AnisetteConfiguration) -> Result { + let anisette = AnisetteData::new(config).await?; + Ok(Self::new_with_anisette(anisette)?) } - pub fn new_with_anisette(anisette: AnisetteData) -> Self { + pub fn new_with_anisette(anisette: AnisetteData) -> Result { let client = ClientBuilder::new() - .add_root_certificate(Certificate::from_der(APPLE_ROOT).unwrap()) + .add_root_certificate(Certificate::from_der(APPLE_ROOT)?) .http1_title_case_headers() .connection_verbose(true) - .build() - .unwrap(); + .build()?; - AppleAccount { + Ok(AppleAccount { client, anisette, spd: None, - } + }) } pub async fn login( @@ -203,8 +201,8 @@ impl AppleAccount { tfa_closure: impl Fn() -> String, config: AnisetteConfiguration, ) -> Result { - let anisette = AnisetteData::new(config); - AppleAccount::login_with_anisette(appleid_closure, tfa_closure, anisette.await.unwrap()).await + let anisette = AnisetteData::new(config).await?; + AppleAccount::login_with_anisette(appleid_closure, tfa_closure, anisette).await } pub async fn get_app_token(&self, app_name: &str) -> Result { @@ -257,7 +255,7 @@ impl AppleAccount { }; let mut buffer = Vec::new(); - plist::to_writer_xml(&mut buffer, &packet).unwrap(); + plist::to_writer_xml(&mut buffer, &packet)?; let buffer = String::from_utf8(buffer).unwrap(); println!("{:?}", gsa_headers.clone()); @@ -269,7 +267,7 @@ impl AppleAccount { .headers(gsa_headers.clone()) .body(buffer) .send().await; - let res = parse_response(res).await; + let res = parse_response(res).await?; let err_check = Self::check_error(&res); if err_check.is_err() { return Err(err_check.err().unwrap()); @@ -314,20 +312,20 @@ impl AppleAccount { tfa_closure: G, anisette: AnisetteData, ) -> Result { - let mut _self = AppleAccount::new_with_anisette(anisette); + let mut _self = AppleAccount::new_with_anisette(anisette)?; let (username, password) = appleid_closure(); let mut response = _self.login_email_pass(username.clone(), password.clone()).await?; loop { match response { - LoginResponse::NeedsDevice2FA() => response = _self.send_2fa_to_devices().await.unwrap(), + LoginResponse::NeedsDevice2FA() => response = _self.send_2fa_to_devices().await?, LoginResponse::Needs2FAVerification() => { - response = _self.verify_2fa(tfa_closure()).await.unwrap() + response = _self.verify_2fa(tfa_closure()).await? } LoginResponse::NeedsSMS2FA() => { - response = _self.send_sms_2fa_to_devices(1).await.unwrap() + response = _self.send_sms_2fa_to_devices(1).await? } LoginResponse::NeedsSMS2FAVerification(body) => { - response = _self.verify_sms_2fa(tfa_closure(), body).await.unwrap() + response = _self.verify_sms_2fa(tfa_closure(), body).await? } LoginResponse::NeedsLogin() => { response = _self.login_email_pass(username.clone(), password.clone()).await? @@ -385,7 +383,7 @@ impl AppleAccount { }; let mut buffer = Vec::new(); - plist::to_writer_xml(&mut buffer, &packet).unwrap(); + plist::to_writer_xml(&mut buffer, &packet)?; let buffer = String::from_utf8(buffer).unwrap(); println!("{:?}", gsa_headers.clone()); @@ -398,7 +396,7 @@ impl AppleAccount { .body(buffer) .send().await; - let res = parse_response(res).await; + let res = parse_response(res).await?; let err_check = Self::check_error(&res); if err_check.is_err() { return Err(err_check.err().unwrap()); @@ -441,7 +439,7 @@ impl AppleAccount { }; let mut buffer = Vec::new(); - plist::to_writer_xml(&mut buffer, &packet).unwrap(); + plist::to_writer_xml(&mut buffer, &packet)?; let buffer = String::from_utf8(buffer).unwrap(); let res = self @@ -451,7 +449,7 @@ impl AppleAccount { .body(buffer) .send().await; - let res = parse_response(res).await; + let res = parse_response(res).await?; let err_check = Self::check_error(&res); if err_check.is_err() { return Err(err_check.err().unwrap()); @@ -506,9 +504,9 @@ impl AppleAccount { .client .get("https://gsa.apple.com/auth/verify/trusteddevice") .headers(headers) - .send().await; + .send().await?; - if !res.as_ref().unwrap().status().is_success() { + if !res.status().is_success() { return Err(Error::AuthSrp); } @@ -531,9 +529,9 @@ impl AppleAccount { .put("https://gsa.apple.com/auth/verify/phone/") .headers(headers) .json(&body) - .send().await; + .send().await?; - if !res.as_ref().unwrap().status().is_success() { + if !res.status().is_success() { return Err(Error::AuthSrp); } @@ -547,8 +545,8 @@ impl AppleAccount { .get("https://gsa.apple.com/auth") .headers(headers) .header("Accept", "application/json") - .send().await.unwrap() - .json::().await.unwrap()) + .send().await? + .json::().await?) } pub async fn verify_2fa(&self, code: String) -> Result { @@ -562,15 +560,12 @@ impl AppleAccount { HeaderName::from_str("security-code").unwrap(), HeaderValue::from_str(&code).unwrap(), ) - .send().await; + .send().await?; let res: plist::Dictionary = - plist::from_bytes(res.unwrap().text().await.unwrap().as_bytes()).unwrap(); + plist::from_bytes(res.text().await?.as_bytes())?; - let err_check = Self::check_error(&res); - if err_check.is_err() { - return Err(err_check.err().unwrap()); - } + Self::check_error(&res)?; Ok(LoginResponse::NeedsLogin()) } @@ -586,7 +581,7 @@ impl AppleAccount { .post("https://gsa.apple.com/auth/verify/phone/securitycode") .headers(headers) .json(&body) - .send().await.unwrap(); + .send().await?; if res.status() != 200 { return Err(Error::AuthSrp); diff --git a/icloud-auth/src/lib.rs b/icloud-auth/src/lib.rs index 3525159..e02bde7 100644 --- a/icloud-auth/src/lib.rs +++ b/icloud-auth/src/lib.rs @@ -1,12 +1,23 @@ pub mod anisette; mod client; +use std::fmt::Display; + pub use client::AppleAccount; -#[derive(Debug)] + +use thiserror::Error; +#[derive(Debug, Error)] pub enum Error { - HttpRequest, Parse, - ErrorGettingAnisette, AuthSrp, AuthSrpWithMessage(i64, String), ExtraStep(String), + PlistError(#[from] plist::Error), + ReqwestError(#[from] reqwest::Error), + ErrorGettingAnisette(#[from] omnisette::AnisetteError) +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", format!("{:?}", self)) + } } diff --git a/omnisette/Cargo.toml b/omnisette/Cargo.toml index a19de32..b954a6b 100644 --- a/omnisette/Cargo.toml +++ b/omnisette/Cargo.toml @@ -10,7 +10,6 @@ default = ["remote-anisette", "dep:remove-async-await"] remote-anisette-v3 = ["async", "dep:serde", "dep:serde_json", "dep:tokio-tungstenite", "dep:futures-util", "dep:chrono"] [dependencies] -anyhow = "1.0" base64 = "0.21" hex = "0.4" plist = "1.4" @@ -28,6 +27,8 @@ serde_json = { version = "1.0.115", optional = true } tokio-tungstenite = { version = "0.20.1", optional = true, features = ["rustls-tls-native-roots"] } futures-util = { version = "0.3.28", optional = true } chrono = { version = "0.4.37", optional = true } +thiserror = "1.0.58" +anyhow = "1.0.81" [target.'cfg(target_os = "macos")'.dependencies] dlopen2 = "0.4" diff --git a/omnisette/src/adi_proxy.rs b/omnisette/src/adi_proxy.rs index 298d652..bd38a2c 100644 --- a/omnisette/src/adi_proxy.rs +++ b/omnisette/src/adi_proxy.rs @@ -1,6 +1,6 @@ use crate::adi_proxy::ProvisioningError::InvalidResponse; use crate::anisette_headers_provider::AnisetteHeadersProvider; -use anyhow::Result; +use crate::AnisetteError; use base64::engine::general_purpose::STANDARD as base64_engine; use base64::Engine; use log::debug; @@ -8,15 +8,15 @@ use plist::{Dictionary, Value}; use rand::RngCore; #[cfg(not(feature = "async"))] use reqwest::blocking::{Client, ClientBuilder, Response}; -use reqwest::header::{HeaderMap, HeaderValue}; +use reqwest::header::{HeaderMap, HeaderValue, InvalidHeaderValue}; #[cfg(feature = "async")] use reqwest::{Client, ClientBuilder, Response}; use sha2::{Digest, Sha256}; use std::collections::HashMap; -use std::error::Error; use std::fmt::{Display, Formatter}; -use std::io::{Read, Write}; +use std::io::{self, Read, Write}; use std::path::PathBuf; +use thiserror::Error; #[derive(Debug)] pub struct ServerError { @@ -38,10 +38,15 @@ impl std::fmt::Display for ProvisioningError { impl std::error::Error for ProvisioningError {} -#[derive(Debug)] +#[derive(Debug, Error)] pub enum ADIError { Unknown(i32), - ProvisioningError(ProvisioningError), + ProvisioningError(#[from] ProvisioningError), + PlistError(#[from] plist::Error), + ReqwestError(#[from] reqwest::Error), + Base64Error(#[from] base64::DecodeError), + InvalidHeaderValue(#[from] InvalidHeaderValue), + IOError(#[from] io::Error) } impl ADIError { @@ -53,13 +58,13 @@ impl ADIError { #[cfg_attr(feature = "async", async_trait::async_trait)] trait ToPlist { #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] - async fn plist(self) -> Result; + async fn plist(self) -> Result; } #[cfg_attr(feature = "async", async_trait::async_trait)] impl ToPlist for Response { #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] - async fn plist(self) -> Result { + async fn plist(self) -> Result { if let Ok(property_list) = Value::from_reader_xml(&*self.bytes().await?) { Ok(property_list.as_dictionary().unwrap().to_owned()) } else { @@ -74,8 +79,6 @@ impl Display for ADIError { } } -impl Error for ADIError {} - pub struct SynchronizeData { pub mid: Vec, pub srm: Vec, @@ -106,7 +109,7 @@ pub trait ADIProxy { fn request_otp(&self, ds_id: i64) -> Result; fn set_local_user_uuid(&mut self, local_user_uuid: String); - fn set_device_identifier(&mut self, device_identifier: String) -> Result<()>; + fn set_device_identifier(&mut self, device_identifier: String) -> Result<(), ADIError>; fn get_local_user_uuid(&self) -> String; fn get_device_identifier(&self) -> String; @@ -126,12 +129,12 @@ pub const IDENTIFIER_LENGTH: usize = 16; pub type Identifier = [u8; IDENTIFIER_LENGTH]; trait AppleRequestResult { - fn check_status(&self) -> Result<()>; - fn get_response(&self) -> Result<&Dictionary>; + fn check_status(&self) -> Result<(), ADIError>; + fn get_response(&self) -> Result<&Dictionary, ADIError>; } impl AppleRequestResult for Dictionary { - fn check_status(&self) -> Result<()> { + fn check_status(&self) -> Result<(), ADIError> { let status = self .get("Status") .ok_or(InvalidResponse)? @@ -146,7 +149,7 @@ impl AppleRequestResult for Dictionary { } } - fn get_response(&self) -> Result<&Dictionary> { + fn get_response(&self) -> Result<&Dictionary, ADIError> { if let Some(response) = self.get("Response") { let response = response.as_dictionary().unwrap(); response.check_status()?; @@ -158,7 +161,7 @@ impl AppleRequestResult for Dictionary { } impl dyn ADIProxy { - fn make_http_client(&mut self) -> Result { + fn make_http_client(&mut self) -> Result { let mut headers = HeaderMap::new(); headers.insert("Content-Type", HeaderValue::from_str("text/x-xml-plist")?); @@ -192,7 +195,7 @@ impl dyn ADIProxy { } #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] - async fn provision_device(&mut self) -> Result<()> { + async fn provision_device(&mut self) -> Result<(), ADIError> { let client = self.make_http_client()?; let url_bag_res = client @@ -288,14 +291,14 @@ pub struct ADIProxyAnisetteProvider { impl ADIProxyAnisetteProvider { /// If you use this method, you are expected to set the identifier yourself. - pub fn without_identifier(adi_proxy: ProxyType) -> Result> { + pub fn without_identifier(adi_proxy: ProxyType) -> Result, ADIError> { Ok(ADIProxyAnisetteProvider { adi_proxy }) } pub fn new( mut adi_proxy: ProxyType, configuration_path: PathBuf, - ) -> Result> { + ) -> Result, ADIError> { let identifier_file_path = configuration_path.join("identifier"); let mut identifier_file = std::fs::OpenOptions::new() .create(true) @@ -337,7 +340,7 @@ impl AnisetteHeadersProvider async fn get_anisette_headers( &mut self, skip_provisioning: bool, - ) -> Result> { + ) -> Result, AnisetteError> { let adi_proxy = &mut self.adi_proxy as &mut dyn ADIProxy; if !adi_proxy.is_machine_provisioned(DS_ID) && !skip_provisioning { diff --git a/omnisette/src/anisette_headers_provider.rs b/omnisette/src/anisette_headers_provider.rs index 372ee4e..fc4c60d 100644 --- a/omnisette/src/anisette_headers_provider.rs +++ b/omnisette/src/anisette_headers_provider.rs @@ -1,16 +1,18 @@ -use anyhow::Result; + use std::collections::HashMap; +use crate::AnisetteError; + #[cfg_attr(feature = "async", async_trait::async_trait(?Send))] pub trait AnisetteHeadersProvider { #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] async fn get_anisette_headers( &mut self, skip_provisioning: bool, - ) -> Result>; + ) -> Result, AnisetteError>; #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] - async fn get_authentication_headers(&mut self) -> Result> { + async fn get_authentication_headers(&mut self) -> Result, AnisetteError> { let headers = self.get_anisette_headers(false).await?; Ok(self.normalize_headers(headers)) } diff --git a/omnisette/src/lib.rs b/omnisette/src/lib.rs index 375789c..31013fd 100644 --- a/omnisette/src/lib.rs +++ b/omnisette/src/lib.rs @@ -6,10 +6,11 @@ use crate::adi_proxy::{ADIProxyAnisetteProvider, ConfigurableADIProxy}; use crate::anisette_headers_provider::AnisetteHeadersProvider; -use crate::remote_anisette_v3::AnisetteState; -use anyhow::Result; use std::fmt::Formatter; +use std::io; use std::path::PathBuf; +use adi_proxy::ADIError; +use thiserror::Error; pub mod adi_proxy; pub mod anisette_headers_provider; @@ -28,21 +29,32 @@ pub mod remote_anisette; pub struct AnisetteHeaders; #[allow(dead_code)] -#[derive(Debug)] -enum AnisetteMetaError { +#[derive(Debug, Error)] +pub enum AnisetteError { #[allow(dead_code)] UnsupportedDevice, InvalidArgument(String), + AnisetteNotProvisioned, + PlistError(#[from] plist::Error), + ReqwestError(#[from] reqwest::Error), + #[cfg(feature = "remote-anisette-v3")] + WsError(#[from] tokio_tungstenite::tungstenite::error::Error), + #[cfg(feature = "remote-anisette-v3")] + SerdeError(#[from] serde_json::Error), + IOError(#[from] io::Error), + ADIError(#[from] ADIError), + InvalidLibraryFormat, + Misc, + MissingLibraries, + Anyhow(#[from] anyhow::Error) } -impl std::fmt::Display for AnisetteMetaError { +impl std::fmt::Display for AnisetteError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "AnisetteMetaError::{self:?}") } } -impl std::error::Error for AnisetteMetaError {} - pub const DEFAULT_ANISETTE_URL: &str = "https://ani.f1sh.me/"; pub const DEFAULT_ANISETTE_URL_V3: &str = "https://ani.sidestore.io"; @@ -124,7 +136,7 @@ impl AnisetteHeadersProviderRes { impl AnisetteHeaders { pub fn get_anisette_headers_provider( configuration: AnisetteConfiguration, - ) -> Result { + ) -> Result { #[cfg(target_os = "macos")] if let Ok(prov) = aos_kit::AOSKitAnisetteProvider::new() { return Ok(AnisetteHeadersProviderRes::local(Box::new(prov))); @@ -153,13 +165,13 @@ impl AnisetteHeaders { pub fn get_ssc_anisette_headers_provider( configuration: AnisetteConfiguration, - ) -> Result { + ) -> Result { let mut ssc_adi_proxy = store_services_core::StoreServicesCoreADIProxy::new( configuration.configuration_path(), )?; let config_path = configuration.configuration_path(); ssc_adi_proxy.set_provisioning_path(config_path.to_str().ok_or( - AnisetteMetaError::InvalidArgument("configuration.configuration_path".to_string()), + AnisetteError::InvalidArgument("configuration.configuration_path".to_string()), )?)?; Ok(AnisetteHeadersProviderRes::local(Box::new( ADIProxyAnisetteProvider::new(ssc_adi_proxy, config_path.to_path_buf())?, @@ -169,7 +181,6 @@ impl AnisetteHeaders { #[cfg(test)] mod tests { - use anyhow::Result; use log::LevelFilter; use simplelog::{ColorChoice, ConfigBuilder, TermLogger, TerminalMode}; diff --git a/omnisette/src/remote_anisette.rs b/omnisette/src/remote_anisette.rs index 0973f46..cf5098d 100644 --- a/omnisette/src/remote_anisette.rs +++ b/omnisette/src/remote_anisette.rs @@ -1,5 +1,4 @@ -use crate::anisette_headers_provider::AnisetteHeadersProvider; -use anyhow::Result; +use crate::{anisette_headers_provider::AnisetteHeadersProvider, AnisetteError}; #[cfg(not(feature = "async"))] use reqwest::blocking::get; #[cfg(feature = "async")] @@ -22,7 +21,7 @@ impl AnisetteHeadersProvider for RemoteAnisetteProvider { async fn get_anisette_headers( &mut self, _skip_provisioning: bool, - ) -> Result> { + ) -> Result, AnisetteError> { Ok(get(&self.url).await?.json().await?) } } @@ -32,11 +31,10 @@ mod tests { use crate::anisette_headers_provider::AnisetteHeadersProvider; use crate::remote_anisette::RemoteAnisetteProvider; use crate::DEFAULT_ANISETTE_URL; - use anyhow::Result; use log::info; #[test] - fn fetch_anisette_remote() -> Result<()> { + fn fetch_anisette_remote() -> Result<(), AnisetteError> { crate::tests::init_logger(); let mut provider = RemoteAnisetteProvider::new(DEFAULT_ANISETTE_URL.to_string()); diff --git a/omnisette/src/remote_anisette_v3.rs b/omnisette/src/remote_anisette_v3.rs index 8ad25de..8b2b123 100644 --- a/omnisette/src/remote_anisette_v3.rs +++ b/omnisette/src/remote_anisette_v3.rs @@ -3,7 +3,6 @@ use std::{collections::HashMap, fs, io::Cursor, path::PathBuf}; -use anyhow::{anyhow, Result}; use base64::engine::general_purpose; use chrono::{DateTime, SubsecRound, Utc}; use log::debug; @@ -19,7 +18,7 @@ use std::fmt::Write; use base64::Engine; use async_trait::async_trait; -use crate::anisette_headers_provider::AnisetteHeadersProvider; +use crate::{anisette_headers_provider::AnisetteHeadersProvider, AnisetteError}; fn plist_to_string(value: &T) -> Result { @@ -165,7 +164,7 @@ impl AnisetteData { } } -fn make_reqwest() -> Result { +fn make_reqwest() -> Result { Ok(ClientBuilder::new() .http1_title_case_headers() .danger_accept_invalid_certs(true) // TODO: pin the apple certificate @@ -173,7 +172,7 @@ fn make_reqwest() -> Result { } impl AnisetteClient { - pub async fn new(url: String) -> Result { + pub async fn new(url: String) -> Result { let path = format!("{}/v3/client_info", url); let http_client = make_reqwest()?; let client_info = http_client.get(path) @@ -198,7 +197,7 @@ impl AnisetteClient { .header("X-Apple-Locale", "en_US") } - pub async fn get_headers(&self, state: &AnisetteState) -> Result { + pub async fn get_headers(&self, state: &AnisetteState) -> Result { let path = format!("{}/v3/get_headers", self.url); let http_client = make_reqwest()?; @@ -209,7 +208,7 @@ impl AnisetteClient { } let body = GetHeadersBody { identifier: base64_encode(&state.keychain_identifier), - adi_pb: base64_encode(state.adi_pb.as_ref().ok_or(anyhow!("AnisetteNotProvisioned"))?), + adi_pb: base64_encode(state.adi_pb.as_ref().ok_or(AnisetteError::AnisetteNotProvisioned)?), }; #[derive(Deserialize)] @@ -235,7 +234,7 @@ impl AnisetteClient { match headers { AnisetteHeaders::GetHeadersError { message } => { if message.contains("-45061") { - Err(anyhow!("AnisetteNotProvisioned")) + Err(AnisetteError::AnisetteNotProvisioned) } else { panic!("Unknown error {}", message) } @@ -253,7 +252,7 @@ impl AnisetteClient { } } - pub async fn provision(&self, state: &mut AnisetteState) -> Result<()> { + pub async fn provision(&self, state: &mut AnisetteState) -> Result<(), AnisetteError> { debug!("Provisioning Anisette"); let http_client = make_reqwest()?; let resp = self.build_apple_request(&state, http_client.get("https://gsa.apple.com/grandslam/GsService2/lookup")) @@ -388,7 +387,7 @@ impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 { async fn get_anisette_headers( &mut self, _skip_provisioning: bool, - ) -> Result> { + ) -> Result, AnisetteError> { if self.client.is_none() { self.client = Some(AnisetteClient::new(self.client_url.clone()).await?); } @@ -413,14 +412,11 @@ impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 { let data = match client.get_headers(&state).await { Ok(data) => data, Err(err) => { - if let Some(message) = err.downcast_ref::() { - // retry provisioning - if message == "AnisetteNotProvisioned" { - state.adi_pb = None; - client.provision(state).await?; - plist::to_file_xml(config_path, state)?; - client.get_headers(&state).await? - } else { panic!() } + if matches!(err, AnisetteError::AnisetteNotProvisioned) { + state.adi_pb = None; + client.provision(state).await?; + plist::to_file_xml(config_path, state)?; + client.get_headers(&state).await? } else { panic!() } }, }; @@ -432,12 +428,11 @@ impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 { mod tests { use crate::anisette_headers_provider::AnisetteHeadersProvider; use crate::remote_anisette_v3::RemoteAnisetteProviderV3; - use crate::DEFAULT_ANISETTE_URL_V3; - use anyhow::Result; + use crate::{AnisetteError, DEFAULT_ANISETTE_URL_V3}; use log::info; #[tokio::test] - async fn fetch_anisette_remote_v3() -> Result<()> { + async fn fetch_anisette_remote_v3() -> Result<(), AnisetteError> { crate::tests::init_logger(); let mut provider = RemoteAnisetteProviderV3::new(DEFAULT_ANISETTE_URL_V3.to_string(), "anisette_test".into(), "0".to_string()); diff --git a/omnisette/src/store_services_core.rs b/omnisette/src/store_services_core.rs index cea369c..5782c03 100644 --- a/omnisette/src/store_services_core.rs +++ b/omnisette/src/store_services_core.rs @@ -7,11 +7,11 @@ use crate::adi_proxy::{ ADIError, ADIProxy, ConfigurableADIProxy, RequestOTPData, StartProvisioningData, SynchronizeData, }; +use crate::AnisetteError; use android_loader::android_library::AndroidLibrary; use android_loader::sysv64_type; use android_loader::{hook_manager, sysv64}; -use anyhow::Result; use std::collections::HashMap; use std::ffi::{c_char, CString}; use std::path::PathBuf; @@ -66,18 +66,18 @@ pub struct StoreServicesCoreADIProxy<'lt> { } impl StoreServicesCoreADIProxy<'_> { - pub fn new<'lt>(library_path: &PathBuf) -> Result> { + pub fn new<'lt>(library_path: &PathBuf) -> Result, AnisetteError> { Self::with_custom_provisioning_path(library_path, library_path) } - pub fn with_custom_provisioning_path<'lt>(library_path: &PathBuf, provisioning_path: &PathBuf) -> Result> { + pub fn with_custom_provisioning_path<'lt>(library_path: &PathBuf, provisioning_path: &PathBuf) -> Result, AnisetteError> { // Should be safe if the library is correct. unsafe { LoaderHelpers::setup_hooks(); if !library_path.exists() { std::fs::create_dir(library_path)?; - return Err(ADIStoreSericesCoreErr::MissingLibraries.into()); + return Err(AnisetteError::MissingLibraries.into()); } let library_path = library_path.canonicalize()?; @@ -94,55 +94,55 @@ impl StoreServicesCoreADIProxy<'_> { let native_library_path = library_path.join("lib").join(ARCH); let path = native_library_path.join("libstoreservicescore.so"); - let path = path.to_str().ok_or(ADIStoreSericesCoreErr::Misc)?; + let path = path.to_str().ok_or(AnisetteError::Misc)?; let store_services_core = AndroidLibrary::load(path)?; let adi_load_library_with_path: sysv64_type!(fn(path: *const u8) -> i32) = std::mem::transmute( store_services_core .get_symbol("kq56gsgHG6") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?, + .ok_or(AnisetteError::InvalidLibraryFormat)?, ); let path = CString::new( native_library_path .to_str() - .ok_or(ADIStoreSericesCoreErr::Misc)?, + .ok_or(AnisetteError::Misc)?, ) .unwrap(); assert_eq!((adi_load_library_with_path)(path.as_ptr() as *const u8), 0); let adi_set_android_id = store_services_core .get_symbol("Sph98paBcz") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_set_provisioning_path = store_services_core .get_symbol("nf92ngaK92") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_provisioning_erase = store_services_core .get_symbol("p435tmhbla") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_synchronize = store_services_core .get_symbol("tn46gtiuhw") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_provisioning_destroy = store_services_core .get_symbol("fy34trz2st") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_provisioning_end = store_services_core .get_symbol("uv5t6nhkui") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_provisioning_start = store_services_core .get_symbol("rsegvyrt87") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_get_login_code = store_services_core .get_symbol("aslgmuibau") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_dispose = store_services_core .get_symbol("jk24uiwqrg") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let adi_otp_request = store_services_core .get_symbol("qi864985u0") - .ok_or(ADIStoreSericesCoreErr::InvalidLibraryFormat)?; + .ok_or(AnisetteError::InvalidLibraryFormat)?; let mut proxy = StoreServicesCoreADIProxy { store_services_core, @@ -164,7 +164,7 @@ impl StoreServicesCoreADIProxy<'_> { }; proxy.set_provisioning_path( - provisioning_path.to_str().ok_or(ADIStoreSericesCoreErr::Misc)?, + provisioning_path.to_str().ok_or(AnisetteError::Misc)?, )?; Ok(proxy) @@ -311,7 +311,7 @@ impl ADIProxy for StoreServicesCoreADIProxy<'_> { self.local_user_uuid = local_user_uuid; } - fn set_device_identifier(&mut self, device_identifier: String) -> Result<()> { + fn set_device_identifier(&mut self, device_identifier: String) -> Result<(), ADIError> { self.set_identifier(&device_identifier[0..16])?; self.device_identifier = device_identifier; Ok(()) @@ -410,31 +410,16 @@ impl LoaderHelpers { } } -#[derive(Debug)] -enum ADIStoreSericesCoreErr { - InvalidLibraryFormat, - Misc, - MissingLibraries, -} - -impl std::fmt::Display for ADIStoreSericesCoreErr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{self:?}") - } -} - -impl std::error::Error for ADIStoreSericesCoreErr {} - #[cfg(test)] mod tests { use crate::{AnisetteConfiguration, AnisetteHeaders}; - use anyhow::Result; use log::info; use std::path::PathBuf; + use crate::AnisetteError; #[cfg(not(feature = "async"))] #[test] - fn fetch_anisette_ssc() -> Result<()> { + fn fetch_anisette_ssc() -> Result<(), AnisetteError> { crate::tests::init_logger(); let mut provider = AnisetteHeaders::get_ssc_anisette_headers_provider( @@ -450,7 +435,8 @@ mod tests { #[cfg(feature = "async")] #[tokio::test] - async fn fetch_anisette_ssc_async() -> Result<()> { + async fn fetch_anisette_ssc_async() -> Result<(), AnisetteError> { + crate::tests::init_logger(); let mut provider = AnisetteHeaders::get_ssc_anisette_headers_provider( From 4577373e6acfbd26e3c327c502b9b01f659fc206 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Tue, 2 Apr 2024 14:42:09 -0600 Subject: [PATCH 07/19] fix(icloud-auth): disable excess logging --- icloud-auth/src/client.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index cb6cdd8..b7992aa 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -386,8 +386,8 @@ impl AppleAccount { plist::to_writer_xml(&mut buffer, &packet)?; let buffer = String::from_utf8(buffer).unwrap(); - println!("{:?}", gsa_headers.clone()); - println!("{:?}", buffer); + // println!("{:?}", gsa_headers.clone()); + // println!("{:?}", buffer); let res = self .client @@ -401,7 +401,7 @@ impl AppleAccount { if err_check.is_err() { return Err(err_check.err().unwrap()); } - println!("{:?}", res); + // println!("{:?}", res); let salt = res.get("s").unwrap().as_data().unwrap(); let b_pub = res.get("B").unwrap().as_data().unwrap(); let iters = res.get("i").unwrap().as_signed_integer().unwrap(); @@ -454,7 +454,7 @@ impl AppleAccount { if err_check.is_err() { return Err(err_check.err().unwrap()); } - println!("{:?}", res); + // println!("{:?}", res); let m2 = res.get("M2").unwrap().as_data().unwrap(); verifier.verify_server(&m2).unwrap(); @@ -551,7 +551,7 @@ impl AppleAccount { pub async fn verify_2fa(&self, code: String) -> Result { let headers = self.build_2fa_headers(false); - println!("Recieved code: {}", code); + // println!("Recieved code: {}", code); let res = self .client .get("https://gsa.apple.com/grandslam/GsService2/validate") @@ -572,7 +572,7 @@ impl AppleAccount { pub async fn verify_sms_2fa(&self, code: String, mut body: VerifyBody) -> Result { let headers = self.build_2fa_headers(true); - println!("Recieved code: {}", code); + // println!("Recieved code: {}", code); body.security_code = Some(VerifyCode { code }); From 61123929369b1c34bb26adc7830b4c77776057cd Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Tue, 2 Apr 2024 14:42:19 -0600 Subject: [PATCH 08/19] fix(omnisette): remove extra logging --- omnisette/src/remote_anisette_v3.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/omnisette/src/remote_anisette_v3.rs b/omnisette/src/remote_anisette_v3.rs index 8b2b123..f55cbb5 100644 --- a/omnisette/src/remote_anisette_v3.rs +++ b/omnisette/src/remote_anisette_v3.rs @@ -147,7 +147,6 @@ pub struct AnisetteData { impl AnisetteData { pub fn get_headers(&self, serial: String) -> HashMap { let dt: DateTime = Utc::now().round_subsecs(0); - println!("here {}", dt.format("%+").to_string()); HashMap::from_iter([ ("X-Apple-I-Client-Time".to_string(), dt.format("%+").to_string().replace("+00:00", "Z")), From bb39f214c4611eb80f0661933860f259401e6283 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Wed, 3 Apr 2024 16:52:28 -0600 Subject: [PATCH 09/19] fix(icloud-auth): more exports and enum formatting --- icloud-auth/src/client.rs | 26 +++++++++++++------------- icloud-auth/src/lib.rs | 1 + 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index b7992aa..4033263 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -106,12 +106,12 @@ pub struct AppToken { pub enum LoginResponse { LoggedIn(AppleAccount), // NeedsSMS2FASent(Send2FAToDevices), - NeedsDevice2FA(), - Needs2FAVerification(), - NeedsSMS2FA(), + NeedsDevice2FA, + Needs2FAVerification, + NeedsSMS2FA, NeedsSMS2FAVerification(VerifyBody), NeedsExtraStep(String), - NeedsLogin(), + NeedsLogin, Failed(Error), } @@ -317,17 +317,17 @@ impl AppleAccount { let mut response = _self.login_email_pass(username.clone(), password.clone()).await?; loop { match response { - LoginResponse::NeedsDevice2FA() => response = _self.send_2fa_to_devices().await?, - LoginResponse::Needs2FAVerification() => { + LoginResponse::NeedsDevice2FA => response = _self.send_2fa_to_devices().await?, + LoginResponse::Needs2FAVerification => { response = _self.verify_2fa(tfa_closure()).await? } - LoginResponse::NeedsSMS2FA() => { + LoginResponse::NeedsSMS2FA => { response = _self.send_sms_2fa_to_devices(1).await? } LoginResponse::NeedsSMS2FAVerification(body) => { response = _self.verify_sms_2fa(tfa_closure(), body).await? } - LoginResponse::NeedsLogin() => { + LoginResponse::NeedsLogin => { response = _self.login_email_pass(username.clone(), password.clone()).await? } LoginResponse::LoggedIn(ac) => return Ok(ac), @@ -468,8 +468,8 @@ impl AppleAccount { if let Some(plist::Value::String(s)) = status.get("au") { return match s.as_str() { - "trustedDeviceSecondaryAuth" => Ok(LoginResponse::NeedsDevice2FA()), - "secondaryAuth" => Ok(LoginResponse::NeedsSMS2FA()), + "trustedDeviceSecondaryAuth" => Ok(LoginResponse::NeedsDevice2FA), + "secondaryAuth" => Ok(LoginResponse::NeedsSMS2FA), _unk => Ok(LoginResponse::NeedsExtraStep(_unk.to_string())) } } @@ -510,7 +510,7 @@ impl AppleAccount { return Err(Error::AuthSrp); } - return Ok(LoginResponse::Needs2FAVerification()); + return Ok(LoginResponse::Needs2FAVerification); } pub async fn send_sms_2fa_to_devices(&self, phone_id: u32) -> Result { @@ -567,7 +567,7 @@ impl AppleAccount { Self::check_error(&res)?; - Ok(LoginResponse::NeedsLogin()) + Ok(LoginResponse::NeedsLogin) } pub async fn verify_sms_2fa(&self, code: String, mut body: VerifyBody) -> Result { @@ -587,7 +587,7 @@ impl AppleAccount { return Err(Error::AuthSrp); } - Ok(LoginResponse::NeedsLogin()) + Ok(LoginResponse::NeedsLogin) } fn check_error(res: &plist::Dictionary) -> Result<(), Error> { diff --git a/icloud-auth/src/lib.rs b/icloud-auth/src/lib.rs index e02bde7..7750861 100644 --- a/icloud-auth/src/lib.rs +++ b/icloud-auth/src/lib.rs @@ -3,6 +3,7 @@ mod client; use std::fmt::Display; pub use client::AppleAccount; +pub use omnisette::AnisetteConfiguration; use thiserror::Error; #[derive(Debug, Error)] From 6ca1554e146ebe6785059686cc4398edb04463a8 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Wed, 3 Apr 2024 16:54:35 -0600 Subject: [PATCH 10/19] fix(icloud-auth): avoid unnessesary cloning --- icloud-auth/src/client.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 4033263..5ef04bb 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -104,7 +104,7 @@ pub struct AppToken { } //Just make it return a custom enum, with LoggedIn(account: AppleAccount) or Needs2FA(FinishLoginDel: fn(i32) -> TFAResponse) pub enum LoginResponse { - LoggedIn(AppleAccount), + LoggedIn, // NeedsSMS2FASent(Send2FAToDevices), NeedsDevice2FA, Needs2FAVerification, @@ -330,7 +330,7 @@ impl AppleAccount { LoginResponse::NeedsLogin => { response = _self.login_email_pass(username.clone(), password.clone()).await? } - LoginResponse::LoggedIn(ac) => return Ok(ac), + LoginResponse::LoggedIn => return Ok(_self), LoginResponse::Failed(e) => return Err(e), LoginResponse::NeedsExtraStep(step) => return Err(Error::ExtraStep(step)) } @@ -474,7 +474,7 @@ impl AppleAccount { } } - Ok(LoginResponse::LoggedIn(self.clone().to_owned())) + Ok(LoginResponse::LoggedIn) } fn create_session_key(usr: &SrpClientVerifier, name: &str) -> Vec { From dae08a5ab560473720c759e3d24b29f093cd7a11 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Wed, 3 Apr 2024 16:57:08 -0600 Subject: [PATCH 11/19] fix(icloud-auth): further login refactoring --- icloud-auth/src/client.rs | 44 +++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 5ef04bb..7d6b79b 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -103,7 +103,7 @@ pub struct AppToken { pub app: String, } //Just make it return a custom enum, with LoggedIn(account: AppleAccount) or Needs2FA(FinishLoginDel: fn(i32) -> TFAResponse) -pub enum LoginResponse { +pub enum LoginState { LoggedIn, // NeedsSMS2FASent(Send2FAToDevices), NeedsDevice2FA, @@ -112,7 +112,6 @@ pub enum LoginResponse { NeedsSMS2FAVerification(VerifyBody), NeedsExtraStep(String), NeedsLogin, - Failed(Error), } #[derive(Serialize)] @@ -317,22 +316,21 @@ impl AppleAccount { let mut response = _self.login_email_pass(username.clone(), password.clone()).await?; loop { match response { - LoginResponse::NeedsDevice2FA => response = _self.send_2fa_to_devices().await?, - LoginResponse::Needs2FAVerification => { + LoginState::NeedsDevice2FA => response = _self.send_2fa_to_devices().await?, + LoginState::Needs2FAVerification => { response = _self.verify_2fa(tfa_closure()).await? } - LoginResponse::NeedsSMS2FA => { + LoginState::NeedsSMS2FA => { response = _self.send_sms_2fa_to_devices(1).await? } - LoginResponse::NeedsSMS2FAVerification(body) => { + LoginState::NeedsSMS2FAVerification(body) => { response = _self.verify_sms_2fa(tfa_closure(), body).await? } - LoginResponse::NeedsLogin => { + LoginState::NeedsLogin => { response = _self.login_email_pass(username.clone(), password.clone()).await? } - LoginResponse::LoggedIn => return Ok(_self), - LoginResponse::Failed(e) => return Err(e), - LoginResponse::NeedsExtraStep(step) => return Err(Error::ExtraStep(step)) + LoginState::LoggedIn => return Ok(_self), + LoginState::NeedsExtraStep(step) => return Err(Error::ExtraStep(step)) } } } @@ -346,7 +344,7 @@ impl AppleAccount { &mut self, username: String, password: String, - ) -> Result { + ) -> Result { let srp_client = SrpClient::::new(&G_2048); let a: Vec = (0..32).map(|_| rand::random::()).collect(); let a_pub = srp_client.compute_public_ephemeral(&a); @@ -468,13 +466,13 @@ impl AppleAccount { if let Some(plist::Value::String(s)) = status.get("au") { return match s.as_str() { - "trustedDeviceSecondaryAuth" => Ok(LoginResponse::NeedsDevice2FA), - "secondaryAuth" => Ok(LoginResponse::NeedsSMS2FA), - _unk => Ok(LoginResponse::NeedsExtraStep(_unk.to_string())) + "trustedDeviceSecondaryAuth" => Ok(LoginState::NeedsDevice2FA), + "secondaryAuth" => Ok(LoginState::NeedsSMS2FA), + _unk => Ok(LoginState::NeedsExtraStep(_unk.to_string())) } } - Ok(LoginResponse::LoggedIn) + Ok(LoginState::LoggedIn) } fn create_session_key(usr: &SrpClientVerifier, name: &str) -> Vec { @@ -497,7 +495,7 @@ impl AppleAccount { .unwrap() } - pub async fn send_2fa_to_devices(&self) -> Result { + pub async fn send_2fa_to_devices(&self) -> Result { let headers = self.build_2fa_headers(false); let res = self @@ -510,10 +508,10 @@ impl AppleAccount { return Err(Error::AuthSrp); } - return Ok(LoginResponse::Needs2FAVerification); + return Ok(LoginState::Needs2FAVerification); } - pub async fn send_sms_2fa_to_devices(&self, phone_id: u32) -> Result { + pub async fn send_sms_2fa_to_devices(&self, phone_id: u32) -> Result { let headers = self.build_2fa_headers(true); let body = VerifyBody { @@ -535,7 +533,7 @@ impl AppleAccount { return Err(Error::AuthSrp); } - return Ok(LoginResponse::NeedsSMS2FAVerification(body)); + return Ok(LoginState::NeedsSMS2FAVerification(body)); } pub async fn get_auth_extras(&self) -> Result { @@ -549,7 +547,7 @@ impl AppleAccount { .json::().await?) } - pub async fn verify_2fa(&self, code: String) -> Result { + pub async fn verify_2fa(&self, code: String) -> Result { let headers = self.build_2fa_headers(false); // println!("Recieved code: {}", code); let res = self @@ -567,10 +565,10 @@ impl AppleAccount { Self::check_error(&res)?; - Ok(LoginResponse::NeedsLogin) + Ok(LoginState::NeedsLogin) } - pub async fn verify_sms_2fa(&self, code: String, mut body: VerifyBody) -> Result { + pub async fn verify_sms_2fa(&self, code: String, mut body: VerifyBody) -> Result { let headers = self.build_2fa_headers(true); // println!("Recieved code: {}", code); @@ -587,7 +585,7 @@ impl AppleAccount { return Err(Error::AuthSrp); } - Ok(LoginResponse::NeedsLogin) + Ok(LoginState::NeedsLogin) } fn check_error(res: &plist::Dictionary) -> Result<(), Error> { From 0a15719b3b9e7375ce9466d970cd349e9260e4bc Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Wed, 3 Apr 2024 17:05:27 -0600 Subject: [PATCH 12/19] fix(icloud-auth): make pet return optional --- icloud-auth/src/client.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 7d6b79b..126dd63 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -335,9 +335,12 @@ impl AppleAccount { } } - pub fn get_pet(&self) -> String { - self.spd.as_ref().unwrap().get("t").unwrap().as_dictionary().unwrap().get("com.apple.gs.idms.pet") - .unwrap().as_dictionary().unwrap().get("token").unwrap().as_string().unwrap().to_string() + pub fn get_pet(&self) -> Option { + let Some(token) = self.spd.as_ref().unwrap().get("t") else { + return None + }; + Some(token.as_dictionary().unwrap().get("com.apple.gs.idms.pet") + .unwrap().as_dictionary().unwrap().get("token").unwrap().as_string().unwrap().to_string()) } pub async fn login_email_pass( From f75a45ed87aa1b354b37f9b03ab8835d20caf3da Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Thu, 4 Apr 2024 16:18:07 -0600 Subject: [PATCH 13/19] fix(icloud-auth): send + sync fixes --- omnisette/src/adi_proxy.rs | 4 ++-- omnisette/src/anisette_headers_provider.rs | 4 ++-- omnisette/src/remote_anisette.rs | 2 +- omnisette/src/remote_anisette_v3.rs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/omnisette/src/adi_proxy.rs b/omnisette/src/adi_proxy.rs index bd38a2c..617af58 100644 --- a/omnisette/src/adi_proxy.rs +++ b/omnisette/src/adi_proxy.rs @@ -95,7 +95,7 @@ pub struct RequestOTPData { } #[cfg_attr(feature = "async", async_trait::async_trait(?Send))] -pub trait ADIProxy { +pub trait ADIProxy: Send + Sync { fn erase_provisioning(&mut self, ds_id: i64) -> Result<(), ADIError>; fn synchronize(&mut self, ds_id: i64, sim: &[u8]) -> Result; fn destroy_provisioning_session(&mut self, session: u32) -> Result<(), ADIError>; @@ -332,7 +332,7 @@ impl ADIProxyAnisetteProvider { } } -#[cfg_attr(feature = "async", async_trait::async_trait(?Send))] +#[cfg_attr(feature = "async", async_trait::async_trait)] impl AnisetteHeadersProvider for ADIProxyAnisetteProvider { diff --git a/omnisette/src/anisette_headers_provider.rs b/omnisette/src/anisette_headers_provider.rs index fc4c60d..903203e 100644 --- a/omnisette/src/anisette_headers_provider.rs +++ b/omnisette/src/anisette_headers_provider.rs @@ -3,8 +3,8 @@ use std::collections::HashMap; use crate::AnisetteError; -#[cfg_attr(feature = "async", async_trait::async_trait(?Send))] -pub trait AnisetteHeadersProvider { +#[cfg_attr(feature = "async", async_trait::async_trait)] +pub trait AnisetteHeadersProvider: Send + Sync { #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] async fn get_anisette_headers( &mut self, diff --git a/omnisette/src/remote_anisette.rs b/omnisette/src/remote_anisette.rs index cf5098d..b061bd9 100644 --- a/omnisette/src/remote_anisette.rs +++ b/omnisette/src/remote_anisette.rs @@ -15,7 +15,7 @@ impl RemoteAnisetteProvider { } } -#[cfg_attr(feature = "async", async_trait::async_trait(?Send))] +#[cfg_attr(feature = "async", async_trait::async_trait)] impl AnisetteHeadersProvider for RemoteAnisetteProvider { #[cfg_attr(not(feature = "async"), remove_async_await::remove_async_await)] async fn get_anisette_headers( diff --git a/omnisette/src/remote_anisette_v3.rs b/omnisette/src/remote_anisette_v3.rs index f55cbb5..d5f28a9 100644 --- a/omnisette/src/remote_anisette_v3.rs +++ b/omnisette/src/remote_anisette_v3.rs @@ -381,7 +381,7 @@ impl RemoteAnisetteProviderV3 { } } -#[async_trait(?Send)] +#[async_trait] impl AnisetteHeadersProvider for RemoteAnisetteProviderV3 { async fn get_anisette_headers( &mut self, From a277a4c17e65982b6155ef05bbda56018262a810 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Thu, 4 Apr 2024 16:18:29 -0600 Subject: [PATCH 14/19] fix(icloud-auth): fix error situation --- icloud-auth/src/client.rs | 20 +++++++++++--------- icloud-auth/src/lib.rs | 17 +++++++++-------- icloud-auth/tests/gsa_auth.rs | 2 +- omnisette/src/lib.rs | 20 +++++++++++++------- 4 files changed, 34 insertions(+), 25 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 126dd63..0654ead 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -103,6 +103,8 @@ pub struct AppToken { pub app: String, } //Just make it return a custom enum, with LoggedIn(account: AppleAccount) or Needs2FA(FinishLoginDel: fn(i32) -> TFAResponse) +#[repr(C)] +#[derive(Debug)] pub enum LoginState { LoggedIn, // NeedsSMS2FASent(Send2FAToDevices), @@ -114,17 +116,17 @@ pub enum LoginState { NeedsLogin, } -#[derive(Serialize)] +#[derive(Serialize, Debug)] struct VerifyCode { code: String, } -#[derive(Serialize)] +#[derive(Serialize, Debug)] struct PhoneNumber { id: u32 } -#[derive(Serialize)] +#[derive(Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct VerifyBody { phone_number: PhoneNumber, @@ -313,7 +315,7 @@ impl AppleAccount { ) -> Result { let mut _self = AppleAccount::new_with_anisette(anisette)?; let (username, password) = appleid_closure(); - let mut response = _self.login_email_pass(username.clone(), password.clone()).await?; + let mut response = _self.login_email_pass(&username, &password).await?; loop { match response { LoginState::NeedsDevice2FA => response = _self.send_2fa_to_devices().await?, @@ -327,7 +329,7 @@ impl AppleAccount { response = _self.verify_sms_2fa(tfa_closure(), body).await? } LoginState::NeedsLogin => { - response = _self.login_email_pass(username.clone(), password.clone()).await? + response = _self.login_email_pass(&username, &password).await? } LoginState::LoggedIn => return Ok(_self), LoginState::NeedsExtraStep(step) => return Err(Error::ExtraStep(step)) @@ -345,8 +347,8 @@ impl AppleAccount { pub async fn login_email_pass( &mut self, - username: String, - password: String, + username: &str, + password: &str, ) -> Result { let srp_client = SrpClient::::new(&G_2048); let a: Vec = (0..32).map(|_| rand::random::()).collect(); @@ -375,7 +377,7 @@ impl AppleAccount { cpd: self.anisette.to_plist(true, false, false), operation: "init".to_string(), ps: vec!["s2k".to_string(), "s2k_fo".to_string()], - username: username.clone(), + username: username.to_string(), }; let packet = InitRequest { @@ -431,7 +433,7 @@ impl AppleAccount { c: c.to_string(), cpd: self.anisette.to_plist(true, false, false), operation: "complete".to_string(), - username, + username: username.to_string(), }; let packet = ChallengeRequest { diff --git a/icloud-auth/src/lib.rs b/icloud-auth/src/lib.rs index 7750861..ad78bdc 100644 --- a/icloud-auth/src/lib.rs +++ b/icloud-auth/src/lib.rs @@ -2,23 +2,24 @@ pub mod anisette; mod client; use std::fmt::Display; -pub use client::AppleAccount; +pub use client::{AppleAccount, LoginState, TrustedPhoneNumber, AuthenticationExtras, VerifyBody}; pub use omnisette::AnisetteConfiguration; use thiserror::Error; #[derive(Debug, Error)] pub enum Error { + #[error("Failed to parse the response")] Parse, + #[error("Failed to authenticate.")] AuthSrp, + #[error("{1} ({0})")] AuthSrpWithMessage(i64, String), + #[error("Please login to appleid.apple.com to fix this account")] ExtraStep(String), + #[error("Failed to parse a plist {0}")] PlistError(#[from] plist::Error), + #[error("Request failed {0}")] ReqwestError(#[from] reqwest::Error), + #[error("Failed getting anisette data {0}")] ErrorGettingAnisette(#[from] omnisette::AnisetteError) -} - -impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", format!("{:?}", self)) - } -} +} \ No newline at end of file diff --git a/icloud-auth/tests/gsa_auth.rs b/icloud-auth/tests/gsa_auth.rs index 91c4e47..443e98a 100644 --- a/icloud-auth/tests/gsa_auth.rs +++ b/icloud-auth/tests/gsa_auth.rs @@ -34,7 +34,7 @@ mod tests { .set_configuration_path(PathBuf::from_str("anisette_test").unwrap())).await; let account = acc.unwrap(); - println!("PET: {}", account.get_pet()); + println!("PET: {}", account.get_pet().unwrap()); return; } } diff --git a/omnisette/src/lib.rs b/omnisette/src/lib.rs index 31013fd..69a5da9 100644 --- a/omnisette/src/lib.rs +++ b/omnisette/src/lib.rs @@ -6,7 +6,6 @@ use crate::adi_proxy::{ADIProxyAnisetteProvider, ConfigurableADIProxy}; use crate::anisette_headers_provider::AnisetteHeadersProvider; -use std::fmt::Formatter; use std::io; use std::path::PathBuf; use adi_proxy::ADIError; @@ -32,29 +31,36 @@ pub struct AnisetteHeaders; #[derive(Debug, Error)] pub enum AnisetteError { #[allow(dead_code)] + #[error("Unsupported device")] UnsupportedDevice, + #[error("Invalid argument {0}")] InvalidArgument(String), + #[error("Anisette not provisioned!")] AnisetteNotProvisioned, + #[error("Plist serialization error {0}")] PlistError(#[from] plist::Error), + #[error("Request Error {0}")] ReqwestError(#[from] reqwest::Error), #[cfg(feature = "remote-anisette-v3")] + #[error("Provisioning socket error {0}")] WsError(#[from] tokio_tungstenite::tungstenite::error::Error), #[cfg(feature = "remote-anisette-v3")] + #[error("JSON error {0}")] SerdeError(#[from] serde_json::Error), + #[error("IO error {0}")] IOError(#[from] io::Error), + #[error("ADI error {0}")] ADIError(#[from] ADIError), + #[error("Invalid library format")] InvalidLibraryFormat, + #[error("Misc")] Misc, + #[error("Missing Libraries")] MissingLibraries, + #[error("{0}")] Anyhow(#[from] anyhow::Error) } -impl std::fmt::Display for AnisetteError { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "AnisetteMetaError::{self:?}") - } -} - pub const DEFAULT_ANISETTE_URL: &str = "https://ani.f1sh.me/"; pub const DEFAULT_ANISETTE_URL_V3: &str = "https://ani.sidestore.io"; From 7fe6075e4644ac9b56ec2bcb2d5c505299de7aa6 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Tue, 9 Apr 2024 20:27:26 -0600 Subject: [PATCH 15/19] fix(icloud-auth): refresh anisette data every 60 seconds --- icloud-auth/Cargo.toml | 1 + icloud-auth/src/anisette.rs | 25 +++++++++++++++++--- icloud-auth/src/client.rs | 46 ++++++++++++++++++++++++------------- omnisette/src/lib.rs | 2 +- 4 files changed, 54 insertions(+), 20 deletions(-) diff --git a/icloud-auth/Cargo.toml b/icloud-auth/Cargo.toml index 4e3bc35..24bc631 100644 --- a/icloud-auth/Cargo.toml +++ b/icloud-auth/Cargo.toml @@ -24,6 +24,7 @@ pkcs7 = "0.3.0" reqwest = { version = "0.11.14", features = ["blocking", "json", "default-tls"] } omnisette = {path = "../omnisette", features = ["remote-anisette-v3"]} thiserror = "1.0.58" +tokio = "1" [dev-dependencies] tokio = { version = "1", features = ["rt", "macros"] } diff --git a/icloud-auth/src/anisette.rs b/icloud-auth/src/anisette.rs index 1055101..d11601a 100644 --- a/icloud-auth/src/anisette.rs +++ b/icloud-auth/src/anisette.rs @@ -1,19 +1,35 @@ use crate::Error; use omnisette::{AnisetteConfiguration, AnisetteHeaders}; -use std::collections::HashMap; +use std::{collections::HashMap, time::SystemTime}; #[derive(Debug, Clone)] pub struct AnisetteData { pub base_headers: HashMap, + pub generated_at: SystemTime, + pub config: AnisetteConfiguration, } impl AnisetteData { /// Fetches the data at an anisette server pub async fn new(config: AnisetteConfiguration) -> Result { - let mut b = AnisetteHeaders::get_anisette_headers_provider(config)?; + let mut b = AnisetteHeaders::get_anisette_headers_provider(config.clone())?; let base_headers = b.provider.get_authentication_headers().await?; - Ok(AnisetteData { base_headers }) + Ok(AnisetteData { base_headers, generated_at: SystemTime::now(), config }) + } + + pub fn needs_refresh(&self) -> bool { + let elapsed = self.generated_at.elapsed().unwrap(); + elapsed.as_secs() > 60 + } + + pub fn is_valid(&self) -> bool { + let elapsed = self.generated_at.elapsed().unwrap(); + elapsed.as_secs() < 90 + } + + pub async fn refresh(&self) -> Result { + Self::new(self.config.clone()).await } pub fn generate_headers( @@ -22,6 +38,9 @@ impl AnisetteData { client_info: bool, app_info: bool, ) -> HashMap { + if !self.is_valid() { + panic!("Invalid data!") + } let mut headers = self.base_headers.clone(); let old_client_info = headers.remove("X-Mme-Client-Info"); if client_info { diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 0654ead..f435da0 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -17,6 +17,7 @@ use srp::{ client::{SrpClient, SrpClientVerifier}, groups::G_2048, }; +use tokio::sync::Mutex; const GSA_ENDPOINT: &str = "https://gsa.apple.com/grandslam/GsService2"; const APPLE_ROOT: &[u8] = include_bytes!("./apple_root.der"); @@ -86,10 +87,9 @@ pub struct AuthTokenRequest { request: AuthTokenRequestBody, } -#[derive(Clone)] pub struct AppleAccount { //TODO: move this to omnisette - pub anisette: AnisetteData, + pub anisette: Mutex, // pub spd: Option, //mutable spd pub spd: Option, @@ -192,7 +192,7 @@ impl AppleAccount { Ok(AppleAccount { client, - anisette, + anisette: Mutex::new(anisette), spd: None, }) } @@ -206,12 +206,22 @@ impl AppleAccount { AppleAccount::login_with_anisette(appleid_closure, tfa_closure, anisette).await } + pub async fn get_anisette(&self) -> AnisetteData { + let mut locked = self.anisette.lock().await; + if locked.needs_refresh() { + *locked = locked.refresh().await.unwrap(); + } + locked.clone() + } + pub async fn get_app_token(&self, app_name: &str) -> Result { let spd = self.spd.as_ref().unwrap(); // println!("spd: {:#?}", spd); let dsid = spd.get("adsid").unwrap().as_string().unwrap(); let auth_token = spd.get("GsIdmsToken").unwrap().as_string().unwrap(); + let valid_anisette = self.get_anisette().await; + let sk = spd.get("sk").unwrap().as_data().unwrap(); let c = spd.get("c").unwrap().as_data().unwrap(); println!("adsid: {}", dsid); @@ -234,14 +244,14 @@ impl AppleAccount { ); gsa_headers.insert( "X-MMe-Client-Info", - HeaderValue::from_str(&self.anisette.get_header("x-mme-client-info")?).unwrap(), + HeaderValue::from_str(&valid_anisette.get_header("x-mme-client-info")?).unwrap(), ); let header = RequestHeader { version: "1.0.1".to_string(), }; let body = AuthTokenRequestBody { - cpd: self.anisette.to_plist(true, false, false), + cpd: valid_anisette.to_plist(true, false, false), app: vec![app_name.to_string()], c: plist::Value::Data(c.to_vec()), operation: "apptokens".to_owned(), @@ -354,6 +364,8 @@ impl AppleAccount { let a: Vec = (0..32).map(|_| rand::random::()).collect(); let a_pub = srp_client.compute_public_ephemeral(&a); + let valid_anisette = self.get_anisette().await; + let mut gsa_headers = HeaderMap::new(); gsa_headers.insert( "Content-Type", @@ -366,7 +378,7 @@ impl AppleAccount { ); gsa_headers.insert( "X-MMe-Client-Info", - HeaderValue::from_str(&self.anisette.get_header("x-mme-client-info")?).unwrap(), + HeaderValue::from_str(&valid_anisette.get_header("x-mme-client-info")?).unwrap(), ); let header = RequestHeader { @@ -374,7 +386,7 @@ impl AppleAccount { }; let body = InitRequestBody { a_pub: plist::Value::Data(a_pub), - cpd: self.anisette.to_plist(true, false, false), + cpd: valid_anisette.to_plist(true, false, false), operation: "init".to_string(), ps: vec!["s2k".to_string(), "s2k_fo".to_string()], username: username.to_string(), @@ -431,7 +443,7 @@ impl AppleAccount { let body = ChallengeRequestBody { m: plist::Value::Data(m.to_vec()), c: c.to_string(), - cpd: self.anisette.to_plist(true, false, false), + cpd: valid_anisette.to_plist(true, false, false), operation: "complete".to_string(), username: username.to_string(), }; @@ -506,7 +518,7 @@ impl AppleAccount { let res = self .client .get("https://gsa.apple.com/auth/verify/trusteddevice") - .headers(headers) + .headers(headers.await) .send().await?; if !res.status().is_success() { @@ -530,7 +542,7 @@ impl AppleAccount { let res = self .client .put("https://gsa.apple.com/auth/verify/phone/") - .headers(headers) + .headers(headers.await) .json(&body) .send().await?; @@ -546,7 +558,7 @@ impl AppleAccount { Ok(self.client .get("https://gsa.apple.com/auth") - .headers(headers) + .headers(headers.await) .header("Accept", "application/json") .send().await? .json::().await?) @@ -558,7 +570,7 @@ impl AppleAccount { let res = self .client .get("https://gsa.apple.com/grandslam/GsService2/validate") - .headers(headers) + .headers(headers.await) .header( HeaderName::from_str("security-code").unwrap(), HeaderValue::from_str(&code).unwrap(), @@ -574,7 +586,7 @@ impl AppleAccount { } pub async fn verify_sms_2fa(&self, code: String, mut body: VerifyBody) -> Result { - let headers = self.build_2fa_headers(true); + let headers = self.build_2fa_headers(true).await; // println!("Recieved code: {}", code); body.security_code = Some(VerifyCode { code }); @@ -609,15 +621,17 @@ impl AppleAccount { Ok(()) } - pub fn build_2fa_headers(&self, sms: bool) -> HeaderMap { + pub async fn build_2fa_headers(&self, sms: bool) -> HeaderMap { let spd = self.spd.as_ref().unwrap(); let dsid = spd.get("adsid").unwrap().as_string().unwrap(); let token = spd.get("GsIdmsToken").unwrap().as_string().unwrap(); let identity_token = base64::encode(format!("{}:{}", dsid, token)); + let valid_anisette = self.get_anisette().await; + let mut headers = HeaderMap::new(); - self.anisette + valid_anisette .generate_headers(false, true, true) .iter() .for_each(|(k, v)| { @@ -643,7 +657,7 @@ impl AppleAccount { headers.insert( "Loc", - HeaderValue::from_str(&self.anisette.get_header("x-apple-locale").unwrap()).unwrap(), + HeaderValue::from_str(&valid_anisette.get_header("x-apple-locale").unwrap()).unwrap(), ); headers diff --git a/omnisette/src/lib.rs b/omnisette/src/lib.rs index 69a5da9..bfd2681 100644 --- a/omnisette/src/lib.rs +++ b/omnisette/src/lib.rs @@ -65,7 +65,7 @@ pub const DEFAULT_ANISETTE_URL: &str = "https://ani.f1sh.me/"; pub const DEFAULT_ANISETTE_URL_V3: &str = "https://ani.sidestore.io"; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct AnisetteConfiguration { anisette_url: String, anisette_url_v3: String, From d0e421e0dde2518379f903355660c12e22735bf8 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Tue, 9 Apr 2024 20:27:53 -0600 Subject: [PATCH 16/19] fix(omnisette): use bundled roots for android --- omnisette/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/omnisette/Cargo.toml b/omnisette/Cargo.toml index b954a6b..1f54873 100644 --- a/omnisette/Cargo.toml +++ b/omnisette/Cargo.toml @@ -24,7 +24,7 @@ async-trait = { version = "0.1", optional = true } remove-async-await = { version = "1.0", optional = true } serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0.115", optional = true } -tokio-tungstenite = { version = "0.20.1", optional = true, features = ["rustls-tls-native-roots"] } +tokio-tungstenite = { version = "0.20.1", optional = true, features = ["rustls-tls-webpki-roots"] } futures-util = { version = "0.3.28", optional = true } chrono = { version = "0.4.37", optional = true } thiserror = "1.0.58" From e64160d0e0c9df0d91e0804a72b870fb78ea7ce6 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Tue, 9 Apr 2024 20:28:06 -0600 Subject: [PATCH 17/19] feat(icloud-auth): support getting account name --- icloud-auth/src/client.rs | 7 +++++++ icloud-auth/tests/gsa_auth.rs | 1 + 2 files changed, 8 insertions(+) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index f435da0..5af4a10 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -355,6 +355,13 @@ impl AppleAccount { .unwrap().as_dictionary().unwrap().get("token").unwrap().as_string().unwrap().to_string()) } + pub fn get_name(&self) -> (String, String) { + ( + self.spd.as_ref().unwrap().get("fn").unwrap().as_string().unwrap().to_string(), + self.spd.as_ref().unwrap().get("ln").unwrap().as_string().unwrap().to_string() + ) + } + pub async fn login_email_pass( &mut self, username: &str, diff --git a/icloud-auth/tests/gsa_auth.rs b/icloud-auth/tests/gsa_auth.rs index 443e98a..6770245 100644 --- a/icloud-auth/tests/gsa_auth.rs +++ b/icloud-auth/tests/gsa_auth.rs @@ -34,6 +34,7 @@ mod tests { .set_configuration_path(PathBuf::from_str("anisette_test").unwrap())).await; let account = acc.unwrap(); + println!("data {:?}", account.get_name()); println!("PET: {}", account.get_pet().unwrap()); return; } From a1ac3795da9765e0b9d3e83ac336e730ac0487d2 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Fri, 26 Apr 2024 22:22:33 -0600 Subject: [PATCH 18/19] Fix SMS 2fa error handling --- icloud-auth/src/client.rs | 18 +++++++++++++----- icloud-auth/src/lib.rs | 2 ++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index 5af4a10..f2215c7 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -116,17 +116,17 @@ pub enum LoginState { NeedsLogin, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Clone)] struct VerifyCode { code: String, } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Clone)] struct PhoneNumber { id: u32 } -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Clone)] #[serde(rename_all = "camelCase")] pub struct VerifyBody { phone_number: PhoneNumber, @@ -342,7 +342,13 @@ impl AppleAccount { response = _self.login_email_pass(&username, &password).await? } LoginState::LoggedIn => return Ok(_self), - LoginState::NeedsExtraStep(step) => return Err(Error::ExtraStep(step)) + LoginState::NeedsExtraStep(step) => { + if _self.get_pet().is_some() { + return Ok(_self) + } else { + return Err(Error::ExtraStep(step)) + } + } } } } @@ -538,6 +544,7 @@ impl AppleAccount { pub async fn send_sms_2fa_to_devices(&self, phone_id: u32) -> Result { let headers = self.build_2fa_headers(true); + let body = VerifyBody { phone_number: PhoneNumber { id: phone_id @@ -602,11 +609,12 @@ impl AppleAccount { .client .post("https://gsa.apple.com/auth/verify/phone/securitycode") .headers(headers) + .header("accept", "application/json") .json(&body) .send().await?; if res.status() != 200 { - return Err(Error::AuthSrp); + return Err(Error::Bad2faCode); } Ok(LoginState::NeedsLogin) diff --git a/icloud-auth/src/lib.rs b/icloud-auth/src/lib.rs index ad78bdc..fc79925 100644 --- a/icloud-auth/src/lib.rs +++ b/icloud-auth/src/lib.rs @@ -12,6 +12,8 @@ pub enum Error { Parse, #[error("Failed to authenticate.")] AuthSrp, + #[error("Bad 2fa code.")] + Bad2faCode, #[error("{1} ({0})")] AuthSrpWithMessage(i64, String), #[error("Please login to appleid.apple.com to fix this account")] From 7ca9bb70652f77550084977928ae12b6c4c2ada0 Mon Sep 17 00:00:00 2001 From: Tae Hagen Date: Sat, 27 Apr 2024 11:03:14 -0600 Subject: [PATCH 19/19] Support list phone numbers 201 --- icloud-auth/src/client.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/icloud-auth/src/client.rs b/icloud-auth/src/client.rs index f2215c7..52135fd 100644 --- a/icloud-auth/src/client.rs +++ b/icloud-auth/src/client.rs @@ -7,9 +7,7 @@ use cbc::cipher::{BlockDecryptMut, KeyIvInit}; use hmac::{Hmac, Mac}; use omnisette::AnisetteConfiguration; use reqwest::{ - Client, ClientBuilder, Response, - header::{HeaderMap, HeaderName, HeaderValue}, - Certificate, + header::{HeaderMap, HeaderName, HeaderValue}, Certificate, Client, ClientBuilder, Proxy, Response }; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; @@ -134,6 +132,7 @@ pub struct VerifyBody { security_code: Option } +#[repr(C)] #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct TrustedPhoneNumber { @@ -153,6 +152,8 @@ pub struct AuthenticationExtras { pub recovery_web_url: Option, pub repair_phone_number_url: Option, pub repair_phone_number_web_url: Option, + #[serde(skip)] + pub new_state: Option, } // impl Send2FAToDevices { @@ -570,12 +571,24 @@ impl AppleAccount { pub async fn get_auth_extras(&self) -> Result { let headers = self.build_2fa_headers(true); - Ok(self.client + let req = self.client .get("https://gsa.apple.com/auth") .headers(headers.await) .header("Accept", "application/json") - .send().await? - .json::().await?) + .send().await?; + let status = req.status().as_u16(); + let mut new_state = req.json::().await?; + if status == 201 { + new_state.new_state = Some(LoginState::NeedsSMS2FAVerification(VerifyBody { + phone_number: PhoneNumber { + id: new_state.trusted_phone_numbers.first().unwrap().id + }, + mode: "sms".to_string(), + security_code: None + })); + } + + Ok(new_state) } pub async fn verify_2fa(&self, code: String) -> Result {