Adjust handling of grandslam check-ins
Dadoum

Dadoum commited on 2026-09-28 23:57:48
Showing 5 changed files, with 22 additions and 12 deletions.

... ...
@@ -5,6 +5,9 @@ request that returns a device certificate. The subsequent APS handshake over
5 5
 TLS returns a push token. Neither step needs an Apple account or anisette.
6 6
 
7 7
 ```rust,ignore
8
+let client = reqwest::Client::builder()
9
+    .tls_certs_merge([reqwest::Certificate::from_der(aps::APPLE_ROOT_CA)?])
10
+    .build()?;
8 11
 let identity = aps::activate(&client, &device, &signer).await?;
9 12
 let mut connection = aps::PushConnection::connect(&client, identity).await?;
10 13
 
... ...
@@ -66,6 +69,9 @@ payloads stay as bytes, allowing AuthKit and future iMessage consumers to decode
66 69
 them independently. IDS registration and iMessage-specific operations are not
67 70
 part of this crate.
68 71
 
72
+The HTTPS client also needs this root for `init.push.apple.com`, as shown above.
73
+It is not included in every operating system's default trust store.
74
+
69 75
 ## Standalone Windows signing identity
70 76
 
71 77
 `WindowsActivationSigner::new()` selects one of ten embedded activation signing
... ...
@@ -46,6 +46,7 @@ async fn main() -> Result<(), ExampleError> {
46 46
     }
47 47
     let state_path = PathBuf::from(&args[0]);
48 48
     let client = reqwest::Client::builder()
49
+        .tls_certs_merge([reqwest::Certificate::from_der(aps::APPLE_ROOT_CA)?])
49 50
         .timeout(Duration::from_secs(30))
50 51
         .build()?;
51 52
     let mut connection = match fs::read(&state_path) {
... ...
@@ -283,9 +283,7 @@ async fn handshake(
283 283
     let target = format!("{}-{host}", 1 + u64::from(OsRng.next_u32()) % count);
284 284
     let server_name = ServerName::try_from(verified.to_owned())?;
285 285
     let mut roots = RootCertStore::empty();
286
-    roots.add(CertificateDer::from_slice(include_bytes!(
287
-        "../certs/AppleRootCA.der"
288
-    )))?;
286
+    roots.add(CertificateDer::from_slice(crate::APPLE_ROOT_CA))?;
289 287
     let mut config = ClientConfig::builder_with_provider(Arc::new(ring::default_provider()))
290 288
         .with_safe_default_protocol_versions()?
291 289
         .with_root_certificates(roots)
... ...
@@ -15,3 +15,7 @@ pub use activation::{
15 15
 pub use connection::{ConnectError, Notification, PushConnection, PushToken};
16 16
 pub use protocol::CourierError;
17 17
 pub use windows_signer::WindowsActivationSigner;
18
+
19
+/// DER-encoded Apple root needed by the push configuration HTTPS endpoint.
20
+/// Add it to the HTTP client's trust store before connecting.
21
+pub const APPLE_ROOT_CA: &[u8] = include_bytes!("../certs/AppleRootCA.der");
... ...
@@ -7,7 +7,7 @@ use base64::Engine;
7 7
 use base64::prelude::BASE64_STANDARD;
8 8
 use chrono::{Local, SecondsFormat};
9 9
 use plist::{Dictionary, Value};
10
-use reqwest::{Method, RequestBuilder};
10
+use reqwest::{Method, RequestBuilder, StatusCode};
11 11
 use serde::{Deserialize, Serialize};
12 12
 use thiserror::Error;
13 13
 
... ...
@@ -55,8 +55,8 @@ pub enum PostDataError {
55 55
     Network(#[from] reqwest::Error),
56 56
     #[error("Failed to perform the request! {0}")]
57 57
     Anisette(#[from] ADIError),
58
-    #[error("Failed to perform the request! {0}")]
59
-    Apple(#[from] AppleError),
58
+    #[error("Check-in returned HTTP {0}")]
59
+    HttpStatus(StatusCode),
60 60
     #[error("Invalid URL bag.")]
61 61
     InvalidURLBag,
62 62
 }
... ...
@@ -198,6 +198,7 @@ impl<'lt, 'adi> HeartbeatHTTPSession<'lt, 'adi> {
198 198
         Ok(dict)
199 199
     }
200 200
 
201
+    /// Check-in succeeds on HTTP 200; AuthKitWin does not interpret its response body.
201 202
     pub async fn post_data(&self, device_data: DeviceData) -> Result<(), PostDataError> {
202 203
         let post_data = plist::to_value(&device_data).map_err(PostDataError::Serialization)?;
203 204
         let post_data_url = self
... ...
@@ -210,19 +211,19 @@ impl<'lt, 'adi> HeartbeatHTTPSession<'lt, 'adi> {
210 211
 
211 212
         let mut request = Dictionary::new();
212 213
         request.insert("Request".into(), post_data);
214
+        request.insert("Header".into(), Dictionary::new().into());
213 215
 
214 216
         let response = self
215 217
             .heartbeat_request_builder(Method::POST, post_data_url)?
216 218
             .header("Content-Type", "text/x-xml-plist")
217 219
             .body(plist_to_body(request.into()))
218 220
             .send()
219
-            .await?
220
-            .bytes()
221 221
             .await?;
222 222
 
223
-        let status: Dictionary =
224
-            plist::from_bytes(&response).map_err(PostDataError::Serialization)?;
225
-
226
-        parse_status(&status).map_err(PostDataError::Apple)
223
+        if response.status() != StatusCode::OK {
224
+            Err(PostDataError::HttpStatus(response.status()))
225
+        } else {
226
+            Ok(())
227
+        }
227 228
     }
228 229
 }
229 230