Merge remote-tracking branch 'origin/main'
Dadoum

Dadoum commited on 2026-10-25 22:47:14
Showing 6 changed files, with 19 additions and 9 deletions.


# Conflicts:
#	Cargo.toml
#	aps/src/activation.rs
#	aps/src/lib.rs
#	aps/src/windows_signer.rs
#	aps/src/windows_signer/constants.rs
#	icloud/src/account.rs
#	xcode/examples/provision_and_login.rs
... ...
@@@ -1,6 -1,6 +1,6 @@@
1 1
  [workspace]
2 2
  resolver = "2"
3
- members = ["airdrop", "adi", "adid-proxy", "sharing", "android-coreadi", "aps", "grandslam", "apple-codesign-quick", "elf-loader", "fairplay", "icloud", "itunes", "library-coreadi", "macos-catalog", "plist-macros", "purple-builder", "super-sideloader", "xcode"]
4 3
 -members = ["adi", "adid-proxy", "android-coreadi", "aps", "grandslam", "apple-codesign-quick", "elf-loader", "fairplay", "icloud", "itunes", "library-coreadi", "plist-macros", "super-sideloader", "xcode"]
4
++members = ["airdrop", "adi", "adid-proxy", "sharing", "android-coreadi", "aps", "grandslam", "apple-codesign-quick", "elf-loader", "fairplay", "icloud", "itunes", "library-coreadi", "macos-catalog", "plist-macros", "super-sideloader", "xcode"]
5 5
  
6 6
  [workspace.dependencies]
7 7
  adi = { path = "adi" }
... ...
@@@ -1,13 -1,12 +1,13 @@@
1
- //! cargo run -p aps --example push_token -- STATE.plist [--check-connection]
2
- use aps::{ActivationDevice, PushConnection, PushIdentity, PushToken, WindowsActivationSigner};
1
+ //! cargo run -p aps --example push_token -- STATE.plist [--device DEVICE.plist] [--check-connection]
2
+ use aps::{ActivationDevice, MacOsActivationSigner, PushConnection, PushIdentity, PushToken};
3 3
  use plist::Data;
4 4
 -use rsa::{
5 5
 -    RsaPrivateKey,
6 6
 -    pkcs8::{DecodePrivateKey, EncodePrivateKey},
7 7
 -};
8 8
 +use rsa::RsaPrivateKey;
9 9
 +use rsa::pkcs8::{DecodePrivateKey, EncodePrivateKey};
10 10
  use serde::{Deserialize, Serialize};
11 11
 -use std::{fs, io, path::PathBuf, time::Duration};
12 12
 +use std::fs;
13 13
 +use std::io;
14 14
 +use std::path::PathBuf;
15 15
 +use std::time::Duration;
16 16
  use thiserror::Error;
17 17
  
18 18
  #[derive(Debug, Error)]
... ...
@@@ -121,67 -121,15 +121,67 @@@ pub(crate) fn sign_sha1(key: &RsaPrivat
121 121
      key.sign_with_rng(&mut OsRng, Pkcs1v15Sign::new_unprefixed(), &digest_info)
122 122
  }
123 123
  
124 124
 -/// Builds the signed `activation-info` request body and the matching device key.
125 125
 -/// `activate` posts this body; exposing it lets a caller replay the identical
126 126
 -/// bytes through another HTTP stack for comparison. Each call generates a fresh
127 127
 -/// device key and activation randomness.
