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::{
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<String> = 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();

View File

@@ -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?;

View File

@@ -156,7 +156,7 @@ impl AppleAccount {
password: &str,
two_factor_callback: impl Fn() -> Option<String>,
) -> 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<String>,
) -> 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<String>,
) -> 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<HeaderMap, Report> {
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()),
});
}

View File

@@ -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<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)
}
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)
}
@@ -117,10 +123,12 @@ impl GrandSlam {
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();
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)?,