Dadoum commited on 2026-01-02 02:55:15
Showing 6 changed files, with 307 additions and 226 deletions.
| ... | ... |
@@ -2,7 +2,9 @@ mod anisette; |
| 2 | 2 |
mod post_data; |
| 3 | 3 |
mod secondary_actions; |
| 4 | 4 |
mod url_switch; |
| 5 |
+mod authenticated_session; |
|
| 5 | 6 |
|
| 7 |
+pub use authenticated_session::*; |
|
| 6 | 8 |
use crate::bundle_information::BundleInformation; |
| 7 | 9 |
use crate::device::Device; |
| 8 | 10 |
use crate::grandslam::AuthOutcome::AnisetteResyncRequired; |
| ... | ... |
@@ -24,7 +26,7 @@ use bytes::Bytes; |
| 24 | 26 |
use cbc::cipher::{BlockModeDecrypt, KeyIvInit};
|
| 25 | 27 |
use chrono::{Local, SecondsFormat};
|
| 26 | 28 |
use hmac::{Hmac, KeyInit, Mac};
|
| 27 |
-use log::trace; |
|
| 29 |
+use log::{trace, warn};
|
|
| 28 | 30 |
use plist::{Dictionary, Value};
|
| 29 | 31 |
use plist_macros::{array, dict};
|
| 30 | 32 |
pub use post_data::*; |
| ... | ... |
@@ -320,7 +322,7 @@ pub fn build_client_provided_data( |
| 320 | 322 |
|
| 321 | 323 |
#[repr(u64)] |
| 322 | 324 |
enum StatusCode {
|
| 323 |
- Success = 0, |
|
| 325 |
+ Success = 200, |
|
| 324 | 326 |
SecondaryActionRequired = 409, |
| 325 | 327 |
AnisetteReprovisionRequired = 433, |
| 326 | 328 |
AnisetteResyncRequired = 434, |
| ... | ... |
@@ -344,24 +346,6 @@ impl TryFrom<u64> for StatusCode {
|
| 344 | 346 |
} |
| 345 | 347 |
} |
| 346 | 348 |
|
| 347 |
-#[derive(Debug, Deserialize)] |
|
| 348 |
-pub struct GSToken {
|
|
| 349 |
- pub duration: u64, |
|
| 350 |
- // #[serde(rename = "cts")] |
|
| 351 |
- // pub start_epoch_millis: u64, |
|
| 352 |
- #[serde(rename = "expiry")] |
|
| 353 |
- pub expiry_epoch_millis: u64, |
|
| 354 |
- pub token: String, |
|
| 355 |
-} |
|
| 356 |
- |
|
| 357 |
-#[derive(Debug)] |
|
| 358 |
-pub struct AuthToken {
|
|
| 359 |
- pub alt_dsid: String, |
|
| 360 |
- pub idms_token: String, |
|
| 361 |
- pub session_key: Vec<u8>, |
|
| 362 |
- pub cookie: Vec<u8>, |
|
| 363 |
-} |
|
| 364 |
- |
|
| 365 | 349 |
pub async fn login( |
| 366 | 350 |
http_session: &AnisetteHTTPSession<'_, '_>, |
| 367 | 351 |
apple_id: &str, |
| ... | ... |
@@ -501,6 +485,8 @@ pub async fn login( |
| 501 | 485 |
let response_plist: Dictionary = |
| 502 | 486 |
plist::from_bytes(&response).map_err(|err| AuthError::Parsing(2, err))?; |
| 503 | 487 |
|
| 488 |
+ trace!("Received: {:#?}", response_plist);
|
|
| 489 |
+ |
|
| 504 | 490 |
let response_dict = response_plist |
| 505 | 491 |
.get("Response")
|
| 506 | 492 |
.and_then(|response| response.as_dictionary()) |
| ... | ... |
@@ -616,201 +602,3 @@ pub async fn login( |
| 616 | 602 |
} |
| 617 | 603 |
} |
| 618 | 604 |
} |
| 619 | 605 |
\ No newline at end of file |
| 620 |
- |
|
| 621 |
-pub fn parse_tokens_from_server_provided_data( |
|
| 622 |
- server_provided_data: &Dictionary, |
|
| 623 |
-) -> Option<(AuthToken, Vec<(String, GSToken)>)> {
|
|
| 624 |
- let alt_dsid = server_provided_data |
|
| 625 |
- .get("adsid")
|
|
| 626 |
- .and_then(|alt_dsid| alt_dsid.as_string()) |
|
| 627 |
- .map(|alt_dsid| alt_dsid.to_string()); |
|
| 628 |
- |
|
| 629 |
- let idms_token = server_provided_data |
|
| 630 |
- .get("GsIdmsToken")
|
|
| 631 |
- .and_then(|idms_token| idms_token.as_string()) |
|
| 632 |
- .map(|idms_token| idms_token.to_string()); |
|
| 633 |
- |
|
| 634 |
- let session_key = server_provided_data |
|
| 635 |
- .get("sk")
|
|
| 636 |
- .and_then(|session_key| session_key.as_data()) |
|
| 637 |
- .map(|session_key| session_key.to_vec()); |
|
| 638 |
- |
|
| 639 |
- let cookie = server_provided_data |
|
| 640 |
- .get("c")
|
|
| 641 |
- .and_then(|cookie| cookie.as_data()) |
|
| 642 |
- .map(|cookie| cookie.to_vec()); |
|
| 643 |
- |
|
| 644 |
- let tokens = server_provided_data |
|
| 645 |
- .get("t")
|
|
| 646 |
- .and_then(|tokens| tokens.as_dictionary()) |
|
| 647 |
- .map(|tokens| {
|
|
| 648 |
- tokens |
|
| 649 |
- .iter() |
|
| 650 |
- .filter_map(|(key, token)| {
|
|
| 651 |
- plist::from_value(token) |
|
| 652 |
- .ok() |
|
| 653 |
- .map(|token| (key.clone(), token)) |
|
| 654 |
- }) |
|
| 655 |
- .collect() |
|
| 656 |
- }); |
|
| 657 |
- |
|
| 658 |
- match (alt_dsid, idms_token, session_key, cookie) {
|
|
| 659 |
- (Some(alt_dsid), Some(idms_token), Some(session_key), Some(cookie)) => Some(( |
|
| 660 |
- AuthToken {
|
|
| 661 |
- alt_dsid, |
|
| 662 |
- idms_token, |
|
| 663 |
- session_key, |
|
| 664 |
- cookie, |
|
| 665 |
- }, |
|
| 666 |
- tokens.unwrap_or_default(), |
|
| 667 |
- )), |
|
| 668 |
- _ => None, |
|
| 669 |
- } |
|
| 670 |
-} |
|
| 671 |
- |
|
| 672 |
-#[derive(Debug)] |
|
| 673 |
-pub enum AppTokenRequestError {
|
|
| 674 |
- InvalidURLBag, |
|
| 675 |
- Anisette(ADIError), |
|
| 676 |
- InvalidAuthToken, |
|
| 677 |
- Network(reqwest::Error), |
|
| 678 |
- Parsing(plist::Error), |
|
| 679 |
- Structure(Dictionary), |
|
| 680 |
- Apple(AppleError), |
|
| 681 |
- InvalidResponse, |
|
| 682 |
-} |
|
| 683 |
- |
|
| 684 |
-impl std::fmt::Display for AppTokenRequestError {
|
|
| 685 |
- fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
| 686 |
- match self {
|
|
| 687 |
- AppTokenRequestError::InvalidURLBag => write!(f, "Invalid URL bag"), |
|
| 688 |
- AppTokenRequestError::Anisette(err) => {
|
|
| 689 |
- write!(f, "Cannot generate device authentication data: {err}")
|
|
| 690 |
- } |
|
| 691 |
- AppTokenRequestError::InvalidAuthToken => {
|
|
| 692 |
- write!(f, "The provided authentication token is not valid") |
|
| 693 |
- } |
|
| 694 |
- AppTokenRequestError::Network(err) => write!(f, "Network error: {err}"),
|
|
| 695 |
- AppTokenRequestError::Parsing(err) => {
|
|
| 696 |
- write!(f, "Failed to parse the app token request response: {err}")
|
|
| 697 |
- } |
|
| 698 |
- AppTokenRequestError::Structure(_) => {
|
|
| 699 |
- write!(f, "Failed to parse the app token request response") |
|
| 700 |
- } |
|
| 701 |
- AppTokenRequestError::Apple(err) => write!(f, "Cannot proceed: {err}"),
|
|
| 702 |
- AppTokenRequestError::InvalidResponse => {
|
|
| 703 |
- write!(f, "Invalid token returned by the server") |
|
| 704 |
- } |
|
| 705 |
- } |
|
| 706 |
- } |
|
| 707 |
-} |
|
| 708 |
- |
|
| 709 |
-impl std::error::Error for AppTokenRequestError {}
|
|
| 710 |
- |
|
| 711 |
-pub async fn get_app_token( |
|
| 712 |
- http_session: &AnisetteHTTPSession<'_, '_>, |
|
| 713 |
- auth_token: &AuthToken, |
|
| 714 |
- app_token_identifier: &str, |
|
| 715 |
-) -> Result<GSToken, AppTokenRequestError> {
|
|
| 716 |
- let gs_service_url = http_session |
|
| 717 |
- .url_bag() |
|
| 718 |
- .get("gsService")
|
|
| 719 |
- .and_then(Value::as_string) |
|
| 720 |
- .ok_or(AppTokenRequestError::InvalidURLBag)?; |
|
| 721 |
- |
|
| 722 |
- let cpd = build_client_provided_data(http_session).map_err(AppTokenRequestError::Anisette)?; |
|
| 723 |
- |
|
| 724 |
- let checksum = Hmac::<Sha256>::new_from_slice(&auth_token.session_key) |
|
| 725 |
- .map_err(|_| AppTokenRequestError::InvalidAuthToken)? |
|
| 726 |
- .chain_update("apptokens")
|
|
| 727 |
- .chain_update(&auth_token.alt_dsid) |
|
| 728 |
- .chain_update(app_token_identifier) |
|
| 729 |
- .finalize() |
|
| 730 |
- .into_bytes() |
|
| 731 |
- .to_vec(); |
|
| 732 |
- |
|
| 733 |
- let request_plist = dict! {
|
|
| 734 |
- "Header": dict!{
|
|
| 735 |
- "Version": "1.0.1" |
|
| 736 |
- }, |
|
| 737 |
- "Request": dict!{
|
|
| 738 |
- "u": auth_token.alt_dsid.clone(), |
|
| 739 |
- "app": array![ |
|
| 740 |
- app_token_identifier |
|
| 741 |
- ], |
|
| 742 |
- "c": Value::Data(auth_token.cookie.clone()), |
|
| 743 |
- "t": auth_token.idms_token.clone(), |
|
| 744 |
- "checksum": Value::Data(checksum), |
|
| 745 |
- "cpd": cpd, |
|
| 746 |
- "o": "apptokens", |
|
| 747 |
- } |
|
| 748 |
- }; |
|
| 749 |
- |
|
| 750 |
- let mut request_body = Vec::new(); |
|
| 751 |
- plist::to_writer_xml(&mut request_body, &request_plist).unwrap(); |
|
| 752 |
- |
|
| 753 |
- let response = http_session |
|
| 754 |
- .anisette_request_builder(GRANDSLAM_DSID, Method::POST, gs_service_url) |
|
| 755 |
- .map_err(AppTokenRequestError::Anisette)? |
|
| 756 |
- .body(request_body) |
|
| 757 |
- .send() |
|
| 758 |
- .await |
|
| 759 |
- .map_err(AppTokenRequestError::Network)? |
|
| 760 |
- .bytes() |
|
| 761 |
- .await |
|
| 762 |
- .map_err(AppTokenRequestError::Network)?; |
|
| 763 |
- |
|
| 764 |
- let response_plist: Dictionary = |
|
| 765 |
- plist::from_bytes(&response).map_err(AppTokenRequestError::Parsing)?; |
|
| 766 |
- |
|
| 767 |
- let response_dict = response_plist |
|
| 768 |
- .get("Response")
|
|
| 769 |
- .and_then(|response| response.as_dictionary()) |
|
| 770 |
- .ok_or(AppTokenRequestError::Structure(response_plist.clone()))?; |
|
| 771 |
- |
|
| 772 |
- // println!("Response: {response_dict:?}");
|
|
| 773 |
- |
|
| 774 |
- let status = response_dict |
|
| 775 |
- .get("Status")
|
|
| 776 |
- .and_then(|status| status.as_dictionary()) |
|
| 777 |
- .ok_or(AppTokenRequestError::Structure(response_plist.clone()))?; |
|
| 778 |
- |
|
| 779 |
- parse_status(status).map_err(AppTokenRequestError::Apple)?; |
|
| 780 |
- |
|
| 781 |
- let encrypted_tokens = response_dict |
|
| 782 |
- .get("et")
|
|
| 783 |
- .and_then(|et| et.as_data()) |
|
| 784 |
- .ok_or(AppTokenRequestError::Structure(response_plist.clone()))?; |
|
| 785 |
- |
|
| 786 |
- let associated_data = &encrypted_tokens[0..3]; |
|
| 787 |
- if associated_data != b"XYZ" {
|
|
| 788 |
- return Err(AppTokenRequestError::InvalidResponse); |
|
| 789 |
- } |
|
| 790 |
- |
|
| 791 |
- let iv = &encrypted_tokens[3..19]; |
|
| 792 |
- let encrypted_token = &encrypted_tokens[19..]; |
|
| 793 |
- |
|
| 794 |
- let tokens_data = AesGcm::<Aes256, U16>::new_from_slice(&auth_token.session_key) |
|
| 795 |
- .map_err(|_| AppTokenRequestError::InvalidAuthToken)? |
|
| 796 |
- .decrypt( |
|
| 797 |
- // iv is of fixed size. It shall not fail. |
|
| 798 |
- iv.try_into().expect("Invalid IV size??"),
|
|
| 799 |
- Payload {
|
|
| 800 |
- msg: encrypted_token, |
|
| 801 |
- aad: associated_data, |
|
| 802 |
- }, |
|
| 803 |
- ) |
|
| 804 |
- .map_err(|_| AppTokenRequestError::InvalidResponse)?; |
|
| 805 |
- |
|
| 806 |
- let tokens: Dictionary = |
|
| 807 |
- plist::from_bytes(&tokens_data).map_err(AppTokenRequestError::Parsing)?; |
|
| 808 |
- |
|
| 809 |
- trace!("Decrypted token response: {:#?}", tokens);
|
|
| 810 |
- |
|
| 811 |
- tokens |
|
| 812 |
- .get("t")
|
|
| 813 |
- .and_then(|token| token.as_dictionary()) |
|
| 814 |
- .and_then(|token| token.get(app_token_identifier)) |
|
| 815 |
- .and_then(|token| plist::from_value(token).ok()) |
|
| 816 |
- .ok_or(AppTokenRequestError::InvalidResponse) |
|
| 817 |
-} |
| ... | ... |
@@ -0,0 +1,288 @@ |
| 1 |
+use aes::Aes256; |
|
| 2 |
+use aes::cipher::consts::U16; |
|
| 3 |
+use aes_gcm::aead::{Aead, Payload};
|
|
| 4 |
+use aes_gcm::{AesGcm, KeyInit};
|
|
| 5 |
+use base64::Engine; |
|
| 6 |
+use base64::prelude::BASE64_STANDARD; |
|
| 7 |
+use hmac::{Hmac, Mac};
|
|
| 8 |
+use log::{trace, warn};
|
|
| 9 |
+use plist::{Dictionary, Value};
|
|
| 10 |
+use reqwest::{Method, RequestBuilder};
|
|
| 11 |
+use serde::Deserialize; |
|
| 12 |
+use sha2::Sha256; |
|
| 13 |
+use adi::proxy::{ADIError, ADIResult};
|
|
| 14 |
+use plist_macros::{array, dict};
|
|
| 15 |
+use crate::grandslam::{build_client_provided_data, GRANDSLAM_DSID};
|
|
| 16 |
+use crate::http_session::{parse_status, AnisetteHTTPSession, AppleError, URLBag};
|
|
| 17 |
+ |
|
| 18 |
+#[derive(Debug, Deserialize)] |
|
| 19 |
+pub struct GSToken {
|
|
| 20 |
+ pub duration: u64, |
|
| 21 |
+ // #[serde(rename = "cts")] |
|
| 22 |
+ // pub start_epoch_millis: u64, |
|
| 23 |
+ #[serde(rename = "expiry")] |
|
| 24 |
+ pub expiry_epoch_millis: u64, |
|
| 25 |
+ pub token: String, |
|
| 26 |
+} |
|
| 27 |
+ |
|
| 28 |
+#[derive(Debug)] |
|
| 29 |
+pub struct AuthToken {
|
|
| 30 |
+ pub alt_dsid: String, |
|
| 31 |
+ pub idms_token: String, |
|
| 32 |
+ pub session_key: Vec<u8>, |
|
| 33 |
+ pub cookie: Vec<u8>, |
|
| 34 |
+ pub identity_token: String, |
|
| 35 |
+} |
|
| 36 |
+ |
|
| 37 |
+impl AuthToken {
|
|
| 38 |
+ fn new(alt_dsid: String, idms_token: String, session_key: Vec<u8>, cookie: Vec<u8>) -> Self {
|
|
| 39 |
+ let identity_token = BASE64_STANDARD.encode(format!("{alt_dsid}:{idms_token}"));
|
|
| 40 |
+ AuthToken {
|
|
| 41 |
+ alt_dsid, |
|
| 42 |
+ idms_token, |
|
| 43 |
+ session_key, |
|
| 44 |
+ cookie, |
|
| 45 |
+ identity_token, |
|
| 46 |
+ } |
|
| 47 |
+ } |
|
| 48 |
+} |
|
| 49 |
+ |
|
| 50 |
+pub fn parse_tokens_from_server_provided_data( |
|
| 51 |
+ server_provided_data: &Dictionary, |
|
| 52 |
+) -> Option<(AuthToken, Vec<(String, GSToken)>)> {
|
|
| 53 |
+ let alt_dsid = server_provided_data |
|
| 54 |
+ .get("adsid")
|
|
| 55 |
+ .and_then(|alt_dsid| alt_dsid.as_string()) |
|
| 56 |
+ .map(|alt_dsid| alt_dsid.to_string()); |
|
| 57 |
+ |
|
| 58 |
+ let idms_token = server_provided_data |
|
| 59 |
+ .get("GsIdmsToken")
|
|
| 60 |
+ .and_then(|idms_token| idms_token.as_string()) |
|
| 61 |
+ .map(|idms_token| idms_token.to_string()); |
|
| 62 |
+ |
|
| 63 |
+ let session_key = server_provided_data |
|
| 64 |
+ .get("sk")
|
|
| 65 |
+ .and_then(|session_key| session_key.as_data()) |
|
| 66 |
+ .map(|session_key| session_key.to_vec()); |
|
| 67 |
+ |
|
| 68 |
+ let cookie = server_provided_data |
|
| 69 |
+ .get("c")
|
|
| 70 |
+ .and_then(|cookie| cookie.as_data()) |
|
| 71 |
+ .map(|cookie| cookie.to_vec()); |
|
| 72 |
+ |
|
| 73 |
+ let tokens = server_provided_data |
|
| 74 |
+ .get("t")
|
|
| 75 |
+ .and_then(|tokens| tokens.as_dictionary()) |
|
| 76 |
+ .map(|tokens| {
|
|
| 77 |
+ tokens |
|
| 78 |
+ .iter() |
|
| 79 |
+ .filter_map(|(key, token)| {
|
|
| 80 |
+ plist::from_value(token) |
|
| 81 |
+ .ok() |
|
| 82 |
+ .map(|token| (key.clone(), token)) |
|
| 83 |
+ }) |
|
| 84 |
+ .collect() |
|
| 85 |
+ }); |
|
| 86 |
+ |
|
| 87 |
+ match (alt_dsid, idms_token, session_key, cookie) {
|
|
| 88 |
+ (Some(alt_dsid), Some(idms_token), Some(session_key), Some(cookie)) => Some(( |
|
| 89 |
+ AuthToken::new( |
|
| 90 |
+ alt_dsid, |
|
| 91 |
+ idms_token, |
|
| 92 |
+ session_key, |
|
| 93 |
+ cookie, |
|
| 94 |
+ ), |
|
| 95 |
+ tokens.unwrap_or_default(), |
|
| 96 |
+ )), |
|
| 97 |
+ _ => None, |
|
| 98 |
+ } |
|
| 99 |
+} |
|
| 100 |
+ |
|
| 101 |
+#[derive(Debug)] |
|
| 102 |
+pub enum AppTokenRequestError {
|
|
| 103 |
+ InvalidURLBag, |
|
| 104 |
+ Anisette(ADIError), |
|
| 105 |
+ InvalidAuthToken, |
|
| 106 |
+ Network(reqwest::Error), |
|
| 107 |
+ Parsing(plist::Error), |
|
| 108 |
+ Structure(Dictionary), |
|
| 109 |
+ Apple(AppleError), |
|
| 110 |
+ InvalidResponse, |
|
| 111 |
+} |
|
| 112 |
+ |
|
| 113 |
+impl std::fmt::Display for AppTokenRequestError {
|
|
| 114 |
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
| 115 |
+ match self {
|
|
| 116 |
+ AppTokenRequestError::InvalidURLBag => write!(f, "Invalid URL bag"), |
|
| 117 |
+ AppTokenRequestError::Anisette(err) => {
|
|
| 118 |
+ write!(f, "Cannot generate device authentication data: {err}")
|
|
| 119 |
+ } |
|
| 120 |
+ AppTokenRequestError::InvalidAuthToken => {
|
|
| 121 |
+ write!(f, "The provided authentication token is not valid") |
|
| 122 |
+ } |
|
| 123 |
+ AppTokenRequestError::Network(err) => write!(f, "Network error: {err}"),
|
|
| 124 |
+ AppTokenRequestError::Parsing(err) => {
|
|
| 125 |
+ write!(f, "Failed to parse the app token request response: {err}")
|
|
| 126 |
+ } |
|
| 127 |
+ AppTokenRequestError::Structure(_) => {
|
|
| 128 |
+ write!(f, "Failed to parse the app token request response") |
|
| 129 |
+ } |
|
| 130 |
+ AppTokenRequestError::Apple(err) => write!(f, "Cannot proceed: {err}"),
|
|
| 131 |
+ AppTokenRequestError::InvalidResponse => {
|
|
| 132 |
+ write!(f, "Invalid token returned by the server") |
|
| 133 |
+ } |
|
| 134 |
+ } |
|
| 135 |
+ } |
|
| 136 |
+} |
|
| 137 |
+ |
|
| 138 |
+impl std::error::Error for AppTokenRequestError {}
|
|
| 139 |
+ |
|
| 140 |
+pub struct AuthenticatedHTTPSession<'lt, 'adi> {
|
|
| 141 |
+ pub http_session: AnisetteHTTPSession<'lt, 'adi>, |
|
| 142 |
+ pub auth_token: AuthToken, |
|
| 143 |
+} |
|
| 144 |
+ |
|
| 145 |
+impl<'a, 'b> AuthenticatedHTTPSession<'a, 'b> {
|
|
| 146 |
+ pub fn new(http_session: AnisetteHTTPSession<'a, 'b>, auth_token: AuthToken) -> Self {
|
|
| 147 |
+ Self {
|
|
| 148 |
+ http_session, |
|
| 149 |
+ auth_token, |
|
| 150 |
+ } |
|
| 151 |
+ } |
|
| 152 |
+ |
|
| 153 |
+ pub fn url_bag(&self) -> &URLBag { self.http_session.url_bag() }
|
|
| 154 |
+ pub fn simple_request_builder(&self, method: Method, url: &str) -> RequestBuilder { self.http_session.simple_request_builder(method, url) }
|
|
| 155 |
+ pub fn anisette_request_builder(&self, method: Method, url: &str) -> ADIResult<RequestBuilder> { self.http_session.anisette_request_builder(GRANDSLAM_DSID, method, url) }
|
|
| 156 |
+ pub fn authenticated_request_builder(&self, method: Method, url: &str) -> ADIResult<RequestBuilder> {
|
|
| 157 |
+ Ok( |
|
| 158 |
+ self.anisette_request_builder(method, url)? |
|
| 159 |
+ .header("X-Apple-Identity-Token", self.auth_token.identity_token.as_str())
|
|
| 160 |
+ ) |
|
| 161 |
+ } |
|
| 162 |
+ |
|
| 163 |
+ pub async fn get_app_token( |
|
| 164 |
+ &self, |
|
| 165 |
+ app_token_identifier: &str, // TODO: maybe create some type for app tokens identifier? |
|
| 166 |
+ ) -> Result<GSToken, AppTokenRequestError> {
|
|
| 167 |
+ let AuthenticatedHTTPSession { http_session, auth_token } = self;
|
|
| 168 |
+ |
|
| 169 |
+ let gs_service_url = http_session |
|
| 170 |
+ .url_bag() |
|
| 171 |
+ .get("gsService")
|
|
| 172 |
+ .and_then(Value::as_string) |
|
| 173 |
+ .ok_or(AppTokenRequestError::InvalidURLBag)?; |
|
| 174 |
+ |
|
| 175 |
+ let cpd = build_client_provided_data(&http_session).map_err(AppTokenRequestError::Anisette)?; |
|
| 176 |
+ |
|
| 177 |
+ let checksum = Hmac::<Sha256>::new_from_slice(&auth_token.session_key) |
|
| 178 |
+ .map_err(|_| AppTokenRequestError::InvalidAuthToken)? |
|
| 179 |
+ .chain_update("apptokens")
|
|
| 180 |
+ .chain_update(&auth_token.alt_dsid) |
|
| 181 |
+ .chain_update(app_token_identifier) |
|
| 182 |
+ .finalize() |
|
| 183 |
+ .into_bytes() |
|
| 184 |
+ .to_vec(); |
|
| 185 |
+ |
|
| 186 |
+ let request_plist = dict! {
|
|
| 187 |
+ "Header": dict!{
|
|
| 188 |
+ "Version": "1.0.1" |
|
| 189 |
+ }, |
|
| 190 |
+ "Request": dict!{
|
|
| 191 |
+ "u": auth_token.alt_dsid.clone(), |
|
| 192 |
+ "app": array![ |
|
| 193 |
+ app_token_identifier |
|
| 194 |
+ ], |
|
| 195 |
+ "c": Value::Data(auth_token.cookie.clone()), |
|
| 196 |
+ "t": auth_token.idms_token.clone(), |
|
| 197 |
+ "checksum": Value::Data(checksum), |
|
| 198 |
+ "cpd": cpd, |
|
| 199 |
+ "o": "apptokens", |
|
| 200 |
+ } |
|
| 201 |
+ }; |
|
| 202 |
+ |
|
| 203 |
+ let mut request_body = Vec::new(); |
|
| 204 |
+ plist::to_writer_xml(&mut request_body, &request_plist).unwrap(); |
|
| 205 |
+ |
|
| 206 |
+ let response = http_session |
|
| 207 |
+ .anisette_request_builder(GRANDSLAM_DSID, Method::POST, gs_service_url) |
|
| 208 |
+ .map_err(AppTokenRequestError::Anisette)? |
|
| 209 |
+ .body(request_body) |
|
| 210 |
+ .send() |
|
| 211 |
+ .await |
|
| 212 |
+ .map_err(AppTokenRequestError::Network)? |
|
| 213 |
+ .bytes() |
|
| 214 |
+ .await |
|
| 215 |
+ .map_err(AppTokenRequestError::Network)?; |
|
| 216 |
+ |
|
| 217 |
+ let response_plist: Dictionary = |
|
| 218 |
+ plist::from_bytes(&response).map_err(AppTokenRequestError::Parsing)?; |
|
| 219 |
+ |
|
| 220 |
+ let response_dict = response_plist |
|
| 221 |
+ .get("Response")
|
|
| 222 |
+ .and_then(|response| response.as_dictionary()) |
|
| 223 |
+ .ok_or(AppTokenRequestError::Structure(response_plist.clone()))?; |
|
| 224 |
+ |
|
| 225 |
+ // println!("Response: {response_dict:?}");
|
|
| 226 |
+ |
|
| 227 |
+ let status = response_dict |
|
| 228 |
+ .get("Status")
|
|
| 229 |
+ .and_then(|status| status.as_dictionary()) |
|
| 230 |
+ .ok_or(AppTokenRequestError::Structure(response_plist.clone()))?; |
|
| 231 |
+ |
|
| 232 |
+ parse_status(status).map_err(AppTokenRequestError::Apple)?; |
|
| 233 |
+ |
|
| 234 |
+ let encrypted_tokens = response_dict |
|
| 235 |
+ .get("et")
|
|
| 236 |
+ .and_then(|et| et.as_data()) |
|
| 237 |
+ .ok_or(AppTokenRequestError::Structure(response_plist.clone()))?; |
|
| 238 |
+ |
|
| 239 |
+ let associated_data = &encrypted_tokens[0..3]; |
|
| 240 |
+ if associated_data != b"XYZ" {
|
|
| 241 |
+ warn!("Surprised by the associated data provided by Apple: {associated_data:02X?}");
|
|
| 242 |
+ // return Err(AppTokenRequestError::InvalidResponse); |
|
| 243 |
+ } |
|
| 244 |
+ |
|
| 245 |
+ let iv = &encrypted_tokens[3..19]; |
|
| 246 |
+ let encrypted_token = &encrypted_tokens[19..]; |
|
| 247 |
+ |
|
| 248 |
+ let tokens_data = AesGcm::<Aes256, U16>::new_from_slice(&auth_token.session_key) |
|
| 249 |
+ .map_err(|_| AppTokenRequestError::InvalidAuthToken)? |
|
| 250 |
+ .decrypt( |
|
| 251 |
+ // iv is of fixed size. It shall not fail. |
|
| 252 |
+ iv.try_into().expect("Invalid IV size??"),
|
|
| 253 |
+ Payload {
|
|
| 254 |
+ msg: encrypted_token, |
|
| 255 |
+ aad: associated_data, |
|
| 256 |
+ }, |
|
| 257 |
+ ) |
|
| 258 |
+ .map_err(|_| AppTokenRequestError::InvalidResponse)?; |
|
| 259 |
+ |
|
| 260 |
+ let tokens: Dictionary = |
|
| 261 |
+ plist::from_bytes(&tokens_data).map_err(AppTokenRequestError::Parsing)?; |
|
| 262 |
+ |
|
| 263 |
+ trace!("Decrypted token response: {:#?}", tokens);
|
|
| 264 |
+ |
|
| 265 |
+ tokens |
|
| 266 |
+ .get("t")
|
|
| 267 |
+ .and_then(|token| token.as_dictionary()) |
|
| 268 |
+ .and_then(|token| token.get(app_token_identifier)) |
|
| 269 |
+ .and_then(|token| plist::from_value(token).ok()) |
|
| 270 |
+ .ok_or(AppTokenRequestError::InvalidResponse) |
|
| 271 |
+ } |
|
| 272 |
+ |
|
| 273 |
+ pub async fn validate_code(&self, validation_code: &str) -> Result<(), AppTokenRequestError> { // TODO: change that error type.
|
|
| 274 |
+ let validate_code_url = self |
|
| 275 |
+ .url_bag() |
|
| 276 |
+ .get("validateCode")
|
|
| 277 |
+ .and_then(Value::as_string) |
|
| 278 |
+ .ok_or(AppTokenRequestError::InvalidURLBag)?; |
|
| 279 |
+ |
|
| 280 |
+ let _ = |
|
| 281 |
+ self.authenticated_request_builder(Method::POST, validate_code_url) |
|
| 282 |
+ .map_err(AppTokenRequestError::Anisette)? |
|
| 283 |
+ .header("security-code", validation_code);
|
|
| 284 |
+ |
|
| 285 |
+ todo!() |
|
| 286 |
+ } |
|
| 287 |
+ |
|
| 288 |
+} |
| ... | ... |
@@ -5,7 +5,6 @@ edition = "2021" |
| 5 | 5 |
|
| 6 | 6 |
[dependencies] |
| 7 | 7 |
adi.workspace = true |
| 8 |
-adid-proxy.workspace = true |
|
| 9 | 8 |
android-coreadi.workspace = true |
| 10 | 9 |
apple-account.workspace = true |
| 11 | 10 |
elf-loader.workspace = true |
| ... | ... |
@@ -13,3 +12,6 @@ elf-loader.workspace = true |
| 13 | 12 |
tokio.workspace = true |
| 14 | 13 |
libc = "0.2.176" |
| 15 | 14 |
env_logger = "0.11.8" |
| 15 |
+ |
|
| 16 |
+[target.'cfg(target_os = "macos")'.dependencies] |
|
| 17 |
+adid-proxy.workspace = true |
| ... | ... |
@@ -2,7 +2,7 @@ use adi::proxy::ADIProxy; |
| 2 | 2 |
use android_coreadi::AndroidCoreADIProxy; |
| 3 | 3 |
use apple_account::bundle_information::{APPLE_TV_BUNDLE_INFORMATION, XCODE_BUNDLE_INFORMATION};
|
| 4 | 4 |
use apple_account::device::Device; |
| 5 |
-use apple_account::grandslam::AuthOutcome; |
|
| 5 |
+use apple_account::grandslam::{AuthOutcome, AuthenticatedHTTPSession};
|
|
| 6 | 6 |
use apple_account::http_session::AnisetteHTTPSession; |
| 7 | 7 |
use apple_account::{grandslam, itunes};
|
| 8 | 8 |
use std::{env, fs};
|
| ... | ... |
@@ -38,9 +38,13 @@ async fn grandslam_test( |
| 38 | 38 |
AuthOutcome::Success(server_provided_data) |
| 39 | 39 |
| AuthOutcome::SecondaryActionRequired(server_provided_data, _) => {
|
| 40 | 40 |
match grandslam::parse_tokens_from_server_provided_data(server_provided_data) {
|
| 41 |
- Some((token, _)) => {
|
|
| 41 |
+ Some((auth_token, _)) => {
|
|
| 42 |
+ let http_session = AuthenticatedHTTPSession::new( |
|
| 43 |
+ http_session, |
|
| 44 |
+ auth_token |
|
| 45 |
+ ); |
|
| 42 | 46 |
let xcode_token = |
| 43 |
- grandslam::get_app_token(&http_session, &token, "com.apple.gs.xcode.auth") |
|
| 47 |
+ http_session.get_app_token("com.apple.gs.xcode.auth")
|
|
| 44 | 48 |
.await?; |
| 45 | 49 |
println!("token: {:?}", xcode_token);
|
| 46 | 50 |
} |
| ... | ... |
@@ -93,7 +97,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
| 93 | 97 |
println!("{}", code);
|
| 94 | 98 |
} |
| 95 | 99 |
let proxy = {
|
| 96 |
- let core_adi_data = fs::read("nodistrib/lib/arm64-v8a/libCoreADI.so")?;
|
|
| 100 |
+ let core_adi_data = fs::read("nodistrib/lib/x86_64/libCoreADI.so")?;
|
|
| 97 | 101 |
let proxy = AndroidCoreADIProxy::load_library(core_adi_data)?; |
| 98 | 102 |
|
| 99 | 103 |
proxy.set_android_id("0123456789012345")?;
|
| 100 | 104 |