Skip to content

feat(droidguard): Support Play Integrity over remote DroidGuard (#2851) - #3941

Open
MilindChatrabhuji wants to merge 25 commits into
microg:masterfrom
MilindChatrabhuji:feat/play-integrity-remote-droidguard-2851
Open

MilindChatrabhuji wants to merge 25 commits into
microg:masterfrom
MilindChatrabhuji:feat/play-integrity-remote-droidguard-2851

Conversation

@MilindChatrabhuji

@MilindChatrabhuji MilindChatrabhuji commented Oct 28, 2026 •

Copy link
Copy Markdown

Summary

Google Play Integrity API evaluates multi-step DroidGuard sessions (\�egin\ -> \snapshot\ -> \close). This PR adds full support for offloading multi-step Play Integrity DroidGuard evaluations to a remote helper device, accompanied by an authenticated HTTP bridge server and a complete deployment guide.

Key Changes

  • Client Transport: Implemented \RemoteDroidGuardSession, \RemoteDroidGuardHttpClient, and \RemoteDroidGuardHandleClient\ supporting bounded HTTP requests and query parameter preservation.
  • Race Condition Prevention: Added \RemoteDroidGuardInitialization\ synchronization barrier ensuring snapshots wait for init completion.
  • Native Helper Support: Added \DroidGuardSessionStore\ and \DroidGuardSessionProvider\ content provider enabling local hardware-backed evaluation via ADB.
  • HTTP Bridge Server: Added lightweight Python 3 adapter (\play-services-droidguard/server/droidguard_server.py) for hosting the remote DroidGuard service.
  • Documentation: Added \play-services-droidguard/REMOTE_DROIDGUARD_SETUP.md\ with complete setup instructions.
  • Testing: 27 unit tests covering concurrency, handle interop, HTTP client safety, initialization barriers, and session stores, plus Python protocol integration tests.

Claiming the bounty on issue #2851.

woahwhattheheck and others added 25 commits October 1, 2026 15:15
## Summary

- Implement `guardWithRequest` on the bounded executor and close its handle on every completion path.
- Reuse remote server state through the existing `init` / repeated `snapshot` / `close` lifecycle.
- Centralize HTTP request encoding, timeouts, non-success status handling, and connection cleanup.

## Verification

- `./gradlew.bat :play-services-droidguard-core:testDebugUnitTest --tests org.microg.gms.droidguard.core.RemoteDroidGuardHttpClientTest --tests org.microg.gms.droidguard.core.RemoteDroidGuardSessionTest --offline`
- BUILD SUCCESSFUL; 4 focused tests pass, and the task compiles the module and AIDL.
- `git diff --check` passes.

This verifies local transport/session behavior. A real server/device Play Integrity flow remains unverified. Related issue: microg#2851.
… ci]

Keep each initialization pending until session setup finishes, including when a synchronous snapshot overtakes one-way init dispatch. Bind snapshot metadata to the observed generation and close late sessions after handle shutdown.

Validation: five maintained JVM tests passed. A loopback HTTP replay of the actual RemoteHandleImpl source with disposable platform stubs improved from 1/4 to 4/4 scenarios: pending begin, snapshot before init dispatch, close during begin, and completed reinitialization. No full Gradle, Binder, device, Play Integrity, or Dott scooter acceptance claim.
Retain the successful three-class, twelve-case Gradle run and VtmDefaultDebug APK receipt for product 7a648d5 plus the original lifecycle test patch. Keep the tested source separate from later PR revisions; no production file is changed.
Move result delivery inside the existing task's cleanup finally boundary. A slow remote close no longer delays the completed callback; fallback, rejection, logging and cleanup on callback failure are preserved. Record the focused production-class loopback replay and controlled callback-latency results, preserving the concurrent integrated Android report.
Add a DroidGuardClient that returns Task<DroidGuardHandle> and then uses
snapshot, isOpened, and close against a begin/snapshot/close session backend.
Loopback interop covers retained-handle reuse and rejects a single-attest
server that has no session. This does not mint Play Integrity or device results.

