Skip to content

Implement play-services-constellation - #3359

Open
opstic wants to merge 44 commits into
microg:masterfrom
opstic:constellation
Open

opstic wants to merge 44 commits into
microg:masterfrom
opstic:constellation

Conversation

@opstic

@opstic opstic commented Mar 24, 2026 •

Copy link
Copy Markdown

This is PR 1 of 2 towards RCS support.
Related PR: #3360
Related issue: #2994

Collectively these changes enable Google Messages to verify the phone number via UPI and retrieve the provisioning document.

Testing from people with environments that pass DroidGuard/Play Integrity would be appreciated, especially with logs/network captures if possible. Many thanks!

Description

This PR implements the Constellation service, including:

  • verifyPhoneNumberV1
  • verifyPhoneNumberSingleUse
  • verifyPhoneNumber
  • getIidToken
  • getPnvCapabilities

getIidToken and verifyPhoneNumber appear to be the main APIs Google Messages would call.

Most of the verification paths are hopefully implemented correctly, only the TS.43 path was tested.
Flashcall is excluded for now as it's implementation in GMS requires a hidden API in Android SDK.

The gRPC proto definitions and client implementation are also used by play-services-asterism (PR #3360)

Screenshots

Screenshot_20260324-000518_Messages Screenshot_20260324-000550_Messages

@mar-v-in

Copy link
Copy Markdown
Member

It will take a while for this to be reviewed, but I'm happy to finally see someone actually tackling the issue instead of having an LLM generate bullshit. 👏

@ale5000-git

Copy link
Copy Markdown
Member

@opstic
There is an error in the build:
error: Field verifications in com.google.android.gms.constellation.VerifyPhoneNumberResponse has unsupported type @org.jetbrains.annotations.NotNull com.google.android.gms.constellation.PhoneNumberVerification in @org.jetbrains.annotations.NotNull com.google.android.gms.constellation.PhoneNumberVerification[].

@mar-v-in

mar-v-in commented Mar 24, 2026 •

Copy link
Copy Markdown
Member

@opstic We generally try to sort things into two modules:

  • A library module, which includes all code needed to connect to the service, that is aidl, parcelables, potential client code, constants/enums and is pure Java (no Kotlin).
  • An implementation module, typically called -core, that has the relevant services and actual logic implementations. Here we often use Kotlin.

The error @ale5000-git mentioned is probably due to having safe parcelable classes in Kotlin (which was never supported or tested), meaning they have automatically generated annotations that shouldn't be there. The Kotlin code also somewhat looks like it was automatically converted from Java (which is not always 100% safe, as seen here).

For this API specifically, because it is an internal API of Google, you can also just add it to the play-services-api module which is the catchall for all APIs that don't have a corresponding official Google client library (meaning they don't show up in this list). The service side should then go in the play-services-core module.

@opstic

opstic commented Mar 24, 2026 •

Copy link
Copy Markdown
Author

@mar-v-in
I've debugged and found that the field.listItemType on that specific field resolves to annotated names, which would fail the getTypeElement call, and thus fail isParcelable, isInterface and come out unsupported.
This doesn't happen on my Windows host where it has it's full name of com.google.android.gms.constellation.PhoneNumberVerification without the annotation so it gets resolved correctly

The @JvmField were workarounds to avoid safe-parcel-processor using reflection to access the fields, I'll be converting these into Java then.

Also I'll restructure a bit to fit the sorting better.

@mar-v-in

Copy link
Copy Markdown
Member

@opstic The issue is that it sees the type as @org.jetbrains.annotations.NotNull com.google.android.gms.constellation.PhoneNumberVerification due to the additional annotation on the type. The Java preprocessor for safe parcelables only supports field annotations, not type annotations, but the Kotlin to JVM compiler seems to add the @org.jetbrains.annotations.NotNull annotation to the type. Not sure why this is different on your system than the CI build. I'm not sure if there is an easy way to make Kotlin compiler not add those, but even easier would be to just continue with API classes being in pure Java, because then you can just not have the annotation on the type and also use the Android version of the NonNull annotation rather than the Jetbrains/Kotlin variant.

