Add SMS 2FA

This commit is contained in:
nab138
2026-01-25 16:57:43 -05:00
parent 8be330a6be
commit 3031baf9ab
4 changed files with 134 additions and 59 deletions

View File

@@ -1,4 +1,4 @@
use std::{env, path::PathBuf}; use std::env;
use isideload::{ use isideload::{
anisette::remote_v3::RemoteV3AnisetteProvider, auth::apple_account::AppleAccountBuilder, anisette::remote_v3::RemoteV3AnisetteProvider, auth::apple_account::AppleAccountBuilder,
@@ -10,19 +10,19 @@ use tracing_subscriber::FmtSubscriber;
async fn main() { async fn main() {
isideload::init().expect("Failed to initialize error reporting"); isideload::init().expect("Failed to initialize error reporting");
let subscriber = FmtSubscriber::builder() let subscriber = FmtSubscriber::builder()
.with_max_level(Level::INFO) .with_max_level(Level::DEBUG)
.finish(); .finish();
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed"); tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
let _app_path = PathBuf::from( // let _app_path = PathBuf::from(
args.get(1) // args.get(1)
.expect("Please provide the path to the app to install"), // .expect("Please provide the path to the app to install"),
); // );
let apple_id = args let apple_id = args
.get(2) .get(1)
.expect("Please provide the Apple ID to use for installation"); .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 get_2fa_code = || {
let mut code = String::new(); let mut code = String::new();

View File

@@ -199,7 +199,7 @@ impl RemoteV3AnisetteProvider {
gs: &mut GrandSlam, gs: &mut GrandSlam,
url: &str, url: &str,
) -> Result<(), Report> { ) -> Result<(), Report> {
info!("Starting provisioning"); debug!("Starting provisioning");
let start_provisioning = gs.get_url("midStartProvisioning").await?; let start_provisioning = gs.get_url("midStartProvisioning").await?;
let end_provisioning = gs.get_url("midFinishProvisioning").await?; let end_provisioning = gs.get_url("midFinishProvisioning").await?;

View File

@@ -156,7 +156,7 @@ impl AppleAccount {
password: &str, password: &str,
two_factor_callback: impl Fn() -> Option<String>, two_factor_callback: impl Fn() -> Option<String>,
) -> Result<(), Report> { ) -> Result<(), Report> {
info!("Logging in to apple ID: {}", self.email); info!("Logging in to Apple ID: {}", self.email);
if self.debug { if self.debug {
warn!("Debug mode enabled: this is a security risk!"); warn!("Debug mode enabled: this is a security risk!");
} }
@@ -184,6 +184,36 @@ impl AppleAccount {
return Ok(()); return Ok(());
} }
LoginState::NeedsDevice2FA => { LoginState::NeedsDevice2FA => {
self.trusted_device_2fa(&two_factor_callback)
.await
.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");
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 => {
debug!("Logging in again...");
self.login_state = self
.login_inner(password)
.await
.context("Failed to login again")?;
}
}
}
}
async fn trusted_device_2fa(
&mut self,
two_factor_callback: impl Fn() -> Option<String>,
) -> Result<(), Report> {
debug!("Trusted device 2FA required"); debug!("Trusted device 2FA required");
let request_code_url = self let request_code_url = self
.grandslam_client .grandslam_client
@@ -203,8 +233,8 @@ impl AppleAccount {
info!("Trusted device 2FA request sent"); info!("Trusted device 2FA request sent");
let code = two_factor_callback() let code =
.ok_or_else(|| report!("No 2FA code provided, aborting"))?; two_factor_callback().ok_or_else(|| report!("No 2FA code provided, aborting"))?;
let res = self let res = self
.grandslam_client .grandslam_client
@@ -227,23 +257,59 @@ impl AppleAccount {
.check_grandslam_error() .check_grandslam_error()
.context("Trusted device 2FA rejected")?; .context("Trusted device 2FA rejected")?;
debug!("Trusted device 2FA completed, need to login again"); Ok(())
self.login_state = LoginState::NeedsLogin;
} }
LoginState::NeedsSMS2FA => {
info!("SMS 2FA required"); async fn sms_2fa(
todo!(); &mut self,
} two_factor_callback: impl Fn() -> Option<String>,
LoginState::NeedsExtraStep(_) => todo!(), ) -> Result<(), Report> {
LoginState::NeedsLogin => { debug!("SMS 2FA required");
debug!("Logging in again..."); let request_code_url = self.grandslam_client.get_url("secondaryAuth").await?;
self.login_state = self
.login_inner(password) self.grandslam_client
.get_sms(&request_code_url)?
.headers(self.build_2fa_headers().await?)
.send()
.await .await
.context("Failed to login again")?; .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<HeaderMap, Report> { async fn build_2fa_headers(&mut self) -> Result<HeaderMap, Report> {
@@ -407,6 +473,7 @@ impl AppleAccount {
return Ok(match s.as_str() { return Ok(match s.as_str() {
"trustedDeviceSecondaryAuth" => LoginState::NeedsDevice2FA, "trustedDeviceSecondaryAuth" => LoginState::NeedsDevice2FA,
"secondaryAuth" => LoginState::NeedsSMS2FA, "secondaryAuth" => LoginState::NeedsSMS2FA,
"repair" => LoginState::LoggedIn, // Just means that you don't have 2FA set up
unknown => LoginState::NeedsExtraStep(unknown.to_string()), unknown => LoginState::NeedsExtraStep(unknown.to_string()),
}); });
} }

View File

@@ -41,7 +41,7 @@ impl GrandSlam {
let resp = self let resp = self
.client .client
.get(URL_BAG) .get(URL_BAG)
.headers(self.base_headers()?) .headers(self.base_headers(false)?)
.send() .send()
.await .await
.context("Failed to fetch URL Bag")? .context("Failed to fetch URL Bag")?
@@ -71,13 +71,19 @@ impl GrandSlam {
} }
pub fn get(&self, url: &str) -> Result<reqwest::RequestBuilder, Report> { pub fn get(&self, url: &str) -> Result<reqwest::RequestBuilder, Report> {
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<reqwest::RequestBuilder, Report> {
let builder = self.client.get(url).headers(self.base_headers(true)?);
Ok(builder) Ok(builder)
} }
pub fn post(&self, url: &str) -> Result<reqwest::RequestBuilder, Report> { pub fn post(&self, url: &str) -> Result<reqwest::RequestBuilder, Report> {
let builder = self.client.post(url).headers(self.base_headers()?); let builder = self.client.post(url).headers(self.base_headers(false)?);
Ok(builder) Ok(builder)
} }
@@ -117,10 +123,12 @@ impl GrandSlam {
Ok(response_plist) Ok(response_plist)
} }
fn base_headers(&self) -> Result<reqwest::header::HeaderMap, Report> { fn base_headers(&self, sms: bool) -> Result<reqwest::header::HeaderMap, Report> {
let mut headers = reqwest::header::HeaderMap::new(); let mut headers = reqwest::header::HeaderMap::new();
if !sms {
headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist")); headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist"));
headers.insert("Accept", HeaderValue::from_static("text/x-xml-plist")); headers.insert("Accept", HeaderValue::from_static("text/x-xml-plist"));
}
headers.insert( headers.insert(
"X-Mme-Client-Info", "X-Mme-Client-Info",
HeaderValue::from_str(&self.client_info.client_info)?, HeaderValue::from_str(&self.client_info.client_info)?,