Skip to content
Merged
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ def execResult(... args) {
}

def ignoreGit = providers.environmentVariable('GRADLE_MICROG_VERSION_WITHOUT_GIT').getOrElse('0') == '1'
def gmsVersion = "25.09.32"
def gmsVersion = "25.24.32"
def gmsVersionCode = Integer.parseInt(gmsVersion.replaceAll('\\.', ''))
def vendingVersion = "40.2.26"
def vendingVersionCode = Integer.parseInt(vendingVersion.replaceAll('\\.', ''))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ public enum GmsService {
GOOGLESETTINGS(349),
HTTPFLAGS(350),
SETUP_SERVICES_REMOTE_SETUP(351),
IDENTITY_CREDENTIALS(352),
IDENTITY_CREDENTIALS(352, "com.google.android.gms.identitycredentials.service.START"),
AMBIENT_CONTEXT(353),
SAFE_BROWSING(354),
MULTIDEVICE_API_FEATURE_SETTINGS(355),
Expand Down
1 change: 1 addition & 0 deletions play-services-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ dependencies {
implementation project(':play-services-fido-core')
implementation project(':play-services-fitness-core')
implementation project(':play-services-gmscompliance-core')
implementation project(':play-services-identity-credentials-core')
implementation project(':play-services-location-core')
implementation project(':play-services-location-core-base')
implementation project(':play-services-oss-licenses-core')
Expand Down
7 changes: 7 additions & 0 deletions play-services-core/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,13 @@
android:theme="@style/Theme.App.Translucent"
android:excludeFromRecents="true"/>

<activity
android:name="org.microg.gms.auth.credentials.identity.IdentityCredentialChooserActivity"
android:exported="false"
android:process=":ui"
android:theme="@style/Theme.App.Translucent"
android:excludeFromRecents="true"/>

<activity
android:theme="@style/Theme.AppCompat.Dialog.Alert"
android:name="org.microg.gms.auth.signin.AssistedSignInActivity"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* SPDX-FileCopyrightText: 2026 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

package org.microg.gms.auth.credentials

import android.content.Context
import android.content.Intent
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer
import org.microg.gms.auth.signin.ACTION_ASSISTED_SIGN_IN
import org.microg.gms.auth.signin.CLIENT_PACKAGE_NAME
import org.microg.gms.auth.signin.GOOGLE_SIGN_IN_OPTIONS
import org.microg.gms.common.GmsService
import org.microg.gms.fido.core.ui.ACTION_FIDO_AUTHENTICATE
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_CALLER
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_CREDENTIAL_ID
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_OPTIONS
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_SERVICE
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_SOURCE
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_TYPE

fun Context.buildFidoAuthenticateIntent(
source: String,
optionsBytes: ByteArray,
callingPackage: String?,
type: String,
credentialIdString: String? = null,
): Intent = Intent(ACTION_FIDO_AUTHENTICATE).apply {
`package` = packageName
putExtra(KEY_SERVICE, GmsService.FIDO2_API.SERVICE_ID)
putExtra(KEY_SOURCE, source)
putExtra(KEY_TYPE, type)
putExtra(KEY_OPTIONS, optionsBytes)
callingPackage?.let { putExtra(KEY_CALLER, it) }
credentialIdString?.let { putExtra(KEY_CREDENTIAL_ID, it) }
}

fun Context.buildAssistedSignInIntent(
requestExtraKey: String,
serializedRequest: ByteArray,
googleSignInOptions: GoogleSignInOptions,
callingPackage: String?,
): Intent = Intent(ACTION_ASSISTED_SIGN_IN).apply {
`package` = packageName
putExtra(requestExtraKey, serializedRequest)
putExtra(GOOGLE_SIGN_IN_OPTIONS, SafeParcelableSerializer.serializeToBytes(googleSignInOptions))
callingPackage?.let { putExtra(CLIENT_PACKAGE_NAME, it) }
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ const val GOOGLE_ID_BUNDLE_KEY_GIVEN_NAME = "com.google.android.libraries.identi
const val GOOGLE_ID_BUNDLE_KEY_FAMILY_NAME = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_FAMILY_NAME"
const val GOOGLE_ID_BUNDLE_KEY_PHONE_NUMBER = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_PHONE_NUMBER"
const val GOOGLE_ID_BUNDLE_KEY_PROFILE_PICTURE_URI = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_PROFILE_PICTURE_URI"
const val GOOGLE_ID_FILTER_BY_AUTHORIZED_ACCOUNTS = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_FILTER_BY_AUTHORIZED_ACCOUNTS"

// Credential types
const val TYPE_GOOGLE_ID_TOKEN_CREDENTIAL = "com.google.android.libraries.identity.googleid.TYPE_GOOGLE_ID_TOKEN_CREDENTIAL"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,8 @@ import androidx.credentials.provider.ProviderGetCredentialRequest
import com.google.android.gms.fido.Fido.FIDO2_KEY_CREDENTIAL_EXTRA
import com.google.android.gms.fido.fido2.api.common.*
import org.json.JSONObject
import org.microg.gms.common.GmsService
import org.microg.gms.fido.core.ui.ACTION_FIDO_AUTHENTICATE
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_CALLER
import org.microg.gms.auth.credentials.buildFidoAuthenticateIntent
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_CREDENTIAL_ID
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_OPTIONS
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_SERVICE
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_SOURCE
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.KEY_TYPE
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.SOURCE_APP
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.SOURCE_BROWSER
import org.microg.gms.fido.core.ui.AuthenticatorActivity.Companion.TYPE_REGISTER
Expand All @@ -50,7 +44,7 @@ class PublicKeyProxyActivity : CredentialProviderActivity() {
val credentialIdString = intent.getStringExtra(KEY_CREDENTIAL_ID)

val (optionsBytes, source) = buildRequestOptions(options, isBrowserRequest, request.callingAppInfo.origin, option.clientDataHash)
val fidoIntent = createFidoIntent(source, optionsBytes, request.callingAppInfo.packageName, TYPE_SIGN, credentialIdString)
val fidoIntent = buildFidoAuthenticateIntent(source, optionsBytes, request.callingAppInfo.packageName, TYPE_SIGN, credentialIdString)
startActivityForResult(fidoIntent, REQUEST_CODE_FIDO)
}

Expand All @@ -76,7 +70,7 @@ class PublicKeyProxyActivity : CredentialProviderActivity() {
Log.d(TAG, "handlePasskeyCreate: options: $options")

val (optionsBytes, source) = buildCreationOptions(options, isBrowserRequest, origin, publicKeyRequest.clientDataHash)
val fidoIntent = createFidoIntent(source, optionsBytes, callingPackage, TYPE_REGISTER)
val fidoIntent = buildFidoAuthenticateIntent(source, optionsBytes, callingPackage, TYPE_REGISTER)

startActivityForResult(fidoIntent, REQUEST_CODE_FIDO)
Log.d(TAG, "Launched FIDO authenticator by PasskeyCreate")
Expand Down Expand Up @@ -118,19 +112,6 @@ class PublicKeyProxyActivity : CredentialProviderActivity() {
}
}

fun createFidoIntent(
source: String, optionsBytes: ByteArray, callingPackage: String, type: String, credentialIdString: String? = null
): Intent = Intent(ACTION_FIDO_AUTHENTICATE).apply {
`package` = packageName
putExtra(KEY_SERVICE, GmsService.FIDO2_API.SERVICE_ID)
putExtra(KEY_SOURCE, source)
putExtra(KEY_TYPE, type)
putExtra(KEY_OPTIONS, optionsBytes)
putExtra(KEY_CALLER, callingPackage)
credentialIdString?.let { putExtra(KEY_CREDENTIAL_ID, it) }
}


private fun handleFidoSuccess(publicKeyCredential: PublicKeyCredential) = runCatching {
when (val response = publicKeyCredential.response) {
is AuthenticatorAttestationResponse -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,8 @@ import com.google.android.gms.auth.api.identity.SignInCredential
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.android.gms.common.internal.safeparcel.SafeParcelableSerializer
import org.microg.gms.auth.AuthConstants
import org.microg.gms.auth.signin.ACTION_ASSISTED_SIGN_IN
import org.microg.gms.auth.signin.CLIENT_PACKAGE_NAME
import org.microg.gms.auth.credentials.buildAssistedSignInIntent
import org.microg.gms.auth.signin.GET_SIGN_IN_INTENT_REQUEST
import org.microg.gms.auth.signin.GOOGLE_SIGN_IN_OPTIONS

private const val TAG = "SignInProxyActivity"
private const val REQUEST_CODE_SIGN_IN = 100
Expand All @@ -32,31 +30,23 @@ private const val REQUEST_CODE_SIGN_IN = 100
class SignInProxyActivity : CredentialProviderActivity() {

override fun onProviderGetCredentialRequest(request: ProviderGetCredentialRequest) {
val bundle = Bundle().apply {
val signInRequest = GetSignInIntentRequest.builder()
.setServerClientId(intent.getStringExtra(GOOGLE_ID_SIWG_SERVER_CLIENT_ID) ?: "")
.apply {
intent.getStringExtra(GOOGLE_ID_SIWG_NONCE)?.let { setNonce(it) }
}
.build()

val googleSignInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestIdToken(intent.getStringExtra(GOOGLE_ID_SIWG_SERVER_CLIENT_ID) ?: "")
.apply { intent.getStringExtra(GOOGLE_ID_SIWG_ACCOUNT_NAME)?.let { setAccountName(it) } }
.build()

putByteArray(GET_SIGN_IN_INTENT_REQUEST, SafeParcelableSerializer.serializeToBytes(signInRequest))
putByteArray(GOOGLE_SIGN_IN_OPTIONS, SafeParcelableSerializer.serializeToBytes(googleSignInOptions))
putString(CLIENT_PACKAGE_NAME, intent.getStringExtra(GOOGLE_ID_SIWG_CALLER_PACKAGE))
}
startActivityForResult(
Intent(ACTION_ASSISTED_SIGN_IN).apply {
`package` = packageName
putExtras(bundle)
},
REQUEST_CODE_SIGN_IN
val serverClientId = intent.getStringExtra(GOOGLE_ID_SIWG_SERVER_CLIENT_ID).orEmpty()
val signInRequest = GetSignInIntentRequest.builder()
.setServerClientId(serverClientId)
.apply { intent.getStringExtra(GOOGLE_ID_SIWG_NONCE)?.let { setNonce(it) } }
.build()
val googleSignInOptions = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail()
.requestIdToken(serverClientId)
.apply { intent.getStringExtra(GOOGLE_ID_SIWG_ACCOUNT_NAME)?.let { setAccountName(it) } }
.build()
val signInIntent = buildAssistedSignInIntent(
requestExtraKey = GET_SIGN_IN_INTENT_REQUEST,
serializedRequest = SafeParcelableSerializer.serializeToBytes(signInRequest),
googleSignInOptions = googleSignInOptions,
callingPackage = intent.getStringExtra(GOOGLE_ID_SIWG_CALLER_PACKAGE)
)
startActivityForResult(signInIntent, REQUEST_CODE_SIGN_IN)
}

override fun onProviderCreateCredentialRequest(request: ProviderCreateCredentialRequest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import org.microg.gms.fido.core.transport.TransportHandler
import org.microg.gms.fido.core.transport.TransportHandlerCallback
import org.microg.gms.utils.toBase64
import java.security.Signature
import java.security.cert.Certificate
import java.security.cert.X509Certificate
import java.security.interfaces.ECPublicKey
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
Expand Down Expand Up @@ -122,34 +124,40 @@ class ScreenLockTransportHandler(private val activity: FragmentActivity, callbac
}
}
val (clientData, clientDataHash) = getClientDataAndHash(activity, options, callerPackage)
val aaguid = if (options.registerOptions.skipAttestation) ByteArray(16) else AAGUID
val keyId = store.createKey(options.rpId, clientDataHash)
val publicKey =
store.getPublicKey(options.rpId, keyId) ?: throw RequestHandlingException(ErrorCode.INVALID_STATE_ERR)

// We're ignoring the signature object as we don't need it for registration
val signature = getActiveSignature(options, callerPackage, keyId)

val skipAttestation = options.registerOptions.skipAttestation
val useAndroidKey = !skipAttestation && SDK_INT >= 24 &&
runCatching { store.getCertificateChain(options.rpId, keyId).hasValidLeafCertificate() }.getOrDefault(false)
val useSafetyNet = !skipAttestation && SDK_INT < 24
val aaguid = if (useAndroidKey || useSafetyNet) AAGUID else ByteArray(16)

val (x, y) = (publicKey as ECPublicKey).w.let { it.affineX to it.affineY }
val coseKey = CoseKey(EC2Algorithm.ES256, x, y, 1, 32)
val credentialId = CredentialId(1, keyId, options.rpId, publicKey)

val credentialData = getCredentialData(aaguid, credentialId, coseKey)
val authenticatorData = getAuthenticatorData(options.rpId, credentialData)

val attestationObject = if (options.registerOptions.skipAttestation) {
NoneAttestationObject(authenticatorData)
} else {
try {
if (SDK_INT >= 24) {
createAndroidKeyAttestation(signature, authenticatorData, clientDataHash, options.rpId, keyId)
} else {
createSafetyNetAttestation(authenticatorData, clientDataHash)
}
val attestationObject = when {
useAndroidKey -> try {
createAndroidKeyAttestation(signature, authenticatorData, clientDataHash, options.rpId, keyId)
} catch (e: Exception) {
Log.w("FidoScreenLockTransport", e)
NoneAttestationObject(authenticatorData)
}
useSafetyNet -> try {
createSafetyNetAttestation(authenticatorData, clientDataHash)
} catch (e: Exception) {
Log.w("FidoScreenLockTransport", e)
NoneAttestationObject(authenticatorData)
}
else -> NoneAttestationObject(authenticatorData)
}

return AuthenticatorResponseWithUser(
Expand Down Expand Up @@ -180,6 +188,11 @@ class ScreenLockTransportHandler(private val activity: FragmentActivity, callbac
store.getCertificateChain(rpId, keyId).map { it.encoded })
}

private fun Array<Certificate>.hasValidLeafCertificate(): Boolean {
val leaf = firstOrNull() as? X509Certificate ?: return false
return runCatching { leaf.checkValidity() }.isSuccess
}

private suspend fun createSafetyNetAttestation(
authenticatorData: AuthenticatorData,
clientDataHash: ByteArray
Expand Down
37 changes: 37 additions & 0 deletions play-services-identity-credentials/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* SPDX-FileCopyrightText: 2026 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

apply plugin: 'com.android.library'

android {
namespace "com.google.android.gms.identitycredentials"

compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"

buildFeatures {
aidl = true
}

defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
}

description = 'microG implementation of play-services-identity-credentials'

dependencies {
api project(':play-services-base')
api project(':play-services-basement')

annotationProcessor project(':safe-parcel-processor')
}
52 changes: 52 additions & 0 deletions play-services-identity-credentials/core/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* SPDX-FileCopyrightText: 2026 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

dependencies {
api project(':play-services-identity-credentials')

implementation project(':play-services-base-core')
implementation project(':play-services-fido-core')

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion"
}

android {
namespace "org.microg.gms.identitycredentials.core"

compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"

defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}

sourceSets {
main {
java.srcDirs = ['src/main/kotlin']
}
}

lintOptions {
disable 'MissingTranslation'
}

compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}

kotlinOptions {
jvmTarget = 1.8
}
}

description = 'microG service implementation for play-services-identity-credentials'
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ SPDX-FileCopyrightText: 2026 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application>
<service android:name="org.microg.gms.identitycredentials.IdentityCredentialApiService">
<intent-filter>
<action android:name="com.google.android.gms.identitycredentials.service.START" />
</intent-filter>
</service>
</application>
</manifest>
Loading