Skip to content

Commit 7685b82

Browse files
committed
Setup and keychain fixes
1 parent 8eff625 commit 7685b82

7 files changed

Lines changed: 191 additions & 22 deletions

File tree

apple-private-apis

src/auth.rs

Lines changed: 162 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rand::Rng;
2121
use aes_gcm::{Aes128Gcm, KeyInit, Nonce, aead::Aead};
2222
use deku::{DekuContainerWrite, DekuUpdate};
2323
use x509_cert::{attr::AttributeTypeAndValue, der::{EncodePem, asn1::{BitString, Null, SetOfVec, Utf8StringRef}, pem::LineEnding}, name::{Name, RdnSequence, RelativeDistinguishedName}, request::{CertReq, CertReqInfo}, spki::{AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo}};
24+
use xml::{EventReader, reader::{self, ParserConfig2}};
2425

2526
use crate::{APSConnection, APSConnectionResource, APSMessage, APSState, OSConfig, PushError, aps::{APSInterestToken, get_message}, ids::user::{IDSUser, IDSUserIdentity, IDSUserType}, keychain::{EncodedPeer, KeychainClient, PrivateUserIdentity}, util::{DebugMutex, IDS_BAG, KeyPair, KeyPairNew, PhoneNumberResponse, REQWEST, base64_decode, base64_encode, decode_hex, duration_since_epoch, encode_hex, get_bag, gzip, gzip_normal, plist_to_bin, plist_to_buf, plist_to_string, ungzip}};
2627

@@ -158,14 +159,9 @@ impl<T: AnisetteProvider> TokenProvider<T> {
158159

159160
pub async fn refresh_mme(&self) -> Result<(), PushError> {
160161
let mut mme = self.mme_delegate.lock().await;
161-
let pet = self.get_gsa_token("com.apple.gs.idms.pet").await.ok_or(PushError::TokenMissing)?;
162-
let account = self.account.lock().await;
163-
164-
let Some(spd) = &account.spd else { panic!("No spd!") };
165-
let adsid = spd.get("adsid").expect("No adsid!").as_string().unwrap();
166162

167-
let delegates = login_apple_delegates(account.username.as_ref().unwrap(), &pet, adsid, None,
168-
&mut *account.anisette.lock().await, &*self.os_config, &[LoginDelegate::MobileMe]).await?;
163+
self.get_gsa_token("com.apple.gs.idms.pet").await.ok_or(PushError::TokenMissing)?;
164+
let delegates = login_apple_delegates(&mut *self.account.lock().await, None, &*self.os_config, &[LoginDelegate::MobileMe]).await?;
169165

170166
*mme = delegates.mobileme;
171167
*self.mme_refreshed.lock().await = SystemTime::now();
@@ -202,7 +198,158 @@ pub struct DelegateResponses {
202198
pub mobileme: Option<MobileMeDelegateResponse>,
203199
}
204200

205-
pub async fn login_apple_delegates<T: AnisetteProvider>(username: &str, pet: &str, adsid: &str, cookie: Option<&str>, anisette: &mut AnisetteClient<T>, os_config: &dyn OSConfig, delegates: &[LoginDelegate]) -> Result<DelegateResponses, PushError> {
201+
pub enum UpdateAccountFinish {
202+
MacOS,
203+
IOS {
204+
url: String,
205+
}
206+
}
207+
208+
impl UpdateAccountFinish {
209+
pub async fn accept_terms<T: AnisetteProvider>(&self, delegates: &[LoginDelegate], account: &AppleAccount<T>, os_config: &dyn OSConfig) -> Result<DelegateResponses, PushError> {
210+
match self {
211+
Self::MacOS => {
212+
login_apple_delegates(account, Some("termsAccepted=true"), os_config, delegates).await
213+
},
214+
Self::IOS { url } => {
215+
let header_map = build_setup_headers(account, os_config).await?;
216+
217+
let text = REQWEST.post(url)
218+
.header("Accept", "*/*")
219+
.header("Accept-Encoding", "gzip, deflate, br")
220+
.header("Accept-Language", "en-US,en")
221+
.header("Content-Type", "application/xml")
222+
.headers(header_map)
223+
.send().await?;
224+
225+
if !text.status().is_success() {
226+
return Err(PushError::FailedToAcceptTOS(text.text().await?))
227+
}
228+
229+
text.bytes().await?;
230+
231+
login_apple_delegates(account, None, os_config, delegates).await
232+
}
233+
}
234+
}
235+
}
236+
237+
async fn build_setup_headers<T: AnisetteProvider>(account: &AppleAccount<T>, os_config: &dyn OSConfig) -> Result<HeaderMap, PushError> {
238+
let mut map = HashMap::new();
239+
let base_headers = account.anisette.lock().await.get_headers().await?.clone();
240+
map.extend(base_headers);
241+
242+
map.extend([
243+
("Authorization", format!("Basic {}", base64::encode(format!("{}:{}", account.username.as_ref().unwrap().trim(), account.get_pet().expect("No pet b?"))))),
244+
("User-Agent", format!("iOS iPhone {} iPhone Setup Assistant", os_config.get_register_meta().software_version)),
245+
("Cookie", "repairSteps=".to_string()),
246+
("X-MMe-Country", "US".to_string()),
247+
("X-MMe-Language", "en,en-US".to_string()),
248+
("X-MMe-Client-Info", os_config.get_mme_clientinfo("com.apple.AppleAccount/1.0 (com.apple.Preferences/1112.96)")),
249+
].into_iter().map(|(a, b)| (a.to_string(), b)));
250+
251+
Ok(HeaderMap::from_iter(map.into_iter().map(|(a, b)| (HeaderName::from_str(&a).unwrap(), b.parse().unwrap()))))
252+
}
253+
254+
pub async fn request_update_account<T: AnisetteProvider>(account: &AppleAccount<T>, os_config: &dyn OSConfig) -> Result<(String, UpdateAccountFinish), PushError> {
255+
let data = os_config.get_version_ua();
256+
if data.contains("macOS") {
257+
let result = account.request_update_account().await?;
258+
Ok((result, UpdateAccountFinish::MacOS))
259+
} else {
260+
let header_map = build_setup_headers(account, os_config).await?;
261+
262+
#[derive(Serialize)]
263+
struct RequestedTerms {
264+
name: &'static str,
265+
}
266+
267+
#[derive(Serialize)]
268+
struct TermsUIRequest {
269+
format: &'static str,
270+
terms: Vec<RequestedTerms>,
271+
}
272+
273+
let buffer = plist_to_string(&TermsUIRequest {
274+
format: "plist/buddyml",
275+
terms: vec![RequestedTerms {
276+
name: "iCloud",
277+
}]
278+
})?;
279+
280+
let text = REQWEST.post("https://setup.icloud.com/setup/iosbuddy/ui/genericTermsUI")
281+
.header("Accept", "application/x-buddyml")
282+
.header("Accept-Encoding", "gzip, deflate, br")
283+
.header("Accept-Language", "en-US,en")
284+
.header("Content-Type", "text/plist")
285+
.header("X-Apple-I-Appearance", "1")
286+
.headers(header_map)
287+
.body(buffer)
288+
.send().await?
289+
.bytes().await?;
290+
291+
let reader: EventReader<Cursor<_>> = EventReader::new_with_config(Cursor::new(&text),
292+
ParserConfig2::new().cdata_to_characters(true));
293+
let mut agree_url: Option<String> = None;
294+
let mut current_page: Option<String> = None;
295+
let mut current_page_html: Option<String> = None;
296+
297+
let mut pages: HashMap<String, String> = HashMap::new();
298+
299+
for e in reader {
300+
match e {
301+
Ok(reader::XmlEvent::StartElement { name, attributes, namespace: _ }) => {
302+
let get_attr = |name: &str, def: Option<&str>| {
303+
attributes.iter().find(|attr| attr.name.to_string() == name)
304+
.map_or_else(|| def.expect(&format!("attribute {} doesn't exist!", name)).to_string(), |data| data.value.to_string())
305+
};
306+
match name.local_name.as_str() {
307+
"clientInfo" => {
308+
agree_url = Some(get_attr("agreeUrl", None));
309+
},
310+
"page" => {
311+
current_page = Some(get_attr("id", Some("default")))
312+
},
313+
"html" => {
314+
current_page_html = current_page.clone();
315+
},
316+
_ => {}
317+
}
318+
},
319+
Ok(reader::XmlEvent::EndElement { name }) => {
320+
match name.local_name.as_str() {
321+
"page" => {
322+
current_page = None;
323+
},
324+
"html" => {
325+
current_page_html = None;
326+
},
327+
_ => {}
328+
}
329+
},
330+
Ok(reader::XmlEvent::Characters(data)) => {
331+
if let Some(page) = &current_page_html {
332+
pages.entry(page.clone()).or_default().push_str(&data);
333+
}
334+
}
335+
_ => {}
336+
}
337+
}
338+
339+
Ok((pages.remove("iCloud").unwrap_or_default(), UpdateAccountFinish::IOS { url: agree_url.expect("No agree url???") }))
340+
}
341+
}
342+
343+
344+
pub async fn login_apple_delegates<T: AnisetteProvider>(account: &AppleAccount<T>, cookie: Option<&str>, os_config: &dyn OSConfig, delegates: &[LoginDelegate]) -> Result<DelegateResponses, PushError> {
345+
let Some(pet) = account.get_pet() else { panic!("No pet!") };
346+
let Some(spd) = &account.spd else { panic!("No spd!") };
347+
348+
debug!("Got spd {:?}", spd);
349+
let adsid = spd.get("adsid").expect("No adsid!").as_string().unwrap();
350+
351+
let username = account.username.as_ref().unwrap();
352+
206353
let request = AuthRequest {
207354
apple_id: username.to_string(),
208355
client_id: Uuid::new_v4().to_string(),
@@ -212,7 +359,7 @@ pub async fn login_apple_delegates<T: AnisetteProvider>(username: &str, pet: &st
212359

213360
let validation_data = os_config.generate_validation_data().await?;
214361

215-
let base_headers = anisette.get_headers().await?;
362+
let base_headers = account.anisette.lock().await.get_headers().await?.clone();
216363
let mut anisette_headers: HeaderMap = base_headers.into_iter().map(|(a, b)| (HeaderName::from_str(&a).unwrap(), b.parse().unwrap())).collect();
217364

218365
if let Some(cookie) = cookie {
@@ -237,6 +384,9 @@ pub async fn login_apple_delegates<T: AnisetteProvider>(username: &str, pet: &st
237384

238385
if let Some(error) = parsed_dict.get("localizedError") {
239386
let error = error.as_string().unwrap();
387+
if error == "UNAUTHORIZED" {
388+
return Err(PushError::UnauthorizedAccountError);
389+
}
240390
return Err(PushError::MobileMeError(error.to_string(), parsed_dict.get("description").and_then(|d| d.as_string().map(|s| s.to_string()))));
241391
}
242392

@@ -979,7 +1129,9 @@ impl<P: AnisetteProvider> CircleClientSession<P> {
9791129
}
9801130

9811131
pub async fn setup_trusted_peers(&mut self, peers: Arc<KeychainClient<P>>, device_password: &[u8]) -> Result<(), PushError> {
982-
self.private_identity = Some(peers.new_user_identity().await?);
1132+
self.private_identity = Some(peers.new_user_identity(false).await?);
1133+
1134+
peers.sync_trust().await?;
9831135

9841136
let Some(request) = &self.saved_step else { return Ok(()) };
9851137
if request.step != 4 {

src/error.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ pub enum PushError {
3535
KeyError(KeyError),
3636
#[error("IDS key missing for {0}")]
3737
KeyNotFound(String),
38-
#[error("Could not deliver message. The recipient does not have iMessage or you are being rate-limited.")]
38+
#[error("Could not deliver message. The recipient does not have iMessage or you are being rate-limited. Rate limits can start at 0 users for brand new accounts. The only way to resolve a rate limit is patience, trying to reconfigure or re-install to 'fix' the rate limit will result in being temporarily blocked from iMessage.")]
3939
NoValidTargets,
4040
#[error("Failed to connect to APS {0}")]
4141
APSConnectError(u8),
@@ -48,7 +48,7 @@ pub enum PushError {
4848
#[cfg(feature = "macos-validation-data")]
4949
#[error("Absinthe error {0}")]
5050
AbsintheError(#[from] AbsintheError),
51-
#[error("Your iMessage access is temporarily disabled. Try again later, or ask Apple to allow you to use iMessage: https://apple.co/IMFT-mac. Choose 'Get Started,' not 'Chat.' Do not mention you are using OpenBubbles. Apple left a message: {0}")]
51+
#[error("Your iMessage access is temporarily disabled. Try again later, or ask Apple to allow you to use iMessage: https://apple.co/IMFT-mac. Choose 'Get Started,' not 'Chat,' and then choose 'Apple Device.' Do not mention you are using OpenBubbles. Apple left a message: {0}")]
5252
CustomerMessage(SupportAlert),
5353
#[error("Send timeout; try again")]
5454
SendTimedOut,
@@ -62,6 +62,8 @@ pub enum PushError {
6262
MMCSGetFailed(Option<String>),
6363
#[error("Failed to authenticate. Try logging in to account.apple.com to fix your Apple Account or create a new one: {1:?} {0}")]
6464
MobileMeError(String, Option<String>),
65+
#[error("You need to add a trusted phone number to your Apple Account at account.apple.com. You may be unable to add your phone number. You can also create a new account at account.apple.com. If you get 'Your account cannot be created at this time,' you need to contact Apple Support. For assistance, join our Discord from our website.")]
66+
UnauthorizedAccountError,
6567
#[error("Bad auth cert {0}")]
6668
AuthInvalid(IDSError),
6769
#[error("APS parse error {0}")]
@@ -208,4 +210,8 @@ pub enum PushError {
208210
NoRoutingKey,
209211
#[error("Removed from Share!")]
210212
RemovedFromShare,
213+
#[error("Failed to accept tos {0}!")]
214+
FailedToAcceptTOS(String),
215+
#[error("The device you have chosen is invalid! Please choose a different device.")]
216+
PeerNoShares,
211217
}

src/icloud/keychain.rs

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,7 @@ impl<P: AnisetteProvider> KeychainClient<P> {
14391439
self.delete(bottle.id()).await?;
14401440
}
14411441

1442-
let private = self.new_user_identity().await?;
1442+
let private = self.new_user_identity(true).await?;
14431443

14441444
let data = self.config.get_register_meta().os_version;
14451445
let mut item = data.split(",");
@@ -1738,7 +1738,7 @@ impl<P: AnisetteProvider> KeychainClient<P> {
17381738
})
17391739
}
17401740

1741-
pub async fn new_user_identity(&self) -> Result<PrivateUserIdentity, PushError> {
1741+
pub async fn new_user_identity(&self, reset_state: bool) -> Result<PrivateUserIdentity, PushError> {
17421742
let mut state = self.state.write().await;
17431743

17441744
let mut anisette_lock = self.anisette.lock().await;
@@ -1748,8 +1748,10 @@ impl<P: AnisetteProvider> KeychainClient<P> {
17481748
let (identity, private) = KeychainUserIdentity::new(&machine_id, &self.config.get_register_meta().hardware_version)?;
17491749

17501750
state.current_bottle = None;
1751-
state.state = HashMap::new();
1752-
state.state_token = None;
1751+
if reset_state {
1752+
state.state = HashMap::new();
1753+
state.state_token = None;
1754+
}
17531755
state.user_identity = Some(identity);
17541756
state.keystore.0.clear();
17551757
state.items.clear();
@@ -1801,7 +1803,10 @@ impl<P: AnisetteProvider> KeychainClient<P> {
18011803
};
18021804
let item = CuttlefishTlkShare::from_record(&share_record.inner.as_ref().unwrap().record_field);
18031805

1804-
let Some(sending_peer) = state.state.get(&item.sender) else { continue };
1806+
let Some(sending_peer) = state.state.get(&item.sender) else {
1807+
warn!("missing sender {} in state! {:?}", item.sender, state.state.keys().collect::<Vec<_>>());
1808+
continue
1809+
};
18051810
sending_peer.verify_signature_dig(MessageDigest::sha256(), &item.data_for_signing(), &base64_decode(&item.signature))?;
18061811

18071812

@@ -1900,7 +1905,7 @@ impl<P: AnisetteProvider> KeychainClient<P> {
19001905
pub async fn join_clique_from_escrow(&self, bottle: &EscrowData, password: &[u8], device_password: &[u8]) -> Result<(), PushError> {
19011906
let other_identity = self.recover_bottle(bottle, password).await?;
19021907

1903-
let new_identity = self.new_user_identity().await?;
1908+
let new_identity = self.new_user_identity(false).await?;
19041909
let state = self.state.read().await;
19051910
let my_identity = state.user_identity.as_ref().unwrap();
19061911

@@ -1910,6 +1915,11 @@ impl<P: AnisetteProvider> KeychainClient<P> {
19101915
drop(state);
19111916

19121917
let shares = self.fetch_shares_for(&other_identity).await?;
1918+
if shares.is_empty() {
1919+
return Err(PushError::PeerNoShares)
1920+
}
1921+
info!("Joining with {} shared keys.", shares.len());
1922+
19131923
self.join_clique(device_password, &new_identity, Some(voucher), &shares, vec![]).await?;
19141924
Ok(())
19151925
}

src/ids/user.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ impl Display for IDSError {
10081008
6001 => write!(f, "Incompatible; Make sure Contact Key Verification and Advanced Data Protection are off. (6001)"),
10091009
6004 => write!(f, "Please try again (6004)"),
10101010
6005 => write!(f, "Bad authentication, try again and re-enter device details if persistent. (6005)"),
1011-
6009 => write!(f, "Your iMessage access is temporarily disabled. Try again later, or ask Apple to allow you to use iMessage: https://apple.co/IMFT-mac. Choose 'Get Started,' not 'Chat.' Do not mention you are using OpenBubbles. (6009)"),
1011+
6009 => write!(f, "Your iMessage access is temporarily disabled. Try again later, or ask Apple to allow you to use iMessage: https://apple.co/IMFT-mac. Choose 'Get Started,' not 'Chat,' and then choose 'Apple Device.' Do not mention you are using OpenBubbles. (6009)"),
10121012
5052 => write!(f, "An alias was just removed from your account. Try again. (5052)"),
10131013
_unk => write!(f, "Unknown IDS error {_unk}")
10141014
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub use util::{DebugRwLock, DebugMutex};
4141
use activation::ActivationInfo;
4242
pub use aps::{APSConnectionResource, APSConnection, APSMessage, APSState};
4343
use async_trait::async_trait;
44+
pub use auth::{request_update_account, UpdateAccountFinish};
4445
pub use mmcs::{FileContainer, prepare_put};
4546
pub use omnisette::AnisetteProvider;
4647
pub use imessage::messages::{TypingApp, SetTranscriptBackgroundMessage, UpdateProfileMessage, UpdateProfileSharingMessage, MessageInst, ShareProfileMessage, SharedPoster, ScheduleMode, PermanentDeleteMessage, OperatedChat, DeleteTarget, MoveToRecycleBinMessage, TextFormat, TextEffect, TextFlags, LinkMeta, LPLinkMetadata, LPSpecializationMetadata, ReactMessageType, ErrorMessage, Reaction, UnsendMessage, EditMessage, UpdateExtensionMessage, PartExtension, ReactMessage, ChangeParticipantMessage, LPImageMetadata, RichLinkImageAttachmentSubstitute, LPIconMetadata, AttachmentType, ExtensionApp, BalloonLayout, Balloon, ConversationData, Message, MessageType, Attachment, NormalMessage, RenameMessage, IconChangeMessage, MessageParts, MessagePart, MMCSFile, IndexedMessagePart};

src/util.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2313,10 +2313,10 @@ impl<T: Write> StreamTypedCoder<T> {
23132313
self.buffer.write_all(&[num as u8]).unwrap();
23142314
} else if num & 0xffff == num {
23152315
self.buffer.write_all(&[0x81]).unwrap();
2316-
self.buffer.write_all(&(num as u16).to_le_bytes()).unwrap();;
2316+
self.buffer.write_all(&(num as u16).to_le_bytes()).unwrap();
23172317
} else {
23182318
self.buffer.write_all(&[0x82]).unwrap();
2319-
self.buffer.write_all(&num.to_le_bytes()).unwrap();;
2319+
self.buffer.write_all(&num.to_le_bytes()).unwrap();
23202320
}
23212321
}
23222322

0 commit comments

Comments
 (0)