nab138_icloud_auth 0.1.10

A library to authenticate with Apple's GSA servers
Documentation
use crate::Error;
use omnisette::{obf, AnisetteConfiguration, AnisetteHeaders};
use std::{collections::HashMap, time::SystemTime};

#[derive(Debug, Clone)]
pub struct AnisetteData {
    pub base_headers: HashMap<String, String>,
    pub generated_at: SystemTime,
    pub config: AnisetteConfiguration,
}

impl AnisetteData {
    /// Fetches the data at an anisette server
    pub async fn new(config: AnisetteConfiguration) -> Result<Self, crate::Error> {
        let mut b = AnisetteHeaders::get_anisette_headers_provider(config.clone())?;
        let base_headers = b.provider.get_authentication_headers().await?;

        Ok(AnisetteData {
            base_headers,
            generated_at: SystemTime::now(),
            config,
        })
    }

    pub fn needs_refresh(&self) -> bool {
        let elapsed = self.generated_at.elapsed().unwrap();
        elapsed.as_secs() > 60
    }

    pub fn is_valid(&self) -> bool {
        let elapsed = self.generated_at.elapsed().unwrap();
        elapsed.as_secs() < 90
    }

    pub async fn refresh(&self) -> Result<Self, crate::Error> {
        Self::new(self.config.clone()).await
    }

    pub fn generate_headers(
        &self,
        cpd: bool,
        client_info: bool,
        app_info: bool,
    ) -> HashMap<String, String> {
        if !self.is_valid() {
            panic!("Invalid data!")
        }
        let mut headers = self.base_headers.clone();
        let old_client_info = headers.remove("X-Mme-Client-Info");
        if client_info {
            let client_info = match old_client_info {
                Some(v) => {
                    let temp = v.as_str();

                    temp.replace(
                        temp.split('<').nth(3).unwrap().split('>').nth(0).unwrap(),
                        obf!("com.apple.AuthKit/1 (com.apple.dt.Xcode/3594.4.19)"),
                    )
                }
                None => {
                    return headers;
                }
            };
            headers.insert(
                obf!("X-Mme-Client-Info").to_string(),
                client_info.to_owned(),
            );
        }

        if app_info {
            headers.insert(
                obf!("X-Apple-App-Info").to_string(),
                obf!("com.apple.gs.xcode.auth").to_string(),
            );
            headers.insert(
                obf!("X-Xcode-Version").to_string(),
                obf!("11.2 (11B41)").to_string(),
            );
        }

        if cpd {
            headers.insert(obf!("bootstrap").to_string(), obf!("true").to_string());
            headers.insert(obf!("icscrec").to_string(), obf!("true").to_string());
            headers.insert(obf!("loc").to_string(), obf!("en_GB").to_string());
            headers.insert(obf!("pbe").to_string(), obf!("false").to_string());
            headers.insert(obf!("prkgen").to_string(), obf!("true").to_string());
            headers.insert(obf!("svct").to_string(), obf!("iCloud").to_string());
        }

        headers
    }

    pub fn to_plist(&self, cpd: bool, client_info: bool, app_info: bool) -> plist::Dictionary {
        let mut plist = plist::Dictionary::new();
        for (key, value) in self.generate_headers(cpd, client_info, app_info).iter() {
            plist.insert(key.to_owned(), plist::Value::String(value.to_owned()));
        }

        plist
    }

    pub fn get_header(&self, header: &str) -> Result<String, Error> {
        let headers = self
            .generate_headers(true, true, true)
            .iter()
            .map(|(k, v)| (k.to_lowercase(), v.to_lowercase()))
            .collect::<HashMap<String, String>>();

        match headers.get(&header.to_lowercase()) {
            Some(v) => Ok(v.to_string()),
            None => Err(Error::Parse),
        }
    }
}