diff --git a/examples/minimal/src/main.rs b/examples/minimal/src/main.rs index f170fb8..d5fb149 100644 --- a/examples/minimal/src/main.rs +++ b/examples/minimal/src/main.rs @@ -1,4 +1,4 @@ -use std::{env, path::PathBuf}; +use std::env; use isideload::{ anisette::remote_v3::RemoteV3AnisetteProvider, auth::apple_account::AppleAccountBuilder, @@ -10,19 +10,19 @@ use tracing_subscriber::FmtSubscriber; async fn main() { isideload::init().expect("Failed to initialize error reporting"); let subscriber = FmtSubscriber::builder() - .with_max_level(Level::INFO) + .with_max_level(Level::DEBUG) .finish(); tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); let args: Vec = env::args().collect(); - let _app_path = PathBuf::from( - args.get(1) - .expect("Please provide the path to the app to install"), - ); + // let _app_path = PathBuf::from( + // args.get(1) + // .expect("Please provide the path to the app to install"), + // ); let apple_id = args - .get(2) + .get(1) .expect("Please provide the Apple ID to use for installation"); - let apple_password = args.get(3).expect("Please provide the Apple ID password"); + let apple_password = args.get(2).expect("Please provide the Apple ID password"); let get_2fa_code = || { let mut code = String::new(); diff --git a/isideload/src/anisette/remote_v3/mod.rs b/isideload/src/anisette/remote_v3/mod.rs index 7f06273..e87d3cb 100644 --- a/isideload/src/anisette/remote_v3/mod.rs +++ b/isideload/src/anisette/remote_v3/mod.rs @@ -199,7 +199,7 @@ impl RemoteV3AnisetteProvider { gs: &mut GrandSlam, url: &str, ) -> Result<(), Report> { - info!("Starting provisioning"); + debug!("Starting provisioning"); let start_provisioning = gs.get_url("midStartProvisioning").await?; let end_provisioning = gs.get_url("midFinishProvisioning").await?; diff --git a/isideload/src/auth/apple_account.rs b/isideload/src/auth/apple_account.rs index bd2673f..1538908 100644 --- a/isideload/src/auth/apple_account.rs +++ b/isideload/src/auth/apple_account.rs @@ -156,7 +156,7 @@ impl AppleAccount { password: &str, two_factor_callback: impl Fn() -> Option, ) -> Result<(), Report> { - info!("Logging in to apple ID: {}", self.email); + info!("Logging in to Apple ID: {}", self.email); if self.debug { warn!("Debug mode enabled: this is a security risk!"); } @@ -184,55 +184,19 @@ impl AppleAccount { return Ok(()); } LoginState::NeedsDevice2FA => { - debug!("Trusted device 2FA required"); - let request_code_url = self - .grandslam_client - .get_url("trustedDeviceSecondaryAuth") - .await?; - - let submit_code_url = self.grandslam_client.get_url("validateCode").await?; - - self.grandslam_client - .get(&request_code_url)? - .headers(self.build_2fa_headers().await?) - .send() + self.trusted_device_2fa(&two_factor_callback) .await - .context("Failed to request trusted device 2fa")? - .error_for_status() - .context("Trusted device 2FA request failed")?; - - info!("Trusted device 2FA request sent"); - - let code = two_factor_callback() - .ok_or_else(|| report!("No 2FA code provided, aborting"))?; - - let res = self - .grandslam_client - .get(&submit_code_url)? - .headers(self.build_2fa_headers().await?) - .header("security-code", code) - .send() - .await - .context("Failed to submit trusted device 2fa code")? - .error_for_status() - .context("Trusted device 2FA code submission failed")? - .text() - .await - .context("Failed to read trusted device 2FA response text")?; - - let plist: Dictionary = plist::from_bytes(res.as_bytes()) - .context("Failed to parse trusted device response plist") - .attach_with(|| res.clone())?; - plist - .check_grandslam_error() - .context("Trusted device 2FA rejected")?; - + .context("Failed to complete trusted device 2FA")?; debug!("Trusted device 2FA completed, need to login again"); self.login_state = LoginState::NeedsLogin; } LoginState::NeedsSMS2FA => { info!("SMS 2FA required"); - todo!(); + self.sms_2fa(&two_factor_callback) + .await + .context("Failed to complete SMS 2FA")?; + debug!("SMS 2FA completed, need to login again"); + self.login_state = LoginState::NeedsLogin; } LoginState::NeedsExtraStep(_) => todo!(), LoginState::NeedsLogin => { @@ -246,6 +210,108 @@ impl AppleAccount { } } + async fn trusted_device_2fa( + &mut self, + two_factor_callback: impl Fn() -> Option, + ) -> Result<(), Report> { + debug!("Trusted device 2FA required"); + let request_code_url = self + .grandslam_client + .get_url("trustedDeviceSecondaryAuth") + .await?; + + let submit_code_url = self.grandslam_client.get_url("validateCode").await?; + + self.grandslam_client + .get(&request_code_url)? + .headers(self.build_2fa_headers().await?) + .send() + .await + .context("Failed to request trusted device 2fa")? + .error_for_status() + .context("Trusted device 2FA request failed")?; + + info!("Trusted device 2FA request sent"); + + let code = + two_factor_callback().ok_or_else(|| report!("No 2FA code provided, aborting"))?; + + let res = self + .grandslam_client + .get(&submit_code_url)? + .headers(self.build_2fa_headers().await?) + .header("security-code", code) + .send() + .await + .context("Failed to submit trusted device 2fa code")? + .error_for_status() + .context("Trusted device 2FA code submission failed")? + .text() + .await + .context("Failed to read trusted device 2FA response text")?; + + let plist: Dictionary = plist::from_bytes(res.as_bytes()) + .context("Failed to parse trusted device response plist") + .attach_with(|| res.clone())?; + plist + .check_grandslam_error() + .context("Trusted device 2FA rejected")?; + + Ok(()) + } + + async fn sms_2fa( + &mut self, + two_factor_callback: impl Fn() -> Option, + ) -> Result<(), Report> { + debug!("SMS 2FA required"); + let request_code_url = self.grandslam_client.get_url("secondaryAuth").await?; + + self.grandslam_client + .get_sms(&request_code_url)? + .headers(self.build_2fa_headers().await?) + .send() + .await + .context("Failed to request SMS 2fa")? + .error_for_status() + .context("SMS 2FA request failed")?; + + info!("SMS 2FA request sent"); + + let code = + two_factor_callback().ok_or_else(|| report!("No 2FA code provided, aborting"))?; + + let body = serde_json::json!({ + "securityCode": { + "code": code + }, + "phoneNumber": { + "id": "1" + }, + "mode": "sms" + }); + + let res = self + .grandslam_client + .post("https://gsa.apple.com/auth/verify/phone/securitycode")? + .headers(self.build_2fa_headers().await?) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .body(body.to_string()) + .send() + .await + .context("Failed to submit SMS 2fa code")? + .error_for_status() + .context("SMS 2FA code submission failed")? + .text() + .await + .context("Failed to read SMS 2FA response text")?; + + debug!("SMS 2FA response: {}", res); + + Ok(()) + } + async fn build_2fa_headers(&mut self) -> Result { let mut headers = self.anisette_data.get_header_map(SERIAL_NUMBER.to_string()); @@ -407,6 +473,7 @@ impl AppleAccount { return Ok(match s.as_str() { "trustedDeviceSecondaryAuth" => LoginState::NeedsDevice2FA, "secondaryAuth" => LoginState::NeedsSMS2FA, + "repair" => LoginState::LoggedIn, // Just means that you don't have 2FA set up unknown => LoginState::NeedsExtraStep(unknown.to_string()), }); } diff --git a/isideload/src/auth/grandslam.rs b/isideload/src/auth/grandslam.rs index 686da8b..718c617 100644 --- a/isideload/src/auth/grandslam.rs +++ b/isideload/src/auth/grandslam.rs @@ -41,7 +41,7 @@ impl GrandSlam { let resp = self .client .get(URL_BAG) - .headers(self.base_headers()?) + .headers(self.base_headers(false)?) .send() .await .context("Failed to fetch URL Bag")? @@ -71,13 +71,19 @@ impl GrandSlam { } pub fn get(&self, url: &str) -> Result { - let builder = self.client.get(url).headers(self.base_headers()?); + let builder = self.client.get(url).headers(self.base_headers(false)?); + + Ok(builder) + } + + pub fn get_sms(&self, url: &str) -> Result { + let builder = self.client.get(url).headers(self.base_headers(true)?); Ok(builder) } pub fn post(&self, url: &str) -> Result { - let builder = self.client.post(url).headers(self.base_headers()?); + let builder = self.client.post(url).headers(self.base_headers(false)?); Ok(builder) } @@ -117,10 +123,12 @@ impl GrandSlam { Ok(response_plist) } - fn base_headers(&self) -> Result { + fn base_headers(&self, sms: bool) -> Result { let mut headers = reqwest::header::HeaderMap::new(); - headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist")); - headers.insert("Accept", HeaderValue::from_static("text/x-xml-plist")); + if !sms { + headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist")); + headers.insert("Accept", HeaderValue::from_static("text/x-xml-plist")); + } headers.insert( "X-Mme-Client-Info", HeaderValue::from_str(&self.client_info.client_info)?,