@opstic

opstic commented Mar 24, 2026 •

Copy link
Copy Markdown
Author

@mar-v-in
The code is quite large and shoving it into play-services-core doesn't look too well to me, can I just do something like play-services-droidguard where there's a core folder inside that handles the service logic?
oops didn't read what you said carefully enough, I'll sort it out with play-services-constellation and play-services-constellation-core then

@opstic

opstic commented Mar 26, 2026 •

Copy link
Copy Markdown
Author

Okay, after some more testing I've determined it's a Java version issue. The annotations aren't passed in with Java 21 so it builds successfully.

I'm still really hoping to keep the parcelables in kotlin as it would be easier for me and the ergonomics feel better but if it's necessary to be in Java and/or updating CI Java to 21 isn't acceptable I'm happy to move it over as well.

I'll push the -core restructuring shortly.

@ale5000-git

ale5000-git commented Mar 26, 2026 •

Copy link
Copy Markdown
Member

updating CI Java to 21

I hope this will never happen since it will break compatibility with old Android versions.

@ale5000-git

ale5000-git commented Mar 26, 2026 •

Copy link
Copy Markdown
Member

@opstic
When you try the code locally on your pc I suggest to try a complete gradlew build.

IMPORTANT: Do not use gradle, but gradlew; since using gradle won't use the intended gradle version and maybe it will hide possible errors.

NOTE: The full build also include the lint phase; with just assemble you won't see lint errors.

PS: Thanks for the good works on the PR :-)

@opstic

opstic commented Mar 27, 2026 •

Copy link
Copy Markdown
Author

Small problem, I am cleaning up by looking at the lint results and I am pretty sure Google Messages is the oldest thing that calls this service, which only goes back to as far as Nougat for meaningful versions.

Any standard method to exclude this module entirely for SDKs under Nougat? I am not a fan of putting @RequireApi(21/26) in front of every function in the module.

Also I've debugged for RCS in Nougat Google Messages, it would first compare if the mnc_mcc of the phenotypes match what's in the phone, then do standard self-contained OTP provisioning (instead of UPI) while only needing to call getIidToken.

But much of the code uses java.time.Instant including the actual protobuf structures, which is introduced in Oreo. So I'm wondering if we can ditch Nougat (and RCS support for Nougat) entirely and restrict to ≥ Oreo as that would result in much less annotations needed (If there's a way to split that cleanly).

@mar-v-in

Copy link
Copy Markdown
Member

I don't see us bumping the minimum API level to 26 any time soon. Google's Play Services has minimum API level of 21. We're currently at 19 and I plan to bump to 21 soonish to be able to make wider use of Compose.

Have you considered applying the annotation to the whole file (via @file:RequiresApi(26))? That's still going to be in a bunch of files, but at least not on every individual function.

@opstic

opstic commented Mar 27, 2026 •

Copy link
Copy Markdown
Author

Alright, that's much better than applying to each function at least.

Thanks for the quick response!

@haramainofficial01-design

Copy link
Copy Markdown

Hi @opstic I prepared a focused compatibility follow-up on top of your constellation branch.

It fixes the current Constellation lint/API compatibility blockers:

  • guards the API-26-only settings path on older Android versions
  • initializes verification preferences during creation
  • uses the AppCompat-compatible text appearance
  • explicitly exports the caller-validated Constellation service

Commit:
haramainofficial01-design@52460d0

The branch is one commit ahead and zero behind opstic/constellation. Lint and Kotlin compilation passed. Because pull requests into your fork are disabled, could you please review and cherry-pick this commit?

@camilo-12ch

Copy link
Copy Markdown

Hi @opstic,

As part of my ongoing investigation into #2994, I reproduced a specific failure in the Constellation IID path in the current #3359 implementation.

