Skip to content

App: Can't log in #248

Open
Open

Description

@hheymans

App version

1.1.2

Phone and Android version

Samsung s25

Where does it go wrong?

Signing in to my Apple account

What happened

opentagviewer-log.txt

Which exporter made the zip?

No response

The log

Unable to log in to Apple

Before you post

  • If I attached a log, I have had a look at it and taken out anything personal the app missed.

Activity

  1. added
    bugSomething isn't working
    @appIssues regarding the OpenTagViewer Android app
    on Oct 12, 2026
  2. parawanderer commented on Oct 12, 2026

    @parawanderer
    Owner

    Apple refused the sign-in at its first step, before your password was sent, so the password is not the problem: Email verification failed: This action cannot be completed at this time. appears in all four attempts in your log, with the app's on-device Anisette working each time.

    Two things to check:

    1. Can you sign in at https://account.apple.com with the same Apple ID, and does Apple ask you to do anything there (confirm details, unlock, accept something)?
    2. Did you enter your email address or a phone number as the Apple ID?

    In 1.1.2 there is no way to switch to a remote Anisette server once the on-device one works, which is the other thing worth trying for this error. The next release adds a "Try a remote server" button for exactly this failure, and records Apple's error code in the log so a failure like this one can be told apart from others.

    Drafted by Claude Code

  3. parawanderer commented on Oct 13, 2026

    @parawanderer
    Owner

    This is not specific to this app. Apple began refusing third-party Grand Slam sign-ins on 2026-10-12 (ec=-80045, "This action cannot be completed at this time"), across every project on this authentication path: AltStore #1828, SideStore #1645, iloader #859, xtool #329, atvloadly #215, plus Sideloadly and PlumeImpactor reports in those threads. Your log shows exactly that signature: refused before your password was sent, on-device Anisette working each time. One caution from those threads: repeated sign-in attempts may put the account at risk, so let it rest rather than retrying.

    Where it is broken. A sign-in request passes two checkpoints:

    your device ──▶ Apple's edge ──▶ GsService2 handler ──▶ sign-in
                  (headers, TLS)    (body: SRP + Anisette
                                     one-time password)
    
    September's 503s lived HERE ──▲            └── the -80045 refusal lives HERE
    our probe covers  HERE ───────┘             (NOT yet covered by our probe)
    

    Our check_gsa_edge.py probe tests the edge daily and passes for every identity we send — yet real sign-ins are refused with HTTP 200, which means the request got past the edge and the handler refused it. So the rule Apple added is behind the front door, in how the handler evaluates the Anisette data and SRP exchange carried in the body.

    What has been ruled out (from controlled probes in the threads above): it is not the password or account — synthetic accounts get the identical error; it is not the client-info strings that caused September's 503s — string variants change nothing; and it is not local Anisette provisioning failing — xtool's provisioning succeeds, then GsService init is refused anyway.

    If you want to help unbreak it, thank you — nobody has published the experiment that localises this yet, and the tooling to run it is in this repo:

    How to investigate, and our current hypothesis

    The investigation below is Claude Code's (an AI model), working from the issue threads and this repository's wire documentation. It is reasoning from evidence, not a confirmed finding — treat it as a starting hypothesis, and treat every claim about what Apple "must" be doing as a guess worth testing.

    The handler's decision at SRP init happens before any password is checked, so it can only be evaluating two things: the structure of the SRP exchange itself, and the Anisette data carried alongside it — the one-time password (X-Apple-I-MD), machine ID (X-Apple-I-MD-M), and the identifiers binding them to a machine. Local ADI provisioning succeeds (xtool's finding), which says the ADI libraries will still mint a one-time password; it says nothing about whether the handler still accepts what it mints. The most likely shape of the change, in order:

    1. The handler now validates the Anisette machine data against something it didn't before — provisioning recency, machine-identifier consistency between the OTP and the body, or a new required field in the init plist. This fits every observation: provisioning succeeds, junk-body edge probes pass (they carry no anisette data at all), and every client regardless of headers is refused identically.
    2. The handler rejects an entire class of machine identities — e.g. identities whose provisioning came from an emulator-emulating path (the unicorn-based providers many of these tools use) rather than real ADI hardware. This would fit the "any account, any IP, any headers" shape too.
    3. A required new field in the init body that only genuine Apple clients send. Least likely, but the cheapest to test.

    What would settle it, and what nobody has published: a real SRP init body, sent without credentials (init runs before the password is checked, so this is still not a sign-in attempt and carries no account risk), with the Anisette contents varied one at a time — fresh vs. stale provisioning, serial present vs. absent, OTP from a different provider. Whichever variation changes the answer is where the rule lives.

    To get started: scripts/probe_gsa_signin.py probes the edge with arbitrary header variants (--set Name=Value, --unset Name, --client-info "...") and no account involved. Its BODY docstring documents exactly where the handler-level extension goes — a --real-init mode building the genuine init body from the pinned FindMy.py's own SRP machinery, reporting the response plist's ec code, and varying the Anisette contents. That mode does not exist yet; building it is the first contribution worth making, and the script is structured so it reuses everything already there.

    Once it exists: run the baseline, vary the Anisette contents one at a time, and compare ec codes. A variation that flips -80045 to something else is the answer.

    And wherever you find the cause, please take it beyond this issue. Every project linked above is locked out identically and none of them can see this thread — post it to those trackers, xtool #329 first since its maintainer is actively investigating there. The whole ecosystem gets unblocked by one finding only if it travels.

    For this app specifically: there is nothing to fix on our side until the rule is understood. The next release adds a remote-Anisette fallback and logs Apple's error code so failures like this are recognisable, but there is no evidence yet that a different Anisette source gets past this refusal, and we won't promise that it will.

    Drafted by Claude Code, reviewed by @parawanderer


    Update, 2026-10-13 08:56 UTC

    As of this time, these are the latest findings across the other trackers. No project has published a cause or a fix: no fix branch, pull request or release in Impactor, isideload/iloader, xtool, SideStore, AltStore, anisette-v3-server or atvloadly.

    Drafted by Claude Code

  4. marked App: #259 as a duplicate of this issue on Oct 13, 2026
  5. hoorayman commented on Oct 14, 2026

    @hoorayman

    Cross-posting per this thread's request (this is not the -80045 refusal itself, but the blocker right behind it — and it is specific to fetching Find My reports):

    The GSA -80045 refusal is now solvable — report an Android identity consistent with the Android CoreADI Anisette machine ID, and treat hsc=409 in the GSA complete response as the 2FA prompt (Apple no longer sends au). With those two, login + trusted-device 2FA + mobileme all succeed.

    But since 2026-10-12 the com.apple.mobileme service-data tokens now contains only mmeAuthToken — searchPartyToken is gone. The report fetch (gateway.icloud.com/acsnservice/fetch) then returns HTTP 401. The token set appears tied to the (platform-consistent) Anisette identity: an Android identity passes mobileme but yields only mmeAuthToken, while a Mac identity with our Android Anisette is refused (status 1). So searchPartyToken may now require a genuine macOS/iOS Anisette.

    Full evidence, raw logs and the 5 variants we tried: malmeloo/FindMy.py#278 — malmeloo/FindMy.py#278

    This affects every project that reads tokens.searchPartyToken (OpenTagViewer, macless-haystack, …). If anyone has obtained searchPartyToken after 2026-10-12, please share.

  6. rubo77 commented on Oct 14, 2026

    @rubo77
    Contributor

    Attribution, stated plainly: the person posting this did not write or review a single
    line of code. The investigation and the fix were done by Devin (Fable 5.1), an automated
    contributor, running against this repository. Everything below is what it observed, in its
    own experiments — nothing is quoted second-hand.

    Root cause, localised to one check

    The refusal is not "Apple blocks third-party clients" — it is a provenance consistency
    check
    in the GsService2 handler:

    • The ADI libraries every project provisions with are Apple's Android binaries (from
      the Apple Music APK). The machine id they mint (X-Apple-I-MD-M) carries that origin.
    • Since 2026-10-12 the handler compares it against the platform X-MMe-Client-Info
      claims. Android-minted machine id + macOS/iPhone client-info → ec=-80045, before
      any credential is touched. A wrong OTP still gets -80045; only a missing OTP gets
      -80010, so the provenance verdict precedes authentication entirely.
    • Same machine id + Android client-info → ec=0 with the full SRP challenge
      (i, s, sp, c, B).
    • Provenance is bound at provisioning: a machine id provisioned under a Mac claim and
      then presented with an Android one is answered hsc=434 (re-sync demand) — you must
      re-provision, not just change the string.

    Second, independent finding: gsd.apple.com now chains to Apple Root CA – G3, which is
    in no shipped trust store. That is the -45003/502 every remote Anisette server hit — not
    a policy refusal, a TLS handshake failure on Apple's own provisioning endpoints.

    How it was found

    Extended probe_gsa_signin.py with --real-init: a structurally real SRP init
    (srp.User.start_authentication() A2k, ps: [s2k, s2k_fo], synthetic nonexistent Apple
    ID, real provisioned cpd) — the body the handler reads, while still never being a
    sign-in attempt. Then varied one cpd field at a time:

    variant answer
    no X-Apple-I-MD-M ec=-80009 "MID is invalid"
    junk X-Apple-I-MD-M ec=-29004 "environment mismatch"
    valid Android-minted MD-M + macOS client-info ec=-80045
    same + junk serial / junk device-id / junk X-Apple-I-MD still -80045
    same + Android client-info ec=0
    Mac-provisioned MD-M + Android client-info hsc=434
    second, independently provisioned machine id same -80045 → a class check, not an identity ban

    xtool found the client-info side independently (xtool-org/xtool#337, shipped in 1.22.2).
    What this adds: the field-level proof that X-Apple-I-MD-M provenance is the trigger, the
    hsc=434 re-provisioning requirement, and the G3 root cause of the remote-server errors.

    The fix, verified end to end

    On rubo77/OpenTagViewer@feature/gsa-real-init (two commits, MR-ready):

    • Hardware.ANDROID profile (Pixel 7 / Android;14;UQ1A.240205.002 / com.apple.akd/1.0),
      stored Mac/iPhone identities are replaced and anisette/provisioning is wiped before
      ADISetProvisioningPath so ADI re-provisions under the Android claim. Costs affected
      installs one re-login.
    • Apple Root CA – G3 added as a trust anchor for gsa/gsd only.

    Verified, honestly stated:

    • ✅ --real-init against live Apple: ec=0 with a freshly Android-provisioned machine id
    • ✅ On-device, Pixel 8 Pro: libraries download, ADI provisioning completes
      (ADIProvisioningEnd = 0), sign-in accepts password and 2FA code
    • ⚠️ After successful auth, that test account hit the known com.apple.mobileme delegate
      refusal (status=1, "account never used with an Apple device") — an account-level gate,
      unrelated to this fix
    • ✅ 469 JVM + 46 pytest + 35 instrumented tests green
    • ❓ Untested: an Apple ID that has previously been activated on real Apple hardware
      should pass the mobileme gate and reach actual fetching — that final step awaits a
      qualifying account
  7. hoorayman commented on Oct 14, 2026

    @hoorayman

    Thanks — this localises -80045 precisely, and matches what we found independently (Android-consistent identity → ec=0).

    One complementary data point that may refine the mobileme conclusion, because your own last two bullets are the crux:

    Your status=1 was the account gate (account never activated on real Apple hardware). We have the other half: our test Apple ID is activated (trusted-device 2FA prompts arrive, idms_pet is issued), and with the Android identity mobileme returns status=0 — so it clears the gate — but the com.apple.mobileme service-data.tokens contains only mmeAuthToken, never searchPartyToken:

    [gsa] o=complete Status={'hsc': 409, ..., 'au': 'trustedDeviceSecondaryAuth'}   # fixed as in your Android profile
    [gsa] o=complete Status={'hsc': 200, ...}                                       # 2FA code accepted
    [mobileme] idms_pet_len=523
    [mobileme] state=LOGGED_IN service_keys=['protocolVersion','tokens'] tokens=['mmeAuthToken']
    

    And the report fetch rejects that token: https://gateway.icloud.com/acsnservice/fetch with Basic <dsid>:<mmeAuthToken> → HTTP 401 (identical to a bogus token).

    We also compared identities in one session:

    mobileme X-MMe-Client-Info anisette result
    Android Android status=0, tokens = mmeAuthToken only
    Mac Android status=1

    So passing the mobileme gate does not yield searchPartyToken — the issued token set appears tied to the (platform-consistent) identity, and an Android identity never gets searchPartyToken. This suggests your final ❓ ("an activated account should reach actual fetching") may still stop at the fetch: the Android re-provisioning fix gets ec=0 and clears mobileme, but the Find My report fetch needs a token we haven't seen issued to Android identities.

    If you or Devin can run the activated account through the fetch step, the one thing that settles it is: does searchPartyToken appear in mobileme tokens (or does the fetch return 200)? That is exactly the question in malmeloo/FindMy.py#278 (malmeloo/FindMy.py#278), also posted to macless-haystack#248. Great work — this is the first field-level proof of the provenance check.

  8. parawanderer commented on Oct 14, 2026

    @parawanderer
    Owner

    Status as of 2026-10-14, with thanks to @rubo77 and @hoorayman for the field-level work above.

    Merged: #262 adds Apple Root CA - G3 as a trust anchor for gsa.apple.com and gsd.apple.com. gsd.apple.com, where ADI provisioning goes, now chains to that root, which Android does not ship. This is the trust-anchor part of @rubo77's branch only, credited to them. It does not fix -80045 by itself.

    What we understand so far:

    What this means for you:

    • If you are signed in and seeing locations, stay signed in. Do not sign out, clear the app's data or reinstall, because signing in again currently fails.
    • If you cannot sign in, there is no fix to install yet. Avoid retrying repeatedly; repeated attempts may put the account at risk.

    Next steps:

    Drafted by Claude Code, reviewed by @parawanderer

  9. rubo77 commented on Oct 14, 2026

    @rubo77
    Contributor

    Same attribution as before: the person posting wrote or reviewed no code; Devin (Fable 5.1) does the work.

    One correction worth making to the record. Our status=1 account was activated on real Apple hardware — it is signed into an iPhone XR with iCloud and Find My enabled, in daily use. So status=1 is not strictly "account never activated"; whatever gate it encodes is narrower than that (device registration, account score, something about the delegate request itself — unknown). Reporting that because it changes what a reproducing account needs to look like, and because @hoorayman's activated-account result (status=0, mmeAuthToken only) shows the gate and the token set are two separate questions: their activated account passed mobileme, ours — also activated, Find My and all — did not. Worth understanding what distinguishes them before the token question is even reached.

    Experiment instrumented and queued. rubo77/OpenTagViewer@feature/gsa-real-init now logs the names of the service tokens Apple issues, on every completed sign-in — Sign-in issued N service token(s): [...]. Values are never logged; names only (the diff is the last commit on that branch if anyone wants to check). An activated account goes through the full flow on a Pixel 8 Pro, on this build. We'll report back whether it clears mobileme at all, and if it does, whether searchPartyToken appears in tokens — and whether acsnservice/fetch accepts it or still answers 401.

  10. sacca97 commented on Oct 14, 2026

    @sacca97

    @parawanderer @rubo77 This might be a possible way to bypass error 80045: https://github.com/SideStore/SideSign/pull/5

  11. parawanderer commented on Oct 14, 2026

    @parawanderer
    Owner

    @sacca97 Check this summary from claude code.

    TL;DR: The problem seems to be that even if you get in to the account by saying you are an Android phone (the SideStore fix), you are not always given the grants needed to fetch FindMy location reports. So the SideStore fix is part of the fix here, but here it requires more work to get it to return FindMy reports again consistently (as well as the whole iCloud account recovery route to work in general, which is untested right now I think).

    To be clear I have not tested the work of the two users above myself yet as I am busy with some other projects, so I am assuming they are on it. Anybody else experimenting and testing this is of course helpful. Current status would be:

    1. We can login as an Android phone by sending different headers than the app does now (The SideStore fix). This gives us a logged in account, but no FindMy network location fetch permissions.
    2. Fetching location reports seems to depend on the grant above. If you don't get that one you can't actually fetch reports using base FindMy.py (not the OpenTagViewer FindMy.py fork)
    3. This app built a custom iCloud "account recovery" flow that also depended on the login. This is what allows you to log in to your apple account in the app and never have to export the tags again - it syncs with your iCloud account in the app. This is untested since this breakage/Android login fix I think. It's a prerequisite for getting the list of tags and their keys to be able to fetch them using that list for (2)
  12. rubo77 commented on Oct 14, 2026

    @rubo77
    Contributor

    Same attribution as before: the person posting wrote or reviewed no
    code; Devin (SWE-2) does the work and runs the experiments.

    Two accounts, four runs, one answer so far.

    Account activated accountsd claim android.music claim
    dev account never status=1 status=1
    second account iPhone XR, iCloud + Find My, daily use status=1 status=1

    The experiment behind the right-hand column: the delegate request's
    X-MMe-Client-Info and User-Agent were flipped from
    com.apple.AOSKit/282 (com.apple.accountsd/113) / iCloudHelper -
    two macOS daemons on an Android claim, the same class of inconsistency
    GSA answers with -80045 - to com.apple.AuthKit/1 (com.apple.android.music/4.9.6), the one client Apple itself ships on
    Android. Same phone, same machine id, minutes apart. status=1,
    unchanged, on both accounts.

    So whatever status=1 gates on at com.apple.mobileme, it is not the
    client-info claim - the check GSA runs at SRP init is not repeated
    there.

    Which leaves the variable @hoorayman's status=0 differs in: the
    machine id's provenance. Ours is minted by the Android ADI libraries
    on-device. Was the anisette behind your mobileme status=0 generated
    by a server, or by local Android ADI? If yours came from a
    non-Android-provisioned identity, the gate would be sitting on the
    machine id itself - which our next experiment tests directly by
    forcing a server identity for the same accounts.

  13. sacca97 commented on Oct 14, 2026

    @sacca97

    Windows get an extra token (malmeloo/FindMy.py#278 (comment)) but not the one we need.

    Also web findmy (https://www.icloud.com/find) does not work either for airtags.

  14. parawanderer commented on Oct 14, 2026

    @parawanderer
    Owner

    @sacca97 Web FindMy never had AirTags (or FindMy "gadgets") FYI, hence the reason I came up with the app. Good to know about windows

  15. rubo77 commented on Oct 14, 2026

    @rubo77
    Contributor

    Same attribution as before: the person posting wrote or reviewed no
    code; Devin (Fable 5.1) does the work and runs the experiments.

    A mechanical datum for status=1 that changes what it might mean.

    The mobileme delegate request authenticates as apple-id : <the com.apple.gs.idms.pet token> - as HTTP Basic auth and in the body's
    password field. We instrumented the exchange in a test build, which
    logs field lengths and key names only, never values. Both runs below
    are apple@r.z11.de (activated: iPhone XR, iCloud + Find My, daily
    use) signed in through a server anisette identity:

    [GSA-PROBE] hsc=200 au=None ec=0
    [MOBILEME-PROBE] enter idms_pet_len=0 adsid_len=46 idms_hb_len=0
    [MOBILEME-PROBE] status=1
    [MOBILEME-PROBE] status-message='Authentication failed: apple@r.z11.de'
    

    Read together: Grand Slam completed (ec=0, au=None, no 2FA
    demanded) and returned a session - but neither com.apple.gs.idms.pet
    nor com.apple.gs.idms.hb was populated in it. The fork reads the pet
    with .get("token", ""), so the delegate request went out
    authenticating with an empty password, and the status-message is the
    literal truth: authentication failed. For these runs status=1 needs
    no account gate at all - the credential it would authenticate with was
    never issued.

    What this does NOT establish:

    • Whether our earlier runs under the local Android-ADI identity
      lacked the pet the same way (also status=1, but the probe did not
      exist then - plausibly the same mechanism, unverified).
    • Whether t carried the tokens under different names or was empty
      entirely - the token-name probe landed in this build but the next
      sign-in was refused upstream first (below).
    • Most interestingly: whether the pet is issued only on the
      challenge path. @hoorayman's successful run went hsc=409 / trustedDeviceSecondaryAuth -> code -> hsc=200 and logged
      idms_pet_len=523. Ours went hsc=200 au=None straight through and
      got idms_pet_len=0. If the pet is only minted when the login
      completed trusted-device auth, then an au=None login starves the
      delegate of its credential and status=1 is a consequence of the
      path, not the account.

    @hoorayman: on your status=0 run, was the pet present precisely
    because your login went through trusted-device auth? If so our data
    matches yours exactly rather than contradicting it.

    Two side observations, both probably benign:

    • Apple pushed trusted-device verification codes to the iPhone on runs
      where the flow itself never requested one (au=None) - an
      informational push, not a prompt our UI failed to show.
    • A later sign-in attempt was refused at Grand Slam with HTTP 503 at
      SRP init, before the password - plausibly the rate limiting
      @parawanderer warned about. We have stopped retrying the account.
  16. rubo77 commented on Oct 14, 2026

    @rubo77
    Contributor

    Same attribution as before (Devin/SWE-2 does the work and runs the experiments).

    Following on the idms_pet_len=0 finding, the next test on our side:

    Force the challenge path. Re-provision the local ADI identity (a fresh, untrusted machine id) so the same account goes through hsc=409 / trustedDeviceSecondaryAuth -> code -> hsc=200, then read idms_pet at the delegate step.

    • If the pet appears (~523, like @hoorayman's), status=1 is a path consequence: an au=None login for this identity class is issued no pet, and the delegate cannot authenticate anything. The interesting part would then be why Apple treats the login as not requiring a challenge yet refuses to bind the credential - and whether real clients get an au demanding the challenge where ours got None.
    • If it stays empty, the refusal is bound to the machine identity itself, and no client-side claim fixes it.

    The instrumented build also sniffs the decrypted spd now, so the same run shows which token names t actually carries - answering the "issued under a different name" question in the same shot.

    (Waiting out the apparent 503 rate-limit first - no more hammering.)

  17. hoorayman commented on Oct 15, 2026

    @hoorayman

    @rubo77 — confirming directly, and it lines up exactly with your mechanism.

    Yes. Our status=0 run is the challenge path, end to end:

    [gsa] complete  hsc=409, au=trustedDeviceSecondaryAuth   # we inject au from hsc=409
    [gsa] complete  hsc=200                                   # trusted-device code accepted
    [mobileme] idms_pet_len=523                               # pet present
    [mobileme] status=0, tokens=['mmeAuthToken']
    

    And our earlier runs — before we handled hsc=409, so findmy skipped 2FA and went straight to the delegate on an au=None login — had idms_pet_len=0 and mobileme status=1. That is the same mechanism you describe: an empty pet → the delegate request authenticates with an empty password.

    So your two runs and ours agree: for this (Android-ADI) identity class the pet is minted on the trusted-device challenge path, not on au=None — status=1 on the au=None path is a consequence of the path, not the account. (It also matches the idms_hb_len=0 you saw; same starvation.)

    The caveat that keeps this issue open: even with the pet present (status=0), the com.apple.mobileme service-data.tokens still contained only mmeAuthToken — no searchPartyToken — and the fetch still answered 401.

    And a data point for the platform-binding question, from @sacca97 below (FindMy.py#278): a Windows identity (provisioning + 2FA + loginDelegates all accepted) got mailToken + mmeAuthToken — a different set than Android's single mmeAuthToken, consistent with "the set follows the claimed platform" — but still no searchPartyToken, and every token 401s on both findmyservice/v2/fetch and acsnservice/fetch. Windows is therefore not a shortcut; this is narrowing to macOS/iOS only.

  18. hoorayman commented on Oct 15, 2026

    @hoorayman

    Solved — with a real Mac. Answering the open question from this thread and malmeloo/FindMy.py#278: a native macOS AOSKit anisette identity is issued the full token set including searchPartyToken, and the report fetch then returns real reports.

    Result (reproduced end to end on a physical Mac, macOS 10.15.8 / x86_64)

    [gsa] o=init      Status={'hsc':200,'ec':0,...}          # no -80045
    [gsa] o=complete  Status={'hsc':409,...}                 # trusted-device 2FA demanded
    [gsa] o=complete  Status={'hsc':200,...}                 # after the code
    mobileme tokens issued:
      ['mmeFMFAppToken','keyTransparencyToken','mapsToken','searchPartyToken',
       'mmeFMIPToken','cloudKitToken','mmeFMIPSiriToken','mmeFMIPAppToken',
       'mmeAuthToken','mmeBTMMInfiniteToken']
    *** searchPartyToken PRESENT ***
    

    POST https://gateway.icloud.com/acsnservice/fetch (Basic dsid:searchPartyToken + native anisette) → HTTP 200 with payloads; decrypted → real coordinates, e.g. 2026-10-15 07:52:07 lat=37.863506 lon=112.506846.

    How the native identity is obtained

    The anisette python lib is always Android ADI (arch=arm64-v8a), even on macOS — that's why macOS alone changes nothing. You must use the Mac's own AOSKit.framework:

    import objc
    from Foundation import NSBundle
    NSBundle.bundleWithPath_("/System/Library/PrivateFrameworks/AOSKit.framework").load()
    AU = objc.lookUpClass("AOSUtilities")
    h = AU.retrieveOTPHeadersForDSID_("-2")   # -> {'X-Apple-MD': ..., 'X-Apple-MD-M': ...}

    Note the keys are X-Apple-MD / X-Apple-MD-M (no X-Apple-I- prefix) — map them to X-Apple-I-MD / X-Apple-I-MD-M. Use the Mac client-info (<MacBookPro18,3> <Mac OS X;13.4.1;22F8> <com.apple.AOSKit/282 (com.apple.accountsd/113)>).

    The token is NOT portable — the Mac must stay in the loop

    fetch request result
    native Mac anisette + Mac searchPartyToken HTTP 200
    Android anisette + Mac searchPartyToken HTTP 401
    no anisette + Mac searchPartyToken HTTP 401

    So a searchPartyToken obtained with a Mac identity only works together with that Mac's native anisette — you cannot harvest the token once and fetch from a Linux/Windows box. Consequence: the Find My login + fetch run on the Mac (it can serve many accounts; the anisette is machine-level). A hosted Mac or a macOS VM is the production shape.

    Practical gotchas

    • TLS: gsa.apple.com chains to the old Apple Root CA (O=Apple Inc./OU=Apple Certification Authority), which is not in certifi → python.org Python fails with self-signed certificate in certificate chain. Add that root (and Apple Root CA - G3 for gsd.apple.com, as @rubo77 found) to a bundle and point SSL_CERT_FILE at it. (curl works because it uses the macOS keychain; Python does not.)
    • macOS 10.15 wheels: pin cryptography<47 (newer releases dropped the 10.9 universal2 wheel).
    • 2FA flow is as in the thread: hsc=409 → trusted-device code; the phone 2FA page (/auth) is 403 for these clients.

    Net: a native macOS/iOS identity is the requirement. Android (and, per earlier reports, Windows) will not receive searchPartyToken. Patch scripts and the full log are in the linked repo; happy to share.

    (Posted by a human, with an AI assistant doing the engineering.)

  19. parawanderer commented on Oct 15, 2026

    @parawanderer
    Owner

    Thank you to both of you @rubo77 and @hoorayman for looking into this and @hoorayman for figuring this out! I think this would be a design change on the FindMy.py end as this would now require a Mac to be running somewhere and producing these anisette reports. Since most users wouldn't have a mac they can leave running/on permanently, an initial fix might look like seeing if some kind of QEMU VM Mac could be made to run and produce these reports instead? Though this obviously instantly makes the service much heavier than the ones everybody has been using in the anisette ecosystem before.

    The question would also be whether we could try to understand if we can reproduce the Mac anisette behaviour in the library somehow to skip needing a Mac entirely (this would be the ideal for the entire FindMy ecosystem), or if there is some limitation by which this is now impossible.

    So I guess this leaves two things:

    1. The immediate fix might be to create a Mac-based anisette server wrapper (runs on any Mac) followed by some sort of QEMU Mac container that runs this. Running a MacOS in a container is already an existing tool (I know of sickcodes/Docker-OSX which I have used and it works) but it currently requires UI interaction to get it running, and it has a slow startup time.
    2. The other question would be to investigate if the Mac's anisette protocol can be reproduced without the Mac (or plausibly an iPhone/iPad's anisette protocol reproduced without an iPhone/iPad, I suspect it'll be equivalent). Note that I don't know of any projects that have accomplished this before, as neighbouring projects like OpenBubbles/rustpush are based on running on a Mac, presumably because of the same limitation which they probably hit earlier for the iMessage token aspect (I have not checked for more projects myself).

    WDYT?

  20. poterpan commented on Oct 15, 2026

    @poterpan

    Independent confirmation of @hoorayman's result (posted while I was writing this), on a newer setup, plus two things not covered above: a macOS 27.0.1 Mac we tried fails the same call, and the fetch does not have to run on the Mac. Only the anisette has to come from it. That is the "Mac-based anisette server wrapper" shape suggested above, and we are running it now.

    Setup

    • An Apple Silicon MacBook Air on macOS 26.4, SIP enabled, signed into iCloud with the same Apple ID.
    • An ordinary ad-hoc-signed binary calls +[AOSUtilities retrieveOTPHeadersForDSID:@"-2"] (AOSKit) and gets X-Apple-MD / X-Apple-MD-M with no special entitlements. The same call on the one macOS 27.0.1 Mac we tried fails with AOSKit WARN: Info request failed: -45070 and returns no headers, even run from a plain Terminal. One machine only (different model and account), so this may be 27 tightening the entitlement check or something about that machine. Worth checking before upgrading a Mac that serves anisette.
    • We serve that anisette over HTTP (anisette-v1 JSON). The machine's own X-Mme-Device-Id, X-Apple-I-SRL-NO and X-Apple-I-MD-LU come from AKDevice. Then we run the usual GSA → loginDelegates flow, with every X-MMe-Client-Info set to that Mac's real serverFriendlyDescription (<MacBookAirX,Y> <macOS;26.4;…>).

    Results

    • GSA ec=0. On the account where the Mac is a trusted device, no second factor was asked. A second account went through hsc=409 → SMS → hsc=200.
    • loginDelegates returns status=0 with the full token set: searchPartyToken, mmeFMIPToken, mmeFMFAppToken, cloudKitToken, … (both accounts).
    • acsnservice/fetch with that token plus the same Mac's anisette → 200 with fresh reports.
    • The same token plus Android (anisette-v3-server) anisette → 401.

    What this suggests

    • The token set depends on the platform the anisette was minted on. This agrees with the Android-only-mmeAuthToken and Windows-mailToken results above.
    • The token is usable only with anisette from the machine that obtained it. Signing in once on a Mac and then fetching from Linux with Android anisette does not work.
    • A workable middle ground: one Mac (≤ macOS 26) serves anisette to headless fetchers elsewhere, for example over an SSH tunnel. That is what we run now. It is not macless, but the Mac only mints headers.

    Not tested: whether a token from Mac A works with anisette from a different Mac B (machine-bound vs. merely "must be macOS").

  21. sacca97 commented on Oct 16, 2026

    @sacca97

    @parawanderer sorted, please test independently: https://github.com/sacca97/os-findmy-e

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    @appIssues regarding the OpenTagViewer Android appbugSomething isn't workinghelp wantedExtra attention is needed

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions