Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -168,7 +168,10 @@ const val GOOGLE_ID_ANDROIDX_AUTO_SELECT = "androidx.credentials.BUNDLE_KEY_IS_A
const val GOOGLE_ID_BUNDLE_KEY_ID = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_ID"
const val GOOGLE_ID_BUNDLE_KEY_ID_TOKEN = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_ID_TOKEN"
const val GOOGLE_ID_BUNDLE_KEY_DISPLAY_NAME = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_DISPLAY_NAME"
const val GOOGLE_ID_BUNDLE_KEY_GIVEN_NAME = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_GIVEN_NAME"
const val GOOGLE_ID_BUNDLE_KEY_FAMILY_NAME = "com.google.android.libraries.identity.googleid.BUNDLE_KEY_FAMILY_NAME"
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