From 69b89685e10b81e1512eddadb8f8e2461c697bb0 Mon Sep 17 00:00:00 2001 From: sim Date: Tue, 5 May 2026 19:56:15 +0200 Subject: [PATCH 1/2] Fido: Add extra to disallow instant auth --- .../gms/fido/core/ui/AuthenticatorActivity.kt | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt index d3cfdf3567..919c702f90 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt @@ -62,6 +62,10 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { val source: String? get() = intent.getStringExtra(KEY_SOURCE) + enum class AllowedInstantLevel { + NONE, PRESELECT, INSTANT + } + private val service: GmsService get() = GmsService.byServiceId(intent.getIntExtra(KEY_SERVICE, GmsService.UNKNOWN.SERVICE_ID)) private val database by lazy { Database(this) } @@ -109,11 +113,17 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { Log.d(TAG, "onCreate caller=$callerPackage options=$options preselectedCredentialId=$preselectedCredentialId") + // By default, we allow instants. This extra may be used by app using fido-core library + // To avoid instant. + val allowInstant = AllowedInstantLevel.entries.getOrNull( + intent.getIntExtra(KEY_ALLOW_INSTANT, AllowedInstantLevel.INSTANT.ordinal) + ) ?: AllowedInstantLevel.INSTANT + val requiresPrivilege = source == SOURCE_BROWSER && !database.isPrivileged(callerPackage, callerSignature) // Check if we can directly open screen lock handling - if (!requiresPrivilege) { + if (allowInstant == AllowedInstantLevel.INSTANT && !requiresPrivilege) { val instantTransport = transportHandlers.firstOrNull { it.isSupported && it.shouldBeUsedInstantly(options, preselectedCredentialId) } @@ -128,7 +138,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { setContentView(R.layout.fido_authenticator_activity) lifecycleScope.launchWhenCreated { - handleRequest(options) + handleRequest(options, allowInstant) } } catch (e: RequestHandlingException) { finishWithError(e.errorCode, e.message ?: e.errorCode.name) @@ -139,7 +149,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { } @RequiresApi(24) - suspend fun handleRequest(options: RequestOptions, allowInstant: Boolean = true) { + suspend fun handleRequest(options: RequestOptions, allowInstantLevel: AllowedInstantLevel = AllowedInstantLevel.INSTANT) { try { val origin = getOrigin(this, options, callerPackage) options.checkIsValid(this, origin, callerPackage) @@ -154,7 +164,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { val noLocalUserForSignInstantBlock = options.type == RequestOptionsType.SIGN && database.getKnownRegistrationInfo(options.rpId).isEmpty() // Check if we can directly open screen lock handling - if (!requiresPrivilege && allowInstant && !noLocalUserForSignInstantBlock) { + if (!requiresPrivilege && allowInstantLevel == AllowedInstantLevel.INSTANT && !noLocalUserForSignInstantBlock) { val instantTransport = transportHandlers.firstOrNull { it.isSupported && it.shouldBeUsedInstantly(options, preselectedCredentialId) } @@ -171,7 +181,11 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { this.requiresPrivilege = requiresPrivilege this.supportedTransports = transportHandlers.filter { it.isSupported }.map { it.transport }.toSet() }.arguments - val next = if (!requiresPrivilege) { + val next = if (requiresPrivilege) { + null + } else if (allowInstantLevel == AllowedInstantLevel.NONE) { + R.id.transportSelectionFragment + } else { val knownRegistrationTransports = mutableSetOf() val allowedTransports = mutableSetOf() if (options.type == RequestOptionsType.SIGN) { @@ -215,8 +229,6 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { } else { null } - } else { - null } navHostFragment = NavHostFragment() supportFragmentManager.commit { @@ -321,7 +333,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { } catch (e: SecurityException) { Log.w(TAG, e) if (instant) { - handleRequest(options, false) + handleRequest(options, AllowedInstantLevel.PRESELECT) } else { finishWithError(SECURITY_ERR, e.message ?: e.javaClass.simpleName) } @@ -381,6 +393,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { const val KEY_SERVICE = "service" const val KEY_SOURCE = "source" const val KEY_TYPE = "type" + const val KEY_ALLOW_INSTANT = "allowInstant" const val KEY_OPTIONS = "options" const val KEY_USER_JSON = "userInfo" const val KEY_CREDENTIAL_ID = "credential" From fd58e4111146c0910889f23e07f7c6e4f69b0af4 Mon Sep 17 00:00:00 2001 From: sim Date: Thu, 7 May 2026 21:45:21 +0200 Subject: [PATCH 2/2] Add option to preselect transport --- .../microg/gms/fido/core/ui/AuthenticatorActivity.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt index 919c702f90..d4fc8c4f5a 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/ui/AuthenticatorActivity.kt @@ -83,6 +83,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { lateinit var callerSignature: String private lateinit var navHostFragment: NavHostFragment private var preselectedCredentialId: String? = null + private var preselectedTransport: Transport? = null private inline fun getTransportHandler(): T? = transportHandlers.filterIsInstance().firstOrNull { it.isSupported } @@ -110,6 +111,9 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { this.callerSignature = packageManager.getFirstSignatureDigest(callerPackage, "SHA-256")?.toBase64() ?: return finishWithError(UNKNOWN_ERR, "Could not determine signature of app") this.preselectedCredentialId = intent.getStringExtra(KEY_CREDENTIAL_ID) + this.preselectedTransport = intent.getStringExtra(KEY_PRESELECTED_TRANSPORT)?.let { + runCatching { Transport.valueOf(it) }.getOrNull() + } Log.d(TAG, "onCreate caller=$callerPackage options=$options preselectedCredentialId=$preselectedCredentialId") @@ -217,7 +221,12 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { } } } - val preselectedTransport = knownRegistrationTransports.singleOrNull() ?: allowedTransports.singleOrNull() + // We do not control if preselectedTransport is in allowedTransports, or if + // allowedTransports is empty, as allowedTransports is a *hint*. + // This is particularly useful if the application sends a credential with + // transport=internal, but we login with another device (via hybrid connection) + val preselectedTransport = preselectedTransport + ?: knownRegistrationTransports.singleOrNull() ?: allowedTransports.singleOrNull() if (database.wasUsed()) { when (preselectedTransport) { USB -> R.id.usbFragment @@ -397,6 +406,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback { const val KEY_OPTIONS = "options" const val KEY_USER_JSON = "userInfo" const val KEY_CREDENTIAL_ID = "credential" + const val KEY_PRESELECTED_TRANSPORT = "transport" val REQUIRED_EXTRAS = setOf(KEY_SOURCE, KEY_TYPE, KEY_OPTIONS) const val SOURCE_BROWSER = "browser"