diff --git a/identity-android/src/main/java/com/android/identity/android/securearea/AndroidKeystoreSecureArea.java b/identity-android/src/main/java/com/android/identity/android/securearea/AndroidKeystoreSecureArea.java index daa040ecc..cd24f54b0 100644 --- a/identity-android/src/main/java/com/android/identity/android/securearea/AndroidKeystoreSecureArea.java +++ b/identity-android/src/main/java/com/android/identity/android/securearea/AndroidKeystoreSecureArea.java @@ -43,6 +43,7 @@ import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyFactory; +import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.KeyStore; import java.security.KeyStoreException; @@ -130,6 +131,8 @@ public class AndroidKeystoreSecureArea implements SecureArea { * Flag indicating that authentication is needed using the user's biometric. */ public static final int USER_AUTHENTICATION_TYPE_BIOMETRIC = 1<<1; + private final int mKeymintTeeFeatureLevel; + private final int mKeymintSbFeatureLevel; /** * An annotation used to indicate different types of user authentication. @@ -152,6 +155,8 @@ public AndroidKeystoreSecureArea(@NonNull Context context, @NonNull StorageEngine storageEngine) { mContext = context; mStorageEngine = storageEngine; + mKeymintTeeFeatureLevel = getFeatureVersionKeystore(context, false); + mKeymintSbFeatureLevel = getFeatureVersionKeystore(context, true); } @Override @@ -179,6 +184,23 @@ public void createKey(@NonNull String alias, throw new IllegalArgumentException( "PURPOSE_AGREE_KEY not supported on this device"); } + + // Android KeyStore tries to be "helpful" by creating keys in Software if + // the Secure World (Keymint) lacks support for the requested feature, for + // example ECDH. This will never work (for RWI, the credential issuer will + // detect it when examining the attestation) so just bail early if this + // is the case. + if (aSettings.getUseStrongBox()) { + if (mKeymintSbFeatureLevel < 100) { + throw new IllegalArgumentException("PURPOSE_AGREE_KEY not supported on " + + "this StrongBox KeyMint version"); + } + } else { + if (mKeymintTeeFeatureLevel < 100) { + throw new IllegalArgumentException("PURPOSE_AGREE_KEY not supported on " + + "this KeyMint version"); + } + } } KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(alias, purposes); @@ -274,7 +296,6 @@ public void createKey(@NonNull String alias, throw new IllegalStateException(e.getMessage(), e); } kpg.generateKeyPair(); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { throw new IllegalStateException("Error creating key", e); @@ -1151,6 +1172,42 @@ public Builder(@NonNull byte[] attestationChallenge) { } + private static int getFeatureVersionKeystore(@NonNull Context appContext, boolean useStrongbox) { + String feature = PackageManager.FEATURE_HARDWARE_KEYSTORE; + if (useStrongbox) { + feature = PackageManager.FEATURE_STRONGBOX_KEYSTORE; + } + PackageManager pm = appContext.getPackageManager(); + if (pm.hasSystemFeature(feature)) { + FeatureInfo info = null; + FeatureInfo[] infos = pm.getSystemAvailableFeatures(); + for (int n = 0; n < infos.length; n++) { + FeatureInfo i = infos[n]; + if (i.name.equals(feature)) { + info = i; + break; + } + } + int version = 0; + if (info != null) { + version = info.version; + } + // It's entirely possible that the feature exists but the version number hasn't + // been set. In that case, assume it's at least KeyMaster 4.1. + if (version < 41) { + version = 41; + } + return version; + } + // It's only a requirement to set PackageManager.FEATURE_HARDWARE_KEYSTORE since + // Android 12 so for old devices this isn't set. However all devices since Android + // 8.1 has had HW-backed keystore so in this case we can report KeyMaster 4.1 + if (!useStrongbox) { + return 41; + } + return 0; + } + /** * Helper class to determine capabilities of the device. * @@ -1163,30 +1220,6 @@ public static class Capabilities { private final int mTeeFeatureLevel; private final int mSbFeatureLevel; - private static int getFeatureVersionKeystore(@NonNull Context appContext, boolean useStrongbox) { - String feature = PackageManager.FEATURE_HARDWARE_KEYSTORE; - if (useStrongbox) { - feature = PackageManager.FEATURE_STRONGBOX_KEYSTORE; - } - PackageManager pm = appContext.getPackageManager(); - int featureVersionFromPm = 0; - if (pm.hasSystemFeature(feature)) { - FeatureInfo info = null; - FeatureInfo[] infos = pm.getSystemAvailableFeatures(); - for (int n = 0; n < infos.length; n++) { - FeatureInfo i = infos[n]; - if (i.name.equals(feature)) { - info = i; - break; - } - } - if (info != null) { - featureVersionFromPm = info.version; - } - } - return featureVersionFromPm; - } - /** * Construct a new Capabilities object. * diff --git a/secure-area-test-app/src/main/java/com/android/identity/secure_area_test_app/MainActivity.kt b/secure-area-test-app/src/main/java/com/android/identity/secure_area_test_app/MainActivity.kt index f7bb87d89..5b85db16e 100644 --- a/secure-area-test-app/src/main/java/com/android/identity/secure_area_test_app/MainActivity.kt +++ b/secure-area-test-app/src/main/java/com/android/identity/secure_area_test_app/MainActivity.kt @@ -163,7 +163,6 @@ class MainActivity : FragmentActivity() { feature = PackageManager.FEATURE_STRONGBOX_KEYSTORE } val pm = appContext.packageManager - var featureVersionFromPm = 0 if (pm.hasSystemFeature(feature)) { var info: FeatureInfo? = null val infos = pm.systemAvailableFeatures @@ -174,11 +173,24 @@ class MainActivity : FragmentActivity() { break } } + var version = 0 if (info != null) { - featureVersionFromPm = info.version + version = info.version } + // It's entirely possible that the feature exists but the version number hasn't + // been set. In that case, assume it's at least KeyMaster 4.1. + if (version < 41) { + version = 41 + } + return version + } + // It's only a requirement to set PackageManager.FEATURE_HARDWARE_KEYSTORE since + // Android 12 so for old devices this isn't set. However all devices since Android + // 8.1 has had HW-backed keystore so in this case we can report KeyMaster 4.1 + if (!useStrongbox) { + return 41 } - return featureVersionFromPm + return 0 } override fun onCreate(savedInstanceState: Bundle?) { @@ -504,6 +516,28 @@ class MainActivity : FragmentActivity() { } } + // Unfortunately this is API is only available to system apps so we + // have to use reflection to use it. + private fun getFirstApiLevel(): Int { + try { + val c = Class.forName("android.os.SystemProperties") + val get = c.getMethod("get", String::class.java) + val firstApiLevelString = get.invoke(c, "ro.product.first_api_level") as String + return firstApiLevelString.toInt() + } catch (e: java.lang.Exception) { + Logger.w(TAG, "Error getting ro.product.first_api_level", e) + return 0 + } + } + + private fun getNameForApiLevel(apiLevel: Int): String { + val fields = Build.VERSION_CODES::class.java.fields + var codeName = "UNKNOWN" + fields.filter { it.getInt(Build.VERSION_CODES::class) == apiLevel } + .forEach { codeName = it.name } + return codeName + } + @Composable fun ShowCapabilitiesDialog(capabilities: AndroidKeystoreSecureArea.Capabilities, onDismissRequest: () -> Unit) { @@ -530,9 +564,12 @@ class MainActivity : FragmentActivity() { .size(400.dp) .verticalScroll(rememberScrollState()) ) { + val apiLevel = Build.VERSION.SDK_INT + val firstApiLevel = getFirstApiLevel() // Would be nice to show first API level but that's only available to tests. Text( - text = "API Level: ${Build.VERSION.SDK_INT}\n" + + text = "API Level: ${apiLevel} (${getNameForApiLevel(apiLevel)})\n" + + "First API Level: ${firstApiLevel} (${getNameForApiLevel(firstApiLevel)})\n" + "TEE KeyMint version: ${keymintVersionTee}\n" + "StrongBox KeyMint version: ${keymintVersionStrongBox}", modifier = Modifier.padding(8.dp),