mirror of
https://github.com/nab138/isideload.git
synced 2026-03-02 06:26:16 +01:00
Add SMS 2FA
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
@@ -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?;
|
||||||
|
|||||||
@@ -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,55 +184,19 @@ impl AppleAccount {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
LoginState::NeedsDevice2FA => {
|
LoginState::NeedsDevice2FA => {
|
||||||
debug!("Trusted device 2FA required");
|
self.trusted_device_2fa(&two_factor_callback)
|
||||||
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
|
.await
|
||||||
.context("Failed to request trusted device 2fa")?
|
.context("Failed to complete 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")?;
|
|
||||||
|
|
||||||
debug!("Trusted device 2FA completed, need to login again");
|
debug!("Trusted device 2FA completed, need to login again");
|
||||||
self.login_state = LoginState::NeedsLogin;
|
self.login_state = LoginState::NeedsLogin;
|
||||||
}
|
}
|
||||||
LoginState::NeedsSMS2FA => {
|
LoginState::NeedsSMS2FA => {
|
||||||
info!("SMS 2FA required");
|
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::NeedsExtraStep(_) => todo!(),
|
||||||
LoginState::NeedsLogin => {
|
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> {
|
async fn build_2fa_headers(&mut self) -> Result<HeaderMap, Report> {
|
||||||
let mut headers = self.anisette_data.get_header_map(SERIAL_NUMBER.to_string());
|
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() {
|
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()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
headers.insert("Content-Type", HeaderValue::from_static("text/x-xml-plist"));
|
if !sms {
|
||||||
headers.insert("Accept", 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(
|
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)?,
|
||||||
|
|||||||
Reference in New Issue
Block a user