Co-authored-by: woahwhattheheck <woahwhattheheck@users.noreply.github.com>
Copy the public String map into a session map so handle.snapshot compiles
and still reuses the retained remote session.

Co-authored-by: woahwhattheheck <woahwhattheheck@users.noreply.github.com>
Keep begin, snapshot, and close ordered within one retained session while
allowing other sessions to progress independently. Prevent close from
retiring remote state during an in-flight snapshot.

Direct production-class loopback execution reproduces both overlaps on the
parent and removes both with this change. The maintained session class
passes four focused JUnit checks. JVM evidence and Android/device limits
are recorded in SESSION_SERIALIZATION.md. Preserve the latest retained
handle client and snapshot map repair.
Return failed Tasks when the supplied executor rejects initial admission. Keep begin, snapshot, result publication and close in one accepted getResults worker, so a second dispatch cannot strand the result Task or the opened remote session. Use trySetException after task publication to avoid duplicate completion when a listener throws. Preserve result-before-close ordering and existing per-session serialization.

Extend RemoteDroidGuardHandleInteropTest with rejected-admission and accept-once executor cases. The three existing interop cases remain unchanged. With the actual microG TaskImpl, full current client/session/HTTP classes and the existing loopback server, parent 04fbd91 passes 3/5 and fails both new cases (RejectedExecutionException and DuplicateTaskCompletionException); this change passes 5/5. The accepted-worker case observes result completion before close and exactly one executor submission.

Focused JVM validation used retained OpenJDK 17, Kotlin 2.2.21 and JUnit 4.13.2. Executed source blob 2836391; test blob dc2b839. Android packaging and device acceptance remain separate.
Add a default-disabled provider and authenticated host adapter for the existing begin/snapshot/close protocol. Retain actual core handles, check readiness, preserve initialization metadata, and return native result bytes through an explicit response envelope. Runtime caller checks restrict the bridge to authorized local operators; the ordinary service broker is unchanged.

Bound active and closing sessions, work queues, operation waits, and idle lifetime. Reject overlapping work for one session before occupying a global worker, so another session can progress. Close failed native setup and late initialization results.

Compile the complete changed Kotlin production classes for JVM 1.8 and execute six focused scenarios with retained Robolectric API29 and a recording VM boundary: 6 passed. Exercise the production Python HTTP/subprocess adapter with ten requests and seven content-command fixtures. The setup guide records source blobs, outcomes, donor credit, and execution limits. No APK, installed-provider, Google VM, Play Integrity, or Dott ride result is asserted.
The native runtime can return encoded error bytes through a successful snapshot response. Clarify that an explicit provider error status becomes an HTTP error; Base64 validity alone does not establish attestation success.
Generate a test-only public API JAR from the active JDK module and resolve jdk.httpserver in unit-test JVMs. The ordinary module task passes all 17 maintained cases on validation tree 4866ca6 in run37234495146, without an init script, filters, or APK assembly. No production dependencies or API behavior change.
Preflight request-target bytes, form-body bytes, and adapter field counts before opening a connection. Preserve the existing response cap and cover accepted and rejected protocol boundaries.
…og#2851)

- Support multi-step challenge-response DroidGuard sessions over remote HTTP/ADB bridge for Google Play Integrity API.
- Implement RemoteDroidGuardSession, RemoteDroidGuardHttpClient, RemoteDroidGuardHandleClient, and RemoteDroidGuardInitialization synchronization barrier.
- Implement server-side DroidGuardSessionStore and DroidGuardSessionProvider with concurrency gating, active operation tracking, and idle reaping.
- Provide authenticated Python HTTP adapter (droidguard_server.py) and comprehensive self-hosting setup guide (REMOTE_DROIDGUARD_SETUP.md).
- Fix Kotlin reserved keyword escaping in DroidGuardSessionProvider.
- Include comprehensive unit test suite (27 unit tests) and Python server protocol integration tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants