Skip to content

Commit b3ceeee

Browse files
committed
Auth fixes and circle support
1 parent 5485396 commit b3ceeee

File tree

5 files changed

+368
-16
lines changed

5 files changed

+368
-16
lines changed

src/auth.rs

Lines changed: 280 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,23 @@
1-
use std::{collections::HashMap, io::Cursor, marker::PhantomData, str::FromStr, time::{SystemTime, UNIX_EPOCH}};
1+
use std::{collections::HashMap, io::Cursor, marker::PhantomData, str::FromStr, sync::Arc, time::{SystemTime, UNIX_EPOCH}};
22

3-
use log::debug;
3+
use aes::{cipher::consts::U16, Aes128};
4+
use hkdf::Hkdf;
5+
use icloud_auth::{AppleAccount, CircleSendMessage};
6+
use log::{debug, warn};
47
use omnisette::{AnisetteClient, AnisetteProvider};
58
use openssl::{hash::MessageDigest, nid::Nid, pkey::{PKey, Private}, rsa::{Padding, Rsa}, sha::sha1, sign::Signer, x509::{X509Name, X509Req}};
69
use plist::{Data, Dictionary, Value};
10+
use rasn::{AsnType, Decode, Encode};
711
use reqwest::{header::{HeaderMap, HeaderName, HeaderValue}, Client, Method, Request, RequestBuilder, Response, Url};
812
use serde::{de::DeserializeOwned, Deserialize, Serialize};
13+
use sha2::Sha256;
14+
use srp::{client::SrpClient, groups::G_3072, server::SrpServer};
15+
use tokio::sync::Mutex;
916
use uuid::Uuid;
1017
use rand::Rng;
18+
use aes_gcm::{Aes128Gcm, KeyInit, Nonce, aead::Aead};
1119

12-
use crate::{aps::get_message, ids::user::{IDSUser, IDSUserIdentity, IDSUserType}, util::{base64_encode, duration_since_epoch, encode_hex, get_bag, gzip, gzip_normal, plist_to_bin, plist_to_buf, plist_to_string, ungzip, KeyPair, IDS_BAG, REQWEST}, APSConnectionResource, APSState, OSConfig, PushError};
20+
use crate::{aps::{get_message, APSInterestToken}, ids::user::{IDSUser, IDSUserIdentity, IDSUserType}, util::{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, KeyPair, IDS_BAG, REQWEST}, APSConnection, APSConnectionResource, APSMessage, APSState, OSConfig, PushError};
1321

1422
#[derive(Serialize)]
1523
#[serde(rename_all = "kebab-case")]
@@ -369,4 +377,273 @@ impl<S: RequestState> SignedRequest<S> {
369377
}
370378
}
371379

380+
#[derive(Clone, Deserialize, Debug)]
381+
pub struct ApsAlert {
382+
pub title: String,
383+
pub body: String,
384+
pub sbdy: String,
385+
pub defbtn: String,
386+
pub albtn: String,
387+
}
388+
389+
#[derive(Clone, Deserialize, Debug)]
390+
pub struct ApsData {
391+
pub alert: ApsAlert,
392+
}
393+
394+
#[derive(Clone, Copy, Deserialize, Debug)]
395+
pub struct AkData {
396+
pub lat: f32,
397+
pub lng: f32,
398+
}
399+
400+
#[derive(Clone, Deserialize, Debug)]
401+
pub struct IdmsRequestedSignIn {
402+
pub aps: ApsData,
403+
pub txnid: String,
404+
pub akdata: AkData,
405+
}
406+
407+
#[derive(Clone, Deserialize, Debug)]
408+
pub struct IdmsCircleMessage {
409+
pub step: u32,
410+
pub atxnid: String,
411+
pub pake: Option<String>,
412+
pub ec: Option<i32>,
413+
pub idmsdata: String,
414+
}
415+
416+
#[derive(Clone, Deserialize, Debug)]
417+
pub struct TeardownSignIn {
418+
pub prevtxnid: String,
419+
}
420+
421+
#[derive(Clone, Debug)]
422+
pub enum IdmsMessage {
423+
RequestedSignIn(IdmsRequestedSignIn),
424+
TeardownSignIn(TeardownSignIn),
425+
CircleRequest(IdmsCircleMessage, Option<IdmsRequestedSignIn>),
426+
}
427+
428+
pub struct IdmsAuthListener {
429+
_interest_token: APSInterestToken,
430+
}
431+
432+
433+
#[derive(AsnType, Encode, Decode)]
434+
struct CircleStep0 {
435+
circle_step: rasn::types::Integer,
436+
public_ephermeral: rasn::types::OctetString,
437+
unk3: rasn::types::Integer, // set to 1
438+
req_uuid: rasn::types::OctetString,
439+
tag: rasn::types::OctetString, // ASCII 'o'
440+
}
441+
442+
#[derive(AsnType, Encode, Decode)]
443+
struct CircleStep1Body {
444+
salt: rasn::types::OctetString,
445+
public_ephermeral: rasn::types::OctetString,
446+
}
447+
448+
#[derive(AsnType, Encode, Decode)]
449+
struct CircleStep1 {
450+
circle_step: rasn::types::Integer,
451+
body: rasn::types::OctetString,
452+
}
453+
454+
#[derive(AsnType, Encode, Decode)]
455+
struct CircleError {
456+
extra_code: rasn::types::Integer,
457+
meta: rasn::types::OctetString,
458+
}
459+
460+
#[derive(AsnType, Encode, Decode)]
461+
struct CircleStep2 {
462+
circle_step: rasn::types::Integer,
463+
proof: rasn::types::OctetString,
464+
}
465+
466+
#[derive(AsnType, Encode, Decode)]
467+
struct CircleEncryptedPayload {
468+
iv: rasn::types::OctetString,
469+
ciphertext: rasn::types::OctetString,
470+
tag: rasn::types::OctetString,
471+
}
472+
473+
type Aes128Gcm16ByteNonce = aes_gcm::AesGcm<Aes128, U16>;
474+
475+
impl CircleEncryptedPayload {
476+
fn new(data: &[u8], key: [u8; 16]) -> Self {
477+
let nonce: [u8; 16] = rand::random();
478+
let cipher = Aes128Gcm16ByteNonce::new(&key.into());
479+
let mut encrypted = cipher.encrypt(Nonce::from_slice(&nonce), data).expect("AES GCM failed?");
480+
481+
let tag = encrypted.split_off(encrypted.len() - 16);
482+
483+
Self {
484+
iv: nonce.to_vec().into(),
485+
ciphertext: encrypted.into(),
486+
tag: tag.into(),
487+
}
488+
}
489+
}
490+
491+
#[derive(AsnType, Encode, Decode)]
492+
struct CircleStep3 {
493+
circle_step: rasn::types::Integer,
494+
proof: rasn::types::OctetString,
495+
payload: rasn::types::OctetString,
496+
}
497+
498+
pub struct CircleServerSession<P: AnisetteProvider> {
499+
salt: [u8; 16],
500+
dsid: u64,
501+
verifier: Vec<u8>,
502+
server: SrpServer<'static, Sha256>,
503+
account: Arc<Mutex<AppleAccount<P>>>,
504+
b: [u8; 32],
505+
client_public: Option<Vec<u8>>,
506+
push_token: [u8; 32],
507+
}
508+
509+
impl<P: AnisetteProvider> CircleServerSession<P> {
510+
pub fn new(dsid: u64, otp: u32, account: Arc<Mutex<AppleAccount<P>>>, push_token: [u8; 32]) -> Self {
511+
let salt: [u8; 16] = rand::random();
512+
let client = SrpClient::<Sha256>::new(&G_3072);
513+
// check password, was guess
514+
let verifier = client.compute_verifier(format!("{dsid}").as_bytes(), format!("{:0>6}", otp).as_bytes(), &salt);
515+
516+
Self {
517+
salt,
518+
dsid,
519+
verifier,
520+
server: SrpServer::<Sha256>::new(&G_3072),
521+
account,
522+
b: rand::random(),
523+
client_public: None,
524+
push_token,
525+
}
526+
}
527+
528+
pub async fn handle_circle_request(&mut self, request: &IdmsCircleMessage) -> Result<(), PushError> {
529+
if let Some(ec) = &request.ec {
530+
return Err(PushError::IdmsCircleError(*ec))
531+
}
532+
let Some(pake) = &request.pake else { return Err(PushError::IdmsCircleError(50)) };
533+
match request.step {
534+
1 => {
535+
let step0: CircleStep0 = rasn::der::decode(&base64_decode(pake)).expect("failed to decode circlestep0");
536+
self.client_public = Some(step0.public_ephermeral.into());
537+
let b_pub = self.server.compute_public_ephemeral(&self.b, &self.verifier);
538+
539+
let step1 = rasn::der::encode(&CircleStep1 {
540+
circle_step: 1.into(),
541+
body: rasn::der::encode(&CircleStep1Body {
542+
salt: self.salt.to_vec().into(),
543+
public_ephermeral: b_pub.into()
544+
}).unwrap().into(),
545+
}).unwrap();
546+
547+
println!("Body {}", encode_hex(&step1));
548+
549+
self.account.lock().await.circle(&CircleSendMessage {
550+
atxid: request.atxnid.clone(),
551+
circlestep: 1,
552+
idmsdata: request.idmsdata.clone(),
553+
pakedata: base64_encode(&step1),
554+
ptkn: encode_hex(&self.push_token).to_uppercase(),
555+
ec: None,
556+
}).await?;
557+
},
558+
3 => {
559+
let step2: CircleStep2 = rasn::der::decode(&base64_decode(pake)).expect("failed to decode circlestep0");
560+
let verifier = self.server.process_reply(&self.b, &self.verifier, self.client_public.as_ref().unwrap(), format!("{}", self.dsid).as_bytes(), &self.salt).expect("Srp failure");
561+
if let Err(e) = verifier.verify_client(&step2.proof) {
562+
warn!("SRP auth error {e}");
563+
self.account.lock().await.circle(&CircleSendMessage {
564+
atxid: request.atxnid.clone(),
565+
circlestep: 3,
566+
idmsdata: request.idmsdata.clone(),
567+
pakedata: base64_encode(&rasn::der::encode(&CircleError {
568+
extra_code: 0.into(),
569+
meta: vec![].into()
570+
}).unwrap()),
571+
ptkn: encode_hex(&self.push_token).to_uppercase(),
572+
ec: Some(-9003),
573+
}).await?;
574+
}
575+
let receipt = verifier.proof();
576+
577+
let hk = Hkdf::<Sha256>::new(None, verifier.key());
578+
let mut key = [0u8; 16];
579+
hk.expand("recv->send".as_bytes(), &mut key).expect("Failed to expand key!");
580+
581+
let twofa_code = self.account.lock().await.anisette.lock().await.provider.get_2fa_code().await?;
582+
let twofa_str = format!("{:0>6}", twofa_code);
583+
584+
let message = rasn::der::encode(&CircleStep3 {
585+
circle_step: 3.into(),
586+
proof: receipt.to_vec().into(),
587+
payload: rasn::der::encode(&CircleEncryptedPayload::new(&rasn::der::encode(&twofa_str).expect("Failed to encode der?"), key)).expect("Encoding failed").into(),
588+
}).expect("outer encoding failed");
589+
590+
self.account.lock().await.circle(&CircleSendMessage {
591+
atxid: request.atxnid.clone(),
592+
circlestep: 3,
593+
idmsdata: request.idmsdata.clone(),
594+
pakedata: base64_encode(&message),
595+
ptkn: encode_hex(&self.push_token).to_uppercase(),
596+
ec: None,
597+
}).await?;
598+
},
599+
5 => {
600+
// this is where we could exchange iCloud keychain keys.
601+
// However, let's just say "I don't have them", because, well, I don't
602+
self.account.lock().await.circle(&CircleSendMessage {
603+
atxid: request.atxnid.clone(),
604+
circlestep: 5,
605+
idmsdata: request.idmsdata.clone(),
606+
pakedata: base64_encode(&rasn::der::encode(&CircleError {
607+
extra_code: 5.into(),
608+
meta: vec![].into(),
609+
}).expect("outer encoding failed")),
610+
ptkn: encode_hex(&self.push_token).to_uppercase(),
611+
ec: None,
612+
}).await?;
613+
},
614+
_circlestep => {
615+
warn!("Ignoring unknown circle step {_circlestep}");
616+
}
617+
}
618+
Ok(())
619+
}
620+
}
621+
622+
623+
impl IdmsAuthListener {
624+
pub async fn new(conn: APSConnection) -> Self {
625+
Self {
626+
_interest_token: conn.request_topics(vec!["com.apple.idmsauth"]).await.0,
627+
}
628+
}
629+
630+
pub fn handle(&self, message: APSMessage) -> Result<Option<IdmsMessage>, PushError> {
631+
let APSMessage::Notification { topic, payload, .. } = message else { return Ok(None) };
632+
if &topic != &sha1("com.apple.idmsauth".as_bytes()) { return Ok(None) }
633+
634+
let data: serde_json::value::Map<String, serde_json::Value> = serde_json::from_slice(&payload)?;
635+
636+
debug!("Got idms message {data:?}");
637+
638+
Ok(match data["cmd"].as_u64().unwrap() {
639+
100 => Some(IdmsMessage::RequestedSignIn(serde_json::from_slice(&payload)?)),
640+
400 => Some(IdmsMessage::TeardownSignIn(serde_json::from_slice(&payload)?)),
641+
700 => Some(IdmsMessage::CircleRequest(serde_json::from_slice(&payload)?, serde_json::from_slice(&payload).ok())),
642+
_cmd => {
643+
debug!("Ignoring unknown IDMS message");
644+
None
645+
}
646+
})
647+
}
648+
}
372649

src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,10 @@ pub enum PushError {
146146
UnknownPoster(String),
147147
#[error("Report spam error {0}")]
148148
ReportSpamError(u32),
149+
#[error("Token missing")]
150+
TokenMissing,
151+
#[error("Circle http error {0}")]
152+
CircleHTTPError(#[from] icloud_auth::Error),
153+
#[error("Circle error {0}")]
154+
IdmsCircleError(i32),
149155
}

0 commit comments

Comments
 (0)