An exception from InstanceID.getToken() could be converted into an empty string by AuthManager. This allowed getIidToken to report SUCCESS with an invalid credential, and the read-only GPNV path could sign, construct, and execute a GetVerifiedPhoneNumbers request with an empty iid_token.

I prepared a focused commit for the #3359 architecture:

camilo-12ch@fb9a853

The change:

  • propagates InstanceID failures through the existing error handlers;
  • rejects an empty IID before reading the FID or signing;
  • prevents construction of IIDTokenAuth and execution of GPNV with an empty identity;
  • preserves the existing status handling and protocol behavior.

Validation:

  • assembleDebug PASS
  • assembleRelease PASS
  • assembleDebugAndroidTest PASS
  • instrumentation tests 7/7 PASS

PR #3388 contains semantically related IID error handling in a different Constellation architecture. This commit specifically adds the guards and regression coverage needed by the implementation in #3359.

If this fits your branch, please feel free to review or cherry-pick it. I’m continuing to investigate the remaining #2994 provisioning path and would be happy to follow up on any issues you spot.

@paulcakeface

Copy link
Copy Markdown

Hi @opstic,

I found one narrow Constellation state bug while testing your current branch.

If stored EC key material is missing or corrupt, AuthManager regenerates it. The old is_public_key_acked flag could remain true, so the next verification request could include ClientAuth for a replacement public key the server has never acknowledged.

Focused fix and regression test:
paulcakeface@325f147

Branch:
https://github.com/paulcakeface/GmsCore/tree/fix/constellation-key-ack

The branch is exactly two commits ahead of your constellation head b7d91f0a7a63fe135baba63bd9d092548b7b823c. The first is Camilo Cabezas's existing IID and test infrastructure followup, with his authorship preserved. The second is this key acknowledgement fix. If you have already applied Camilo's commit, 325f1477 can be cherry-picked on top of it.

Evidence:

  • On the baseline, a corrupt private key plus an acknowledged flag failed the regression test, while the valid key control passed.
  • After the patch, the focused tests pass 2 of 2.
  • The full Constellation instrumentation suite passes 9 of 9 on a Pixel 6 Pro running API 37.
  • All four Constellation and Asterism modules assemble in debug and release after merging PR Implement play-services-asterism #3360 onto current master.
  • git diff --check is clean.

I have not tested end to end RCS registration on a locked bootloader, and I am not claiming that validation.

If this fits your branch, please feel free to review or cherry-pick it.

nwinkelman2 pushed a commit to nwinkelman2/GmsCore that referenced this pull request Sep 2, 2026
InstanceID failures could be suppressed into an empty string. The empty credential was then exposed as a successful getIidToken response, signed, and included in a GetVerifiedPhoneNumbers request.

Propagate InstanceID failures to the existing error handlers and reject empty credentials before reading the FID, signing, building IIDTokenAuth, or executing GPNV. Regression tests cover exceptions, empty credentials, and the valid request path.

PR microg#3388 handles IID failures in a different Constellation implementation. This change applies equivalent error semantics plus explicit empty-token and GPNV guards to the microg#3359 architecture.
Chess-Debug added a commit to Chess-Debug/GmsCore that referenced this pull request Sep 16, 2026
Extract the existing Gaia-only consent mapping into a behavior-neutral helper,
wire the Asterism JVM test source set, and add the focused resolver matrix.

The baseline keeps matching-Gaia behavior, but fails the two cases where the
server returns a valid RCS-specific consent without a matching Gaia entry.

