From 5991207cfe744103d6e7c14b40c62ca8184e615a Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Tue, 24 Mar 2026 01:26:55 -0400 Subject: [PATCH 01/31] Implement `play-services-constellation` --- play-services-constellation/build.gradle | 73 ++ .../src/main/AndroidManifest.xml | 17 + .../gms/constellation/GetIidTokenRequest.aidl | 3 + .../constellation/GetIidTokenResponse.aidl | 3 + .../GetPnvCapabilitiesRequest.aidl | 3 + .../GetPnvCapabilitiesResponse.aidl | 3 + .../gms/constellation/PhoneNumberInfo.aidl | 3 + .../VerifyPhoneNumberRequest.aidl | 3 + .../VerifyPhoneNumberResponse.aidl | 3 + .../internal/IConstellationApiService.aidl | 39 + .../internal/IConstellationCallbacks.aidl | 15 + .../android/gms/constellation/GetIidToken.kt | 43 + .../gms/constellation/GetPnvCapabilities.kt | 87 ++ .../gms/constellation/PhoneNumberInfo.kt | 28 + .../gms/constellation/VerifyPhoneNumber.kt | 95 ++ .../microg/gms/constellation/AuthManager.kt | 102 ++ .../constellation/ConstellationApiService.kt | 124 ++ .../constellation/ConstellationStateStore.kt | 180 +++ .../org/microg/gms/constellation/GServices.kt | 33 + .../microg/gms/constellation/GetIidToken.kt | 35 + .../gms/constellation/GetPnvCapabilities.kt | 120 ++ .../GetPnvCapabilitiesApiPhenotype.kt | 5 + .../constellation/GetVerifiedPhoneNumbers.kt | 137 +++ .../gms/constellation/IidTokenPhenotypes.kt | 9 + .../org/microg/gms/constellation/RpcClient.kt | 47 + .../gms/constellation/VerificationMappings.kt | 134 +++ .../VerificationSettingsPhenotypes.kt | 6 + .../gms/constellation/VerifyPhoneNumber.kt | 346 ++++++ .../VerifyPhoneNumberApiPhenotypes.kt | 37 + .../proto/builders/ClientInfoBuilder.kt | 173 +++ .../proto/builders/CommonBuilders.kt | 70 ++ .../proto/builders/GaiaInfoBuilder.kt | 74 ++ .../proto/builders/RequestBuildContext.kt | 20 + .../proto/builders/SyncRequestBuilder.kt | 190 +++ .../proto/builders/TelephonyInfoBuilder.kt | 143 +++ .../verification/CarrierIdVerifier.kt | 84 ++ .../verification/ChallengeProcessor.kt | 152 +++ .../verification/MoSmsVerifier.kt | 210 ++++ .../verification/MtSmsVerifier.kt | 95 ++ .../verification/RegisteredSmsVerifier.kt | 159 +++ .../verification/Ts43Verifier.kt | 431 +++++++ .../verification/ts43/EapAkaService.kt | 211 ++++ .../verification/ts43/Fips186Prf.kt | 108 ++ .../ts43/ServiceEntitlementExtension.kt | 200 ++++ .../src/main/proto/constellation.proto | 1025 +++++++++++++++++ play-services-core/build.gradle | 1 + .../src/main/AndroidManifest.xml | 11 +- .../google/android/gms/iid/InstanceID.java | 20 +- settings.gradle | 1 + 49 files changed, 5108 insertions(+), 3 deletions(-) create mode 100644 play-services-constellation/build.gradle create mode 100644 play-services-constellation/src/main/AndroidManifest.xml create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetIidTokenRequest.aidl create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetIidTokenResponse.aidl create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.aidl create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.aidl create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/PhoneNumberInfo.aidl create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/VerifyPhoneNumberRequest.aidl create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/VerifyPhoneNumberResponse.aidl create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/internal/IConstellationApiService.aidl create mode 100644 play-services-constellation/src/main/aidl/com/google/android/gms/constellation/internal/IConstellationCallbacks.aidl create mode 100644 play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetIidToken.kt create mode 100644 play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt create mode 100644 play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt create mode 100644 play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/AuthManager.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationApiService.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationStateStore.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GServices.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetIidToken.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilities.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilitiesApiPhenotype.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetVerifiedPhoneNumbers.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/IidTokenPhenotypes.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/RpcClient.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationMappings.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationSettingsPhenotypes.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumber.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumberApiPhenotypes.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/ClientInfoBuilder.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/CommonBuilders.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/GaiaInfoBuilder.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/RequestBuildContext.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/SyncRequestBuilder.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/TelephonyInfoBuilder.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/CarrierIdVerifier.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ChallengeProcessor.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MoSmsVerifier.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MtSmsVerifier.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/RegisteredSmsVerifier.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/Ts43Verifier.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/EapAkaService.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/Fips186Prf.kt create mode 100644 play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/ServiceEntitlementExtension.kt create mode 100644 play-services-constellation/src/main/proto/constellation.proto diff --git a/play-services-constellation/build.gradle b/play-services-constellation/build.gradle new file mode 100644 index 0000000000..2301c04063 --- /dev/null +++ b/play-services-constellation/build.gradle @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2026, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' +apply plugin: 'signing' + +buildscript { + repositories { + mavenCentral() + } + dependencies { + classpath 'com.squareup.wire:wire-gradle-plugin:4.9.3' + } +} +apply plugin: 'com.squareup.wire' + +android { + namespace "org.microg.gms.constellation" + + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + buildFeatures { + aidl = true + } + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +wire { + kotlin { + rpcRole = 'client' + rpcCallStyle = 'suspending' + } +} + +apply from: '../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-constellation' + +dependencies { + implementation project(':play-services-base-core') + implementation project(':play-services-iid') + implementation project(':play-services-auth-base') + + implementation project(':play-services-droidguard') + implementation project(':play-services-tasks-ktx') + + api 'com.squareup.wire:wire-runtime:4.9.3' + api 'com.squareup.wire:wire-grpc-client:4.9.3' + api 'com.squareup.okhttp3:okhttp:4.12.0' + + kapt project(":safe-parcel-processor") +} diff --git a/play-services-constellation/src/main/AndroidManifest.xml b/play-services-constellation/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..bf92ddcaf3 --- /dev/null +++ b/play-services-constellation/src/main/AndroidManifest.xml @@ -0,0 +1,17 @@ + + + diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetIidTokenRequest.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetIidTokenRequest.aidl new file mode 100644 index 0000000000..4621cec67a --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetIidTokenRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.constellation; + +parcelable GetIidTokenRequest; diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetIidTokenResponse.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetIidTokenResponse.aidl new file mode 100644 index 0000000000..832407aa6e --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetIidTokenResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.constellation; + +parcelable GetIidTokenResponse; diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.aidl new file mode 100644 index 0000000000..ffd7ecad31 --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.constellation; + +parcelable GetPnvCapabilitiesRequest; \ No newline at end of file diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.aidl new file mode 100644 index 0000000000..1b318c7ec7 --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.constellation; + +parcelable GetPnvCapabilitiesResponse; \ No newline at end of file diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/PhoneNumberInfo.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/PhoneNumberInfo.aidl new file mode 100644 index 0000000000..b6889c7bd4 --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/PhoneNumberInfo.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.constellation; + +parcelable PhoneNumberInfo; diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/VerifyPhoneNumberRequest.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/VerifyPhoneNumberRequest.aidl new file mode 100644 index 0000000000..f5ca759758 --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/VerifyPhoneNumberRequest.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.constellation; + +parcelable VerifyPhoneNumberRequest; diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/VerifyPhoneNumberResponse.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/VerifyPhoneNumberResponse.aidl new file mode 100644 index 0000000000..31eb20049c --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/VerifyPhoneNumberResponse.aidl @@ -0,0 +1,3 @@ +package com.google.android.gms.constellation; + +parcelable VerifyPhoneNumberResponse; diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/internal/IConstellationApiService.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/internal/IConstellationApiService.aidl new file mode 100644 index 0000000000..912acc7371 --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/internal/IConstellationApiService.aidl @@ -0,0 +1,39 @@ +package com.google.android.gms.constellation.internal; + +import com.google.android.gms.common.api.ApiMetadata; +import com.google.android.gms.constellation.internal.IConstellationCallbacks; +import com.google.android.gms.constellation.GetIidTokenRequest; +import com.google.android.gms.constellation.GetPnvCapabilitiesRequest; +import com.google.android.gms.constellation.VerifyPhoneNumberRequest; + +interface IConstellationApiService { + void verifyPhoneNumberV1( + IConstellationCallbacks cb, + in Bundle bundle, + in ApiMetadata apiMetadata + ); + + void verifyPhoneNumberSingleUse( + IConstellationCallbacks cb, + in Bundle bundle, + in ApiMetadata apiMetadata + ); + + void verifyPhoneNumber( + IConstellationCallbacks cb, + in VerifyPhoneNumberRequest request, + in ApiMetadata apiMetadata + ); + + void getIidToken( + IConstellationCallbacks cb, + in GetIidTokenRequest request, + in ApiMetadata apiMetadata + ); + + void getPnvCapabilities( + IConstellationCallbacks cb, + in GetPnvCapabilitiesRequest request, + in ApiMetadata apiMetadata + ); +} diff --git a/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/internal/IConstellationCallbacks.aidl b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/internal/IConstellationCallbacks.aidl new file mode 100644 index 0000000000..74ce532b86 --- /dev/null +++ b/play-services-constellation/src/main/aidl/com/google/android/gms/constellation/internal/IConstellationCallbacks.aidl @@ -0,0 +1,15 @@ +package com.google.android.gms.constellation.internal; + +import com.google.android.gms.common.api.ApiMetadata; +import com.google.android.gms.common.api.Status; +import com.google.android.gms.constellation.GetIidTokenResponse; +import com.google.android.gms.constellation.GetPnvCapabilitiesResponse; +import com.google.android.gms.constellation.PhoneNumberInfo; +import com.google.android.gms.constellation.VerifyPhoneNumberResponse; + +oneway interface IConstellationCallbacks { + void onPhoneNumberVerified(in Status status, in List phoneNumbers, in ApiMetadata apiMetadata); + void onPhoneNumberVerificationsCompleted(in Status status, in VerifyPhoneNumberResponse response, in ApiMetadata apiMetadata); + void onIidTokenGenerated(in Status status, in GetIidTokenResponse response, in ApiMetadata apiMetadata); + void onGetPnvCapabilitiesCompleted(in Status status, in GetPnvCapabilitiesResponse response, in ApiMetadata apiMetadata); +} diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetIidToken.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetIidToken.kt new file mode 100644 index 0000000000..53760184ab --- /dev/null +++ b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetIidToken.kt @@ -0,0 +1,43 @@ +package com.google.android.gms.constellation + +import android.os.Parcel +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable +import com.google.android.gms.common.internal.safeparcel.SafeParcelable +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter + +@SafeParcelable.Class +data class GetIidTokenRequest @Constructor constructor( + @JvmField @Param(1) @Field(1) val projectNumber: Long? +) : AbstractSafeParcelable() { + override fun writeToParcel(out: Parcel, flags: Int) { + CREATOR.writeToParcel(this, out, flags) + } + + companion object { + @JvmField + val CREATOR: SafeParcelableCreatorAndWriter = + findCreator(GetIidTokenRequest::class.java) + } +} + +@SafeParcelable.Class +data class GetIidTokenResponse @Constructor constructor( + @JvmField @Param(1) @Field(1) val iidToken: String, + @JvmField @Param(2) @Field(2) val fid: String, + @JvmField @Param(3) @Field(value = 3, type = "byte[]") val signature: ByteArray?, + @JvmField @Param(4) @Field(4) val timestamp: Long +) : AbstractSafeParcelable() { + + override fun writeToParcel(out: Parcel, flags: Int) { + CREATOR.writeToParcel(this, out, flags) + } + + companion object { + @JvmField + val CREATOR: SafeParcelableCreatorAndWriter = + findCreator(GetIidTokenResponse::class.java) + } +} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt new file mode 100644 index 0000000000..4598d98bb1 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt @@ -0,0 +1,87 @@ +package com.google.android.gms.constellation + +import android.os.Parcel +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable +import com.google.android.gms.common.internal.safeparcel.SafeParcelable +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param + +@SafeParcelable.Class +data class GetPnvCapabilitiesRequest @Constructor constructor( + @JvmField @Param(1) @Field(1) val policyId: String, + @JvmField @Param(2) @Field(2) val verificationTypes: List, + @JvmField @Param(3) @Field(3) val simSlotIndices: List +) : AbstractSafeParcelable() { + override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) + + companion object { + @JvmField + val CREATOR = findCreator(GetPnvCapabilitiesRequest::class.java) + } +} + +@SafeParcelable.Class +data class GetPnvCapabilitiesResponse @Constructor constructor( + @JvmField @Param(1) @Field(1) val simCapabilities: List +) : AbstractSafeParcelable() { + override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) + + companion object { + @JvmField + val CREATOR = findCreator(GetPnvCapabilitiesResponse::class.java) + } +} + +@SafeParcelable.Class +data class SimCapability @Constructor constructor( + @JvmField @Param(1) @Field(1) val slotValue: Int, + @JvmField @Param(2) @Field(2) val subscriberIdDigest: String, + @JvmField @Param(3) @Field(3) val carrierId: Int, + @JvmField @Param(4) @Field(4) val operatorName: String, + @JvmField @Param(5) @Field(5) val verificationCapabilities: List +) : AbstractSafeParcelable() { + override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) + + companion object { + @JvmField + val CREATOR = findCreator(SimCapability::class.java) + } +} + +@SafeParcelable.Class +data class VerificationCapability @Constructor constructor( + @JvmField @Param(1) @Field(1) val verificationMethod: Int, + @JvmField @Param(2) @Field(2) val statusValue: Int // Enums are typically passed as Ints in SafeParcelable +) : AbstractSafeParcelable() { + constructor(verificationMethod: Int, status: VerificationStatus) : this( + verificationMethod, + status.value + ) + + val status: VerificationStatus + get() = VerificationStatus.fromInt(statusValue) + + override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) + + companion object { + @JvmField + val CREATOR = findCreator(VerificationCapability::class.java) + } +} + +enum class VerificationStatus(val value: Int) { + SUPPORTED(1), + UNSUPPORTED_CARRIER(2), + UNSUPPORTED_API_VERSION(3), + UNSUPPORTED_SIM_NOT_READY(4); + + companion object { + fun fromInt(value: Int): VerificationStatus = when (value) { + 2 -> UNSUPPORTED_CARRIER + 3 -> UNSUPPORTED_API_VERSION + 4 -> UNSUPPORTED_SIM_NOT_READY + else -> SUPPORTED + } + } +} diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt new file mode 100644 index 0000000000..459a05b3cd --- /dev/null +++ b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt @@ -0,0 +1,28 @@ +package com.google.android.gms.constellation + +import android.os.Bundle +import android.os.Parcel +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable +import com.google.android.gms.common.internal.safeparcel.SafeParcelable +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter + +@SafeParcelable.Class +data class PhoneNumberInfo @Constructor constructor( + @JvmField @Param(1) @Field(1) val version: Int, + @JvmField @Param(2) @Field(2) val phoneNumber: String?, + @JvmField @Param(3) @Field(3) val verificationTime: Long, + @JvmField @Param(4) @Field(4) val extras: Bundle? +) : AbstractSafeParcelable() { + + override fun writeToParcel(out: Parcel, flags: Int) { + CREATOR.writeToParcel(this, out, flags) + } + + companion object { + @JvmField + val CREATOR = findCreator(PhoneNumberInfo::class.java) + } +} diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt new file mode 100644 index 0000000000..e2f159a44c --- /dev/null +++ b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt @@ -0,0 +1,95 @@ +package com.google.android.gms.constellation + +import android.os.Bundle +import android.os.Parcel +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable +import com.google.android.gms.common.internal.safeparcel.SafeParcelable +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field +import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter +import org.microg.gms.constellation.proto.VerificationMethod + +@SafeParcelable.Class +data class VerifyPhoneNumberRequest @Constructor constructor( + @JvmField @Param(1) @Field(1) val policyId: String, + @JvmField @Param(2) @Field(2) val timeout: Long, + @JvmField @Param(3) @Field(3) val idTokenRequest: IdTokenRequest, + @JvmField @Param(4) @Field(4) val extras: Bundle, + @JvmField @Param(5) @Field(5) val targetedSims: List, + @JvmField @Param(6) @Field(6) val silent: Boolean, + @JvmField @Param(7) @Field(7) val apiVersion: Int, + @JvmField @Param(8) @Field(8) val verificationTypes: List +) : AbstractSafeParcelable() { + val verificationMethods: List + get() = verificationTypes.mapNotNull { VerificationMethod.fromValue(it) } + + override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) + + companion object { + @JvmField + val CREATOR = findCreator(VerifyPhoneNumberRequest::class.java) + } +} + +@SafeParcelable.Class +data class IdTokenRequest @Constructor constructor( + @JvmField @Param(1) @Field(1) val idToken: String, + @JvmField @Param(2) @Field(2) val subscriberHash: String +) : AbstractSafeParcelable() { + + override fun writeToParcel(out: Parcel, flags: Int) { + CREATOR.writeToParcel(this, out, flags) + } + + companion object { + @JvmField + val CREATOR: SafeParcelableCreatorAndWriter = + findCreator(IdTokenRequest::class.java) + } +} + +@SafeParcelable.Class +data class ImsiRequest @Constructor constructor( + @JvmField @Param(1) @Field(1) val imsi: String, + @JvmField @Param(2) @Field(2) val phoneNumberHint: String +) : AbstractSafeParcelable() { + override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) + + companion object { + @JvmField + val CREATOR = findCreator(ImsiRequest::class.java) + } +} + +@SafeParcelable.Class +data class VerifyPhoneNumberResponse @Constructor constructor( + @JvmField @Param(1) @Field(1) val verifications: Array, + @JvmField @Param(2) @Field(2) val extras: Bundle +) : AbstractSafeParcelable() { + override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) + + companion object { + @JvmField + val CREATOR = findCreator(VerifyPhoneNumberResponse::class.java) + } +} + +@SafeParcelable.Class +data class PhoneNumberVerification @Constructor constructor( + @JvmField @Param(1) @Field(1) val phoneNumber: String?, + @JvmField @Param(2) @Field(2) val timestampMillis: Long, + @JvmField @Param(3) @Field(3) val verificationMethod: Int, + @JvmField @Param(4) @Field(4) val simSlot: Int, + @JvmField @Param(5) @Field(5) val verificationToken: String?, + @JvmField @Param(6) @Field(6) val extras: Bundle?, + @JvmField @Param(7) @Field(7) val verificationStatus: Int, + @JvmField @Param(8) @Field(8) val retryAfterSeconds: Long +) : AbstractSafeParcelable() { + override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) + + companion object { + @JvmField + val CREATOR = findCreator(PhoneNumberVerification::class.java) + } +} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/AuthManager.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/AuthManager.kt new file mode 100644 index 0000000000..148d585ed3 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/AuthManager.kt @@ -0,0 +1,102 @@ +package org.microg.gms.constellation + +import android.content.Context +import android.util.Base64 +import androidx.core.content.edit +import com.google.android.gms.iid.InstanceID +import com.squareup.wire.Instant +import java.nio.charset.StandardCharsets +import java.security.KeyFactory +import java.security.KeyPair +import java.security.KeyPairGenerator +import java.security.Signature +import java.security.spec.PKCS8EncodedKeySpec +import java.security.spec.X509EncodedKeySpec + +class AuthManager(context: Context) { + private val context = context.applicationContext + + companion object { + private const val PREFS_NAME = "constellation_prefs" + private const val KEY_PRIVATE = "private_key" + private const val KEY_PUBLIC = "public_key" + + @Volatile + private var instance: AuthManager? = null + + fun get(context: Context): AuthManager { + val existing = instance + if (existing != null) return existing + + return synchronized(this) { + instance ?: AuthManager(context).also { instance = it } + } + } + } + + private val sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + // GMS signing format: {iidToken}:{seconds}:{nanos} + fun signIidToken(iidToken: String): Pair { + val now = System.currentTimeMillis() + val timestamp = Instant.ofEpochMilli(now) + val content = "$iidToken:${timestamp.epochSecond}:${timestamp.nano}" + return sign(content) to timestamp + } + + fun getIidToken(projectNumber: String? = null): String { + return try { + val sender = projectNumber ?: IidTokenPhenotypes.DEFAULT_PROJECT_NUMBER.toString() + InstanceID.getInstance(context).getToken(sender, "GCM") + } catch (_: Exception) { + "" + } + } + + fun getOrCreateKeyPair(): KeyPair { + val privateKeyStr = sharedPrefs.getString(KEY_PRIVATE, null) + val publicKeyStr = sharedPrefs.getString(KEY_PUBLIC, null) + + if (privateKeyStr != null && publicKeyStr != null) { + try { + val kf = KeyFactory.getInstance("EC") + val privateKey = kf.generatePrivate( + PKCS8EncodedKeySpec(Base64.decode(privateKeyStr, Base64.DEFAULT)) + ) + val publicKey = kf.generatePublic( + X509EncodedKeySpec(Base64.decode(publicKeyStr, Base64.DEFAULT)) + ) + return KeyPair(publicKey, privateKey) + } catch (_: Exception) { + // Fall through to regeneration on failure + } + } + + val kpg = KeyPairGenerator.getInstance("EC") + kpg.initialize(256) + val kp = kpg.generateKeyPair() + + sharedPrefs.edit { + putString(KEY_PRIVATE, Base64.encodeToString(kp.private.encoded, Base64.NO_WRAP)) + putString(KEY_PUBLIC, Base64.encodeToString(kp.public.encoded, Base64.NO_WRAP)) + } + + return kp + } + + fun sign(content: String): ByteArray { + return try { + val kp = getOrCreateKeyPair() + val signature = Signature.getInstance("SHA256withECDSA") + signature.initSign(kp.private) + signature.update(content.toByteArray(StandardCharsets.UTF_8)) + signature.sign() + } catch (_: Exception) { + ByteArray(0) + } + } + + fun getFid(): String { + return InstanceID.getInstance(context).id + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationApiService.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationApiService.kt new file mode 100644 index 0000000000..a6829f734b --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationApiService.kt @@ -0,0 +1,124 @@ +package org.microg.gms.constellation + +import android.content.Context +import android.os.Bundle +import android.util.Log +import com.google.android.gms.common.Feature +import com.google.android.gms.common.api.ApiMetadata +import com.google.android.gms.common.internal.ConnectionInfo +import com.google.android.gms.common.internal.GetServiceRequest +import com.google.android.gms.common.internal.IGmsCallbacks +import com.google.android.gms.constellation.GetIidTokenRequest +import com.google.android.gms.constellation.GetPnvCapabilitiesRequest +import com.google.android.gms.constellation.VerifyPhoneNumberRequest +import com.google.android.gms.constellation.internal.IConstellationApiService +import com.google.android.gms.constellation.internal.IConstellationCallbacks +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch +import org.microg.gms.BaseService +import org.microg.gms.common.GmsService +import org.microg.gms.common.PackageUtils + +private const val TAG = "C11NApiService" + +class ConstellationApiService : BaseService(TAG, GmsService.CONSTELLATION) { + private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + + override fun handleServiceRequest( + callback: IGmsCallbacks?, + request: GetServiceRequest?, + service: GmsService? + ) { + val packageName = PackageUtils.getAndCheckCallingPackage(this, request?.packageName) + if (!PackageUtils.isGooglePackage(this, packageName)) { + throw SecurityException("$packageName is not a Google package") + } + callback!!.onPostInitCompleteWithConnectionInfo( + 0, + ConstellationApiServiceImpl(this, packageName, serviceScope).asBinder(), + ConnectionInfo().apply { + features = arrayOf( + Feature("asterism_consent", 3), + Feature("one_time_verification", 1), + Feature("carrier_auth", 1), + Feature("verify_phone_number", 2), + Feature("get_iid_token", 1), + Feature("get_pnv_capabilities", 1), + Feature("ts43", 1), + Feature("verify_phone_number_local_read", 1) + ) + }) + } + + override fun onDestroy() { + super.onDestroy() + serviceScope.cancel("ConstellationApiService destroyed") + } +} + +class ConstellationApiServiceImpl( + private val context: Context, + private val packageName: String?, + private val serviceScope: CoroutineScope +) : IConstellationApiService.Stub() { + override fun verifyPhoneNumberV1( + cb: IConstellationCallbacks?, + bundle: Bundle?, + apiMetadata: ApiMetadata? + ) { + Log.i( + TAG, + "verifyPhoneNumberV1(): mode=${bundle?.getInt("verification_mode")}, policy=${ + bundle?.getString("policy_id") + }" + ) + if (cb == null || bundle == null) return + serviceScope.launch { handleVerifyPhoneNumberV1(context, cb, bundle, packageName) } + } + + override fun verifyPhoneNumberSingleUse( + cb: IConstellationCallbacks?, + bundle: Bundle?, + apiMetadata: ApiMetadata? + ) { + Log.i(TAG, "verifyPhoneNumberSingleUse()") + if (cb == null || bundle == null) return + serviceScope.launch { handleVerifyPhoneNumberSingleUse(context, cb, bundle, packageName) } + } + + override fun verifyPhoneNumber( + cb: IConstellationCallbacks?, + request: VerifyPhoneNumberRequest?, + apiMetadata: ApiMetadata? + ) { + Log.i( + TAG, + "verifyPhoneNumber(): apiVersion=${request?.apiVersion}, policy=${request?.policyId}" + ) + if (cb == null || request == null) return + serviceScope.launch { handleVerifyPhoneNumberRequest(context, cb, request, packageName) } + } + + override fun getIidToken( + cb: IConstellationCallbacks?, + request: GetIidTokenRequest?, + apiMetadata: ApiMetadata?, + ) { + Log.i(TAG, "getIidToken(): $request") + if (cb == null || request == null) return + serviceScope.launch { handleGetIidToken(context, cb, request) } + } + + override fun getPnvCapabilities( + cb: IConstellationCallbacks?, + request: GetPnvCapabilitiesRequest?, + apiMetadata: ApiMetadata?, + ) { + Log.i(TAG, "getPnvCapabilities(): $request") + if (cb == null || request == null) return + serviceScope.launch { handleGetPnvCapabilities(context, cb, request) } + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationStateStore.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationStateStore.kt new file mode 100644 index 0000000000..5c6f07bd08 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationStateStore.kt @@ -0,0 +1,180 @@ +package org.microg.gms.constellation + +import android.annotation.SuppressLint +import android.content.Context +import android.util.Base64 +import androidx.core.content.edit +import com.squareup.wire.Instant +import okio.ByteString.Companion.toByteString +import org.microg.gms.constellation.proto.DroidguardToken +import org.microg.gms.constellation.proto.ProceedResponse +import org.microg.gms.constellation.proto.Consent +import org.microg.gms.constellation.proto.ConsentSource +import org.microg.gms.constellation.proto.ConsentVersion +import org.microg.gms.constellation.proto.ServerTimestamp +import org.microg.gms.constellation.proto.SyncResponse +import org.microg.gms.constellation.proto.VerificationToken + +private const val STATE_PREFS_NAME = "constellation_prefs" +private const val TOKEN_PREFS_NAME = "com.google.android.gms.constellation" +private const val KEY_VERIFICATION_TOKENS = "verification_tokens_v1" +private const val KEY_DROIDGUARD_TOKEN = "droidguard_token" +private const val KEY_DROIDGUARD_TOKEN_TTL = "droidguard_token_ttl" +private const val KEY_NEXT_SYNC_TIMESTAMP_MS = "next_sync_timestamp_in_millis" +private const val KEY_PUBLIC_KEY_ACKED = "is_public_key_acked" +private const val KEY_PNVR_NOTICE_CONSENT = "pnvr_notice_consent" +private const val KEY_PNVR_NOTICE_SOURCE = "pnvr_notice_source" +private const val KEY_PNVR_NOTICE_VERSION = "pnvr_notice_version" +private const val KEY_PNVR_NOTICE_UPDATED_AT_MS = "pnvr_notice_updated_at_ms" + +object ConstellationStateStore { + fun loadVerificationTokens(context: Context): List { + val prefs = tokenPrefs(context) + val serialized = prefs.getString(KEY_VERIFICATION_TOKENS, null) ?: return emptyList() + return serialized.split(",").mapNotNull { entry -> + val parts = entry.split("|", limit = 2) + if (parts.size != 2) return@mapNotNull null + val tokenBytes = runCatching { + Base64.decode(parts[0], Base64.DEFAULT).toByteString() + }.getOrNull() ?: return@mapNotNull null + val expirationMillis = parts[1].toLongOrNull() ?: return@mapNotNull null + if (expirationMillis <= System.currentTimeMillis()) return@mapNotNull null + VerificationToken( + token = tokenBytes, + expiration_time = Instant.ofEpochMilli(expirationMillis) + ) + } + } + + fun storeSyncResponse(context: Context, response: SyncResponse) { + storeVerificationTokens(context, response.verification_tokens) + storeDroidGuardToken(context, response.droidguard_token) + storeNextSyncTime(context, response.next_sync_time) + } + + fun storeProceedResponse(context: Context, response: ProceedResponse) { + storeDroidGuardToken(context, response.droidguard_token) + storeNextSyncTime(context, response.next_sync_time) + } + + fun loadDroidGuardToken(context: Context): String? { + val statePrefs = statePrefs(context) + var token = statePrefs.getString(KEY_DROIDGUARD_TOKEN, null) + var expiration = statePrefs.getLong(KEY_DROIDGUARD_TOKEN_TTL, 0L) + + if (token.isNullOrBlank() || expiration == 0L) { + val legacyPrefs = tokenPrefs(context) + val legacyToken = legacyPrefs.getString(KEY_DROIDGUARD_TOKEN, null) + val legacyExpiration = legacyPrefs.getLong(KEY_DROIDGUARD_TOKEN_TTL, 0L) + if (!legacyToken.isNullOrBlank() && legacyExpiration > 0L) { + statePrefs.edit { + putString(KEY_DROIDGUARD_TOKEN, legacyToken) + putLong(KEY_DROIDGUARD_TOKEN_TTL, legacyExpiration) + } + token = legacyToken + expiration = legacyExpiration + } + } + + if (!token.isNullOrBlank() && expiration > System.currentTimeMillis()) { + return token + } + + if (!token.isNullOrBlank() || expiration > 0L) { + clearDroidGuardToken(context) + } + return null + } + + private fun storeCachedDroidGuardToken( + context: Context, + token: String, + expirationMillis: Long + ) { + if (token.isBlank() || expirationMillis <= System.currentTimeMillis()) return + statePrefs(context).edit { + putString(KEY_DROIDGUARD_TOKEN, token) + putLong(KEY_DROIDGUARD_TOKEN_TTL, expirationMillis) + } + } + + fun clearDroidGuardToken(context: Context) { + statePrefs(context).edit { + remove(KEY_DROIDGUARD_TOKEN) + remove(KEY_DROIDGUARD_TOKEN_TTL) + } + tokenPrefs(context).edit { + remove(KEY_DROIDGUARD_TOKEN) + remove(KEY_DROIDGUARD_TOKEN_TTL) + } + } + + fun isPublicKeyAcked(context: Context): Boolean { + return statePrefs(context).getBoolean(KEY_PUBLIC_KEY_ACKED, false) + } + + @SuppressLint("ApplySharedPref") + fun setPublicKeyAcked(context: Context, acked: Boolean) { + statePrefs(context).edit { putBoolean(KEY_PUBLIC_KEY_ACKED, acked) } + } + + fun loadPnvrNoticeConsent(context: Context): Consent { + val value = statePrefs(context).getInt(KEY_PNVR_NOTICE_CONSENT, Consent.CONSENT_UNKNOWN.value) + return Consent.fromValue(value) ?: Consent.CONSENT_UNKNOWN + } + + fun storePnvrNotice( + context: Context, + consent: Consent, + source: ConsentSource, + version: ConsentVersion + ) { + statePrefs(context).edit { + putInt(KEY_PNVR_NOTICE_CONSENT, consent.value) + putInt(KEY_PNVR_NOTICE_SOURCE, source.value) + putInt(KEY_PNVR_NOTICE_VERSION, version.value) + putLong(KEY_PNVR_NOTICE_UPDATED_AT_MS, System.currentTimeMillis()) + } + } + + private fun storeVerificationTokens(context: Context, tokens: List) { + if (tokens.isEmpty()) return + val filtered = tokens.filter { + (it.expiration_time?.toEpochMilli() ?: 0L) > System.currentTimeMillis() + } + if (filtered.isEmpty()) return + + val serialized = filtered.joinToString(",") { token -> + val encoded = Base64.encodeToString(token.token.toByteArray(), Base64.NO_WRAP) + val expiration = token.expiration_time?.toEpochMilli() ?: 0L + "$encoded|$expiration" + } + tokenPrefs(context).edit { putString(KEY_VERIFICATION_TOKENS, serialized) } + } + + private fun storeDroidGuardToken(context: Context, token: DroidguardToken?) { + val tokenValue = token?.token?.takeIf { it.isNotEmpty() } ?: return + val expiration = token.ttl?.toEpochMilli() ?: return + storeCachedDroidGuardToken(context, tokenValue, expiration) + } + + private fun storeNextSyncTime(context: Context, timestamp: ServerTimestamp?) { + val nextSyncDelayMillis = timestamp?.let(::nextSyncDelayMillis) ?: return + statePrefs(context).edit { + // GMS stores the next sync deadline as an absolute wall-clock timestamp + putLong(KEY_NEXT_SYNC_TIMESTAMP_MS, System.currentTimeMillis() + nextSyncDelayMillis) + } + } + + private fun nextSyncDelayMillis(timestamp: ServerTimestamp): Long { + val serverMillis = timestamp.timestamp?.toEpochMilli() ?: 0L + val localMillis = timestamp.now?.toEpochMilli() ?: 0L + return serverMillis - localMillis + } + + private fun statePrefs(context: Context) = + context.getSharedPreferences(STATE_PREFS_NAME, Context.MODE_PRIVATE) + + private fun tokenPrefs(context: Context) = + context.getSharedPreferences(TOKEN_PREFS_NAME, Context.MODE_PRIVATE) +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GServices.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GServices.kt new file mode 100644 index 0000000000..ca5b6acbb9 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GServices.kt @@ -0,0 +1,33 @@ +package org.microg.gms.constellation + +import android.content.ContentResolver +import android.net.Uri +import androidx.core.net.toUri + +// TODO: This is taken from vending-app, can we have a common client for GServices please? + +object GServices { + private val CONTENT_URI: Uri = "content://com.google.android.gsf.gservices".toUri() + + fun getString(resolver: ContentResolver, key: String, defaultValue: String?): String? { + var result = defaultValue + val cursor = resolver.query(CONTENT_URI, null, null, arrayOf(key), null) + cursor?.use { + if (cursor.moveToNext()) { + result = cursor.getString(1) + } + } + return result + } + + fun getLong(resolver: ContentResolver, key: String, defaultValue: Long): Long { + val result = getString(resolver, key, null) + if (result != null) { + try { + return result.toLong() + } catch (_: NumberFormatException) { + } + } + return defaultValue + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetIidToken.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetIidToken.kt new file mode 100644 index 0000000000..08775f8b9f --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetIidToken.kt @@ -0,0 +1,35 @@ +package org.microg.gms.constellation + +import android.content.Context +import android.util.Log +import com.google.android.gms.common.api.ApiMetadata +import com.google.android.gms.common.api.Status +import com.google.android.gms.constellation.GetIidTokenRequest +import com.google.android.gms.constellation.GetIidTokenResponse +import com.google.android.gms.constellation.internal.IConstellationCallbacks +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +private const val TAG = "GetIidToken" + +suspend fun handleGetIidToken( + context: Context, + callbacks: IConstellationCallbacks, + request: GetIidTokenRequest +) = withContext(Dispatchers.IO) { + try { + val authManager = AuthManager.get(context) + val iidToken = authManager.getIidToken(request.projectNumber?.toString()) + val fid = authManager.getFid() + val (signature, timestamp) = authManager.signIidToken(iidToken) + + callbacks.onIidTokenGenerated( + Status.SUCCESS, + GetIidTokenResponse(iidToken, fid, signature, timestamp.toEpochMilli()), + ApiMetadata.DEFAULT + ) + } catch (e: Exception) { + Log.e(TAG, "getIidToken failed", e) + callbacks.onIidTokenGenerated(Status.INTERNAL_ERROR, null, ApiMetadata.DEFAULT) + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilities.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilities.kt new file mode 100644 index 0000000000..38fec9f3e8 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilities.kt @@ -0,0 +1,120 @@ +package org.microg.gms.constellation + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.util.Base64 +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.annotation.RequiresPermission +import androidx.core.content.getSystemService +import com.google.android.gms.common.api.ApiMetadata +import com.google.android.gms.common.api.Status +import com.google.android.gms.constellation.GetPnvCapabilitiesRequest +import com.google.android.gms.constellation.GetPnvCapabilitiesResponse +import com.google.android.gms.constellation.SimCapability +import com.google.android.gms.constellation.VerificationCapability +import com.google.android.gms.constellation.VerificationStatus +import com.google.android.gms.constellation.internal.IConstellationCallbacks +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.security.MessageDigest + +private const val TAG = "GetPnvCapabilities" + +@SuppressLint("HardwareIds") +@RequiresPermission( + allOf = + [ + Manifest.permission.READ_PHONE_STATE, + "android.permission.READ_PRIVILEGED_PHONE_STATE"] +) +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +suspend fun handleGetPnvCapabilities( + context: Context, + callbacks: IConstellationCallbacks, + request: GetPnvCapabilitiesRequest +) = withContext(Dispatchers.IO) { + try { + val baseTelephonyManager = + context.getSystemService() + ?: throw IllegalStateException("TelephonyManager unavailable") + val subscriptionManager = + context.getSystemService() + ?: throw IllegalStateException("SubscriptionManager unavailable") + val simCapabilities = subscriptionManager.activeSubscriptionInfoList + .orEmpty() + .filter { request.simSlotIndices.isEmpty() || it.simSlotIndex in request.simSlotIndices } + .map { info -> + val telephonyManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + baseTelephonyManager.createForSubscriptionId(info.subscriptionId) + } else { + baseTelephonyManager + } + + val carrierId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + telephonyManager.simCarrierId + } else { + 0 + } + + // GMS hardcodes public verification method 9 for the Firebase PNV TS43 capability path. + val verificationCapabilities = if (9 in request.verificationTypes) { + listOf( + VerificationCapability( + 9, + when { + !GetPnvCapabilitiesApiPhenotype.FPNV_ALLOWED_CARRIER_IDS.contains(carrierId) -> + VerificationStatus.UNSUPPORTED_CARRIER + + telephonyManager.simState != TelephonyManager.SIM_STATE_READY -> + VerificationStatus.UNSUPPORTED_SIM_NOT_READY + + else -> VerificationStatus.SUPPORTED + } + ) + ) + } else { + emptyList() + } + + // TODO: Reflection should be used to call telephonyManager.getSubscriberId(it.subscriptionId) for SDK < N + val subscriberIdDigest = MessageDigest.getInstance("SHA-256") + .digest(telephonyManager.subscriberId.orEmpty().toByteArray()) + val subscriberIdDigestEncoded = + Base64.encodeToString(subscriberIdDigest, Base64.NO_WRAP) + + SimCapability( + info.simSlotIndex, + subscriberIdDigestEncoded, + carrierId, + // TODO: SDK < N is TelephonyManager.getSimOperatorNameForSubscription + telephonyManager.simOperatorName.orEmpty(), + verificationCapabilities + ) + } + + callbacks.onGetPnvCapabilitiesCompleted( + Status.SUCCESS, + GetPnvCapabilitiesResponse(simCapabilities), + ApiMetadata.DEFAULT + ) + } catch (e: SecurityException) { + Log.e(TAG, "getPnvCapabilities missing permission", e) + callbacks.onGetPnvCapabilitiesCompleted( + Status(5000), + GetPnvCapabilitiesResponse(emptyList()), + ApiMetadata.DEFAULT + ) + } catch (e: Exception) { + Log.e(TAG, "getPnvCapabilities failed", e) + callbacks.onGetPnvCapabilitiesCompleted( + Status.INTERNAL_ERROR, + GetPnvCapabilitiesResponse(emptyList()), + ApiMetadata.DEFAULT + ) + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilitiesApiPhenotype.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilitiesApiPhenotype.kt new file mode 100644 index 0000000000..814fae3f1c --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilitiesApiPhenotype.kt @@ -0,0 +1,5 @@ +package org.microg.gms.constellation + +object GetPnvCapabilitiesApiPhenotype { + val FPNV_ALLOWED_CARRIER_IDS = ArrayList() +} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetVerifiedPhoneNumbers.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetVerifiedPhoneNumbers.kt new file mode 100644 index 0000000000..b382bef5a8 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetVerifiedPhoneNumbers.kt @@ -0,0 +1,137 @@ +package org.microg.gms.constellation + +import android.content.Context +import android.os.Bundle +import android.util.Log +import com.google.android.gms.common.api.ApiMetadata +import com.google.android.gms.common.api.Status +import com.google.android.gms.constellation.PhoneNumberInfo +import com.google.android.gms.constellation.PhoneNumberVerification +import com.google.android.gms.constellation.VerifyPhoneNumberResponse +import com.google.android.gms.constellation.internal.IConstellationCallbacks +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okio.ByteString.Companion.toByteString +import org.microg.gms.constellation.proto.GetVerifiedPhoneNumbersRequest +import org.microg.gms.constellation.proto.GetVerifiedPhoneNumbersRequest.PhoneNumberSelection +import org.microg.gms.constellation.proto.IIDTokenAuth +import org.microg.gms.constellation.proto.TokenOption +import org.microg.gms.constellation.proto.VerifiedPhoneNumber as RpcVerifiedPhoneNumber +import java.util.UUID + +private const val TAG = "GetVerifiedPhoneNumbers" + +suspend fun handleGetVerifiedPhoneNumbers( + context: Context, + callbacks: IConstellationCallbacks, + bundle: Bundle +) = withContext(Dispatchers.IO) { + try { + val phoneNumbers = fetchVerifiedPhoneNumbers(context, bundle).map { it.toPhoneNumberInfo() } + + callbacks.onPhoneNumberVerified(Status.SUCCESS, phoneNumbers, ApiMetadata.DEFAULT) + } catch (e: Exception) { + Log.e(TAG, "Error in GetVerifiedPhoneNumbers (read-only)", e) + callbacks.onPhoneNumberVerified(Status.INTERNAL_ERROR, emptyList(), ApiMetadata.DEFAULT) + } +} + +internal suspend fun fetchVerifiedPhoneNumbers( + context: Context, + bundle: Bundle, + callingPackage: String = bundle.getString("calling_package") ?: context.packageName +): List = withContext(Dispatchers.IO) { + val authManager = AuthManager.get(context) + val sessionId = UUID.randomUUID().toString() + val selections = extractPhoneNumberSelections(bundle) + val certificateHash = bundle.getString("certificate_hash") ?: "" + val tokenNonce = bundle.getString("token_nonce") ?: "" + + val iidToken = authManager.getIidToken() + val iidTokenAuth = if (VerifyPhoneNumberApiPhenotypes.ENABLE_CLIENT_SIGNATURE) { + val (signatureBytes, signTimestamp) = authManager.signIidToken(iidToken) + IIDTokenAuth( + iid_token = iidToken, + client_sign = signatureBytes.toByteString(), + sign_timestamp = signTimestamp + ) + } else { + IIDTokenAuth(iid_token = iidToken) + } + + val getRequest = GetVerifiedPhoneNumbersRequest( + session_id = sessionId, + iid_token_auth = iidTokenAuth, + phone_number_selections = selections, + token_option = TokenOption( + certificate_hash = certificateHash, + token_nonce = tokenNonce, + package_name = callingPackage + ) + ) + + Log.d(TAG, "Calling GetVerifiedPhoneNumbers RPC (read-only mode)...") + val response = RpcClient.phoneNumberClient + .GetVerifiedPhoneNumbers() + .execute(getRequest) + Log.d(TAG, "GetVerifiedPhoneNumbers response: ${response.phone_numbers.size} numbers") + response.phone_numbers +} + +internal fun List.toVerifyPhoneNumberResponse(): VerifyPhoneNumberResponse { + return VerifyPhoneNumberResponse(map { it.toPhoneNumberVerification() }.toTypedArray(), Bundle.EMPTY) +} + +private fun RpcVerifiedPhoneNumber.toPhoneNumberInfo(): PhoneNumberInfo { + val extras = Bundle().apply { + if (id_token.isNotEmpty()) { + putString("id_token", id_token) + } + putInt("rcs_state", rcs_state.value) + } + + return PhoneNumberInfo( + 1, + phone_number, + verification_time?.toEpochMilli() ?: 0L, + extras + ) +} + +private fun RpcVerifiedPhoneNumber.toPhoneNumberVerification(): PhoneNumberVerification { + val extras = Bundle().apply { + putInt("rcs_state", rcs_state.value) + } + + // GMS read-only V2 leaves method/slot unset and returns a verified record directly. + return PhoneNumberVerification( + phone_number, + verification_time?.toEpochMilli() ?: 0L, + 0, + -1, + id_token.ifEmpty { null }, + extras, + 1, + -1L + ) +} + +private fun extractPhoneNumberSelections(bundle: Bundle): List { + val selections = mutableListOf() + val selectionInts = bundle.getIntegerArrayList("phone_number_selection") + + if (!selectionInts.isNullOrEmpty()) { + selections.addAll(selectionInts.mapNotNull { PhoneNumberSelection.fromValue(it) }) + } else { + when (bundle.getString("rcs_read_option", "")) { + "READ_PROVISIONED" -> { + selections.add(PhoneNumberSelection.CONSTELLATION) + selections.add(PhoneNumberSelection.RCS) + } + + "READ_PROVISIONED_ONLY" -> selections.add(PhoneNumberSelection.RCS) + else -> selections.add(PhoneNumberSelection.CONSTELLATION) + } + } + return selections +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/IidTokenPhenotypes.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/IidTokenPhenotypes.kt new file mode 100644 index 0000000000..a700f12f2f --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/IidTokenPhenotypes.kt @@ -0,0 +1,9 @@ +package org.microg.gms.constellation + +object IidTokenPhenotypes { + const val ASTERISM_PROJECT_NUMBER = 496232013492 + const val DEFAULT_PROJECT_NUMBER = 496232013492 + const val EXTERNAL_CONSENT_ACTIVITY_PROJECT_NUMBER = 496232013492 + const val MESSAGES_PROJECT_NUMBER = 496232013492 + const val READ_ONLY_PROJECT_NUMBER = 745476177629 +} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/RpcClient.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/RpcClient.kt new file mode 100644 index 0000000000..f7a49733aa --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/RpcClient.kt @@ -0,0 +1,47 @@ +package org.microg.gms.constellation + +import com.squareup.wire.GrpcClient +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.Response +import org.microg.gms.common.Constants +import org.microg.gms.constellation.proto.PhoneDeviceVerificationClient +import org.microg.gms.constellation.proto.PhoneNumberClient + +private class AuthInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val originalRequest = chain.request() + + val builder = originalRequest.newBuilder() + .header("X-Goog-Api-Key", "AIzaSyAP-gfH3qvi6vgHZbSYwQ_XHqV_mXHhzIk") + .header("X-Android-Package", Constants.GMS_PACKAGE_NAME) + .header("X-Android-Cert", Constants.GMS_PACKAGE_SIGNATURE_SHA1.uppercase()) + + return chain.proceed(builder.build()) + } +} + +object RpcClient { + private val client: OkHttpClient by lazy { + OkHttpClient.Builder() + .addInterceptor(AuthInterceptor()) + .build() + } + + private val grpcClient: GrpcClient by lazy { + GrpcClient.Builder() + .client(client) + // Google's constellationserver does NOT like compressed requests + .minMessageToCompress(Long.MAX_VALUE) + .baseUrl("https://phonedeviceverification-pa.googleapis.com/") + .build() + } + + val phoneDeviceVerificationClient: PhoneDeviceVerificationClient by lazy { + grpcClient.create(PhoneDeviceVerificationClient::class) + } + + val phoneNumberClient: PhoneNumberClient by lazy { + grpcClient.create(PhoneNumberClient::class) + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationMappings.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationMappings.kt new file mode 100644 index 0000000000..55bbdda27e --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationMappings.kt @@ -0,0 +1,134 @@ +package org.microg.gms.constellation + +import android.os.Bundle +import com.google.android.gms.constellation.PhoneNumberVerification +import org.microg.gms.constellation.proto.Param +import org.microg.gms.constellation.proto.UnverifiedInfo +import org.microg.gms.constellation.proto.Verification +import org.microg.gms.constellation.proto.VerificationMethod + +fun UnverifiedInfo.Reason.toVerificationStatus(): Verification.Status { + return when (this) { + UnverifiedInfo.Reason.UNKNOWN_REASON -> Verification.Status.STATUS_PENDING + UnverifiedInfo.Reason.THROTTLED -> Verification.Status.STATUS_THROTTLED + UnverifiedInfo.Reason.FAILED -> Verification.Status.STATUS_FAILED + UnverifiedInfo.Reason.SKIPPED -> Verification.Status.STATUS_SKIPPED + UnverifiedInfo.Reason.NOT_REQUIRED -> Verification.Status.STATUS_NOT_REQUIRED + UnverifiedInfo.Reason.PHONE_NUMBER_ENTRY_REQUIRED -> + Verification.Status.STATUS_PHONE_NUMBER_ENTRY_REQUIRED + + UnverifiedInfo.Reason.INELIGIBLE -> Verification.Status.STATUS_INELIGIBLE + UnverifiedInfo.Reason.DENIED -> Verification.Status.STATUS_DENIED + UnverifiedInfo.Reason.NOT_IN_SERVICE -> Verification.Status.STATUS_NOT_IN_SERVICE + } +} + +fun Verification.getState(): Verification.State { + return when { + verification_info != null -> Verification.State.VERIFIED + pending_verification_info != null -> Verification.State.PENDING + unverified_info != null -> Verification.State.NONE + else -> Verification.State.UNKNOWN + } +} + +fun Verification.getVerificationStatus(): Verification.Status { + return when (getState()) { + Verification.State.VERIFIED -> Verification.Status.STATUS_VERIFIED + + Verification.State.PENDING -> { + if (status != Verification.Status.STATUS_UNKNOWN) { + status + } else { + Verification.Status.STATUS_PENDING + } + } + + Verification.State.NONE -> { + unverified_info?.reason?.toVerificationStatus() ?: Verification.Status.STATUS_PENDING + } + + Verification.State.UNKNOWN -> Verification.Status.STATUS_UNKNOWN + } +} + +fun Verification.toClientVerification(imsiToSlotMap: Map): PhoneNumberVerification { + val verificationStatus = this.getVerificationStatus() + var phoneNumber: String? = null + var timestampMillis = System.currentTimeMillis() + var verificationMethod = VerificationMethod.UNKNOWN + var retryAfterSeconds = 0L + val extras = buildClientExtras() + + when (this.getState()) { + Verification.State.VERIFIED -> { + val info = this.verification_info + phoneNumber = info?.phone_number + timestampMillis = info?.verification_time?.toEpochMilli() ?: System.currentTimeMillis() + verificationMethod = info?.challenge_method ?: VerificationMethod.UNKNOWN + } + + Verification.State.PENDING -> { + verificationMethod = + this.pending_verification_info?.challenge?.type ?: VerificationMethod.UNKNOWN + } + + Verification.State.NONE -> { + val info = this.unverified_info + verificationMethod = info?.challenge_method ?: VerificationMethod.UNKNOWN + retryAfterSeconds = info?.retry_after_time?.let { ts -> + val now = System.currentTimeMillis() / 1000L + (ts.epochSecond - now).coerceAtLeast(0L) + } ?: 0L + } + + else -> {} + } + + val simImsi = this.association?.sim?.sim_info?.imsi?.firstOrNull() + val simSlot = if (simImsi != null) imsiToSlotMap[simImsi] ?: 0 else 0 + val verificationToken = extras.getString("id_token") + + return PhoneNumberVerification( + phoneNumber, + timestampMillis, + verificationMethod.toClientMethod(), + simSlot, + verificationToken, + extras, + verificationStatus.value, + retryAfterSeconds + ) +} + +private fun Verification.buildClientExtras(): Bundle { + val bundle = Bundle() + for (param in api_params) { + bundle.putParam(param) + } + + val slotIndex = association?.sim?.sim_slot?.slot_index + if (slotIndex != null && slotIndex >= 0) { + // GMS exposes this as a string inside the extras bundle. + bundle.putString("sim_slot_index", slotIndex.toString()) + } + return bundle +} + +private fun Bundle.putParam(param: Param) { + if (param.key == "verification_method") { + param.value_.toIntOrNull()?.let { + putInt(param.key, it) + return + } + } + putString(param.key, param.value_) +} + +fun VerificationMethod.toClientMethod(): Int { + return when (this) { + VerificationMethod.TS43 -> 9 + VerificationMethod.UNKNOWN -> 0 + else -> value + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationSettingsPhenotypes.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationSettingsPhenotypes.kt new file mode 100644 index 0000000000..c51f0a279e --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationSettingsPhenotypes.kt @@ -0,0 +1,6 @@ +package org.microg.gms.constellation + +object VerificationSettingsPhenotypes { + const val A2P_SMS_SIGNAL_GRANULARITY_HRS = 1L + const val A2P_HISTORY_WINDOW_HOURS = 168L +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumber.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumber.kt new file mode 100644 index 0000000000..d0dc3b314c --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumber.kt @@ -0,0 +1,346 @@ +package org.microg.gms.constellation + +import android.content.Context +import android.os.Build +import android.os.Bundle +import android.telephony.SubscriptionInfo +import android.util.Log +import androidx.annotation.RequiresApi +import com.google.android.gms.common.api.ApiMetadata +import com.google.android.gms.common.api.Status +import com.google.android.gms.constellation.IdTokenRequest +import com.google.android.gms.constellation.PhoneNumberInfo +import com.google.android.gms.constellation.PhoneNumberVerification +import com.google.android.gms.constellation.VerifyPhoneNumberRequest +import com.google.android.gms.constellation.VerifyPhoneNumberResponse +import com.google.android.gms.constellation.internal.IConstellationCallbacks +import com.squareup.wire.GrpcException +import com.squareup.wire.GrpcStatus +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.constellation.proto.SyncRequest +import org.microg.gms.constellation.proto.Verification +import org.microg.gms.constellation.proto.builders.RequestBuildContext +import org.microg.gms.constellation.proto.builders.buildImsiToSubscriptionInfoMap +import org.microg.gms.constellation.proto.builders.buildRequestContext +import org.microg.gms.constellation.proto.builders.invoke +import org.microg.gms.constellation.verification.ChallengeProcessor +import java.util.UUID + +private const val TAG = "VerifyPhoneNumber" + +private enum class ReadCallbackMode { + NONE, + LEGACY, + TYPED +} + +suspend fun handleVerifyPhoneNumberV1( + context: Context, + callbacks: IConstellationCallbacks, + bundle: Bundle, + packageName: String? +) { + val callingPackage = packageName ?: bundle.getString("calling_package") ?: context.packageName + val extras = Bundle(bundle).apply { + putString("calling_package", callingPackage) + putString("calling_api", "verifyPhoneNumber") + } + val timeout = when (val timeoutValue = extras.get("timeout")) { + is Long -> timeoutValue + is Int -> timeoutValue.toLong() + else -> 300L + } + val request = VerifyPhoneNumberRequest( + policyId = extras.getString("policy_id", ""), + timeout = timeout, + idTokenRequest = IdTokenRequest( + extras.getString("certificate_hash", ""), + extras.getString("token_nonce", "") + ), + extras = extras, + targetedSims = emptyList(), + silent = false, + apiVersion = 2, + verificationTypes = emptyList() + ) + val policyId = bundle.getString("policy_id", "") + val mode = bundle.getInt("verification_mode", 0) + val useReadPath = when (mode) { + 0 -> policyId in VerifyPhoneNumberApiPhenotypes.READ_ONLY_POLICY_IDS + 2 -> VerifyPhoneNumberApiPhenotypes.ENABLE_READ_FLOW + + else -> false + } + + handleVerifyPhoneNumberRequest( + context, + callbacks, + request, + callingPackage, + if (useReadPath) ReadCallbackMode.LEGACY else ReadCallbackMode.NONE, + legacyCallbackOnFullFlow = true + ) +} + +suspend fun handleVerifyPhoneNumberSingleUse( + context: Context, + callbacks: IConstellationCallbacks, + bundle: Bundle, + packageName: String? +) { + val callingPackage = packageName ?: bundle.getString("calling_package") ?: context.packageName + val extras = Bundle(bundle).apply { + putString("calling_package", callingPackage) + putString("calling_api", "verifyPhoneNumberSingleUse") + putString("one_time_verification", "True") + } + val timeout = when (val timeoutValue = extras.get("timeout")) { + is Long -> timeoutValue + is Int -> timeoutValue.toLong() + else -> 300L + } + val request = VerifyPhoneNumberRequest( + policyId = extras.getString("policy_id", ""), + timeout = timeout, + idTokenRequest = IdTokenRequest( + extras.getString("certificate_hash", ""), + extras.getString("token_nonce", "") + ), + extras = extras, + targetedSims = emptyList(), + silent = false, + apiVersion = 2, + verificationTypes = emptyList() + ) + + handleVerifyPhoneNumberRequest( + context, + callbacks, + request, + callingPackage, + ReadCallbackMode.NONE, + legacyCallbackOnFullFlow = true + ) +} + +suspend fun handleVerifyPhoneNumberRequest( + context: Context, + callbacks: IConstellationCallbacks, + request: VerifyPhoneNumberRequest, + packageName: String? +) { + val callingPackage = packageName ?: context.packageName + val requestWithExtras = request.copy( + extras = Bundle(request.extras).apply { + putString("calling_api", "verifyPhoneNumber") + } + ) + val useReadPath = when (requestWithExtras.apiVersion) { + 0 -> requestWithExtras.policyId in VerifyPhoneNumberApiPhenotypes.READ_ONLY_POLICY_IDS + 2 -> VerifyPhoneNumberApiPhenotypes.ENABLE_READ_FLOW + 3 -> requestWithExtras.policyId in VerifyPhoneNumberApiPhenotypes.POLICY_IDS_ALLOWED_FOR_LOCAL_READ + else -> false + } + + handleVerifyPhoneNumberRequest( + context, + callbacks, + requestWithExtras, + callingPackage, + if (useReadPath) ReadCallbackMode.TYPED else ReadCallbackMode.NONE, + localReadFallback = requestWithExtras.apiVersion == 3 && useReadPath, + legacyCallbackOnFullFlow = false + ) +} + +private suspend fun handleVerifyPhoneNumberRequest( + context: Context, + callbacks: IConstellationCallbacks, + request: VerifyPhoneNumberRequest, + callingPackage: String, + readCallbackMode: ReadCallbackMode, + localReadFallback: Boolean = false, + legacyCallbackOnFullFlow: Boolean = false +) { + try { + when (readCallbackMode) { + ReadCallbackMode.LEGACY -> { + Log.d(TAG, "Using read-only mode") + handleGetVerifiedPhoneNumbers(context, callbacks, request.extras) + } + + ReadCallbackMode.TYPED -> { + if (localReadFallback) { + Log.w(TAG, "Local-read mode not implemented, falling back to read-only RPC") + } else { + Log.d(TAG, "Using typed read-only mode") + } + val response = fetchVerifiedPhoneNumbers(context, request.extras, callingPackage) + .toVerifyPhoneNumberResponse() + callbacks.onPhoneNumberVerificationsCompleted( + Status.SUCCESS, + response, + ApiMetadata.DEFAULT + ) + } + + ReadCallbackMode.NONE -> { + Log.d(TAG, "Using full verification mode") + runVerificationFlow( + context, + request, + callingPackage, + callbacks, + legacyCallback = legacyCallbackOnFullFlow + ) + } + } + } catch (e: Exception) { + Log.e(TAG, "verifyPhoneNumber failed", e) + when (readCallbackMode) { + ReadCallbackMode.LEGACY -> { + callbacks.onPhoneNumberVerified(Status.INTERNAL_ERROR, emptyList(), ApiMetadata.DEFAULT) + } + + ReadCallbackMode.NONE -> { + if (legacyCallbackOnFullFlow) { + callbacks.onPhoneNumberVerified(Status.INTERNAL_ERROR, emptyList(), ApiMetadata.DEFAULT) + } else { + callbacks.onPhoneNumberVerificationsCompleted( + Status.INTERNAL_ERROR, + VerifyPhoneNumberResponse(emptyArray(), Bundle()), + ApiMetadata.DEFAULT + ) + } + } + + ReadCallbackMode.TYPED -> { + callbacks.onPhoneNumberVerificationsCompleted( + Status.INTERNAL_ERROR, + VerifyPhoneNumberResponse(emptyArray(), Bundle()), + ApiMetadata.DEFAULT + ) + } + } + } +} + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +private suspend fun runVerificationFlow( + context: Context, + request: VerifyPhoneNumberRequest, + callingPackage: String, + callbacks: IConstellationCallbacks, + legacyCallback: Boolean +) { + val sessionId = UUID.randomUUID().toString() + + val imsiToInfoMap = buildImsiToSubscriptionInfoMap(context) + val buildContext = buildRequestContext(context) + val syncRequest = SyncRequest( + context, + sessionId, + request, + buildContext, + imsiToInfoMap = imsiToInfoMap, + includeClientAuth = ConstellationStateStore.isPublicKeyAcked(context), + callingPackage = callingPackage + ) + val (verifications, isPublicKeyAcked) = executeSyncFlow( + context, + sessionId, + request, + syncRequest, + buildContext, + imsiToInfoMap + ) + if (isPublicKeyAcked) { + ConstellationStateStore.setPublicKeyAcked(context, true) + } + + if (legacyCallback) { + callbacks.onPhoneNumberVerified( + Status.SUCCESS, + verifications.mapNotNull { it.toLegacyPhoneNumberInfoOrNull() }, + ApiMetadata.DEFAULT + ) + } else { + callbacks.onPhoneNumberVerificationsCompleted( + Status.SUCCESS, + VerifyPhoneNumberResponse(verifications, Bundle()), + ApiMetadata.DEFAULT + ) + } +} + +private fun PhoneNumberVerification.toLegacyPhoneNumberInfoOrNull(): PhoneNumberInfo? { + if (verificationStatus != Verification.Status.STATUS_VERIFIED.value || phoneNumber.isNullOrEmpty()) { + return null + } + val extras = Bundle(this.extras ?: Bundle.EMPTY).apply { + verificationToken?.let { putString("id_token", it) } + } + return PhoneNumberInfo(1, phoneNumber, timestampMillis, extras) +} + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +private suspend fun executeSyncFlow( + context: Context, + sessionId: String, + request: VerifyPhoneNumberRequest, + syncRequest: SyncRequest, + buildContext: RequestBuildContext, + imsiToInfoMap: Map +): Pair, Boolean> = withContext(Dispatchers.IO) { + Log.d(TAG, "Sending Sync request") + val syncResponse = try { + RpcClient.phoneDeviceVerificationClient.Sync().execute(syncRequest) + } catch (e: GrpcException) { + if (e.grpcStatus == GrpcStatus.PERMISSION_DENIED || + e.grpcStatus == GrpcStatus.UNAUTHENTICATED + ) { + Log.w(TAG, "Suspicious client status ${e.grpcStatus.name}. Clearing DroidGuard cache...") + ConstellationStateStore.clearDroidGuardToken(context) + } + throw e + } + Log.d(TAG, "Sync response: ${syncResponse.responses.size} verifications") + ConstellationStateStore.storeSyncResponse(context, syncResponse) + + val isPublicKeyAcked = syncResponse.header_?.status?.code == 1 + val imsiToSlotMap = imsiToInfoMap.mapValues { it.value.simSlotIndex } + val requestedImsis = request.targetedSims.map { it.imsi }.toSet() + + val verifications = syncResponse.responses.mapNotNull { result -> + val verification = result.verification ?: Verification() + val verificationImsis = verification.association?.sim?.sim_info?.imsi.orEmpty() + if (requestedImsis.isNotEmpty() && verificationImsis.none { it in requestedImsis }) { + Log.w( + TAG, + "Skipping verification for IMSIs=$verificationImsis because it does not match requested IMSIs=$requestedImsis" + ) + return@mapNotNull null + } + + val finalVerification = if (verification.getState() == Verification.State.PENDING) { + ChallengeProcessor.process( + context, + sessionId, + imsiToInfoMap, + buildContext, + verification + ) + } else { + verification + } + + finalVerification.toClientVerification(imsiToSlotMap) + }.toTypedArray() + + if (isPublicKeyAcked) { + Log.d(TAG, "Server acknowledged client public key") + } + + verifications to isPublicKeyAcked +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumberApiPhenotypes.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumberApiPhenotypes.kt new file mode 100644 index 0000000000..93faca2dd1 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumberApiPhenotypes.kt @@ -0,0 +1,37 @@ +package org.microg.gms.constellation + +object VerifyPhoneNumberApiPhenotypes { + val PACKAGES_ALLOWED_TO_CALL = listOf( + "com.google.android.gms", + "com.google.android.apps.messaging", + "com.google.android.ims", + "com.google.android.apps.tachyon", + "com.google.android.dialer", + "com.google.android.apps.nbu.paisa.user.dev", + "com.google.android.apps.nbu.paisa.user.qa", + "com.google.android.apps.nbu.paisa.user.teamfood2", + "com.google.android.apps.nbu.paisa.user.partner", + "com.google.android.apps.nbu.paisa.user", + "com.google.android.gms.constellation.getiidtoken", + "com.google.android.gms.constellation.ondemandconsent", + "com.google.android.gms.constellation.ondemandconsentv2", + "com.google.android.gms.constellation.readphonenumber", + "com.google.android.gms.constellation.verifyphonenumberlite", + "com.google.android.gms.constellation.verifyphonenumber", + "com.google.android.gms.test", + "com.google.android.apps.stargate", + "com.google.android.gms.firebase.fpnv", + "com.google.firebase.pnv.testapp", + "com.google.firebase.pnv" + ) + val POLICY_IDS_ALLOWED_FOR_LOCAL_READ = listOf("emergency_location") + val READ_ONLY_POLICY_IDS = listOf( + "business_voice", + "verifiedsmsconsent", + "hint", + "nearbysharing" + ) + const val ENABLE_CLIENT_SIGNATURE = true + const val ENABLE_LOCAL_READ_FLOW = true + const val ENABLE_READ_FLOW = true +} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/ClientInfoBuilder.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/ClientInfoBuilder.kt new file mode 100644 index 0000000000..99e224cb3d --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/ClientInfoBuilder.kt @@ -0,0 +1,173 @@ +package org.microg.gms.constellation.proto.builders + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.util.Log +import androidx.annotation.RequiresPermission +import androidx.core.content.edit +import androidx.core.content.getSystemService +import com.google.android.gms.tasks.await +import okio.ByteString.Companion.toByteString +import org.microg.gms.common.Constants +import org.microg.gms.constellation.AuthManager +import org.microg.gms.constellation.ConstellationStateStore +import org.microg.gms.constellation.GServices +import org.microg.gms.constellation.proto.ClientInfo +import org.microg.gms.constellation.proto.CountryInfo +import org.microg.gms.constellation.proto.DeviceID +import org.microg.gms.constellation.proto.DeviceType +import org.microg.gms.constellation.proto.DroidGuardSignals +import org.microg.gms.constellation.proto.GaiaSignals +import org.microg.gms.constellation.proto.GaiaToken +import org.microg.gms.constellation.proto.NetworkSignal +import org.microg.gms.constellation.proto.SimOperatorInfo +import org.microg.gms.constellation.proto.UserProfileType +import java.util.Locale + +private const val TAG = "ClientInfoBuilder" +private const val PREFS_NAME = "constellation_prefs" + +@RequiresPermission(Manifest.permission.GET_ACCOUNTS) +@SuppressLint("HardwareIds") +suspend operator fun ClientInfo.Companion.invoke(context: Context, iidToken: String): ClientInfo { + return ClientInfo( + context, + RequestBuildContext( + iidToken = iidToken, + gaiaTokens = GaiaToken.getList(context) + ) + ) +} + +@RequiresPermission(Manifest.permission.GET_ACCOUNTS) +@SuppressLint("HardwareIds") +suspend operator fun ClientInfo.Companion.invoke( + context: Context, + buildContext: RequestBuildContext +): ClientInfo { + val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) + val locale = Locale.getDefault().let { "${it.language}_${it.country}" } + val authManager = AuthManager.get(context) + + return ClientInfo( + device_id = DeviceID(context, buildContext.iidToken), + client_public_key = authManager.getOrCreateKeyPair().public.encoded.toByteString(), + locale = locale, + gmscore_version_number = Constants.GMS_VERSION_CODE / 1000, + gmscore_version = packageInfo.versionName ?: "", + android_sdk_version = Build.VERSION.SDK_INT, + user_profile_type = UserProfileType.REGULAR_USER, + gaia_tokens = buildContext.gaiaTokens, + country_info = CountryInfo(context), + connectivity_infos = NetworkSignal.getList(context), + model = Build.MODEL, + manufacturer = Build.MANUFACTURER, + partial_sim_infos = SimOperatorInfo.getList(context), + device_type = DeviceType.DEVICE_TYPE_PHONE, + is_wearable_standalone = false, + gaia_signals = GaiaSignals(context), + device_fingerprint = Build.FINGERPRINT, + droidguard_signals = DroidGuardSignals(context), + ) +} + +operator fun DeviceID.Companion.invoke(context: Context, iidToken: String): DeviceID { + val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + + var gmsAndroidId = prefs.getLong("gms_android_id", 0L) + if (gmsAndroidId == 0L) { + gmsAndroidId = GServices.getLong(context.contentResolver, "android_id", 0L) + if (gmsAndroidId == 0L) { + val androidIdStr = + android.provider.Settings.Secure.getString(context.contentResolver, "android_id") + gmsAndroidId = + androidIdStr?.toLongOrNull(16) ?: (Build.ID.hashCode() + .toLong() and 0x7FFFFFFFFFFFFFFFL) + } + prefs.edit { putLong("gms_android_id", gmsAndroidId) } + } + + val userSerial = try { + val userManager = context.getSystemService() + userManager?.getSerialNumberForUser(android.os.Process.myUserHandle()) ?: 0L + } catch (_: Exception) { + 0L + } + + var primaryDeviceId = prefs.getLong("primary_device_id", 0L) + val isSystemUser = try { + val userManager = context.getSystemService() + userManager?.isSystemUser ?: true + } catch (_: Exception) { + true + } + if (primaryDeviceId == 0L && isSystemUser) { + primaryDeviceId = gmsAndroidId + prefs.edit { putLong("primary_device_id", primaryDeviceId) } + } + + return DeviceID( + iid_token = iidToken, + primary_device_id = primaryDeviceId, + user_serial = userSerial, + gms_android_id = gmsAndroidId + ) +} + +operator fun CountryInfo.Companion.invoke(context: Context): CountryInfo { + val simCountries = mutableListOf() + val networkCountries = mutableListOf() + + try { + val sm = context.getSystemService() + val tm = context.getSystemService() + + sm?.activeSubscriptionInfoList?.forEach { info -> + val targetTM = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tm?.createForSubscriptionId(info.subscriptionId) + } else tm + + targetTM?.networkCountryIso?.let { networkCountries.add(it.lowercase()) } + info.countryIso?.let { simCountries.add(it.lowercase()) } + } + } catch (e: SecurityException) { + Log.w(TAG, "No permission to access country info", e) + } + + if (simCountries.isEmpty()) { + val tm = context.getSystemService() + val simCountry = tm?.simCountryIso?.lowercase()?.takeIf { it.isNotBlank() } + if (simCountry != null) simCountries.add(simCountry) + } + if (networkCountries.isEmpty()) { + val tm = context.getSystemService() + val networkCountry = tm?.networkCountryIso?.lowercase()?.takeIf { it.isNotBlank() } + if (networkCountry != null) networkCountries.add(networkCountry) + } + + return CountryInfo(sim_countries = simCountries, network_countries = networkCountries) +} + +suspend operator fun DroidGuardSignals.Companion.invoke(context: Context): DroidGuardSignals? { + val cachedToken = ConstellationStateStore.loadDroidGuardToken(context) + if (!cachedToken.isNullOrBlank()) { + return DroidGuardSignals(droidguard_token = cachedToken, droidguard_result = "") + } + + return try { + val client = com.google.android.gms.droidguard.DroidGuard.getClient(context) + val data = mapOf( + "package_name" to context.packageName, + "timestamp" to System.currentTimeMillis().toString() + ) + val result = client.getResults("constellation_verify", data, null).await() + DroidGuardSignals(droidguard_result = result, droidguard_token = "") + } catch (e: Exception) { + Log.w(TAG, "DroidGuard generation failed", e) + null + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/CommonBuilders.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/CommonBuilders.kt new file mode 100644 index 0000000000..b05e0dc40c --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/CommonBuilders.kt @@ -0,0 +1,70 @@ +package org.microg.gms.constellation.proto.builders + +import android.Manifest +import android.content.Context +import android.os.Bundle +import androidx.annotation.RequiresPermission +import okio.ByteString.Companion.toByteString +import org.microg.gms.constellation.AuthManager +import org.microg.gms.constellation.proto.AuditToken +import org.microg.gms.constellation.proto.AuditTokenMetadata +import org.microg.gms.constellation.proto.AuditUuid +import org.microg.gms.constellation.proto.ClientInfo +import org.microg.gms.constellation.proto.DeviceID +import org.microg.gms.constellation.proto.Param +import org.microg.gms.constellation.proto.RequestHeader +import org.microg.gms.constellation.proto.RequestTrigger + +fun Param.Companion.getList(extras: Bundle?): List { + if (extras == null) return emptyList() + val params = mutableListOf() + val ignoreKeys = setOf("consent_variant_key", "consent_trigger_key", "gaia_access_token") + for (key in extras.keySet()) { + if (key !in ignoreKeys) { + extras.get(key)?.toString()?.let { value -> + params.add(Param(key = key, value_ = value)) + } + } + } + return params +} + +fun AuditToken.Companion.generate(): AuditToken { + val uuid = java.util.UUID.randomUUID() + return AuditToken( + metadata = AuditTokenMetadata( + uuid = AuditUuid( + uuid_msb = uuid.mostSignificantBits, + uuid_lsb = uuid.leastSignificantBits + ) + ) + ) +} + +@RequiresPermission(Manifest.permission.GET_ACCOUNTS) +suspend operator fun RequestHeader.Companion.invoke( + context: Context, + sessionId: String, + buildContext: RequestBuildContext, + triggerType: RequestTrigger.Type = RequestTrigger.Type.CONSENT_API_TRIGGER, + includeClientAuth: Boolean = false +): RequestHeader { + val authManager = if (includeClientAuth) AuthManager.get(context) else null + val clientAuth = if (includeClientAuth) { + val (signature, timestamp) = authManager!!.signIidToken(buildContext.iidToken) + org.microg.gms.constellation.proto.ClientAuth( + device_id = DeviceID(context, buildContext.iidToken), + signature = signature.toByteString(), + sign_timestamp = timestamp + ) + } else { + null + } + + return RequestHeader( + client_info = ClientInfo(context, buildContext), + client_auth = clientAuth, + session_id = sessionId, + trigger = RequestTrigger(type = triggerType) + ) +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/GaiaInfoBuilder.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/GaiaInfoBuilder.kt new file mode 100644 index 0000000000..dd2b34b42d --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/GaiaInfoBuilder.kt @@ -0,0 +1,74 @@ +package org.microg.gms.constellation.proto.builders + +import android.Manifest +import android.accounts.AccountManager +import android.content.Context +import android.util.Log +import androidx.annotation.RequiresPermission +import com.squareup.wire.Instant +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.microg.gms.constellation.proto.GaiaAccountSignalType +import org.microg.gms.constellation.proto.GaiaSignalEntry +import org.microg.gms.constellation.proto.GaiaSignals +import org.microg.gms.constellation.proto.GaiaToken +import java.security.MessageDigest +import kotlin.math.abs +import kotlin.math.absoluteValue + +private const val TAG = "GaiaInfoBuilder" + +@RequiresPermission(Manifest.permission.GET_ACCOUNTS) +operator fun GaiaSignals.Companion.invoke(context: Context): GaiaSignals? { + val entries = mutableListOf() + try { + val accounts = AccountManager.get(context).getAccountsByType("com.google") + val md = MessageDigest.getInstance("SHA-256") + + for (account in accounts) { + val hash = md.digest(account.name.toByteArray(Charsets.UTF_8)) + val number = + hash.take(8).fold(0L) { acc, byte -> (acc shl 8) or (byte.toLong() and 0xFF) } + val obfuscatedId = try { + number.absoluteValue.toString() + } catch (_: Exception) { + abs(account.name.hashCode().toLong()).toString() + } + + entries.add( + GaiaSignalEntry( + gaia_id = obfuscatedId, + signal_type = GaiaAccountSignalType.GAIA_ACCOUNT_SIGNAL_AUTHENTICATED, + timestamp = Instant.ofEpochMilli(System.currentTimeMillis()) + ) + ) + } + } catch (e: Exception) { + Log.w(TAG, "Could not build Gaia signals", e) + } + return if (entries.isNotEmpty()) GaiaSignals(gaia_signals = entries) else null +} + +@Suppress("DEPRECATION") +suspend fun GaiaToken.Companion.getList(context: Context): List = + withContext(Dispatchers.IO) { + val gaiaTokens = mutableListOf() + try { + val accounts = AccountManager.get(context).getAccountsByType("com.google") + if (accounts.isNotEmpty()) { + val future = AccountManager.get(context).getAuthToken( + accounts.first(), + "oauth2:https://www.googleapis.com/auth/numberer", + null, + false, + null, + null + ) + val token = future.result?.getString(AccountManager.KEY_AUTHTOKEN) + if (!token.isNullOrBlank()) gaiaTokens.add(GaiaToken(token = token)) + } + } catch (e: Exception) { + Log.w(TAG, "Could not retrieve Gaia tokens", e) + } + gaiaTokens + } diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/RequestBuildContext.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/RequestBuildContext.kt new file mode 100644 index 0000000000..5ac41bba97 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/RequestBuildContext.kt @@ -0,0 +1,20 @@ +package org.microg.gms.constellation.proto.builders + +import android.content.Context +import org.microg.gms.constellation.AuthManager +import org.microg.gms.constellation.proto.GaiaToken + +data class RequestBuildContext( + val iidToken: String, + val gaiaTokens: List +) + +suspend fun buildRequestContext( + context: Context, + authManager: AuthManager = AuthManager.get(context) +): RequestBuildContext { + return RequestBuildContext( + iidToken = authManager.getIidToken(), + gaiaTokens = GaiaToken.getList(context) + ) +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/SyncRequestBuilder.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/SyncRequestBuilder.kt new file mode 100644 index 0000000000..7d86e3964f --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/SyncRequestBuilder.kt @@ -0,0 +1,190 @@ +package org.microg.gms.constellation.proto.builders + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Build +import android.telephony.SubscriptionInfo +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.content.getSystemService +import com.google.android.gms.constellation.VerifyPhoneNumberRequest +import org.microg.gms.constellation.ConstellationStateStore +import org.microg.gms.constellation.proto.ChallengePreference +import org.microg.gms.constellation.proto.ChallengePreferenceMetadata +import org.microg.gms.constellation.proto.IdTokenRequest +import org.microg.gms.constellation.proto.Param +import org.microg.gms.constellation.proto.RequestHeader +import org.microg.gms.constellation.proto.RequestTrigger +import org.microg.gms.constellation.proto.SIMAssociation +import org.microg.gms.constellation.proto.SIMSlotInfo +import org.microg.gms.constellation.proto.SyncRequest +import org.microg.gms.constellation.proto.TelephonyInfo +import org.microg.gms.constellation.proto.TelephonyPhoneNumberType +import org.microg.gms.constellation.proto.Verification +import org.microg.gms.constellation.proto.VerificationAssociation +import org.microg.gms.constellation.proto.VerificationParam +import org.microg.gms.constellation.proto.VerificationPolicy + +private const val TAG = "SyncRequestBuilder" + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +@SuppressLint("HardwareIds", "MissingPermission") +fun buildImsiToSubscriptionInfoMap(context: Context): Map { + val subscriptionManager = + context.getSystemService() ?: return emptyMap() + val telephonyManager = + context.getSystemService() ?: return emptyMap() + val map = mutableMapOf() + + try { + subscriptionManager.activeSubscriptionInfoList?.forEach { info -> + val subsTelephonyManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + telephonyManager.createForSubscriptionId(info.subscriptionId) + } else { + telephonyManager + } + subsTelephonyManager.subscriberId?.let { imsi -> + map[imsi] = info + } + } + } catch (e: SecurityException) { + Log.w(TAG, "No permission to read SIM info for SubscriptionInfo mapping", e) + } + return map +} + +@SuppressLint("MissingPermission") +fun getTelephonyPhoneNumbers( + context: Context, + subscriptionId: Int +): List { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return emptyList() + + val subscriptionManager = + context.getSystemService() ?: return emptyList() + + val sources = intArrayOf( + SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER, + SubscriptionManager.PHONE_NUMBER_SOURCE_UICC, + SubscriptionManager.PHONE_NUMBER_SOURCE_IMS + ) + + return try { + sources.map { source -> + val number = subscriptionManager.getPhoneNumber(subscriptionId, source) + if (number.isNotEmpty()) { + SIMAssociation.TelephonyPhoneNumber( + phone_number = number, + phone_number_type = when (source) { + SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER -> + TelephonyPhoneNumberType.PHONE_NUMBER_SOURCE_CARRIER + + SubscriptionManager.PHONE_NUMBER_SOURCE_UICC -> + TelephonyPhoneNumberType.PHONE_NUMBER_SOURCE_UICC + + SubscriptionManager.PHONE_NUMBER_SOURCE_IMS -> + TelephonyPhoneNumberType.PHONE_NUMBER_SOURCE_IMS + + else -> TelephonyPhoneNumberType.PHONE_NUMBER_SOURCE_UNSPECIFIED + } + ) + } else null + }.filterNotNull() + } catch (e: Exception) { + Log.w(TAG, "Error getting telephony phone numbers", e) + emptyList() + } +} + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +@SuppressLint("HardwareIds", "MissingPermission") +suspend operator fun SyncRequest.Companion.invoke( + context: Context, + sessionId: String, + request: VerifyPhoneNumberRequest, + includeClientAuth: Boolean = false, + callingPackage: String = context.packageName, + triggerType: RequestTrigger.Type = RequestTrigger.Type.TRIGGER_API_CALL +): SyncRequest { + val buildContext = buildRequestContext(context) + return SyncRequest( + context = context, + sessionId = sessionId, + request = request, + buildContext = buildContext, + includeClientAuth = includeClientAuth, + callingPackage = callingPackage, + triggerType = triggerType + ) +} + +@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) +@SuppressLint("HardwareIds", "MissingPermission") +suspend operator fun SyncRequest.Companion.invoke( + context: Context, + sessionId: String, + request: VerifyPhoneNumberRequest, + buildContext: RequestBuildContext, + imsiToInfoMap: Map = buildImsiToSubscriptionInfoMap(context), + includeClientAuth: Boolean = false, + callingPackage: String = context.packageName, + triggerType: RequestTrigger.Type = RequestTrigger.Type.TRIGGER_API_CALL +): SyncRequest { + val apiParamsList = Param.getList(request.extras) + + val verificationParams = request.targetedSims.map { + VerificationParam(key = it.imsi, value_ = it.phoneNumberHint) + } + + val structuredParams = VerificationPolicy( + policy_id = request.policyId, + max_verification_age_hours = request.timeout, + id_token_request = IdTokenRequest( + certificate_hash = request.idTokenRequest.idToken, + token_nonce = request.idTokenRequest.subscriberHash + ), + calling_package = callingPackage, + params = verificationParams + ) + + val verifications = imsiToInfoMap.map { (imsi, subscriptionInfo) -> + val subscriptionId = subscriptionInfo.subscriptionId + val slotIndex = subscriptionInfo.simSlotIndex + val phoneNumber = subscriptionInfo.number ?: "" + val iccid = subscriptionInfo.iccId ?: "" + + Verification( + status = Verification.Status.STATUS_NONE, + association = VerificationAssociation( + sim = SIMAssociation( + sim_info = SIMAssociation.SIMInfo( + imsi = listOf(imsi), + sim_readable_number = phoneNumber, + telephony_phone_number = getTelephonyPhoneNumbers(context, subscriptionId), + iccid = iccid + ), + gaia_tokens = buildContext.gaiaTokens, + sim_slot = SIMSlotInfo( + slot_index = slotIndex, + subscription_id = subscriptionId + ) + ) + ), + telephony_info = TelephonyInfo(context, subscriptionId), + structured_api_params = structuredParams, + api_params = apiParamsList, + challenge_preference = ChallengePreference( + capabilities = request.verificationMethods, + metadata = ChallengePreferenceMetadata() + ) + ) + } + + return SyncRequest( + verifications = verifications, + header_ = RequestHeader(context, sessionId, buildContext, triggerType, includeClientAuth), + verification_tokens = ConstellationStateStore.loadVerificationTokens(context) + ) +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/TelephonyInfoBuilder.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/TelephonyInfoBuilder.kt new file mode 100644 index 0000000000..62e4ada9b7 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/TelephonyInfoBuilder.kt @@ -0,0 +1,143 @@ +package org.microg.gms.constellation.proto.builders + +import android.annotation.SuppressLint +import android.content.Context +import android.net.ConnectivityManager +import android.os.Build +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.util.Base64 +import android.util.Log +import androidx.core.content.getSystemService +import org.microg.gms.constellation.proto.NetworkSignal +import org.microg.gms.constellation.proto.SimNetworkInfo +import org.microg.gms.constellation.proto.SimOperatorInfo +import org.microg.gms.constellation.proto.TelephonyInfo +import java.security.MessageDigest + +private const val TAG = "TelephonyInfoBuilder" + +@SuppressLint("HardwareIds") +operator fun TelephonyInfo.Companion.invoke(context: Context, subscriptionId: Int): TelephonyInfo { + val tm = context.getSystemService() + val targetTm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && subscriptionId >= 0) { + tm?.createForSubscriptionId(subscriptionId) + } else tm + + val simState = when (targetTm?.simState) { + TelephonyManager.SIM_STATE_READY -> TelephonyInfo.SimState.SIM_STATE_READY + else -> TelephonyInfo.SimState.SIM_STATE_NOT_READY + } + + val phoneType = when (targetTm?.phoneType) { + TelephonyManager.PHONE_TYPE_GSM -> TelephonyInfo.PhoneType.PHONE_TYPE_GSM + TelephonyManager.PHONE_TYPE_CDMA -> TelephonyInfo.PhoneType.PHONE_TYPE_CDMA + TelephonyManager.PHONE_TYPE_SIP -> TelephonyInfo.PhoneType.PHONE_TYPE_SIP + else -> TelephonyInfo.PhoneType.PHONE_TYPE_UNKNOWN + } + + val networkRoaming = if (targetTm?.isNetworkRoaming == true) { + TelephonyInfo.RoamingState.ROAMING_ROAMING + } else { + TelephonyInfo.RoamingState.ROAMING_HOME + } + + val cm = context.getSystemService() + val activeNetwork = cm?.activeNetworkInfo + val connectivityState = when { + activeNetwork == null -> TelephonyInfo.ConnectivityState.CONNECTIVITY_UNKNOWN + activeNetwork.isRoaming -> TelephonyInfo.ConnectivityState.CONNECTIVITY_ROAMING + else -> TelephonyInfo.ConnectivityState.CONNECTIVITY_HOME + } + + val simInfo = SimNetworkInfo( + country_iso = targetTm?.simCountryIso?.lowercase() ?: "", + operator_ = targetTm?.simOperator ?: "", + operator_name = targetTm?.simOperatorName ?: "" + ) + + val networkInfo = SimNetworkInfo( + country_iso = targetTm?.networkCountryIso?.lowercase() ?: "", + operator_ = targetTm?.networkOperator ?: "", + operator_name = targetTm?.networkOperatorName ?: "" + ) + + return TelephonyInfo( + phone_type = phoneType, + group_id_level1 = targetTm?.groupIdLevel1 ?: "", + sim_info = simInfo, + network_info = networkInfo, + network_roaming = networkRoaming, + connectivity_state = connectivityState, + sms_capability = if (targetTm?.isSmsCapable == true) TelephonyInfo.SmsCapability.SMS_CAPABLE else TelephonyInfo.SmsCapability.SMS_NOT_CAPABLE, + sim_state = simState, + is_embedded = false + ) +} + +fun NetworkSignal.Companion.getList(context: Context): List { + val connectivityInfos = mutableListOf() + try { + val cm = context.getSystemService() + cm?.activeNetworkInfo?.let { networkInfo -> + val type = when (networkInfo.type) { + ConnectivityManager.TYPE_WIFI -> NetworkSignal.Type.TYPE_WIFI + ConnectivityManager.TYPE_MOBILE -> NetworkSignal.Type.TYPE_MOBILE + else -> NetworkSignal.Type.TYPE_UNKNOWN + } + val state = when { + networkInfo.isConnected -> NetworkSignal.State.STATE_CONNECTED + networkInfo.isConnectedOrConnecting && !networkInfo.isConnected -> NetworkSignal.State.STATE_CONNECTING + else -> NetworkSignal.State.STATE_DISCONNECTED + } + val availability = + if (networkInfo.isAvailable) NetworkSignal.Availability.AVAILABLE else NetworkSignal.Availability.NOT_AVAILABLE + + connectivityInfos.add( + NetworkSignal( + type = type, + availability = availability, + state = state + ) + ) + } + } catch (e: Exception) { + Log.w(TAG, "Could not retrieve network info", e) + } + return connectivityInfos +} + +fun SimOperatorInfo.Companion.getList(context: Context): List { + val infos = mutableListOf() + try { + val sm = context.getSystemService() + val tm = context.getSystemService() + + sm?.activeSubscriptionInfoList?.forEach { info -> + val targetTM = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + tm?.createForSubscriptionId(info.subscriptionId) + } else tm + + targetTM?.subscriberId?.let { imsi -> + val md = MessageDigest.getInstance("SHA-256") + val hash = Base64.encodeToString( + md.digest(imsi.toByteArray(Charsets.UTF_8)), + Base64.NO_WRAP + ) + + val simOperator = targetTM.simOperator ?: "" + infos.add( + SimOperatorInfo( + sim_operator = simOperator, + imsi_hash = hash + ) + ) + } + } + } catch (e: SecurityException) { + Log.e(TAG, "No permission to access SIM info for operator list", e) + } catch (e: Exception) { + Log.e(TAG, "Error hashing IMSI", e) + } + return infos +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/CarrierIdVerifier.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/CarrierIdVerifier.kt new file mode 100644 index 0000000000..b7c138638f --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/CarrierIdVerifier.kt @@ -0,0 +1,84 @@ +package org.microg.gms.constellation.verification + +import android.content.Context +import android.os.Build +import android.telephony.TelephonyManager +import android.util.Log +import androidx.core.content.getSystemService +import org.microg.gms.constellation.proto.CarrierIdChallengeResponse +import org.microg.gms.constellation.proto.CarrierIdError +import org.microg.gms.constellation.proto.Challenge +import org.microg.gms.constellation.proto.ChallengeResponse + +private const val TAG = "CarrierIdVerifier" + +class CarrierIdVerifier(private val context: Context, private val subId: Int) { + fun verify(challenge: Challenge?): ChallengeResponse? { + val carrierChallenge = challenge?.carrier_id_challenge ?: return null + val challengeData = carrierChallenge.isim_request.takeIf { it.isNotEmpty() } + ?: return failure( + CarrierIdError.CARRIER_ID_ERROR_UNKNOWN_ERROR, + "Carrier challenge data missing" + ) + if (subId == -1) return failure( + CarrierIdError.CARRIER_ID_ERROR_NO_SIM, + "No active subscription for carrier auth" + ) + + val appType = carrierChallenge.app_type + val authType = carrierChallenge.auth_type + + return try { + val telephonyManager = context.getSystemService() + ?: return failure( + CarrierIdError.CARRIER_ID_ERROR_NOT_SUPPORTED, + "TelephonyManager unavailable" + ) + val targetManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + telephonyManager.createForSubscriptionId(subId) + } else { + telephonyManager + } + + val response = targetManager.getIccAuthentication(appType, authType, challengeData) + if (response.isNullOrEmpty()) { + failure(CarrierIdError.CARRIER_ID_ERROR_NULL_RESPONSE, "Null ISIM response") + } else { + ChallengeResponse( + carrier_id_response = CarrierIdChallengeResponse( + isim_response = response, + carrier_id_error = CarrierIdError.CARRIER_ID_ERROR_NO_ERROR + ) + ) + } + } catch (e: SecurityException) { + Log.w(TAG, "Unable to read subscription for carrier auth", e) + failure( + CarrierIdError.CARRIER_ID_ERROR_UNABLE_TO_READ_SUBSCRIPTION, + e.message ?: "SecurityException" + ) + } catch (e: UnsupportedOperationException) { + Log.w(TAG, "Carrier auth API unavailable", e) + failure( + CarrierIdError.CARRIER_ID_ERROR_NOT_SUPPORTED, + e.message ?: "UnsupportedOperationException" + ) + } catch (e: Exception) { + Log.e(TAG, "Carrier auth failed", e) + failure( + CarrierIdError.CARRIER_ID_ERROR_REFLECTION_ERROR, + e.message ?: "Reflection or platform error" + ) + } + } + + private fun failure(status: CarrierIdError, message: String): ChallengeResponse { + Log.w(TAG, message) + return ChallengeResponse( + carrier_id_response = CarrierIdChallengeResponse( + isim_response = "", + carrier_id_error = status + ) + ) + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ChallengeProcessor.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ChallengeProcessor.kt new file mode 100644 index 0000000000..e731f5112d --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ChallengeProcessor.kt @@ -0,0 +1,152 @@ +package org.microg.gms.constellation.verification + +import android.content.Context +import android.telephony.SubscriptionInfo +import android.util.Log +import com.squareup.wire.GrpcException +import com.squareup.wire.GrpcStatus +import org.microg.gms.constellation.ConstellationStateStore +import org.microg.gms.constellation.RpcClient +import org.microg.gms.constellation.getState +import org.microg.gms.constellation.proto.ChallengeResponse +import org.microg.gms.constellation.proto.ProceedRequest +import org.microg.gms.constellation.proto.RequestHeader +import org.microg.gms.constellation.proto.RequestTrigger +import org.microg.gms.constellation.proto.Verification +import org.microg.gms.constellation.proto.VerificationMethod +import org.microg.gms.constellation.proto.builders.RequestBuildContext +import org.microg.gms.constellation.proto.builders.invoke + +object ChallengeProcessor { + private const val TAG = "ChallengeProcessor" + private const val MAX_PROCEED_ROUNDS = 16 + + private fun challengeTimeRemainingMillis(verification: Verification): Long? { + val expiry = verification.pending_verification_info?.challenge?.expiry_time ?: return null + val targetMillis = expiry.timestamp?.toEpochMilli() ?: return null + val referenceMillis = expiry.now?.toEpochMilli() ?: return null + return targetMillis - referenceMillis + } + + suspend fun process( + context: Context, + sessionId: String, + imsiToInfoMap: Map, + buildContext: RequestBuildContext, + verification: Verification, + ): Verification { + var currentVerification = verification + + for (attempt in 1..MAX_PROCEED_ROUNDS) { + if (currentVerification.getState() != Verification.State.PENDING) { + Log.d( + TAG, + "Verification state: ${currentVerification.getState()}. Stopping sequential verification." + ) + return currentVerification + } + + val challenge = currentVerification.pending_verification_info?.challenge + if (challenge == null) { + Log.w( + TAG, + "Attempt $attempt: Pending verification but no challenge found. Stopping." + ) + return currentVerification + } + + val challengeId = challenge.challenge_id?.id ?: "" + val remainingMillis = challengeTimeRemainingMillis(currentVerification) + if (remainingMillis != null && remainingMillis <= 0L) { + Log.w(TAG, "Attempt $attempt: Challenge $challengeId expired before proceed") + return currentVerification + } + Log.d( + TAG, + "Attempt $attempt: Solving challenge ID: $challengeId, Type: ${challenge.type}" + ) + + val challengeImsi = currentVerification.association?.sim?.sim_info?.imsi?.firstOrNull() + val info = imsiToInfoMap[challengeImsi] + val subId = info?.subscriptionId ?: -1 + + val challengeResponse: ChallengeResponse? = when { + challenge.type == VerificationMethod.TS43 -> Ts43Verifier( + context, + subId + ).verify(challenge.ts43_challenge) + + challenge.type == VerificationMethod.CARRIER_ID && challenge.ts43_challenge != null -> + Ts43Verifier(context, subId).verify(challenge.ts43_challenge) + + challenge.type == VerificationMethod.CARRIER_ID -> + CarrierIdVerifier(context, subId).verify(challenge) + + challenge.type == VerificationMethod.MT_SMS -> MtSmsVerifier( + context, + subId + ).verify(challenge.mt_challenge) + + challenge.type == VerificationMethod.MO_SMS -> MoSmsVerifier( + context, + subId + ).verify(challenge.mo_challenge) + + challenge.type == VerificationMethod.REGISTERED_SMS -> + RegisteredSmsVerifier(context, subId).verify(challenge.registered_sms_challenge) + + challenge.type == VerificationMethod.FLASH_CALL -> { + Log.w(TAG, "Flash call verification is unavailable on this build") + null + } + + else -> { + Log.w(TAG, "Unsupported verification method: ${challenge.type}") + null + } + } + + if (challengeResponse != null) { + Log.d(TAG, "Attempt $attempt: Challenge successfully solved. Proceeding...") + val proceedHeader = RequestHeader( + context, + sessionId, + buildContext, + RequestTrigger.Type.TRIGGER_API_CALL, + includeClientAuth = true + ) + val proceedRequest = ProceedRequest( + verification = currentVerification, + challenge_response = challengeResponse, + header_ = proceedHeader + ) + val proceedResponse = try { + RpcClient.phoneDeviceVerificationClient.Proceed().execute(proceedRequest) + } catch (e: GrpcException) { + if (e.grpcStatus == GrpcStatus.PERMISSION_DENIED || + e.grpcStatus == GrpcStatus.UNAUTHENTICATED + ) { + Log.w( + TAG, + "Suspicious client status ${e.grpcStatus.name}. Clearing DroidGuard cache..." + ) + ConstellationStateStore.clearDroidGuardToken(context) + } + throw e + } + ConstellationStateStore.storeProceedResponse(context, proceedResponse) + currentVerification = proceedResponse.verification ?: currentVerification + } else { + Log.w( + TAG, + "Attempt $attempt: Challenge verification failed or returned no response." + ) + // GMS continues looping if IMSI doesn't match or other issues, but here we return to avoid infinite retries if verifier is broken + return currentVerification + } + } + + Log.w(TAG, "Exhausted all $MAX_PROCEED_ROUNDS proceed rounds, record is still pending.") + return currentVerification + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MoSmsVerifier.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MoSmsVerifier.kt new file mode 100644 index 0000000000..f9a6f6bc4a --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MoSmsVerifier.kt @@ -0,0 +1,210 @@ +package org.microg.gms.constellation.verification + +import android.Manifest +import android.app.PendingIntent +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageManager +import android.os.Build +import android.telephony.SmsManager +import android.telephony.SubscriptionManager +import android.util.Log +import androidx.annotation.RequiresPermission +import androidx.core.content.getSystemService +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull +import org.microg.gms.constellation.proto.ChallengeResponse +import org.microg.gms.constellation.proto.MoChallenge +import org.microg.gms.constellation.proto.MOChallengeResponseData +import java.util.UUID +import kotlin.coroutines.resume + +private const val TAG = "MoSmsVerifier" +private const val ACTION_MO_SMS_SENT = "org.microg.gms.constellation.MO_SMS_SENT" + +class MoSmsVerifier(private val context: Context, private val subId: Int) { + suspend fun verify(challenge: MoChallenge?): ChallengeResponse? { + if (challenge == null) return null + if (challenge.proxy_number.isEmpty() || challenge.sms.isEmpty()) { + return failureResponse(MOChallengeResponseData.Status.FAILED_TO_SEND_MO) + } + if (context.checkCallingOrSelfPermission(Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "SEND_SMS permission missing") + return failureResponse(MOChallengeResponseData.Status.FAILED_TO_SEND_MO) + } + + val smsManager = resolveSmsManager() ?: return failureResponse( + if (subId != -1 && !isActiveSubscription(subId)) { + MOChallengeResponseData.Status.NO_ACTIVE_SUBSCRIPTION + } else { + MOChallengeResponseData.Status.NO_SMS_MANAGER + } + ) + + val port = challenge.data_sms_info?.destination_port ?: 0 + val isBinarySms = port > 0 + val action = if (isBinarySms) ACTION_MO_SMS_SENT else ACTION_MO_SMS_SENT + val messageId = UUID.randomUUID().toString() + val sentIntent = Intent(action).apply { + `package` = context.packageName + putExtra("message_id", messageId) + } + + val pendingIntent = PendingIntent.getBroadcast( + context, + messageId.hashCode(), + sentIntent, + PendingIntent.FLAG_ONE_SHOT or (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) + ) + + Log.d(TAG, "Sending MO SMS to ${challenge.proxy_number} with messageId: $messageId") + + return withTimeoutOrNull(30000) { + suspendCancellableCoroutine { continuation -> + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == action) { + val receivedId = intent.getStringExtra("message_id") + if (receivedId != messageId) return + + val resultCode = resultCode + val errorCode = intent.getIntExtra("errorCode", -1) + + Log.d(TAG, "MO SMS sent result: $resultCode, error: $errorCode") + + val status = when (resultCode) { + -1 -> MOChallengeResponseData.Status.COMPLETED + else -> MOChallengeResponseData.Status.FAILED_TO_SEND_MO + } + + try { + context.unregisterReceiver(this) + } catch (_: Exception) { + } + + if (continuation.isActive) { + continuation.resume( + ChallengeResponse( + mo_response = MOChallengeResponseData( + status = status, + sms_result_code = resultCode.toLong(), + sms_error_code = errorCode.toLong() + ) + ) + ) + } + } + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver( + receiver, + IntentFilter(action), + Context.RECEIVER_NOT_EXPORTED + ) + } else { + context.registerReceiver(receiver, IntentFilter(action)) + } + + continuation.invokeOnCancellation { + try { + context.unregisterReceiver(receiver) + } catch (_: Exception) { + } + } + + try { + if (isBinarySms) { + smsManager.sendDataMessage( + challenge.proxy_number, + null, + port.toShort(), + challenge.sms.encodeToByteArray(), + pendingIntent, + null + ) + } else { + smsManager.sendTextMessage( + challenge.proxy_number, + null, + challenge.sms, + pendingIntent, + null + ) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to initiate MO SMS send", e) + try { + context.unregisterReceiver(receiver) + } catch (_: Exception) { + } + if (continuation.isActive) { + continuation.resume( + ChallengeResponse( + mo_response = MOChallengeResponseData( + status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO + ) + ) + ) + } + } + } + } ?: ChallengeResponse( + mo_response = MOChallengeResponseData( + status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO + ) + ) + } + + private fun failureResponse(status: MOChallengeResponseData.Status): ChallengeResponse { + return ChallengeResponse( + mo_response = MOChallengeResponseData( + status = status + ) + ) + } + + @RequiresPermission(Manifest.permission.READ_PHONE_STATE) + private fun isActiveSubscription(subscriptionId: Int): Boolean { + if (subscriptionId == -1) return false + return try { + context.getSystemService()?.isActiveSubscriptionId(subscriptionId) + ?: false + } catch (e: Exception) { + Log.w(TAG, "Failed to query active subscription for $subscriptionId", e) + false + } + } + + private fun resolveSmsManager(): SmsManager? { + if (subId != -1 && !isActiveSubscription(subId)) { + return null + } + return try { + val manager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + context.getSystemService(SmsManager::class.java) + } else { + null + } + when { + subId != -1 && manager != null -> manager.createForSubscriptionId(subId) + manager != null -> manager + subId != -1 -> { + @Suppress("DEPRECATION") + SmsManager.getSmsManagerForSubscriptionId(subId) + } + + else -> { + @Suppress("DEPRECATION") + SmsManager.getDefault() + } + } + } catch (e: Exception) { + Log.e(TAG, "Failed to resolve SmsManager for subId: $subId", e) + null + } + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MtSmsVerifier.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MtSmsVerifier.kt new file mode 100644 index 0000000000..e757d60a07 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MtSmsVerifier.kt @@ -0,0 +1,95 @@ +package org.microg.gms.constellation.verification + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Build +import android.provider.Telephony +import android.util.Log +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.withTimeoutOrNull +import org.microg.gms.constellation.proto.ChallengeResponse +import org.microg.gms.constellation.proto.MTChallenge +import org.microg.gms.constellation.proto.MTChallengeResponseData +import kotlin.coroutines.resume + +private const val TAG = "MtSmsVerifier" + +class MtSmsVerifier(private val context: Context, private val subId: Int) { + suspend fun verify(challenge: MTChallenge?): ChallengeResponse? { + val expectedBody = challenge?.sms?.takeIf { it.isNotEmpty() } ?: return null + + Log.d(TAG, "Waiting for MT SMS containing challenge string") + + val result = withTimeoutOrNull(300_000) { + suspendCancellableCoroutine?> { continuation -> + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + try { + if (intent.action == Telephony.Sms.Intents.SMS_RECEIVED_ACTION) { + val receivedSubId = intent.getIntExtra( + "android.telephony.extra.SUBSCRIPTION_INDEX", + intent.getIntExtra("subscription", -1) + ) + if (subId != -1 && receivedSubId != -1 && receivedSubId != subId) { + return + } + val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent) + for (msg in messages) { + val body = msg.messageBody + if (body != null && body.contains(expectedBody)) { + Log.d( + TAG, + "Matching MT SMS received from ${msg.originatingAddress}" + ) + context.unregisterReceiver(this) + if (continuation.isActive) { + continuation.resume( + Pair( + body, + msg.originatingAddress ?: "" + ) + ) + } + return + } + } + } + } catch (e: Exception) { + Log.e(TAG, "Error in MT SMS receiver", e) + } + } + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver( + receiver, + IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION), + Context.RECEIVER_NOT_EXPORTED + ) + } else { + context.registerReceiver( + receiver, + IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) + ) + } + continuation.invokeOnCancellation { + try { + context.unregisterReceiver(receiver) + } catch (_: Exception) { + } + } + } + } + + return result?.let { (body, sender) -> + ChallengeResponse( + mt_response = MTChallengeResponseData( + sms = body, + sender = sender + ) + ) + } + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/RegisteredSmsVerifier.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/RegisteredSmsVerifier.kt new file mode 100644 index 0000000000..301527f0d7 --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/RegisteredSmsVerifier.kt @@ -0,0 +1,159 @@ +package org.microg.gms.constellation.verification + +import android.content.Context +import android.provider.Telephony +import android.telephony.SubscriptionManager +import android.telephony.TelephonyManager +import android.util.Log +import androidx.core.content.getSystemService +import okio.ByteString.Companion.toByteString +import org.microg.gms.constellation.VerificationSettingsPhenotypes +import org.microg.gms.constellation.proto.ChallengeResponse +import org.microg.gms.constellation.proto.RegisteredSmsChallenge +import org.microg.gms.constellation.proto.RegisteredSmsChallengeResponse +import org.microg.gms.constellation.proto.RegisteredSmsChallengeResponseItem +import org.microg.gms.constellation.proto.RegisteredSmsChallengeResponsePayload +import java.nio.charset.StandardCharsets +import java.security.MessageDigest +import java.util.concurrent.TimeUnit + +private const val TAG = "RegisteredSmsVerifier" + +class RegisteredSmsVerifier(private val context: Context, private val subId: Int) { + fun verify(challenge: RegisteredSmsChallenge?): ChallengeResponse? { + val expectedPayloads = challenge?.verified_senders + ?.map { it.phone_number_id.toByteArray() } + ?.filter { it.isNotEmpty() } + .orEmpty() + if (expectedPayloads.isEmpty()) return null + + val localNumbers = getLocalNumbers() + if (localNumbers.isEmpty()) { + Log.w(TAG, "No local phone numbers available for registered SMS verification") + return emptyResponse() + } + + val historyStart = System.currentTimeMillis() - + TimeUnit.HOURS.toMillis(VerificationSettingsPhenotypes.A2P_HISTORY_WINDOW_HOURS) + val bucketSizeMillis = (TimeUnit.HOURS.toMillis( + VerificationSettingsPhenotypes.A2P_SMS_SIGNAL_GRANULARITY_HRS + ) / 2).coerceAtLeast(1L) + + val responseItems = mutableListOf() + + try { + val cursor = context.contentResolver.query( + Telephony.Sms.Inbox.CONTENT_URI, + arrayOf("date", "address", "body", "sub_id"), + "date > ?", + arrayOf(historyStart.toString()), + "date DESC" + ) ?: return emptyResponse() + + cursor.use { + while (it.moveToNext()) { + val date = it.getLong(it.getColumnIndexOrThrow("date")) + val sender = it.getString(it.getColumnIndexOrThrow("address")) ?: continue + val body = it.getString(it.getColumnIndexOrThrow("body")) ?: continue + val messageSubId = runCatching { + it.getInt(it.getColumnIndexOrThrow("sub_id")) + }.getOrDefault(-1) + + val candidateNumbers = if (subId != -1 && messageSubId == subId) { + localNumbers + } else if (messageSubId == -1) { + localNumbers + } else { + getLocalNumbers(messageSubId).ifEmpty { localNumbers } + } + + val bucketStart = date - (date % bucketSizeMillis) + for (localNumber in candidateNumbers) { + val payload = computePayload(bucketStart, localNumber, sender, body) + if (expectedPayloads.any { expected -> expected.contentEquals(payload) }) { + responseItems += RegisteredSmsChallengeResponseItem( + payload = RegisteredSmsChallengeResponsePayload( + payload = payload.toByteString() + ) + ) + } + } + } + } + } catch (e: SecurityException) { + Log.w(TAG, "SMS inbox access denied", e) + return emptyResponse() + } catch (e: Exception) { + Log.e(TAG, "Registered SMS verification failed", e) + return emptyResponse() + } + + return ChallengeResponse( + registered_sms_response = RegisteredSmsChallengeResponse(items = responseItems.distinct()) + ) + } + + private fun emptyResponse(): ChallengeResponse { + return ChallengeResponse( + registered_sms_response = RegisteredSmsChallengeResponse(items = emptyList()) + ) + } + + private fun getLocalNumbers(targetSubId: Int = subId): List { + val numbers = linkedSetOf() + val subscriptionManager = + context.getSystemService() + val telephonyManager = context.getSystemService() + + try { + subscriptionManager?.activeSubscriptionInfoList.orEmpty().forEach { info -> + if (targetSubId != -1 && info.subscriptionId != targetSubId) return@forEach + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU && subscriptionManager != null) { + numbers += subscriptionManager.getPhoneNumber( + info.subscriptionId, + SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER + ) + numbers += subscriptionManager.getPhoneNumber( + info.subscriptionId, + SubscriptionManager.PHONE_NUMBER_SOURCE_UICC + ) + numbers += subscriptionManager.getPhoneNumber( + info.subscriptionId, + SubscriptionManager.PHONE_NUMBER_SOURCE_IMS + ) + } + val targetManager = + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + telephonyManager?.createForSubscriptionId(info.subscriptionId) + } else { + telephonyManager + } + numbers += targetManager?.line1Number.orEmpty() + numbers += info.number.orEmpty() + } + } catch (e: Exception) { + Log.w(TAG, "Unable to collect local phone numbers", e) + } + + return numbers.filter { it.isNotBlank() }.distinct() + } + + private fun computePayload( + bucketStart: Long, + localNumber: String, + sender: String, + body: String + ): ByteArray { + val digest = MessageDigest.getInstance("SHA-512") + digest.update(bucketStart.toString().toByteArray(StandardCharsets.UTF_8)) + digest.update(hashUtf8(localNumber)) + digest.update(hashUtf8(sender)) + digest.update(body.toByteArray(StandardCharsets.UTF_8)) + return digest.digest() + } + + private fun hashUtf8(value: String): ByteArray { + return MessageDigest.getInstance("SHA-512") + .digest(value.toByteArray(StandardCharsets.UTF_8)) + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/Ts43Verifier.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/Ts43Verifier.kt new file mode 100644 index 0000000000..4030817bcc --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/Ts43Verifier.kt @@ -0,0 +1,431 @@ +package org.microg.gms.constellation.verification + +import android.content.Context +import android.os.Build +import android.telephony.TelephonyManager +import android.util.Log +import androidx.core.content.getSystemService +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.RequestBody.Companion.toRequestBody +import org.json.JSONObject +import org.microg.gms.constellation.proto.ChallengeResponse +import org.microg.gms.constellation.proto.ClientChallengeResponse +import org.microg.gms.constellation.proto.OdsaOperation +import org.microg.gms.constellation.proto.ServerChallengeResponse +import org.microg.gms.constellation.proto.ServiceEntitlementRequest +import org.microg.gms.constellation.proto.Ts43Challenge +import org.microg.gms.constellation.proto.Ts43ChallengeResponse +import org.microg.gms.constellation.proto.Ts43ChallengeResponseError +import org.microg.gms.constellation.proto.Ts43ChallengeResponseStatus +import org.microg.gms.constellation.verification.ts43.EapAkaService +import org.microg.gms.constellation.verification.ts43.builder +import org.microg.gms.constellation.verification.ts43.userAgent +import org.xml.sax.InputSource +import java.io.IOException +import java.io.StringReader +import java.util.Locale +import java.util.concurrent.TimeUnit +import javax.xml.parsers.DocumentBuilderFactory +import kotlin.text.ifEmpty + +private const val TAG = "Ts43Verifier" + +class Ts43Verifier(private val context: Context, private val subId: Int) { + + private class Ts43ApiException( + val errorCode: Ts43ChallengeResponseError.Code, + val httpStatus: Int, + val requestType: Ts43ChallengeResponseError.RequestType, + cause: Throwable? = null + ) : Exception(cause) + + private val httpHistory = mutableListOf() + + private val telephonyManager: TelephonyManager by lazy { + val tm = requireNotNull(context.getSystemService()) { + "TelephonyManager unavailable" + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && subId >= 0) { + tm.createForSubscriptionId(subId) + } else tm + } + + private val eapAkaService by lazy { EapAkaService(telephonyManager) } + + private fun errorCode(rawCode: Int): Ts43ChallengeResponseError.Code = + Ts43ChallengeResponseError.Code.fromValue(rawCode) + ?: Ts43ChallengeResponseError.Code.TS43_ERROR_CODE_UNSPECIFIED + + private val okHttpClient by lazy { + OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .followRedirects(false) + .followSslRedirects(false) + .cookieJar(object : CookieJar { + private val cookieStore = mutableMapOf>() + override fun saveFromResponse(url: HttpUrl, cookies: List) { + val existing = cookieStore.getOrPut(url.host) { mutableListOf() } + for (cookie in cookies) { + existing.removeAll { + it.name == cookie.name && + it.domain == cookie.domain && + it.path == cookie.path + } + existing += cookie + } + } + + override fun loadForRequest(url: HttpUrl): List { + return cookieStore[url.host] + ?.filter { cookie -> cookie.matches(url) } + .orEmpty() + } + }) + .build() + } + + fun verify(challenge: Ts43Challenge?): ChallengeResponse? { + if (challenge == null) return null + httpHistory.clear() + + return try { + when { + challenge.client_challenge != null -> { + val op = challenge.client_challenge.operation + Log.d(TAG, "TS43: Client ODSA challenge route: ${op?.operation}") + if (op == null) { + buildFailureResponse( + challenge, + Ts43ChallengeResponseStatus.Code.TS43_STATUS_CHALLENGE_NOT_SET + ) + } else { + val requestPayload = buildOdsaRequestPayload( + challenge.entitlement_url, + challenge.eap_aka_realm, + challenge.service_entitlement_request, + challenge.app_id + ) + if (requestPayload == null) { + buildFailureResponse( + challenge, + Ts43ChallengeResponseStatus.Code.TS43_STATUS_INTERNAL_ERROR + ) + } else { + val responseBody = performOdsaRequest( + challenge.entitlement_url, + op, + requestPayload, + challenge.app_id, + Ts43ChallengeResponseError.RequestType.TS43_REQUEST_TYPE_GET_PHONE_NUMBER_API + ) + buildClientResponse(challenge, responseBody) + } + } + } + + challenge.server_challenge != null -> { + val op = challenge.server_challenge.operation + Log.d(TAG, "TS43: Server ODSA challenge route: ${op?.operation}") + if (op == null) { + buildFailureResponse( + challenge, + Ts43ChallengeResponseStatus.Code.TS43_STATUS_CHALLENGE_NOT_SET + ) + } else { + val requestPayload = buildOdsaRequestPayload( + challenge.entitlement_url, + challenge.eap_aka_realm, + challenge.service_entitlement_request, + challenge.app_id + ) + if (requestPayload == null) { + buildFailureResponse( + challenge, + Ts43ChallengeResponseStatus.Code.TS43_STATUS_INTERNAL_ERROR + ) + } else { + val responseBody = performOdsaRequest( + challenge.entitlement_url, + op, + requestPayload, + challenge.app_id, + Ts43ChallengeResponseError.RequestType.TS43_REQUEST_TYPE_ACQUIRE_TEMPORARY_TOKEN_API + ) + buildServerResponse(challenge, responseBody) + } + } + } + + else -> buildFailureResponse( + challenge, + Ts43ChallengeResponseStatus.Code.TS43_STATUS_CHALLENGE_NOT_SET + ) + } + } catch (e: Ts43ApiException) { + Log.w(TAG, "TS43 API failure requestType=${e.requestType} http=${e.httpStatus}", e) + buildApiErrorResponse(challenge, e) + } catch (e: NullPointerException) { + Log.e(TAG, "TS43 null failure", e) + buildFailureResponse( + challenge, + Ts43ChallengeResponseStatus.Code.TS43_STATUS_INTERNAL_ERROR + ) + } catch (e: RuntimeException) { + Log.e(TAG, "TS43 runtime failure", e) + buildFailureResponse( + challenge, + Ts43ChallengeResponseStatus.Code.TS43_STATUS_RUNTIME_ERROR + ) + } + } + + private fun performOdsaRequest( + entitlementUrl: String, + op: OdsaOperation, + req: ServiceEntitlementRequest?, + challengeAppId: String?, + requestType: Ts43ChallengeResponseError.RequestType + ): String { + val builder = op.builder(telephonyManager, req, currentAppIds(challengeAppId)) + val url = builder.buildBaseUrl(entitlementUrl) + val userAgent = req?.userAgent(context) ?: "PRD-TS43 OS-Android/${Build.VERSION.RELEASE}" + val accept = req?.accept_content_type?.takeIf { it.isNotEmpty() } ?: "application/json" + val acceptLanguage = Locale.getDefault().toLanguageTag().ifEmpty { "en-US" } + httpHistory += "GET $url" + + val request = Request.Builder() + .url(url) + .header("Accept", accept) + .header("User-Agent", userAgent) + .header("Accept-Language", acceptLanguage) + .build() + + return try { + val response = okHttpClient.newCall(request).execute() + httpHistory += "RESP ${response.code} ${request.url}" + if (response.isSuccessful) { + response.body?.string() + ?: throw Ts43ApiException( + errorCode = errorCode(32), + httpStatus = response.code, + requestType = requestType + ) + } else { + Log.w(TAG, "ODSA request failed: ${response.code} for ${op.operation}") + throw Ts43ApiException( + errorCode = errorCode(31), + httpStatus = response.code, + requestType = requestType + ) + } + } catch (e: IOException) { + Log.e(TAG, " Network error in ODSA request", e) + throw Ts43ApiException( + errorCode = errorCode(30), + httpStatus = -1, + requestType = requestType, + cause = e + ) + } + } + + private fun buildOdsaRequestPayload( + entitlementUrl: String, + eapAkaRealm: String?, + req: ServiceEntitlementRequest?, + challengeAppId: String? + ): ServiceEntitlementRequest? { + if (req == null) return null + if (req.authentication_token.isNotEmpty() || req.temporary_token.isNotEmpty()) return req + + val mccMnc = telephonyManager.simOperator ?: "" + val imsi = telephonyManager.subscriberId ?: "" + if (mccMnc.length < 5 || imsi.isEmpty()) return null + + val eapId = eapAkaService.buildEapId(mccMnc, imsi, eapAkaRealm) + val builder = req.builder(telephonyManager, eapId, currentAppIds(challengeAppId)) + + val initialUrl = builder.buildBaseUrl(entitlementUrl) + val postUrl = entitlementUrl + val userAgent = req.userAgent(context) + val acceptLanguage = Locale.getDefault().toLanguageTag().ifEmpty { "en-US" } + + // 1. Initial Identity Probe (GET) + var currentRequest = Request.Builder() + .url(initialUrl) + .header("Accept", "application/vnd.gsma.eap-relay.v1.0+json") + .header("User-Agent", userAgent) + .header("Accept-Language", acceptLanguage) + .build() + httpHistory += "GET $initialUrl" + + // 2. EAP Rounds Loop + for (round in 1..3) { + val response = try { + okHttpClient.newCall(currentRequest).execute() + } catch (e: IOException) { + Log.e(TAG, "Network error in EAP round $round", e) + throw Ts43ApiException( + errorCode = errorCode(30), + httpStatus = -1, + requestType = Ts43ChallengeResponseError.RequestType.TS43_REQUEST_TYPE_AUTH_API, + cause = e + ) + } + + val body = response.body?.string() ?: return null + httpHistory += "RESP ${response.code} ${currentRequest.url}" + if (!response.isSuccessful) { + Log.w(TAG, "EAP round $round failed with code ${response.code}") + throw Ts43ApiException( + errorCode = errorCode(31), + httpStatus = response.code, + requestType = Ts43ChallengeResponseError.RequestType.TS43_REQUEST_TYPE_AUTH_API + ) + } + + val token = extractAuthToken(body) + if (token != null) { + return req.copy(authentication_token = token) + } + + val eapRelayPacket = extractEapRelayPacket(body) + ?: throw Ts43ApiException( + errorCode = errorCode(32), + httpStatus = response.code, + requestType = Ts43ChallengeResponseError.RequestType.TS43_REQUEST_TYPE_AUTH_API + ) + + val akaResponse = + eapAkaService.performSimAkaAuth(eapRelayPacket, imsi, mccMnc) ?: return null + + val postBody = JSONObject().put("eap-relay-packet", akaResponse).toString() + currentRequest = Request.Builder() + .url(postUrl) + .header( + "Accept", + "application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap.connectivity-xml" + ) + .header("User-Agent", userAgent) + .header("Content-Type", "application/vnd.gsma.eap-relay.v1.0+json") + .header("Accept-Language", acceptLanguage) + .post( + postBody.toByteArray().toRequestBody( + "application/vnd.gsma.eap-relay.v1.0+json".toMediaType() + ) + ) + .build() + httpHistory += "POST $postUrl" + } + + return null + } + + private fun currentAppIds(challengeAppId: String? = null): List { + return listOfNotNull( + challengeAppId?.takeIf { it.isNotBlank() } + ).ifEmpty { + listOf("ap2014") + } + } + + private fun extractEapRelayPacket(body: String): String? { + return runCatching { JSONObject(body).optString("eap-relay-packet") } + .getOrNull() + ?.takeIf { it.isNotEmpty() } + } + + private fun extractAuthToken(body: String): String? { + runCatching { + JSONObject(body).optJSONObject("Token")?.optString("token") + }.getOrNull()?.takeIf { it.isNotEmpty() }?.let { return it } + + return runCatching { + val normalized = body + .replace("&", "&") + .replace("&amp;", "&") + .replace("\r\n", "\n") + val doc = DocumentBuilderFactory.newInstance() + .newDocumentBuilder() + .parse(InputSource(StringReader(normalized))) + val parms = doc.getElementsByTagName("parm") + for (i in 0 until parms.length) { + val node = parms.item(i) + val attrs = node.attributes ?: continue + val name = attrs.getNamedItem("name")?.nodeValue ?: continue + if (name == "token") { + return@runCatching attrs.getNamedItem("value")?.nodeValue + } + } + null + }.getOrNull()?.takeIf { it.isNotEmpty() } + } + + private fun buildServerResponse( + challenge: Ts43Challenge, + responseBody: String + ): ChallengeResponse { + return ChallengeResponse( + ts43_challenge_response = Ts43ChallengeResponse( + ts43_type = challenge.ts43_type, + server_challenge_response = ServerChallengeResponse( + acquire_temporary_token_response = responseBody + ), + http_history = httpHistory.toList() + ) + ) + } + + private fun buildClientResponse( + challenge: Ts43Challenge, + responseBody: String + ): ChallengeResponse { + return ChallengeResponse( + ts43_challenge_response = Ts43ChallengeResponse( + ts43_type = challenge.ts43_type, + client_challenge_response = ClientChallengeResponse( + get_phone_number_response = responseBody + ), + http_history = httpHistory.toList() + ) + ) + } + + private fun buildFailureResponse( + challenge: Ts43Challenge, + statusCode: Ts43ChallengeResponseStatus.Code + ): ChallengeResponse { + return ChallengeResponse( + ts43_challenge_response = Ts43ChallengeResponse( + ts43_type = challenge.ts43_type, + status = Ts43ChallengeResponseStatus(status_code = statusCode), + http_history = httpHistory.toList() + ) + ) + } + + private fun buildApiErrorResponse( + challenge: Ts43Challenge, + error: Ts43ApiException + ): ChallengeResponse { + return ChallengeResponse( + ts43_challenge_response = Ts43ChallengeResponse( + ts43_type = challenge.ts43_type, + status = Ts43ChallengeResponseStatus( + error = Ts43ChallengeResponseError( + error_code = error.errorCode, + http_status = error.httpStatus, + request_type = error.requestType + ) + ), + http_history = httpHistory.toList() + ) + ) + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/EapAkaService.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/EapAkaService.kt new file mode 100644 index 0000000000..9e271e5a4f --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/EapAkaService.kt @@ -0,0 +1,211 @@ +package org.microg.gms.constellation.verification.ts43 + +import android.os.Build +import android.telephony.TelephonyManager +import android.util.Base64 +import android.util.Log +import androidx.annotation.RequiresApi +import java.nio.ByteBuffer +import java.nio.charset.StandardCharsets +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +private const val TAG = "EapAkaService" + +class EapAkaService(private val telephonyManager: TelephonyManager) { + + companion object { + private const val EAP_CODE_REQUEST = 1 + private const val EAP_CODE_RESPONSE = 2 + + private const val EAP_TYPE_AKA = 23 + + private const val EAP_AKA_SUBTYPE_CHALLENGE = 1 + private const val EAP_AKA_SUBTYPE_SYNC_FAILURE = 4 + + private const val AT_RAND = 1 + private const val AT_AUTN = 2 + private const val AT_RES = 3 + private const val AT_AUTS = 4 + private const val AT_MAC = 11 + + private const val SIM_RES_SUCCESS = 0xDB.toByte() + private const val SIM_RES_SYNC_FAIL = 0xDC.toByte() + } + + @RequiresApi(Build.VERSION_CODES.N) + fun performSimAkaAuth(eapRelayBase64: String, imsi: String, mccMnc: String): String? { + val eapPacket = Base64.decode(eapRelayBase64, Base64.DEFAULT) + if (eapPacket.size < 12) return null + + val code = eapPacket[0].toInt() + val eapId = eapPacket[1] + val type = eapPacket[4].toInt() + val subtype = eapPacket[5].toInt() + + if (code != EAP_CODE_REQUEST || type != EAP_TYPE_AKA || subtype != EAP_AKA_SUBTYPE_CHALLENGE) { + Log.w(TAG, "Unexpected EAP packet: code=$code, type=$type, subtype=$subtype") + return null + } + + // Parse attributes (starting at offset 8) + var rand: ByteArray? = null + var autn: ByteArray? = null + + var offset = 8 + while (offset + 2 <= eapPacket.size) { + val attrType = eapPacket[offset].toInt() and 0xFF + val attrLen = (eapPacket[offset + 1].toInt() and 0xFF) * 4 + if (offset + attrLen > eapPacket.size || attrLen < 4) break + + when (attrType) { + AT_RAND -> { + if (attrLen >= 20) { + rand = ByteArray(16) + System.arraycopy(eapPacket, offset + 4, rand, 0, 16) + } + } + + AT_AUTN -> { + if (attrLen >= 20) { + autn = ByteArray(16) + System.arraycopy(eapPacket, offset + 4, autn, 0, 16) + } + } + } + offset += attrLen + if (rand != null && autn != null) break + } + + if (rand == null || autn == null) { + Log.e(TAG, "Missing RAND or AUTN in EAP-AKA challenge") + return null + } + + val challengeBytes = byteArrayOf(16) + rand + byteArrayOf(16) + autn + val challengeB64 = Base64.encodeToString(challengeBytes, Base64.NO_WRAP) + + val iccAuthResult = telephonyManager.getIccAuthentication( + TelephonyManager.APPTYPE_USIM, TelephonyManager.AUTHTYPE_EAP_AKA, challengeB64 + ) ?: run { + Log.e(TAG, "SIM returned null for AKA auth") + return null + } + + val iccBytes = Base64.decode(iccAuthResult, Base64.DEFAULT) + if (iccBytes.isEmpty()) return null + + return when (iccBytes[0]) { + SIM_RES_SUCCESS -> { + val res = extractTlv(1, iccBytes) ?: return null + val ck = extractTlv(1 + res.size + 1, iccBytes) ?: return null + val ik = extractTlv(1 + res.size + 1 + ck.size + 1, iccBytes) ?: return null + + val identity = buildEapId(mccMnc, imsi) + val identityBytes = identity.toByteArray(StandardCharsets.UTF_8) + val keys = Fips186Prf.deriveKeys(identityBytes, ik, ck) + + val kAut = keys["K_aut"] ?: run { + Log.e(TAG, "Failed to derive K_aut") + return null + } + + val responsePacket = buildEapAkaResponse(eapId, res, kAut) ?: return null + Base64.encodeToString(responsePacket, Base64.NO_WRAP) + } + + SIM_RES_SYNC_FAIL -> { + val auts = extractTlv(1, iccBytes) ?: return null + val responsePacket = buildEapAkaSyncFailure(eapId, auts) + Base64.encodeToString(responsePacket, Base64.NO_WRAP) + } + + else -> { + Log.e(TAG, "Unknown SIM response tag: ${iccBytes[0]}") + null + } + } + } + + fun buildEapId(mccMnc: String, imsi: String, realm: String? = null): String { + val mcc = mccMnc.substring(0, 3) + var mnc = mccMnc.substring(3) + if (mnc.length == 2) mnc = "0$mnc" // Zero-pad 2-digit MNCs + val defaultRealm = "nai.epc.mnc$mnc.mcc$mcc.3gppnetwork.org" + val resolvedRealm = when { + realm.isNullOrBlank() -> defaultRealm + realm == "nai.epc" -> defaultRealm + realm.contains(".mnc") && realm.contains(".mcc") && realm.contains("3gppnetwork.org") -> realm + else -> realm + } + return "0$imsi@$resolvedRealm" + } + + private fun extractTlv(index: Int, data: ByteArray): ByteArray? { + if (index >= data.size) return null + val len = data[index].toInt() and 0xFF + if (index + 1 + len > data.size) return null + return data.copyOfRange(index + 1, index + 1 + len) + } + + private fun buildEapAkaResponse(id: Byte, res: ByteArray, kAut: ByteArray): ByteArray? { + val resAttrLen = ((res.size + 4 + 3) / 4) * 4 + val totalLen = 8 + resAttrLen + 20 + val buffer = ByteBuffer.allocate(totalLen) + + buffer.put(EAP_CODE_RESPONSE.toByte()) + buffer.put(id) + buffer.putShort(totalLen.toShort()) + buffer.put(EAP_TYPE_AKA.toByte()) + buffer.put(EAP_AKA_SUBTYPE_CHALLENGE.toByte()) + buffer.putShort(0) + + buffer.put(AT_RES.toByte()) + buffer.put((resAttrLen / 4).toByte()) + buffer.putShort((res.size * 8).toShort()) + buffer.put(res) + + val padding = resAttrLen - 4 - res.size + if (padding > 0) buffer.put(ByteArray(padding)) + + buffer.position() + buffer.put(AT_MAC.toByte()) + buffer.put(5) + buffer.putShort(0) + val macValueOffset = buffer.position() + buffer.put(ByteArray(16)) + + val packet = buffer.array() + val mac = hmacSha1(kAut, packet) ?: return null + + System.arraycopy(mac, 0, packet, macValueOffset, 16) + return packet + } + + private fun buildEapAkaSyncFailure(id: Byte, auts: ByteArray): ByteArray { + val attrLen = ((auts.size + 2 + 3) / 4) * 4 + val totalLen = 8 + attrLen + + return ByteBuffer.allocate(totalLen).apply { + put(EAP_CODE_RESPONSE.toByte()) + put(id) + putShort(totalLen.toShort()) + put(EAP_TYPE_AKA.toByte()) + put(EAP_AKA_SUBTYPE_SYNC_FAILURE.toByte()) + putShort(0) + + put(AT_AUTS.toByte()) + put((attrLen / 4).toByte()) + put(auts) + + val padding = attrLen - 2 - auts.size + if (padding > 0) put(ByteArray(padding)) + }.array() + } + + private fun hmacSha1(key: ByteArray, data: ByteArray): ByteArray? = try { + Mac.getInstance("HmacSHA1").apply { init(SecretKeySpec(key, "HmacSHA1")) }.doFinal(data) + } catch (_: Exception) { + null + } +} diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/Fips186Prf.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/Fips186Prf.kt new file mode 100644 index 0000000000..0f2b138ebe --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/Fips186Prf.kt @@ -0,0 +1,108 @@ +package org.microg.gms.constellation.verification.ts43 + +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException + +object Fips186Prf { + fun deriveKeys( + identityBytes: ByteArray, + ik: ByteArray, + ck: ByteArray + ): Map { + val xKey = try { + val md = MessageDigest.getInstance("SHA-1") + md.update(identityBytes) + md.update(ik) + md.update(ck) + md.digest() + } catch (_: NoSuchAlgorithmException) { + ByteArray(20) + } + + if (xKey.size != 20) return emptyMap() + + val result = ByteArray(160) + val xKeyWorking = xKey.copyOf() + var resultOffset = 0 + + repeat(8) { + val h = intArrayOf( + 0x67452301, + 0xEFCDAB89.toInt(), + 0x98BADCFE.toInt(), + 0x10325476, + 0xC3D2E1F0.toInt() + ) + val w = IntArray(80) + for (k in 0 until 16) { + val wordIdx = k * 4 + w[k] = if (wordIdx < 20) { + ((xKeyWorking.getOrElse(wordIdx) { 0 }.toInt() and 0xFF) shl 24) or + ((xKeyWorking.getOrElse(wordIdx + 1) { 0 } + .toInt() and 0xFF) shl 16) or + ((xKeyWorking.getOrElse(wordIdx + 2) { 0 } + .toInt() and 0xFF) shl 8) or + (xKeyWorking.getOrElse(wordIdx + 3) { 0 }.toInt() and 0xFF) + } else 0 + } + for (k in 16 until 80) { + val temp = w[k - 3] xor w[k - 8] xor w[k - 14] xor w[k - 16] + w[k] = (temp shl 1) or (temp ushr 31) + } + var a = h[0] + var b = h[1] + var c = h[2] + var d = h[3] + var e = h[4] + for (t in 0 until 80) { + val f: Int + val k: Int + when { + t <= 19 -> { + f = (b and c) or (b.inv() and d); k = 0x5A827999 + } + + t <= 39 -> { + f = b xor c xor d; k = 0x6ED9EBA1 + } + + t <= 59 -> { + f = (b and c) or (b and d) or (c and d); k = 0x8F1BBCDC.toInt() + } + + else -> { + f = b xor c xor d; k = 0xCA62C1D6.toInt() + } + } + val temp = ((a shl 5) or (a ushr 27)) + f + e + k + w[t] + e = d; d = c; c = (b shl 30) or (b ushr 2); b = a; a = temp + } + val block = IntArray(5) + block[0] = h[0] + a; block[1] = h[1] + b; block[2] = h[2] + c; block[3] = + h[3] + d; block[4] = h[4] + e + for (k in 0 until 5) { + val word = block[k] + result[resultOffset++] = (word shr 24).toByte() + result[resultOffset++] = (word shr 16).toByte() + result[resultOffset++] = (word shr 8).toByte() + result[resultOffset++] = word.toByte() + } + var carry = 1 + for (k in 19 downTo 0) { + val resByte = result[resultOffset - 20 + k].toInt() and 0xFF + val keyByte = xKeyWorking[k].toInt() and 0xFF + val sum = carry + keyByte + resByte + xKeyWorking[k] = sum.toByte() + carry = sum shr 8 + } + } + + // RFC 4187 Section 7: PRF output slicing + return mapOf( + "K_encr" to result.copyOfRange(0, 16), + "K_aut" to result.copyOfRange(16, 32), + "MSK" to result.copyOfRange(32, 96), + "EMSK" to result.copyOfRange(96, 160) + ) + } +} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/ServiceEntitlementExtension.kt b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/ServiceEntitlementExtension.kt new file mode 100644 index 0000000000..71f9e4c87b --- /dev/null +++ b/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/ServiceEntitlementExtension.kt @@ -0,0 +1,200 @@ +package org.microg.gms.constellation.verification.ts43 + +import android.content.Context +import android.telephony.TelephonyManager +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import org.microg.gms.constellation.proto.OdsaOperation +import org.microg.gms.constellation.proto.ServiceEntitlementRequest + +fun ServiceEntitlementRequest.builder( + telephonyManager: TelephonyManager, + eapId: String, + appIds: List = emptyList() +) = ServiceEntitlementBuilder( + imsi = telephonyManager.subscriberId ?: "", + iccid = try { + telephonyManager.simSerialNumber + } catch (_: Exception) { + null + }, + terminalId = telephonyManager.imei, + groupIdLevel1 = runCatching { telephonyManager.groupIdLevel1 }.getOrNull(), + eapId = eapId, + appIds = appIds, + req = this +) + +fun ServiceEntitlementRequest.userAgent(context: Context): String { + val packageVersion = runCatching { + context.packageManager.getPackageInfo(context.packageName, 0).versionName.orEmpty() + }.getOrDefault("") + val vendor = terminal_vendor.take(4) + val model = terminal_model.take(10) + val swVersion = terminal_software_version.take(20) + return "PRD-TS43 term-$vendor/$model /$packageVersion OS-Android/$swVersion" +} + +fun OdsaOperation.builder( + telephonyManager: TelephonyManager, + serviceEntitlementRequest: ServiceEntitlementRequest? = null, + appIds: List = emptyList() +) = ServiceEntitlementBuilder( + imsi = telephonyManager.subscriberId ?: "", + iccid = try { + telephonyManager.simSerialNumber + } catch (_: Exception) { + null + }, + terminalId = telephonyManager.imei, + groupIdLevel1 = runCatching { telephonyManager.groupIdLevel1 }.getOrNull(), + eapId = "", // Not needed for ODSA + appIds = appIds, + req = serviceEntitlementRequest ?: ServiceEntitlementRequest(), + odsa = this +) + +class ServiceEntitlementBuilder( + private val imsi: String, + private val iccid: String?, + private val terminalId: String?, + private val groupIdLevel1: String?, + private val eapId: String, + private val appIds: List, + private val req: ServiceEntitlementRequest, + private val odsa: OdsaOperation? = null +) { + fun buildBaseUrl(entitlementUrl: String): HttpUrl { + val baseUrl = entitlementUrl.toHttpUrl() + + // GMS truncates these fields: vendor (4), model (10), sw_version (20) + val vendor = req.terminal_vendor.take(4) + val model = req.terminal_model.take(10) + val swVersion = req.terminal_software_version.take(20) + + return baseUrl.newBuilder().apply { + when { + req.authentication_token.isNotEmpty() -> { + addQueryParameter("token", req.authentication_token) + if (imsi.isNotEmpty()) addQueryParameter("IMSI", imsi) + } + + req.temporary_token.isNotEmpty() -> { + addQueryParameter("temporary_token", req.temporary_token) + } + + eapId.isNotEmpty() -> { + addQueryParameter("EAP_ID", eapId) + } + } + addQueryParameter("terminal_id", terminalId ?: req.terminal_id) + if (req.gid1.isNotEmpty()) { + addQueryParameter("GID1", req.gid1) + } else if ((req.entitlement_version.toBigDecimalOrNull()?.toInt() ?: 0) >= 12) { + groupIdLevel1?.takeIf { it.isNotEmpty() }?.let { addQueryParameter("GID1", it) } + } + if (req.app_version.isNotEmpty()) addQueryParameter("app_version", req.app_version) + addQueryParameter("terminal_vendor", vendor) + addQueryParameter("terminal_model", model) + addQueryParameter("terminal_sw_version", swVersion) + addQueryParameter("app_name", req.app_name) + if (req.boost_type.isNotEmpty()) addQueryParameter("boost_type", req.boost_type) + if (appIds.isNotEmpty()) { + appIds.forEach { addQueryParameter("app", it) } + } else { + addQueryParameter("app", "ap2014") + } + addQueryParameter("vers", req.configuration_version.toString()) + addQueryParameter("entitlement_version", req.entitlement_version) + if (req.notification_token.isNotEmpty()) { + addQueryParameter("notif_action", req.notification_action.toString()) + addQueryParameter("notif_token", req.notification_token) + } + + // Handle ODSA specific fields if present + odsa?.let { + addQueryParameter("operation", it.operation) + if (it.operation_type != -1) { + addQueryParameter("operation_type", it.operation_type.toString()) + } + if (it.operation_targets.isNotEmpty()) { + addQueryParameter("operation_targets", it.operation_targets.joinToString(",")) + } + if (it.terminal_iccid.isNotEmpty()) addQueryParameter( + "terminal_iccid", + it.terminal_iccid + ) + else iccid?.let { i -> addQueryParameter("terminal_iccid", i) } + + if (it.terminal_eid.isNotEmpty()) addQueryParameter("terminal_eid", it.terminal_eid) + if (it.target_terminal_id.isNotEmpty()) addQueryParameter( + "target_terminal_id", + it.target_terminal_id + ) + if (it.target_terminal_iccid.isNotEmpty()) addQueryParameter( + "target_terminal_iccid", + it.target_terminal_iccid + ) + if (it.target_terminal_eid.isNotEmpty()) addQueryParameter( + "target_terminal_eid", + it.target_terminal_eid + ) + if (it.target_terminal_model.isNotEmpty()) addQueryParameter( + "target_terminal_model", + it.target_terminal_model + ) + if (it.target_terminal_serial_number.isNotEmpty()) addQueryParameter( + "target_terminal_sn", + it.target_terminal_serial_number + ) + + it.target_terminal_ids.forEach { id -> + addQueryParameter("target_terminal_imeis", id) + } + + if (it.old_terminal_id.isNotEmpty()) addQueryParameter( + "old_terminal_id", + it.old_terminal_id + ) + if (it.old_terminal_iccid.isNotEmpty()) addQueryParameter( + "old_terminal_iccid", + it.old_terminal_iccid + ) + + // Companion fields + if (it.companion_terminal_id.isNotEmpty()) addQueryParameter( + "companion_terminal_id", + it.companion_terminal_id + ) + if (it.companion_terminal_vendor.isNotEmpty()) addQueryParameter( + "companion_terminal_vendor", + it.companion_terminal_vendor + ) + if (it.companion_terminal_model.isNotEmpty()) addQueryParameter( + "companion_terminal_model", + it.companion_terminal_model + ) + if (it.companion_terminal_software_version.isNotEmpty()) addQueryParameter( + "companion_terminal_sw_version", + it.companion_terminal_software_version + ) + if (it.companion_terminal_friendly_name.isNotEmpty()) addQueryParameter( + "companion_terminal_friendly_name", + it.companion_terminal_friendly_name + ) + if (it.companion_terminal_service.isNotEmpty()) addQueryParameter( + "companion_terminal_service", + it.companion_terminal_service + ) + if (it.companion_terminal_iccid.isNotEmpty()) addQueryParameter( + "companion_terminal_iccid", + it.companion_terminal_iccid + ) + if (it.companion_terminal_eid.isNotEmpty()) addQueryParameter( + "companion_terminal_eid", + it.companion_terminal_eid + ) + } + }.build() + } +} diff --git a/play-services-constellation/src/main/proto/constellation.proto b/play-services-constellation/src/main/proto/constellation.proto new file mode 100644 index 0000000000..29a8bd027a --- /dev/null +++ b/play-services-constellation/src/main/proto/constellation.proto @@ -0,0 +1,1025 @@ +syntax = "proto3"; +option java_package = "org.microg.gms.constellation.proto"; + +package google.internal.communications.phonedeviceverification.v1; + +import "google/protobuf/timestamp.proto"; + +// Verification and consent RPCs. +service PhoneDeviceVerification { + rpc GetConsent(GetConsentRequest) returns (GetConsentResponse); + rpc SetConsent(SetConsentRequest) returns (SetConsentResponse); + rpc Sync(SyncRequest) returns (SyncResponse); + rpc Proceed(ProceedRequest) returns (ProceedResponse); +} + +// Read-only phone number retrieval RPC. +service PhoneNumber { + rpc GetVerifiedPhoneNumbers(GetVerifiedPhoneNumbersRequest) returns (GetVerifiedPhoneNumbersResponse); +} + +// Shared request and response messages. +// Header for each client request. +message RequestHeader { + ClientInfo client_info = 1; + ClientAuth client_auth = 2; + string session_id = 3; + RequestTrigger trigger = 4; +} +message ResponseHeader { + StatusProtoSimple status = 1; +} +message StatusProtoSimple { + int32 code = 1; +} +enum StatusCode { + STATUS_CODE_UNSPECIFIED = 0; + STATUS_CODE_OK = 1; + STATUS_CODE_ERROR = 2; +} +message ClientInfo { + DeviceID device_id = 1; + bytes client_public_key = 2; + string locale = 3; + int32 gmscore_version_number = 4; + string gmscore_version = 5; + int32 android_sdk_version = 6; + DroidGuardSignals droidguard_signals = 8; + repeated Experiment experiments = 9; + UserProfileType user_profile_type = 11; + repeated GaiaToken gaia_tokens = 12; + CountryInfo country_info = 13; + repeated NetworkSignal connectivity_infos = 14; + string model = 15; + string manufacturer = 16; + repeated SimOperatorInfo partial_sim_infos = 17; + DeviceType device_type = 18; + bool is_wearable_standalone = 19; + GaiaSignals gaia_signals = 20; + string device_fingerprint = 21; +} + +enum UserProfileType { + UNKNOWN_PROFILE_TYPE = 0; + REGULAR_USER = 1; + MANAGED_PROFILE = 2; +} + +enum DeviceType { + DEVICE_TYPE_UNKNOWN = 0; + DEVICE_TYPE_PHONE = 1; + DEVICE_TYPE_PHONE_GO = 2; + DEVICE_TYPE_TV = 3; + DEVICE_TYPE_WEARABLE = 4; + DEVICE_TYPE_AUTOMOTIVE = 5; + DEVICE_TYPE_BATTLESTAR = 6; + DEVICE_TYPE_CHROME_OS = 7; + DEVICE_TYPE_XR = 8; + DEVICE_TYPE_DESKTOP = 9; + DEVICE_TYPE_XR_PERIPHERAL = 10; +} +message ClientAuth { + DeviceID device_id = 1; + bytes signature = 2; + google.protobuf.Timestamp sign_timestamp = 3; +} +message CountryInfo { + repeated string sim_countries = 1; + repeated string network_countries = 2; +} +message DroidGuardSignals { + string droidguard_result = 1; + string droidguard_token = 2; +} +message DeviceID { + string iid_token = 1; + int64 primary_device_id = 2; + int64 user_serial = 3; + int64 gms_android_id = 4; +} +message RequestTrigger { + enum Type { + UNKNOWN = 0; + PERIODIC_CONSENT_CHECK = 1; + PERIODIC_REFRESH = 2; + SIM_STATE_CHANGED = 3; + GAIA_CHANGE_EVENT = 4; + USER_SETTINGS = 5; + DEBUG_SETTINGS = 6; + TRIGGER_API_CALL = 7; + REBOOT_CHECKER = 8; + SERVER_TRIGGER = 9; + FAILURE_RETRY = 10; + CONSENT_API_TRIGGER = 11; + PNVR_DEVICE_SETTINGS = 12; + } + Type type = 1; +} +message DroidGuardAttestation { + string droidguard_result = 1; + string droidguard_token = 2; +} +message Experiment { + string key = 1; + string value = 2; +} +message GaiaID { + string id = 1; +} +message GaiaToken { + string token = 1; +} +message GaiaSignalEntry { + string gaia_id = 1; + GaiaAccountSignalType signal_type = 2; + google.protobuf.Timestamp timestamp = 3; +} + +enum GaiaAccountSignalType { + GAIA_ACCOUNT_SIGNAL_UNSPECIFIED = 0; + GAIA_ACCOUNT_SIGNAL_AUTHENTICATED = 1; + GAIA_ACCOUNT_SIGNAL_UNAUTHENTICATED = 2; + GAIA_ACCOUNT_SIGNAL_REMOVED_WITH_GMS_AUTH_BROADCAST = 3; +} +message GaiaSignals { + repeated GaiaSignalEntry gaia_signals = 1; +} +message Status { + int32 code = 1; +} + +// Named API params used by verification RPCs. +message VerificationPolicy { + string policy_id = 1; + int64 max_verification_age_hours = 2; + IdTokenRequest id_token_request = 3; + string calling_package = 4; + repeated VerificationParam params = 5; +} + +// Parameters for ID token generation. +message IdTokenRequest { + string certificate_hash = 1; + string token_nonce = 2; +} +message VerificationParam { + string key = 1; + string value = 2; +} +message SimOperatorInfo { + string imsi_hash = 1; + + string sim_operator = 3; +} +message NetworkSignal { + enum Type { + TYPE_UNKNOWN = 0; + TYPE_WIFI = 1; + TYPE_MOBILE = 2; + } + enum Availability { + AVAILABILITY_UNKNOWN = 0; + AVAILABLE = 1; + NOT_AVAILABLE = 2; + } + enum State { + STATE_UNKNOWN = 0; + STATE_CONNECTING = 1; + STATE_CONNECTED = 2; + STATE_DISCONNECTING = 3; + STATE_DISCONNECTED = 4; + STATE_SUSPENDED = 5; + } + Type type = 1; + State state = 2; + Availability availability = 3; +} +message MobileOperatorInfo { + string country_code = 1; + uint64 nil_since_usec = 2; + string operator = 3; + uint32 nil_since_micros = 4; + string operator_name = 5; +} + +// Sync and proceed flow. +message SyncRequest { + repeated Verification verifications = 3; + RequestHeader header = 4; + repeated VerificationToken verification_tokens = 5; +} +message SyncResponse { + repeated VerificationResponse responses = 1; + ServerTimestamp next_sync_time = 2; + ResponseHeader header = 3; + DroidguardToken droidguard_token = 4; + repeated VerificationToken verification_tokens = 5; +} +message ProceedRequest { + Verification verification = 2; + ChallengeResponse challenge_response = 3; + RequestHeader header = 4; +} +message ProceedResponse { + Verification verification = 1; + ResponseHeader header = 2; + ServerTimestamp next_sync_time = 3; + DroidguardToken droidguard_token = 4; +} + +// Server time plus the time when the server wrote this message. +message ServerTimestamp { + google.protobuf.Timestamp timestamp = 1; + google.protobuf.Timestamp now = 2; +} +message VerificationToken { + bytes token = 1; + google.protobuf.Timestamp expiration_time = 2; +} + +// Verification state and challenge model. +message Verification { + enum State { + UNKNOWN = 0; + NONE = 1; + PENDING = 2; + VERIFIED = 3; + } + + enum Status { + STATUS_UNKNOWN = 0; + STATUS_NONE = 1; + STATUS_PENDING = 2; + STATUS_VERIFIED = 3; + STATUS_THROTTLED = 4; + STATUS_FAILED = 5; + STATUS_SKIPPED = 6; + STATUS_NOT_REQUIRED = 7; + STATUS_PHONE_NUMBER_ENTRY_REQUIRED = 8; + STATUS_INELIGIBLE = 9; + STATUS_DENIED = 10; + STATUS_NOT_IN_SERVICE = 11; + } + + VerificationAssociation association = 1; + Status status = 2; + oneof info { + VerificationInfo verification_info = 3; + PendingVerificationInfo pending_verification_info = 4; + UnverifiedInfo unverified_info = 9; + } + TelephonyInfo telephony_info = 5; + repeated Param api_params = 6; + ChallengePreference challenge_preference = 7; + VerificationPolicy structured_api_params = 8; +} +message VerificationResponse { + Verification verification = 1; + StatusProto error = 2; +} +message StatusProto { + int32 code = 1; + string message = 3; +} +message SIMSlotInfo { + int32 slot_index = 1; + int32 subscription_id = 2; +} +message SIMAssociation { + + message SIMInfo { + repeated string imsi = 1; + string sim_readable_number = 2; + repeated TelephonyPhoneNumber telephony_phone_number = 3; + string iccid = 4; + } + + message TelephonyPhoneNumber { + string phone_number = 1; + TelephonyPhoneNumberType phone_number_type = 2; + } + + SIMInfo sim_info = 1; + repeated GaiaToken gaia_tokens = 2; + SIMSlotInfo sim_slot = 4; +} +message GaiaAssociation { +} +message VerificationAssociation { + oneof association { + SIMAssociation sim = 1; + GaiaAssociation gaia = 2; + } +} + +// Information for a verified record. +message VerificationInfo { + string phone_number = 1; + google.protobuf.Timestamp verification_time = 2; + VerificationMethod challenge_method = 6; +} +message PendingVerificationInfo { + Challenge challenge = 2; +} + +// Challenge payloads and responses. +// Challenge issued by the server. +message Challenge { + ChallengeID challenge_id = 1; + VerificationMethod type = 2; + MoChallenge mo_challenge = 3; + CarrierIdChallenge carrier_id_challenge = 4; + ServerTimestamp expiry_time = 5; + MTChallenge mt_challenge = 6; + RegisteredSmsChallenge registered_sms_challenge = 7; + FlashCallChallenge flash_call_challenge = 8; + Ts43Challenge ts43_challenge = 12; +} + +// Carrier ID challenge used for SS7 traffic. +message CarrierIdChallenge { + string isim_request = 3; + int32 auth_type = 5; + int32 app_type = 6; +} +message MTChallenge { + string sms = 1; +} +message ChallengeID { + string id = 1; +} +message Capabilities { + string droidguard_result = 1; + string droidguard_token = 2; +} + +// MO SMS challenge. +message MoChallenge { + string proxy_number = 1; + DataSmsInfo data_sms_info = 3; + string sms = 4; + string polling_intervals = 5; + string sms_without_persisting = 6; +} +message DataSmsInfo { + int32 destination_port = 1; +} +message RegisteredSmsChallenge { + repeated PhoneNumberID verified_senders = 1; +} +message PhoneNumberID { + bytes phone_number_id = 1; +} +message FlashCallChallenge { + repeated PhoneRange phone_ranges = 1; + FlashCallState state = 2; + repeated ChallengeID previous_challenge_ids = 3; + repeated ChallengeResponse previous_challenge_responses = 4; + int64 millis_between_interceptions = 5; +} +message PhoneRange { + string phone_number_prefix = 1; + string phone_number_suffix = 2; + string country_code = 3; + string carrier = 4; +} + +enum FlashCallState { + FLASH_CALL_STATE_UNKNOWN = 0; + FLASH_CALL_STATE_PREPARING = 1; + FLASH_CALL_STATE_VERIFYING = 2; + FLASH_CALL_STATE_VERIFIED = 3; + FLASH_CALL_STATE_FAILED = 4; +} +message UnverifiedInfo { + enum Reason { + UNKNOWN_REASON = 0; + THROTTLED = 1; + FAILED = 2; + SKIPPED = 3; + NOT_REQUIRED = 4; + PHONE_NUMBER_ENTRY_REQUIRED = 5; + INELIGIBLE = 6; + DENIED = 7; + NOT_IN_SERVICE = 8; + } + Reason reason = 1; + google.protobuf.Timestamp retry_after_time = 2; + VerificationMethod challenge_method = 3; +} + +// Challenge response. +message ChallengeResponse { + MTChallengeResponseData mt_response = 1; + CarrierIdChallengeResponse carrier_id_response = 2; + MOChallengeResponseData mo_response = 3; + RegisteredSmsChallengeResponse registered_sms_response = 6; + FlashCallChallengeResponse flash_call_response = 7; + Ts43ChallengeResponse ts43_challenge_response = 9; +} +message MTChallengeResponseData { + string sms = 1; + string sender = 2; +} +message CarrierIdChallengeResponse { + string isim_response = 3; + CarrierIdError carrier_id_error = 4; +} + +enum CarrierIdError { + CARRIER_ID_ERROR_NO_ERROR = 0; + CARRIER_ID_ERROR_NOT_SUPPORTED = 1; + CARRIER_ID_ERROR_RETRY_ATTEMPT_EXCEEDED = 2; + CARRIER_ID_ERROR_NULL_RESPONSE = 3; + CARRIER_ID_ERROR_REFLECTION_ERROR = 4; + CARRIER_ID_ERROR_NO_SIM = 5; + CARRIER_ID_ERROR_UNABLE_TO_READ_SUBSCRIPTION = 6; + CARRIER_ID_ERROR_UNKNOWN_ERROR = 7; + CARRIER_ID_ERROR_ENTITLEMENT_SERVER_ERROR = 8; + CARRIER_ID_ERROR_JSON_PARSE_ERROR = 9; + CARRIER_ID_ERROR_INTERNAL_ERROR = 10; + CARRIER_ID_ERROR_INVALID_ARGUMENT = 11; +} +message MOChallengeResponseData { + enum Status { + UNKNOWN_STATUS = 0; + COMPLETED = 1; + FAILED_TO_SEND_MO = 2; + NO_ACTIVE_SUBSCRIPTION = 3; + NO_SMS_MANAGER = 4; + } + Status status = 1; + int64 sms_result_code = 2; + int64 sms_error_code = 3; +} +message RegisteredSmsChallengeResponse { + repeated RegisteredSmsChallengeResponseItem items = 1; +} +message RegisteredSmsChallengeResponseItem { + RegisteredSmsChallengeResponsePayload payload = 2; +} +message RegisteredSmsChallengeResponsePayload { + bytes payload = 1; +} +message Ts43ChallengeResponse { + Ts43Type ts43_type = 1; + ClientChallengeResponse client_challenge_response = 2; + ServerChallengeResponse server_challenge_response = 3; + Ts43ChallengeResponseStatus status = 4; + repeated string http_history = 5; +} +message Ts43ChallengeResponseStatus { + enum Code { + TS43_STATUS_UNSPECIFIED = 0; + TS43_STATUS_NOT_SUPPORTED = 1; + TS43_STATUS_CHALLENGE_NOT_SET = 2; + TS43_STATUS_INTERNAL_ERROR = 3; + TS43_STATUS_RUNTIME_ERROR = 4; + TS43_STATUS_JSON_PARSE_ERROR = 5; + } + + oneof result { + Code status_code = 1; + Ts43ChallengeResponseError error = 2; + } +} + +// The numeric 1..9 range is intentionally left generic because those values are +// overloaded across multiple TS.43 request stages. +message Ts43ChallengeResponseError { + enum Code { + TS43_ERROR_CODE_UNSPECIFIED = 0; + TS43_ERROR_CODE_1 = 1; + TS43_ERROR_CODE_2 = 2; + TS43_ERROR_CODE_3 = 3; + TS43_ERROR_CODE_4 = 4; + TS43_ERROR_CODE_5 = 5; + TS43_ERROR_CODE_6 = 6; + TS43_ERROR_CODE_7 = 7; + TS43_ERROR_CODE_8 = 8; + TS43_ERROR_CODE_9 = 9; + TS43_ERROR_CODE_PHONE_UNAVAILABLE = 10; + TS43_ERROR_CODE_INVALID_IMSI_OR_MCCMNC = 11; + TS43_ERROR_CODE_EAP_AKA_CHALLENGE_FAILED = 20; + TS43_ERROR_CODE_EAP_AKA_SYNCHRONIZATION_FAILURE = 21; + TS43_ERROR_CODE_EAP_AKA_AUTHENTICATION_FAILED = 22; + TS43_ERROR_CODE_CONNECTIVITY_FAILURE = 30; + TS43_ERROR_CODE_HTTP_ERROR_STATUS = 31; + TS43_ERROR_CODE_HTTP_MALFORMED_RESPONSE = 32; + TS43_ERROR_CODE_TEMPORARY_TOKEN_UNAVAILABLE = 60; + } + + enum RequestType { + TS43_REQUEST_TYPE_UNSPECIFIED = 0; + TS43_REQUEST_TYPE_AUTH_API = 1; + TS43_REQUEST_TYPE_GET_PHONE_NUMBER_API = 2; + TS43_REQUEST_TYPE_ACQUIRE_TEMPORARY_TOKEN_API = 3; + TS43_REQUEST_TYPE_SERVICE_ENTITLEMENT_API = 4; + } + + Code error_code = 1; + int32 http_status = 2; + RequestType request_type = 3; +} +message ServerChallengeResponse { + string acquire_temporary_token_response = 2; +} +message ClientChallengeResponse { + string get_phone_number_response = 2; +} +message FlashCallChallengeResponse { + enum Error { + NO_ERROR = 0; + UNSPECIFIED = 1; + TIMED_OUT = 2; + NETWORK_NOT_AVAILABLE = 3; + TOO_MANY_CALLS = 4; + CONCURRENT_REQUESTS = 5; + IN_ECBM = 6; + IN_EMERGENCY_CALL = 7; + PRECONDITIONS_FAILED = 8; + API_NOT_AVAILABLE = 9; + ERROR_PREVIOUS_INCOMING_CALL = 10; + STATE_NOT_PREPARING = 11; + STATE_NOT_VERIFYING = 12; + ERROR_PENDING_VERIFICATION = 13; + PROCEED_FAILED = 14; + INTERCEPTION_FAILED = 15; + } + string caller = 1; + Error error = 2; +} +message Ts43Type { + enum Integrator { + TS43_INTEGRATOR_UNSPECIFIED = 0; + JIO = 1; + TELUS = 2; + ERICSSON = 3; + HPE = 4; + TMO = 5; + TMO_SERVER = 6; + TELENOR = 7; + RCS_CIS_PROXY = 8; + MOBI_US = 9; + SFR = 10; + SASKTEL_CANADA = 11; + DT = 13; + DT_SERVER = 14; + NETLYNC = 17; + ORANGE_FRANCE = 18; + AMDOCS = 19; + } + Integrator integrator = 1; +} +message CellularNetworkEvent { + google.protobuf.Timestamp timestamp = 1; + bool mobile_data_enabled = 2; + bool airplane_mode_enabled = 3; + bool data_roaming_enabled = 4; + bool mobile_data_always_on = 5; + repeated NetworkState networks = 6; +} +message NetworkState { + repeated int32 types = 1; + bool available = 2; +} +message ServiceStateEvent { + google.protobuf.Timestamp timestamp = 1; + bool airplane_mode_enabled = 2; + bool mobile_data_enabled = 3; + int32 voice_registration_state = 4; + int32 data_registration_state = 5; + int32 voice_network_type = 6; + int32 data_network_type = 7; + int32 signal_strength = 8; +} +message SMSEvent { + google.protobuf.Timestamp timestamp = 1; + EventDirection direction = 2; + EventPhoneNumberType number_type = 3; +} +message CallEvent { + google.protobuf.Timestamp timestamp = 1; + EventDirection direction = 2; +} + +enum EventDirection { + UNKNOWN_DIRECTION = 0; + INCOMING = 1; + OUTGOING = 2; + MISSED = 3; +} + +enum EventPhoneNumberType { + UNKNOWN_TYPE = 0; + LONG_NUMBER = 1; + SHORT_CODE = 2; +} +message TelephonyInfo { + enum PhoneType { + PHONE_TYPE_UNKNOWN = 0; + PHONE_TYPE_GSM = 1; + PHONE_TYPE_CDMA = 2; + PHONE_TYPE_SIP = 3; + } + enum SimState { + SIM_STATE_UNKNOWN = 0; + SIM_STATE_NOT_READY = 1; + SIM_STATE_READY = 2; + } + enum RoamingState { + ROAMING_UNKNOWN = 0; + ROAMING_HOME = 1; + ROAMING_ROAMING = 2; + } + + enum ConnectivityState { + CONNECTIVITY_UNKNOWN = 0; + CONNECTIVITY_HOME = 1; + CONNECTIVITY_ROAMING = 2; + } + enum SmsCapability { + SMS_UNKNOWN = 0; + SMS_NOT_CAPABLE = 1; + SMS_CAPABLE = 2; + SMS_DEFAULT_CAPABILITY = 3; + SMS_RESTRICTED = 4; + } + enum ToggleState { + TOGGLE_UNKNOWN = 0; + TOGGLE_DISABLED = 1; + TOGGLE_ENABLED = 2; + } + enum ServiceState { + SERVICE_STATE_UNKNOWN = 0; + SERVICE_STATE_IN_SERVICE = 1; + SERVICE_STATE_OUT_OF_SERVICE = 2; + SERVICE_STATE_EMERGENCY_ONLY = 3; + SERVICE_STATE_POWER_OFF = 4; + } + + PhoneType phone_type = 1; + string group_id_level1 = 2; + SimNetworkInfo sim_info = 3; + SimNetworkInfo network_info = 4; + RoamingState network_roaming = 5; + ConnectivityState connectivity_state = 6; + SmsCapability sms_capability = 7; + int32 active_sub_count = 8; + int32 active_sub_count_max = 9; + ToggleState vowifi_state = 11; + ToggleState sms_no_confirmation_state = 12; + SimState sim_state = 13; + string device_id = 15; + ServiceState service_state = 16; + repeated CellularNetworkEvent cellular_events = 17; + repeated CallEvent call_events = 18; + repeated SMSEvent sms_events = 19; + repeated ServiceStateEvent service_events = 20; + bool is_embedded = 21; + ToggleState carrier_id_capability = 23; + int64 sim_carrier_id = 25; +} +message SimNetworkInfo { + string country_iso = 1; + string operator = 2; + string operator_name = 3; + int32 inactive_time_diff_ms = 4; +} +message DroidguardToken { + string token = 1; + google.protobuf.Timestamp ttl = 2; +} + +// Consent and Asterism messages. +message GetConsentRequest { + DeviceID device_id = 1; + repeated GaiaToken gaia_tokens = 2; + RequestHeader header = 4; + repeated VerificationToken verification_tokens = 5; + AsterismClient asterism_client = 6; + VerificationPolicy structured_api_params = 7; + bool force_refresh = 8; + string session_id = 9; + bool unknown_flag = 10; +} +message GetConsentResponse { + RcsConsent rcs_consent = 1; + ServerTimestamp next_check_time = 5; + ConsentState device_consent = 6; + repeated GaiaConsent gaia_consents = 8; + DroidguardToken droidguard_token = 9; + PermissionState permission_state = 10; +} + +// These values intentionally alias because the same field is reused across +// multiple consent surfaces. +enum FlowContext { + option allow_alias = true; + FLOW_CONTEXT_UNSPECIFIED = 0; + FLOW_CONTEXT_RCS_DEFAULT_ON_LEGAL_FYI = 0; + FLOW_CONTEXT_RCS_CONSENT = 1; + FLOW_CONTEXT_RCS_DEFAULT_ON_OUT_OF_BOX = 2; + FLOW_CONTEXT_RCS_SAMSUNG_UNFREEZE = 3; + FLOW_CONTEXT_RCS_DEFAULT_ON_LEGAL_FYI_IN_SETTINGS = 4; + FLOW_CONTEXT_RCS_UNKNOWN_5 = 5; +} +message SetConsentRequest { + RequestHeader header = 1; + RcsConsent rcs_consent = 3; + AsterismClient asterism_client = 4; + bytes audit_record = 6; + repeated Param api_params = 7; + OnDemandConsent on_demand_consent = 8; + FlowContext flow_context = 9; + oneof device_consent_oneof { + AsterismConsent device_consent = 10; + } +} +message SetConsentResponse { +} + +enum Consent { + CONSENT_UNKNOWN = 0; + CONSENTED = 1; + NO_CONSENT = 2; + EXPIRED = 3; +} + +// These values intentionally alias because RCS and device consent versions +// share the same field. +enum ConsentVersion { + option allow_alias = true; + CONSENT_VERSION_UNSPECIFIED = 0; + RCS_LEGAL_FYI = 0; + RCS_CONSENT = 1; + RCS_OUT_OF_BOX = 2; + PHONE_VERIFICATION_DEFAULT = 1; + PHONE_VERIFICATION_MESSAGES_CALLS_V1 = 2; + PHONE_VERIFICATION_INTL_SMS_CALLS = 3; + PHONE_VERIFICATION_REACHABILITY_INTL_SMS_CALLS = 4; +} +message AsterismConsent { + Consent consent = 1; + ConsentSource consent_source = 2; + ConsentVersion consent_version = 3; +} +message ConsentState { + Consent state = 2; +} + +// `flags` is still an opaque server-defined bitfield. +message PermissionState { + int32 flags = 1; + PermissionType type = 2; +} +enum PermissionType { + PERMISSION_TYPE_UNSPECIFIED = 0; + LEGACY_DPNV = 1; + PNVR = 2; + NOT_ALLOWED = 3; +} +message RcsConsent { + Consent consent = 2; + ConsentVersion consent_version = 3; +} +message GaiaConsent { + AsterismClient asterism_client = 1; + Consent consent = 2; + ConsentVersion consent_version = 3; +} +message OnDemandConsent { + Consent consent = 1; + GaiaToken gaia_token = 2; + string consent_variant = 3; + string trigger = 4; +} +enum ConsentSource { + SOURCE_UNSPECIFIED = 0; + ANDROID_DEVICE_SETTINGS = 1; + GAIA_USERNAME_RECOVERY = 2; + AOB_SETUP_WIZARD = 3; + MINUTEMAID_JS_BRIDGE = 4; + GAIA_WEB_JS_BRIDGE = 5; + AM_PROFILES = 6; + MEET_ON_DEMAND_CONSENT = 7; + GPAY_ON_DEMAND_CONSENT = 8; +} +enum AsterismClient { + UNKNOWN_CLIENT = 0; + CONSTELLATION = 1; + RCS = 2; + ONE_TIME_VERIFICATION = 3; +} + +// Read-only phone number RPC. +// Request message for GetVerifiedPhoneNumbers. +message GetVerifiedPhoneNumbersRequest { + enum PhoneNumberSelection { + SELECTION_UNSPECIFIED = 0; + CONSTELLATION = 1; + RCS = 2; + } + + string session_id = 1; + IIDTokenAuth iid_token_auth = 2; + repeated PhoneNumberSelection phone_number_selections = 3; + TokenOption token_option = 4; + string droidguard_result = 5; + ConsistencyOption consistency_option = 6; + RequestInfo request_info = 7; +} + +// Response message for GetVerifiedPhoneNumbers. +message GetVerifiedPhoneNumbersResponse { + repeated VerifiedPhoneNumber phone_numbers = 2; +} +message IIDTokenAuth { + string iid_token = 1; + bytes client_sign = 2; + google.protobuf.Timestamp sign_timestamp = 3; +} +message TokenOption { + string certificate_hash = 1; + string token_nonce = 2; + string package_name = 3; +} +message VerifiedPhoneNumber { + string phone_number = 1; + google.protobuf.Timestamp verification_time = 2; + string id_token = 3; + RcsState rcs_state = 4; +} + +enum RcsState { + STATE_UNSPECIFIED = 0; + ACTIVE = 1; +} + +enum TelephonyPhoneNumberType { + PHONE_NUMBER_SOURCE_UNSPECIFIED = 0; + PHONE_NUMBER_SOURCE_CARRIER = 1; + PHONE_NUMBER_SOURCE_UICC = 2; + PHONE_NUMBER_SOURCE_IMS = 3; +} + +// Shared helpers. +// Data consistency requirement for read-only RPCs. +message ConsistencyOption { + enum Consistency { + CONSISTENCY_UNSPECIFIED = 0; + STALE = 1; + STRONG = 2; + } + Consistency consistency = 1; +} + +// Request metadata for analytics and legacy policy attribution. +message RequestInfo { + string policy_id = 1; +} +message Param { + string key = 1; + string value = 2; +} + +// Client challenge preference. +message ChallengePreference { + repeated VerificationMethod capabilities = 1; + ChallengePreferenceMetadata metadata = 2; +} +message ChallengePreferenceMetadata { + string sms_signature = 2; +} +enum VerificationMethod { + UNKNOWN = 0; + MO_SMS = 1; + MT_SMS = 2; + CARRIER_ID = 3; + IMSI_LOOKUP = 5; + REGISTERED_SMS = 7; + FLASH_CALL = 8; + TS43 = 11; +} + +// Audit payloads. +message AuditToken { + AuditTokenMetadata metadata = 2; +} +message AuditTokenMetadata { + AuditUuid uuid = 1; +} +message AuditUuid { + int64 uuid_msb = 1; + int64 uuid_lsb = 2; +} +message AuditPayload { + AuditDeviceInfo device_info = 1; + AuditDeviceId device_id = 2; + AuditEventMetadata event_metadata = 10; +} +message AuditDeviceInfo { + oneof device_identifier { + string android_id_hash = 1; + string instance_id = 2; + } +} +message AuditDeviceId { + string android_id_hash = 1; +} +message AuditEventMetadata { + AuditEventType event_type = 1; + string session_id = 2; + int64 event_timestamp = 3; + AuditConsentDetails consent_details = 4; + string package_name = 5; + string tos_url = 6; + string tos_version = 7; + string language = 8; + string country = 9; + string gaia_id = 10; + AuditComponentInfo component_info = 11; + string trigger = 12; +} +enum AuditEventType { + EVENT_TYPE_UNSPECIFIED = 0; + ASTERISM_CONSENT_CHANGE = 1; + RCS_CONSENT_CHANGE = 2; + VERIFICATION_COMPLETE = 3; +} +message AuditConsentDetails { + repeated int32 sim_slot_ids = 1; +} +message AuditComponentInfo { + AuditComponentType component_type = 1; +} +enum AuditComponentType { + COMPONENT_UNSPECIFIED = 0; + ASTERISM_CONSTELLATION = 119; + ASTERISM_RCS = 120; +} +message AuditConsentState { + Consent state = 1; +} +enum AuditEventTypeValue { + EVENT_VALUE_UNSPECIFIED = 0; + ASTERISM_CLIENT_CONSENT_CHANGE = 187; +} + +// TS.43 challenge messages. +message Ts43Challenge { + Ts43Type ts43_type = 1; + string entitlement_url = 2; + ServiceEntitlementRequest service_entitlement_request = 3; + ClientChallenge client_challenge = 4; + ServerChallenge server_challenge = 5; + string app_id = 6; + string eap_aka_realm = 7; +} +message ServerChallenge { + OdsaOperation operation = 1; +} +message ClientChallenge { + OdsaOperation operation = 1; +} +message OdsaOperation { + string operation = 1; + int32 operation_type = 2; + repeated string operation_targets = 3; + string companion_terminal_id = 4; + string companion_terminal_vendor = 5; + string companion_terminal_model = 6; + string companion_terminal_software_version = 7; + string companion_terminal_friendly_name = 8; + string companion_terminal_service = 9; + string companion_terminal_iccid = 10; + string companion_terminal_eid = 11; + string terminal_iccid = 12; + string terminal_eid = 13; + string target_terminal_id = 14; + repeated string target_terminal_ids = 15; + string target_terminal_iccid = 16; + string target_terminal_eid = 17; + string target_terminal_serial_number = 18; + string target_terminal_model = 19; + string old_terminal_id = 20; + string old_terminal_iccid = 21; +} +message ServiceEntitlementRequest { + int32 notification_action = 1; + string entitlement_version = 2; + string temporary_token = 3; + string authentication_token = 4; + string terminal_id = 5; + string terminal_vendor = 6; + string terminal_model = 7; + string terminal_software_version = 8; + string app_name = 9; + string app_version = 10; + string notification_token = 11; + int32 configuration_version = 12; + string accept_content_type = 13; + string boost_type = 14; + string gid1 = 15; +} + diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 8fc2896bbc..47c2dc6553 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -59,6 +59,7 @@ dependencies { implementation project(':play-services-auth-base') implementation project(':play-services-auth') implementation project(':play-services-clearcut') + implementation project(':play-services-constellation') implementation project(':play-services-drive') implementation project(':play-services-games') implementation project(':play-services-maps') diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 66038da7e2..1b7e601d97 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -179,6 +179,9 @@ android:name="android.hardware.camera" android:required="false" /> + + + + + + + + @@ -1218,7 +1228,6 @@ - diff --git a/play-services-iid/src/main/java/com/google/android/gms/iid/InstanceID.java b/play-services-iid/src/main/java/com/google/android/gms/iid/InstanceID.java index 327d8b1cc0..92df450a4f 100644 --- a/play-services-iid/src/main/java/com/google/android/gms/iid/InstanceID.java +++ b/play-services-iid/src/main/java/com/google/android/gms/iid/InstanceID.java @@ -246,8 +246,24 @@ public InstanceIdStore getStore() { } @PublicApi(exclude = true) - public String requestToken(String authorizedEntity, String scope, Bundle extras) { - throw new UnsupportedOperationException(); + public String requestToken(String authorizedEntity, String scope, Bundle extras) throws IOException { + if (extras == null) extras = new Bundle(); + + extras.putString(EXTRA_SENDER, authorizedEntity); + if (scope != null) { + extras.putString(EXTRA_SCOPE, scope); + } + + String actualSubtype = TextUtils.isEmpty(this.subtype) ? authorizedEntity : this.subtype; + + if (!extras.containsKey("legacy.register")) { + extras.putString(EXTRA_SUBSCIPTION, authorizedEntity); + extras.putString(EXTRA_SUBTYPE, actualSubtype); + extras.putString("X-" + EXTRA_SUBSCIPTION, authorizedEntity); + extras.putString("X-" + EXTRA_SUBTYPE, actualSubtype); + } + + return rpc.handleRegisterMessageResult(rpc.sendRegisterMessageBlocking(extras, getKeyPair())); } private synchronized KeyPair getKeyPair() { diff --git a/settings.gradle b/settings.gradle index 1ab99b9000..bc68c6a7b1 100644 --- a/settings.gradle +++ b/settings.gradle @@ -36,6 +36,7 @@ include ':play-services-basement' include ':play-services-cast' include ':play-services-cast-framework' include ':play-services-clearcut' +include ':play-services-constellation' include ':play-services-drive' include ':play-services-droidguard' include ':play-services-fido' From 977afd11ee1855cd932e9da43413a04916a3502f Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Thu, 26 Mar 2026 00:17:26 -0400 Subject: [PATCH 02/31] Refactor and small cleanups --- play-services-constellation/build.gradle | 30 +-------- play-services-constellation/core/build.gradle | 64 +++++++++++++++++++ .../core/src/main/AndroidManifest.xml | 26 ++++++++ .../gms/constellation/GetPnvCapabilities.kt | 30 +++++++++ .../gms/constellation/VerifyPhoneNumber.kt | 6 ++ .../gms/constellation/core}/AuthManager.kt | 2 +- .../core}/ConstellationApiService.kt | 2 +- .../core}/ConstellationStateStore.kt | 21 +++--- .../gms/constellation/core}/GServices.kt | 2 +- .../gms/constellation/core}/GetIidToken.kt | 2 +- .../constellation/core}/GetPnvCapabilities.kt | 15 ++--- .../core}/GetPnvCapabilitiesApiPhenotype.kt | 2 +- .../core}/GetVerifiedPhoneNumbers.kt | 17 +++-- .../constellation/core}/IidTokenPhenotypes.kt | 2 +- .../gms/constellation/core}/RpcClient.kt | 6 +- .../core}/VerificationMappings.kt | 10 +-- .../core}/VerificationSettingsPhenotypes.kt | 2 +- .../constellation/core}/VerifyPhoneNumber.kt | 37 +++++++---- .../core}/VerifyPhoneNumberApiPhenotypes.kt | 2 +- .../core}/proto/builders/ClientInfoBuilder.kt | 32 ++++------ .../core}/proto/builders/CommonBuilders.kt | 27 ++++---- .../core}/proto/builders/GaiaInfoBuilder.kt | 13 ++-- .../proto/builders/RequestBuildContext.kt | 6 +- .../proto/builders/SyncRequestBuilder.kt | 35 +++++----- .../proto/builders/TelephonyInfoBuilder.kt | 11 ++-- .../core}/verification/CarrierIdVerifier.kt | 10 +-- .../core}/verification/ChallengeProcessor.kt | 24 +++---- .../core}/verification/MoSmsVerifier.kt | 12 ++-- .../core}/verification/MtSmsVerifier.kt | 8 +-- .../verification/RegisteredSmsVerifier.kt | 14 ++-- .../core}/verification/Ts43Verifier.kt | 27 ++++---- .../core}/verification/ts43/EapAkaService.kt | 2 +- .../core}/verification/ts43/Fips186Prf.kt | 2 +- .../ts43/ServiceEntitlementExtension.kt | 6 +- .../src/main/proto/constellation.proto | 2 +- .../src/main/AndroidManifest.xml | 15 +---- .../gms/constellation/GetPnvCapabilities.kt | 28 +------- .../gms/constellation/PhoneNumberInfo.kt | 1 - .../gms/constellation/VerifyPhoneNumber.kt | 6 +- play-services-core/build.gradle | 2 +- .../src/main/AndroidManifest.xml | 10 --- settings.gradle | 1 + 42 files changed, 311 insertions(+), 261 deletions(-) create mode 100644 play-services-constellation/core/build.gradle create mode 100644 play-services-constellation/core/src/main/AndroidManifest.xml create mode 100644 play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt create mode 100644 play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/AuthManager.kt (98%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/ConstellationApiService.kt (99%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/ConstellationStateStore.kt (91%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/GServices.kt (95%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/GetIidToken.kt (96%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/GetPnvCapabilities.kt (93%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/GetPnvCapabilitiesApiPhenotype.kt (69%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/GetVerifiedPhoneNumbers.kt (89%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/IidTokenPhenotypes.kt (88%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/RpcClient.kt (88%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/VerificationMappings.kt (94%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/VerificationSettingsPhenotypes.kt (76%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/VerifyPhoneNumber.kt (90%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/VerifyPhoneNumberApiPhenotypes.kt (97%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/proto/builders/ClientInfoBuilder.kt (86%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/proto/builders/CommonBuilders.kt (69%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/proto/builders/GaiaInfoBuilder.kt (85%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/proto/builders/RequestBuildContext.kt (70%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/proto/builders/SyncRequestBuilder.kt (85%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/proto/builders/TelephonyInfoBuilder.kt (94%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/CarrierIdVerifier.kt (90%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/ChallengeProcessor.kt (88%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/MoSmsVerifier.kt (95%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/MtSmsVerifier.kt (94%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/RegisteredSmsVerifier.kt (92%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/Ts43Verifier.kt (94%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/ts43/EapAkaService.kt (99%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/ts43/Fips186Prf.kt (98%) rename play-services-constellation/{src/main/kotlin/org/microg/gms/constellation => core/src/main/kotlin/org/microg/gms/constellation/core}/verification/ts43/ServiceEntitlementExtension.kt (97%) rename play-services-constellation/{ => core}/src/main/proto/constellation.proto (99%) diff --git a/play-services-constellation/build.gradle b/play-services-constellation/build.gradle index 2301c04063..e0cc981be3 100644 --- a/play-services-constellation/build.gradle +++ b/play-services-constellation/build.gradle @@ -10,16 +10,6 @@ apply plugin: 'kotlin-parcelize' apply plugin: 'kotlin-kapt' apply plugin: 'signing' -buildscript { - repositories { - mavenCentral() - } - dependencies { - classpath 'com.squareup.wire:wire-gradle-plugin:4.9.3' - } -} -apply plugin: 'com.squareup.wire' - android { namespace "org.microg.gms.constellation" @@ -46,28 +36,12 @@ android { } } -wire { - kotlin { - rpcRole = 'client' - rpcCallStyle = 'suspending' - } -} - apply from: '../gradle/publish-android.gradle' -description = 'microG service implementation for play-services-constellation' +description = 'microG implementation of play-services-constellation' dependencies { - implementation project(':play-services-base-core') - implementation project(':play-services-iid') - implementation project(':play-services-auth-base') - - implementation project(':play-services-droidguard') - implementation project(':play-services-tasks-ktx') - - api 'com.squareup.wire:wire-runtime:4.9.3' - api 'com.squareup.wire:wire-grpc-client:4.9.3' - api 'com.squareup.okhttp3:okhttp:4.12.0' + implementation project(':play-services-basement') kapt project(":safe-parcel-processor") } diff --git a/play-services-constellation/core/build.gradle b/play-services-constellation/core/build.gradle new file mode 100644 index 0000000000..c51c1f39ea --- /dev/null +++ b/play-services-constellation/core/build.gradle @@ -0,0 +1,64 @@ +/* + * SPDX-FileCopyrightText: 2026, microG Project Team + * SPDX-License-Identifier: Apache-2.0 + */ + +apply plugin: 'com.android.library' +apply plugin: 'maven-publish' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-parcelize' +apply plugin: 'kotlin-kapt' +apply plugin: 'signing' +apply plugin: 'com.squareup.wire' + +android { + namespace "org.microg.gms.constellation.core" + + compileSdkVersion androidCompileSdk + buildToolsVersion "$androidBuildVersionTools" + + defaultConfig { + versionName version + minSdkVersion androidMinSdk + targetSdkVersion androidTargetSdk + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = 1.8 + } +} + +wire { + kotlin { + rpcRole = 'client' + rpcCallStyle = 'suspending' + } +} + +apply from: '../../gradle/publish-android.gradle' + +description = 'microG service implementation for play-services-constellation' + +dependencies { + api project(':play-services-constellation') + + implementation project(':play-services-base-core') + implementation project(':play-services-iid') + implementation project(':play-services-auth-base') + + implementation project(':play-services-droidguard') + implementation project(':play-services-tasks-ktx') + + api "com.squareup.wire:wire-runtime:$wireVersion" + api "com.squareup.wire:wire-grpc-client:$wireVersion" + api "com.squareup.okhttp3:okhttp:$okHttpVersion" +} diff --git a/play-services-constellation/core/src/main/AndroidManifest.xml b/play-services-constellation/core/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..cbe65637d0 --- /dev/null +++ b/play-services-constellation/core/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + diff --git a/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt b/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt new file mode 100644 index 0000000000..3bd89b61e0 --- /dev/null +++ b/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt @@ -0,0 +1,30 @@ +package com.google.android.gms.constellation + +enum class VerificationStatus(val value: Int) { + SUPPORTED(1), + UNSUPPORTED_CARRIER(2), + UNSUPPORTED_API_VERSION(3), + UNSUPPORTED_SIM_NOT_READY(4); + + companion object { + fun fromInt(value: Int): VerificationStatus = when (value) { + 2 -> UNSUPPORTED_CARRIER + 3 -> UNSUPPORTED_API_VERSION + 4 -> UNSUPPORTED_SIM_NOT_READY + else -> SUPPORTED + } + } +} + +operator fun VerificationCapability.Companion.invoke( + verificationMethod: Int, + status: VerificationStatus +): VerificationCapability { + return VerificationCapability( + verificationMethod = verificationMethod, + statusValue = status.value + ) +} + +val VerificationCapability.status: VerificationStatus + get() = VerificationStatus.fromInt(statusValue) \ No newline at end of file diff --git a/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt new file mode 100644 index 0000000000..95c87e2688 --- /dev/null +++ b/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt @@ -0,0 +1,6 @@ +package com.google.android.gms.constellation + +import org.microg.gms.constellation.core.proto.VerificationMethod + +val VerifyPhoneNumberRequest.verificationMethods: List + get() = verificationMethodsValues.mapNotNull { VerificationMethod.fromValue(it) } \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/AuthManager.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt similarity index 98% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/AuthManager.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt index 148d585ed3..94b4bdb8a8 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/AuthManager.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import android.content.Context import android.util.Base64 diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationApiService.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationApiService.kt similarity index 99% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationApiService.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationApiService.kt index a6829f734b..f4ddbb4123 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationApiService.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationApiService.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import android.content.Context import android.os.Bundle diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationStateStore.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt similarity index 91% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationStateStore.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt index 5c6f07bd08..dc802dfacc 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/ConstellationStateStore.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import android.annotation.SuppressLint import android.content.Context @@ -6,14 +6,14 @@ import android.util.Base64 import androidx.core.content.edit import com.squareup.wire.Instant import okio.ByteString.Companion.toByteString -import org.microg.gms.constellation.proto.DroidguardToken -import org.microg.gms.constellation.proto.ProceedResponse -import org.microg.gms.constellation.proto.Consent -import org.microg.gms.constellation.proto.ConsentSource -import org.microg.gms.constellation.proto.ConsentVersion -import org.microg.gms.constellation.proto.ServerTimestamp -import org.microg.gms.constellation.proto.SyncResponse -import org.microg.gms.constellation.proto.VerificationToken +import org.microg.gms.constellation.core.proto.Consent +import org.microg.gms.constellation.core.proto.ConsentSource +import org.microg.gms.constellation.core.proto.ConsentVersion +import org.microg.gms.constellation.core.proto.DroidguardToken +import org.microg.gms.constellation.core.proto.ProceedResponse +import org.microg.gms.constellation.core.proto.ServerTimestamp +import org.microg.gms.constellation.core.proto.SyncResponse +import org.microg.gms.constellation.core.proto.VerificationToken private const val STATE_PREFS_NAME = "constellation_prefs" private const val TOKEN_PREFS_NAME = "com.google.android.gms.constellation" @@ -119,7 +119,8 @@ object ConstellationStateStore { } fun loadPnvrNoticeConsent(context: Context): Consent { - val value = statePrefs(context).getInt(KEY_PNVR_NOTICE_CONSENT, Consent.CONSENT_UNKNOWN.value) + val value = + statePrefs(context).getInt(KEY_PNVR_NOTICE_CONSENT, Consent.CONSENT_UNKNOWN.value) return Consent.fromValue(value) ?: Consent.CONSENT_UNKNOWN } diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GServices.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GServices.kt similarity index 95% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GServices.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GServices.kt index ca5b6acbb9..a8c3c64481 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GServices.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GServices.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import android.content.ContentResolver import android.net.Uri diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetIidToken.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt similarity index 96% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetIidToken.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt index 08775f8b9f..9ba0e0b8af 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetIidToken.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import android.content.Context import android.util.Log diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilities.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilities.kt similarity index 93% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilities.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilities.kt index 38fec9f3e8..72d4617f2e 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilities.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilities.kt @@ -1,6 +1,5 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core -import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.os.Build @@ -9,7 +8,6 @@ import android.telephony.TelephonyManager import android.util.Base64 import android.util.Log import androidx.annotation.RequiresApi -import androidx.annotation.RequiresPermission import androidx.core.content.getSystemService import com.google.android.gms.common.api.ApiMetadata import com.google.android.gms.common.api.Status @@ -19,6 +17,7 @@ import com.google.android.gms.constellation.SimCapability import com.google.android.gms.constellation.VerificationCapability import com.google.android.gms.constellation.VerificationStatus import com.google.android.gms.constellation.internal.IConstellationCallbacks +import com.google.android.gms.constellation.invoke import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.security.MessageDigest @@ -26,12 +25,6 @@ import java.security.MessageDigest private const val TAG = "GetPnvCapabilities" @SuppressLint("HardwareIds") -@RequiresPermission( - allOf = - [ - Manifest.permission.READ_PHONE_STATE, - "android.permission.READ_PRIVILEGED_PHONE_STATE"] -) @RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) suspend fun handleGetPnvCapabilities( context: Context, @@ -67,7 +60,9 @@ suspend fun handleGetPnvCapabilities( VerificationCapability( 9, when { - !GetPnvCapabilitiesApiPhenotype.FPNV_ALLOWED_CARRIER_IDS.contains(carrierId) -> + !GetPnvCapabilitiesApiPhenotype.FPNV_ALLOWED_CARRIER_IDS.contains( + carrierId + ) -> VerificationStatus.UNSUPPORTED_CARRIER telephonyManager.simState != TelephonyManager.SIM_STATE_READY -> diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilitiesApiPhenotype.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilitiesApiPhenotype.kt similarity index 69% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilitiesApiPhenotype.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilitiesApiPhenotype.kt index 814fae3f1c..9a7f8b21bd 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetPnvCapabilitiesApiPhenotype.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilitiesApiPhenotype.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core object GetPnvCapabilitiesApiPhenotype { val FPNV_ALLOWED_CARRIER_IDS = ArrayList() diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetVerifiedPhoneNumbers.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt similarity index 89% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetVerifiedPhoneNumbers.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt index b382bef5a8..5ab87e178c 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/GetVerifiedPhoneNumbers.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import android.content.Context import android.os.Bundle @@ -12,12 +12,12 @@ import com.google.android.gms.constellation.internal.IConstellationCallbacks import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okio.ByteString.Companion.toByteString -import org.microg.gms.constellation.proto.GetVerifiedPhoneNumbersRequest -import org.microg.gms.constellation.proto.GetVerifiedPhoneNumbersRequest.PhoneNumberSelection -import org.microg.gms.constellation.proto.IIDTokenAuth -import org.microg.gms.constellation.proto.TokenOption -import org.microg.gms.constellation.proto.VerifiedPhoneNumber as RpcVerifiedPhoneNumber +import org.microg.gms.constellation.core.proto.GetVerifiedPhoneNumbersRequest +import org.microg.gms.constellation.core.proto.GetVerifiedPhoneNumbersRequest.PhoneNumberSelection +import org.microg.gms.constellation.core.proto.IIDTokenAuth +import org.microg.gms.constellation.core.proto.TokenOption import java.util.UUID +import org.microg.gms.constellation.core.proto.VerifiedPhoneNumber as RpcVerifiedPhoneNumber private const val TAG = "GetVerifiedPhoneNumbers" @@ -79,7 +79,10 @@ internal suspend fun fetchVerifiedPhoneNumbers( } internal fun List.toVerifyPhoneNumberResponse(): VerifyPhoneNumberResponse { - return VerifyPhoneNumberResponse(map { it.toPhoneNumberVerification() }.toTypedArray(), Bundle.EMPTY) + return VerifyPhoneNumberResponse( + map { it.toPhoneNumberVerification() }.toTypedArray(), + Bundle.EMPTY + ) } private fun RpcVerifiedPhoneNumber.toPhoneNumberInfo(): PhoneNumberInfo { diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/IidTokenPhenotypes.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/IidTokenPhenotypes.kt similarity index 88% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/IidTokenPhenotypes.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/IidTokenPhenotypes.kt index a700f12f2f..984c902321 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/IidTokenPhenotypes.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/IidTokenPhenotypes.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core object IidTokenPhenotypes { const val ASTERISM_PROJECT_NUMBER = 496232013492 diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/RpcClient.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/RpcClient.kt similarity index 88% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/RpcClient.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/RpcClient.kt index f7a49733aa..57495656c3 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/RpcClient.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/RpcClient.kt @@ -1,12 +1,12 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import com.squareup.wire.GrpcClient import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Response import org.microg.gms.common.Constants -import org.microg.gms.constellation.proto.PhoneDeviceVerificationClient -import org.microg.gms.constellation.proto.PhoneNumberClient +import org.microg.gms.constellation.core.proto.PhoneDeviceVerificationClient +import org.microg.gms.constellation.core.proto.PhoneNumberClient private class AuthInterceptor : Interceptor { override fun intercept(chain: Interceptor.Chain): Response { diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationMappings.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt similarity index 94% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationMappings.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt index 55bbdda27e..62255e1fc0 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationMappings.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt @@ -1,11 +1,11 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import android.os.Bundle import com.google.android.gms.constellation.PhoneNumberVerification -import org.microg.gms.constellation.proto.Param -import org.microg.gms.constellation.proto.UnverifiedInfo -import org.microg.gms.constellation.proto.Verification -import org.microg.gms.constellation.proto.VerificationMethod +import org.microg.gms.constellation.core.proto.Param +import org.microg.gms.constellation.core.proto.UnverifiedInfo +import org.microg.gms.constellation.core.proto.Verification +import org.microg.gms.constellation.core.proto.VerificationMethod fun UnverifiedInfo.Reason.toVerificationStatus(): Verification.Status { return when (this) { diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationSettingsPhenotypes.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationSettingsPhenotypes.kt similarity index 76% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationSettingsPhenotypes.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationSettingsPhenotypes.kt index c51f0a279e..f8deaa5a7f 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerificationSettingsPhenotypes.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationSettingsPhenotypes.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core object VerificationSettingsPhenotypes { const val A2P_SMS_SIGNAL_GRANULARITY_HRS = 1L diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt similarity index 90% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumber.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index d0dc3b314c..1d07ea48c3 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core import android.content.Context import android.os.Build @@ -18,13 +18,13 @@ import com.squareup.wire.GrpcException import com.squareup.wire.GrpcStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.microg.gms.constellation.proto.SyncRequest -import org.microg.gms.constellation.proto.Verification -import org.microg.gms.constellation.proto.builders.RequestBuildContext -import org.microg.gms.constellation.proto.builders.buildImsiToSubscriptionInfoMap -import org.microg.gms.constellation.proto.builders.buildRequestContext -import org.microg.gms.constellation.proto.builders.invoke -import org.microg.gms.constellation.verification.ChallengeProcessor +import org.microg.gms.constellation.core.proto.SyncRequest +import org.microg.gms.constellation.core.proto.Verification +import org.microg.gms.constellation.core.proto.builders.RequestBuildContext +import org.microg.gms.constellation.core.proto.builders.buildImsiToSubscriptionInfoMap +import org.microg.gms.constellation.core.proto.builders.buildRequestContext +import org.microg.gms.constellation.core.proto.builders.invoke +import org.microg.gms.constellation.core.verification.ChallengeProcessor import java.util.UUID private const val TAG = "VerifyPhoneNumber" @@ -62,7 +62,7 @@ suspend fun handleVerifyPhoneNumberV1( targetedSims = emptyList(), silent = false, apiVersion = 2, - verificationTypes = emptyList() + verificationMethodsValues = emptyList() ) val policyId = bundle.getString("policy_id", "") val mode = bundle.getInt("verification_mode", 0) @@ -111,7 +111,7 @@ suspend fun handleVerifyPhoneNumberSingleUse( targetedSims = emptyList(), silent = false, apiVersion = 2, - verificationTypes = emptyList() + verificationMethodsValues = emptyList() ) handleVerifyPhoneNumberRequest( @@ -200,12 +200,20 @@ private suspend fun handleVerifyPhoneNumberRequest( Log.e(TAG, "verifyPhoneNumber failed", e) when (readCallbackMode) { ReadCallbackMode.LEGACY -> { - callbacks.onPhoneNumberVerified(Status.INTERNAL_ERROR, emptyList(), ApiMetadata.DEFAULT) + callbacks.onPhoneNumberVerified( + Status.INTERNAL_ERROR, + emptyList(), + ApiMetadata.DEFAULT + ) } ReadCallbackMode.NONE -> { if (legacyCallbackOnFullFlow) { - callbacks.onPhoneNumberVerified(Status.INTERNAL_ERROR, emptyList(), ApiMetadata.DEFAULT) + callbacks.onPhoneNumberVerified( + Status.INTERNAL_ERROR, + emptyList(), + ApiMetadata.DEFAULT + ) } else { callbacks.onPhoneNumberVerificationsCompleted( Status.INTERNAL_ERROR, @@ -300,7 +308,10 @@ private suspend fun executeSyncFlow( if (e.grpcStatus == GrpcStatus.PERMISSION_DENIED || e.grpcStatus == GrpcStatus.UNAUTHENTICATED ) { - Log.w(TAG, "Suspicious client status ${e.grpcStatus.name}. Clearing DroidGuard cache...") + Log.w( + TAG, + "Suspicious client status ${e.grpcStatus.name}. Clearing DroidGuard cache..." + ) ConstellationStateStore.clearDroidGuardToken(context) } throw e diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumberApiPhenotypes.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumberApiPhenotypes.kt similarity index 97% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumberApiPhenotypes.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumberApiPhenotypes.kt index 93faca2dd1..ceebfcb771 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/VerifyPhoneNumberApiPhenotypes.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumberApiPhenotypes.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation +package org.microg.gms.constellation.core object VerifyPhoneNumberApiPhenotypes { val PACKAGES_ALLOWED_TO_CALL = listOf( diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/ClientInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt similarity index 86% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/ClientInfoBuilder.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt index 99e224cb3d..bc97172d63 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/ClientInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt @@ -1,37 +1,34 @@ -package org.microg.gms.constellation.proto.builders +package org.microg.gms.constellation.core.proto.builders -import android.Manifest import android.annotation.SuppressLint import android.content.Context import android.os.Build import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.util.Log -import androidx.annotation.RequiresPermission import androidx.core.content.edit import androidx.core.content.getSystemService import com.google.android.gms.tasks.await import okio.ByteString.Companion.toByteString import org.microg.gms.common.Constants -import org.microg.gms.constellation.AuthManager -import org.microg.gms.constellation.ConstellationStateStore -import org.microg.gms.constellation.GServices -import org.microg.gms.constellation.proto.ClientInfo -import org.microg.gms.constellation.proto.CountryInfo -import org.microg.gms.constellation.proto.DeviceID -import org.microg.gms.constellation.proto.DeviceType -import org.microg.gms.constellation.proto.DroidGuardSignals -import org.microg.gms.constellation.proto.GaiaSignals -import org.microg.gms.constellation.proto.GaiaToken -import org.microg.gms.constellation.proto.NetworkSignal -import org.microg.gms.constellation.proto.SimOperatorInfo -import org.microg.gms.constellation.proto.UserProfileType +import org.microg.gms.constellation.core.AuthManager +import org.microg.gms.constellation.core.ConstellationStateStore +import org.microg.gms.constellation.core.GServices +import org.microg.gms.constellation.core.proto.ClientInfo +import org.microg.gms.constellation.core.proto.CountryInfo +import org.microg.gms.constellation.core.proto.DeviceID +import org.microg.gms.constellation.core.proto.DeviceType +import org.microg.gms.constellation.core.proto.DroidGuardSignals +import org.microg.gms.constellation.core.proto.GaiaSignals +import org.microg.gms.constellation.core.proto.GaiaToken +import org.microg.gms.constellation.core.proto.NetworkSignal +import org.microg.gms.constellation.core.proto.SimOperatorInfo +import org.microg.gms.constellation.core.proto.UserProfileType import java.util.Locale private const val TAG = "ClientInfoBuilder" private const val PREFS_NAME = "constellation_prefs" -@RequiresPermission(Manifest.permission.GET_ACCOUNTS) @SuppressLint("HardwareIds") suspend operator fun ClientInfo.Companion.invoke(context: Context, iidToken: String): ClientInfo { return ClientInfo( @@ -43,7 +40,6 @@ suspend operator fun ClientInfo.Companion.invoke(context: Context, iidToken: Str ) } -@RequiresPermission(Manifest.permission.GET_ACCOUNTS) @SuppressLint("HardwareIds") suspend operator fun ClientInfo.Companion.invoke( context: Context, diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/CommonBuilders.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt similarity index 69% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/CommonBuilders.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt index b05e0dc40c..d2f6f1e0da 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/CommonBuilders.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt @@ -1,19 +1,17 @@ -package org.microg.gms.constellation.proto.builders +package org.microg.gms.constellation.core.proto.builders -import android.Manifest import android.content.Context import android.os.Bundle -import androidx.annotation.RequiresPermission import okio.ByteString.Companion.toByteString -import org.microg.gms.constellation.AuthManager -import org.microg.gms.constellation.proto.AuditToken -import org.microg.gms.constellation.proto.AuditTokenMetadata -import org.microg.gms.constellation.proto.AuditUuid -import org.microg.gms.constellation.proto.ClientInfo -import org.microg.gms.constellation.proto.DeviceID -import org.microg.gms.constellation.proto.Param -import org.microg.gms.constellation.proto.RequestHeader -import org.microg.gms.constellation.proto.RequestTrigger +import org.microg.gms.constellation.core.AuthManager +import org.microg.gms.constellation.core.proto.AuditToken +import org.microg.gms.constellation.core.proto.AuditTokenMetadata +import org.microg.gms.constellation.core.proto.AuditUuid +import org.microg.gms.constellation.core.proto.ClientInfo +import org.microg.gms.constellation.core.proto.DeviceID +import org.microg.gms.constellation.core.proto.Param +import org.microg.gms.constellation.core.proto.RequestHeader +import org.microg.gms.constellation.core.proto.RequestTrigger fun Param.Companion.getList(extras: Bundle?): List { if (extras == null) return emptyList() @@ -21,7 +19,7 @@ fun Param.Companion.getList(extras: Bundle?): List { val ignoreKeys = setOf("consent_variant_key", "consent_trigger_key", "gaia_access_token") for (key in extras.keySet()) { if (key !in ignoreKeys) { - extras.get(key)?.toString()?.let { value -> + extras.getString(key)?.let { value -> params.add(Param(key = key, value_ = value)) } } @@ -41,7 +39,6 @@ fun AuditToken.Companion.generate(): AuditToken { ) } -@RequiresPermission(Manifest.permission.GET_ACCOUNTS) suspend operator fun RequestHeader.Companion.invoke( context: Context, sessionId: String, @@ -52,7 +49,7 @@ suspend operator fun RequestHeader.Companion.invoke( val authManager = if (includeClientAuth) AuthManager.get(context) else null val clientAuth = if (includeClientAuth) { val (signature, timestamp) = authManager!!.signIidToken(buildContext.iidToken) - org.microg.gms.constellation.proto.ClientAuth( + org.microg.gms.constellation.core.proto.ClientAuth( device_id = DeviceID(context, buildContext.iidToken), signature = signature.toByteString(), sign_timestamp = timestamp diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/GaiaInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt similarity index 85% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/GaiaInfoBuilder.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt index dd2b34b42d..febce16e53 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/GaiaInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt @@ -1,24 +1,21 @@ -package org.microg.gms.constellation.proto.builders +package org.microg.gms.constellation.core.proto.builders -import android.Manifest import android.accounts.AccountManager import android.content.Context import android.util.Log -import androidx.annotation.RequiresPermission import com.squareup.wire.Instant import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import org.microg.gms.constellation.proto.GaiaAccountSignalType -import org.microg.gms.constellation.proto.GaiaSignalEntry -import org.microg.gms.constellation.proto.GaiaSignals -import org.microg.gms.constellation.proto.GaiaToken +import org.microg.gms.constellation.core.proto.GaiaAccountSignalType +import org.microg.gms.constellation.core.proto.GaiaSignalEntry +import org.microg.gms.constellation.core.proto.GaiaSignals +import org.microg.gms.constellation.core.proto.GaiaToken import java.security.MessageDigest import kotlin.math.abs import kotlin.math.absoluteValue private const val TAG = "GaiaInfoBuilder" -@RequiresPermission(Manifest.permission.GET_ACCOUNTS) operator fun GaiaSignals.Companion.invoke(context: Context): GaiaSignals? { val entries = mutableListOf() try { diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/RequestBuildContext.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt similarity index 70% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/RequestBuildContext.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt index 5ac41bba97..c581237c17 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/RequestBuildContext.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt @@ -1,8 +1,8 @@ -package org.microg.gms.constellation.proto.builders +package org.microg.gms.constellation.core.proto.builders import android.content.Context -import org.microg.gms.constellation.AuthManager -import org.microg.gms.constellation.proto.GaiaToken +import org.microg.gms.constellation.core.AuthManager +import org.microg.gms.constellation.core.proto.GaiaToken data class RequestBuildContext( val iidToken: String, diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/SyncRequestBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt similarity index 85% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/SyncRequestBuilder.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt index 7d86e3964f..61bc46d639 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/SyncRequestBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.proto.builders +package org.microg.gms.constellation.core.proto.builders import android.annotation.SuppressLint import android.content.Context @@ -10,22 +10,23 @@ import android.util.Log import androidx.annotation.RequiresApi import androidx.core.content.getSystemService import com.google.android.gms.constellation.VerifyPhoneNumberRequest -import org.microg.gms.constellation.ConstellationStateStore -import org.microg.gms.constellation.proto.ChallengePreference -import org.microg.gms.constellation.proto.ChallengePreferenceMetadata -import org.microg.gms.constellation.proto.IdTokenRequest -import org.microg.gms.constellation.proto.Param -import org.microg.gms.constellation.proto.RequestHeader -import org.microg.gms.constellation.proto.RequestTrigger -import org.microg.gms.constellation.proto.SIMAssociation -import org.microg.gms.constellation.proto.SIMSlotInfo -import org.microg.gms.constellation.proto.SyncRequest -import org.microg.gms.constellation.proto.TelephonyInfo -import org.microg.gms.constellation.proto.TelephonyPhoneNumberType -import org.microg.gms.constellation.proto.Verification -import org.microg.gms.constellation.proto.VerificationAssociation -import org.microg.gms.constellation.proto.VerificationParam -import org.microg.gms.constellation.proto.VerificationPolicy +import com.google.android.gms.constellation.verificationMethods +import org.microg.gms.constellation.core.ConstellationStateStore +import org.microg.gms.constellation.core.proto.ChallengePreference +import org.microg.gms.constellation.core.proto.ChallengePreferenceMetadata +import org.microg.gms.constellation.core.proto.IdTokenRequest +import org.microg.gms.constellation.core.proto.Param +import org.microg.gms.constellation.core.proto.RequestHeader +import org.microg.gms.constellation.core.proto.RequestTrigger +import org.microg.gms.constellation.core.proto.SIMAssociation +import org.microg.gms.constellation.core.proto.SIMSlotInfo +import org.microg.gms.constellation.core.proto.SyncRequest +import org.microg.gms.constellation.core.proto.TelephonyInfo +import org.microg.gms.constellation.core.proto.TelephonyPhoneNumberType +import org.microg.gms.constellation.core.proto.Verification +import org.microg.gms.constellation.core.proto.VerificationAssociation +import org.microg.gms.constellation.core.proto.VerificationParam +import org.microg.gms.constellation.core.proto.VerificationPolicy private const val TAG = "SyncRequestBuilder" diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/TelephonyInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt similarity index 94% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/TelephonyInfoBuilder.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt index 62e4ada9b7..4bef0d7581 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/proto/builders/TelephonyInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.proto.builders +package org.microg.gms.constellation.core.proto.builders import android.annotation.SuppressLint import android.content.Context @@ -9,10 +9,10 @@ import android.telephony.TelephonyManager import android.util.Base64 import android.util.Log import androidx.core.content.getSystemService -import org.microg.gms.constellation.proto.NetworkSignal -import org.microg.gms.constellation.proto.SimNetworkInfo -import org.microg.gms.constellation.proto.SimOperatorInfo -import org.microg.gms.constellation.proto.TelephonyInfo +import org.microg.gms.constellation.core.proto.NetworkSignal +import org.microg.gms.constellation.core.proto.SimNetworkInfo +import org.microg.gms.constellation.core.proto.SimOperatorInfo +import org.microg.gms.constellation.core.proto.TelephonyInfo import java.security.MessageDigest private const val TAG = "TelephonyInfoBuilder" @@ -107,6 +107,7 @@ fun NetworkSignal.Companion.getList(context: Context): List { return connectivityInfos } +@SuppressLint("HardwareIds") fun SimOperatorInfo.Companion.getList(context: Context): List { val infos = mutableListOf() try { diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/CarrierIdVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt similarity index 90% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/CarrierIdVerifier.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt index b7c138638f..f7159238c1 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/CarrierIdVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt @@ -1,14 +1,14 @@ -package org.microg.gms.constellation.verification +package org.microg.gms.constellation.core.verification import android.content.Context import android.os.Build import android.telephony.TelephonyManager import android.util.Log import androidx.core.content.getSystemService -import org.microg.gms.constellation.proto.CarrierIdChallengeResponse -import org.microg.gms.constellation.proto.CarrierIdError -import org.microg.gms.constellation.proto.Challenge -import org.microg.gms.constellation.proto.ChallengeResponse +import org.microg.gms.constellation.core.proto.CarrierIdChallengeResponse +import org.microg.gms.constellation.core.proto.CarrierIdError +import org.microg.gms.constellation.core.proto.Challenge +import org.microg.gms.constellation.core.proto.ChallengeResponse private const val TAG = "CarrierIdVerifier" diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt similarity index 88% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ChallengeProcessor.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index e731f5112d..a5775cdedb 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -1,21 +1,21 @@ -package org.microg.gms.constellation.verification +package org.microg.gms.constellation.core.verification import android.content.Context import android.telephony.SubscriptionInfo import android.util.Log import com.squareup.wire.GrpcException import com.squareup.wire.GrpcStatus -import org.microg.gms.constellation.ConstellationStateStore -import org.microg.gms.constellation.RpcClient -import org.microg.gms.constellation.getState -import org.microg.gms.constellation.proto.ChallengeResponse -import org.microg.gms.constellation.proto.ProceedRequest -import org.microg.gms.constellation.proto.RequestHeader -import org.microg.gms.constellation.proto.RequestTrigger -import org.microg.gms.constellation.proto.Verification -import org.microg.gms.constellation.proto.VerificationMethod -import org.microg.gms.constellation.proto.builders.RequestBuildContext -import org.microg.gms.constellation.proto.builders.invoke +import org.microg.gms.constellation.core.ConstellationStateStore +import org.microg.gms.constellation.core.RpcClient +import org.microg.gms.constellation.core.getState +import org.microg.gms.constellation.core.proto.ChallengeResponse +import org.microg.gms.constellation.core.proto.ProceedRequest +import org.microg.gms.constellation.core.proto.RequestHeader +import org.microg.gms.constellation.core.proto.RequestTrigger +import org.microg.gms.constellation.core.proto.Verification +import org.microg.gms.constellation.core.proto.VerificationMethod +import org.microg.gms.constellation.core.proto.builders.RequestBuildContext +import org.microg.gms.constellation.core.proto.builders.invoke object ChallengeProcessor { private const val TAG = "ChallengeProcessor" diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MoSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt similarity index 95% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MoSmsVerifier.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt index f9a6f6bc4a..a5c914b595 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MoSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.verification +package org.microg.gms.constellation.core.verification import android.Manifest import android.app.PendingIntent @@ -11,18 +11,17 @@ import android.os.Build import android.telephony.SmsManager import android.telephony.SubscriptionManager import android.util.Log -import androidx.annotation.RequiresPermission import androidx.core.content.getSystemService import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull -import org.microg.gms.constellation.proto.ChallengeResponse -import org.microg.gms.constellation.proto.MoChallenge -import org.microg.gms.constellation.proto.MOChallengeResponseData +import org.microg.gms.constellation.core.proto.ChallengeResponse +import org.microg.gms.constellation.core.proto.MOChallengeResponseData +import org.microg.gms.constellation.core.proto.MoChallenge import java.util.UUID import kotlin.coroutines.resume private const val TAG = "MoSmsVerifier" -private const val ACTION_MO_SMS_SENT = "org.microg.gms.constellation.MO_SMS_SENT" +private const val ACTION_MO_SMS_SENT = "org.microg.gms.constellation.coreMO_SMS_SENT" class MoSmsVerifier(private val context: Context, private val subId: Int) { suspend fun verify(challenge: MoChallenge?): ChallengeResponse? { @@ -167,7 +166,6 @@ class MoSmsVerifier(private val context: Context, private val subId: Int) { ) } - @RequiresPermission(Manifest.permission.READ_PHONE_STATE) private fun isActiveSubscription(subscriptionId: Int): Boolean { if (subscriptionId == -1) return false return try { diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MtSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt similarity index 94% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MtSmsVerifier.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt index e757d60a07..5d5cc838f7 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/MtSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.verification +package org.microg.gms.constellation.core.verification import android.content.BroadcastReceiver import android.content.Context @@ -9,9 +9,9 @@ import android.provider.Telephony import android.util.Log import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull -import org.microg.gms.constellation.proto.ChallengeResponse -import org.microg.gms.constellation.proto.MTChallenge -import org.microg.gms.constellation.proto.MTChallengeResponseData +import org.microg.gms.constellation.core.proto.ChallengeResponse +import org.microg.gms.constellation.core.proto.MTChallenge +import org.microg.gms.constellation.core.proto.MTChallengeResponseData import kotlin.coroutines.resume private const val TAG = "MtSmsVerifier" diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/RegisteredSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt similarity index 92% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/RegisteredSmsVerifier.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt index 301527f0d7..3e1ea8d37e 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/RegisteredSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.verification +package org.microg.gms.constellation.core.verification import android.content.Context import android.provider.Telephony @@ -7,12 +7,12 @@ import android.telephony.TelephonyManager import android.util.Log import androidx.core.content.getSystemService import okio.ByteString.Companion.toByteString -import org.microg.gms.constellation.VerificationSettingsPhenotypes -import org.microg.gms.constellation.proto.ChallengeResponse -import org.microg.gms.constellation.proto.RegisteredSmsChallenge -import org.microg.gms.constellation.proto.RegisteredSmsChallengeResponse -import org.microg.gms.constellation.proto.RegisteredSmsChallengeResponseItem -import org.microg.gms.constellation.proto.RegisteredSmsChallengeResponsePayload +import org.microg.gms.constellation.core.VerificationSettingsPhenotypes +import org.microg.gms.constellation.core.proto.ChallengeResponse +import org.microg.gms.constellation.core.proto.RegisteredSmsChallenge +import org.microg.gms.constellation.core.proto.RegisteredSmsChallengeResponse +import org.microg.gms.constellation.core.proto.RegisteredSmsChallengeResponseItem +import org.microg.gms.constellation.core.proto.RegisteredSmsChallengeResponsePayload import java.nio.charset.StandardCharsets import java.security.MessageDigest import java.util.concurrent.TimeUnit diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/Ts43Verifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt similarity index 94% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/Ts43Verifier.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt index 4030817bcc..4066a5dffb 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/Ts43Verifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.verification +package org.microg.gms.constellation.core.verification import android.content.Context import android.os.Build @@ -13,25 +13,24 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.toRequestBody import org.json.JSONObject -import org.microg.gms.constellation.proto.ChallengeResponse -import org.microg.gms.constellation.proto.ClientChallengeResponse -import org.microg.gms.constellation.proto.OdsaOperation -import org.microg.gms.constellation.proto.ServerChallengeResponse -import org.microg.gms.constellation.proto.ServiceEntitlementRequest -import org.microg.gms.constellation.proto.Ts43Challenge -import org.microg.gms.constellation.proto.Ts43ChallengeResponse -import org.microg.gms.constellation.proto.Ts43ChallengeResponseError -import org.microg.gms.constellation.proto.Ts43ChallengeResponseStatus -import org.microg.gms.constellation.verification.ts43.EapAkaService -import org.microg.gms.constellation.verification.ts43.builder -import org.microg.gms.constellation.verification.ts43.userAgent +import org.microg.gms.constellation.core.proto.ChallengeResponse +import org.microg.gms.constellation.core.proto.ClientChallengeResponse +import org.microg.gms.constellation.core.proto.OdsaOperation +import org.microg.gms.constellation.core.proto.ServerChallengeResponse +import org.microg.gms.constellation.core.proto.ServiceEntitlementRequest +import org.microg.gms.constellation.core.proto.Ts43Challenge +import org.microg.gms.constellation.core.proto.Ts43ChallengeResponse +import org.microg.gms.constellation.core.proto.Ts43ChallengeResponseError +import org.microg.gms.constellation.core.proto.Ts43ChallengeResponseStatus +import org.microg.gms.constellation.core.verification.ts43.EapAkaService +import org.microg.gms.constellation.core.verification.ts43.builder +import org.microg.gms.constellation.core.verification.ts43.userAgent import org.xml.sax.InputSource import java.io.IOException import java.io.StringReader import java.util.Locale import java.util.concurrent.TimeUnit import javax.xml.parsers.DocumentBuilderFactory -import kotlin.text.ifEmpty private const val TAG = "Ts43Verifier" diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/EapAkaService.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/EapAkaService.kt similarity index 99% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/EapAkaService.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/EapAkaService.kt index 9e271e5a4f..47fae2cb31 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/EapAkaService.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/EapAkaService.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.verification.ts43 +package org.microg.gms.constellation.core.verification.ts43 import android.os.Build import android.telephony.TelephonyManager diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/Fips186Prf.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/Fips186Prf.kt similarity index 98% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/Fips186Prf.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/Fips186Prf.kt index 0f2b138ebe..618cb719cb 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/Fips186Prf.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/Fips186Prf.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.verification.ts43 +package org.microg.gms.constellation.core.verification.ts43 import java.security.MessageDigest import java.security.NoSuchAlgorithmException diff --git a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/ServiceEntitlementExtension.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt similarity index 97% rename from play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/ServiceEntitlementExtension.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt index 71f9e4c87b..69e7e846cb 100644 --- a/play-services-constellation/src/main/kotlin/org/microg/gms/constellation/verification/ts43/ServiceEntitlementExtension.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt @@ -1,11 +1,11 @@ -package org.microg.gms.constellation.verification.ts43 +package org.microg.gms.constellation.core.verification.ts43 import android.content.Context import android.telephony.TelephonyManager import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl -import org.microg.gms.constellation.proto.OdsaOperation -import org.microg.gms.constellation.proto.ServiceEntitlementRequest +import org.microg.gms.constellation.core.proto.OdsaOperation +import org.microg.gms.constellation.core.proto.ServiceEntitlementRequest fun ServiceEntitlementRequest.builder( telephonyManager: TelephonyManager, diff --git a/play-services-constellation/src/main/proto/constellation.proto b/play-services-constellation/core/src/main/proto/constellation.proto similarity index 99% rename from play-services-constellation/src/main/proto/constellation.proto rename to play-services-constellation/core/src/main/proto/constellation.proto index 29a8bd027a..93a10c652c 100644 --- a/play-services-constellation/src/main/proto/constellation.proto +++ b/play-services-constellation/core/src/main/proto/constellation.proto @@ -1,5 +1,5 @@ syntax = "proto3"; -option java_package = "org.microg.gms.constellation.proto"; +option java_package = "org.microg.gms.constellation.core.proto"; package google.internal.communications.phonedeviceverification.v1; diff --git a/play-services-constellation/src/main/AndroidManifest.xml b/play-services-constellation/src/main/AndroidManifest.xml index bf92ddcaf3..670e962e03 100644 --- a/play-services-constellation/src/main/AndroidManifest.xml +++ b/play-services-constellation/src/main/AndroidManifest.xml @@ -1,17 +1,6 @@ diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt index 4598d98bb1..fe80003950 100644 --- a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt +++ b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt @@ -52,36 +52,12 @@ data class SimCapability @Constructor constructor( @SafeParcelable.Class data class VerificationCapability @Constructor constructor( @JvmField @Param(1) @Field(1) val verificationMethod: Int, - @JvmField @Param(2) @Field(2) val statusValue: Int // Enums are typically passed as Ints in SafeParcelable + @JvmField @Param(2) @Field(2) val statusValue: Int ) : AbstractSafeParcelable() { - constructor(verificationMethod: Int, status: VerificationStatus) : this( - verificationMethod, - status.value - ) - - val status: VerificationStatus - get() = VerificationStatus.fromInt(statusValue) - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) companion object { @JvmField val CREATOR = findCreator(VerificationCapability::class.java) } -} - -enum class VerificationStatus(val value: Int) { - SUPPORTED(1), - UNSUPPORTED_CARRIER(2), - UNSUPPORTED_API_VERSION(3), - UNSUPPORTED_SIM_NOT_READY(4); - - companion object { - fun fromInt(value: Int): VerificationStatus = when (value) { - 2 -> UNSUPPORTED_CARRIER - 3 -> UNSUPPORTED_API_VERSION - 4 -> UNSUPPORTED_SIM_NOT_READY - else -> SUPPORTED - } - } -} +} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt index 459a05b3cd..f16110f7f9 100644 --- a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt +++ b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt @@ -7,7 +7,6 @@ import com.google.android.gms.common.internal.safeparcel.SafeParcelable import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param -import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter @SafeParcelable.Class data class PhoneNumberInfo @Constructor constructor( diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt index e2f159a44c..c2a0ef8115 100644 --- a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt +++ b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt @@ -8,7 +8,6 @@ import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Construc import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter -import org.microg.gms.constellation.proto.VerificationMethod @SafeParcelable.Class data class VerifyPhoneNumberRequest @Constructor constructor( @@ -19,11 +18,8 @@ data class VerifyPhoneNumberRequest @Constructor constructor( @JvmField @Param(5) @Field(5) val targetedSims: List, @JvmField @Param(6) @Field(6) val silent: Boolean, @JvmField @Param(7) @Field(7) val apiVersion: Int, - @JvmField @Param(8) @Field(8) val verificationTypes: List + @JvmField @Param(8) @Field(8) val verificationMethodsValues: List ) : AbstractSafeParcelable() { - val verificationMethods: List - get() = verificationTypes.mapNotNull { VerificationMethod.fromValue(it) } - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) companion object { diff --git a/play-services-core/build.gradle b/play-services-core/build.gradle index 47c2dc6553..362e39c76a 100644 --- a/play-services-core/build.gradle +++ b/play-services-core/build.gradle @@ -32,6 +32,7 @@ dependencies { implementation project(':play-services-cast-core') implementation project(':play-services-cast-framework-core') implementation project(':play-services-conscrypt-provider-core') + implementation project(':play-services-constellation-core') implementation project(':play-services-cronet-core') implementation project(':play-services-droidguard-core') implementation project(':play-services-fido-core') @@ -59,7 +60,6 @@ dependencies { implementation project(':play-services-auth-base') implementation project(':play-services-auth') implementation project(':play-services-clearcut') - implementation project(':play-services-constellation') implementation project(':play-services-drive') implementation project(':play-services-games') implementation project(':play-services-maps') diff --git a/play-services-core/src/main/AndroidManifest.xml b/play-services-core/src/main/AndroidManifest.xml index 2d9e8447d6..a29fff819c 100644 --- a/play-services-core/src/main/AndroidManifest.xml +++ b/play-services-core/src/main/AndroidManifest.xml @@ -179,9 +179,6 @@ android:name="android.hardware.camera" android:required="false" /> - - - - - - - - diff --git a/settings.gradle b/settings.gradle index bc68c6a7b1..9fcbcea289 100644 --- a/settings.gradle +++ b/settings.gradle @@ -93,6 +93,7 @@ sublude ':play-services-cast:core' sublude ':play-services-cast-framework:core' include ':play-services-chimera-core' include ':play-services-conscrypt-provider-core' +sublude ':play-services-constellation:core' sublude ':play-services-cronet:core' sublude ':play-services-droidguard:core' sublude ':play-services-fido:core' From 08b36eba647b0c704aca1975251815efada997a5 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Thu, 26 Mar 2026 01:10:07 -0400 Subject: [PATCH 03/31] Refactor `org.microg.gms.constellation.core.verification` --- .../core/verification/CarrierIdVerifier.kt | 119 ++++---- .../core/verification/ChallengeProcessor.kt | 38 +-- .../core/verification/MoSmsVerifier.kt | 287 +++++++++--------- .../core/verification/MtSmsVerifier.kt | 118 ++++--- .../verification/RegisteredSmsVerifier.kt | 243 ++++++++------- .../core/verification/Ts43Verifier.kt | 170 +++++------ 6 files changed, 468 insertions(+), 507 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt index f7159238c1..53c1bcc72d 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt @@ -12,73 +12,74 @@ import org.microg.gms.constellation.core.proto.ChallengeResponse private const val TAG = "CarrierIdVerifier" -class CarrierIdVerifier(private val context: Context, private val subId: Int) { - fun verify(challenge: Challenge?): ChallengeResponse? { - val carrierChallenge = challenge?.carrier_id_challenge ?: return null - val challengeData = carrierChallenge.isim_request.takeIf { it.isNotEmpty() } - ?: return failure( - CarrierIdError.CARRIER_ID_ERROR_UNKNOWN_ERROR, - "Carrier challenge data missing" - ) - if (subId == -1) return failure( - CarrierIdError.CARRIER_ID_ERROR_NO_SIM, - "No active subscription for carrier auth" +fun Challenge.verifyCarrierId(context: Context, subId: Int): ChallengeResponse { + val carrierChallenge = carrier_id_challenge ?: return failure( + CarrierIdError.CARRIER_ID_ERROR_UNKNOWN_ERROR, + "Carrier challenge data missing" + ) + val challengeData = carrierChallenge.isim_request.takeIf { it.isNotEmpty() } + ?: return failure( + CarrierIdError.CARRIER_ID_ERROR_UNKNOWN_ERROR, + "Carrier challenge data missing" ) + if (subId == -1) return failure( + CarrierIdError.CARRIER_ID_ERROR_NO_SIM, + "No active subscription for carrier auth" + ) - val appType = carrierChallenge.app_type - val authType = carrierChallenge.auth_type - - return try { - val telephonyManager = context.getSystemService() - ?: return failure( - CarrierIdError.CARRIER_ID_ERROR_NOT_SUPPORTED, - "TelephonyManager unavailable" - ) - val targetManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - telephonyManager.createForSubscriptionId(subId) - } else { - telephonyManager - } + val appType = carrierChallenge.app_type + val authType = carrierChallenge.auth_type - val response = targetManager.getIccAuthentication(appType, authType, challengeData) - if (response.isNullOrEmpty()) { - failure(CarrierIdError.CARRIER_ID_ERROR_NULL_RESPONSE, "Null ISIM response") - } else { - ChallengeResponse( - carrier_id_response = CarrierIdChallengeResponse( - isim_response = response, - carrier_id_error = CarrierIdError.CARRIER_ID_ERROR_NO_ERROR - ) - ) - } - } catch (e: SecurityException) { - Log.w(TAG, "Unable to read subscription for carrier auth", e) - failure( - CarrierIdError.CARRIER_ID_ERROR_UNABLE_TO_READ_SUBSCRIPTION, - e.message ?: "SecurityException" - ) - } catch (e: UnsupportedOperationException) { - Log.w(TAG, "Carrier auth API unavailable", e) - failure( + return try { + val telephonyManager = context.getSystemService() + ?: return failure( CarrierIdError.CARRIER_ID_ERROR_NOT_SUPPORTED, - e.message ?: "UnsupportedOperationException" - ) - } catch (e: Exception) { - Log.e(TAG, "Carrier auth failed", e) - failure( - CarrierIdError.CARRIER_ID_ERROR_REFLECTION_ERROR, - e.message ?: "Reflection or platform error" + "TelephonyManager unavailable" ) + val targetManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + telephonyManager.createForSubscriptionId(subId) + } else { + telephonyManager } - } - private fun failure(status: CarrierIdError, message: String): ChallengeResponse { - Log.w(TAG, message) - return ChallengeResponse( - carrier_id_response = CarrierIdChallengeResponse( - isim_response = "", - carrier_id_error = status + val response = targetManager.getIccAuthentication(appType, authType, challengeData) + if (response.isNullOrEmpty()) { + failure(CarrierIdError.CARRIER_ID_ERROR_NULL_RESPONSE, "Null ISIM response") + } else { + ChallengeResponse( + carrier_id_response = CarrierIdChallengeResponse( + isim_response = response, + carrier_id_error = CarrierIdError.CARRIER_ID_ERROR_NO_ERROR + ) ) + } + } catch (e: SecurityException) { + Log.w(TAG, "Unable to read subscription for carrier auth", e) + failure( + CarrierIdError.CARRIER_ID_ERROR_UNABLE_TO_READ_SUBSCRIPTION, + e.message ?: "SecurityException" + ) + } catch (e: UnsupportedOperationException) { + Log.w(TAG, "Carrier auth API unavailable", e) + failure( + CarrierIdError.CARRIER_ID_ERROR_NOT_SUPPORTED, + e.message ?: "UnsupportedOperationException" + ) + } catch (e: Exception) { + Log.e(TAG, "Carrier auth failed", e) + failure( + CarrierIdError.CARRIER_ID_ERROR_REFLECTION_ERROR, + e.message ?: "Reflection or platform error" ) } } + +private fun failure(status: CarrierIdError, message: String): ChallengeResponse { + Log.w(TAG, message) + return ChallengeResponse( + carrier_id_response = CarrierIdChallengeResponse( + isim_response = "", + carrier_id_error = status + ) + ) +} diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index a5775cdedb..ac79655766 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -70,35 +70,25 @@ object ChallengeProcessor { val info = imsiToInfoMap[challengeImsi] val subId = info?.subscriptionId ?: -1 - val challengeResponse: ChallengeResponse? = when { - challenge.type == VerificationMethod.TS43 -> Ts43Verifier( - context, - subId - ).verify(challenge.ts43_challenge) - - challenge.type == VerificationMethod.CARRIER_ID && challenge.ts43_challenge != null -> - Ts43Verifier(context, subId).verify(challenge.ts43_challenge) + val challengeResponse: ChallengeResponse? = when (challenge.type) { + VerificationMethod.TS43 -> challenge.ts43_challenge?.verify(context, subId) + + VerificationMethod.CARRIER_ID -> { + if (challenge.ts43_challenge != null) { + challenge.ts43_challenge.verify(context, subId) + } else { + challenge.verifyCarrierId(context, subId) + } + } - challenge.type == VerificationMethod.CARRIER_ID -> - CarrierIdVerifier(context, subId).verify(challenge) + VerificationMethod.MT_SMS -> challenge.mt_challenge?.verify(context, subId) - challenge.type == VerificationMethod.MT_SMS -> MtSmsVerifier( - context, - subId - ).verify(challenge.mt_challenge) + VerificationMethod.MO_SMS -> challenge.mo_challenge?.verify(context, subId) - challenge.type == VerificationMethod.MO_SMS -> MoSmsVerifier( + VerificationMethod.REGISTERED_SMS -> challenge.registered_sms_challenge?.verify( context, subId - ).verify(challenge.mo_challenge) - - challenge.type == VerificationMethod.REGISTERED_SMS -> - RegisteredSmsVerifier(context, subId).verify(challenge.registered_sms_challenge) - - challenge.type == VerificationMethod.FLASH_CALL -> { - Log.w(TAG, "Flash call verification is unavailable on this build") - null - } + ) else -> { Log.w(TAG, "Unsupported verification method: ${challenge.type}") diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt index a5c914b595..19fd2d0305 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt @@ -23,186 +23,177 @@ import kotlin.coroutines.resume private const val TAG = "MoSmsVerifier" private const val ACTION_MO_SMS_SENT = "org.microg.gms.constellation.coreMO_SMS_SENT" -class MoSmsVerifier(private val context: Context, private val subId: Int) { - suspend fun verify(challenge: MoChallenge?): ChallengeResponse? { - if (challenge == null) return null - if (challenge.proxy_number.isEmpty() || challenge.sms.isEmpty()) { - return failureResponse(MOChallengeResponseData.Status.FAILED_TO_SEND_MO) - } - if (context.checkCallingOrSelfPermission(Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { - Log.w(TAG, "SEND_SMS permission missing") - return failureResponse(MOChallengeResponseData.Status.FAILED_TO_SEND_MO) - } +suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse { + if (proxy_number.isEmpty() || sms.isEmpty()) { + return ChallengeResponse(mo_response = MOChallengeResponseData(status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO)) + } + if (context.checkCallingOrSelfPermission(Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { + Log.w(TAG, "SEND_SMS permission missing") + return ChallengeResponse(mo_response = MOChallengeResponseData(status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO)) + } - val smsManager = resolveSmsManager() ?: return failureResponse( - if (subId != -1 && !isActiveSubscription(subId)) { + val smsManager = resolveSmsManager(context, subId) ?: return ChallengeResponse( + mo_response = MOChallengeResponseData( + status = if (subId != -1 && !isActiveSubscription(context, subId)) { MOChallengeResponseData.Status.NO_ACTIVE_SUBSCRIPTION } else { MOChallengeResponseData.Status.NO_SMS_MANAGER } ) + ) + + val port = data_sms_info?.destination_port ?: 0 + val isBinarySms = port > 0 + val action = ACTION_MO_SMS_SENT + val messageId = UUID.randomUUID().toString() + val sentIntent = Intent(action).apply { + `package` = context.packageName + putExtra("message_id", messageId) + } - val port = challenge.data_sms_info?.destination_port ?: 0 - val isBinarySms = port > 0 - val action = if (isBinarySms) ACTION_MO_SMS_SENT else ACTION_MO_SMS_SENT - val messageId = UUID.randomUUID().toString() - val sentIntent = Intent(action).apply { - `package` = context.packageName - putExtra("message_id", messageId) - } + val pendingIntent = PendingIntent.getBroadcast( + context, + messageId.hashCode(), + sentIntent, + PendingIntent.FLAG_ONE_SHOT or (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) + ) - val pendingIntent = PendingIntent.getBroadcast( - context, - messageId.hashCode(), - sentIntent, - PendingIntent.FLAG_ONE_SHOT or (if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE else 0) - ) + Log.d(TAG, "Sending MO SMS to $proxy_number with messageId: $messageId") + + return withTimeoutOrNull(30000) { + suspendCancellableCoroutine { continuation -> + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == action) { + val receivedId = intent.getStringExtra("message_id") + if (receivedId != messageId) return + + val resultCode = resultCode + val errorCode = intent.getIntExtra("errorCode", -1) + + Log.d(TAG, "MO SMS sent result: $resultCode, error: $errorCode") + + val status = when (resultCode) { + -1 -> MOChallengeResponseData.Status.COMPLETED + else -> MOChallengeResponseData.Status.FAILED_TO_SEND_MO + } - Log.d(TAG, "Sending MO SMS to ${challenge.proxy_number} with messageId: $messageId") - - return withTimeoutOrNull(30000) { - suspendCancellableCoroutine { continuation -> - val receiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action == action) { - val receivedId = intent.getStringExtra("message_id") - if (receivedId != messageId) return - - val resultCode = resultCode - val errorCode = intent.getIntExtra("errorCode", -1) - - Log.d(TAG, "MO SMS sent result: $resultCode, error: $errorCode") - - val status = when (resultCode) { - -1 -> MOChallengeResponseData.Status.COMPLETED - else -> MOChallengeResponseData.Status.FAILED_TO_SEND_MO - } - - try { - context.unregisterReceiver(this) - } catch (_: Exception) { - } - - if (continuation.isActive) { - continuation.resume( - ChallengeResponse( - mo_response = MOChallengeResponseData( - status = status, - sms_result_code = resultCode.toLong(), - sms_error_code = errorCode.toLong() - ) + try { + context.unregisterReceiver(this) + } catch (_: Exception) { + } + + if (continuation.isActive) { + continuation.resume( + ChallengeResponse( + mo_response = MOChallengeResponseData( + status = status, + sms_result_code = resultCode.toLong(), + sms_error_code = errorCode.toLong() ) ) - } + ) } } } + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver( - receiver, - IntentFilter(action), - Context.RECEIVER_NOT_EXPORTED - ) - } else { - context.registerReceiver(receiver, IntentFilter(action)) - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver( + receiver, + IntentFilter(action), + Context.RECEIVER_NOT_EXPORTED + ) + } else { + context.registerReceiver(receiver, IntentFilter(action)) + } - continuation.invokeOnCancellation { - try { - context.unregisterReceiver(receiver) - } catch (_: Exception) { - } + continuation.invokeOnCancellation { + try { + context.unregisterReceiver(receiver) + } catch (_: Exception) { } + } + try { + if (isBinarySms) { + smsManager.sendDataMessage( + proxy_number, + null, + port.toShort(), + sms.encodeToByteArray(), + pendingIntent, + null + ) + } else { + smsManager.sendTextMessage( + proxy_number, + null, + sms, + pendingIntent, + null + ) + } + } catch (e: Exception) { + Log.e(TAG, "Failed to initiate MO SMS send", e) try { - if (isBinarySms) { - smsManager.sendDataMessage( - challenge.proxy_number, - null, - port.toShort(), - challenge.sms.encodeToByteArray(), - pendingIntent, - null - ) - } else { - smsManager.sendTextMessage( - challenge.proxy_number, - null, - challenge.sms, - pendingIntent, - null - ) - } - } catch (e: Exception) { - Log.e(TAG, "Failed to initiate MO SMS send", e) - try { - context.unregisterReceiver(receiver) - } catch (_: Exception) { - } - if (continuation.isActive) { - continuation.resume( - ChallengeResponse( - mo_response = MOChallengeResponseData( - status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO - ) + context.unregisterReceiver(receiver) + } catch (_: Exception) { + } + if (continuation.isActive) { + continuation.resume( + ChallengeResponse( + mo_response = MOChallengeResponseData( + status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO ) ) - } + ) } } - } ?: ChallengeResponse( - mo_response = MOChallengeResponseData( - status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO - ) + } + } ?: ChallengeResponse( + mo_response = MOChallengeResponseData( + status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO ) - } + ) +} - private fun failureResponse(status: MOChallengeResponseData.Status): ChallengeResponse { - return ChallengeResponse( - mo_response = MOChallengeResponseData( - status = status - ) - ) +private fun isActiveSubscription(context: Context, subscriptionId: Int): Boolean { + if (subscriptionId == -1) return false + return try { + context.getSystemService()?.isActiveSubscriptionId(subscriptionId) + ?: false + } catch (e: Exception) { + Log.w(TAG, "Failed to query active subscription for $subscriptionId", e) + false } +} - private fun isActiveSubscription(subscriptionId: Int): Boolean { - if (subscriptionId == -1) return false - return try { - context.getSystemService()?.isActiveSubscriptionId(subscriptionId) - ?: false - } catch (e: Exception) { - Log.w(TAG, "Failed to query active subscription for $subscriptionId", e) - false - } +private fun resolveSmsManager(context: Context, subId: Int): SmsManager? { + if (subId != -1 && !isActiveSubscription(context, subId)) { + return null } - - private fun resolveSmsManager(): SmsManager? { - if (subId != -1 && !isActiveSubscription(subId)) { - return null + return try { + val manager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + context.getSystemService(SmsManager::class.java) + } else { + null } - return try { - val manager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - context.getSystemService(SmsManager::class.java) - } else { - null + when { + subId != -1 && manager != null -> manager.createForSubscriptionId(subId) + manager != null -> manager + subId != -1 -> { + @Suppress("DEPRECATION") + SmsManager.getSmsManagerForSubscriptionId(subId) } - when { - subId != -1 && manager != null -> manager.createForSubscriptionId(subId) - manager != null -> manager - subId != -1 -> { - @Suppress("DEPRECATION") - SmsManager.getSmsManagerForSubscriptionId(subId) - } - else -> { - @Suppress("DEPRECATION") - SmsManager.getDefault() - } + else -> { + @Suppress("DEPRECATION") + SmsManager.getDefault() } - } catch (e: Exception) { - Log.e(TAG, "Failed to resolve SmsManager for subId: $subId", e) - null } + } catch (e: Exception) { + Log.e(TAG, "Failed to resolve SmsManager for subId: $subId", e) + null } } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt index 5d5cc838f7..4ad7e84e6a 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt @@ -16,80 +16,78 @@ import kotlin.coroutines.resume private const val TAG = "MtSmsVerifier" -class MtSmsVerifier(private val context: Context, private val subId: Int) { - suspend fun verify(challenge: MTChallenge?): ChallengeResponse? { - val expectedBody = challenge?.sms?.takeIf { it.isNotEmpty() } ?: return null +suspend fun MTChallenge.verify(context: Context, subId: Int): ChallengeResponse? { + val expectedBody = sms.takeIf { it.isNotEmpty() } ?: return null - Log.d(TAG, "Waiting for MT SMS containing challenge string") + Log.d(TAG, "Waiting for MT SMS containing challenge string") - val result = withTimeoutOrNull(300_000) { - suspendCancellableCoroutine?> { continuation -> - val receiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - try { - if (intent.action == Telephony.Sms.Intents.SMS_RECEIVED_ACTION) { - val receivedSubId = intent.getIntExtra( - "android.telephony.extra.SUBSCRIPTION_INDEX", - intent.getIntExtra("subscription", -1) - ) - if (subId != -1 && receivedSubId != -1 && receivedSubId != subId) { - return - } - val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent) - for (msg in messages) { - val body = msg.messageBody - if (body != null && body.contains(expectedBody)) { - Log.d( - TAG, - "Matching MT SMS received from ${msg.originatingAddress}" - ) - context.unregisterReceiver(this) - if (continuation.isActive) { - continuation.resume( - Pair( - body, - msg.originatingAddress ?: "" - ) + val result = withTimeoutOrNull(300_000) { + suspendCancellableCoroutine?> { continuation -> + val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + try { + if (intent.action == Telephony.Sms.Intents.SMS_RECEIVED_ACTION) { + val receivedSubId = intent.getIntExtra( + "android.telephony.extra.SUBSCRIPTION_INDEX", + intent.getIntExtra("subscription", -1) + ) + if (subId != -1 && receivedSubId != -1 && receivedSubId != subId) { + return + } + val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent) + for (msg in messages) { + val body = msg.messageBody + if (body != null && body.contains(expectedBody)) { + Log.d( + TAG, + "Matching MT SMS received from ${msg.originatingAddress}" + ) + context.unregisterReceiver(this) + if (continuation.isActive) { + continuation.resume( + Pair( + body, + msg.originatingAddress ?: "" ) - } - return + ) } + return } } - } catch (e: Exception) { - Log.e(TAG, "Error in MT SMS receiver", e) } + } catch (e: Exception) { + Log.e(TAG, "Error in MT SMS receiver", e) } } + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver( - receiver, - IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION), - Context.RECEIVER_NOT_EXPORTED - ) - } else { - context.registerReceiver( - receiver, - IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) - ) - } - continuation.invokeOnCancellation { - try { - context.unregisterReceiver(receiver) - } catch (_: Exception) { - } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver( + receiver, + IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION), + Context.RECEIVER_NOT_EXPORTED + ) + } else { + context.registerReceiver( + receiver, + IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) + ) + } + continuation.invokeOnCancellation { + try { + context.unregisterReceiver(receiver) + } catch (_: Exception) { } } } + } - return result?.let { (body, sender) -> - ChallengeResponse( - mt_response = MTChallengeResponseData( - sms = body, - sender = sender - ) + return result?.let { (body, sender) -> + ChallengeResponse( + mt_response = MTChallengeResponseData( + sms = body, + sender = sender ) - } + ) } } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt index 3e1ea8d37e..ceb4395bb1 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt @@ -19,141 +19,140 @@ import java.util.concurrent.TimeUnit private const val TAG = "RegisteredSmsVerifier" -class RegisteredSmsVerifier(private val context: Context, private val subId: Int) { - fun verify(challenge: RegisteredSmsChallenge?): ChallengeResponse? { - val expectedPayloads = challenge?.verified_senders - ?.map { it.phone_number_id.toByteArray() } - ?.filter { it.isNotEmpty() } - .orEmpty() - if (expectedPayloads.isEmpty()) return null - - val localNumbers = getLocalNumbers() - if (localNumbers.isEmpty()) { - Log.w(TAG, "No local phone numbers available for registered SMS verification") - return emptyResponse() - } +fun RegisteredSmsChallenge.verify(context: Context, subId: Int): ChallengeResponse { + val expectedPayloads = verified_senders + .map { it.phone_number_id.toByteArray() } + .filter { it.isNotEmpty() } + if (expectedPayloads.isEmpty()) return ChallengeResponse( + registered_sms_response = RegisteredSmsChallengeResponse( + items = emptyList() + ) + ) - val historyStart = System.currentTimeMillis() - - TimeUnit.HOURS.toMillis(VerificationSettingsPhenotypes.A2P_HISTORY_WINDOW_HOURS) - val bucketSizeMillis = (TimeUnit.HOURS.toMillis( - VerificationSettingsPhenotypes.A2P_SMS_SIGNAL_GRANULARITY_HRS - ) / 2).coerceAtLeast(1L) - - val responseItems = mutableListOf() - - try { - val cursor = context.contentResolver.query( - Telephony.Sms.Inbox.CONTENT_URI, - arrayOf("date", "address", "body", "sub_id"), - "date > ?", - arrayOf(historyStart.toString()), - "date DESC" - ) ?: return emptyResponse() - - cursor.use { - while (it.moveToNext()) { - val date = it.getLong(it.getColumnIndexOrThrow("date")) - val sender = it.getString(it.getColumnIndexOrThrow("address")) ?: continue - val body = it.getString(it.getColumnIndexOrThrow("body")) ?: continue - val messageSubId = runCatching { - it.getInt(it.getColumnIndexOrThrow("sub_id")) - }.getOrDefault(-1) - - val candidateNumbers = if (subId != -1 && messageSubId == subId) { - localNumbers - } else if (messageSubId == -1) { - localNumbers - } else { - getLocalNumbers(messageSubId).ifEmpty { localNumbers } - } + val localNumbers = getLocalNumbers(context, subId) + if (localNumbers.isEmpty()) { + Log.w(TAG, "No local phone numbers available for registered SMS verification") + return ChallengeResponse(registered_sms_response = RegisteredSmsChallengeResponse(items = emptyList())) + } + + val historyStart = System.currentTimeMillis() - + TimeUnit.HOURS.toMillis(VerificationSettingsPhenotypes.A2P_HISTORY_WINDOW_HOURS) + val bucketSizeMillis = (TimeUnit.HOURS.toMillis( + VerificationSettingsPhenotypes.A2P_SMS_SIGNAL_GRANULARITY_HRS + ) / 2).coerceAtLeast(1L) + + val responseItems = mutableListOf() + + try { + val cursor = context.contentResolver.query( + Telephony.Sms.Inbox.CONTENT_URI, + arrayOf("date", "address", "body", "sub_id"), + "date > ?", + arrayOf(historyStart.toString()), + "date DESC" + ) ?: return ChallengeResponse( + registered_sms_response = RegisteredSmsChallengeResponse( + items = emptyList() + ) + ) - val bucketStart = date - (date % bucketSizeMillis) - for (localNumber in candidateNumbers) { - val payload = computePayload(bucketStart, localNumber, sender, body) - if (expectedPayloads.any { expected -> expected.contentEquals(payload) }) { - responseItems += RegisteredSmsChallengeResponseItem( - payload = RegisteredSmsChallengeResponsePayload( - payload = payload.toByteString() - ) + cursor.use { + while (it.moveToNext()) { + val date = it.getLong(it.getColumnIndexOrThrow("date")) + val sender = it.getString(it.getColumnIndexOrThrow("address")) ?: continue + val body = it.getString(it.getColumnIndexOrThrow("body")) ?: continue + val messageSubId = runCatching { + it.getInt(it.getColumnIndexOrThrow("sub_id")) + }.getOrDefault(-1) + + val candidateNumbers = if (subId != -1 && messageSubId == subId) { + localNumbers + } else if (messageSubId == -1) { + localNumbers + } else { + getLocalNumbers(context, messageSubId).ifEmpty { localNumbers } + } + + val bucketStart = date - (date % bucketSizeMillis) + for (localNumber in candidateNumbers) { + val payload = computePayload(bucketStart, localNumber, sender, body) + if (expectedPayloads.any { expected -> expected.contentEquals(payload) }) { + responseItems += RegisteredSmsChallengeResponseItem( + payload = RegisteredSmsChallengeResponsePayload( + payload = payload.toByteString() ) - } + ) } } } - } catch (e: SecurityException) { - Log.w(TAG, "SMS inbox access denied", e) - return emptyResponse() - } catch (e: Exception) { - Log.e(TAG, "Registered SMS verification failed", e) - return emptyResponse() } - - return ChallengeResponse( - registered_sms_response = RegisteredSmsChallengeResponse(items = responseItems.distinct()) - ) + } catch (e: SecurityException) { + Log.w(TAG, "SMS inbox access denied", e) + return ChallengeResponse(registered_sms_response = RegisteredSmsChallengeResponse(items = emptyList())) + } catch (e: Exception) { + Log.e(TAG, "Registered SMS verification failed", e) + return ChallengeResponse(registered_sms_response = RegisteredSmsChallengeResponse(items = emptyList())) } - private fun emptyResponse(): ChallengeResponse { - return ChallengeResponse( - registered_sms_response = RegisteredSmsChallengeResponse(items = emptyList()) - ) - } + return ChallengeResponse( + registered_sms_response = RegisteredSmsChallengeResponse(items = responseItems.distinct()) + ) +} - private fun getLocalNumbers(targetSubId: Int = subId): List { - val numbers = linkedSetOf() - val subscriptionManager = - context.getSystemService() - val telephonyManager = context.getSystemService() - - try { - subscriptionManager?.activeSubscriptionInfoList.orEmpty().forEach { info -> - if (targetSubId != -1 && info.subscriptionId != targetSubId) return@forEach - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU && subscriptionManager != null) { - numbers += subscriptionManager.getPhoneNumber( - info.subscriptionId, - SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER - ) - numbers += subscriptionManager.getPhoneNumber( - info.subscriptionId, - SubscriptionManager.PHONE_NUMBER_SOURCE_UICC - ) - numbers += subscriptionManager.getPhoneNumber( - info.subscriptionId, - SubscriptionManager.PHONE_NUMBER_SOURCE_IMS - ) - } - val targetManager = - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { - telephonyManager?.createForSubscriptionId(info.subscriptionId) - } else { - telephonyManager - } - numbers += targetManager?.line1Number.orEmpty() - numbers += info.number.orEmpty() +private fun getLocalNumbers(context: Context, targetSubId: Int): List { + val numbers = linkedSetOf() + val subscriptionManager = + context.getSystemService() + val telephonyManager = context.getSystemService() + + try { + subscriptionManager?.activeSubscriptionInfoList.orEmpty().forEach { info -> + if (targetSubId != -1 && info.subscriptionId != targetSubId) return@forEach + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU && subscriptionManager != null) { + numbers += subscriptionManager.getPhoneNumber( + info.subscriptionId, + SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER + ) + numbers += subscriptionManager.getPhoneNumber( + info.subscriptionId, + SubscriptionManager.PHONE_NUMBER_SOURCE_UICC + ) + numbers += subscriptionManager.getPhoneNumber( + info.subscriptionId, + SubscriptionManager.PHONE_NUMBER_SOURCE_IMS + ) } - } catch (e: Exception) { - Log.w(TAG, "Unable to collect local phone numbers", e) + val targetManager = + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + telephonyManager?.createForSubscriptionId(info.subscriptionId) + } else { + telephonyManager + } + numbers += targetManager?.line1Number.orEmpty() + numbers += info.number.orEmpty() } - - return numbers.filter { it.isNotBlank() }.distinct() + } catch (e: Exception) { + Log.w(TAG, "Unable to collect local phone numbers", e) } - private fun computePayload( - bucketStart: Long, - localNumber: String, - sender: String, - body: String - ): ByteArray { - val digest = MessageDigest.getInstance("SHA-512") - digest.update(bucketStart.toString().toByteArray(StandardCharsets.UTF_8)) - digest.update(hashUtf8(localNumber)) - digest.update(hashUtf8(sender)) - digest.update(body.toByteArray(StandardCharsets.UTF_8)) - return digest.digest() - } + return numbers.filter { it.isNotBlank() }.distinct() +} - private fun hashUtf8(value: String): ByteArray { - return MessageDigest.getInstance("SHA-512") - .digest(value.toByteArray(StandardCharsets.UTF_8)) - } +private fun computePayload( + bucketStart: Long, + localNumber: String, + sender: String, + body: String +): ByteArray { + val digest = MessageDigest.getInstance("SHA-512") + digest.update(bucketStart.toString().toByteArray(StandardCharsets.UTF_8)) + digest.update(hashUtf8(localNumber)) + digest.update(hashUtf8(sender)) + digest.update(body.toByteArray(StandardCharsets.UTF_8)) + return digest.digest() +} + +private fun hashUtf8(value: String): ByteArray { + return MessageDigest.getInstance("SHA-512") + .digest(value.toByteArray(StandardCharsets.UTF_8)) } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt index 4066a5dffb..ebaa6733ef 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt @@ -34,15 +34,12 @@ import javax.xml.parsers.DocumentBuilderFactory private const val TAG = "Ts43Verifier" -class Ts43Verifier(private val context: Context, private val subId: Int) { - - private class Ts43ApiException( - val errorCode: Ts43ChallengeResponseError.Code, - val httpStatus: Int, - val requestType: Ts43ChallengeResponseError.RequestType, - cause: Throwable? = null - ) : Exception(cause) +fun Ts43Challenge.verify(context: Context, subId: Int): ChallengeResponse { + val verifier = InternalTs43Verifier(context, subId) + return verifier.verify(this) +} +private class InternalTs43Verifier(private val context: Context, private val subId: Int) { private val httpHistory = mutableListOf() private val telephonyManager: TelephonyManager by lazy { @@ -56,41 +53,7 @@ class Ts43Verifier(private val context: Context, private val subId: Int) { private val eapAkaService by lazy { EapAkaService(telephonyManager) } - private fun errorCode(rawCode: Int): Ts43ChallengeResponseError.Code = - Ts43ChallengeResponseError.Code.fromValue(rawCode) - ?: Ts43ChallengeResponseError.Code.TS43_ERROR_CODE_UNSPECIFIED - - private val okHttpClient by lazy { - OkHttpClient.Builder() - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .followRedirects(false) - .followSslRedirects(false) - .cookieJar(object : CookieJar { - private val cookieStore = mutableMapOf>() - override fun saveFromResponse(url: HttpUrl, cookies: List) { - val existing = cookieStore.getOrPut(url.host) { mutableListOf() } - for (cookie in cookies) { - existing.removeAll { - it.name == cookie.name && - it.domain == cookie.domain && - it.path == cookie.path - } - existing += cookie - } - } - - override fun loadForRequest(url: HttpUrl): List { - return cookieStore[url.host] - ?.filter { cookie -> cookie.matches(url) } - .orEmpty() - } - }) - .build() - } - - fun verify(challenge: Ts43Challenge?): ChallengeResponse? { - if (challenge == null) return null + fun verify(challenge: Ts43Challenge): ChallengeResponse { httpHistory.clear() return try { @@ -123,7 +86,15 @@ class Ts43Verifier(private val context: Context, private val subId: Int) { challenge.app_id, Ts43ChallengeResponseError.RequestType.TS43_REQUEST_TYPE_GET_PHONE_NUMBER_API ) - buildClientResponse(challenge, responseBody) + ChallengeResponse( + ts43_challenge_response = Ts43ChallengeResponse( + ts43_type = challenge.ts43_type, + client_challenge_response = ClientChallengeResponse( + get_phone_number_response = responseBody + ), + http_history = httpHistory.toList() + ) + ) } } } @@ -156,7 +127,15 @@ class Ts43Verifier(private val context: Context, private val subId: Int) { challenge.app_id, Ts43ChallengeResponseError.RequestType.TS43_REQUEST_TYPE_ACQUIRE_TEMPORARY_TOKEN_API ) - buildServerResponse(challenge, responseBody) + ChallengeResponse( + ts43_challenge_response = Ts43ChallengeResponse( + ts43_type = challenge.ts43_type, + server_challenge_response = ServerChallengeResponse( + acquire_temporary_token_response = responseBody + ), + http_history = httpHistory.toList() + ) + ) } } } @@ -168,7 +147,19 @@ class Ts43Verifier(private val context: Context, private val subId: Int) { } } catch (e: Ts43ApiException) { Log.w(TAG, "TS43 API failure requestType=${e.requestType} http=${e.httpStatus}", e) - buildApiErrorResponse(challenge, e) + ChallengeResponse( + ts43_challenge_response = Ts43ChallengeResponse( + ts43_type = challenge.ts43_type, + status = Ts43ChallengeResponseStatus( + error = Ts43ChallengeResponseError( + error_code = e.errorCode, + http_status = e.httpStatus, + request_type = e.requestType + ) + ), + http_history = httpHistory.toList() + ) + ) } catch (e: NullPointerException) { Log.e(TAG, "TS43 null failure", e) buildFailureResponse( @@ -366,36 +357,6 @@ class Ts43Verifier(private val context: Context, private val subId: Int) { }.getOrNull()?.takeIf { it.isNotEmpty() } } - private fun buildServerResponse( - challenge: Ts43Challenge, - responseBody: String - ): ChallengeResponse { - return ChallengeResponse( - ts43_challenge_response = Ts43ChallengeResponse( - ts43_type = challenge.ts43_type, - server_challenge_response = ServerChallengeResponse( - acquire_temporary_token_response = responseBody - ), - http_history = httpHistory.toList() - ) - ) - } - - private fun buildClientResponse( - challenge: Ts43Challenge, - responseBody: String - ): ChallengeResponse { - return ChallengeResponse( - ts43_challenge_response = Ts43ChallengeResponse( - ts43_type = challenge.ts43_type, - client_challenge_response = ClientChallengeResponse( - get_phone_number_response = responseBody - ), - http_history = httpHistory.toList() - ) - ) - } - private fun buildFailureResponse( challenge: Ts43Challenge, statusCode: Ts43ChallengeResponseStatus.Code @@ -408,23 +369,44 @@ class Ts43Verifier(private val context: Context, private val subId: Int) { ) ) } +} - private fun buildApiErrorResponse( - challenge: Ts43Challenge, - error: Ts43ApiException - ): ChallengeResponse { - return ChallengeResponse( - ts43_challenge_response = Ts43ChallengeResponse( - ts43_type = challenge.ts43_type, - status = Ts43ChallengeResponseStatus( - error = Ts43ChallengeResponseError( - error_code = error.errorCode, - http_status = error.httpStatus, - request_type = error.requestType - ) - ), - http_history = httpHistory.toList() - ) - ) - } +private class Ts43ApiException( + val errorCode: Ts43ChallengeResponseError.Code, + val httpStatus: Int, + val requestType: Ts43ChallengeResponseError.RequestType, + cause: Throwable? = null +) : Exception(cause) + +private fun errorCode(rawCode: Int): Ts43ChallengeResponseError.Code = + Ts43ChallengeResponseError.Code.fromValue(rawCode) + ?: Ts43ChallengeResponseError.Code.TS43_ERROR_CODE_UNSPECIFIED + +private val okHttpClient by lazy { + OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .followRedirects(false) + .followSslRedirects(false) + .cookieJar(object : CookieJar { + private val cookieStore = mutableMapOf>() + override fun saveFromResponse(url: HttpUrl, cookies: List) { + val existing = cookieStore.getOrPut(url.host) { mutableListOf() } + for (cookie in cookies) { + existing.removeAll { + it.name == cookie.name && + it.domain == cookie.domain && + it.path == cookie.path + } + existing += cookie + } + } + + override fun loadForRequest(url: HttpUrl): List { + return cookieStore[url.host] + ?.filter { cookie -> cookie.matches(url) } + .orEmpty() + } + }) + .build() } From 01400ff25b4a27b1f90905ada67ca04c1e86cda3 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Thu, 26 Mar 2026 01:38:08 -0400 Subject: [PATCH 04/31] More refactoring --- .../gms/constellation/core/AuthManager.kt | 39 +++++++------- .../gms/constellation/core/GetIidToken.kt | 2 +- .../core/GetVerifiedPhoneNumbers.kt | 2 +- .../gms/constellation/core/RpcClient.kt | 54 +++++++------------ .../core/proto/builders/ClientInfoBuilder.kt | 4 +- .../core/proto/builders/CommonBuilders.kt | 3 +- .../proto/builders/RequestBuildContext.kt | 3 +- 7 files changed, 49 insertions(+), 58 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt index 94b4bdb8a8..371e677448 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt @@ -13,8 +13,11 @@ import java.security.Signature import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec -class AuthManager(context: Context) { +val Context.authManager: AuthManager get() = AuthManager.get(this) + +class AuthManager private constructor(context: Context) { private val context = context.applicationContext + private val sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) companion object { private const val PREFS_NAME = "constellation_prefs" @@ -24,22 +27,14 @@ class AuthManager(context: Context) { @Volatile private var instance: AuthManager? = null - fun get(context: Context): AuthManager { - val existing = instance - if (existing != null) return existing - - return synchronized(this) { - instance ?: AuthManager(context).also { instance = it } - } + fun get(context: Context): AuthManager = instance ?: synchronized(this) { + instance ?: AuthManager(context).also { instance = it } } } - private val sharedPrefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) - // GMS signing format: {iidToken}:{seconds}:{nanos} fun signIidToken(iidToken: String): Pair { - val now = System.currentTimeMillis() - val timestamp = Instant.ofEpochMilli(now) + val timestamp = Instant.ofEpochMilli(System.currentTimeMillis()) val content = "$iidToken:${timestamp.epochSecond}:${timestamp.nano}" return sign(content) to timestamp } @@ -61,10 +56,20 @@ class AuthManager(context: Context) { try { val kf = KeyFactory.getInstance("EC") val privateKey = kf.generatePrivate( - PKCS8EncodedKeySpec(Base64.decode(privateKeyStr, Base64.DEFAULT)) + PKCS8EncodedKeySpec( + Base64.decode( + privateKeyStr, + Base64.DEFAULT + ) + ) ) val publicKey = kf.generatePublic( - X509EncodedKeySpec(Base64.decode(publicKeyStr, Base64.DEFAULT)) + X509EncodedKeySpec( + Base64.decode( + publicKeyStr, + Base64.DEFAULT + ) + ) ) return KeyPair(publicKey, privateKey) } catch (_: Exception) { @@ -96,7 +101,5 @@ class AuthManager(context: Context) { } } - fun getFid(): String { - return InstanceID.getInstance(context).id - } -} + fun getFid(): String = InstanceID.getInstance(context).id +} \ No newline at end of file diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt index 9ba0e0b8af..d7dc64cffe 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt @@ -18,7 +18,7 @@ suspend fun handleGetIidToken( request: GetIidTokenRequest ) = withContext(Dispatchers.IO) { try { - val authManager = AuthManager.get(context) + val authManager = context.authManager val iidToken = authManager.getIidToken(request.projectNumber?.toString()) val fid = authManager.getFid() val (signature, timestamp) = authManager.signIidToken(iidToken) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt index 5ab87e178c..d311bcf562 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt @@ -41,7 +41,7 @@ internal suspend fun fetchVerifiedPhoneNumbers( bundle: Bundle, callingPackage: String = bundle.getString("calling_package") ?: context.packageName ): List = withContext(Dispatchers.IO) { - val authManager = AuthManager.get(context) + val authManager = context.authManager val sessionId = UUID.randomUUID().toString() val selections = extractPhoneNumberSelections(bundle) val certificateHash = bundle.getString("certificate_hash") ?: "" diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/RpcClient.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/RpcClient.kt index 57495656c3..e5030bac66 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/RpcClient.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/RpcClient.kt @@ -1,47 +1,33 @@ package org.microg.gms.constellation.core import com.squareup.wire.GrpcClient -import okhttp3.Interceptor import okhttp3.OkHttpClient -import okhttp3.Response import org.microg.gms.common.Constants import org.microg.gms.constellation.core.proto.PhoneDeviceVerificationClient import org.microg.gms.constellation.core.proto.PhoneNumberClient -private class AuthInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val originalRequest = chain.request() - - val builder = originalRequest.newBuilder() - .header("X-Goog-Api-Key", "AIzaSyAP-gfH3qvi6vgHZbSYwQ_XHqV_mXHhzIk") - .header("X-Android-Package", Constants.GMS_PACKAGE_NAME) - .header("X-Android-Cert", Constants.GMS_PACKAGE_SIGNATURE_SHA1.uppercase()) - - return chain.proceed(builder.build()) - } -} - object RpcClient { - private val client: OkHttpClient by lazy { - OkHttpClient.Builder() - .addInterceptor(AuthInterceptor()) - .build() - } - - private val grpcClient: GrpcClient by lazy { - GrpcClient.Builder() - .client(client) - // Google's constellationserver does NOT like compressed requests - .minMessageToCompress(Long.MAX_VALUE) - .baseUrl("https://phonedeviceverification-pa.googleapis.com/") - .build() - } - - val phoneDeviceVerificationClient: PhoneDeviceVerificationClient by lazy { + private val client: OkHttpClient = OkHttpClient.Builder() + .addInterceptor { chain -> + val originalRequest = chain.request() + val builder = originalRequest.newBuilder() + .header("X-Goog-Api-Key", "AIzaSyAP-gfH3qvi6vgHZbSYwQ_XHqV_mXHhzIk") + .header("X-Android-Package", Constants.GMS_PACKAGE_NAME) + .header("X-Android-Cert", Constants.GMS_PACKAGE_SIGNATURE_SHA1.uppercase()) + chain.proceed(builder.build()) + } + .build() + + private val grpcClient: GrpcClient = GrpcClient.Builder() + .client(client) + // Google's constellationserver does NOT like compressed requests + .minMessageToCompress(Long.MAX_VALUE) + .baseUrl("https://phonedeviceverification-pa.googleapis.com/") + .build() + + val phoneDeviceVerificationClient: PhoneDeviceVerificationClient = grpcClient.create(PhoneDeviceVerificationClient::class) - } - val phoneNumberClient: PhoneNumberClient by lazy { + val phoneNumberClient: PhoneNumberClient = grpcClient.create(PhoneNumberClient::class) - } } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt index bc97172d63..8e61d1005b 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt @@ -14,6 +14,7 @@ import org.microg.gms.common.Constants import org.microg.gms.constellation.core.AuthManager import org.microg.gms.constellation.core.ConstellationStateStore import org.microg.gms.constellation.core.GServices +import org.microg.gms.constellation.core.authManager import org.microg.gms.constellation.core.proto.ClientInfo import org.microg.gms.constellation.core.proto.CountryInfo import org.microg.gms.constellation.core.proto.DeviceID @@ -47,11 +48,10 @@ suspend operator fun ClientInfo.Companion.invoke( ): ClientInfo { val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val locale = Locale.getDefault().let { "${it.language}_${it.country}" } - val authManager = AuthManager.get(context) return ClientInfo( device_id = DeviceID(context, buildContext.iidToken), - client_public_key = authManager.getOrCreateKeyPair().public.encoded.toByteString(), + client_public_key = context.authManager.getOrCreateKeyPair().public.encoded.toByteString(), locale = locale, gmscore_version_number = Constants.GMS_VERSION_CODE / 1000, gmscore_version = packageInfo.versionName ?: "", diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt index d2f6f1e0da..c79cdbff62 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt @@ -4,6 +4,7 @@ import android.content.Context import android.os.Bundle import okio.ByteString.Companion.toByteString import org.microg.gms.constellation.core.AuthManager +import org.microg.gms.constellation.core.authManager import org.microg.gms.constellation.core.proto.AuditToken import org.microg.gms.constellation.core.proto.AuditTokenMetadata import org.microg.gms.constellation.core.proto.AuditUuid @@ -46,7 +47,7 @@ suspend operator fun RequestHeader.Companion.invoke( triggerType: RequestTrigger.Type = RequestTrigger.Type.CONSENT_API_TRIGGER, includeClientAuth: Boolean = false ): RequestHeader { - val authManager = if (includeClientAuth) AuthManager.get(context) else null + val authManager = if (includeClientAuth) context.authManager else null val clientAuth = if (includeClientAuth) { val (signature, timestamp) = authManager!!.signIidToken(buildContext.iidToken) org.microg.gms.constellation.core.proto.ClientAuth( diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt index c581237c17..1262045264 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt @@ -2,6 +2,7 @@ package org.microg.gms.constellation.core.proto.builders import android.content.Context import org.microg.gms.constellation.core.AuthManager +import org.microg.gms.constellation.core.authManager import org.microg.gms.constellation.core.proto.GaiaToken data class RequestBuildContext( @@ -11,7 +12,7 @@ data class RequestBuildContext( suspend fun buildRequestContext( context: Context, - authManager: AuthManager = AuthManager.get(context) + authManager: AuthManager = context.authManager ): RequestBuildContext { return RequestBuildContext( iidToken = authManager.getIidToken(), From 3f15e8bf4011898dce4b33deabecaba58b4349ea Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 27 Mar 2026 03:40:55 -0400 Subject: [PATCH 05/31] Add `android.permission.USE_CREDENTIALS` into `AndroidManifest.xml` --- play-services-constellation/core/src/main/AndroidManifest.xml | 1 + .../gms/constellation/core/proto/builders/GaiaInfoBuilder.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/play-services-constellation/core/src/main/AndroidManifest.xml b/play-services-constellation/core/src/main/AndroidManifest.xml index cbe65637d0..bd95282658 100644 --- a/play-services-constellation/core/src/main/AndroidManifest.xml +++ b/play-services-constellation/core/src/main/AndroidManifest.xml @@ -12,6 +12,7 @@ + diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt index febce16e53..31ee9146ab 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt @@ -23,6 +23,7 @@ operator fun GaiaSignals.Companion.invoke(context: Context): GaiaSignals? { val md = MessageDigest.getInstance("SHA-256") for (account in accounts) { + // Note: Simple implementation, maybe do actual obfuscated Gaia ID retrieval later? val hash = md.digest(account.name.toByteArray(Charsets.UTF_8)) val number = hash.take(8).fold(0L) { acc, byte -> (acc shl 8) or (byte.toLong() and 0xFF) } From 65961f8491cbe0e8c7c898d39da33742f3cbec9b Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 27 Mar 2026 06:15:36 -0400 Subject: [PATCH 06/31] Fix `NewApi` lints --- .../gms/constellation/core/AuthManager.kt | 21 ++++++- .../core/ConstellationApiService.kt | 5 ++ .../core/ConstellationStateStore.kt | 4 ++ .../gms/constellation/core/GetIidToken.kt | 4 +- .../core/GetVerifiedPhoneNumbers.kt | 17 ++++-- .../core/VerificationMappings.kt | 3 + .../constellation/core/VerifyPhoneNumber.kt | 4 +- .../core/proto/builders/ClientInfoBuilder.kt | 8 ++- .../core/proto/builders/CommonBuilders.kt | 5 +- .../core/proto/builders/GaiaInfoBuilder.kt | 5 ++ .../core/proto/builders/SyncRequestBuilder.kt | 10 +--- .../proto/builders/TelephonyInfoBuilder.kt | 7 ++- .../core/verification/CarrierIdVerifier.kt | 3 + .../core/verification/ChallengeProcessor.kt | 4 ++ .../core/verification/MoSmsVerifier.kt | 55 ++++++++++++------- .../verification/RegisteredSmsVerifier.kt | 8 ++- .../core/verification/Ts43Verifier.kt | 7 ++- .../ts43/ServiceEntitlementExtension.kt | 6 ++ 18 files changed, 127 insertions(+), 49 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt index 371e677448..ba601c3832 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt @@ -1,7 +1,10 @@ package org.microg.gms.constellation.core +import android.annotation.SuppressLint import android.content.Context +import android.os.Build import android.util.Base64 +import androidx.annotation.RequiresApi import androidx.core.content.edit import com.google.android.gms.iid.InstanceID import com.squareup.wire.Instant @@ -24,6 +27,8 @@ class AuthManager private constructor(context: Context) { private const val KEY_PRIVATE = "private_key" private const val KEY_PUBLIC = "public_key" + // This is safe as the Context is immediately converted to the application context. + @SuppressLint("StaticFieldLeak") @Volatile private var instance: AuthManager? = null @@ -33,10 +38,20 @@ class AuthManager private constructor(context: Context) { } // GMS signing format: {iidToken}:{seconds}:{nanos} + fun signIidTokenCompat(iidToken: String): Pair { + val currentTimeMillis = System.currentTimeMillis() + + val epochSecond = currentTimeMillis / 1000 + val nano = (currentTimeMillis % 1000) * 1_000_000 + + val content = "$iidToken:$epochSecond:$nano" + return sign(content) to currentTimeMillis + } + + @RequiresApi(Build.VERSION_CODES.O) fun signIidToken(iidToken: String): Pair { - val timestamp = Instant.ofEpochMilli(System.currentTimeMillis()) - val content = "$iidToken:${timestamp.epochSecond}:${timestamp.nano}" - return sign(content) to timestamp + val (bytes, millis) = signIidTokenCompat(iidToken) + return bytes to Instant.ofEpochMilli(millis) } fun getIidToken(projectNumber: String? = null): String { diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationApiService.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationApiService.kt index f4ddbb4123..39334ec569 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationApiService.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationApiService.kt @@ -1,6 +1,7 @@ package org.microg.gms.constellation.core import android.content.Context +import android.os.Build import android.os.Bundle import android.util.Log import com.google.android.gms.common.Feature @@ -76,6 +77,7 @@ class ConstellationApiServiceImpl( }" ) if (cb == null || bundle == null) return + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return serviceScope.launch { handleVerifyPhoneNumberV1(context, cb, bundle, packageName) } } @@ -86,6 +88,7 @@ class ConstellationApiServiceImpl( ) { Log.i(TAG, "verifyPhoneNumberSingleUse()") if (cb == null || bundle == null) return + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return serviceScope.launch { handleVerifyPhoneNumberSingleUse(context, cb, bundle, packageName) } } @@ -99,6 +102,7 @@ class ConstellationApiServiceImpl( "verifyPhoneNumber(): apiVersion=${request?.apiVersion}, policy=${request?.policyId}" ) if (cb == null || request == null) return + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return serviceScope.launch { handleVerifyPhoneNumberRequest(context, cb, request, packageName) } } @@ -119,6 +123,7 @@ class ConstellationApiServiceImpl( ) { Log.i(TAG, "getPnvCapabilities(): $request") if (cb == null || request == null) return + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) return serviceScope.launch { handleGetPnvCapabilities(context, cb, request) } } } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt index dc802dfacc..f41739c10b 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt @@ -1,8 +1,12 @@ +@file:RequiresApi(Build.VERSION_CODES.O) + package org.microg.gms.constellation.core import android.annotation.SuppressLint import android.content.Context +import android.os.Build import android.util.Base64 +import androidx.annotation.RequiresApi import androidx.core.content.edit import com.squareup.wire.Instant import okio.ByteString.Companion.toByteString diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt index d7dc64cffe..5ee8a6ac71 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetIidToken.kt @@ -21,11 +21,11 @@ suspend fun handleGetIidToken( val authManager = context.authManager val iidToken = authManager.getIidToken(request.projectNumber?.toString()) val fid = authManager.getFid() - val (signature, timestamp) = authManager.signIidToken(iidToken) + val (signature, timestamp) = authManager.signIidTokenCompat(iidToken) callbacks.onIidTokenGenerated( Status.SUCCESS, - GetIidTokenResponse(iidToken, fid, signature, timestamp.toEpochMilli()), + GetIidTokenResponse(iidToken, fid, signature, timestamp), ApiMetadata.DEFAULT ) } catch (e: Exception) { diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt index d311bcf562..e02b3565fa 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt @@ -1,6 +1,9 @@ +@file:SuppressLint("NewApi") package org.microg.gms.constellation.core +import android.annotation.SuppressLint import android.content.Context +import android.os.Build import android.os.Bundle import android.util.Log import com.google.android.gms.common.api.ApiMetadata @@ -17,7 +20,7 @@ import org.microg.gms.constellation.core.proto.GetVerifiedPhoneNumbersRequest.Ph import org.microg.gms.constellation.core.proto.IIDTokenAuth import org.microg.gms.constellation.core.proto.TokenOption import java.util.UUID -import org.microg.gms.constellation.core.proto.VerifiedPhoneNumber as RpcVerifiedPhoneNumber +import org.microg.gms.constellation.core.proto.VerifiedPhoneNumber private const val TAG = "GetVerifiedPhoneNumbers" @@ -27,6 +30,10 @@ suspend fun handleGetVerifiedPhoneNumbers( bundle: Bundle ) = withContext(Dispatchers.IO) { try { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + throw Exception("Unsupported SDK") + } + val phoneNumbers = fetchVerifiedPhoneNumbers(context, bundle).map { it.toPhoneNumberInfo() } callbacks.onPhoneNumberVerified(Status.SUCCESS, phoneNumbers, ApiMetadata.DEFAULT) @@ -40,7 +47,7 @@ internal suspend fun fetchVerifiedPhoneNumbers( context: Context, bundle: Bundle, callingPackage: String = bundle.getString("calling_package") ?: context.packageName -): List = withContext(Dispatchers.IO) { +): List = withContext(Dispatchers.IO) { val authManager = context.authManager val sessionId = UUID.randomUUID().toString() val selections = extractPhoneNumberSelections(bundle) @@ -78,14 +85,14 @@ internal suspend fun fetchVerifiedPhoneNumbers( response.phone_numbers } -internal fun List.toVerifyPhoneNumberResponse(): VerifyPhoneNumberResponse { +internal fun List.toVerifyPhoneNumberResponse(): VerifyPhoneNumberResponse { return VerifyPhoneNumberResponse( map { it.toPhoneNumberVerification() }.toTypedArray(), Bundle.EMPTY ) } -private fun RpcVerifiedPhoneNumber.toPhoneNumberInfo(): PhoneNumberInfo { +private fun VerifiedPhoneNumber.toPhoneNumberInfo(): PhoneNumberInfo { val extras = Bundle().apply { if (id_token.isNotEmpty()) { putString("id_token", id_token) @@ -101,7 +108,7 @@ private fun RpcVerifiedPhoneNumber.toPhoneNumberInfo(): PhoneNumberInfo { ) } -private fun RpcVerifiedPhoneNumber.toPhoneNumberVerification(): PhoneNumberVerification { +private fun VerifiedPhoneNumber.toPhoneNumberVerification(): PhoneNumberVerification { val extras = Bundle().apply { putInt("rcs_state", rcs_state.value) } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt index 62255e1fc0..6102ca047a 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt @@ -1,6 +1,8 @@ package org.microg.gms.constellation.core +import android.os.Build import android.os.Bundle +import androidx.annotation.RequiresApi import com.google.android.gms.constellation.PhoneNumberVerification import org.microg.gms.constellation.core.proto.Param import org.microg.gms.constellation.core.proto.UnverifiedInfo @@ -52,6 +54,7 @@ fun Verification.getVerificationStatus(): Verification.Status { } } +@RequiresApi(Build.VERSION_CODES.O) fun Verification.toClientVerification(imsiToSlotMap: Map): PhoneNumberVerification { val verificationStatus = this.getVerificationStatus() var phoneNumber: String? = null diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index 1d07ea48c3..e456fb00c4 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -1,3 +1,5 @@ +@file:RequiresApi(Build.VERSION_CODES.O) + package org.microg.gms.constellation.core import android.content.Context @@ -234,7 +236,6 @@ private suspend fun handleVerifyPhoneNumberRequest( } } -@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) private suspend fun runVerificationFlow( context: Context, request: VerifyPhoneNumberRequest, @@ -292,7 +293,6 @@ private fun PhoneNumberVerification.toLegacyPhoneNumberInfoOrNull(): PhoneNumber return PhoneNumberInfo(1, phoneNumber, timestampMillis, extras) } -@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) private suspend fun executeSyncFlow( context: Context, sessionId: String, diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt index 8e61d1005b..8a67cb84ad 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt @@ -1,3 +1,5 @@ +@file:RequiresApi(Build.VERSION_CODES.O) + package org.microg.gms.constellation.core.proto.builders import android.annotation.SuppressLint @@ -6,12 +8,12 @@ import android.os.Build import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.util.Log +import androidx.annotation.RequiresApi import androidx.core.content.edit import androidx.core.content.getSystemService import com.google.android.gms.tasks.await import okio.ByteString.Companion.toByteString import org.microg.gms.common.Constants -import org.microg.gms.constellation.core.AuthManager import org.microg.gms.constellation.core.ConstellationStateStore import org.microg.gms.constellation.core.GServices import org.microg.gms.constellation.core.authManager @@ -71,6 +73,7 @@ suspend operator fun ClientInfo.Companion.invoke( ) } +@SuppressLint("HardwareIds") operator fun DeviceID.Companion.invoke(context: Context, iidToken: String): DeviceID { val prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) @@ -123,9 +126,8 @@ operator fun CountryInfo.Companion.invoke(context: Context): CountryInfo { val tm = context.getSystemService() sm?.activeSubscriptionInfoList?.forEach { info -> - val targetTM = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + val targetTM = tm?.createForSubscriptionId(info.subscriptionId) - } else tm targetTM?.networkCountryIso?.let { networkCountries.add(it.lowercase()) } info.countryIso?.let { simCountries.add(it.lowercase()) } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt index c79cdbff62..ae7e49ba83 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt @@ -1,9 +1,11 @@ + package org.microg.gms.constellation.core.proto.builders import android.content.Context +import android.os.Build import android.os.Bundle +import androidx.annotation.RequiresApi import okio.ByteString.Companion.toByteString -import org.microg.gms.constellation.core.AuthManager import org.microg.gms.constellation.core.authManager import org.microg.gms.constellation.core.proto.AuditToken import org.microg.gms.constellation.core.proto.AuditTokenMetadata @@ -40,6 +42,7 @@ fun AuditToken.Companion.generate(): AuditToken { ) } +@RequiresApi(Build.VERSION_CODES.O) suspend operator fun RequestHeader.Companion.invoke( context: Context, sessionId: String, diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt index 31ee9146ab..60ff924562 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt @@ -1,8 +1,11 @@ + package org.microg.gms.constellation.core.proto.builders import android.accounts.AccountManager import android.content.Context +import android.os.Build import android.util.Log +import androidx.annotation.RequiresApi import com.squareup.wire.Instant import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -16,6 +19,8 @@ import kotlin.math.absoluteValue private const val TAG = "GaiaInfoBuilder" + +@RequiresApi(Build.VERSION_CODES.O) operator fun GaiaSignals.Companion.invoke(context: Context): GaiaSignals? { val entries = mutableListOf() try { diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt index 61bc46d639..e2553dcb9b 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt @@ -1,3 +1,6 @@ +@file:RequiresApi(Build.VERSION_CODES.O) +@file:SuppressLint("HardwareIds") + package org.microg.gms.constellation.core.proto.builders import android.annotation.SuppressLint @@ -30,8 +33,6 @@ import org.microg.gms.constellation.core.proto.VerificationPolicy private const val TAG = "SyncRequestBuilder" -@RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) -@SuppressLint("HardwareIds", "MissingPermission") fun buildImsiToSubscriptionInfoMap(context: Context): Map { val subscriptionManager = context.getSystemService() ?: return emptyMap() @@ -56,7 +57,6 @@ fun buildImsiToSubscriptionInfoMap(context: Context): Map() val targetTm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && subscriptionId >= 0) { @@ -107,7 +111,6 @@ fun NetworkSignal.Companion.getList(context: Context): List { return connectivityInfos } -@SuppressLint("HardwareIds") fun SimOperatorInfo.Companion.getList(context: Context): List { val infos = mutableListOf() try { diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt index 53c1bcc72d..00d0fb8919 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt @@ -1,9 +1,12 @@ +@file:RequiresApi(Build.VERSION_CODES.N) + package org.microg.gms.constellation.core.verification import android.content.Context import android.os.Build import android.telephony.TelephonyManager import android.util.Log +import androidx.annotation.RequiresApi import androidx.core.content.getSystemService import org.microg.gms.constellation.core.proto.CarrierIdChallengeResponse import org.microg.gms.constellation.core.proto.CarrierIdError diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index ac79655766..f01997d252 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -1,8 +1,12 @@ +@file:RequiresApi(Build.VERSION_CODES.O) + package org.microg.gms.constellation.core.verification import android.content.Context +import android.os.Build import android.telephony.SubscriptionInfo import android.util.Log +import androidx.annotation.RequiresApi import com.squareup.wire.GrpcException import com.squareup.wire.GrpcStatus import org.microg.gms.constellation.core.ConstellationStateStore diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt index 19fd2d0305..5ff49b42c9 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt @@ -1,3 +1,5 @@ +@file:RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) + package org.microg.gms.constellation.core.verification import android.Manifest @@ -11,6 +13,8 @@ import android.os.Build import android.telephony.SmsManager import android.telephony.SubscriptionManager import android.util.Log +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull @@ -21,7 +25,7 @@ import java.util.UUID import kotlin.coroutines.resume private const val TAG = "MoSmsVerifier" -private const val ACTION_MO_SMS_SENT = "org.microg.gms.constellation.coreMO_SMS_SENT" +private const val ACTION_MO_SMS_SENT = "org.microg.gms.constellation.core.MO_SMS_SENT" suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse { if (proxy_number.isEmpty() || sms.isEmpty()) { @@ -105,7 +109,12 @@ suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse Context.RECEIVER_NOT_EXPORTED ) } else { - context.registerReceiver(receiver, IntentFilter(action)) + ContextCompat.registerReceiver( + context, + receiver, + IntentFilter(action), + ContextCompat.RECEIVER_NOT_EXPORTED + ) } continuation.invokeOnCancellation { @@ -158,37 +167,41 @@ suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse ) } -private fun isActiveSubscription(context: Context, subscriptionId: Int): Boolean { - if (subscriptionId == -1) return false +private fun isActiveSubscription(context: Context, subId: Int): Boolean { + if (subId == -1) return true + return try { - context.getSystemService()?.isActiveSubscriptionId(subscriptionId) - ?: false + val subManager = context.getSystemService() ?: return false + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + subManager.isActiveSubscriptionId(subId) + } else { + subManager.getActiveSubscriptionInfo(subId) != null + } } catch (e: Exception) { - Log.w(TAG, "Failed to query active subscription for $subscriptionId", e) + Log.w(TAG, "Failed to query active subscription for $subId", e) false } } +@Suppress("DEPRECATION") private fun resolveSmsManager(context: Context, subId: Int): SmsManager? { if (subId != -1 && !isActiveSubscription(context, subId)) { return null } + return try { - val manager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - context.getSystemService(SmsManager::class.java) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + val manager = context.getSystemService(SmsManager::class.java) + if (subId != -1) { + manager?.createForSubscriptionId(subId) + } else { + manager + } } else { - null - } - when { - subId != -1 && manager != null -> manager.createForSubscriptionId(subId) - manager != null -> manager - subId != -1 -> { - @Suppress("DEPRECATION") + if (subId != -1) { SmsManager.getSmsManagerForSubscriptionId(subId) - } - - else -> { - @Suppress("DEPRECATION") + } else { SmsManager.getDefault() } } @@ -196,4 +209,4 @@ private fun resolveSmsManager(context: Context, subId: Int): SmsManager? { Log.e(TAG, "Failed to resolve SmsManager for subId: $subId", e) null } -} +} \ No newline at end of file diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt index ceb4395bb1..14e4e42f87 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt @@ -1,10 +1,14 @@ +@file:RequiresApi(Build.VERSION_CODES.LOLLIPOP_MR1) + package org.microg.gms.constellation.core.verification import android.content.Context +import android.os.Build import android.provider.Telephony import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.util.Log +import androidx.annotation.RequiresApi import androidx.core.content.getSystemService import okio.ByteString.Companion.toByteString import org.microg.gms.constellation.core.VerificationSettingsPhenotypes @@ -108,7 +112,7 @@ private fun getLocalNumbers(context: Context, targetSubId: Int): List { try { subscriptionManager?.activeSubscriptionInfoList.orEmpty().forEach { info -> if (targetSubId != -1 && info.subscriptionId != targetSubId) return@forEach - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU && subscriptionManager != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && subscriptionManager != null) { numbers += subscriptionManager.getPhoneNumber( info.subscriptionId, SubscriptionManager.PHONE_NUMBER_SOURCE_CARRIER @@ -123,7 +127,7 @@ private fun getLocalNumbers(context: Context, targetSubId: Int): List { ) } val targetManager = - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { telephonyManager?.createForSubscriptionId(info.subscriptionId) } else { telephonyManager diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt index ebaa6733ef..a43ef69f95 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/Ts43Verifier.kt @@ -1,9 +1,14 @@ +@file:RequiresApi(Build.VERSION_CODES.O) +@file:SuppressLint("HardwareIds") + package org.microg.gms.constellation.core.verification +import android.annotation.SuppressLint import android.content.Context import android.os.Build import android.telephony.TelephonyManager import android.util.Log +import androidx.annotation.RequiresApi import androidx.core.content.getSystemService import okhttp3.Cookie import okhttp3.CookieJar @@ -46,7 +51,7 @@ private class InternalTs43Verifier(private val context: Context, private val sub val tm = requireNotNull(context.getSystemService()) { "TelephonyManager unavailable" } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && subId >= 0) { + if (subId >= 0) { tm.createForSubscriptionId(subId) } else tm } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt index 69e7e846cb..6fe7256578 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt @@ -1,7 +1,13 @@ +@file:RequiresApi(Build.VERSION_CODES.O) +@file:SuppressLint("HardwareIds") + package org.microg.gms.constellation.core.verification.ts43 +import android.annotation.SuppressLint import android.content.Context +import android.os.Build import android.telephony.TelephonyManager +import androidx.annotation.RequiresApi import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl import org.microg.gms.constellation.core.proto.OdsaOperation From 52a58d4e9b54232b0e72e295a0f24b399f7ee507 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 27 Mar 2026 06:55:23 -0400 Subject: [PATCH 07/31] Fix the rest of the lints --- .../core/proto/builders/SyncRequestBuilder.kt | 11 ++++++---- .../proto/builders/TelephonyInfoBuilder.kt | 1 + .../core/verification/MoSmsVerifier.kt | 5 +++++ .../verification/RegisteredSmsVerifier.kt | 21 ++++++++++++++++--- .../ts43/ServiceEntitlementExtension.kt | 2 +- 5 files changed, 32 insertions(+), 8 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt index e2553dcb9b..2be034dba2 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt @@ -3,14 +3,17 @@ package org.microg.gms.constellation.core.proto.builders +import android.Manifest import android.annotation.SuppressLint import android.content.Context +import android.content.pm.PackageManager import android.os.Build import android.telephony.SubscriptionInfo import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.util.Log import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import com.google.android.gms.constellation.VerifyPhoneNumberRequest import com.google.android.gms.constellation.verificationMethods @@ -42,11 +45,8 @@ fun buildImsiToSubscriptionInfoMap(context: Context): Map - val subsTelephonyManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + val subsTelephonyManager = telephonyManager.createForSubscriptionId(info.subscriptionId) - } else { - telephonyManager - } subsTelephonyManager.subscriberId?.let { imsi -> map[imsi] = info } @@ -63,6 +63,9 @@ fun getTelephonyPhoneNumbers( ): List { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return emptyList() + val permissions = listOf(Manifest.permission.READ_PHONE_NUMBERS, Manifest.permission.READ_PHONE_STATE) + if (permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_DENIED }) return emptyList() + val subscriptionManager = context.getSystemService() ?: return emptyList() diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt index fabda054a5..a66a562957 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt @@ -22,6 +22,7 @@ import java.security.MessageDigest private const val TAG = "TelephonyInfoBuilder" +@SuppressLint("MissingPermission") operator fun TelephonyInfo.Companion.invoke(context: Context, subscriptionId: Int): TelephonyInfo { val tm = context.getSystemService() val targetTm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && subscriptionId >= 0) { diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt index 5ff49b42c9..c1f746d109 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt @@ -170,6 +170,11 @@ suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse private fun isActiveSubscription(context: Context, subId: Int): Boolean { if (subId == -1) return true + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED) { + Log.e(TAG, "Permission not granted") + return false + } + return try { val subManager = context.getSystemService() ?: return false diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt index 14e4e42f87..aa8f8044b8 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt @@ -2,13 +2,17 @@ package org.microg.gms.constellation.core.verification +import android.Manifest +import android.annotation.SuppressLint import android.content.Context +import android.content.pm.PackageManager import android.os.Build import android.provider.Telephony import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.util.Log import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import okio.ByteString.Companion.toByteString import org.microg.gms.constellation.core.VerificationSettingsPhenotypes @@ -90,9 +94,6 @@ fun RegisteredSmsChallenge.verify(context: Context, subId: Int): ChallengeRespon } } } - } catch (e: SecurityException) { - Log.w(TAG, "SMS inbox access denied", e) - return ChallengeResponse(registered_sms_response = RegisteredSmsChallengeResponse(items = emptyList())) } catch (e: Exception) { Log.e(TAG, "Registered SMS verification failed", e) return ChallengeResponse(registered_sms_response = RegisteredSmsChallengeResponse(items = emptyList())) @@ -103,12 +104,26 @@ fun RegisteredSmsChallenge.verify(context: Context, subId: Int): ChallengeRespon ) } +@SuppressLint("HardwareIds") private fun getLocalNumbers(context: Context, targetSubId: Int): List { val numbers = linkedSetOf() val subscriptionManager = context.getSystemService() val telephonyManager = context.getSystemService() + val hasState = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED + val isCarrier = telephonyManager?.hasCarrierPrivileges() == true + val hasNumbers = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED + } else { + hasState + } + + if (!isCarrier && (!hasState || !hasNumbers)) { + Log.e(TAG, "Permission not granted") + return emptyList() + } + try { subscriptionManager?.activeSubscriptionInfoList.orEmpty().forEach { info -> if (targetSubId != -1 && info.subscriptionId != targetSubId) return@forEach diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt index 6fe7256578..56d8114022 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt @@ -1,5 +1,5 @@ @file:RequiresApi(Build.VERSION_CODES.O) -@file:SuppressLint("HardwareIds") +@file:SuppressLint("HardwareIds", "MissingPermission") package org.microg.gms.constellation.core.verification.ts43 From 3917eeba97fa5bae9ec0ea164044e97f4bb463b3 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 27 Mar 2026 07:20:33 -0400 Subject: [PATCH 08/31] Convert parcelables into Java --- .../gms/constellation/GetPnvCapabilities.kt | 9 +- .../constellation/core/GetPnvCapabilities.kt | 7 +- .../core/GetVerifiedPhoneNumbers.kt | 2 +- .../core/VerificationMappings.kt | 2 +- .../constellation/core/VerifyPhoneNumber.kt | 52 ++++---- .../gms/constellation/GetIidTokenRequest.java | 32 +++++ .../constellation/GetIidTokenResponse.java | 47 +++++++ .../GetPnvCapabilitiesRequest.java | 42 ++++++ .../GetPnvCapabilitiesResponse.java | 101 +++++++++++++++ .../gms/constellation/PhoneNumberInfo.java | 49 +++++++ .../VerifyPhoneNumberRequest.java | 120 ++++++++++++++++++ .../VerifyPhoneNumberResponse.java | 96 ++++++++++++++ .../android/gms/constellation/GetIidToken.kt | 43 ------- .../gms/constellation/GetPnvCapabilities.kt | 63 --------- .../gms/constellation/PhoneNumberInfo.kt | 27 ---- .../gms/constellation/VerifyPhoneNumber.kt | 91 ------------- 16 files changed, 520 insertions(+), 263 deletions(-) create mode 100644 play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java create mode 100644 play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java create mode 100644 play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java create mode 100644 play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java create mode 100644 play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java create mode 100644 play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java create mode 100644 play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java delete mode 100644 play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetIidToken.kt delete mode 100644 play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt delete mode 100644 play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt delete mode 100644 play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt diff --git a/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt b/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt index 3bd89b61e0..79565ca454 100644 --- a/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt +++ b/play-services-constellation/core/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt @@ -1,5 +1,7 @@ package com.google.android.gms.constellation +import com.google.android.gms.constellation.GetPnvCapabilitiesResponse.VerificationCapability + enum class VerificationStatus(val value: Int) { SUPPORTED(1), UNSUPPORTED_CARRIER(2), @@ -16,14 +18,11 @@ enum class VerificationStatus(val value: Int) { } } -operator fun VerificationCapability.Companion.invoke( +fun verificationCapability( verificationMethod: Int, status: VerificationStatus ): VerificationCapability { - return VerificationCapability( - verificationMethod = verificationMethod, - statusValue = status.value - ) + return VerificationCapability(verificationMethod, status.value) } val VerificationCapability.status: VerificationStatus diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilities.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilities.kt index 72d4617f2e..5aeacfcf12 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilities.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetPnvCapabilities.kt @@ -13,11 +13,10 @@ import com.google.android.gms.common.api.ApiMetadata import com.google.android.gms.common.api.Status import com.google.android.gms.constellation.GetPnvCapabilitiesRequest import com.google.android.gms.constellation.GetPnvCapabilitiesResponse -import com.google.android.gms.constellation.SimCapability -import com.google.android.gms.constellation.VerificationCapability +import com.google.android.gms.constellation.GetPnvCapabilitiesResponse.SimCapability import com.google.android.gms.constellation.VerificationStatus import com.google.android.gms.constellation.internal.IConstellationCallbacks -import com.google.android.gms.constellation.invoke +import com.google.android.gms.constellation.verificationCapability import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.security.MessageDigest @@ -57,7 +56,7 @@ suspend fun handleGetPnvCapabilities( // GMS hardcodes public verification method 9 for the Firebase PNV TS43 capability path. val verificationCapabilities = if (9 in request.verificationTypes) { listOf( - VerificationCapability( + verificationCapability( 9, when { !GetPnvCapabilitiesApiPhenotype.FPNV_ALLOWED_CARRIER_IDS.contains( diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt index e02b3565fa..b344d37d86 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt @@ -9,7 +9,7 @@ import android.util.Log import com.google.android.gms.common.api.ApiMetadata import com.google.android.gms.common.api.Status import com.google.android.gms.constellation.PhoneNumberInfo -import com.google.android.gms.constellation.PhoneNumberVerification +import com.google.android.gms.constellation.VerifyPhoneNumberResponse.PhoneNumberVerification import com.google.android.gms.constellation.VerifyPhoneNumberResponse import com.google.android.gms.constellation.internal.IConstellationCallbacks import kotlinx.coroutines.Dispatchers diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt index 6102ca047a..2fae9c903b 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt @@ -3,7 +3,7 @@ package org.microg.gms.constellation.core import android.os.Build import android.os.Bundle import androidx.annotation.RequiresApi -import com.google.android.gms.constellation.PhoneNumberVerification +import com.google.android.gms.constellation.VerifyPhoneNumberResponse.PhoneNumberVerification import org.microg.gms.constellation.core.proto.Param import org.microg.gms.constellation.core.proto.UnverifiedInfo import org.microg.gms.constellation.core.proto.Verification diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index e456fb00c4..32c5bd82d8 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -10,9 +10,9 @@ import android.util.Log import androidx.annotation.RequiresApi import com.google.android.gms.common.api.ApiMetadata import com.google.android.gms.common.api.Status -import com.google.android.gms.constellation.IdTokenRequest +import com.google.android.gms.constellation.VerifyPhoneNumberRequest.IdTokenRequest import com.google.android.gms.constellation.PhoneNumberInfo -import com.google.android.gms.constellation.PhoneNumberVerification +import com.google.android.gms.constellation.VerifyPhoneNumberResponse.PhoneNumberVerification import com.google.android.gms.constellation.VerifyPhoneNumberRequest import com.google.android.gms.constellation.VerifyPhoneNumberResponse import com.google.android.gms.constellation.internal.IConstellationCallbacks @@ -54,17 +54,17 @@ suspend fun handleVerifyPhoneNumberV1( else -> 300L } val request = VerifyPhoneNumberRequest( - policyId = extras.getString("policy_id", ""), - timeout = timeout, - idTokenRequest = IdTokenRequest( + extras.getString("policy_id", ""), + timeout, + IdTokenRequest( extras.getString("certificate_hash", ""), extras.getString("token_nonce", "") ), - extras = extras, - targetedSims = emptyList(), - silent = false, - apiVersion = 2, - verificationMethodsValues = emptyList() + extras, + emptyList(), + false, + 2, + emptyList() ) val policyId = bundle.getString("policy_id", "") val mode = bundle.getInt("verification_mode", 0) @@ -103,17 +103,17 @@ suspend fun handleVerifyPhoneNumberSingleUse( else -> 300L } val request = VerifyPhoneNumberRequest( - policyId = extras.getString("policy_id", ""), - timeout = timeout, - idTokenRequest = IdTokenRequest( + extras.getString("policy_id", ""), + timeout, + IdTokenRequest( extras.getString("certificate_hash", ""), extras.getString("token_nonce", "") ), - extras = extras, - targetedSims = emptyList(), - silent = false, - apiVersion = 2, - verificationMethodsValues = emptyList() + extras, + emptyList(), + false, + 2, + emptyList() ) handleVerifyPhoneNumberRequest( @@ -133,25 +133,21 @@ suspend fun handleVerifyPhoneNumberRequest( packageName: String? ) { val callingPackage = packageName ?: context.packageName - val requestWithExtras = request.copy( - extras = Bundle(request.extras).apply { - putString("calling_api", "verifyPhoneNumber") - } - ) - val useReadPath = when (requestWithExtras.apiVersion) { - 0 -> requestWithExtras.policyId in VerifyPhoneNumberApiPhenotypes.READ_ONLY_POLICY_IDS + request.extras.putString("calling_api", "verifyPhoneNumber") + val useReadPath = when (request.apiVersion) { + 0 -> request.policyId in VerifyPhoneNumberApiPhenotypes.READ_ONLY_POLICY_IDS 2 -> VerifyPhoneNumberApiPhenotypes.ENABLE_READ_FLOW - 3 -> requestWithExtras.policyId in VerifyPhoneNumberApiPhenotypes.POLICY_IDS_ALLOWED_FOR_LOCAL_READ + 3 -> request.policyId in VerifyPhoneNumberApiPhenotypes.POLICY_IDS_ALLOWED_FOR_LOCAL_READ else -> false } handleVerifyPhoneNumberRequest( context, callbacks, - requestWithExtras, + request, callingPackage, if (useReadPath) ReadCallbackMode.TYPED else ReadCallbackMode.NONE, - localReadFallback = requestWithExtras.apiVersion == 3 && useReadPath, + localReadFallback = request.apiVersion == 3 && useReadPath, legacyCallbackOnFullFlow = false ) } diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java new file mode 100644 index 0000000000..5467842558 --- /dev/null +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java @@ -0,0 +1,32 @@ +package com.google.android.gms.constellation; + +import android.os.Parcel; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GetIidTokenRequest extends AbstractSafeParcelable { + @Field(1) + @Nullable + public final Long projectNumber; + + @Constructor + public GetIidTokenRequest( + @Param(1) @Nullable Long projectNumber + ) { + this.projectNumber = projectNumber; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(GetIidTokenRequest.class); +} \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java new file mode 100644 index 0000000000..0c58b7238b --- /dev/null +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java @@ -0,0 +1,47 @@ +package com.google.android.gms.constellation; + +import android.os.Parcel; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class GetIidTokenResponse extends AbstractSafeParcelable { + @Field(1) + public final String iidToken; + + @Field(2) + public final String fid; + + @Field(3) + @Nullable + public final byte[] signature; + + @Field(4) + public final long timestamp; + + @Constructor + public GetIidTokenResponse( + @Param(1) String iidToken, + @Param(2) String fid, + @Param(3) @Nullable byte[] signature, + @Param(4) long timestamp + ) { + this.iidToken = iidToken; + this.fid = fid; + this.signature = signature; + this.timestamp = timestamp; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(GetIidTokenResponse.class); +} \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java new file mode 100644 index 0000000000..aeafd2b357 --- /dev/null +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java @@ -0,0 +1,42 @@ +package com.google.android.gms.constellation; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.util.List; + +@SafeParcelable.Class +public class GetPnvCapabilitiesRequest extends AbstractSafeParcelable { + @Field(1) + public final String policyId; + + @Field(2) + public final List verificationTypes; + + @Field(3) + public final List simSlotIndices; + + @Constructor + public GetPnvCapabilitiesRequest( + @Param(1) String policyId, + @Param(2) List verificationTypes, + @Param(3) List simSlotIndices + ) { + this.policyId = policyId; + this.verificationTypes = verificationTypes; + this.simSlotIndices = simSlotIndices; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(GetPnvCapabilitiesRequest.class); +} \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java new file mode 100644 index 0000000000..e5dea8c34c --- /dev/null +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java @@ -0,0 +1,101 @@ +package com.google.android.gms.constellation; + +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.util.List; + +@SafeParcelable.Class +public class GetPnvCapabilitiesResponse extends AbstractSafeParcelable { + @Field(1) + public final List simCapabilities; + + @Constructor + public GetPnvCapabilitiesResponse( + @Param(1) List simCapabilities + ) { + this.simCapabilities = simCapabilities; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(GetPnvCapabilitiesResponse.class); + + @SafeParcelable.Class + public static class SimCapability extends AbstractSafeParcelable { + + @Field(1) + public final int slotValue; + + @Field(2) + public final String subscriberIdDigest; + + @Field(3) + public final int carrierId; + + @Field(4) + public final String operatorName; + + @Field(5) + public final List verificationCapabilities; + + @Constructor + public SimCapability( + @Param(1) int slotValue, + @Param(2) String subscriberIdDigest, + @Param(3) int carrierId, + @Param(4) String operatorName, + @Param(5) List verificationCapabilities + ) { + this.slotValue = slotValue; + this.subscriberIdDigest = subscriberIdDigest; + this.carrierId = carrierId; + this.operatorName = operatorName; + this.verificationCapabilities = verificationCapabilities; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(SimCapability.class); + } + + @SafeParcelable.Class + public static class VerificationCapability extends AbstractSafeParcelable { + + @Field(1) + public final int verificationMethod; + + @Field(2) + public final int statusValue; + + @Constructor + public VerificationCapability( + @Param(1) int verificationMethod, + @Param(2) int statusValue + ) { + this.verificationMethod = verificationMethod; + this.statusValue = statusValue; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(VerificationCapability.class); + } +} \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java new file mode 100644 index 0000000000..82b4ce0268 --- /dev/null +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java @@ -0,0 +1,49 @@ +package com.google.android.gms.constellation; + +import android.os.Bundle; +import android.os.Parcel; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class PhoneNumberInfo extends AbstractSafeParcelable { + @Field(1) + public final int version; + + @Field(2) + @Nullable + public final String phoneNumber; + + @Field(3) + public final long verificationTime; + + @Field(4) + @Nullable + public final Bundle extras; + + @Constructor + public PhoneNumberInfo( + @SafeParcelable.Param(1) int version, + @SafeParcelable.Param(2) @Nullable String phoneNumber, + @SafeParcelable.Param(3) long verificationTime, + @SafeParcelable.Param(4) @Nullable Bundle extras + ) { + this.version = version; + this.phoneNumber = phoneNumber; + this.verificationTime = verificationTime; + this.extras = extras; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(PhoneNumberInfo.class); +} \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java new file mode 100644 index 0000000000..e554934ae6 --- /dev/null +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java @@ -0,0 +1,120 @@ +package com.google.android.gms.constellation; + +import android.os.Bundle; +import android.os.Parcel; + +import androidx.annotation.NonNull; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +import java.util.List; + +@SafeParcelable.Class +public class VerifyPhoneNumberRequest extends AbstractSafeParcelable { + @Field(1) + public final String policyId; + + @Field(2) + public final long timeout; + + @Field(3) + public final IdTokenRequest idTokenRequest; + + @Field(4) + public final Bundle extras; + + @Field(5) + public final List targetedSims; + + @Field(6) + public final boolean silent; + + @Field(7) + public final int apiVersion; + + @Field(8) + public final List verificationMethodsValues; + + @Constructor + public VerifyPhoneNumberRequest( + @Param(1) String policyId, + @Param(2) long timeout, + @Param(3) IdTokenRequest idTokenRequest, + @Param(4) Bundle extras, + @Param(5) List targetedSims, + @Param(6) boolean silent, + @Param(7) int apiVersion, + @Param(8) List verificationMethodsValues + ) { + this.policyId = policyId; + this.timeout = timeout; + this.idTokenRequest = idTokenRequest; + this.extras = extras; + this.targetedSims = targetedSims; + this.silent = silent; + this.apiVersion = apiVersion; + this.verificationMethodsValues = verificationMethodsValues; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(VerifyPhoneNumberRequest.class); + + @SafeParcelable.Class + public static class IdTokenRequest extends AbstractSafeParcelable { + @Field(1) + public final String idToken; + + @Field(2) + public final String subscriberHash; + + @Constructor + public IdTokenRequest( + @Param(1) String idToken, + @Param(2) String subscriberHash + ) { + this.idToken = idToken; + this.subscriberHash = subscriberHash; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(IdTokenRequest.class); + } + + @SafeParcelable.Class + public static class ImsiRequest extends AbstractSafeParcelable { + @Field(1) + public final String imsi; + + @Field(2) + public final String phoneNumberHint; + + @Constructor + public ImsiRequest( + @Param(1) String imsi, + @Param(2) String phoneNumberHint + ) { + this.imsi = imsi; + this.phoneNumberHint = phoneNumberHint; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(ImsiRequest.class); + } +} \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java new file mode 100644 index 0000000000..4a94badd74 --- /dev/null +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java @@ -0,0 +1,96 @@ +package com.google.android.gms.constellation; + +import android.os.Bundle; +import android.os.Parcel; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelable; +import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter; + +@SafeParcelable.Class +public class VerifyPhoneNumberResponse extends AbstractSafeParcelable { + @Field(1) + public final PhoneNumberVerification[] verifications; + + @Field(2) + public final Bundle extras; + + @Constructor + public VerifyPhoneNumberResponse( + @Param(1) PhoneNumberVerification[] verifications, + @Param(2) Bundle extras + ) { + this.verifications = verifications; + this.extras = extras; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(VerifyPhoneNumberResponse.class); + + @SafeParcelable.Class + public static class PhoneNumberVerification extends AbstractSafeParcelable { + @Field(1) + @Nullable + public final String phoneNumber; + + @Field(2) + public final long timestampMillis; + + @Field(3) + public final int verificationMethod; + + @Field(4) + public final int simSlot; + + @Field(5) + @Nullable + public final String verificationToken; + + @Field(6) + @Nullable + public final Bundle extras; + + @Field(7) + public final int verificationStatus; + + @Field(8) + public final long retryAfterSeconds; + + @Constructor + public PhoneNumberVerification( + @Param(1) @Nullable String phoneNumber, + @Param(2) long timestampMillis, + @Param(3) int verificationMethod, + @Param(4) int simSlot, + @Param(5) @Nullable String verificationToken, + @Param(6) @Nullable Bundle extras, + @Param(7) int verificationStatus, + @Param(8) long retryAfterSeconds + ) { + this.phoneNumber = phoneNumber; + this.timestampMillis = timestampMillis; + this.verificationMethod = verificationMethod; + this.simSlot = simSlot; + this.verificationToken = verificationToken; + this.extras = extras; + this.verificationStatus = verificationStatus; + this.retryAfterSeconds = retryAfterSeconds; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + CREATOR.writeToParcel(this, dest, flags); + } + + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(PhoneNumberVerification.class); + } +} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetIidToken.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetIidToken.kt deleted file mode 100644 index 53760184ab..0000000000 --- a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetIidToken.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.google.android.gms.constellation - -import android.os.Parcel -import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable -import com.google.android.gms.common.internal.safeparcel.SafeParcelable -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param -import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter - -@SafeParcelable.Class -data class GetIidTokenRequest @Constructor constructor( - @JvmField @Param(1) @Field(1) val projectNumber: Long? -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) { - CREATOR.writeToParcel(this, out, flags) - } - - companion object { - @JvmField - val CREATOR: SafeParcelableCreatorAndWriter = - findCreator(GetIidTokenRequest::class.java) - } -} - -@SafeParcelable.Class -data class GetIidTokenResponse @Constructor constructor( - @JvmField @Param(1) @Field(1) val iidToken: String, - @JvmField @Param(2) @Field(2) val fid: String, - @JvmField @Param(3) @Field(value = 3, type = "byte[]") val signature: ByteArray?, - @JvmField @Param(4) @Field(4) val timestamp: Long -) : AbstractSafeParcelable() { - - override fun writeToParcel(out: Parcel, flags: Int) { - CREATOR.writeToParcel(this, out, flags) - } - - companion object { - @JvmField - val CREATOR: SafeParcelableCreatorAndWriter = - findCreator(GetIidTokenResponse::class.java) - } -} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt deleted file mode 100644 index fe80003950..0000000000 --- a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/GetPnvCapabilities.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.google.android.gms.constellation - -import android.os.Parcel -import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable -import com.google.android.gms.common.internal.safeparcel.SafeParcelable -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param - -@SafeParcelable.Class -data class GetPnvCapabilitiesRequest @Constructor constructor( - @JvmField @Param(1) @Field(1) val policyId: String, - @JvmField @Param(2) @Field(2) val verificationTypes: List, - @JvmField @Param(3) @Field(3) val simSlotIndices: List -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) - - companion object { - @JvmField - val CREATOR = findCreator(GetPnvCapabilitiesRequest::class.java) - } -} - -@SafeParcelable.Class -data class GetPnvCapabilitiesResponse @Constructor constructor( - @JvmField @Param(1) @Field(1) val simCapabilities: List -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) - - companion object { - @JvmField - val CREATOR = findCreator(GetPnvCapabilitiesResponse::class.java) - } -} - -@SafeParcelable.Class -data class SimCapability @Constructor constructor( - @JvmField @Param(1) @Field(1) val slotValue: Int, - @JvmField @Param(2) @Field(2) val subscriberIdDigest: String, - @JvmField @Param(3) @Field(3) val carrierId: Int, - @JvmField @Param(4) @Field(4) val operatorName: String, - @JvmField @Param(5) @Field(5) val verificationCapabilities: List -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) - - companion object { - @JvmField - val CREATOR = findCreator(SimCapability::class.java) - } -} - -@SafeParcelable.Class -data class VerificationCapability @Constructor constructor( - @JvmField @Param(1) @Field(1) val verificationMethod: Int, - @JvmField @Param(2) @Field(2) val statusValue: Int -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) - - companion object { - @JvmField - val CREATOR = findCreator(VerificationCapability::class.java) - } -} \ No newline at end of file diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt deleted file mode 100644 index f16110f7f9..0000000000 --- a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/PhoneNumberInfo.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.google.android.gms.constellation - -import android.os.Bundle -import android.os.Parcel -import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable -import com.google.android.gms.common.internal.safeparcel.SafeParcelable -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param - -@SafeParcelable.Class -data class PhoneNumberInfo @Constructor constructor( - @JvmField @Param(1) @Field(1) val version: Int, - @JvmField @Param(2) @Field(2) val phoneNumber: String?, - @JvmField @Param(3) @Field(3) val verificationTime: Long, - @JvmField @Param(4) @Field(4) val extras: Bundle? -) : AbstractSafeParcelable() { - - override fun writeToParcel(out: Parcel, flags: Int) { - CREATOR.writeToParcel(this, out, flags) - } - - companion object { - @JvmField - val CREATOR = findCreator(PhoneNumberInfo::class.java) - } -} diff --git a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt b/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt deleted file mode 100644 index c2a0ef8115..0000000000 --- a/play-services-constellation/src/main/kotlin/com/google/android/gms/constellation/VerifyPhoneNumber.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.google.android.gms.constellation - -import android.os.Bundle -import android.os.Parcel -import com.google.android.gms.common.internal.safeparcel.AbstractSafeParcelable -import com.google.android.gms.common.internal.safeparcel.SafeParcelable -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Constructor -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Field -import com.google.android.gms.common.internal.safeparcel.SafeParcelable.Param -import com.google.android.gms.common.internal.safeparcel.SafeParcelableCreatorAndWriter - -@SafeParcelable.Class -data class VerifyPhoneNumberRequest @Constructor constructor( - @JvmField @Param(1) @Field(1) val policyId: String, - @JvmField @Param(2) @Field(2) val timeout: Long, - @JvmField @Param(3) @Field(3) val idTokenRequest: IdTokenRequest, - @JvmField @Param(4) @Field(4) val extras: Bundle, - @JvmField @Param(5) @Field(5) val targetedSims: List, - @JvmField @Param(6) @Field(6) val silent: Boolean, - @JvmField @Param(7) @Field(7) val apiVersion: Int, - @JvmField @Param(8) @Field(8) val verificationMethodsValues: List -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) - - companion object { - @JvmField - val CREATOR = findCreator(VerifyPhoneNumberRequest::class.java) - } -} - -@SafeParcelable.Class -data class IdTokenRequest @Constructor constructor( - @JvmField @Param(1) @Field(1) val idToken: String, - @JvmField @Param(2) @Field(2) val subscriberHash: String -) : AbstractSafeParcelable() { - - override fun writeToParcel(out: Parcel, flags: Int) { - CREATOR.writeToParcel(this, out, flags) - } - - companion object { - @JvmField - val CREATOR: SafeParcelableCreatorAndWriter = - findCreator(IdTokenRequest::class.java) - } -} - -@SafeParcelable.Class -data class ImsiRequest @Constructor constructor( - @JvmField @Param(1) @Field(1) val imsi: String, - @JvmField @Param(2) @Field(2) val phoneNumberHint: String -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) - - companion object { - @JvmField - val CREATOR = findCreator(ImsiRequest::class.java) - } -} - -@SafeParcelable.Class -data class VerifyPhoneNumberResponse @Constructor constructor( - @JvmField @Param(1) @Field(1) val verifications: Array, - @JvmField @Param(2) @Field(2) val extras: Bundle -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) - - companion object { - @JvmField - val CREATOR = findCreator(VerifyPhoneNumberResponse::class.java) - } -} - -@SafeParcelable.Class -data class PhoneNumberVerification @Constructor constructor( - @JvmField @Param(1) @Field(1) val phoneNumber: String?, - @JvmField @Param(2) @Field(2) val timestampMillis: Long, - @JvmField @Param(3) @Field(3) val verificationMethod: Int, - @JvmField @Param(4) @Field(4) val simSlot: Int, - @JvmField @Param(5) @Field(5) val verificationToken: String?, - @JvmField @Param(6) @Field(6) val extras: Bundle?, - @JvmField @Param(7) @Field(7) val verificationStatus: Int, - @JvmField @Param(8) @Field(8) val retryAfterSeconds: Long -) : AbstractSafeParcelable() { - override fun writeToParcel(out: Parcel, flags: Int) = CREATOR.writeToParcel(this, out, flags) - - companion object { - @JvmField - val CREATOR = findCreator(PhoneNumberVerification::class.java) - } -} \ No newline at end of file From aec46b8221eaf9a7e419bb65c426bec10870737c Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 27 Mar 2026 07:42:52 -0400 Subject: [PATCH 09/31] Small fixes --- .../gms/constellation/core/AuthManager.kt | 2 +- .../core/GetVerifiedPhoneNumbers.kt | 2 +- .../constellation/core/IidTokenPhenotypes.kt | 10 +++++----- .../core/proto/builders/GaiaInfoBuilder.kt | 19 ++++++++++--------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt index ba601c3832..6f2a151427 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/AuthManager.kt @@ -56,7 +56,7 @@ class AuthManager private constructor(context: Context) { fun getIidToken(projectNumber: String? = null): String { return try { - val sender = projectNumber ?: IidTokenPhenotypes.DEFAULT_PROJECT_NUMBER.toString() + val sender = projectNumber ?: IidTokenPhenotypes.DEFAULT_PROJECT_NUMBER InstanceID.getInstance(context).getToken(sender, "GCM") } catch (_: Exception) { "" diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt index b344d37d86..224e7a4f9a 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt @@ -54,7 +54,7 @@ internal suspend fun fetchVerifiedPhoneNumbers( val certificateHash = bundle.getString("certificate_hash") ?: "" val tokenNonce = bundle.getString("token_nonce") ?: "" - val iidToken = authManager.getIidToken() + val iidToken = authManager.getIidToken(IidTokenPhenotypes.READ_ONLY_PROJECT_NUMBER) val iidTokenAuth = if (VerifyPhoneNumberApiPhenotypes.ENABLE_CLIENT_SIGNATURE) { val (signatureBytes, signTimestamp) = authManager.signIidToken(iidToken) IIDTokenAuth( diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/IidTokenPhenotypes.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/IidTokenPhenotypes.kt index 984c902321..4b42087420 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/IidTokenPhenotypes.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/IidTokenPhenotypes.kt @@ -1,9 +1,9 @@ package org.microg.gms.constellation.core object IidTokenPhenotypes { - const val ASTERISM_PROJECT_NUMBER = 496232013492 - const val DEFAULT_PROJECT_NUMBER = 496232013492 - const val EXTERNAL_CONSENT_ACTIVITY_PROJECT_NUMBER = 496232013492 - const val MESSAGES_PROJECT_NUMBER = 496232013492 - const val READ_ONLY_PROJECT_NUMBER = 745476177629 + const val ASTERISM_PROJECT_NUMBER = "496232013492" + const val DEFAULT_PROJECT_NUMBER = "496232013492" + const val EXTERNAL_CONSENT_ACTIVITY_PROJECT_NUMBER = "496232013492" + const val MESSAGES_PROJECT_NUMBER = "496232013492" + const val READ_ONLY_PROJECT_NUMBER = "745476177629" } \ No newline at end of file diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt index 60ff924562..bf76f353e6 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt @@ -55,12 +55,11 @@ operator fun GaiaSignals.Companion.invoke(context: Context): GaiaSignals? { @Suppress("DEPRECATION") suspend fun GaiaToken.Companion.getList(context: Context): List = withContext(Dispatchers.IO) { - val gaiaTokens = mutableListOf() - try { - val accounts = AccountManager.get(context).getAccountsByType("com.google") - if (accounts.isNotEmpty()) { + val accounts = AccountManager.get(context).getAccountsByType("com.google") + accounts.mapNotNull { account -> + try { val future = AccountManager.get(context).getAuthToken( - accounts.first(), + account, "oauth2:https://www.googleapis.com/auth/numberer", null, false, @@ -68,10 +67,12 @@ suspend fun GaiaToken.Companion.getList(context: Context): List = null ) val token = future.result?.getString(AccountManager.KEY_AUTHTOKEN) - if (!token.isNullOrBlank()) gaiaTokens.add(GaiaToken(token = token)) + + if (!token.isNullOrBlank()) GaiaToken(token = token) else null + + } catch (e: Exception) { + Log.w(TAG, "Could not retrieve Gaia token for an account", e) + null } - } catch (e: Exception) { - Log.w(TAG, "Could not retrieve Gaia tokens", e) } - gaiaTokens } From 23e80ba63c1028c896afbd0b8f2ee81ce65b2dbe Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 27 Mar 2026 07:47:27 -0400 Subject: [PATCH 10/31] Formatting --- .../core/src/main/AndroidManifest.xml | 9 +++---- .../core/GetVerifiedPhoneNumbers.kt | 5 ++-- .../constellation/core/VerifyPhoneNumber.kt | 6 +++-- .../core/proto/builders/CommonBuilders.kt | 1 - .../core/proto/builders/GaiaInfoBuilder.kt | 1 - .../core/proto/builders/SyncRequestBuilder.kt | 11 +++++++-- .../core/verification/MoSmsVerifier.kt | 6 ++++- .../verification/RegisteredSmsVerifier.kt | 11 +++++++-- .../gms/constellation/GetIidTokenRequest.java | 5 ++-- .../constellation/GetIidTokenResponse.java | 8 ++----- .../GetPnvCapabilitiesRequest.java | 7 ++---- .../GetPnvCapabilitiesResponse.java | 20 +++++----------- .../gms/constellation/PhoneNumberInfo.java | 8 ++----- .../VerifyPhoneNumberRequest.java | 24 +++++-------------- .../VerifyPhoneNumberResponse.java | 18 ++++---------- 15 files changed, 59 insertions(+), 81 deletions(-) diff --git a/play-services-constellation/core/src/main/AndroidManifest.xml b/play-services-constellation/core/src/main/AndroidManifest.xml index bd95282658..8ad6d5d3fe 100644 --- a/play-services-constellation/core/src/main/AndroidManifest.xml +++ b/play-services-constellation/core/src/main/AndroidManifest.xml @@ -6,16 +6,17 @@ + - - - + + diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt index 224e7a4f9a..28f5df0669 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt @@ -1,4 +1,5 @@ @file:SuppressLint("NewApi") + package org.microg.gms.constellation.core import android.annotation.SuppressLint @@ -9,8 +10,8 @@ import android.util.Log import com.google.android.gms.common.api.ApiMetadata import com.google.android.gms.common.api.Status import com.google.android.gms.constellation.PhoneNumberInfo -import com.google.android.gms.constellation.VerifyPhoneNumberResponse.PhoneNumberVerification import com.google.android.gms.constellation.VerifyPhoneNumberResponse +import com.google.android.gms.constellation.VerifyPhoneNumberResponse.PhoneNumberVerification import com.google.android.gms.constellation.internal.IConstellationCallbacks import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -19,8 +20,8 @@ import org.microg.gms.constellation.core.proto.GetVerifiedPhoneNumbersRequest import org.microg.gms.constellation.core.proto.GetVerifiedPhoneNumbersRequest.PhoneNumberSelection import org.microg.gms.constellation.core.proto.IIDTokenAuth import org.microg.gms.constellation.core.proto.TokenOption -import java.util.UUID import org.microg.gms.constellation.core.proto.VerifiedPhoneNumber +import java.util.UUID private const val TAG = "GetVerifiedPhoneNumbers" diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index 32c5bd82d8..cf5871186e 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -10,11 +10,11 @@ import android.util.Log import androidx.annotation.RequiresApi import com.google.android.gms.common.api.ApiMetadata import com.google.android.gms.common.api.Status -import com.google.android.gms.constellation.VerifyPhoneNumberRequest.IdTokenRequest import com.google.android.gms.constellation.PhoneNumberInfo -import com.google.android.gms.constellation.VerifyPhoneNumberResponse.PhoneNumberVerification import com.google.android.gms.constellation.VerifyPhoneNumberRequest +import com.google.android.gms.constellation.VerifyPhoneNumberRequest.IdTokenRequest import com.google.android.gms.constellation.VerifyPhoneNumberResponse +import com.google.android.gms.constellation.VerifyPhoneNumberResponse.PhoneNumberVerification import com.google.android.gms.constellation.internal.IConstellationCallbacks import com.squareup.wire.GrpcException import com.squareup.wire.GrpcStatus @@ -37,6 +37,7 @@ private enum class ReadCallbackMode { TYPED } +@Suppress("DEPRECATION") suspend fun handleVerifyPhoneNumberV1( context: Context, callbacks: IConstellationCallbacks, @@ -85,6 +86,7 @@ suspend fun handleVerifyPhoneNumberV1( ) } +@Suppress("DEPRECATION") suspend fun handleVerifyPhoneNumberSingleUse( context: Context, callbacks: IConstellationCallbacks, diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt index ae7e49ba83..d3a4e4beb5 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt @@ -1,4 +1,3 @@ - package org.microg.gms.constellation.core.proto.builders import android.content.Context diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt index bf76f353e6..4c6dd5987d 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt @@ -1,4 +1,3 @@ - package org.microg.gms.constellation.core.proto.builders import android.accounts.AccountManager diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt index 2be034dba2..3d1dca1acf 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt @@ -63,8 +63,14 @@ fun getTelephonyPhoneNumbers( ): List { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) return emptyList() - val permissions = listOf(Manifest.permission.READ_PHONE_NUMBERS, Manifest.permission.READ_PHONE_STATE) - if (permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_DENIED }) return emptyList() + val permissions = + listOf(Manifest.permission.READ_PHONE_NUMBERS, Manifest.permission.READ_PHONE_STATE) + if (permissions.all { + ContextCompat.checkSelfPermission( + context, + it + ) == PackageManager.PERMISSION_DENIED + }) return emptyList() val subscriptionManager = context.getSystemService() ?: return emptyList() @@ -122,6 +128,7 @@ suspend operator fun SyncRequest.Companion.invoke( ) } +@Suppress("DEPRECATION") suspend operator fun SyncRequest.Companion.invoke( context: Context, sessionId: String, diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt index c1f746d109..2ab7176df7 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt @@ -170,7 +170,11 @@ suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse private fun isActiveSubscription(context: Context, subId: Int): Boolean { if (subId == -1) return true - if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_DENIED) { + if (ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_PHONE_STATE + ) == PackageManager.PERMISSION_DENIED + ) { Log.e(TAG, "Permission not granted") return false } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt index aa8f8044b8..b010277b41 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/RegisteredSmsVerifier.kt @@ -105,16 +105,23 @@ fun RegisteredSmsChallenge.verify(context: Context, subId: Int): ChallengeRespon } @SuppressLint("HardwareIds") +@Suppress("DEPRECATION") private fun getLocalNumbers(context: Context, targetSubId: Int): List { val numbers = linkedSetOf() val subscriptionManager = context.getSystemService() val telephonyManager = context.getSystemService() - val hasState = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED + val hasState = ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_PHONE_STATE + ) == PackageManager.PERMISSION_GRANTED val isCarrier = telephonyManager?.hasCarrierPrivileges() == true val hasNumbers = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_NUMBERS) == PackageManager.PERMISSION_GRANTED + ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_PHONE_NUMBERS + ) == PackageManager.PERMISSION_GRANTED } else { hasState } diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java index 5467842558..311dcb0fd9 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java @@ -11,6 +11,8 @@ @SafeParcelable.Class public class GetIidTokenRequest extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(GetIidTokenRequest.class); @Field(1) @Nullable public final Long projectNumber; @@ -26,7 +28,4 @@ public GetIidTokenRequest( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(GetIidTokenRequest.class); } \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java index 0c58b7238b..fd2a3c66d6 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java @@ -11,16 +11,15 @@ @SafeParcelable.Class public class GetIidTokenResponse extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(GetIidTokenResponse.class); @Field(1) public final String iidToken; - @Field(2) public final String fid; - @Field(3) @Nullable public final byte[] signature; - @Field(4) public final long timestamp; @@ -41,7 +40,4 @@ public GetIidTokenResponse( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(GetIidTokenResponse.class); } \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java index aeafd2b357..9fbc98b5c0 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java @@ -12,12 +12,12 @@ @SafeParcelable.Class public class GetPnvCapabilitiesRequest extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(GetPnvCapabilitiesRequest.class); @Field(1) public final String policyId; - @Field(2) public final List verificationTypes; - @Field(3) public final List simSlotIndices; @@ -36,7 +36,4 @@ public GetPnvCapabilitiesRequest( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(GetPnvCapabilitiesRequest.class); } \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java index e5dea8c34c..b1382969ca 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java @@ -12,6 +12,8 @@ @SafeParcelable.Class public class GetPnvCapabilitiesResponse extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(GetPnvCapabilitiesResponse.class); @Field(1) public final List simCapabilities; @@ -27,24 +29,19 @@ public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(GetPnvCapabilitiesResponse.class); - @SafeParcelable.Class public static class SimCapability extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(SimCapability.class); @Field(1) public final int slotValue; - @Field(2) public final String subscriberIdDigest; - @Field(3) public final int carrierId; - @Field(4) public final String operatorName; - @Field(5) public final List verificationCapabilities; @@ -67,17 +64,15 @@ public SimCapability( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(SimCapability.class); } @SafeParcelable.Class public static class VerificationCapability extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(VerificationCapability.class); @Field(1) public final int verificationMethod; - @Field(2) public final int statusValue; @@ -94,8 +89,5 @@ public VerificationCapability( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(VerificationCapability.class); } } \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java index 82b4ce0268..0dc2c9fbfe 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java @@ -12,16 +12,15 @@ @SafeParcelable.Class public class PhoneNumberInfo extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(PhoneNumberInfo.class); @Field(1) public final int version; - @Field(2) @Nullable public final String phoneNumber; - @Field(3) public final long verificationTime; - @Field(4) @Nullable public final Bundle extras; @@ -43,7 +42,4 @@ public PhoneNumberInfo( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(PhoneNumberInfo.class); } \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java index e554934ae6..c68ecc7e53 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java @@ -13,27 +13,22 @@ @SafeParcelable.Class public class VerifyPhoneNumberRequest extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(VerifyPhoneNumberRequest.class); @Field(1) public final String policyId; - @Field(2) public final long timeout; - @Field(3) public final IdTokenRequest idTokenRequest; - @Field(4) public final Bundle extras; - @Field(5) public final List targetedSims; - @Field(6) public final boolean silent; - @Field(7) public final int apiVersion; - @Field(8) public final List verificationMethodsValues; @@ -63,14 +58,12 @@ public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(VerifyPhoneNumberRequest.class); - @SafeParcelable.Class public static class IdTokenRequest extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(IdTokenRequest.class); @Field(1) public final String idToken; - @Field(2) public final String subscriberHash; @@ -87,16 +80,14 @@ public IdTokenRequest( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(IdTokenRequest.class); } @SafeParcelable.Class public static class ImsiRequest extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(ImsiRequest.class); @Field(1) public final String imsi; - @Field(2) public final String phoneNumberHint; @@ -113,8 +104,5 @@ public ImsiRequest( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(ImsiRequest.class); } } \ No newline at end of file diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java index 4a94badd74..d66fca5c19 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java @@ -12,9 +12,10 @@ @SafeParcelable.Class public class VerifyPhoneNumberResponse extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(VerifyPhoneNumberResponse.class); @Field(1) public final PhoneNumberVerification[] verifications; - @Field(2) public final Bundle extras; @@ -32,35 +33,27 @@ public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(VerifyPhoneNumberResponse.class); - @SafeParcelable.Class public static class PhoneNumberVerification extends AbstractSafeParcelable { + public static SafeParcelableCreatorAndWriter CREATOR = + findCreator(PhoneNumberVerification.class); @Field(1) @Nullable public final String phoneNumber; - @Field(2) public final long timestampMillis; - @Field(3) public final int verificationMethod; - @Field(4) public final int simSlot; - @Field(5) @Nullable public final String verificationToken; - @Field(6) @Nullable public final Bundle extras; - @Field(7) public final int verificationStatus; - @Field(8) public final long retryAfterSeconds; @@ -89,8 +82,5 @@ public PhoneNumberVerification( public void writeToParcel(@NonNull Parcel dest, int flags) { CREATOR.writeToParcel(this, dest, flags); } - - public static SafeParcelableCreatorAndWriter CREATOR = - findCreator(PhoneNumberVerification.class); } } \ No newline at end of file From 59e189566908bf8beeea334f3936555c5184f72f Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 27 Mar 2026 08:01:21 -0400 Subject: [PATCH 11/31] why did i write it like this --- .../gms/constellation/core/proto/builders/GaiaInfoBuilder.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt index 4c6dd5987d..cb9c9e508f 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt @@ -41,7 +41,7 @@ operator fun GaiaSignals.Companion.invoke(context: Context): GaiaSignals? { GaiaSignalEntry( gaia_id = obfuscatedId, signal_type = GaiaAccountSignalType.GAIA_ACCOUNT_SIGNAL_AUTHENTICATED, - timestamp = Instant.ofEpochMilli(System.currentTimeMillis()) + timestamp = Instant.now() ) ) } From b20cc3caeded72faf3f7fd02bb95483b6f8a1f9d Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 27 Mar 2026 19:39:24 -0400 Subject: [PATCH 12/31] Remove full name and use import instead --- .../gms/constellation/core/proto/builders/ClientInfoBuilder.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt index 8a67cb84ad..8ef77487f8 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt @@ -11,6 +11,7 @@ import android.util.Log import androidx.annotation.RequiresApi import androidx.core.content.edit import androidx.core.content.getSystemService +import com.google.android.gms.droidguard.DroidGuard import com.google.android.gms.tasks.await import okio.ByteString.Companion.toByteString import org.microg.gms.common.Constants @@ -157,7 +158,7 @@ suspend operator fun DroidGuardSignals.Companion.invoke(context: Context): Droid } return try { - val client = com.google.android.gms.droidguard.DroidGuard.getClient(context) + val client = DroidGuard.getClient(context) val data = mapOf( "package_name" to context.packageName, "timestamp" to System.currentTimeMillis().toString() From 2552a9e8937d28766ce17f25e1172c442f50b907 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sun, 29 Mar 2026 00:54:33 -0400 Subject: [PATCH 13/31] Use GMS package name instead of our own --- .../gms/constellation/core/GetVerifiedPhoneNumbers.kt | 3 ++- .../microg/gms/constellation/core/VerifyPhoneNumber.kt | 9 ++++++--- .../core/proto/builders/ClientInfoBuilder.kt | 2 +- .../core/proto/builders/SyncRequestBuilder.kt | 5 +++-- .../verification/ts43/ServiceEntitlementExtension.kt | 3 +-- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt index 28f5df0669..5f73229910 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/GetVerifiedPhoneNumbers.kt @@ -16,6 +16,7 @@ import com.google.android.gms.constellation.internal.IConstellationCallbacks import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import okio.ByteString.Companion.toByteString +import org.microg.gms.common.Constants import org.microg.gms.constellation.core.proto.GetVerifiedPhoneNumbersRequest import org.microg.gms.constellation.core.proto.GetVerifiedPhoneNumbersRequest.PhoneNumberSelection import org.microg.gms.constellation.core.proto.IIDTokenAuth @@ -47,7 +48,7 @@ suspend fun handleGetVerifiedPhoneNumbers( internal suspend fun fetchVerifiedPhoneNumbers( context: Context, bundle: Bundle, - callingPackage: String = bundle.getString("calling_package") ?: context.packageName + callingPackage: String = bundle.getString("calling_package") ?: Constants.GMS_PACKAGE_NAME ): List = withContext(Dispatchers.IO) { val authManager = context.authManager val sessionId = UUID.randomUUID().toString() diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index cf5871186e..572d8dcf8f 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -20,6 +20,7 @@ import com.squareup.wire.GrpcException import com.squareup.wire.GrpcStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.microg.gms.common.Constants import org.microg.gms.constellation.core.proto.SyncRequest import org.microg.gms.constellation.core.proto.Verification import org.microg.gms.constellation.core.proto.builders.RequestBuildContext @@ -44,7 +45,8 @@ suspend fun handleVerifyPhoneNumberV1( bundle: Bundle, packageName: String? ) { - val callingPackage = packageName ?: bundle.getString("calling_package") ?: context.packageName + val callingPackage = + packageName ?: bundle.getString("calling_package") ?: Constants.GMS_PACKAGE_NAME val extras = Bundle(bundle).apply { putString("calling_package", callingPackage) putString("calling_api", "verifyPhoneNumber") @@ -93,7 +95,8 @@ suspend fun handleVerifyPhoneNumberSingleUse( bundle: Bundle, packageName: String? ) { - val callingPackage = packageName ?: bundle.getString("calling_package") ?: context.packageName + val callingPackage = + packageName ?: bundle.getString("calling_package") ?: Constants.GMS_PACKAGE_NAME val extras = Bundle(bundle).apply { putString("calling_package", callingPackage) putString("calling_api", "verifyPhoneNumberSingleUse") @@ -134,7 +137,7 @@ suspend fun handleVerifyPhoneNumberRequest( request: VerifyPhoneNumberRequest, packageName: String? ) { - val callingPackage = packageName ?: context.packageName + val callingPackage = packageName ?: Constants.GMS_PACKAGE_NAME request.extras.putString("calling_api", "verifyPhoneNumber") val useReadPath = when (request.apiVersion) { 0 -> request.policyId in VerifyPhoneNumberApiPhenotypes.READ_ONLY_POLICY_IDS diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt index 8ef77487f8..a456974288 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt @@ -160,7 +160,7 @@ suspend operator fun DroidGuardSignals.Companion.invoke(context: Context): Droid return try { val client = DroidGuard.getClient(context) val data = mapOf( - "package_name" to context.packageName, + "package_name" to Constants.GMS_PACKAGE_NAME, "timestamp" to System.currentTimeMillis().toString() ) val result = client.getResults("constellation_verify", data, null).await() diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt index 3d1dca1acf..0dc25d93b6 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt @@ -17,6 +17,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import com.google.android.gms.constellation.VerifyPhoneNumberRequest import com.google.android.gms.constellation.verificationMethods +import org.microg.gms.common.Constants import org.microg.gms.constellation.core.ConstellationStateStore import org.microg.gms.constellation.core.proto.ChallengePreference import org.microg.gms.constellation.core.proto.ChallengePreferenceMetadata @@ -113,7 +114,7 @@ suspend operator fun SyncRequest.Companion.invoke( sessionId: String, request: VerifyPhoneNumberRequest, includeClientAuth: Boolean = false, - callingPackage: String = context.packageName, + callingPackage: String = Constants.GMS_PACKAGE_NAME, triggerType: RequestTrigger.Type = RequestTrigger.Type.TRIGGER_API_CALL ): SyncRequest { val buildContext = buildRequestContext(context) @@ -136,7 +137,7 @@ suspend operator fun SyncRequest.Companion.invoke( buildContext: RequestBuildContext, imsiToInfoMap: Map = buildImsiToSubscriptionInfoMap(context), includeClientAuth: Boolean = false, - callingPackage: String = context.packageName, + callingPackage: String = Constants.GMS_PACKAGE_NAME, triggerType: RequestTrigger.Type = RequestTrigger.Type.TRIGGER_API_CALL ): SyncRequest { val apiParamsList = Param.getList(request.extras) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt index 56d8114022..f5966f6691 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ts43/ServiceEntitlementExtension.kt @@ -32,9 +32,8 @@ fun ServiceEntitlementRequest.builder( ) fun ServiceEntitlementRequest.userAgent(context: Context): String { - val packageVersion = runCatching { + val packageVersion = context.packageManager.getPackageInfo(context.packageName, 0).versionName.orEmpty() - }.getOrDefault("") val vendor = terminal_vendor.take(4) val model = terminal_model.take(10) val swVersion = terminal_software_version.take(20) From d08e4d8cb3f92c5d97f6b0e3e8e7a620ab50fbec Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sun, 29 Mar 2026 01:07:57 -0400 Subject: [PATCH 14/31] Remove unnecessary dependencies --- play-services-constellation/build.gradle | 9 +-------- play-services-constellation/core/build.gradle | 4 +--- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/play-services-constellation/build.gradle b/play-services-constellation/build.gradle index e0cc981be3..f2bacc16a6 100644 --- a/play-services-constellation/build.gradle +++ b/play-services-constellation/build.gradle @@ -5,9 +5,6 @@ apply plugin: 'com.android.library' apply plugin: 'maven-publish' -apply plugin: 'kotlin-android' -apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlin-kapt' apply plugin: 'signing' android { @@ -30,10 +27,6 @@ android { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } - - kotlinOptions { - jvmTarget = 1.8 - } } apply from: '../gradle/publish-android.gradle' @@ -43,5 +36,5 @@ description = 'microG implementation of play-services-constellation' dependencies { implementation project(':play-services-basement') - kapt project(":safe-parcel-processor") + annotationProcessor project(":safe-parcel-processor") } diff --git a/play-services-constellation/core/build.gradle b/play-services-constellation/core/build.gradle index c51c1f39ea..cd7df4f4db 100644 --- a/play-services-constellation/core/build.gradle +++ b/play-services-constellation/core/build.gradle @@ -6,8 +6,6 @@ apply plugin: 'com.android.library' apply plugin: 'maven-publish' apply plugin: 'kotlin-android' -apply plugin: 'kotlin-parcelize' -apply plugin: 'kotlin-kapt' apply plugin: 'signing' apply plugin: 'com.squareup.wire' @@ -58,7 +56,7 @@ dependencies { implementation project(':play-services-droidguard') implementation project(':play-services-tasks-ktx') + implementation "com.squareup.okhttp3:okhttp:$okHttpVersion" api "com.squareup.wire:wire-runtime:$wireVersion" api "com.squareup.wire:wire-grpc-client:$wireVersion" - api "com.squareup.okhttp3:okhttp:$okHttpVersion" } From 05cd22d05449097c6d6b12cccf318e0e6496075b Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sun, 29 Mar 2026 01:54:18 -0400 Subject: [PATCH 15/31] Refactor package name from `builders` to `builder` --- .../microg/gms/constellation/core/VerifyPhoneNumber.kt | 8 ++++---- .../core/proto/{builders => builder}/ClientInfoBuilder.kt | 2 +- .../core/proto/{builders => builder}/CommonBuilders.kt | 2 +- .../core/proto/{builders => builder}/GaiaInfoBuilder.kt | 2 +- .../proto/{builders => builder}/RequestBuildContext.kt | 2 +- .../proto/{builders => builder}/SyncRequestBuilder.kt | 2 +- .../proto/{builders => builder}/TelephonyInfoBuilder.kt | 2 +- .../constellation/core/verification/ChallengeProcessor.kt | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) rename play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/{builders => builder}/ClientInfoBuilder.kt (99%) rename play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/{builders => builder}/CommonBuilders.kt (97%) rename play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/{builders => builder}/GaiaInfoBuilder.kt (98%) rename play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/{builders => builder}/RequestBuildContext.kt (90%) rename play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/{builders => builder}/SyncRequestBuilder.kt (99%) rename play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/{builders => builder}/TelephonyInfoBuilder.kt (99%) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index 572d8dcf8f..afdb361a67 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -23,10 +23,10 @@ import kotlinx.coroutines.withContext import org.microg.gms.common.Constants import org.microg.gms.constellation.core.proto.SyncRequest import org.microg.gms.constellation.core.proto.Verification -import org.microg.gms.constellation.core.proto.builders.RequestBuildContext -import org.microg.gms.constellation.core.proto.builders.buildImsiToSubscriptionInfoMap -import org.microg.gms.constellation.core.proto.builders.buildRequestContext -import org.microg.gms.constellation.core.proto.builders.invoke +import org.microg.gms.constellation.core.proto.builder.RequestBuildContext +import org.microg.gms.constellation.core.proto.builder.buildImsiToSubscriptionInfoMap +import org.microg.gms.constellation.core.proto.builder.buildRequestContext +import org.microg.gms.constellation.core.proto.builder.invoke import org.microg.gms.constellation.core.verification.ChallengeProcessor import java.util.UUID diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt similarity index 99% rename from play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt index a456974288..e3265a57b1 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/ClientInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt @@ -1,6 +1,6 @@ @file:RequiresApi(Build.VERSION_CODES.O) -package org.microg.gms.constellation.core.proto.builders +package org.microg.gms.constellation.core.proto.builder import android.annotation.SuppressLint import android.content.Context diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt similarity index 97% rename from play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt index d3a4e4beb5..b34b11010f 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/CommonBuilders.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.core.proto.builders +package org.microg.gms.constellation.core.proto.builder import android.content.Context import android.os.Build diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/GaiaInfoBuilder.kt similarity index 98% rename from play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/GaiaInfoBuilder.kt index cb9c9e508f..6eca28cdf7 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/GaiaInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/GaiaInfoBuilder.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.core.proto.builders +package org.microg.gms.constellation.core.proto.builder import android.accounts.AccountManager import android.content.Context diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/RequestBuildContext.kt similarity index 90% rename from play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/RequestBuildContext.kt index 1262045264..951255aa86 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/RequestBuildContext.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/RequestBuildContext.kt @@ -1,4 +1,4 @@ -package org.microg.gms.constellation.core.proto.builders +package org.microg.gms.constellation.core.proto.builder import android.content.Context import org.microg.gms.constellation.core.AuthManager diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt similarity index 99% rename from play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt index 0dc25d93b6..d58eaeb8f7 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/SyncRequestBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt @@ -1,7 +1,7 @@ @file:RequiresApi(Build.VERSION_CODES.O) @file:SuppressLint("HardwareIds") -package org.microg.gms.constellation.core.proto.builders +package org.microg.gms.constellation.core.proto.builder import android.Manifest import android.annotation.SuppressLint diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/TelephonyInfoBuilder.kt similarity index 99% rename from play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt rename to play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/TelephonyInfoBuilder.kt index a66a562957..307ae6a24e 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builders/TelephonyInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/TelephonyInfoBuilder.kt @@ -2,7 +2,7 @@ @file:SuppressLint("HardwareIds") @file:Suppress("DEPRECATION") -package org.microg.gms.constellation.core.proto.builders +package org.microg.gms.constellation.core.proto.builder import android.annotation.SuppressLint import android.content.Context diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index f01997d252..61105217b2 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -18,8 +18,8 @@ import org.microg.gms.constellation.core.proto.RequestHeader import org.microg.gms.constellation.core.proto.RequestTrigger import org.microg.gms.constellation.core.proto.Verification import org.microg.gms.constellation.core.proto.VerificationMethod -import org.microg.gms.constellation.core.proto.builders.RequestBuildContext -import org.microg.gms.constellation.core.proto.builders.invoke +import org.microg.gms.constellation.core.proto.builder.RequestBuildContext +import org.microg.gms.constellation.core.proto.builder.invoke object ChallengeProcessor { private const val TAG = "ChallengeProcessor" From 117312df213e08ab08eba7329573697565a2875f Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sun, 29 Mar 2026 02:53:47 -0400 Subject: [PATCH 16/31] Clean up `constellation.proto` --- .../core/src/main/proto/constellation.proto | 125 +++++++++++++----- 1 file changed, 93 insertions(+), 32 deletions(-) diff --git a/play-services-constellation/core/src/main/proto/constellation.proto b/play-services-constellation/core/src/main/proto/constellation.proto index 93a10c652c..de4e950b6e 100644 --- a/play-services-constellation/core/src/main/proto/constellation.proto +++ b/play-services-constellation/core/src/main/proto/constellation.proto @@ -5,7 +5,6 @@ package google.internal.communications.phonedeviceverification.v1; import "google/protobuf/timestamp.proto"; -// Verification and consent RPCs. service PhoneDeviceVerification { rpc GetConsent(GetConsentRequest) returns (GetConsentResponse); rpc SetConsent(SetConsentRequest) returns (SetConsentResponse); @@ -13,30 +12,31 @@ service PhoneDeviceVerification { rpc Proceed(ProceedRequest) returns (ProceedResponse); } -// Read-only phone number retrieval RPC. service PhoneNumber { rpc GetVerifiedPhoneNumbers(GetVerifiedPhoneNumbersRequest) returns (GetVerifiedPhoneNumbersResponse); } -// Shared request and response messages. -// Header for each client request. message RequestHeader { ClientInfo client_info = 1; ClientAuth client_auth = 2; string session_id = 3; RequestTrigger trigger = 4; } + message ResponseHeader { StatusProtoSimple status = 1; } + message StatusProtoSimple { int32 code = 1; } + enum StatusCode { STATUS_CODE_UNSPECIFIED = 0; STATUS_CODE_OK = 1; STATUS_CODE_ERROR = 2; } + message ClientInfo { DeviceID device_id = 1; bytes client_public_key = 2; @@ -78,25 +78,30 @@ enum DeviceType { DEVICE_TYPE_DESKTOP = 9; DEVICE_TYPE_XR_PERIPHERAL = 10; } + message ClientAuth { DeviceID device_id = 1; bytes signature = 2; google.protobuf.Timestamp sign_timestamp = 3; } + message CountryInfo { repeated string sim_countries = 1; repeated string network_countries = 2; } + message DroidGuardSignals { string droidguard_result = 1; string droidguard_token = 2; } + message DeviceID { string iid_token = 1; int64 primary_device_id = 2; int64 user_serial = 3; int64 gms_android_id = 4; } + message RequestTrigger { enum Type { UNKNOWN = 0; @@ -115,20 +120,25 @@ message RequestTrigger { } Type type = 1; } + message DroidGuardAttestation { string droidguard_result = 1; string droidguard_token = 2; } + message Experiment { string key = 1; string value = 2; } + message GaiaID { string id = 1; } + message GaiaToken { string token = 1; } + message GaiaSignalEntry { string gaia_id = 1; GaiaAccountSignalType signal_type = 2; @@ -141,14 +151,15 @@ enum GaiaAccountSignalType { GAIA_ACCOUNT_SIGNAL_UNAUTHENTICATED = 2; GAIA_ACCOUNT_SIGNAL_REMOVED_WITH_GMS_AUTH_BROADCAST = 3; } + message GaiaSignals { repeated GaiaSignalEntry gaia_signals = 1; } + message Status { int32 code = 1; } -// Named API params used by verification RPCs. message VerificationPolicy { string policy_id = 1; int64 max_verification_age_hours = 2; @@ -157,20 +168,22 @@ message VerificationPolicy { repeated VerificationParam params = 5; } -// Parameters for ID token generation. message IdTokenRequest { string certificate_hash = 1; string token_nonce = 2; } + message VerificationParam { string key = 1; string value = 2; } + message SimOperatorInfo { string imsi_hash = 1; string sim_operator = 3; } + message NetworkSignal { enum Type { TYPE_UNKNOWN = 0; @@ -194,6 +207,7 @@ message NetworkSignal { State state = 2; Availability availability = 3; } + message MobileOperatorInfo { string country_code = 1; uint64 nil_since_usec = 2; @@ -202,12 +216,12 @@ message MobileOperatorInfo { string operator_name = 5; } -// Sync and proceed flow. message SyncRequest { repeated Verification verifications = 3; RequestHeader header = 4; repeated VerificationToken verification_tokens = 5; } + message SyncResponse { repeated VerificationResponse responses = 1; ServerTimestamp next_sync_time = 2; @@ -215,11 +229,13 @@ message SyncResponse { DroidguardToken droidguard_token = 4; repeated VerificationToken verification_tokens = 5; } + message ProceedRequest { Verification verification = 2; ChallengeResponse challenge_response = 3; RequestHeader header = 4; } + message ProceedResponse { Verification verification = 1; ResponseHeader header = 2; @@ -227,17 +243,16 @@ message ProceedResponse { DroidguardToken droidguard_token = 4; } -// Server time plus the time when the server wrote this message. message ServerTimestamp { google.protobuf.Timestamp timestamp = 1; google.protobuf.Timestamp now = 2; } + message VerificationToken { bytes token = 1; google.protobuf.Timestamp expiration_time = 2; } -// Verification state and challenge model. message Verification { enum State { UNKNOWN = 0; @@ -273,18 +288,22 @@ message Verification { ChallengePreference challenge_preference = 7; VerificationPolicy structured_api_params = 8; } + message VerificationResponse { Verification verification = 1; StatusProto error = 2; } + message StatusProto { int32 code = 1; string message = 3; } + message SIMSlotInfo { int32 slot_index = 1; int32 subscription_id = 2; } + message SIMAssociation { message SIMInfo { @@ -303,8 +322,10 @@ message SIMAssociation { repeated GaiaToken gaia_tokens = 2; SIMSlotInfo sim_slot = 4; } + message GaiaAssociation { } + message VerificationAssociation { oneof association { SIMAssociation sim = 1; @@ -312,18 +333,16 @@ message VerificationAssociation { } } -// Information for a verified record. message VerificationInfo { string phone_number = 1; google.protobuf.Timestamp verification_time = 2; VerificationMethod challenge_method = 6; } + message PendingVerificationInfo { Challenge challenge = 2; } -// Challenge payloads and responses. -// Challenge issued by the server. message Challenge { ChallengeID challenge_id = 1; VerificationMethod type = 2; @@ -336,24 +355,25 @@ message Challenge { Ts43Challenge ts43_challenge = 12; } -// Carrier ID challenge used for SS7 traffic. message CarrierIdChallenge { string isim_request = 3; int32 auth_type = 5; int32 app_type = 6; } + message MTChallenge { string sms = 1; } + message ChallengeID { string id = 1; } + message Capabilities { string droidguard_result = 1; string droidguard_token = 2; } -// MO SMS challenge. message MoChallenge { string proxy_number = 1; DataSmsInfo data_sms_info = 3; @@ -361,15 +381,19 @@ message MoChallenge { string polling_intervals = 5; string sms_without_persisting = 6; } + message DataSmsInfo { int32 destination_port = 1; } + message RegisteredSmsChallenge { repeated PhoneNumberID verified_senders = 1; } + message PhoneNumberID { bytes phone_number_id = 1; } + message FlashCallChallenge { repeated PhoneRange phone_ranges = 1; FlashCallState state = 2; @@ -377,6 +401,7 @@ message FlashCallChallenge { repeated ChallengeResponse previous_challenge_responses = 4; int64 millis_between_interceptions = 5; } + message PhoneRange { string phone_number_prefix = 1; string phone_number_suffix = 2; @@ -391,6 +416,7 @@ enum FlashCallState { FLASH_CALL_STATE_VERIFIED = 3; FLASH_CALL_STATE_FAILED = 4; } + message UnverifiedInfo { enum Reason { UNKNOWN_REASON = 0; @@ -408,7 +434,6 @@ message UnverifiedInfo { VerificationMethod challenge_method = 3; } -// Challenge response. message ChallengeResponse { MTChallengeResponseData mt_response = 1; CarrierIdChallengeResponse carrier_id_response = 2; @@ -417,10 +442,12 @@ message ChallengeResponse { FlashCallChallengeResponse flash_call_response = 7; Ts43ChallengeResponse ts43_challenge_response = 9; } + message MTChallengeResponseData { string sms = 1; string sender = 2; } + message CarrierIdChallengeResponse { string isim_response = 3; CarrierIdError carrier_id_error = 4; @@ -440,6 +467,7 @@ enum CarrierIdError { CARRIER_ID_ERROR_INTERNAL_ERROR = 10; CARRIER_ID_ERROR_INVALID_ARGUMENT = 11; } + message MOChallengeResponseData { enum Status { UNKNOWN_STATUS = 0; @@ -452,15 +480,19 @@ message MOChallengeResponseData { int64 sms_result_code = 2; int64 sms_error_code = 3; } + message RegisteredSmsChallengeResponse { repeated RegisteredSmsChallengeResponseItem items = 1; } + message RegisteredSmsChallengeResponseItem { RegisteredSmsChallengeResponsePayload payload = 2; } + message RegisteredSmsChallengeResponsePayload { bytes payload = 1; } + message Ts43ChallengeResponse { Ts43Type ts43_type = 1; ClientChallengeResponse client_challenge_response = 2; @@ -468,6 +500,7 @@ message Ts43ChallengeResponse { Ts43ChallengeResponseStatus status = 4; repeated string http_history = 5; } + message Ts43ChallengeResponseStatus { enum Code { TS43_STATUS_UNSPECIFIED = 0; @@ -484,8 +517,6 @@ message Ts43ChallengeResponseStatus { } } -// The numeric 1..9 range is intentionally left generic because those values are -// overloaded across multiple TS.43 request stages. message Ts43ChallengeResponseError { enum Code { TS43_ERROR_CODE_UNSPECIFIED = 0; @@ -521,12 +552,15 @@ message Ts43ChallengeResponseError { int32 http_status = 2; RequestType request_type = 3; } + message ServerChallengeResponse { string acquire_temporary_token_response = 2; } + message ClientChallengeResponse { string get_phone_number_response = 2; } + message FlashCallChallengeResponse { enum Error { NO_ERROR = 0; @@ -549,6 +583,7 @@ message FlashCallChallengeResponse { string caller = 1; Error error = 2; } + message Ts43Type { enum Integrator { TS43_INTEGRATOR_UNSPECIFIED = 0; @@ -571,6 +606,7 @@ message Ts43Type { } Integrator integrator = 1; } + message CellularNetworkEvent { google.protobuf.Timestamp timestamp = 1; bool mobile_data_enabled = 2; @@ -579,10 +615,12 @@ message CellularNetworkEvent { bool mobile_data_always_on = 5; repeated NetworkState networks = 6; } + message NetworkState { repeated int32 types = 1; bool available = 2; } + message ServiceStateEvent { google.protobuf.Timestamp timestamp = 1; bool airplane_mode_enabled = 2; @@ -593,11 +631,13 @@ message ServiceStateEvent { int32 data_network_type = 7; int32 signal_strength = 8; } + message SMSEvent { google.protobuf.Timestamp timestamp = 1; EventDirection direction = 2; EventPhoneNumberType number_type = 3; } + message CallEvent { google.protobuf.Timestamp timestamp = 1; EventDirection direction = 2; @@ -615,6 +655,7 @@ enum EventPhoneNumberType { LONG_NUMBER = 1; SHORT_CODE = 2; } + message TelephonyInfo { enum PhoneType { PHONE_TYPE_UNKNOWN = 0; @@ -680,18 +721,19 @@ message TelephonyInfo { ToggleState carrier_id_capability = 23; int64 sim_carrier_id = 25; } + message SimNetworkInfo { string country_iso = 1; string operator = 2; string operator_name = 3; int32 inactive_time_diff_ms = 4; } + message DroidguardToken { string token = 1; google.protobuf.Timestamp ttl = 2; } -// Consent and Asterism messages. message GetConsentRequest { DeviceID device_id = 1; repeated GaiaToken gaia_tokens = 2; @@ -703,6 +745,7 @@ message GetConsentRequest { string session_id = 9; bool unknown_flag = 10; } + message GetConsentResponse { RcsConsent rcs_consent = 1; ServerTimestamp next_check_time = 5; @@ -712,8 +755,6 @@ message GetConsentResponse { PermissionState permission_state = 10; } -// These values intentionally alias because the same field is reused across -// multiple consent surfaces. enum FlowContext { option allow_alias = true; FLOW_CONTEXT_UNSPECIFIED = 0; @@ -724,6 +765,7 @@ enum FlowContext { FLOW_CONTEXT_RCS_DEFAULT_ON_LEGAL_FYI_IN_SETTINGS = 4; FLOW_CONTEXT_RCS_UNKNOWN_5 = 5; } + message SetConsentRequest { RequestHeader header = 1; RcsConsent rcs_consent = 3; @@ -736,6 +778,7 @@ message SetConsentRequest { AsterismConsent device_consent = 10; } } + message SetConsentResponse { } @@ -746,8 +789,6 @@ enum Consent { EXPIRED = 3; } -// These values intentionally alias because RCS and device consent versions -// share the same field. enum ConsentVersion { option allow_alias = true; CONSENT_VERSION_UNSPECIFIED = 0; @@ -759,41 +800,47 @@ enum ConsentVersion { PHONE_VERIFICATION_INTL_SMS_CALLS = 3; PHONE_VERIFICATION_REACHABILITY_INTL_SMS_CALLS = 4; } + message AsterismConsent { Consent consent = 1; ConsentSource consent_source = 2; ConsentVersion consent_version = 3; } + message ConsentState { Consent state = 2; } -// `flags` is still an opaque server-defined bitfield. message PermissionState { int32 flags = 1; PermissionType type = 2; } + enum PermissionType { PERMISSION_TYPE_UNSPECIFIED = 0; LEGACY_DPNV = 1; PNVR = 2; NOT_ALLOWED = 3; } + message RcsConsent { Consent consent = 2; ConsentVersion consent_version = 3; } + message GaiaConsent { AsterismClient asterism_client = 1; Consent consent = 2; ConsentVersion consent_version = 3; } + message OnDemandConsent { Consent consent = 1; GaiaToken gaia_token = 2; string consent_variant = 3; string trigger = 4; } + enum ConsentSource { SOURCE_UNSPECIFIED = 0; ANDROID_DEVICE_SETTINGS = 1; @@ -805,6 +852,7 @@ enum ConsentSource { MEET_ON_DEMAND_CONSENT = 7; GPAY_ON_DEMAND_CONSENT = 8; } + enum AsterismClient { UNKNOWN_CLIENT = 0; CONSTELLATION = 1; @@ -812,8 +860,6 @@ enum AsterismClient { ONE_TIME_VERIFICATION = 3; } -// Read-only phone number RPC. -// Request message for GetVerifiedPhoneNumbers. message GetVerifiedPhoneNumbersRequest { enum PhoneNumberSelection { SELECTION_UNSPECIFIED = 0; @@ -830,20 +876,22 @@ message GetVerifiedPhoneNumbersRequest { RequestInfo request_info = 7; } -// Response message for GetVerifiedPhoneNumbers. message GetVerifiedPhoneNumbersResponse { repeated VerifiedPhoneNumber phone_numbers = 2; } + message IIDTokenAuth { string iid_token = 1; bytes client_sign = 2; google.protobuf.Timestamp sign_timestamp = 3; } + message TokenOption { string certificate_hash = 1; string token_nonce = 2; string package_name = 3; } + message VerifiedPhoneNumber { string phone_number = 1; google.protobuf.Timestamp verification_time = 2; @@ -863,8 +911,6 @@ enum TelephonyPhoneNumberType { PHONE_NUMBER_SOURCE_IMS = 3; } -// Shared helpers. -// Data consistency requirement for read-only RPCs. message ConsistencyOption { enum Consistency { CONSISTENCY_UNSPECIFIED = 0; @@ -874,23 +920,24 @@ message ConsistencyOption { Consistency consistency = 1; } -// Request metadata for analytics and legacy policy attribution. message RequestInfo { string policy_id = 1; } + message Param { string key = 1; string value = 2; } -// Client challenge preference. message ChallengePreference { repeated VerificationMethod capabilities = 1; ChallengePreferenceMetadata metadata = 2; } + message ChallengePreferenceMetadata { string sms_signature = 2; } + enum VerificationMethod { UNKNOWN = 0; MO_SMS = 1; @@ -902,31 +949,36 @@ enum VerificationMethod { TS43 = 11; } -// Audit payloads. message AuditToken { AuditTokenMetadata metadata = 2; } + message AuditTokenMetadata { AuditUuid uuid = 1; } + message AuditUuid { int64 uuid_msb = 1; int64 uuid_lsb = 2; } + message AuditPayload { AuditDeviceInfo device_info = 1; AuditDeviceId device_id = 2; AuditEventMetadata event_metadata = 10; } + message AuditDeviceInfo { oneof device_identifier { string android_id_hash = 1; string instance_id = 2; } } + message AuditDeviceId { string android_id_hash = 1; } + message AuditEventMetadata { AuditEventType event_type = 1; string session_id = 2; @@ -941,32 +993,37 @@ message AuditEventMetadata { AuditComponentInfo component_info = 11; string trigger = 12; } + enum AuditEventType { EVENT_TYPE_UNSPECIFIED = 0; ASTERISM_CONSENT_CHANGE = 1; RCS_CONSENT_CHANGE = 2; VERIFICATION_COMPLETE = 3; } + message AuditConsentDetails { repeated int32 sim_slot_ids = 1; } + message AuditComponentInfo { AuditComponentType component_type = 1; } + enum AuditComponentType { COMPONENT_UNSPECIFIED = 0; ASTERISM_CONSTELLATION = 119; ASTERISM_RCS = 120; } + message AuditConsentState { Consent state = 1; } + enum AuditEventTypeValue { EVENT_VALUE_UNSPECIFIED = 0; ASTERISM_CLIENT_CONSENT_CHANGE = 187; } -// TS.43 challenge messages. message Ts43Challenge { Ts43Type ts43_type = 1; string entitlement_url = 2; @@ -976,12 +1033,15 @@ message Ts43Challenge { string app_id = 6; string eap_aka_realm = 7; } + message ServerChallenge { OdsaOperation operation = 1; } + message ClientChallenge { OdsaOperation operation = 1; } + message OdsaOperation { string operation = 1; int32 operation_type = 2; @@ -1005,6 +1065,7 @@ message OdsaOperation { string old_terminal_id = 20; string old_terminal_iccid = 21; } + message ServiceEntitlementRequest { int32 notification_action = 1; string entitlement_version = 2; From db60565d3822c9da99d6d53037dde1960800b731 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sun, 29 Mar 2026 04:16:31 -0400 Subject: [PATCH 17/31] More consistent formatting --- .../android/gms/constellation/GetIidTokenRequest.java | 1 + .../android/gms/constellation/GetIidTokenResponse.java | 4 ++++ .../gms/constellation/GetPnvCapabilitiesRequest.java | 3 +++ .../gms/constellation/GetPnvCapabilitiesResponse.java | 1 + .../google/android/gms/constellation/PhoneNumberInfo.java | 4 ++++ .../gms/constellation/VerifyPhoneNumberRequest.java | 8 ++++++++ .../gms/constellation/VerifyPhoneNumberResponse.java | 2 ++ 7 files changed, 23 insertions(+) diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java index 311dcb0fd9..c24de63130 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenRequest.java @@ -13,6 +13,7 @@ public class GetIidTokenRequest extends AbstractSafeParcelable { public static SafeParcelableCreatorAndWriter CREATOR = findCreator(GetIidTokenRequest.class); + @Field(1) @Nullable public final Long projectNumber; diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java index fd2a3c66d6..19b97ec9cc 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetIidTokenResponse.java @@ -13,13 +13,17 @@ public class GetIidTokenResponse extends AbstractSafeParcelable { public static SafeParcelableCreatorAndWriter CREATOR = findCreator(GetIidTokenResponse.class); + @Field(1) public final String iidToken; + @Field(2) public final String fid; + @Field(3) @Nullable public final byte[] signature; + @Field(4) public final long timestamp; diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java index 9fbc98b5c0..a0c13b3306 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesRequest.java @@ -14,10 +14,13 @@ public class GetPnvCapabilitiesRequest extends AbstractSafeParcelable { public static SafeParcelableCreatorAndWriter CREATOR = findCreator(GetPnvCapabilitiesRequest.class); + @Field(1) public final String policyId; + @Field(2) public final List verificationTypes; + @Field(3) public final List simSlotIndices; diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java index b1382969ca..ee522b446e 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/GetPnvCapabilitiesResponse.java @@ -14,6 +14,7 @@ public class GetPnvCapabilitiesResponse extends AbstractSafeParcelable { public static SafeParcelableCreatorAndWriter CREATOR = findCreator(GetPnvCapabilitiesResponse.class); + @Field(1) public final List simCapabilities; diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java index 0dc2c9fbfe..77f4fd7753 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/PhoneNumberInfo.java @@ -14,13 +14,17 @@ public class PhoneNumberInfo extends AbstractSafeParcelable { public static SafeParcelableCreatorAndWriter CREATOR = findCreator(PhoneNumberInfo.class); + @Field(1) public final int version; + @Field(2) @Nullable public final String phoneNumber; + @Field(3) public final long verificationTime; + @Field(4) @Nullable public final Bundle extras; diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java index c68ecc7e53..8c2327f091 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java @@ -15,20 +15,28 @@ public class VerifyPhoneNumberRequest extends AbstractSafeParcelable { public static SafeParcelableCreatorAndWriter CREATOR = findCreator(VerifyPhoneNumberRequest.class); + @Field(1) public final String policyId; + @Field(2) public final long timeout; + @Field(3) public final IdTokenRequest idTokenRequest; + @Field(4) public final Bundle extras; + @Field(5) public final List targetedSims; + @Field(6) public final boolean silent; + @Field(7) public final int apiVersion; + @Field(8) public final List verificationMethodsValues; diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java index d66fca5c19..263461debf 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberResponse.java @@ -14,8 +14,10 @@ public class VerifyPhoneNumberResponse extends AbstractSafeParcelable { public static SafeParcelableCreatorAndWriter CREATOR = findCreator(VerifyPhoneNumberResponse.class); + @Field(1) public final PhoneNumberVerification[] verifications; + @Field(2) public final Bundle extras; From 0621ee71712dc07a3ee0b98142da033d553c4fa9 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Wed, 1 Apr 2026 10:19:31 -0400 Subject: [PATCH 18/31] Use actual Gaia ID --- .../core/src/main/AndroidManifest.xml | 1 + .../core/proto/builder/GaiaInfoBuilder.kt | 70 +++++++++++-------- 2 files changed, 40 insertions(+), 31 deletions(-) diff --git a/play-services-constellation/core/src/main/AndroidManifest.xml b/play-services-constellation/core/src/main/AndroidManifest.xml index 8ad6d5d3fe..aa877ea5e5 100644 --- a/play-services-constellation/core/src/main/AndroidManifest.xml +++ b/play-services-constellation/core/src/main/AndroidManifest.xml @@ -7,6 +7,7 @@ xmlns:tools="http://schemas.android.com/tools"> + diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/GaiaInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/GaiaInfoBuilder.kt index 6eca28cdf7..9c7c7134b0 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/GaiaInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/GaiaInfoBuilder.kt @@ -12,52 +12,60 @@ import org.microg.gms.constellation.core.proto.GaiaAccountSignalType import org.microg.gms.constellation.core.proto.GaiaSignalEntry import org.microg.gms.constellation.core.proto.GaiaSignals import org.microg.gms.constellation.core.proto.GaiaToken -import java.security.MessageDigest -import kotlin.math.abs -import kotlin.math.absoluteValue private const val TAG = "GaiaInfoBuilder" @RequiresApi(Build.VERSION_CODES.O) -operator fun GaiaSignals.Companion.invoke(context: Context): GaiaSignals? { - val entries = mutableListOf() - try { - val accounts = AccountManager.get(context).getAccountsByType("com.google") - val md = MessageDigest.getInstance("SHA-256") +suspend operator fun GaiaSignals.Companion.invoke(context: Context): GaiaSignals? = + withContext(Dispatchers.IO) { + val entries = mutableListOf() + val accountManager = AccountManager.get(context) - for (account in accounts) { - // Note: Simple implementation, maybe do actual obfuscated Gaia ID retrieval later? - val hash = md.digest(account.name.toByteArray(Charsets.UTF_8)) - val number = - hash.take(8).fold(0L) { acc, byte -> (acc shl 8) or (byte.toLong() and 0xFF) } - val obfuscatedId = try { - number.absoluteValue.toString() - } catch (_: Exception) { - abs(account.name.hashCode().toLong()).toString() - } + try { + val accounts = accountManager.getAccountsByType("com.google") - entries.add( - GaiaSignalEntry( - gaia_id = obfuscatedId, - signal_type = GaiaAccountSignalType.GAIA_ACCOUNT_SIGNAL_AUTHENTICATED, - timestamp = Instant.now() + for (account in accounts) { + var id = accountManager.getUserData(account, "GoogleUserId") + if (id == "") { + try { + val future = accountManager.getAuthToken( + account, + "^^_account_id_^^", + null, + false, + null, + null + ) + id = future.result?.getString(AccountManager.KEY_AUTHTOKEN) + accountManager.setUserData(account, "GoogleUserId", id) + } catch (e: Exception) { + Log.w(TAG, "Could not retrieve Gaia ID for account ${account.name}", e) + continue + } + } + entries.add( + GaiaSignalEntry( + gaia_id = id, + signal_type = GaiaAccountSignalType.GAIA_ACCOUNT_SIGNAL_AUTHENTICATED, + timestamp = Instant.now() + ) ) - ) + } + } catch (e: Exception) { + Log.w(TAG, "Could not build Gaia signals", e) } - } catch (e: Exception) { - Log.w(TAG, "Could not build Gaia signals", e) + if (entries.isNotEmpty()) GaiaSignals(gaia_signals = entries) else null } - return if (entries.isNotEmpty()) GaiaSignals(gaia_signals = entries) else null -} @Suppress("DEPRECATION") suspend fun GaiaToken.Companion.getList(context: Context): List = withContext(Dispatchers.IO) { - val accounts = AccountManager.get(context).getAccountsByType("com.google") + val accountManager = AccountManager.get(context) + val accounts = accountManager.getAccountsByType("com.google") accounts.mapNotNull { account -> try { - val future = AccountManager.get(context).getAuthToken( + val future = accountManager.getAuthToken( account, "oauth2:https://www.googleapis.com/auth/numberer", null, @@ -70,7 +78,7 @@ suspend fun GaiaToken.Companion.getList(context: Context): List = if (!token.isNullOrBlank()) GaiaToken(token = token) else null } catch (e: Exception) { - Log.w(TAG, "Could not retrieve Gaia token for an account", e) + Log.w(TAG, "Could not retrieve Gaia token for account ${account.name}", e) null } } From b82676c0d9b90804b45a186dbdb0e9fdba79fe65 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 3 Apr 2026 03:52:42 -0400 Subject: [PATCH 19/31] Align more with official behavior --- .../core/VerificationMappings.kt | 87 ++++++++++++------- .../constellation/core/VerifyPhoneNumber.kt | 27 ++++-- .../core/verification/ChallengeProcessor.kt | 6 +- .../VerifyPhoneNumberRequest.java | 6 +- 4 files changed, 79 insertions(+), 47 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt index 2fae9c903b..edf9a5de0b 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerificationMappings.kt @@ -11,7 +11,7 @@ import org.microg.gms.constellation.core.proto.VerificationMethod fun UnverifiedInfo.Reason.toVerificationStatus(): Verification.Status { return when (this) { - UnverifiedInfo.Reason.UNKNOWN_REASON -> Verification.Status.STATUS_PENDING + UnverifiedInfo.Reason.UNKNOWN_REASON -> Verification.Status.STATUS_UNKNOWN UnverifiedInfo.Reason.THROTTLED -> Verification.Status.STATUS_THROTTLED UnverifiedInfo.Reason.FAILED -> Verification.Status.STATUS_FAILED UnverifiedInfo.Reason.SKIPPED -> Verification.Status.STATUS_SKIPPED @@ -25,72 +25,76 @@ fun UnverifiedInfo.Reason.toVerificationStatus(): Verification.Status { } } -fun Verification.getState(): Verification.State { - return when { +val Verification.state: Verification.State + get() = when { verification_info != null -> Verification.State.VERIFIED pending_verification_info != null -> Verification.State.PENDING unverified_info != null -> Verification.State.NONE else -> Verification.State.UNKNOWN } -} -fun Verification.getVerificationStatus(): Verification.Status { - return when (getState()) { +val Verification.effectiveStatus: Verification.Status + get() = when (state) { Verification.State.VERIFIED -> Verification.Status.STATUS_VERIFIED - - Verification.State.PENDING -> { + Verification.State.PENDING -> if (status != Verification.Status.STATUS_UNKNOWN) { status } else { Verification.Status.STATUS_PENDING } - } - Verification.State.NONE -> { - unverified_info?.reason?.toVerificationStatus() ?: Verification.Status.STATUS_PENDING - } + Verification.State.NONE -> + unverified_info?.reason?.toVerificationStatus() ?: Verification.Status.STATUS_UNKNOWN Verification.State.UNKNOWN -> Verification.Status.STATUS_UNKNOWN } -} @RequiresApi(Build.VERSION_CODES.O) fun Verification.toClientVerification(imsiToSlotMap: Map): PhoneNumberVerification { - val verificationStatus = this.getVerificationStatus() - var phoneNumber: String? = null - var timestampMillis = System.currentTimeMillis() - var verificationMethod = VerificationMethod.UNKNOWN - var retryAfterSeconds = 0L + val clientStatus = effectiveStatus.toClientStatus() val extras = buildClientExtras() + val simImsi = association?.sim?.sim_info?.imsi?.firstOrNull() + val simSlot = if (simImsi != null) imsiToSlotMap[simImsi] ?: -1 else -1 - when (this.getState()) { + var phoneNumber = "" + var timestampMillis = 0L + var verificationMethod = VerificationMethod.UNKNOWN + var verificationToken: String? = null + var retryAfterSeconds = -1L + + when (state) { Verification.State.VERIFIED -> { - val info = this.verification_info - phoneNumber = info?.phone_number - timestampMillis = info?.verification_time?.toEpochMilli() ?: System.currentTimeMillis() - verificationMethod = info?.challenge_method ?: VerificationMethod.UNKNOWN + val info = verification_info + val verifiedPhoneNumber = info?.phone_number + require(!verifiedPhoneNumber.isNullOrEmpty()) { "Verified phone number is empty" } + phoneNumber = verifiedPhoneNumber + timestampMillis = info.verification_time?.toEpochMilli() ?: 0L + verificationMethod = info.challenge_method + verificationToken = extras.getString("id_token") + extras.remove("phone_number") + extras.remove("id_token") + extras.remove("verification_time_millis") } Verification.State.PENDING -> { verificationMethod = - this.pending_verification_info?.challenge?.type ?: VerificationMethod.UNKNOWN + pending_verification_info?.challenge?.type ?: VerificationMethod.UNKNOWN } Verification.State.NONE -> { - val info = this.unverified_info + val info = unverified_info verificationMethod = info?.challenge_method ?: VerificationMethod.UNKNOWN retryAfterSeconds = info?.retry_after_time?.let { ts -> val now = System.currentTimeMillis() / 1000L (ts.epochSecond - now).coerceAtLeast(0L) - } ?: 0L + } ?: -1L } - else -> {} + Verification.State.UNKNOWN -> Unit } - val simImsi = this.association?.sim?.sim_info?.imsi?.firstOrNull() - val simSlot = if (simImsi != null) imsiToSlotMap[simImsi] ?: 0 else 0 - val verificationToken = extras.getString("id_token") + extras.remove("verification_method") + extras.remove("sim_slot_index") return PhoneNumberVerification( phoneNumber, @@ -99,7 +103,7 @@ fun Verification.toClientVerification(imsiToSlotMap: Map): PhoneNum simSlot, verificationToken, extras, - verificationStatus.value, + clientStatus, retryAfterSeconds ) } @@ -112,7 +116,6 @@ private fun Verification.buildClientExtras(): Bundle { val slotIndex = association?.sim?.sim_slot?.slot_index if (slotIndex != null && slotIndex >= 0) { - // GMS exposes this as a string inside the extras bundle. bundle.putString("sim_slot_index", slotIndex.toString()) } return bundle @@ -130,8 +133,26 @@ private fun Bundle.putParam(param: Param) { fun VerificationMethod.toClientMethod(): Int { return when (this) { - VerificationMethod.TS43 -> 9 VerificationMethod.UNKNOWN -> 0 + VerificationMethod.TS43 -> 9 else -> value } } + +fun Verification.Status.toClientStatus(): Int { + return when (this) { + Verification.Status.STATUS_UNKNOWN, + Verification.Status.STATUS_NONE -> 0 + + Verification.Status.STATUS_PENDING -> 6 + Verification.Status.STATUS_VERIFIED -> 1 + Verification.Status.STATUS_THROTTLED -> 3 + Verification.Status.STATUS_FAILED -> 2 + Verification.Status.STATUS_SKIPPED -> 4 + Verification.Status.STATUS_NOT_REQUIRED -> 5 + Verification.Status.STATUS_PHONE_NUMBER_ENTRY_REQUIRED -> 7 + Verification.Status.STATUS_INELIGIBLE -> 8 + Verification.Status.STATUS_DENIED -> 9 + Verification.Status.STATUS_NOT_IN_SERVICE -> 10 + } +} diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index afdb361a67..a1901667f6 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -257,7 +257,7 @@ private suspend fun runVerificationFlow( includeClientAuth = ConstellationStateStore.isPublicKeyAcked(context), callingPackage = callingPackage ) - val (verifications, isPublicKeyAcked) = executeSyncFlow( + val verifications = executeSyncFlow( context, sessionId, request, @@ -265,9 +265,6 @@ private suspend fun runVerificationFlow( buildContext, imsiToInfoMap ) - if (isPublicKeyAcked) { - ConstellationStateStore.setPublicKeyAcked(context, true) - } if (legacyCallback) { callbacks.onPhoneNumberVerified( @@ -285,7 +282,10 @@ private suspend fun runVerificationFlow( } private fun PhoneNumberVerification.toLegacyPhoneNumberInfoOrNull(): PhoneNumberInfo? { - if (verificationStatus != Verification.Status.STATUS_VERIFIED.value || phoneNumber.isNullOrEmpty()) { + if ( + verificationStatus != Verification.Status.STATUS_VERIFIED.toClientStatus() || + phoneNumber.isNullOrEmpty() + ) { return null } val extras = Bundle(this.extras ?: Bundle.EMPTY).apply { @@ -301,7 +301,7 @@ private suspend fun executeSyncFlow( syncRequest: SyncRequest, buildContext: RequestBuildContext, imsiToInfoMap: Map -): Pair, Boolean> = withContext(Dispatchers.IO) { +): Array = withContext(Dispatchers.IO) { Log.d(TAG, "Sending Sync request") val syncResponse = try { RpcClient.phoneDeviceVerificationClient.Sync().execute(syncRequest) @@ -335,7 +335,7 @@ private suspend fun executeSyncFlow( return@mapNotNull null } - val finalVerification = if (verification.getState() == Verification.State.PENDING) { + val finalVerification = if (verification.state == Verification.State.PENDING) { ChallengeProcessor.process( context, sessionId, @@ -347,12 +347,23 @@ private suspend fun executeSyncFlow( verification } + if (finalVerification.state != Verification.State.VERIFIED) { + Log.w(TAG, "Unverified. State: ${finalVerification.state}") + (finalVerification.pending_verification_info + ?: finalVerification.unverified_info)?.let { Log.w(TAG, it.toString()) } + + if (!request.includeUnverified) { + return@mapNotNull null + } + } + finalVerification.toClientVerification(imsiToSlotMap) }.toTypedArray() if (isPublicKeyAcked) { Log.d(TAG, "Server acknowledged client public key") + ConstellationStateStore.setPublicKeyAcked(context, true) } - verifications to isPublicKeyAcked + verifications } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index 61105217b2..532c5733a4 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -11,7 +11,6 @@ import com.squareup.wire.GrpcException import com.squareup.wire.GrpcStatus import org.microg.gms.constellation.core.ConstellationStateStore import org.microg.gms.constellation.core.RpcClient -import org.microg.gms.constellation.core.getState import org.microg.gms.constellation.core.proto.ChallengeResponse import org.microg.gms.constellation.core.proto.ProceedRequest import org.microg.gms.constellation.core.proto.RequestHeader @@ -20,6 +19,7 @@ import org.microg.gms.constellation.core.proto.Verification import org.microg.gms.constellation.core.proto.VerificationMethod import org.microg.gms.constellation.core.proto.builder.RequestBuildContext import org.microg.gms.constellation.core.proto.builder.invoke +import org.microg.gms.constellation.core.state object ChallengeProcessor { private const val TAG = "ChallengeProcessor" @@ -42,10 +42,10 @@ object ChallengeProcessor { var currentVerification = verification for (attempt in 1..MAX_PROCEED_ROUNDS) { - if (currentVerification.getState() != Verification.State.PENDING) { + if (currentVerification.state != Verification.State.PENDING) { Log.d( TAG, - "Verification state: ${currentVerification.getState()}. Stopping sequential verification." + "Verification state: ${currentVerification.state}. Stopping sequential verification." ) return currentVerification } diff --git a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java index 8c2327f091..a0ced6ac30 100644 --- a/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java +++ b/play-services-constellation/src/main/java/com/google/android/gms/constellation/VerifyPhoneNumberRequest.java @@ -32,7 +32,7 @@ public class VerifyPhoneNumberRequest extends AbstractSafeParcelable { public final List targetedSims; @Field(6) - public final boolean silent; + public final boolean includeUnverified; @Field(7) public final int apiVersion; @@ -47,7 +47,7 @@ public VerifyPhoneNumberRequest( @Param(3) IdTokenRequest idTokenRequest, @Param(4) Bundle extras, @Param(5) List targetedSims, - @Param(6) boolean silent, + @Param(6) boolean includeUnverified, @Param(7) int apiVersion, @Param(8) List verificationMethodsValues ) { @@ -56,7 +56,7 @@ public VerifyPhoneNumberRequest( this.idTokenRequest = idTokenRequest; this.extras = extras; this.targetedSims = targetedSims; - this.silent = silent; + this.includeUnverified = includeUnverified; this.apiVersion = apiVersion; this.verificationMethodsValues = verificationMethodsValues; } From 58732b56d982785798e1b4d8257c44f542da5fb7 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 3 Apr 2026 04:15:32 -0400 Subject: [PATCH 20/31] Add more infos to `TelephonyInfo` --- .../core/src/main/AndroidManifest.xml | 1 + .../proto/builder/TelephonyInfoBuilder.kt | 86 ++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/play-services-constellation/core/src/main/AndroidManifest.xml b/play-services-constellation/core/src/main/AndroidManifest.xml index aa877ea5e5..db636fcc6e 100644 --- a/play-services-constellation/core/src/main/AndroidManifest.xml +++ b/play-services-constellation/core/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/TelephonyInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/TelephonyInfoBuilder.kt index 307ae6a24e..5bc26d6157 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/TelephonyInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/TelephonyInfoBuilder.kt @@ -4,15 +4,20 @@ package org.microg.gms.constellation.core.proto.builder +import android.Manifest import android.annotation.SuppressLint import android.content.Context +import android.content.pm.PackageManager import android.net.ConnectivityManager import android.os.Build +import android.os.UserManager +import android.telephony.ServiceState import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.util.Base64 import android.util.Log import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import org.microg.gms.constellation.core.proto.NetworkSignal import org.microg.gms.constellation.core.proto.SimNetworkInfo @@ -25,6 +30,7 @@ private const val TAG = "TelephonyInfoBuilder" @SuppressLint("MissingPermission") operator fun TelephonyInfo.Companion.invoke(context: Context, subscriptionId: Int): TelephonyInfo { val tm = context.getSystemService() + val subscriptionManager = context.getSystemService() val targetTm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && subscriptionId >= 0) { tm?.createForSubscriptionId(subscriptionId) } else tm @@ -67,6 +73,8 @@ operator fun TelephonyInfo.Companion.invoke(context: Context, subscriptionId: In operator_name = targetTm?.networkOperatorName ?: "" ) + val (activeSubCount, activeSubCountMax) = getActiveSubCounts(subscriptionManager) + return TelephonyInfo( phone_type = phoneType, group_id_level1 = targetTm?.groupIdLevel1 ?: "", @@ -74,12 +82,88 @@ operator fun TelephonyInfo.Companion.invoke(context: Context, subscriptionId: In network_info = networkInfo, network_roaming = networkRoaming, connectivity_state = connectivityState, - sms_capability = if (targetTm?.isSmsCapable == true) TelephonyInfo.SmsCapability.SMS_CAPABLE else TelephonyInfo.SmsCapability.SMS_NOT_CAPABLE, + sms_capability = getSmsCapability(context, targetTm), + active_sub_count = activeSubCount, + active_sub_count_max = activeSubCountMax, sim_state = simState, + service_state = getServiceState(targetTm), + sim_carrier_id = getSimCarrierId(targetTm), is_embedded = false ) } +private fun getSmsCapability( + context: Context, + telephonyManager: TelephonyManager? +): TelephonyInfo.SmsCapability { + val hasReadSms = + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) == + PackageManager.PERMISSION_GRANTED + val hasSendSms = + ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) == + PackageManager.PERMISSION_GRANTED + + if (!hasReadSms || !hasSendSms) { + return TelephonyInfo.SmsCapability.SMS_DEFAULT_CAPABILITY + } + + val isSmsRestricted = + context.getSystemService()?.userRestrictions?.getBoolean(UserManager.DISALLOW_SMS) == + true + if (isSmsRestricted) { + return TelephonyInfo.SmsCapability.SMS_RESTRICTED + } + + return if (telephonyManager?.isSmsCapable == true) { + TelephonyInfo.SmsCapability.SMS_CAPABLE + } else { + TelephonyInfo.SmsCapability.SMS_NOT_CAPABLE + } +} + +private fun getActiveSubCounts(subscriptionManager: SubscriptionManager?): Pair { + if (subscriptionManager == null) { + return 0 to 0 + } + + return try { + subscriptionManager.activeSubscriptionInfoCount to + subscriptionManager.activeSubscriptionInfoCountMax + } catch (e: SecurityException) { + Log.w(TAG, "Could not retrieve active subscription counts", e) + 0 to 0 + } +} + +private fun getServiceState(telephonyManager: TelephonyManager?): TelephonyInfo.ServiceState { + val state = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + telephonyManager?.serviceState?.state + } catch (e: SecurityException) { + Log.w(TAG, "Could not retrieve service state", e) + null + } + } else { + null + } + + return when (state) { + ServiceState.STATE_IN_SERVICE -> TelephonyInfo.ServiceState.SERVICE_STATE_IN_SERVICE + ServiceState.STATE_OUT_OF_SERVICE -> TelephonyInfo.ServiceState.SERVICE_STATE_OUT_OF_SERVICE + ServiceState.STATE_EMERGENCY_ONLY -> TelephonyInfo.ServiceState.SERVICE_STATE_EMERGENCY_ONLY + ServiceState.STATE_POWER_OFF -> TelephonyInfo.ServiceState.SERVICE_STATE_POWER_OFF + else -> TelephonyInfo.ServiceState.SERVICE_STATE_UNKNOWN + } +} + +private fun getSimCarrierId(telephonyManager: TelephonyManager?): Long { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + telephonyManager?.simCarrierId?.toLong() ?: -1L + } else { + -1L + } +} + fun NetworkSignal.Companion.getList(context: Context): List { val connectivityInfos = mutableListOf() try { From edb6de4ec6fb01351fe3e180232ca3a46792de73 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 3 Apr 2026 04:49:13 -0400 Subject: [PATCH 21/31] Improve DroidGuard generation --- .../core/proto/builder/ClientInfoBuilder.kt | 36 ++++++++++--------- .../core/proto/builder/CommonBuilders.kt | 5 +-- .../core/proto/builder/SyncRequestBuilder.kt | 9 ++++- .../core/verification/ChallengeProcessor.kt | 3 +- 4 files changed, 32 insertions(+), 21 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt index e3265a57b1..ae9d3a9e9a 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt @@ -7,6 +7,7 @@ import android.content.Context import android.os.Build import android.telephony.SubscriptionManager import android.telephony.TelephonyManager +import android.util.Base64 import android.util.Log import androidx.annotation.RequiresApi import androidx.core.content.edit @@ -24,30 +25,20 @@ import org.microg.gms.constellation.core.proto.DeviceID import org.microg.gms.constellation.core.proto.DeviceType import org.microg.gms.constellation.core.proto.DroidGuardSignals import org.microg.gms.constellation.core.proto.GaiaSignals -import org.microg.gms.constellation.core.proto.GaiaToken import org.microg.gms.constellation.core.proto.NetworkSignal import org.microg.gms.constellation.core.proto.SimOperatorInfo import org.microg.gms.constellation.core.proto.UserProfileType +import java.security.MessageDigest import java.util.Locale private const val TAG = "ClientInfoBuilder" private const val PREFS_NAME = "constellation_prefs" -@SuppressLint("HardwareIds") -suspend operator fun ClientInfo.Companion.invoke(context: Context, iidToken: String): ClientInfo { - return ClientInfo( - context, - RequestBuildContext( - iidToken = iidToken, - gaiaTokens = GaiaToken.getList(context) - ) - ) -} - @SuppressLint("HardwareIds") suspend operator fun ClientInfo.Companion.invoke( context: Context, - buildContext: RequestBuildContext + buildContext: RequestBuildContext, + rpc: String ): ClientInfo { val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) val locale = Locale.getDefault().let { "${it.language}_${it.country}" } @@ -70,7 +61,7 @@ suspend operator fun ClientInfo.Companion.invoke( is_wearable_standalone = false, gaia_signals = GaiaSignals(context), device_fingerprint = Build.FINGERPRINT, - droidguard_signals = DroidGuardSignals(context), + droidguard_signals = DroidGuardSignals(context, buildContext.iidToken, rpc), ) } @@ -151,17 +142,28 @@ operator fun CountryInfo.Companion.invoke(context: Context): CountryInfo { return CountryInfo(sim_countries = simCountries, network_countries = networkCountries) } -suspend operator fun DroidGuardSignals.Companion.invoke(context: Context): DroidGuardSignals? { +suspend operator fun DroidGuardSignals.Companion.invoke( + context: Context, + iidToken: String, + rpc: String +): DroidGuardSignals? { val cachedToken = ConstellationStateStore.loadDroidGuardToken(context) if (!cachedToken.isNullOrBlank()) { return DroidGuardSignals(droidguard_token = cachedToken, droidguard_result = "") } + val md = MessageDigest.getInstance("SHA-256") + val digest = md.digest(iidToken.toByteArray(Charsets.UTF_8)) + val iidHash = Base64.encodeToString( + digest.copyOf(64), + Base64.NO_PADDING or Base64.NO_WRAP + ).substring(0, 32) + return try { val client = DroidGuard.getClient(context) val data = mapOf( - "package_name" to Constants.GMS_PACKAGE_NAME, - "timestamp" to System.currentTimeMillis().toString() + "iidHash" to iidHash, + "rpc" to rpc ) val result = client.getResults("constellation_verify", data, null).await() DroidGuardSignals(droidguard_result = result, droidguard_token = "") diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt index b34b11010f..8cf436b2b6 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt @@ -47,7 +47,8 @@ suspend operator fun RequestHeader.Companion.invoke( sessionId: String, buildContext: RequestBuildContext, triggerType: RequestTrigger.Type = RequestTrigger.Type.CONSENT_API_TRIGGER, - includeClientAuth: Boolean = false + includeClientAuth: Boolean = false, + rpc: String ): RequestHeader { val authManager = if (includeClientAuth) context.authManager else null val clientAuth = if (includeClientAuth) { @@ -62,7 +63,7 @@ suspend operator fun RequestHeader.Companion.invoke( } return RequestHeader( - client_info = ClientInfo(context, buildContext), + client_info = ClientInfo(context, buildContext, rpc), client_auth = clientAuth, session_id = sessionId, trigger = RequestTrigger(type = triggerType) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt index d58eaeb8f7..27eb45e747 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt @@ -192,7 +192,14 @@ suspend operator fun SyncRequest.Companion.invoke( return SyncRequest( verifications = verifications, - header_ = RequestHeader(context, sessionId, buildContext, triggerType, includeClientAuth), + header_ = RequestHeader( + context, + sessionId, + buildContext, + triggerType, + includeClientAuth, + "sync" + ), verification_tokens = ConstellationStateStore.loadVerificationTokens(context) ) } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index 532c5733a4..eb708cfd8d 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -107,7 +107,8 @@ object ChallengeProcessor { sessionId, buildContext, RequestTrigger.Type.TRIGGER_API_CALL, - includeClientAuth = true + includeClientAuth = true, + "proceed" ) val proceedRequest = ProceedRequest( verification = currentVerification, From 94c442ae107d060d03779f4ed0cf23343e05d2c6 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 3 Apr 2026 17:55:51 -0400 Subject: [PATCH 22/31] Improve `MtSmsVerifier` --- .../core/src/main/AndroidManifest.xml | 3 + .../constellation/core/VerifyPhoneNumber.kt | 26 ++- .../core/verification/ChallengeProcessor.kt | 2 +- .../core/verification/MtSmsVerifier.kt | 201 ++++++++++++------ 4 files changed, 163 insertions(+), 69 deletions(-) diff --git a/play-services-constellation/core/src/main/AndroidManifest.xml b/play-services-constellation/core/src/main/AndroidManifest.xml index db636fcc6e..943034c187 100644 --- a/play-services-constellation/core/src/main/AndroidManifest.xml +++ b/play-services-constellation/core/src/main/AndroidManifest.xml @@ -18,6 +18,9 @@ + diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index a1901667f6..2578baf068 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -28,6 +28,7 @@ import org.microg.gms.constellation.core.proto.builder.buildImsiToSubscriptionIn import org.microg.gms.constellation.core.proto.builder.buildRequestContext import org.microg.gms.constellation.core.proto.builder.invoke import org.microg.gms.constellation.core.verification.ChallengeProcessor +import org.microg.gms.constellation.core.verification.MtSmsInboxRegistry import java.util.UUID private const val TAG = "VerifyPhoneNumber" @@ -257,14 +258,23 @@ private suspend fun runVerificationFlow( includeClientAuth = ConstellationStateStore.isPublicKeyAcked(context), callingPackage = callingPackage ) - val verifications = executeSyncFlow( - context, - sessionId, - request, - syncRequest, - buildContext, - imsiToInfoMap - ) + + MtSmsInboxRegistry.prepare( + context.applicationContext, + imsiToInfoMap.values.map { it.subscriptionId }) + + val verifications = try { + executeSyncFlow( + context, + sessionId, + request, + syncRequest, + buildContext, + imsiToInfoMap + ) + } finally { + MtSmsInboxRegistry.dispose() + } if (legacyCallback) { callbacks.onPhoneNumberVerified( diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index eb708cfd8d..17a2a0f37b 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -85,7 +85,7 @@ object ChallengeProcessor { } } - VerificationMethod.MT_SMS -> challenge.mt_challenge?.verify(context, subId) + VerificationMethod.MT_SMS -> challenge.mt_challenge?.verify(subId, remainingMillis) VerificationMethod.MO_SMS -> challenge.mo_challenge?.verify(context, subId) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt index 4ad7e84e6a..fe7abe47a1 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt @@ -6,88 +6,169 @@ import android.content.Intent import android.content.IntentFilter import android.os.Build import android.provider.Telephony +import android.telephony.SmsMessage import android.util.Log +import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull import org.microg.gms.constellation.core.proto.ChallengeResponse import org.microg.gms.constellation.core.proto.MTChallenge import org.microg.gms.constellation.core.proto.MTChallengeResponseData +import java.util.concurrent.ConcurrentHashMap import kotlin.coroutines.resume private const val TAG = "MtSmsVerifier" -suspend fun MTChallenge.verify(context: Context, subId: Int): ChallengeResponse? { +suspend fun MTChallenge.verify( + subId: Int, + timeoutMillis: Long? = null +): ChallengeResponse? { val expectedBody = sms.takeIf { it.isNotEmpty() } ?: return null + val inbox = MtSmsInboxRegistry.get(subId) + val effectiveTimeoutMillis = timeoutMillis?.coerceIn(0L..300_000L) ?: 300_000L Log.d(TAG, "Waiting for MT SMS containing challenge string") - val result = withTimeoutOrNull(300_000) { - suspendCancellableCoroutine?> { continuation -> - val receiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - try { - if (intent.action == Telephony.Sms.Intents.SMS_RECEIVED_ACTION) { - val receivedSubId = intent.getIntExtra( - "android.telephony.extra.SUBSCRIPTION_INDEX", - intent.getIntExtra("subscription", -1) - ) - if (subId != -1 && receivedSubId != -1 && receivedSubId != subId) { - return - } - val messages = Telephony.Sms.Intents.getMessagesFromIntent(intent) - for (msg in messages) { - val body = msg.messageBody - if (body != null && body.contains(expectedBody)) { - Log.d( - TAG, - "Matching MT SMS received from ${msg.originatingAddress}" - ) - context.unregisterReceiver(this) - if (continuation.isActive) { - continuation.resume( - Pair( - body, - msg.originatingAddress ?: "" - ) - ) - } - return - } - } - } - } catch (e: Exception) { - Log.e(TAG, "Error in MT SMS receiver", e) + val match = withTimeoutOrNull(effectiveTimeoutMillis) { + inbox.awaitMatch(expectedBody) + } + + if (match == null) { + Log.w(TAG, "Timed out waiting for MT SMS, proceeding with empty response") + } + + val response = match ?: ReceivedSms(body = "", sender = "") + return ChallengeResponse( + mt_response = MTChallengeResponseData( + sms = response.body, + sender = response.sender + ) + ) +} + +internal object MtSmsInboxRegistry { + private val inboxes = ConcurrentHashMap() + + fun prepare(context: Context, subIds: Iterable) { + dispose() + + val effectiveSubIds = subIds.distinct().ifEmpty { listOf(-1) } + for (subId in effectiveSubIds) { + inboxes[subId] = MtSmsInbox(context.applicationContext, subId) + } + } + + fun get(subId: Int): MtSmsInbox { + return inboxes[subId] + ?: error("MT SMS inbox for subId=$subId was not initialized") + } + + fun dispose() { + val currentInboxes = inboxes.values.toList() + inboxes.clear() + for (inbox in currentInboxes) { + inbox.dispose() + } + } +} + +internal data class ReceivedSms( + val body: String, + val sender: String +) + +private data class PendingMatch( + val expectedBody: String, + val continuation: CancellableContinuation +) + +internal class MtSmsInbox( + context: Context, + private val subId: Int +) { + private val context = context.applicationContext + private val lock = Any() + private val bufferedMessages = mutableListOf() + private val pendingMatches = mutableListOf() + + private val receiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Telephony.Sms.Intents.SMS_RECEIVED_ACTION) return + + val receivedSubId = intent.getIntExtra( + "android.telephony.extra.SUBSCRIPTION_INDEX", + intent.getIntExtra("subscription", -1) + ) + if (subId != -1 && receivedSubId != subId) return + + onMessagesReceived(Telephony.Sms.Intents.getMessagesFromIntent(intent)) + } + } + + init { + val filter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED) + } else { + context.registerReceiver(receiver, filter) + } + } + + suspend fun awaitMatch(expectedBody: String): ReceivedSms? { + return suspendCancellableCoroutine { continuation -> + synchronized(lock) { + bufferedMessages.firstOrNull { it.body.contains(expectedBody) }?.let { + continuation.resume(it) + return@suspendCancellableCoroutine + } + + val pendingMatch = PendingMatch(expectedBody, continuation) + pendingMatches += pendingMatch + continuation.invokeOnCancellation { + synchronized(lock) { + pendingMatches.remove(pendingMatch) } } } + } + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver( - receiver, - IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION), - Context.RECEIVER_NOT_EXPORTED - ) - } else { - context.registerReceiver( - receiver, - IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) - ) - } - continuation.invokeOnCancellation { - try { - context.unregisterReceiver(receiver) - } catch (_: Exception) { + private fun onMessagesReceived(messages: Array) { + val receivedMessages = messages.mapNotNull { message -> + val body = message.messageBody ?: return@mapNotNull null + ReceivedSms( + body = body, + sender = message.originatingAddress ?: "" + ) + } + if (receivedMessages.isEmpty()) return + + synchronized(lock) { + bufferedMessages += receivedMessages + for (receivedMessage in receivedMessages) { + val iterator = pendingMatches.iterator() + while (iterator.hasNext()) { + val pendingMatch = iterator.next() + if (!receivedMessage.body.contains(pendingMatch.expectedBody)) continue + + iterator.remove() + Log.d(TAG, "Matching MT SMS received from ${receivedMessage.sender}") + if (pendingMatch.continuation.isActive) { + pendingMatch.continuation.resume(receivedMessage) + } } } } } - return result?.let { (body, sender) -> - ChallengeResponse( - mt_response = MTChallengeResponseData( - sms = body, - sender = sender - ) - ) + fun dispose() { + synchronized(lock) { + pendingMatches.clear() + bufferedMessages.clear() + } + try { + context.unregisterReceiver(receiver) + } catch (_: IllegalArgumentException) { + } } } From eddc24dccd288b9b853a918818f66e03c2a48517 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Fri, 3 Apr 2026 18:02:42 -0400 Subject: [PATCH 23/31] Reorder `RequestHeader` arguments --- .../gms/constellation/core/proto/builder/CommonBuilders.kt | 4 ++-- .../constellation/core/proto/builder/SyncRequestBuilder.kt | 2 +- .../gms/constellation/core/verification/ChallengeProcessor.kt | 3 +-- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt index 8cf436b2b6..f93d8f21ed 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/CommonBuilders.kt @@ -46,9 +46,9 @@ suspend operator fun RequestHeader.Companion.invoke( context: Context, sessionId: String, buildContext: RequestBuildContext, - triggerType: RequestTrigger.Type = RequestTrigger.Type.CONSENT_API_TRIGGER, + rpc: String, + triggerType: RequestTrigger.Type = RequestTrigger.Type.TRIGGER_API_CALL, includeClientAuth: Boolean = false, - rpc: String ): RequestHeader { val authManager = if (includeClientAuth) context.authManager else null val clientAuth = if (includeClientAuth) { diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt index 27eb45e747..b68f0d5991 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/SyncRequestBuilder.kt @@ -196,9 +196,9 @@ suspend operator fun SyncRequest.Companion.invoke( context, sessionId, buildContext, + "sync", triggerType, includeClientAuth, - "sync" ), verification_tokens = ConstellationStateStore.loadVerificationTokens(context) ) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index 17a2a0f37b..a5190b30eb 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -106,9 +106,8 @@ object ChallengeProcessor { context, sessionId, buildContext, - RequestTrigger.Type.TRIGGER_API_CALL, + "proceed", includeClientAuth = true, - "proceed" ) val proceedRequest = ProceedRequest( verification = currentVerification, From 85567bae3c97413d2cbf6fc4b01771bc5c4d98ac Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sat, 4 Apr 2026 02:00:25 -0400 Subject: [PATCH 24/31] Improve RPC error handling --- .../constellation/core/VerifyPhoneNumber.kt | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index 2578baf068..af7ae4358f 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -202,10 +202,15 @@ private suspend fun handleVerifyPhoneNumberRequest( } } catch (e: Exception) { Log.e(TAG, "verifyPhoneNumber failed", e) + val status = if (readCallbackMode == ReadCallbackMode.NONE && e is GrpcException) { + handleRpcError(e) + } else { + Status.INTERNAL_ERROR + } when (readCallbackMode) { ReadCallbackMode.LEGACY -> { callbacks.onPhoneNumberVerified( - Status.INTERNAL_ERROR, + status, emptyList(), ApiMetadata.DEFAULT ) @@ -214,13 +219,13 @@ private suspend fun handleVerifyPhoneNumberRequest( ReadCallbackMode.NONE -> { if (legacyCallbackOnFullFlow) { callbacks.onPhoneNumberVerified( - Status.INTERNAL_ERROR, + status, emptyList(), ApiMetadata.DEFAULT ) } else { callbacks.onPhoneNumberVerificationsCompleted( - Status.INTERNAL_ERROR, + status, VerifyPhoneNumberResponse(emptyArray(), Bundle()), ApiMetadata.DEFAULT ) @@ -229,7 +234,7 @@ private suspend fun handleVerifyPhoneNumberRequest( ReadCallbackMode.TYPED -> { callbacks.onPhoneNumberVerificationsCompleted( - Status.INTERNAL_ERROR, + status, VerifyPhoneNumberResponse(emptyArray(), Bundle()), ApiMetadata.DEFAULT ) @@ -238,6 +243,19 @@ private suspend fun handleVerifyPhoneNumberRequest( } } +private fun handleRpcError(error: GrpcException): Status { + val statusCode = when (error.grpcStatus) { + GrpcStatus.RESOURCE_EXHAUSTED -> 5008 + GrpcStatus.DEADLINE_EXCEEDED, + GrpcStatus.ABORTED, + GrpcStatus.UNAVAILABLE -> 5007 + + GrpcStatus.PERMISSION_DENIED -> 5009 + else -> 5002 + } + return Status(statusCode, error.message) +} + private suspend fun runVerificationFlow( context: Context, request: VerifyPhoneNumberRequest, From f3449a71f615b7044a6b6f9165615e1059c7e770 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sat, 4 Apr 2026 04:42:26 -0400 Subject: [PATCH 25/31] Improve `MoSmsVerifier` --- .../core/verification/ChallengeProcessor.kt | 26 ++++++- .../core/verification/MoSmsVerifier.kt | 77 +++++++++++++++---- 2 files changed, 88 insertions(+), 15 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index a5190b30eb..c09e26022b 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -9,12 +9,12 @@ import android.util.Log import androidx.annotation.RequiresApi import com.squareup.wire.GrpcException import com.squareup.wire.GrpcStatus +import kotlinx.coroutines.delay import org.microg.gms.constellation.core.ConstellationStateStore import org.microg.gms.constellation.core.RpcClient import org.microg.gms.constellation.core.proto.ChallengeResponse import org.microg.gms.constellation.core.proto.ProceedRequest import org.microg.gms.constellation.core.proto.RequestHeader -import org.microg.gms.constellation.core.proto.RequestTrigger import org.microg.gms.constellation.core.proto.Verification import org.microg.gms.constellation.core.proto.VerificationMethod import org.microg.gms.constellation.core.proto.builder.RequestBuildContext @@ -40,6 +40,7 @@ object ChallengeProcessor { verification: Verification, ): Verification { var currentVerification = verification + var moSession: MoSmsSession? = null for (attempt in 1..MAX_PROCEED_ROUNDS) { if (currentVerification.state != Verification.State.PENDING) { @@ -73,6 +74,9 @@ object ChallengeProcessor { val challengeImsi = currentVerification.association?.sim?.sim_info?.imsi?.firstOrNull() val info = imsiToInfoMap[challengeImsi] val subId = info?.subscriptionId ?: -1 + if (challenge.type != VerificationMethod.MO_SMS) { + moSession = null + } val challengeResponse: ChallengeResponse? = when (challenge.type) { VerificationMethod.TS43 -> challenge.ts43_challenge?.verify(context, subId) @@ -87,7 +91,24 @@ object ChallengeProcessor { VerificationMethod.MT_SMS -> challenge.mt_challenge?.verify(subId, remainingMillis) - VerificationMethod.MO_SMS -> challenge.mo_challenge?.verify(context, subId) + VerificationMethod.MO_SMS -> challenge.mo_challenge?.let { moChallenge -> + val activeSession = moSession?.takeIf { it.matches(challengeId, subId) } + if (activeSession != null) { + val delayMillis = activeSession.nextPollingDelayMillis(remainingMillis) + if (delayMillis > 0L) { + Log.d( + TAG, + "Attempt $attempt: Challenge $challengeId still pending. Waiting ${delayMillis}ms before next MO proceed ping." + ) + delay(delayMillis) + } + activeSession.response + } else { + moChallenge.startSession(context, challengeId, subId).also { + moSession = it + }.response + } + } VerificationMethod.REGISTERED_SMS -> challenge.registered_sms_challenge?.verify( context, @@ -95,6 +116,7 @@ object ChallengeProcessor { ) else -> { + moSession = null Log.w(TAG, "Unsupported verification method: ${challenge.type}") null } diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt index 2ab7176df7..e77b97fc82 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt @@ -26,14 +26,54 @@ import kotlin.coroutines.resume private const val TAG = "MoSmsVerifier" private const val ACTION_MO_SMS_SENT = "org.microg.gms.constellation.core.MO_SMS_SENT" +private const val DEFAULT_MO_POLLING_INTERVALS_MILLIS = + "4000,1000,1000,3000,5000,5000,5000,5000,30000,30000,30000,240000,600000,300000" -suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse { +data class MoSmsSession( + val challengeId: String, + val subId: Int, + val response: ChallengeResponse, + private val pollingIntervalsMillis: List, + private var nextPollingIndex: Int = 0, +) { + fun matches(challengeId: String, subId: Int): Boolean { + return this.challengeId == challengeId && this.subId == subId + } + + fun nextPollingDelayMillis(remainingMillis: Long?): Long { + val configuredDelay = pollingIntervalsMillis.getOrNull(nextPollingIndex) + if (configuredDelay != null) { + nextPollingIndex += 1 + } + val delayMillis = configuredDelay ?: remainingMillis ?: 0L + return if (remainingMillis != null) { + delayMillis.coerceAtMost(remainingMillis.coerceAtLeast(0L)) + } else { + delayMillis.coerceAtLeast(0L) + } + } +} + +suspend fun MoChallenge.startSession( + context: Context, + challengeId: String, + subId: Int +): MoSmsSession { + return MoSmsSession( + challengeId = challengeId, + subId = subId, + response = sendOnce(context, subId), + pollingIntervalsMillis = pollingIntervalsMillis() + ) +} + +private suspend fun MoChallenge.sendOnce(context: Context, subId: Int): ChallengeResponse { if (proxy_number.isEmpty() || sms.isEmpty()) { - return ChallengeResponse(mo_response = MOChallengeResponseData(status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO)) + return failedMoResponse() } if (context.checkCallingOrSelfPermission(Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) { Log.w(TAG, "SEND_SMS permission missing") - return ChallengeResponse(mo_response = MOChallengeResponseData(status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO)) + return failedMoResponse() } val smsManager = resolveSmsManager(context, subId) ?: return ChallengeResponse( @@ -64,7 +104,7 @@ suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse Log.d(TAG, "Sending MO SMS to $proxy_number with messageId: $messageId") - return withTimeoutOrNull(30000) { + return withTimeoutOrNull(30_000L) { suspendCancellableCoroutine { continuation -> val receiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -150,17 +190,28 @@ suspend fun MoChallenge.verify(context: Context, subId: Int): ChallengeResponse } catch (_: Exception) { } if (continuation.isActive) { - continuation.resume( - ChallengeResponse( - mo_response = MOChallengeResponseData( - status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO - ) - ) - ) + continuation.resume(failedMoResponse()) } } } - } ?: ChallengeResponse( + } ?: failedMoResponse() +} + +private fun MoChallenge.pollingIntervalsMillis(): List { + val configuredIntervals = polling_intervals.parsePollingIntervals() + return configuredIntervals.ifEmpty { + DEFAULT_MO_POLLING_INTERVALS_MILLIS.parsePollingIntervals() + } +} + +private fun String.parsePollingIntervals(): List { + return split(',') + .mapNotNull { it.trim().toLongOrNull() } + .filter { it > 0L } +} + +private fun failedMoResponse(): ChallengeResponse { + return ChallengeResponse( mo_response = MOChallengeResponseData( status = MOChallengeResponseData.Status.FAILED_TO_SEND_MO ) @@ -218,4 +269,4 @@ private fun resolveSmsManager(context: Context, subId: Int): SmsManager? { Log.e(TAG, "Failed to resolve SmsManager for subId: $subId", e) null } -} \ No newline at end of file +} From 6920424d6e7375529204d2af8d4604a9ca996ad1 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sat, 4 Apr 2026 18:18:12 -0400 Subject: [PATCH 26/31] Improve MT SMS timeout behavior --- .../core/verification/ChallengeProcessor.kt | 11 +++++++++-- .../constellation/core/verification/MtSmsVerifier.kt | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index c09e26022b..54427076e7 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -63,8 +63,15 @@ object ChallengeProcessor { val challengeId = challenge.challenge_id?.id ?: "" val remainingMillis = challengeTimeRemainingMillis(currentVerification) if (remainingMillis != null && remainingMillis <= 0L) { - Log.w(TAG, "Attempt $attempt: Challenge $challengeId expired before proceed") - return currentVerification + if (challenge.type == VerificationMethod.MT_SMS) { + Log.w( + TAG, + "Attempt $attempt: Challenge $challengeId expired before MT wait. Proceeding with empty MT response." + ) + } else { + Log.w(TAG, "Attempt $attempt: Challenge $challengeId expired before proceed") + return currentVerification + } } Log.d( TAG, diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt index fe7abe47a1..fb7447ec7a 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt @@ -15,6 +15,7 @@ import org.microg.gms.constellation.core.proto.ChallengeResponse import org.microg.gms.constellation.core.proto.MTChallenge import org.microg.gms.constellation.core.proto.MTChallengeResponseData import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit import kotlin.coroutines.resume private const val TAG = "MtSmsVerifier" @@ -25,7 +26,7 @@ suspend fun MTChallenge.verify( ): ChallengeResponse? { val expectedBody = sms.takeIf { it.isNotEmpty() } ?: return null val inbox = MtSmsInboxRegistry.get(subId) - val effectiveTimeoutMillis = timeoutMillis?.coerceIn(0L..300_000L) ?: 300_000L + val effectiveTimeoutMillis = timeoutMillis?.coerceAtLeast(0L) ?: TimeUnit.MINUTES.toMillis(30) Log.d(TAG, "Waiting for MT SMS containing challenge string") From fbcf53c57de7906e31b943aa0927923451366358 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sat, 4 Apr 2026 18:19:10 -0400 Subject: [PATCH 27/31] Add consent check before verification --- .../constellation/core/VerifyPhoneNumber.kt | 78 ++++++++++++++++++- 1 file changed, 74 insertions(+), 4 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt index af7ae4358f..848978f6c9 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/VerifyPhoneNumber.kt @@ -21,6 +21,12 @@ import com.squareup.wire.GrpcStatus import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.microg.gms.common.Constants +import org.microg.gms.constellation.core.proto.AsterismClient +import org.microg.gms.constellation.core.proto.Consent +import org.microg.gms.constellation.core.proto.DeviceID +import org.microg.gms.constellation.core.proto.GetConsentRequest +import org.microg.gms.constellation.core.proto.GetConsentResponse +import org.microg.gms.constellation.core.proto.RequestHeader import org.microg.gms.constellation.core.proto.SyncRequest import org.microg.gms.constellation.core.proto.Verification import org.microg.gms.constellation.core.proto.builder.RequestBuildContext @@ -202,10 +208,11 @@ private suspend fun handleVerifyPhoneNumberRequest( } } catch (e: Exception) { Log.e(TAG, "verifyPhoneNumber failed", e) - val status = if (readCallbackMode == ReadCallbackMode.NONE && e is GrpcException) { - handleRpcError(e) - } else { - Status.INTERNAL_ERROR + val status = when { + readCallbackMode != ReadCallbackMode.NONE -> Status.INTERNAL_ERROR + e is GrpcException -> handleRpcError(e) + e is NoConsentException -> Status(5001) + else -> Status.INTERNAL_ERROR } when (readCallbackMode) { ReadCallbackMode.LEGACY -> { @@ -256,6 +263,8 @@ private fun handleRpcError(error: GrpcException): Status { return Status(statusCode, error.message) } +private class NoConsentException : Exception("No consent") + private suspend fun runVerificationFlow( context: Context, request: VerifyPhoneNumberRequest, @@ -267,6 +276,34 @@ private suspend fun runVerificationFlow( val imsiToInfoMap = buildImsiToSubscriptionInfoMap(context) val buildContext = buildRequestContext(context) + + val asterismClient = when (request.extras.getString("required_consumer_consent")) { + "RCS" -> AsterismClient.RCS + else -> AsterismClient.UNKNOWN_CLIENT + } + + if ( + request.extras.getString("one_time_verification") != "True" && + asterismClient != AsterismClient.UNKNOWN_CLIENT + ) { + val consent = getConsent( + context, + buildContext, + asterismClient, + sessionId, + ) + + val consented = consent.rcs_consent?.consent == Consent.CONSENTED || + consent.gaia_consents.any { + it.asterism_client == asterismClient && it.consent == Consent.CONSENTED + } + + if (!consented) { + Log.e(TAG, "Consent has not been set. Not running verification.") + throw NoConsentException() + } + } + val syncRequest = SyncRequest( context, sessionId, @@ -395,3 +432,36 @@ private suspend fun executeSyncFlow( verifications } + +suspend fun getConsent( + context: Context, + buildContext: RequestBuildContext, + asterismClient: AsterismClient, + sessionId: String = UUID.randomUUID().toString() +): GetConsentResponse { + val request = GetConsentRequest( + device_id = DeviceID(context, buildContext.iidToken), + header_ = RequestHeader( + context, + sessionId, + buildContext, + "getConsent" + ), + asterism_client = asterismClient + ) + + return try { + RpcClient.phoneDeviceVerificationClient.GetConsent().execute(request) + } catch (e: GrpcException) { + if (e.grpcStatus == GrpcStatus.PERMISSION_DENIED || + e.grpcStatus == GrpcStatus.UNAUTHENTICATED + ) { + Log.w( + TAG, + "Suspicious client status ${e.grpcStatus.name}. Clearing DroidGuard cache..." + ) + ConstellationStateStore.clearDroidGuardToken(context) + } + throw e + } +} \ No newline at end of file From 9904b50c4ea02b7d799ae225435b3d75cac9ef6f Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sat, 4 Apr 2026 19:24:14 -0400 Subject: [PATCH 28/31] Improve `CarrierIdVerifier` --- .../core/verification/CarrierIdVerifier.kt | 48 +++++++++++++------ .../core/verification/ChallengeProcessor.kt | 46 +++++++++++++++++- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt index 00d0fb8919..05091a9f7c 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt @@ -15,6 +15,16 @@ import org.microg.gms.constellation.core.proto.ChallengeResponse private const val TAG = "CarrierIdVerifier" +internal data class CarrierIdSession( + val challengeId: String, + val subId: Int, + var attempts: Int = 0, +) { + fun matches(challengeId: String, subId: Int): Boolean { + return this.challengeId == challengeId && this.subId == subId + } +} + fun Challenge.verifyCarrierId(context: Context, subId: Int): ChallengeResponse { val carrierChallenge = carrier_id_challenge ?: return failure( CarrierIdError.CARRIER_ID_ERROR_UNKNOWN_ERROR, @@ -30,22 +40,25 @@ fun Challenge.verifyCarrierId(context: Context, subId: Int): ChallengeResponse { "No active subscription for carrier auth" ) - val appType = carrierChallenge.app_type - val authType = carrierChallenge.auth_type + val telephonyManager = context.getSystemService() + ?: return failure( + CarrierIdError.CARRIER_ID_ERROR_NOT_SUPPORTED, + "TelephonyManager unavailable" + ) + val targetManager = + telephonyManager.createForSubscriptionId(subId) + if (challengeData.startsWith("[ts43]")) { + // Not supported for now, try to get the server to dispatch something different + return failure( + CarrierIdError.CARRIER_ID_ERROR_NOT_SUPPORTED, + "TS43-prefixed Carrier ID challenge not supported" + ) + } - return try { - val telephonyManager = context.getSystemService() - ?: return failure( - CarrierIdError.CARRIER_ID_ERROR_NOT_SUPPORTED, - "TelephonyManager unavailable" - ) - val targetManager = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - telephonyManager.createForSubscriptionId(subId) - } else { - telephonyManager - } + val appType = carrierChallenge.app_type.takeIf { it != 0 } ?: TelephonyManager.APPTYPE_USIM - val response = targetManager.getIccAuthentication(appType, authType, challengeData) + return try { + val response = targetManager.getIccAuthentication(appType, carrierChallenge.auth_type, challengeData) if (response.isNullOrEmpty()) { failure(CarrierIdError.CARRIER_ID_ERROR_NULL_RESPONSE, "Null ISIM response") } else { @@ -77,6 +90,13 @@ fun Challenge.verifyCarrierId(context: Context, subId: Int): ChallengeResponse { } } +fun retryExceededCarrierId(): ChallengeResponse { + return failure( + CarrierIdError.CARRIER_ID_ERROR_RETRY_ATTEMPT_EXCEEDED, + "Carrier ID retry attempt exceeded" + ) +} + private fun failure(status: CarrierIdError, message: String): ChallengeResponse { Log.w(TAG, message) return ChallengeResponse( diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt index 54427076e7..e121a44dfe 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/ChallengeProcessor.kt @@ -41,6 +41,7 @@ object ChallengeProcessor { ): Verification { var currentVerification = verification var moSession: MoSmsSession? = null + var carrierIdSession: CarrierIdSession? = null for (attempt in 1..MAX_PROCEED_ROUNDS) { if (currentVerification.state != Verification.State.PENDING) { @@ -73,6 +74,7 @@ object ChallengeProcessor { return currentVerification } } + Log.d( TAG, "Attempt $attempt: Solving challenge ID: $challengeId, Type: ${challenge.type}" @@ -81,9 +83,13 @@ object ChallengeProcessor { val challengeImsi = currentVerification.association?.sim?.sim_info?.imsi?.firstOrNull() val info = imsiToInfoMap[challengeImsi] val subId = info?.subscriptionId ?: -1 + if (challenge.type != VerificationMethod.MO_SMS) { moSession = null } + if (challenge.type != VerificationMethod.CARRIER_ID || challenge.ts43_challenge != null) { + carrierIdSession = null + } val challengeResponse: ChallengeResponse? = when (challenge.type) { VerificationMethod.TS43 -> challenge.ts43_challenge?.verify(context, subId) @@ -92,7 +98,31 @@ object ChallengeProcessor { if (challenge.ts43_challenge != null) { challenge.ts43_challenge.verify(context, subId) } else { - challenge.verifyCarrierId(context, subId) + val activeSession = + carrierIdSession?.takeIf { it.matches(challengeId, subId) } + ?: CarrierIdSession(challengeId, subId).also { + carrierIdSession = it + } + + activeSession.attempts += 1 + + if (activeSession.attempts >= 4) { + Log.w( + TAG, + "Attempt $attempt: Carrier ID challenge $challengeId is still pending after retry-exceeded response. Stopping." + ) + return currentVerification + } + + if (activeSession.attempts >= 3) { + Log.w( + TAG, + "Attempt $attempt: Carrier ID challenge $challengeId exceeded retry budget. Proceeding with retry-exceeded response." + ) + retryExceededCarrierId() + } else { + challenge.verifyCarrierId(context, subId) + } } } @@ -159,6 +189,18 @@ object ChallengeProcessor { } ConstellationStateStore.storeProceedResponse(context, proceedResponse) currentVerification = proceedResponse.verification ?: currentVerification + + if (challenge.type == VerificationMethod.CARRIER_ID) { + val nextChallenge = currentVerification.pending_verification_info?.challenge + val sameCarrierIdChallenge = + nextChallenge?.type == VerificationMethod.CARRIER_ID && + nextChallenge.challenge_id?.id == challengeId && + nextChallenge.ts43_challenge == null + + if (!sameCarrierIdChallenge) { + carrierIdSession = null + } + } } else { Log.w( TAG, @@ -172,4 +214,4 @@ object ChallengeProcessor { Log.w(TAG, "Exhausted all $MAX_PROCEED_ROUNDS proceed rounds, record is still pending.") return currentVerification } -} +} \ No newline at end of file From ec02375745193d2b26be61083577d460cb68cc30 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Sun, 5 Apr 2026 06:12:16 -0400 Subject: [PATCH 29/31] Update `constellation.proto` --- .../core/ConstellationStateStore.kt | 4 +- .../core/src/main/proto/constellation.proto | 40 ++++++++----------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt index f41739c10b..ef2ebe25d1 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/ConstellationStateStore.kt @@ -10,9 +10,9 @@ import androidx.annotation.RequiresApi import androidx.core.content.edit import com.squareup.wire.Instant import okio.ByteString.Companion.toByteString +import org.microg.gms.constellation.core.proto.AsterismConsent.DeviceConsentVersion import org.microg.gms.constellation.core.proto.Consent import org.microg.gms.constellation.core.proto.ConsentSource -import org.microg.gms.constellation.core.proto.ConsentVersion import org.microg.gms.constellation.core.proto.DroidguardToken import org.microg.gms.constellation.core.proto.ProceedResponse import org.microg.gms.constellation.core.proto.ServerTimestamp @@ -132,7 +132,7 @@ object ConstellationStateStore { context: Context, consent: Consent, source: ConsentSource, - version: ConsentVersion + version: DeviceConsentVersion ) { statePrefs(context).edit { putInt(KEY_PNVR_NOTICE_CONSENT, consent.value) diff --git a/play-services-constellation/core/src/main/proto/constellation.proto b/play-services-constellation/core/src/main/proto/constellation.proto index de4e950b6e..98297b63b2 100644 --- a/play-services-constellation/core/src/main/proto/constellation.proto +++ b/play-services-constellation/core/src/main/proto/constellation.proto @@ -755,15 +755,13 @@ message GetConsentResponse { PermissionState permission_state = 10; } -enum FlowContext { - option allow_alias = true; - FLOW_CONTEXT_UNSPECIFIED = 0; - FLOW_CONTEXT_RCS_DEFAULT_ON_LEGAL_FYI = 0; - FLOW_CONTEXT_RCS_CONSENT = 1; - FLOW_CONTEXT_RCS_DEFAULT_ON_OUT_OF_BOX = 2; - FLOW_CONTEXT_RCS_SAMSUNG_UNFREEZE = 3; - FLOW_CONTEXT_RCS_DEFAULT_ON_LEGAL_FYI_IN_SETTINGS = 4; - FLOW_CONTEXT_RCS_UNKNOWN_5 = 5; +enum ConsentVersion { + CONSENT_VERSION_UNSPECIFIED = 0; + RCS_CONSENT = 1; + RCS_DEFAULT_ON_LEGAL_FYI = 2; + RCS_DEFAULT_ON_OUT_OF_BOX = 3; + RCS_SAMSUNG_UNFREEZE = 4; + RCS_DEFAULT_ON_LEGAL_FYI_IN_SETTINGS = 5; } message SetConsentRequest { @@ -773,7 +771,7 @@ message SetConsentRequest { bytes audit_record = 6; repeated Param api_params = 7; OnDemandConsent on_demand_consent = 8; - FlowContext flow_context = 9; + ConsentVersion consent_version = 9; oneof device_consent_oneof { AsterismConsent device_consent = 10; } @@ -789,22 +787,18 @@ enum Consent { EXPIRED = 3; } -enum ConsentVersion { - option allow_alias = true; - CONSENT_VERSION_UNSPECIFIED = 0; - RCS_LEGAL_FYI = 0; - RCS_CONSENT = 1; - RCS_OUT_OF_BOX = 2; - PHONE_VERIFICATION_DEFAULT = 1; - PHONE_VERIFICATION_MESSAGES_CALLS_V1 = 2; - PHONE_VERIFICATION_INTL_SMS_CALLS = 3; - PHONE_VERIFICATION_REACHABILITY_INTL_SMS_CALLS = 4; -} - message AsterismConsent { Consent consent = 1; ConsentSource consent_source = 2; - ConsentVersion consent_version = 3; + DeviceConsentVersion consent_version = 3; + + enum DeviceConsentVersion { + UNKNOWN = 0; + PHONE_VERIFICATION_DEFAULT = 1; + PHONE_VERIFICATION_MESSAGES_CALLS_V1 = 2; + PHONE_VERIFICATION_INTL_SMS_CALLS = 3; + PHONE_VERIFICATION_REACHABILITY_INTL_SMS_CALLS = 4; + } } message ConsentState { From ce85aeb232f9f9d49e3ec23e98407ccd130c3a41 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:30:18 -0400 Subject: [PATCH 30/31] Small cleanups --- .../core/proto/builder/ClientInfoBuilder.kt | 8 ++++--- .../core/verification/CarrierIdVerifier.kt | 3 ++- .../core/verification/MoSmsVerifier.kt | 22 ++++++------------- .../core/verification/MtSmsVerifier.kt | 13 ++++++----- 4 files changed, 21 insertions(+), 25 deletions(-) diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt index ae9d3a9e9a..b1644dc893 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/proto/builder/ClientInfoBuilder.kt @@ -5,6 +5,8 @@ package org.microg.gms.constellation.core.proto.builder import android.annotation.SuppressLint import android.content.Context import android.os.Build +import android.os.Process +import android.os.UserManager import android.telephony.SubscriptionManager import android.telephony.TelephonyManager import android.util.Base64 @@ -83,15 +85,15 @@ operator fun DeviceID.Companion.invoke(context: Context, iidToken: String): Devi } val userSerial = try { - val userManager = context.getSystemService() - userManager?.getSerialNumberForUser(android.os.Process.myUserHandle()) ?: 0L + val userManager = context.getSystemService() + userManager?.getSerialNumberForUser(Process.myUserHandle()) ?: 0L } catch (_: Exception) { 0L } var primaryDeviceId = prefs.getLong("primary_device_id", 0L) val isSystemUser = try { - val userManager = context.getSystemService() + val userManager = context.getSystemService() userManager?.isSystemUser ?: true } catch (_: Exception) { true diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt index 05091a9f7c..42d73ac890 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/CarrierIdVerifier.kt @@ -58,7 +58,8 @@ fun Challenge.verifyCarrierId(context: Context, subId: Int): ChallengeResponse { val appType = carrierChallenge.app_type.takeIf { it != 0 } ?: TelephonyManager.APPTYPE_USIM return try { - val response = targetManager.getIccAuthentication(appType, carrierChallenge.auth_type, challengeData) + val response = + targetManager.getIccAuthentication(appType, carrierChallenge.auth_type, challengeData) if (response.isNullOrEmpty()) { failure(CarrierIdError.CARRIER_ID_ERROR_NULL_RESPONSE, "Null ISIM response") } else { diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt index e77b97fc82..458712c4cb 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MoSmsVerifier.kt @@ -142,20 +142,12 @@ private suspend fun MoChallenge.sendOnce(context: Context, subId: Int): Challeng } } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver( - receiver, - IntentFilter(action), - Context.RECEIVER_NOT_EXPORTED - ) - } else { - ContextCompat.registerReceiver( - context, - receiver, - IntentFilter(action), - ContextCompat.RECEIVER_NOT_EXPORTED - ) - } + ContextCompat.registerReceiver( + context, + receiver, + IntentFilter(action), + ContextCompat.RECEIVER_NOT_EXPORTED + ) continuation.invokeOnCancellation { try { @@ -252,7 +244,7 @@ private fun resolveSmsManager(context: Context, subId: Int): SmsManager? { return try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - val manager = context.getSystemService(SmsManager::class.java) + val manager = context.getSystemService() if (subId != -1) { manager?.createForSubscriptionId(subId) } else { diff --git a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt index fb7447ec7a..7a522c1e18 100644 --- a/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt +++ b/play-services-constellation/core/src/main/kotlin/org/microg/gms/constellation/core/verification/MtSmsVerifier.kt @@ -4,10 +4,10 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.os.Build import android.provider.Telephony import android.telephony.SmsMessage import android.util.Log +import androidx.core.content.ContextCompat import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeoutOrNull @@ -108,11 +108,12 @@ internal class MtSmsInbox( init { val filter = IntentFilter(Telephony.Sms.Intents.SMS_RECEIVED_ACTION) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - context.registerReceiver(receiver, filter, Context.RECEIVER_NOT_EXPORTED) - } else { - context.registerReceiver(receiver, filter) - } + ContextCompat.registerReceiver( + context, + receiver, + filter, + ContextCompat.RECEIVER_NOT_EXPORTED + ) } suspend fun awaitMatch(expectedBody: String): ReceivedSms? { From bafa5f405a1efe4a0e07bb74d66289ab661fc768 Mon Sep 17 00:00:00 2001 From: Opstic <46141527+opstic@users.noreply.github.com> Date: Mon, 6 Apr 2026 02:39:20 -0400 Subject: [PATCH 31/31] Add permissions to Self-Check --- .../main/java/org/microg/gms/ui/SelfCheckFragment.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java b/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java index 8d809ca0a7..789db690b9 100644 --- a/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java +++ b/play-services-core/src/main/java/org/microg/gms/ui/SelfCheckFragment.java @@ -50,8 +50,11 @@ import static android.Manifest.permission.GET_ACCOUNTS; import static android.Manifest.permission.POST_NOTIFICATIONS; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.Manifest.permission.READ_PHONE_NUMBERS; import static android.Manifest.permission.READ_PHONE_STATE; +import static android.Manifest.permission.READ_SMS; import static android.Manifest.permission.RECEIVE_SMS; +import static android.Manifest.permission.SEND_SMS; import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE; import static android.os.Build.VERSION.SDK_INT; @@ -76,8 +79,13 @@ protected void prepareSelfCheckList(Context context, List checks if (SDK_INT >= 33) { permissions.add(POST_NOTIFICATIONS); } + if (SDK_INT >= 26) { + permissions.add(READ_PHONE_NUMBERS); + } permissions.add(READ_PHONE_STATE); + permissions.add(READ_SMS); permissions.add(RECEIVE_SMS); + permissions.add(SEND_SMS); checks.add(new PermissionCheckGroup(permissions.toArray(new String[0])) { @Override public void doChecks(Context context, ResultCollector collector) {