mirror of
https://github.com/fenleon/open-madrid.git
synced 2026-10-22 16:12:35 +00:00
Spec rev 25: first live GSA login — au in Status, PET via post-2FA spd token map, trusted-device push semantics, one-time anisette, 409 ICLOUD_UNSUPPORTED_DEVICE refusal shape
This commit is contained in:
1 parent
d876a9af87
commit
e2075458b4
2 files changed
+11
-6
No files matched your search
@@ -59,8 +59,12 @@ Unlike other projects, Open Madrid's is a permissively licensed (Apache-2.0), de
|
||||
test).
|
||||
- **The protocol around it is proven live** by this project's private sibling implementation:
|
||||
a working device-identity chain (Apple's validation endpoint, key establishment, the 645-byte
|
||||
validation blob), SRP login with Apple's GSA endpoints, and the decrypted response parsing —
|
||||
on real accounts. Those findings are folded into the spec here.
|
||||
validation blob), and — as of 2026-09-29 — the full GSA login against Apple's live endpoints
|
||||
with a real Apple ID: SRP, trusted-device 2FA, the PET delivery (both live-confirmed
|
||||
channels), and the delegate sign-in request shape. Those findings are folded into the spec
|
||||
here (rev 25). The sign-in itself is currently refused by Apple with
|
||||
`ICLOUD_UNSUPPORTED_DEVICE` (a device-identity-bound iCloud-activation limit) — recorded as
|
||||
the spec's first documented live refusal, not a protocol gap.
|
||||
|
||||
## What is not validated (yet)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# iMessage protocol spec (clean-room)
|
||||
|
||||
**Status:** draft — revision 24. The full revision history is at the bottom ([Revision history](#revision-history)); the sections below state the facts as of rev 24.
|
||||
**Status:** draft — revision 25. The full revision history is at the bottom ([Revision history](#revision-history)); the sections below state the facts as of rev 25.
|
||||
|
||||
**Firewall rule:** this document contains facts only — no code, no function/type names, no copied expression from any reference implementation. Wire-format field names are quoted as on-the-wire protocol data (observable in captures, capture-arbitrated), never as source expression. Facts derived from SSPL/AGPL sources are used facts-only; nothing from them is reproduced. Where the source-derived fact sets agree, the fact is stated once with both citations; where they disagree, the disagreement is stated and tagged `TODO(capture):` — running mitmproxy captures arbitrate. All `TODO(capture)` items are collected in §7.
|
||||
|
||||
@@ -62,9 +62,9 @@
|
||||
- SRP-6a variant (byte-exact): 2048-bit group, g = 2, SHA-256 throughout. Password key: SHA-256 of the raw password; for `s2k_fo` that digest is ASCII-lowercase-hex-encoded (64 chars) and those bytes feed PBKDF2, for `s2k` the raw 32-byte digest feeds it; PBKDF2-HMAC-SHA256 with server salt `s` and server iteration count `i`, output 32 bytes = the SRP password. Identity hash quirk (GSA-specific, must match): the identity hash uses an EMPTY username — SHA-256(empty ‖ ":" ‖ password-buffer) — while M1's H(username) term uses the REAL username. x = SHA-256(salt ‖ identity-hash); u = SHA-256(A ‖ B); k = SHA-256(N ‖ g padded to N's length); premaster = (B − k·g^x)^(a + u·x) mod N; K = SHA-256(premaster big-endian); M1 = SHA-256(XOR(H(N), H(pad(g))) ‖ H(username) ‖ salt ‖ A ‖ B ‖ K); M2 = SHA-256(A ‖ M1 ‖ K). A is sent as `A2k`; the received `M2` is verified against the computed M2 [APA:icloud-auth].
|
||||
- `spd` decryption: AES-256-CBC + PKCS7; key = HMAC-SHA256(key = K, message = ASCII `extra data key:`), IV = first 16 bytes of HMAC-SHA256(key = K, message = ASCII `extra data iv:`). Decrypted plist carries: a per-service token map `t` (service id → token + expiry in epoch-ms or duration in seconds, e.g. `GsIdmsToken`), `acname`, `DsPrsId`, `adsid`, first/last name [APA:icloud-auth].
|
||||
- Anisette at login: the anisette base headers (§1.1) ride the POSTs filtered to `X-Apple-I-MD-LU`, `X-Apple-I-MD-RINFO`, `X-Apple-I-MD-M`, `X-Apple-I-MD`, `X-Mme-Device-Id`, plus akd-variant `X-MMe-Client-Info`/User-Agent and `X-Apple-AK-Context-Type`/`X-Apple-Client-App-Name`/`X-Apple-I-Client-Bundle-Id`. The `cpd` dictionary carries anisette values as KEYS inside the body: `X-Apple-I-Client-Time`, `X-Apple-I-MD`, `X-Apple-I-MD-LU`, `X-Apple-I-MD-M`, `X-Apple-I-MD-RINFO`, `X-Mme-Device-Id`, optional `ptkn` (APNs token), plus fixed values: `X-Apple-I-Device-Configuration-Mode` = `0`, `X-Apple-I-Request-UUID` (uppercase UUIDv4, one per login, reused across both steps), `X-Apple-Requested-Partition` = `0`, `X-Apple-Security-Upgrade-Context` = `com.apple.authkit.generic`, `capp`, `cbid`, `cou` = `US`, `loc` = `en_US`, `svct` (e.g. `imessage`), and booleans `X-Apple-Offer-Security-Upgrade`=true, `at`=0, `bootstrap`=true, `ckgen`=true, `fcd`=true, `icdrsDisabled`=false, `icscrec`=true, `pbe`=false, `prkgen`=true, `webAccessEnabled`=false [APA:icloud-auth].
|
||||
- 2FA: `au` = `secondaryAuth` → `GET https://gsa.apple.com/auth` returns JSON `AuthenticationExtras` (trustedPhoneNumbers with `numberWithDialCode`/`lastTwoDigits`/`pushMode`/`id`; HTTP 201 = SMS already sent); request a code with PUT `.../auth/verify/phone` body `{phoneNumber:{id}, mode:"sms"}`; verify with POST `.../auth/verify/phone/securitycode` (same body + `securityCode:{code}`). `au` = `trustedDeviceSecondaryAuth` → trigger the code push with `GET .../auth/verify/trusteddevice`, then submit: `GET .../GsService2/validate` with the code in a `security-code` request header. All 2FA requests carry `X-Apple-Identity-Token` = base64 of `adsid:GsIdmsToken` (from `spd`) and a large browser-style fixed header set. Success returns tokens in RESPONSE HEADERS: repeated `X-Apple-GS-Token` (one per GSA service), `X-Apple-HB-Token`, `X-Apple-PE-Token` — each base64 of colon-separated `ID:TOKEN[:...]` with inconsistent duration/expiration trailer forms (epoch-ms vs seconds, heuristically parsed) [APA:icloud-auth]. After a successful 2FA, the full SRP login is run a SECOND time, which then completes as logged-in [APA:icloud-auth]. An on-device variant exists (Circle PAKE over `.../GsService2/circle`: SRP-6a over the 3072-bit group with the DSID as username and the zero-padded 6-digit code as password, 16-byte random salt, DER/ASN.1 step payloads base64 in `pakedata`; the code arrives AES-128-GCM-encrypted over an SRP-key-derived channel with `recv->send`/`send->recv` info labels) [RP:auth.rs], [APA:icloud-auth].
|
||||
- PET: arrives as `X-Apple-PE-Token`; stored under service id `com.apple.gs.idms.pet`; default expiry 300 s when no trailer, otherwise parsed seconds/epoch-ms; refreshed by silently re-running the SRP login with the persisted password hash. Used as the HTTP Basic password (`username:PET`) on the iCloud requests below [APA:icloud-auth], [RP:auth.rs].
|
||||
- Delegate logins: POST `https://setup.icloud.com/setup/signin/v2/login`, XML plist body with `delegates` (dictionary keyed by bundle id: `com.apple.private.ids` → {`protocol-version`: `4`}; `com.apple.mobileme` → {}), `protocolVersion` = `1.0`, `userInfo` = {`clientId` (uppercase UUIDv4), `language` = `en-US`, `timezone`}. Headers: Basic `username:PET`, `X-Mme-Nas-Qualify` = base64 validation blob (§1.1), `X-Apple-ADSID` = adsid, akd-flavor `X-Mme-Client-Info`, `User-Agent` = `com.apple.iCloudHelper/282 …`, full anisette set. Response plist: `status` (0 = ok), optional `localizedError` + `description`, `delegates` dictionary with per-entry `status`, `statusMessage`, `serviceData`. The IDS delegate's serviceData (kebab-case) yields `authToken` and `profileId` — exactly the IDS `auth-token` (sent as `authentication-data` = {`auth-token`: token}) and the `profile-id` (realm user id) that feed the authenticate exchange (§1.2) and registration (§1.4). The MobileMe delegate's serviceData yields a token map (incl. `mmeAuthToken`) and service configs [RP:auth.rs], [RP:macos.rs].
|
||||
- 2FA: `au` = `secondaryAuth` → `GET https://gsa.apple.com/auth` returns JSON `AuthenticationExtras` (trustedPhoneNumbers with `numberWithDialCode`/`lastTwoDigits`/`pushMode`/`id`; HTTP 201 = SMS already sent); request a code with PUT `.../auth/verify/phone` body `{phoneNumber:{id}, mode:"sms"}`; verify with POST `.../auth/verify/phone/securitycode` (same body + `securityCode:{code}`). `au` = `trustedDeviceSecondaryAuth` → trigger the code push with `GET .../auth/verify/trusteddevice`, then submit: `GET .../GsService2/validate` with the code in a `security-code` request header. All 2FA requests carry `X-Apple-Identity-Token` = base64 of `adsid:GsIdmsToken` (from `spd`) and a large browser-style fixed header set. Success returns tokens in RESPONSE HEADERS: repeated `X-Apple-GS-Token` (one per GSA service), `X-Apple-HB-Token`, `X-Apple-PE-Token` — each base64 of colon-separated `ID:TOKEN[:...]` with inconsistent duration/expiration trailer forms (epoch-ms vs seconds, heuristically parsed) [APA:icloud-auth]. After a successful 2FA, the full SRP login is run a SECOND time, which then completes as logged-in [APA:icloud-auth]. An on-device variant exists (Circle PAKE over `.../GsService2/circle`: SRP-6a over the 3072-bit group with the DSID as username and the zero-padded 6-digit code as password, 16-byte random salt, DER/ASN.1 step payloads base64 in `pakedata`; the code arrives AES-128-GCM-encrypted over an SRP-key-derived channel with `recv->send`/`send->recv` info labels) [RP:auth.rs], [APA:icloud-auth]. — 2026-09-29 (rev 25, live): the trusted-device trigger is what makes the code APPEAR on the devices — it must run BEFORE prompting the user, and a SECOND trigger before the same validate invalidates the outstanding code and re-prompts every device (both observed live); `au` is returned inside the complete response's `Status` dict, not at the `Response` level.
|
||||
- PET: arrives as `X-Apple-PE-Token`; stored under service id `com.apple.gs.idms.pet`; default expiry 300 s when no trailer, otherwise parsed seconds/epoch-ms; refreshed by silently re-running the SRP login with the persisted password hash. Used as the HTTP Basic password (`username:PET`) on the iCloud requests below [APA:icloud-auth], [RP:auth.rs]. — 2026-09-29 (rev 25, live): the post-2FA SRP re-login's decrypted `spd` token map `t` is an ALTERNATIVE delivery channel carrying the PET plus the full per-service token set under the same `ID` keys (`com.apple.gs.idms.pet`, `com.apple.gs.icloud.auth`, `com.apple.gs.idms.hb`, `com.apple.gs.pb.auth`, `com.apple.gs.gpk.auth`, …21 observed in total); a no-2FA password login's `spd` carries only `com.apple.gs.appleid.auth` and NO PET — the header route on the 2FA-verification response and the re-login `spd` map are the two live-confirmed PET paths.
|
||||
- Delegate logins: POST `https://setup.icloud.com/setup/signin/v2/login`, XML plist body with `delegates` (dictionary keyed by bundle id: `com.apple.private.ids` → {`protocol-version`: `4`}; `com.apple.mobileme` → {}), `protocolVersion` = `1.0`, `userInfo` = {`clientId` (uppercase UUIDv4), `language` = `en-US`, `timezone`}. Headers: Basic `username:PET`, `X-Mme-Nas-Qualify` = base64 validation blob (§1.1), `X-Apple-ADSID` = adsid, akd-flavor `X-Mme-Client-Info`, `User-Agent` = `com.apple.iCloudHelper/282 …`, full anisette set. Response plist: `status` (0 = ok), optional `localizedError` + `description`, `delegates` dictionary with per-entry `status`, `statusMessage`, `serviceData`. The IDS delegate's serviceData (kebab-case) yields `authToken` and `profileId` — exactly the IDS `auth-token` (sent as `authentication-data` = {`auth-token`: token}) and the `profile-id` (realm user id) that feed the authenticate exchange (§1.2) and registration (§1.4). The MobileMe delegate's serviceData yields a token map (incl. `mmeAuthToken`) and service configs [RP:auth.rs], [RP:macos.rs]. — 2026-09-29 (rev 25, live): the anisette headers minted at login start are SPENT by the GSA hops — the sign-in POST needs a FRESH set (a reused set was refused; the reference client mints per request); and the endpoint answered HTTP 409 with `ErrorID=ICLOUD_UNSUPPORTED_DEVICE` ("This Apple Account is valid but is not an iCloud account" / "Cannot create another iCloud account with this device") — a device-identity-bound iCloud-activation refusal (response plist carries `protocolVersion` 2, `ErrorID`, `Header`, `Body`, `DialogType`, `DefaultButton*`, `AlternateButton*`, `HelpLink`), consistent with the per-Mac-identity account cap the upstream docs put at ~20 accounts.
|
||||
- Not present in these sources: no `idmsa.apple.com`, no `scnt`/session-token headers — the session role is played by `X-Apple-Identity-Token` (GSA requests) and Basic `username:PET` (iCloud requests); the idmsa-based flow recorded in §1.2's [IG] citations is a different client's route (C57 note) [APA:icloud-auth], [RP:auth.rs].
|
||||
- — 2026-09-11 (rev 15 extraction: C59 closed). Group constants: the GSA login uses the RFC 5054 2048-bit MODP group, g = 2 (the standard Appendix-A prime); the Circle PAKE uses the RFC 5054 3072-bit group with g = 5 [APA:groups.rs], [RP:auth.rs]. The 2FA browser-style header set (all `/auth/verify/*` and `GsService2/validate` requests): the anisette base headers filtered to `X-Apple-I-MD-LU`/`X-Apple-I-MD-RINFO`/`X-Apple-I-MD-M`/`X-Apple-I-MD`/`X-Mme-Device-Id`, plus fixed literals `X-Apple-Client-App-Name`, `X-Apple-I-Client-Bundle-Id`, non-akd `X-MMe-Client-Info`, `X-Apple-I-CDP-Circle-Status: false`, `X-Apple-I-ICSCREC: true`, browser `User-Agent`, `Sec-Fetch-Site: same-origin`, `X-Apple-Requested-Partition: 0`, `X-Apple-I-DeviceUserMode: 0`, `X-Apple-I-Locale: en_US`, `X-Apple-Security-Upgrade-Context: com.apple.authkit.generic`, `Accept-Language: en-US,en;q=0.9`, `X-Apple-I-PRK-Gen: true`, `Sec-Fetch-Mode: cors`, `X-Apple-I-TimeZone: UTC`, `X-Apple-I-OT-Status: false`, `X-Apple-I-TimeZone-Offset: 0`, `X-MMe-Country: US`, `X-Apple-I-CDP-Status: false`, `X-Apple-I-Device-Configuration-Mode: 0`, `Sec-Fetch-Dest: empty`, `X-Apple-AK-Context-Type`, and `X-Apple-I-CFU-State` — a hardcoded base64 whose decoded XML plist is just an empty `<array/>` [APA:anisette.rs]. cpd values: `capp` = client app name, `cbid` = bundle id, `svct` = the AK context type — for the Messages flavor: `capp` = `Messages`, `cbid` = `com.apple.MobileSMS`, `svct` = `imessage` (AppleIDSettings flavor: `icloud` / `com.apple.systempreferences.AppleIDSettings`); `cou` = `US`, `loc` = `en_US`; `ptkn` = the APS push token as uppercase hex [APA:anisette.rs], [RP:lib.rs]. akd request values: `User-Agent` = literal `akd/1.0 CFNetwork/1494.0.7 Darwin/23.4.0`; `X-MMe-Client-Info` (akd form) = `<HardwareModel> <OSname;OSversion;OSbuild> <com.apple.AuthKit/1 (com.apple.akd/1.0)>` (observed `<iMac13,1> <macOS;13.6.4;22G513> …` and `<iPhone7,2> <iPhone OS;12.5.5;16H62> …`); `X-Apple-AK-Context-Type` = `imessage` or `icloud` per flavor [APA:lib.rs], [APA:gsa_auth.rs], [RP:lib.rs]. `spd` details: `GsIdmsToken` is a TOP-LEVEL string key (not inside `t`); `t` entries carry `token` (string) plus `expiry` (epoch-ms) or `duration` (seconds); account-name keys `fn`/`ln` (optional strings) [APA:client.rs]. Host split: `postdata` and `teardown` go to `gsas.apple.com` (with the S) with `X-Apple-I-UrlSwitch-Info` = base64(`adsid:postdata`/`adsid:teardown`); circle requests use `X-Apple-Identity-Token` when 2FA-authenticated, `X-Apple-HB-Token` = base64(`adsid:hbtoken`) otherwise; the app-token checksum is HMAC-SHA256 keyed with the session key over `apptokens` ‖ dsid ‖ app name [APA:client.rs]. Sign-in request values: `User-Agent` = `com.apple.iCloudHelper/282 CFNetwork/1408.0.4 Darwin/22.5.0` (static config constants); `userInfo.timezone` = a hardcoded IANA name (`America/New_York` in the reference config); `X-Mme-Client-Info` wraps the AOSKit form `com.apple.AOSKit/282 (com.apple.accountsd/113)` [RP:auth.rs], [RP:test.rs]. HTTP client notes: Apple root CA pinned, HTTP/1.1 title-cased headers forced, cookie store in use; a parser quirk makes the reference's composed sign-in UA literally `com.apple.iCloudHelper/282 com.apple.iCloudHelper/282` (duplicated token — treat the full static string as the recorded form) [RP:relay.rs]. — 2026-09-11 (small follow-up: the three remaining literals): the 2FA browser `User-Agent` = `Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)` — static in every source, notably with NO Safari/Version token; the non-akd `X-MMe-Client-Info` for the browser set = the same device-bracket form with inner component `com.apple.akd/1.0 (com.apple.akd/1.0)` (rustpush's variant wraps `com.apple.AuthKit/1 (com.apple.MobileSMS/1262.500.151.1.2)`); `X-Apple-I-CFU-State`'s hardcoded base64 decodes to the FULL plist wrapper — `<?xml version="1.0" encoding="UTF-8"?>` + Apple DOCTYPE + `<plist version="1.0">` + `<array/>` + `</plist>` + trailing newline (182 bytes), NOT a bare `<array/>`; the sign-in `X-Mme-Client-Info` is bracketed like the others: `<HardwareModel> <macOS;13.6.4;22G513> <com.apple.AOSKit/282 (com.apple.accountsd/113)>`; and the sign-in UA quirk is confirmed — the composed header is the duplicated `com.apple.iCloudHelper/282 com.apple.iCloudHelper/282` while the full `com.apple.iCloudHelper/282 CFNetwork/1408.0.4 Darwin/22.5.0` string is the config literal other endpoints draw from [APA:anisette.rs], [APA:lib.rs], [APA:gsa_auth.rs], [RP:lib.rs], [RP:test.rs], [RP:macos.rs].
|
||||
|
||||
@@ -330,6 +330,7 @@ Ordered by value. IDs match the inline `TODO(capture):` markers above.
|
||||
|
||||
## Revision history
|
||||
|
||||
- Rev 25 (2026-09-29, first LIVE GSA login of this protocol family — SRP init/complete, both 2FA steps, and the PET-bearing re-login against `gsa.apple.com` with a real Apple ID, user-gated; the delegate sign-in POST was attempted and refused): the §1.5 chain is live-confirmed end-to-end up to the delegate sign-in, with five behavioral facts beyond the extraction record. (1) `au` (`trustedDeviceSecondaryAuth` observed) rides INSIDE the complete response's `Status` dict — reading it at the `Response` level silently turns a 2FA login into a "logged in, no PET" dead end (§1.5 2FA). (2) The PET's second live-confirmed delivery channel: the post-2FA re-login's decrypted `spd` token map `t` (21 service tokens observed incl. `com.apple.gs.idms.pet`), while a no-2FA login's `spd` carries only `com.apple.gs.appleid.auth` and no PET — an earlier implementation inference that a no-2FA login suffices for the PET is dead; 2FA is mandatory for the PET on this account class (§1.5 PET). (3) The trusted-device trigger creates the code on the devices and must precede the prompt; triggering twice before one validate invalidates the outstanding code and re-prompts (§1.5 2FA). (4) Anisette is one-time per request: headers minted at flow start are spent by the GSA hops, and the delegate sign-in with a reused set is refused — mint fresh per POST, matching the reference client's per-request provider (§1.5 delegate logins). Post-login ADI behavior confirmed live: after an account login the machine re-provisions for the personal DSID (GetLoginCode → not-provisioned → full exchange succeeds), which is the personal-`X-Apple-I-MD-M` path, not a defect. (5) The delegate sign-in answered HTTP 409 `ICLOUD_UNSUPPORTED_DEVICE` with the recorded error-plist shape (§1.5 delegate logins) — a device-identity-bound refusal, the current open blocker. Velocity note: a persisted-password-hash re-login was rejected once with -22406 ("enter the correct password") after four full logins in 30 minutes, with the identical hash derivation succeeding twice earlier in the same window — treat as server-side velocity throttling, not a derivation defect.
|
||||
- Rev 24 (2026-09-28, first live probes of this implementation — two unauthenticated bag GETs + a no-client-cert courier TLS handshake, runner `:engine:runProbe`; nothing else touched): (1) C18 SETTLED — the courier demands the Albert client certificate via a fatal TLS 1.3 `certificate_required` alert after a successful handshake, on both ports (§3.1); (2) the bags are TYPED CONFIG DICTIONARIES, not URL maps — the live IDS bag carries ints/bools/floats/string-lists alongside 400+ URLs and a `bag-expiry-timestamp` (~17-day observed TTL; 403 entries live vs 405 on 2026-09-10, two new push-debounce keys in, four A/B-test keys out); the APNs bag is 65 stable entries incl. BAA-authentication flags (`APNSEnableBAAAuthenticationForDevices` etc. — C17/C20-relevant); (3) AppleTrust's embedded-root pinning validates the live chains of `init.ess.apple.com` and `courier.push.apple.com` (no root served — chain anchored at the embedded root); (4) rev-23's container-fact follow-ups: sort-version 0 and the ref-width rule are now capture/probe-consistent.
|
||||
- Rev 23 (2026-09-27, writer-pass follow-up, [CF] public material + host-decoder empirical cross-check only — no restricted source): three §4 container-format gaps found by the first writer session are closed — integer signedness (1/2/4-byte forms unsigned; the 8-byte form is two's-complement signed; non-negative values take the minimal width, negative values always the 8-byte form), the object-reference width rule (minimal width whose 2^(8w) ≥ object count, so every reference 0..count−1 is representable), and the sort-version byte (written as 0).
|
||||
- Rev 22 (2026-09-12, offline byte-audit of the saved live step-2 request — no network call, nothing committed): the CLIENT-SIDE ENCODING class is closed. The exact 3898 bytes the live runner sent (§7 item 24) parse as an XML plist with the single `session-info-request` `<data>` key, and its 2729-byte payload walks the invented TLV v1 to the last byte with ZERO slack, 17 distinct tags, no duplicates and no trailing bytes — our bytes are exactly what the code intends, so no writer bug hides there (the framing *intent* stays invented and unverified). By-product fact: the step-1 `cert` data field is NOT a bare DER cert — it is Apple's own container, `01 02` followed by two `(4-byte BE length, DER cert)` pairs (1046 and 1329 bytes; 2+4+1046+4+1329 = 2385 exactly), carried through verbatim by this implementation (§1.1) and cross-checking against the archived genuine step-1 response size. Apple's length convention there is 4-BYTE big-endian where our TLV v1 uses 2 — the only Apple framing ground truth in hand, and it weakens TLV v1 further.
|
||||
|
||||
Reference in new issue
Block a user