From e5352e77ceed3aeeb77e25ef5027eca94a46e84c Mon Sep 17 00:00:00 2001 From: fenleon Date: Tue, 13 Oct 2026 16:35:03 +0200 Subject: [PATCH] =?UTF-8?q?registration:=20step-2=20optional=20extraHeader?= =?UTF-8?q?s=20(A/B=20instrumentation=20=E2=80=94=20anisette-on-mint=20fal?= =?UTF-8?q?sified=20live=202026-10-13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../imessage/registration/GsaDelegateLogin.kt | 15 +++++++++++++-- .../registration/ValidationDataExchange.kt | 10 ++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/registration/src/main/kotlin/dev/fenn/imessage/registration/GsaDelegateLogin.kt b/registration/src/main/kotlin/dev/fenn/imessage/registration/GsaDelegateLogin.kt index 8020b95..66042bd 100644 --- a/registration/src/main/kotlin/dev/fenn/imessage/registration/GsaDelegateLogin.kt +++ b/registration/src/main/kotlin/dev/fenn/imessage/registration/GsaDelegateLogin.kt @@ -149,8 +149,7 @@ class GsaDelegateLoginClient( val delegates = linkedMapOf( IDS_DELEGATE_BUNDLE_ID to linkedMapOf("protocol-version" to "4"), MOBILEME_DELEGATE_BUNDLE_ID to linkedMapOf(), - ) - // The vendored reference (open_absinthe, RE 2026-10-13) ships ONLY the V1 request + ) // The vendored reference (open_absinthe, RE 2026-10-13) ships ONLY the V1 request // shape on this endpoint: kebab-case `AuthRequest {apple-id, client-id, delegates, // password}` with the PET in the body — the .so has no V2AuthRequest/protocolVersion/ // userInfo strings at all. Basic `username:PET` still rides (the closure's @@ -188,6 +187,18 @@ class GsaDelegateLoginClient( put("User-Agent", SIGNIN_USER_AGENT) } val encoded = XmlPlist.encode(body) + // A/B instrumentation: dump the exact outgoing request (headers + body) when the + // marker exists, for a host-side rustls replay with byte-identical content. The + // dump carries the Basic credential — /data/local/tmp only, never logged. + if (java.io.File("/data/local/tmp/debug-capture-signin").isFile) { + runCatching { + java.io.File("/data/local/tmp/debug-last-signin.bin").writeBytes( + (listOf("POST $endpoint", "Content-Type: $CONTENT_TYPE") + + headers.entries.map { (k, v) -> "$k: $v" } + listOf("", "")).joinToString("\r\n") + .toByteArray(Charsets.UTF_8) + encoded, + ) + } + } val response = http.post(endpoint, headers, encoded, CONTENT_TYPE) if (response.status != 200) { throw GsaLoginException( diff --git a/registration/src/main/kotlin/dev/fenn/imessage/registration/ValidationDataExchange.kt b/registration/src/main/kotlin/dev/fenn/imessage/registration/ValidationDataExchange.kt index efa64c7..655a215 100644 --- a/registration/src/main/kotlin/dev/fenn/imessage/registration/ValidationDataExchange.kt +++ b/registration/src/main/kotlin/dev/fenn/imessage/registration/ValidationDataExchange.kt @@ -78,12 +78,18 @@ class ValidationCertClient( /** * Step 2: POST the single-key `session-info-request` plist body — no anisette, no auth * headers (§1.1: body only) — and return the response's `session-info` data. + * [extraHeaders] rides verbatim (the A/B instrumentation: whether the genuine client + * binds the VEN session to an anisette identity here has never been wire-observed — + * the only accepted captures predate any anisette test). */ - suspend fun initializeValidation(sessionInfoRequest: ByteArray): ByteArray { + suspend fun initializeValidation( + sessionInfoRequest: ByteArray, + extraHeaders: Map = emptyMap(), + ): ByteArray { val url = bags.idsBag().url(ValidationBagKeys.INITIALIZE) logEvent("validation: initializing the validation session") val body = XmlPlist.encode(linkedMapOf("session-info-request" to sessionInfoRequest)) - val response = http.post(url, emptyMap(), body, CONTENT_TYPE) + val response = http.post(url, extraHeaders, body, CONTENT_TYPE) if (response.status != 200) { throw ValidationDataException("POST $url → HTTP ${response.status} (§1.1 step 2)") }