Provenance:
- Original Constellation/Asterism implementation: @opstic (microg#3359/microg#3360)
- Focused fallback tests/fix proposal: @keeltrace, naormeit#1 and #6
- Parallel follow-up: @Anusha0501, naormeit#4
- Foundragon delta: freeze only the minimal missing-field fallback; do not
  invent a new Gaia-vs-RCS precedence rule

Expected BEFORE failures:
- rcsConsentIsUsedWhenMatchingGaiaConsentIsAbsent
- unrelatedGaiaConsentDoesNotMaskRcsConsent

Physical RCS E2E remains NOT YET PHYSICALLY VERIFIED.
@BillTheHuman

BillTheHuman commented Sep 17, 2026 •

Copy link
Copy Markdown

Hi @opstic. I prepared a tested follow-up to your exact Constellation head b7d91f0a7a63fe135baba63bd9d092548b7b823c, preserving your implementation and history:

BillTheHuman@c9c05bf

Functional fix: Ts43Verifier constructs the initial EAP_ID using the supplied carrier realm, but performSimAkaAuth rebuilt the default-realm identity for key derivation. A nondefault realm therefore generated a response MAC with the wrong identity. The patch passes the exact advertised EAP_ID into the service. A differential regression reproduces the wrong response MAC on the original and passes on the patch.

Challenge validation: the original parser stopped after RAND/AUTN and did not verify the request AT_MAC. The patch parses the complete EAP-framed challenge, checks mandatory attributes and SIM-result lengths, and verifies HMAC-SHA1-128 before emitting a successful response. The synchronization-failure/AUTS path is retained. The PRF implementation is unchanged.

Actual verification: the included JVM harness compiles your actual service and PRF source with small synthetic Android dependency adapters. The same 16 scenarios yield 5 pass / 11 fail before, 16 pass / 0 fail after. Positive controls cover valid request and response MACs, custom realm, alternative attribute order, skippable attributes, link padding, and synchronization failure.

Reproduce with the compiler artifacts documented in the committed README:
python3 tools/rcs-aka-regression-tests/run.py --compiler-dir /path/to/compiler-jars

The initial evidence was JVM-level only. The full Debug and Release build/lint result is recorded in the hosted-build update below; device instrumentation and carrier/RCS send/receive remain unverified. It is a focused, AI-assisted contribution from @BillTheHuman, not a claim to the existing RCS implementation or the full bounty.

I attempted a PR against opstic/GmsCore:constellation, but the creation endpoint returned HTTP 404. The commit is available for review/cherry-pick; I am posting it here rather than opening a duplicate upstream RCS PR.

Hosted-build compatibility follow-up

The original submitted head c9c05bf completed full Debug assembly in hosted CI, then failed Constellation lint with 11 errors. I added a separate compatibility commit rather than suppressing the checks:

BillTheHuman@137b414

It narrows API-26 annotations to the timestamp-using state methods; initializes preferences through the public lifecycle method; explicitly declares the already Google-caller-checked service export; and corrects the layout's API/resource and accessibility findings. No blanket lint baseline is introduced. The workflow retains lint reports and lets Debug/Release finish independently.

The existing 16 service-level regression scenarios still pass locally. Full hosted validation for this revised head has completed successfully. Both Debug and Release assembly and lint passed, with build/lint reports retained as workflow artifacts:

https://github.com/BillTheHuman/GmsCore/actions/runs/35208388516

This update adds build integration work to the existing focused patch. It does not establish SIM/carrier interoperability or locked-bootloader RCS send/receive.

Verified September 17 at 2026-09-17T10:52:56.119615+00:00. This successful CI result closes the compile/lint blocker, not the remaining actual-device interoperability test.

ayush8620 added a commit to ayush8620/GmsCore that referenced this pull request Sep 21, 2026
)

Replace DummyService API_DISABLED stub with a real play-services-constellation
implementation: AIDL API surface, ConstellationApiService binder, gRPC/Wire
client, TS.43/MO/MT SMS verifiers, IID token signing, and settings-backed PNV
state.

Adapted from high-signal open work toward microg#2994 (notably @opstic microg#3359 and
hardening consolidated in microg#3808/microg#3784). Not a claim of end-to-end RCS on device.
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.

[BOUNTY] RCS Support [14999$]

8 participants