|
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}}; |
2 | 2 |
|
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}; |
4 | 7 | use omnisette::{AnisetteClient, AnisetteProvider}; |
5 | 8 | use openssl::{hash::MessageDigest, nid::Nid, pkey::{PKey, Private}, rsa::{Padding, Rsa}, sha::sha1, sign::Signer, x509::{X509Name, X509Req}}; |
6 | 9 | use plist::{Data, Dictionary, Value}; |
| 10 | +use rasn::{AsnType, Decode, Encode}; |
7 | 11 | use reqwest::{header::{HeaderMap, HeaderName, HeaderValue}, Client, Method, Request, RequestBuilder, Response, Url}; |
8 | 12 | 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; |
9 | 16 | use uuid::Uuid; |
10 | 17 | use rand::Rng; |
| 18 | +use aes_gcm::{Aes128Gcm, KeyInit, Nonce, aead::Aead}; |
11 | 19 |
|
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}; |
13 | 21 |
|
14 | 22 | #[derive(Serialize)] |
15 | 23 | #[serde(rename_all = "kebab-case")] |
@@ -369,4 +377,273 @@ impl<S: RequestState> SignedRequest<S> { |
369 | 377 | } |
370 | 378 | } |
371 | 379 |
|
| 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 | +} |
372 | 649 |
|
0 commit comments