Git · dadoum.zone
Repositories
Help
Report an Issue
apple-crates
Code
Commits
Branches
Tags
Search
Tree:
a74924e
Branches
Tags
main
apple-crates
sharing
src
identity.rs
Refactor and unify a lot of things for another project and implement airdrop support
Dadoum
commited
a74924e
at 2026-10-25 22:15:58
identity.rs
Blame
History
Raw
//! `createCertificate` and `fetchCertificate`: issuing the AirDrop identity in //! two steps. `createCertificate` takes a CSR and answers with a certificate //! token and a delay; once the delay has passed, `fetchCertificate` trades the //! token for the signed certificate. //! //! The create body is what `-[SDAppleIDIdentityRequest _handleKeyPairAvailable]` //! builds; the replies are what `-[SDAppleIDServerTask _parseCreateCertificateResponse:error:]` //! and `_parseFetchCertificateResponse:error:` read. The fetch body and the //! nesting of the fetch reply still need a capture to confirm. use std::time::Duration; use serde::{Deserialize, Serialize}; use crate::{SharingError, SharingSession, csr, impl_sharing_request}; /// `{ "csr": "<PEM PKCS#10>" }`. #[derive(Debug, Serialize)] pub struct CreateCertificate { pub csr: String, } #[derive(Debug, Deserialize)] pub struct CreateCertificateResponse { #[serde(rename = "altDsId")] pub alt_dsid: Option<String>, pub certificate: Option<CertificateToken>, /// Milliseconds to wait before fetching (a capture shows 90000). pub delay: Option<u64>, pub status: Option<i64>, } #[derive(Debug, Deserialize)] pub struct CertificateToken { #[serde(rename = "certificateToken")] pub certificate_token: String, } impl_sharing_request!( CreateCertificate, "createCertificate", CreateCertificateResponse ); #[derive(Debug, Serialize)] pub struct FetchCertificate { #[serde(rename = "certificateToken")] pub certificate_token: String, } #[derive(Debug, Deserialize)] pub struct FetchCertificateResponse { #[serde(rename = "altDsId")] pub alt_dsid: Option<String>, pub certificate: Option<IssuedCertificate>, /// Intermediate certificate (PEM, or base64 DER). #[serde(rename = "intermediateCaCertificate")] pub intermediate: Option<String>, /// Milliseconds to wait while the certificate is still pending. pub delay: Option<u64>, } /// The `certificate` object of a `fetchCertificate` reply. #[derive(Debug, Deserialize)] pub struct IssuedCertificate { /// The leaf certificate (PEM, or base64 DER); null while still pending. pub certificate: Option<String>, #[serde(rename = "certificateToken")] pub certificate_token: Option<String>, #[serde(rename = "certificateType")] pub certificate_type: Option<String>, /// Epoch milliseconds. #[serde(rename = "expiryDate")] pub expiry_date: Option<i64>, #[serde(rename = "serialNumber")] pub serial_number: Option<String>, pub status: Option<String>, } /// Return `certificate` as PEM, wrapping bare base64 DER if that is what the /// server sent. fn as_pem(certificate: String) -> String { if certificate.contains("-----BEGIN") { return certificate; } let body: String = certificate.split_whitespace().collect(); let lines: Vec<&str> = body .as_bytes() .chunks(64) .map(|line| std::str::from_utf8(line).unwrap_or_default()) .collect(); format!( "-----BEGIN CERTIFICATE-----\n{}\n-----END CERTIFICATE-----\n", lines.join("\n") ) } impl_sharing_request!( FetchCertificate, "fetchCertificate", FetchCertificateResponse ); /// An issued AirDrop identity, laid out the way omdrop reads `~/.omdrop/keys`. pub struct AppleIdentity { /// `key.pem`, PKCS#8. pub private_key_pem: String, /// `certificate.pem`. pub certificate_pem: String, pub intermediate_pem: Option<String>, /// Certificate expiry, epoch milliseconds, when the server gave one. /// sharingd assumes six months from now otherwise. pub expiry_date: Option<i64>, } const FETCH_ATTEMPTS: u32 = 10; impl SharingSession<'_, '_> { /// Generate a key and CSR, create the certificate, wait as told, then fetch /// it. `sleep` is the caller's timer (for example `tokio::time::sleep`). pub async fn request_identity<S, F>( &self, common_name: &str, mut sleep: S, ) -> Result<AppleIdentity, SharingError> where S: FnMut(Duration) -> F, F: Future<Output = ()>, { let key = csr::generate_identity_key(common_name, csr::AIRDROP_RSA_BITS)?; let created = self .perform_sharing_request(&CreateCertificate { csr: key.csr_pem }) .await?; let certificate_token = created .certificate .ok_or(SharingError::Unexpected( "createCertificate returned no token", ))? .certificate_token; if let Some(delay) = created.delay { sleep(Duration::from_millis(delay)).await; } let fetch = FetchCertificate { certificate_token }; for _ in 0..FETCH_ATTEMPTS { let fetched = self.perform_sharing_request(&fetch).await?; let issued = fetched.certificate; let expiry_date = issued.as_ref().and_then(|issued| issued.expiry_date); if let Some(certificate) = issued .and_then(|issued| issued.certificate) .filter(|certificate| !certificate.is_empty()) { return Ok(AppleIdentity { private_key_pem: key.private_key_pem, certificate_pem: as_pem(certificate), intermediate_pem: fetched .intermediate .filter(|certificate| !certificate.is_empty()) .map(as_pem), expiry_date, }); } sleep(Duration::from_millis(fetched.delay.unwrap_or(2000))).await; } Err(SharingError::Pending(2)) } }