Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,23 @@ python -m venv .venv && .venv/bin/pip install "FindMy==<pinned version>"
the pre-fix stub back and requires the job to fail. `native-stubs.yml` checks the stubs' exports
and return conventions on their own, in seconds.

- **The machine id carries its provenance, and Grand Slam reads it.** The libraries are Apple's
Android ADI binaries, and the `X-Apple-I-MD-M` they mint says so. Since 2026-10-12 the
GsService2 handler refuses SRP init with `ec=-80045` when that machine id is presented under a
client-info claiming macOS or iPhone — before the password, before the OTP (a wrong OTP still
gets `-80045`; a missing one gets `-80010`). An Android client-info over the same machine id is
answered `ec=0`. That is why `AdiDeviceIdentity` claims a Pixel 7 and why `canSignIn()` exists:
provenance is bound at provisioning, so a Mac-provisioned machine presented as Android is
answered `hsc=434` — the profile must be set before `ADISetProvisioningPath`, and moving an
install means wiping `anisette/provisioning`, not just the preference. The field-level matrix
is reproducible with `scripts/probe_gsa_signin.py --real-init`, which needs no credentials.
- **`gsd.apple.com` chains to Apple Root CA - G3; nothing else in the world does.** The two
provisioning endpoints the URL bag points at are served under a root that is in no shipped
trust store — not Android's, not the anisette package's bundled one. It is in
`network_security_config.xml` for `gsa` and `gsd` only, as a PEM in `res/raw` so a diff can
review it. Remove it and provisioning fails the handshake; every public Anisette server
hitting `-45003`/502 in October 2026 was this.

### 5. Never bundle an AMap API key

AMap issues keys per developer account, bound to a package name and signing fingerprint, and
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.wander.android.opentagviewer.anisette;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;

Expand Down Expand Up @@ -101,9 +102,12 @@ public void everyProfileIsInternallyConsistent() throws Exception {
for (final Hardware hardware : Hardware.values()) {
final JSONObject json = new JSONObject(hardware.toJson());
final boolean phone = json.getString("model").startsWith("iPhone");
final boolean android = "Android".equals(json.getString("os_name"));

assertEquals(hardware + " names a phone in one field and a Mac in another",
phone, "iPhone OS".equals(json.getString("os_name")));
assertEquals(hardware + " names Android in one field and Apple hardware in another",
android, "Pixel 7".equals(json.getString("model")));
assertEquals(hardware + "'s user agent describes a different release",
hardware.userAgent(),
"akd/1.0 CFNetwork/" + hardware.cfnetwork()
Expand All @@ -114,8 +118,34 @@ public void everyProfileIsInternallyConsistent() throws Exception {
/** Two profiles that were the same machine would make the distinction meaningless. */
@Test
public void theProfilesAreActuallyDifferentMachines() {
assertNotEquals(Hardware.LEGACY_MAC.toJson(), Hardware.IPHONE.toJson());
assertNotEquals(Hardware.LEGACY_MAC.clientInfo(), Hardware.IPHONE.clientInfo());
final Hardware[] all = Hardware.values();
for (int i = 0; i < all.length; i++) {
for (int j = i + 1; j < all.length; j++) {
assertNotEquals(all[i] + " and " + all[j] + " are one machine",
all[i].toJson(), all[j].toJson());
assertNotEquals(all[i] + " and " + all[j] + " are one machine",
all[i].clientInfo(), all[j].clientInfo());
}
}
}

/**
* The Android profile is the exact string the Grand Slam handler was verified to accept.
*
* <p>Verified on 2026-10-14 with {@code scripts/probe_gsa_signin.py --real-init}: a machine
* id provisioned under this client info, presented with this client info, is answered
* {@code ec=0} and an SRP challenge; the same machine id under the Mac's is answered
* {@code ec=-80045}. It is also the string xtool shipped in xtool-org/xtool#337. One
* character of drift here is a sign-in nobody can complete, so the whole string is pinned.
*/
@Test
public void theAndroidProfileIsTheOneAppleAccepts() {
assertEquals("<Pixel 7> <Android;14;UQ1A.240205.002> <com.apple.AuthKit/1 "
+ "(com.apple.akd/1.0)>",
Hardware.ANDROID.clientInfo());
assertTrue(Hardware.ANDROID.canSignIn());
assertFalse(Hardware.LEGACY_MAC.canSignIn());
assertFalse(Hardware.IPHONE.canSignIn());
}

/**
Expand Down Expand Up @@ -143,20 +173,20 @@ public void everyProfileSuppliesTheSixFieldsFindMyExpects() throws Exception {
}

/**
* A fresh install claims the Mac.
* A fresh install claims the Android phone.
*
* <p>It claimed the iPhone until Apple began answering 401 to {@code get_2fa_methods} for
* clients presenting that profile - provisioning and password auth both succeeded, and the
* very next request did not. The desktop exporter makes the same call against the same
* account and is answered, and this profile is byte-identical to what it provisions with.
* <p>Since 2026-10-12 the Grand Slam handler refuses SRP init for a Mac or iPhone claim
* over the Android-provisioned machine id this app's ADI libraries mint
* ({@code ec=-80045}); the Android claim is answered. See
* {@code AdiDeviceIdentity#generate()} and issue #248.
*
* <p>So this asserts a decision taken from evidence, not a preference. If it ever changes
* back, that has to be because the 2FA question was answered - not because an iPhone icon
* looks better in a device list.
* <p>So this asserts a decision taken from evidence, not a preference. If it ever changes,
* that has to be because the handler's rule changed - re-run the probe first.
*/
@Test
public void afreshIdentityIsTheMacTheExporterAlsoUses() {
assertEquals(Hardware.LEGACY_MAC, AdiDeviceIdentity.generate().hardware());
public void afreshIdentityIsTheAndroidPhoneAppleStillAccepts() {
assertEquals(Hardware.ANDROID, AdiDeviceIdentity.generate().hardware());
assertEquals(Hardware.ANDROID, Hardware.DEFAULT);
}

/**
Expand Down
Loading
Loading