mirror of
https://github.com/fenleon/open-madrid.git
synced 2026-10-22 16:12:35 +00:00
C56-a closed: NacRequestBlob (rev 35) — the 338-B session-info-request blob fully derived
Direct in-process replay of ValidationCtx::new (two byte-verified runs, no Apple network): pearKey16 = the first RNG16 draw (= init_key input = OAEP msg[0:16]); the post-encrypt 'xor' loops are byte-substitution tables (0x2249f9/0x224af9, appended to PearTablesData); the pear-AES plaintext = substitute(02 ‖ u32be(1) ‖ innerKey16 ‖ zeros(10)); the encrypt_cbc iv rides an in/out 16-B block. NacRequestBlob builds the blob with platform OAEP + HmacSHA1; 4 synthetic-vector tests; :registration:test 213 green. Remaining live-mint gate: C56-c only.
This commit is contained in:
1 parent
ca057376ee
commit
f4be1f638f
6 files changed
+231
-19
No files matched your search
@@ -12,7 +12,7 @@ Validated:
|
||||
- The registration chain ran live against Apple (2026-10-02→04, spec rev 26): Albert activation, the packed APNs courier, the full GSA sign-in (SRP, trusted-device 2FA, PET delivery, postdata), then IDS authenticate and register — both green, `mailto:` handle active. The earlier 409 refusals turned out to be Apple's validation-stamp replay guard (one mint = one device record), documented in the spec.
|
||||
- Two live unauthenticated probes against Apple succeeded: both bag endpoints return plists this stack parses, and certificate pinning validates Apple's real TLS chains. A drift check ships with the repo (`:engine:runProbe`).
|
||||
- The courier's auth model is settled: the packed path needs no client certificate; the legacy path answers the handshake with a TLS 1.3 post-handshake `certificate_required` alert.
|
||||
- The ADI anisette chain is reimplemented natively, no emulation or helper binary: the white-box OTP ciphers, the GSA provisioning exchange, the full spim/cpim/ptm wire framing, and the validation-stamp ("NAC") mint — pear white-box cipher, blob assembly, and the 16-byte OTS signature (`NacSign`) — are transcribed clean-room from analysis of a compiled ADI implementation, verified byte-exact against oracle vectors and live captures (three genuine provisioning runs plus validation-stamp captures under an instrumented debugger). Every byte group of the exchange and of the 517-byte stamp is identified and reproduced; the full stamp computes byte-exact in Kotlin from its inputs (the earlier python reference model is retired to dev capture notes; the Kotlin port is pinned by synthetic-vector tests against it). 209 tests green, spec rev 34. A minimal public facade (`NacMint`) exposes the mint for consumers. Of the five wire-input mappings from the validation exchange (§1.1 C56), four are live-pinned (pear key, session blob, RNG pads, cert tail); the last gate is the step-2 request blob's field map (C56-a) — ~85% pinned (RSA-OAEP of the pear key + an HMAC key, an HMAC-SHA1 tail, one 32-B pear-AES field and one 20-B slot remain), with the two unpinned sub-fields named in the spec. No anisette server, no Unicorn, no Mac relay.
|
||||
- The ADI anisette chain is reimplemented natively, no emulation or helper binary: the white-box OTP ciphers, the GSA provisioning exchange, the full spim/cpim/ptm wire framing, and the validation-stamp ("NAC") mint — pear white-box cipher, blob assembly, and the 16-byte OTS signature (`NacSign`) — are transcribed clean-room from analysis of a compiled ADI implementation, verified byte-exact against oracle vectors and live captures (three genuine provisioning runs plus validation-stamp captures under an instrumented debugger). Every byte group of the exchange and of the 517-byte stamp is identified and reproduced; the full stamp computes byte-exact in Kotlin from its inputs (the earlier python reference model is retired to dev capture notes; the Kotlin port is pinned by synthetic-vector tests against it). 213 tests green, spec rev 35. A minimal public facade (`NacMint`) exposes the mint for consumers. All five wire-input mappings from the validation exchange (§1.1 C56) are now pinned: the step-2 request blob (C56-a) was closed by direct in-process replay of the reference client's validation context — the full 338-byte recipe (constant header, the RSA-OAEP of the pear key + an HMAC key, a fresh 16-byte slot, the length marker, the substituted-and-encrypted 32-byte field, the HMAC-SHA1 tail) is implemented in Kotlin (`NacRequestBlob`) and verified byte-exact on two independent runs. The last gate before a live validation mint is the hardware-identity serialization's field values (C56-c). No anisette server, no Unicorn, no Mac relay.
|
||||
|
||||
Not validated yet:
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# iMessage protocol spec (clean-room)
|
||||
|
||||
**Status:** draft — revision 32. The full revision history is at the bottom ([Revision history](#revision-history)); the sections below state the facts as of rev 31.
|
||||
**Status:** draft — revision 35. The full revision history is at the bottom ([Revision history](#revision-history)); the sections below state the facts as of rev 31.
|
||||
|
||||
**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.
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
- Current shipped flow (OpenBubbles, 2026): the Mac-side helper (`Mac-Hardware-Info` 1.2.0, notarized) only *reads* the machine identifiers — serial, board ID, model, etc. — via public IORegistry/sysctl APIs (docs: "High Sierra and above", no upper macOS version bound) and exports them as a shareable code (≤ 20 users per code, optional one-time-use/48-hour mode); it never invokes an Apple private framework [OB-MHI], [OB-FAQ]. The client then performs the three-step exchange above itself, in software, keyed with those identifiers and paired with an Apple-Account login — the [JJ] emulation path with an official identifier-export helper in front of it. `TODO(capture):` **C56** — the client-side generation procedure behind the three-step exchange: what the client computes with the `id-initialize-validation` session info (the key-establishment math and blob assembly), how it interacts with the Apple-Account/GSA login and the anisette header set (C6) at activation time, and which pieces are fixed protocol constants vs per-account/per-session state. — 2026-09-10: steps 1–2 of C56 are recorded (bullet above); step 3 — key establishment + signing — is closed source: the public OpenAbsinthe-Stub repo is an official mock whose methods are stubs (its README states the real repository is closed source), so the spec records only the API contract: context creation consumes the certificate chain + hardware configuration and outputs the session-info-request blob; key establishment consumes the server response; signing produces the final blob with no further inputs (all state retained in the context) [RP:macos.rs], [RP:activation.rs], [RP:auth.rs], [OA]. Remaining: wire-verification of the blob against one live activation; the enc-variant derivation rides C57. — 2026-09-11 (rev 14 extraction): the five obfuscation labels are now MAPPED to their fields (previously only listed together): serial → `Gq3489ugfi`, platform UUID → `Fyp98tpgj`, root-disk UUID → `kbjfrfpoJU`, ROM → `oycqAZloTNDm`, MLB → `abKPld1EcMni` [OA:nac.rs]; the derivation producing each `_enc` byte variant is still closed source. The hardware-configuration field set is recorded in full: product name (Apple product-type form, e.g. `Macmini9,1`), MAC address (exactly 6 raw bytes), platform serial, platform UUID, root-disk UUID, board ID, OS build number, ROM (raw bytes), MLB, plus the five `_enc` raw-byte variants [OA:nac.rs]. Step 3's closure is confirmed at the source level: the crate holding the key-establishment math ships as a closed-source stub whose mock methods panic; only the API contract is observable [OA:README.md], [OA:nac.rs].
|
||||
- The helper's export-code format (2026-09-11, rev 15 — **C58 closed** by facts-only extraction from the helper's public upstream repo, which is unlicensed — facts only): the payload is a 4-byte ASCII magic `OABS`, a one-byte flag (1 = "prevent sharing" mode, 0 otherwise), then a raw protobuf message (no length prefix; protobuf starts at offset 5). Protobuf field map: outer — 1 nested identifier message, 2 macOS product version (sysctl `kern.osproductversion`), 3 protocol version (hardcoded `1640`), 4 device id (the IOPlatformUUID again), 5 a hardcoded iCloudHelper user-agent string, 6 a hardcoded AOSKit version string; nested identifier message — 1 product name (e.g. `MacBookPro18,3`), 2 MAC address (6 raw bytes), 3 platform serial, 4 platform UUID, 5 root-disk UUID, 6 board ID (Apple Silicon: literal prefix `Mac-` + lowercase hex of the device-tree board-id bytes), 7 OS build number (sysctl `kern.osversion`), 8/9/10/12/14 the five `_enc` obfuscated blobs read from the `IOPower:/` registry under Apple's key names (`Gq3489ugfi` serial, `Fyp98tpgj` platform UUID, `kbjfrfpoJU` root-disk UUID, `oycqAZloTNDm` ROM, `abKPld1EcMni` MLB — matching §1.1's label mapping), 11 ROM (Intel: the device-tree options ROM value; Apple Silicon: last 6 bytes of SHA-256 of `/chosen` `unique-chip-id`), 13 MLB string. Two delivery modes: plain = standard base64 of the payload (clipboard text, or the raw payload bytes as a QR code at medium error correction); restricted = AES-256-CBC/PKCS7 over the payload, ciphertext prefixed with ASCII `Salted__` + 8-byte random salt (legacy OpenSSL `enc` container), key/IV derived by the legacy EVP_BytesToKey KDF (repeated MD5 of previous-hash ‖ password ‖ salt to 48 bytes: first 32 = key, next 16 = IV) keyed by a human activation code (`MB` + 4×4 chars from the alphabet `ABCDEFGHJKLMNPQRSTUVWXYZ123456789`), then base64. The restricted mode additionally uploads the blob (JSON `data` + `id` = lowercase hex SHA-256 of the activation code) to the vendor's server (`hw.openbubbles.app/code`) — the ≤20-user/one-time/48-hour mechanics live SERVER-side; nothing is embedded in the payload (no checksum, signature, compression, or expiry). No public parser exists (the client apps are closed-source); the construction above is the parse specification [MHG:ViewController.swift], [MHG:HwIdentifiers.swift], [MHG:mac_hw_info.pb.swift].
|
||||
- The validation blob is submitted as the `validation-data` field of the IDS register body and, base64, in the `X-Mme-Nas-Qualify` header of the iCloud account login [RP:ids/user.rs], [RP:auth.rs]. `TODO(capture):` C5 — exact byte shape of the blob a genuine Mac produces, and the current exchange over the two validation bag URLs. — 2026-10-04 (rev 26, live): **REPLAY GUARD — one mint = one device record.** Apple keys a validation blob's consumption server-side: re-submitting an already-consumed blob at the sign-in/register pair answers HTTP 409, which is a REPLAY refusal, NOT a blob rejection — a fresh blob under the same device identity succeeds where the reused one failed, and a captured blob of external provenance succeeds outright (session 17: a captured stamp replaced the local mint and the 409 vanished). Client consequences: mint or fetch a fresh blob per registration attempt (the 15-minute validity above makes stale reuse doubly fatal), and mint the sign-in's blob AT SIGN-IN TIME — never a login-start blob carried across the 2FA wait. A caller-injected blob from any source (local mint, relay Mac, staged capture) is a legitimate stamp; the injection points are the register body's `validation-data` and the sign-in's `X-Mme-Nas-Qualify` header.
|
||||
- Hardware-related anisette headers seen in activation flows: `X-Apple-I-SRL-NO` (serial), `X-Apple-I-MLB` (logic-board serial), `X-Apple-I-ROM` (hex ROM, lowercase) [RP:relay.rs], [APA:anisette]. `TODO(capture):` C6 — the full anisette header set a current client actually sends, including which optional hardware headers appear. — 2026-09-10: the header-set half of C6 is closed by facts-only extraction: per-request anisette headers `X-Apple-I-Client-Time`, `X-Apple-I-TimeZone`, `X-Apple-Locale`, `X-Apple-I-MD-RINFO`, `X-Mme-Device-Id`, `X-Apple-I-MD` (base64 OTP), `X-Apple-I-MD-M` (base64 machine ID), plus per-platform hardware headers — macOS: `X-Apple-I-MLB`, `X-Apple-I-ROM` (lowercase hex), `X-Apple-I-SRL-NO` [APA:omnisette], [APA:icloud-auth], [RP:relay.rs]. They are produced by an ADI provisioning exchange with gsa.apple.com (start/finish provisioning returning machine tokens), then a local OTP [APA:omnisette]. GSA context (cross-referenced with C6): the Albert activation itself needs NO Apple-Account login — only the FairPlay-signed activation-info plist (§1.2); the GSA login (full Apple-ID password, SRP over gsa.apple.com/grandslam/GsService2, s2k/s2k_fo PBKDF2, 2FA-capable) feeds the delegate logins that yield the IDS `auth-token`/`profile-id` and the PET token [RP:auth.rs], [APA:icloud-auth]. The extraction confirms the validation blob rides base64 in the `X-Mme-Nas-Qualify` header of the iCloud sign-in POST (recorded above) [RP:auth.rs]. — 2026-09-11 (rev 14 extraction round over the anisette crates): the value-format and provisioning halves of C6 are closed. Per-request value forms: `X-Apple-I-Client-Time` = UTC now truncated to whole seconds, RFC 3339/ISO-8601 with the offset suffix replaced by `Z` (provisioning requests send the raw offset form without the Z — a recorded inconsistency between code paths); `X-Apple-I-TimeZone` = literal `UTC` (not the device zone); `X-Apple-Locale` = `en_US` hardcoded (a companion `X-Apple-I-Locale: en_US` variant exists on the browser-style header set); `X-Apple-I-MD-RINFO` = verbatim passthrough of the string returned in the finish-provisioning response; `X-Mme-Device-Id` = lowercase hyphenated UUID string of 16 locally random bytes (`keychain_identifier`); `X-Apple-I-MD-M` = base64 (standard, padded) of the 60-byte `mid`; `X-Apple-I-MD-LU` = lowercase hex (no dashes) SHA-256 over the same 16 bytes (real macOS instead uses AuthKit's local-user UUID) — present in some header sets, omitted in others (disagreement — C57). Header sets are cached 60 s; a fresh OTP is generated per cache miss. The ADI provisioning flow: `GET https://gsa.apple.com/grandslam/GsService2/lookup` → XML plist whose `urls` keys `midStartProvisioning`/`midFinishProvisioning` give the endpoint URLs (never hardcoded); POST to the start URL with an XML plist body of two empty top-level dicts `Header` and `Request` → response `Response` → `spim` (base64); a local session is built from `spim` (+ empty hostuuid, DSID `-2`, flavor `Mac`=1 / `IOS`=3, flavor chosen by whether the client-info string contains `iPhone OS`) yielding `cpim` (base64); POST to the finish URL with `Request` = {`cpim`} → response `Response` → `ptm`, `tk` (both base64) and `X-Apple-I-MD-RINFO`; finishing the session with the decoded `tk`/`ptm` yields the machine state: `client_secret` (32 bytes), `mid` (60 bytes, Apple-issued at provisioning — reused for every OTP until re-provisioning, which happens only on a not-provisioned failure, e.g. ADI error -45061), `metadata` (variable blob). Provisioning HTTP specifics: `Content-Type: application/x-www-form-urlencoded` despite the XML-plist body (intentional), title-cased header names forced, HTTP/1.1, Apple root CA pinned (embedded DER), and a fixed companion header set on all three requests: akd `User-Agent`, `X-Apple-Baa-E: -10000`, `X-Apple-Baa-Avail: 2`, `X-Apple-Baa-UE: AKAuthenticationError:-7066|com.apple.devicecheck.error.baa:-10000`, `X-Apple-Host-Baa-E: -7066`, `X-Apple-Client-App-Name: akd`, `Accept: */*`, `Accept-Language: en-US,en;q=0.9`, plus `X-Apple-I-MD-LU`, `X-Mme-Device-Id`, `X-Mme-Client-Info`, `X-Apple-I-Client-Time` and any caller-supplied hardware headers. State persists as an XML plist with `keychain_identifier` (data) + the provisioned fields (`client_secret`, `mid`, `metadata`, `rinfo`, `flavor`) [APA:omnisette]. CLOSED-SOURCE RESIDUE: the OTP (`X-Apple-I-MD`) and trusted-device 2FA-code derivations live inside a closed-source crate — its public repo is a stub with `todo!()` methods; recorded is only the call contract: inputs (32-byte `client_secret`, 60-byte `mid`, `metadata`, flavor) → raw OTP bytes (base64 into the header) / 32-bit 2FA code [OA-stub], [APA:omnisette]. — rides C57/C56 residue; the seam stays injectable in the code. — 2026-10-07 (rev 27, clean-room RE of the OpenBubbles ADI library — unstripped Linux `.so`, oracle-verified; the derivation is no longer closed residue): the OTP is fully local and purLine truncated
|
||||
- Hardware-related anisette headers seen in activation flows: `X-Apple-I-SRL-NO` (serial), `X-Apple-I-MLB` (logic-board serial), `X-Apple-I-ROM` (hex ROM, lowercase) [RP:relay.rs], [APA:anisette]. `TODO(capture):` C6 — the full anisette header set a current client actually sends, including which optional hardware headers appear. — 2026-09-10: the header-set half of C6 is closed by facts-only extraction: per-request anisette headers `X-Apple-I-Client-Time`, `X-Apple-I-TimeZone`, `X-Apple-Locale`, `X-Apple-I-MD-RINFO`, `X-Mme-Device-Id`, `X-Apple-I-MD` (base64 OTP), `X-Apple-I-MD-M` (base64 machine ID), plus per-platform hardware headers — macOS: `X-Apple-I-MLB`, `X-Apple-I-ROM` (lowercase hex), `X-Apple-I-SRL-NO` [APA:omnisette], [APA:icloud-auth], [RP:relay.rs]. They are produced by an ADI provisioning exchange with gsa.apple.com (start/finish provisioning returning machine tokens), then a local OTP [APA:omnisette]. GSA context (cross-referenced with C6): the Albert activation itself needs NO Apple-Account login — only the FairPlay-signed activation-info plist (§1.2); the GSA login (full Apple-ID password, SRP over gsa.apple.com/grandslam/GsService2, s2k/s2k_fo PBKDF2, 2FA-capable) feeds the delegate logins that yield the IDS `auth-token`/`profile-id` and the PET token [RP:auth.rs], [APA:icloud-auth]. The extraction confirms the validation blob rides base64 in the `X-Mme-Nas-Qualify` header of the iCloud sign-in POST (recorded above) [RP:auth.rs]. — 2026-09-11 (rev 14 extraction round over the anisette crates): the value-format and provisioning halves of C6 are closed. Per-request value forms: `X-Apple-I-Client-Time` = UTC now truncated to whole seconds, RFC 3339/ISO-8601 with the offset suffix replaced by `Z` (provisioning requests send the raw offset form without the Z — a recorded inconsistency between code paths); `X-Apple-I-TimeZone` = literal `UTC` (not the device zone); `X-Apple-Locale` = `en_US` hardcoded (a companion `X-Apple-I-Locale: en_US` variant exists on the browser-style header set); `X-Apple-I-MD-RINFO` = verbatim passthrough of the string returned in the finish-provisioning response; `X-Mme-Device-Id` = lowercase hyphenated UUID string of 16 locally random bytes (`keychain_identifier`); `X-Apple-I-MD-M` = base64 (standard, padded) of the 60-byte `mid`; `X-Apple-I-MD-LU` = lowercase hex (no dashes) SHA-256 over the same 16 bytes (real macOS instead uses AuthKit's local-user UUID) — present in some header sets, omitted in others (disagreement — C57). Header sets are cached 60 s; a fresh OTP is generated per cache miss. The ADI provisioning flow: `GET https://gsa.apple.com/grandslam/GsService2/lookup` → XML plist whose `urls` keys `midStartProvisioning`/`midFinishProvisioning` give the endpoint URLs (never hardcoded); POST to the start URL with an XML plist body of two empty top-level dicts `Header` and `Request` → response `Response` → `spim` (base64); a local session is built from `spim` (+ empty hostuuid, DSID `-2`, flavor `Mac`=1 / `IOS`=3, flavor chosen by whether the client-info string contains `iPhone OS`) yielding `cpim` (base64); POST to the finish URL with `Request` = {`cpim`} → response `Response` → `ptm`, `tk` (both base64) and `X-Apple-I-MD-RINFO`; finishing the session with the decoded `tk`/`ptm` yields the machine state: `client_secret` (32 bytes), `mid` (60 bytes, Apple-issued at provisioning — reused for every OTP until re-provisioning, which happens only on a not-provisioned failure, e.g. ADI error -45061), `metadata` (variable blob). Provisioning HTTP specifics: `Content-Type: application/x-www-form-urlencoded` despite the XML-plist body (intentional), title-cased header names forced, HTTP/1.1, Apple root CA pinned (embedded DER), and a fixed companion header set on all three requests: akd `User-Agent`, `X-Apple-Baa-E: -10000`, `X-Apple-Baa-Avail: 2`, `X-Apple-Baa-UE: AKAuthenticationError:-7066|com.apple.devicecheck.error.baa:-10000`, `X-Apple-Host-Baa-E: -7066`, `X-Apple-Client-App-Name: akd`, `Accept: */*`, `Accept-Language: en-US,en;q=0.9`, plus `X-Apple-I-MD-LU`, `X-Mme-Device-Id`, `X-Mme-Client-Info`, `X-Apple-I-Client-Time` and any caller-supplied hardware headers. State persists as an XML plist with `keychain_identifier` (data) + the provisioned fields (`client_secret`, `mid`, `metadata`, `rinfo`, `flavor`) [APA:omnisette]. CLOSED-SOURCE RESIDUE: the OTP (`X-Apple-I-MD`) and trusted-device 2FA-code derivations live inside a closed-source crate — its public repo is a stub with `todo!()` methods; recorded is only the call contract: inputs (32-byte `client_secret`, 60-byte `mid`, `metadata`, flavor) → raw OTP bytes (base64 into the header) / 32-bit 2FA code [OA-stub], [APA:omnisette]. — rides C57/C56 residue; the seam stays injectable in the code. — 2026-10-07 (rev 27, clean-room RE of the OpenBubbles ADI library — unstripped Linux `.so`, oracle-verified; the derivation is no longer closed residue): the OTP is fully local and purLine truncated
|
||||
|
||||
### 1.2 Activation endpoints and certificates
|
||||
|
||||
@@ -330,6 +330,7 @@ Ordered by value. IDs match the inline `TODO(capture):` markers above.
|
||||
|
||||
|
||||
## Revision history
|
||||
- Rev 35 (2026-10-12, C56-a CLOSED — direct in-process replay of ValidationCtx::new, two byte-verified runs): the request blob's full recipe — header ‖ OAEP(pubkey, pearKey16 ‖ hmacKey20) ‖ RNG16 ‖ u32be(32) ‖ field32 ‖ HMAC-SHA1(hmacKey20, field32), with field32 = mapA[encrypt_cbc(mapB[02 ‖ u32be(1) ‖ innerKey16 ‖ zeros], iv, init_key(pearKey16))] and mapA/mapB the 0x2249f9/0x224af9 byte-substitution tables (table[byte], not xor). Kotlin `NacRequestBlob`; imessage unstubbed at C56-a; live-mint gate = C56-c only.
|
||||
|
||||
- Rev 34 (2026-10-09, C56-a request-blob field map ~85% pinned — four instrumented runs): the 338-B request blob decomposes as header ‖ RSA-2048-OAEP-SHA1(cert pubkey, pearKey16 ‖ hmacKey20) ‖ 20 B (unpinned, ends u32be(32)) ‖ 32 B pear-AES field (derivation open) ‖ HMAC-SHA1(hmacKey20, the 32-B field) — the HMAC chain verified byte-exact. Live mint still gated on the two unpinned C56-a sub-fields; C58 parser + §1.2/§1.4 activation chain wired in imessage (`register()`), tool UI inputs (export-code paste, courier push token) pending.
|
||||
- Rev 33 (2026-10-09, the sign-model fresh-input divergence CLOSED — the last functional live-mint blocker on the sign path): two session-20-frozen constants inside `SignState::sign` are falsified and replaced by their input-dependent forms — the staging maze's i==10 `ecx2` index (`perm2u[st[0x6fa2]]`, was frozen `perm2u[0x40]`) and the ENTIRE P region state+0x6898 (7×256; five of seven tables are parameterized on five body-derived byte XOR pairs via the SSE builder at 0x17a1381-0x17a20b5 — the "byte-identical across base/mod/body-flip" claim held only because those captures shared a body). The builder stream is serialized into generated Kotlin (`NacPTable`) and re-executed lane-wise, byte-exact vs the emulated builder on fresh + random + s20 states. `NacSign` re-ported with all body-derived regions computed (region6f98/0x6fd8/0x6fe8/0x6fec/N/cursor/RC); fresh live sig16 + full 517-B stamp byte-exact, session-20 still byte-exact; 209 registration tests green, imessage `:server:testDebugUnitTest` green. Live mint still gated on C56-a + C56-c (capture) and C58 (OABS parser).
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
package dev.fenn.imessage.registration
|
||||
|
||||
import java.security.PublicKey
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
/**
|
||||
* The §1.1 step-2 `session-info-request` blob (C56-a — the 338-B request the client sends
|
||||
* with the step-1 certificate's public key wrapped inside). Field map pinned 2026-10-12 by
|
||||
* direct in-process replay of `ValidationCtx::new` (two independent runs, byte-verified;
|
||||
* NAC-NOTES.md "Session-24 continuation" + spec §1.1 rev 35):
|
||||
*
|
||||
* ```
|
||||
* blob 338 B = header `01 08 4b2c91481d9b7da0` (10 B, constant)
|
||||
* ‖ RSA-2048-OAEP-SHA1(step-1 cert pubkey, pearKey16 ‖ hmacKey20) (256 B)
|
||||
* ‖ slot16 (16 fresh client RNG bytes) (16 B)
|
||||
* ‖ u32be(32) — the next field's length marker (4 B)
|
||||
* ‖ field32 = blobFieldMap[PearAes.encryptCbc(plain32, iv16, PearAes.initKey(pearKey16))]
|
||||
* (32 B)
|
||||
* ‖ HMAC-SHA1(hmacKey20, field32) (20 B)
|
||||
* ```
|
||||
*
|
||||
* `plain32 = blobPayloadMap[02 ‖ u32be(1) ‖ innerKey16 ‖ zeros(10)]` — the wire format the
|
||||
* binary's serializer writes (`u8 tag=2, u32be version=1, key16`), zero-padded to the 32-B
|
||||
* pear-AES block pair and then byte-mapped through the payload table. `blobFieldMap` /
|
||||
* `blobPayloadMap` are the .so's byte→byte substitution tables at 0x2249f9 / 0x224af9 (the
|
||||
* serializer and the post-encrypt loops apply `table[byte]`, not XOR).
|
||||
*
|
||||
* Role model: the server decrypts the OAEP → (pearKey16, hmacKey20), derives the PAESKey
|
||||
* from pearKey16, decrypts field32, and verifies the HMAC — a possession-of-pearKey16 proof
|
||||
* bound to the blob. Everything is minted locally: three fresh RNG draws plus the caller's
|
||||
* pearKey16 (the SAME pearKey16 the §1.1 key establishment consumes — [NacMint.keyEstablishmentFull]).
|
||||
*/
|
||||
object NacRequestBlob {
|
||||
|
||||
/** The constant 10-byte header every observed blob starts with. */
|
||||
val header: ByteArray = byteArrayOf(
|
||||
0x01.toByte(), 0x08, 0x4b.toByte(), 0x2c.toByte(), (0x91).toByte(),
|
||||
0x48, 0x1d.toByte(), (0x9b).toByte(), 0x7d.toByte(), (0xa0).toByte(),
|
||||
)
|
||||
|
||||
const val BLOB_BYTES = 338
|
||||
const val PEAR_KEY16_BYTES = 16
|
||||
|
||||
/** Builds the blob; [pearKey16] is returned-consumed by the §1.1 key establishment. */
|
||||
fun build(certDer: ByteArray, pearKey16: ByteArray, random: SecureRandom): ByteArray {
|
||||
val certificate = java.security.cert.CertificateFactory.getInstance("X.509")
|
||||
.generateCertificate(certDer.inputStream())
|
||||
return build(certificate.publicKey, pearKey16, random)
|
||||
}
|
||||
|
||||
fun build(publicKey: PublicKey, pearKey16: ByteArray, random: SecureRandom): ByteArray =
|
||||
build(publicKey, pearKey16) { n -> ByteArray(n).also(random::nextBytes) }
|
||||
|
||||
/** Deterministic core (tests): every randomness draw routes through [draw]. */
|
||||
internal fun build(publicKey: PublicKey, pearKey16: ByteArray, draw: (Int) -> ByteArray): ByteArray {
|
||||
require(pearKey16.size == PEAR_KEY16_BYTES) {
|
||||
"pearKey16 must be $PEAR_KEY16_BYTES bytes (got ${pearKey16.size})"
|
||||
}
|
||||
val hmacKey20 = draw(20)
|
||||
val innerKey16 = draw(16)
|
||||
val slot16 = draw(16)
|
||||
val iv16 = draw(16)
|
||||
|
||||
val paesKey = PearAes.initKey(pearKey16)
|
||||
val plain32 = ByteArray(32)
|
||||
plain32[0] = 0x02
|
||||
plain32[4] = 0x01 // u32be(1) — the serializer's version field
|
||||
innerKey16.copyInto(plain32, W16_OFFSET)
|
||||
val payloadMap = PearTables.blobPayloadMap
|
||||
val mappedPlain = ByteArray(32) { i -> payloadMap[plain32[i].toInt() and 0xff] }
|
||||
val enc32 = PearAes.encryptCbc(mappedPlain, iv16, paesKey)
|
||||
val fieldMap = PearTables.blobFieldMap
|
||||
val field32 = ByteArray(32) { i -> fieldMap[enc32[i].toInt() and 0xff] }
|
||||
|
||||
val oaep = Cipher.getInstance(OAEP_TRANSFORMATION).apply {
|
||||
init(Cipher.ENCRYPT_MODE, publicKey)
|
||||
}
|
||||
val oaepOut = oaep.doFinal(pearKey16 + hmacKey20)
|
||||
check(oaepOut.size == 256) { "OAEP output ${oaepOut.size} B, expected 256" }
|
||||
|
||||
val mac = Mac.getInstance("HmacSHA1").apply {
|
||||
init(SecretKeySpec(hmacKey20, "HmacSHA1"))
|
||||
}
|
||||
val tail = mac.doFinal(field32)
|
||||
|
||||
val blob = ByteArray(BLOB_BYTES)
|
||||
header.copyInto(blob, 0)
|
||||
oaepOut.copyInto(blob, 10)
|
||||
slot16.copyInto(blob, 266)
|
||||
blob[282] = 0x00
|
||||
blob[283] = 0x00
|
||||
blob[284] = 0x00
|
||||
blob[285] = 0x20 // u32be(32) — the field32 length marker
|
||||
field32.copyInto(blob, 286)
|
||||
tail.copyInto(blob, 318)
|
||||
return blob
|
||||
}
|
||||
|
||||
private const val W16_OFFSET = 5
|
||||
private const val OAEP_TRANSFORMATION = "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
|
||||
}
|
||||
@@ -57,8 +57,7 @@ internal object PearTables {
|
||||
val tc: IntArray by lazy { wordsAt(8464, 256) }
|
||||
/** .rodata 0x2465b8 — encrypt T-table D. */
|
||||
val td: IntArray by lazy { wordsAt(9488, 256) }
|
||||
/** .rodata 0x2469b8 — establish_key schedule sbox. */
|
||||
val s3: ByteArray by lazy { bytesAt(10512, 256) }
|
||||
/** .rodata 0x2469b8 — establish_key schedule sbox. */ val s3: ByteArray by lazy { bytesAt(10512, 256) }
|
||||
/** .rodata 0x246ab8 — establish_key schedule rcon (only bytes 1..10 consumed; the tail overlaps the est-transform tables). */
|
||||
val r3: ByteArray by lazy { bytesAt(10768, 256) }
|
||||
/** .rodata 0x246ac4 — establish_key transform T-table 0. */
|
||||
@@ -73,4 +72,8 @@ internal object PearTables {
|
||||
val s2: ByteArray by lazy { bytesAt(15120, 256) }
|
||||
/** .rodata 0x247bc4 — init_key schedule rcon (only bytes 1..10 consumed). */
|
||||
val r2: ByteArray by lazy { bytesAt(15376, 256) }
|
||||
/** .rodata 0x2249f9 — the §1.1 request blob's pear-AES-output byte map (table[byte]). */
|
||||
val blobFieldMap: ByteArray by lazy { bytesAt(15632, 256) }
|
||||
/** .rodata 0x224af9 — the §1.1 request blob's plaintext byte map (table[byte]). */
|
||||
val blobPayloadMap: ByteArray by lazy { bytesAt(15888, 256) }
|
||||
}
|
||||
@@ -1,22 +1,24 @@
|
||||
package dev.fenn.imessage.registration
|
||||
|
||||
/**
|
||||
* GENERATED FILE — regenerate with the extraction script described in WORKLOG (session: NAC/pear
|
||||
* port); do not hand-edit. The pear_aes / NAC white-box .rodata tables (librust_lib_bluebubbles),
|
||||
* embedded as base64 source so no Apple-derived binary ships in the repo: one 15632-byte image
|
||||
* of the 26 tables consumed by [PearTables], concatenated in vaddr order
|
||||
* (0x1a0ea0 .. 0x247cc4; the r3 tail deliberately overlaps the est-transform tables — the key
|
||||
* schedule only consumes rcon bytes 1..10). [PearTables] decodes it once at first use; raw dev
|
||||
* copies stay under registration/dev/clearadi/ (gitignored) for byte-level cross-checks.
|
||||
* GENERATED FILE — the pear_aes / NAC white-box .rodata tables (librust_lib_bluebubbles),
|
||||
* embedded as base64 source so no Apple-derived binary ships in the repo: one 16144-byte image
|
||||
* of the 28 tables consumed by [PearTables], concatenated in vaddr order (0x1a0ea0 .. 0x247cc4,
|
||||
* then the two 2026-10-12 request-blob byte maps 0x2249f9/0x224af9 appended — the original
|
||||
* extraction script's vaddr ordering was not re-run, the maps are tail-appended). [PearTables]
|
||||
* decodes it once at first use; raw dev copies stay under registration/dev/clearadi/ (gitignored)
|
||||
* for byte-level cross-checks.
|
||||
*/
|
||||
internal object PearTablesData {
|
||||
/** pear tables image, base64, 6 chunk(s) — concatenate in order. */
|
||||
/** pear tables image, base64, 8 chunk(s) — concatenate in order. */
|
||||
val pearChunks: List<String> = listOf(
|
||||
"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",
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package dev.fenn.imessage.registration
|
||||
|
||||
import java.security.KeyPairGenerator
|
||||
import java.security.SecureRandom
|
||||
import javax.crypto.Mac
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFailsWith
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/** Synthetic-vector tests for the §1.1 step-2 request blob (C56-a; spec §1.1 rev 35). */
|
||||
class NacRequestBlobTest {
|
||||
|
||||
private class FixedDraw(values: List<ByteArray>) {
|
||||
val drawn = mutableListOf<ByteArray>()
|
||||
private val queue = ArrayDeque(values)
|
||||
fun next(n: Int): ByteArray {
|
||||
val v = queue.removeFirst()
|
||||
assertEquals(n, v.size, "draw size")
|
||||
drawn.add(v)
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
private fun keyPair() = KeyPairGenerator.getInstance("RSA").apply { initialize(2048) }.generateKeyPair()
|
||||
|
||||
private fun blob(publicKey: java.security.PublicKey, draw: FixedDraw): Pair<ByteArray, ByteArray> {
|
||||
val pearKey16 = ByteArray(16) { (it * 17 + 3).toByte() }
|
||||
val out = NacRequestBlob.build(publicKey, pearKey16) { n -> draw.next(n) }
|
||||
return out to pearKey16
|
||||
}
|
||||
|
||||
@Test
|
||||
fun blobShape() {
|
||||
val pair = keyPair()
|
||||
val draw = FixedDraw(
|
||||
listOf(
|
||||
ByteArray(20) { it.toByte() }, // hmacKey20
|
||||
ByteArray(16) { (it + 1).toByte() }, // innerKey16
|
||||
ByteArray(16) { (it + 2).toByte() }, // slot16
|
||||
ByteArray(16) { (it + 3).toByte() }, // iv16
|
||||
),
|
||||
)
|
||||
val (blob, pearKey16) = blob(pair.public, draw)
|
||||
assertEquals(NacRequestBlob.BLOB_BYTES, blob.size)
|
||||
assertContentEquals(NacRequestBlob.header, blob.copyOfRange(0, 10))
|
||||
// slot16 rides at [266:282], followed by the u32be(32) length marker
|
||||
assertContentEquals(draw.drawn[2], blob.copyOfRange(266, 282))
|
||||
assertContentEquals(byteArrayOf(0, 0, 0, 0x20), blob.copyOfRange(282, 286))
|
||||
// OAEP decrypt round-trip: the plaintext is pearKey16 ‖ hmacKey20
|
||||
val cipher = javax.crypto.Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding")
|
||||
cipher.init(javax.crypto.Cipher.DECRYPT_MODE, pair.private)
|
||||
val plain = cipher.doFinal(blob.copyOfRange(10, 266))
|
||||
assertEquals(36, plain.size)
|
||||
assertContentEquals(pearKey16, plain.copyOfRange(0, 16))
|
||||
assertContentEquals(draw.drawn[0], plain.copyOfRange(16, 36))
|
||||
// the tail is HMAC-SHA1(hmacKey20, field32)
|
||||
val mac = Mac.getInstance("HmacSHA1")
|
||||
mac.init(SecretKeySpec(draw.drawn[0], "HmacSHA1"))
|
||||
assertContentEquals(mac.doFinal(blob.copyOfRange(286, 318)), blob.copyOfRange(318, 338))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun field32IsMappedEncryptedPayload() {
|
||||
val hmacKey20 = ByteArray(20) { (0x40 + it).toByte() }
|
||||
val innerKey16 = ByteArray(16) { (0x50 + it).toByte() }
|
||||
val slot16 = ByteArray(16) { (0x60 + it).toByte() }
|
||||
val iv16 = ByteArray(16) { (0x70 + it).toByte() }
|
||||
val draw = FixedDraw(listOf(hmacKey20, innerKey16, slot16, iv16))
|
||||
val (blob, pearKey16) = blob(keyPair().public, draw)
|
||||
|
||||
val plain32 = ByteArray(32)
|
||||
plain32[0] = 0x02
|
||||
plain32[4] = 0x01
|
||||
innerKey16.copyInto(plain32, 5)
|
||||
val mappedPlain = ByteArray(32) { i -> PearTables.blobPayloadMap[plain32[i].toInt() and 0xff] }
|
||||
val enc32 = PearAes.encryptCbc(mappedPlain, iv16, PearAes.initKey(pearKey16))
|
||||
val field32 = ByteArray(32) { i -> PearTables.blobFieldMap[enc32[i].toInt() and 0xff] }
|
||||
assertContentEquals(field32, blob.copyOfRange(286, 318))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun rejectsWrongPearKeySize() {
|
||||
val draw = FixedDraw(listOf(ByteArray(20), ByteArray(16), ByteArray(16), ByteArray(16)))
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
NacRequestBlob.build(keyPair().public, ByteArray(15)) { n -> draw.next(n) }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun publicBuildIsRandomizedAndWellFormed() {
|
||||
val pearKey16 = ByteArray(16).also(SecureRandom()::nextBytes)
|
||||
val blob = NacRequestBlob.build(keyPair().public, pearKey16, SecureRandom())
|
||||
assertEquals(NacRequestBlob.BLOB_BYTES, blob.size)
|
||||
assertContentEquals(NacRequestBlob.header, blob.copyOfRange(0, 10))
|
||||
val blob2 = NacRequestBlob.build(keyPair().public, pearKey16, SecureRandom())
|
||||
// fresh randomness per build: the OAEP ciphertext (padded RSA) must differ
|
||||
assertTrue(!blob.contentEquals(blob2))
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user