128 128
 -pub fn build_activation_request<S: ActivationSigner>(
129 129
 +/// Generates a device key and CSR, then requests an activation certificate.
130 130
 +/// Use a plain HTTP client; account authentication headers are not needed here.
131 131
 +pub async fn activate<S: ActivationSigner>(
132 132
 +    client: &Client,
133 133
      device: &ActivationDevice<'_>,
134 134
      signer: &S,
135 135
 -) -> Result<(Vec<u8>, RsaPrivateKey), ActivationError<S::Error>> {
136 136
 +) -> Result<PushIdentity, ActivationError<S::Error>> {
137 137
      let private_key = RsaPrivateKey::new(&mut OsRng, 1024)?;
138 138
 +
139 139
 +    let activation_info = dict! {
140 140
 +        "DeviceClass": device.device_class,
141 141
 +        "ProductType": device.product_type,
142 142
 +        "ProductVersion": device.product_version,
143 143
 +        "BuildVersion": device.build_version,
144 144
 +        "SerialNumber": device.serial_number,
145 145
 +        "UniqueDeviceID": device.unique_device_id,
146 146
 +        "ActivationState": "Unactivated",
147 147
 +        "ActivationRandomness": Uuid::new_v4().to_string().to_uppercase(),
148 148
 +        "DeviceCertRequest": Value::Data(certificate_request(&private_key)?.into_bytes()),
149 149
 +    };
150 150
 +
151 151
 +    let mut activation_info_xml = Vec::new();
152 152
 +    plist::to_writer_xml(&mut activation_info_xml, &activation_info)?;
153 153
 +    let signed = signer
154 154
 +        .sign(&activation_info_xml)
155 155
 +        .map_err(ActivationError::Signing)?;
156 156
 +
157 157
 +    let activation_request = dict! {
158 158
 +        "ActivationInfoComplete": true,
159 159
 +        "ActivationInfoXML": Value::Data(activation_info_xml),
160 160
 +        "FairPlaySignature": Value::Data(signed.signature),
161 161
 +        "FairPlayCertChain": Value::Data(signed.certificate_chain),
162 162
 +    };
163 163
 +
164 164
 +    let mut activation_request_xml = Vec::new();
165 165
 +    plist::to_writer_xml(&mut activation_request_xml, &activation_request)?;
166 166
 +
167 167
 +    let response = client
168
-         .post("https://albert.apple.com/WebObjects/ALUnbrick.woa/wa/deviceActivation")
168
++        .post("https://albert.apple.com/deviceservices/deviceActivation")
169 169
 +        .query(&[("device", device.device_class)])
170 170
 +        .form(&[(
171 171
 +            "activation-info",
172 172
 +            String::from_utf8_lossy(&activation_request_xml).as_ref(),
173 173
 +        )])
174 174
 +        .send()
175 175
 +        .await?
176 176
 +        .error_for_status()?
177 177
 +        .bytes()
178 178
 +        .await?;
179 179
 +
180 180
 +    let certificate = device_certificate(&response)
181 181
 +        .ok_or_else(|| ActivationError::InvalidResponse(response.to_vec()))?;
182 182
 +
183 183
 +    Ok(PushIdentity::new(
184 184
 +        Certificate::from_pem(certificate)?.to_der()?,
185 185
 +        private_key,
186 186
 +    )?)
187 187
 +}
188 188
 +
189 189
 +/// A PEM certificate signing request for the device key, signed with RSA/SHA-1.
190 190
 +fn certificate_request<E>(private_key: &RsaPrivateKey) -> Result<String, ActivationError<E>> {
191 191
      let info = CertReqInfo {
192 192
          version: Default::default(),
193 193
          subject: "CN=Client Push Certificate".parse()?,
... ...
@@@ -1,12 -1,22 +1,22 @@@
1
+ //! Device activation and connections to the Apple Push Service.
2
+ //!
3
+ //! Activation yields a device identity. The courier handshake then yields a
4
+ //! push token. Neither operation requires an Apple account or anisette.
5
+ 
1 6
  mod activation;
2 7
  mod connection;
8
+ mod macos_signer;
3 9
  mod protocol;
4
- mod windows_signer;
5 10
  
6
- pub use activation::*;
7
- pub use connection::*;
8
- pub use protocol::*;
9
- pub use windows_signer::*;
11
+ pub use activation::{
12
+     ActivationDevice, ActivationError, ActivationSignature, ActivationSigner, IdentityError,
10 13
 -    PushIdentity, activate, build_activation_request,
14
++    PushIdentity, activate
15
+ };
16
+ pub use connection::{
17
+     ConnectError, Event, Notification, PushConnection, PushToken, SendError, SendStatus,
18
+ };
19
+ pub use macos_signer::MacOsActivationSigner;
20
+ pub use protocol::CourierError;
11 21
  
12 22
  /// DER-encoded Apple root needed by the push configuration HTTPS endpoint.
13 23
  /// Add it to the HTTP client's trust store before connecting.