From 8e0aa42f7c921593f8427e4153702bdc1fdc6e24 Mon Sep 17 00:00:00 2001 From: Martin Asprusten Date: Tue, 27 Feb 2024 20:48:06 +0100 Subject: [PATCH] Made the screenlock authenticator try to use StrongBox and attestation if available, by try again without them if it doesn't work --- .../screenlock/ScreenLockCredentialStore.kt | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockCredentialStore.kt b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockCredentialStore.kt index 7a0f1cdb5d..b42f5fb8a8 100644 --- a/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockCredentialStore.kt +++ b/play-services-fido/core/src/main/kotlin/org/microg/gms/fido/core/transport/screenlock/ScreenLockCredentialStore.kt @@ -6,12 +6,14 @@ package org.microg.gms.fido.core.transport.screenlock import android.content.Context +import android.content.pm.PackageManager import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.os.Build.VERSION.SDK_INT import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyPermanentlyInvalidatedException import android.security.keystore.KeyProperties +import android.security.keystore.StrongBoxUnavailableException import android.util.Base64 import android.util.Log import androidx.annotation.RequiresApi @@ -32,6 +34,8 @@ class ScreenLockCredentialStore(val context: Context) { @RequiresApi(23) fun createKey(rpId: String, challenge: ByteArray): ByteArray { + var useStrongbox = false + if (SDK_INT >= 28) useStrongbox = context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE) val keyId = Random.nextBytes(32) val identifier = getAlias(rpId, keyId) Log.d(TAG, "Creating key for $identifier") @@ -40,9 +44,44 @@ class ScreenLockCredentialStore(val context: Context) { .setDigests(KeyProperties.DIGEST_SHA256) .setAlgorithmParameterSpec(ECGenParameterSpec("secp256r1")) .setUserAuthenticationRequired(true) + if (SDK_INT >= 28) builder.setIsStrongBoxBacked(useStrongbox) if (SDK_INT >= 24) builder.setAttestationChallenge(challenge) - generator.initialize(builder.build()) - generator.generateKeyPair() + + var generatedKeypair = false + val exceptionClassesCaught = HashSet>() + while (!generatedKeypair) { + try { + generator.initialize(builder.build()) + generator.generateKeyPair() + generatedKeypair = true + } catch (e: Exception) { + // Catch each exception class at most once. + // If we've caught the exception before, tried to correct it, and still catch the + // same exception, then we can't fix it and the exception should be thrown further + if (exceptionClassesCaught.contains(e.javaClass)) { + throw e + } + exceptionClassesCaught.add(e.javaClass) + + if (SDK_INT >= 28 && e is StrongBoxUnavailableException) { + Log.w(TAG, "Failed with StrongBox, retrying without it...") + // Not all algorithms are backed by the Strongbox. If the Strongbox doesn't + // support this keypair, fall back to TEE + builder.setIsStrongBoxBacked(false) + } else if (SDK_INT >= 24 && e is ProviderException) { + Log.w(TAG, "Failed with attestation challenge, retrying without it...") + // This ProviderException is often thrown if the TEE or Strongbox doesn't have + // a built-in key to attest the new key pair with. If this happens, remove the + // attestation challenge and create an unattested key + builder.setAttestationChallenge(null) + } else { + // We don't know how to handle other errors, so they should be thrown up the + // system + throw e + } + } + } + return keyId }