| /* |
| * Copyright 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package androidx.biometric; |
| |
| import android.os.Build; |
| import android.security.keystore.KeyGenParameterSpec; |
| import android.security.keystore.KeyProperties; |
| import android.util.Log; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RequiresApi; |
| |
| import java.io.IOException; |
| import java.security.InvalidAlgorithmParameterException; |
| import java.security.InvalidKeyException; |
| import java.security.KeyStore; |
| import java.security.KeyStoreException; |
| import java.security.NoSuchAlgorithmException; |
| import java.security.NoSuchProviderException; |
| import java.security.Signature; |
| import java.security.UnrecoverableKeyException; |
| import java.security.cert.CertificateException; |
| import java.security.spec.AlgorithmParameterSpec; |
| |
| import javax.crypto.Cipher; |
| import javax.crypto.KeyGenerator; |
| import javax.crypto.Mac; |
| import javax.crypto.NoSuchPaddingException; |
| import javax.crypto.SecretKey; |
| |
| /** |
| * Utility class for creating and converting between different types of crypto objects that may be |
| * used internally by {@link BiometricPrompt} and {@link BiometricManager}. |
| */ |
| class CryptoObjectUtils { |
| private static final String TAG = "CryptoObjectUtils"; |
| |
| /** |
| * The key name used when creating a fake crypto object. |
| */ |
| private static final String FAKE_KEY_NAME = "androidxBiometric"; |
| |
| /** |
| * The name of the Android keystore instance. |
| */ |
| private static final String KEYSTORE_INSTANCE = "AndroidKeyStore"; |
| |
| // Prevent instantiation. |
| private CryptoObjectUtils() {} |
| |
| /** |
| * Unwraps a crypto object returned by {@link android.hardware.biometrics.BiometricPrompt}. |
| * |
| * @param cryptoObject A crypto object from {@link android.hardware.biometrics.BiometricPrompt}. |
| * @return An equivalent {@link androidx.biometric.BiometricPrompt.CryptoObject} instance. |
| */ |
| @RequiresApi(Build.VERSION_CODES.P) |
| @Nullable |
| static BiometricPrompt.CryptoObject unwrapFromBiometricPrompt( |
| @Nullable android.hardware.biometrics.BiometricPrompt.CryptoObject cryptoObject) { |
| |
| if (cryptoObject == null) { |
| return null; |
| } |
| |
| final Cipher cipher = Api28Impl.getCipher(cryptoObject); |
| if (cipher != null) { |
| return new BiometricPrompt.CryptoObject(cipher); |
| } |
| |
| final Signature signature = Api28Impl.getSignature(cryptoObject); |
| if (signature != null) { |
| return new BiometricPrompt.CryptoObject(signature); |
| } |
| |
| final Mac mac = Api28Impl.getMac(cryptoObject); |
| if (mac != null) { |
| return new BiometricPrompt.CryptoObject(mac); |
| } |
| |
| // Identity credential is only supported on API 30 and above. |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { |
| final android.security.identity.IdentityCredential identityCredential = |
| Api30Impl.getIdentityCredential(cryptoObject); |
| if (identityCredential != null) { |
| return new BiometricPrompt.CryptoObject(identityCredential); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Wraps a crypto object to be passed to {@link android.hardware.biometrics.BiometricPrompt}. |
| * |
| * @param cryptoObject An instance of {@link androidx.biometric.BiometricPrompt.CryptoObject}. |
| * @return An equivalent crypto object that is compatible with |
| * {@link android.hardware.biometrics.BiometricPrompt}. |
| */ |
| @RequiresApi(Build.VERSION_CODES.P) |
| @Nullable |
| static android.hardware.biometrics.BiometricPrompt.CryptoObject |
| wrapForBiometricPrompt(@Nullable BiometricPrompt.CryptoObject cryptoObject) { |
| |
| if (cryptoObject == null) { |
| return null; |
| } |
| |
| final Cipher cipher = cryptoObject.getCipher(); |
| if (cipher != null) { |
| return Api28Impl.create(cipher); |
| } |
| |
| final Signature signature = cryptoObject.getSignature(); |
| if (signature != null) { |
| return Api28Impl.create(signature); |
| } |
| |
| final Mac mac = cryptoObject.getMac(); |
| if (mac != null) { |
| return Api28Impl.create(mac); |
| } |
| |
| // Identity credential is only supported on API 30 and above. |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { |
| final android.security.identity.IdentityCredential identityCredential = |
| cryptoObject.getIdentityCredential(); |
| if (identityCredential != null) { |
| return Api30Impl.create(identityCredential); |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Unwraps a crypto object returned by |
| * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}. |
| * |
| * @param cryptoObject A crypto object from |
| * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}. |
| * @return An equivalent {@link androidx.biometric.BiometricPrompt.CryptoObject} instance. |
| */ |
| @SuppressWarnings("deprecation") |
| @Nullable |
| static BiometricPrompt.CryptoObject unwrapFromFingerprintManager( |
| @Nullable androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject |
| cryptoObject) { |
| |
| if (cryptoObject == null) { |
| return null; |
| } |
| |
| final Cipher cipher = cryptoObject.getCipher(); |
| if (cipher != null) { |
| return new BiometricPrompt.CryptoObject(cipher); |
| } |
| |
| final Signature signature = cryptoObject.getSignature(); |
| if (signature != null) { |
| return new BiometricPrompt.CryptoObject(signature); |
| } |
| |
| final Mac mac = cryptoObject.getMac(); |
| if (mac != null) { |
| return new BiometricPrompt.CryptoObject(mac); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Wraps a crypto object to be passed to |
| * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}. |
| * |
| * @param cryptoObject An instance of {@link androidx.biometric.BiometricPrompt.CryptoObject}. |
| * @return An equivalent crypto object that is compatible with |
| * {@link androidx.core.hardware.fingerprint.FingerprintManagerCompat}. |
| */ |
| @SuppressWarnings("deprecation") |
| @Nullable |
| static androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject |
| wrapForFingerprintManager(@Nullable BiometricPrompt.CryptoObject cryptoObject) { |
| |
| if (cryptoObject == null) { |
| return null; |
| } |
| |
| final Cipher cipher = cryptoObject.getCipher(); |
| if (cipher != null) { |
| return new androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject( |
| cipher); |
| } |
| |
| final Signature signature = cryptoObject.getSignature(); |
| if (signature != null) { |
| return new androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject( |
| signature); |
| } |
| |
| final Mac mac = cryptoObject.getMac(); |
| if (mac != null) { |
| return new androidx.core.hardware.fingerprint.FingerprintManagerCompat.CryptoObject( |
| mac); |
| } |
| |
| if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R |
| && cryptoObject.getIdentityCredential() != null) { |
| Log.e(TAG, "Identity credential is not supported by FingerprintManager."); |
| return null; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Creates a {@link androidx.biometric.BiometricPrompt.CryptoObject} instance that can be passed |
| * to {@link BiometricManager} and {@link BiometricPrompt} in order to force crypto-based |
| * authentication behavior. |
| * |
| * @return An internal-only instance of {@link androidx.biometric.BiometricPrompt.CryptoObject}. |
| */ |
| @RequiresApi(Build.VERSION_CODES.M) |
| @Nullable |
| static BiometricPrompt.CryptoObject createFakeCryptoObject() { |
| try { |
| final KeyStore keystore = KeyStore.getInstance(KEYSTORE_INSTANCE); |
| keystore.load(null); |
| |
| final KeyGenParameterSpec.Builder keySpecBuilder = |
| Api23Impl.createKeyGenParameterSpecBuilder( |
| FAKE_KEY_NAME, |
| KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT); |
| Api23Impl.setBlockModeCBC(keySpecBuilder); |
| Api23Impl.setEncryptionPaddingPKCS7(keySpecBuilder); |
| |
| final KeyGenerator keyGenerator = |
| KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_INSTANCE); |
| final KeyGenParameterSpec keySpec = Api23Impl.buildKeyGenParameterSpec(keySpecBuilder); |
| Api23Impl.initKeyGenerator(keyGenerator, keySpec); |
| keyGenerator.generateKey(); |
| |
| final SecretKey secretKey = |
| (SecretKey) keystore.getKey(FAKE_KEY_NAME, null /* password */); |
| final Cipher cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" |
| + KeyProperties.BLOCK_MODE_CBC + "/" |
| + KeyProperties.ENCRYPTION_PADDING_PKCS7); |
| cipher.init(Cipher.ENCRYPT_MODE, secretKey); |
| |
| return new BiometricPrompt.CryptoObject(cipher); |
| } catch (NoSuchPaddingException | NoSuchAlgorithmException | CertificateException |
| | KeyStoreException | InvalidKeyException | InvalidAlgorithmParameterException |
| | UnrecoverableKeyException | IOException | NoSuchProviderException e) { |
| Log.w(TAG, "Failed to create fake crypto object.", e); |
| return null; |
| } |
| } |
| |
| /** |
| * Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28). |
| */ |
| @RequiresApi(Build.VERSION_CODES.R) |
| private static class Api30Impl { |
| // Prevent instantiation. |
| private Api30Impl() {} |
| |
| /** |
| * Creates an instance of the framework class |
| * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} from the given identity |
| * credential. |
| * |
| * @param identityCredential The identity credential object to be wrapped. |
| * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}. |
| */ |
| @NonNull |
| static android.hardware.biometrics.BiometricPrompt.CryptoObject create( |
| @NonNull android.security.identity.IdentityCredential identityCredential) { |
| return new android.hardware.biometrics.BiometricPrompt.CryptoObject(identityCredential); |
| } |
| |
| /** |
| * Gets the identity credential associated with the given crypto object, if any. |
| * |
| * @param crypto An instance of |
| * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}. |
| * @return The wrapped identity credential object, or {@code null}. |
| */ |
| @Nullable |
| static android.security.identity.IdentityCredential getIdentityCredential( |
| @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto) { |
| return crypto.getIdentityCredential(); |
| } |
| } |
| |
| /** |
| * Nested class to avoid verification errors for methods introduced in Android 9.0 (API 28). |
| */ |
| @RequiresApi(Build.VERSION_CODES.P) |
| private static class Api28Impl { |
| // Prevent instantiation. |
| private Api28Impl() {} |
| |
| /** |
| * Creates an instance of the framework class |
| * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} from the given cipher. |
| * |
| * @param cipher The cipher object to be wrapped. |
| * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}. |
| */ |
| @NonNull |
| static android.hardware.biometrics.BiometricPrompt.CryptoObject create( |
| @NonNull Cipher cipher) { |
| return new android.hardware.biometrics.BiometricPrompt.CryptoObject(cipher); |
| } |
| |
| /** |
| * Creates an instance of the framework class |
| * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} from the given |
| * signature. |
| * |
| * @param signature The signature object to be wrapped. |
| * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}. |
| */ |
| @NonNull |
| static android.hardware.biometrics.BiometricPrompt.CryptoObject create( |
| @NonNull Signature signature) { |
| return new android.hardware.biometrics.BiometricPrompt.CryptoObject(signature); |
| } |
| |
| /** |
| * Creates an instance of the framework class |
| * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} from the given MAC. |
| * |
| * @param mac The MAC object to be wrapped. |
| * @return An instance of {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}. |
| */ |
| @NonNull |
| static android.hardware.biometrics.BiometricPrompt.CryptoObject create(@NonNull Mac mac) { |
| return new android.hardware.biometrics.BiometricPrompt.CryptoObject(mac); |
| } |
| |
| /** |
| * Gets the cipher associated with the given crypto object, if any. |
| * |
| * @param crypto An instance of |
| * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}. |
| * @return The wrapped cipher object, or {@code null}. |
| */ |
| @Nullable |
| static Cipher getCipher( |
| @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto) { |
| return crypto.getCipher(); |
| } |
| |
| /** |
| * Gets the signature associated with the given crypto object, if any. |
| * |
| * @param crypto An instance of |
| * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}. |
| * @return The wrapped signature object, or {@code null}. |
| */ |
| @Nullable |
| static Signature getSignature( |
| @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto) { |
| return crypto.getSignature(); |
| } |
| |
| /** |
| * Gets the MAC associated with the given crypto object, if any. |
| * |
| * @param crypto An instance of |
| * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject}. |
| * @return The wrapped MAC object, or {@code null}. |
| */ |
| @Nullable |
| static Mac getMac( |
| @NonNull android.hardware.biometrics.BiometricPrompt.CryptoObject crypto) { |
| return crypto.getMac(); |
| } |
| } |
| |
| /** |
| * Nested class to avoid verification errors for methods introduced in Android 6.0 (API 23). |
| */ |
| @RequiresApi(Build.VERSION_CODES.M) |
| private static class Api23Impl { |
| // Prevent instantiation. |
| private Api23Impl() {} |
| |
| /** |
| * Creates a new instance of {@link KeyGenParameterSpec.Builder}. |
| * |
| * @param keystoreAlias The keystore alias for the resulting key. |
| * @param purposes The purposes for which the resulting key will be used. |
| * @return An instance of {@link KeyGenParameterSpec.Builder}. |
| */ |
| @SuppressWarnings("SameParameterValue") |
| @NonNull |
| static KeyGenParameterSpec.Builder createKeyGenParameterSpecBuilder( |
| @NonNull String keystoreAlias, int purposes) { |
| return new KeyGenParameterSpec.Builder(keystoreAlias, purposes); |
| } |
| |
| /** |
| * Sets CBC block mode for the given key spec builder. |
| * |
| * @param keySpecBuilder An instance of {@link KeyGenParameterSpec.Builder}. |
| */ |
| static void setBlockModeCBC(@NonNull KeyGenParameterSpec.Builder keySpecBuilder) { |
| keySpecBuilder.setBlockModes(KeyProperties.BLOCK_MODE_CBC); |
| } |
| |
| /** |
| * Sets PKCS7 encryption padding for the given key spec builder. |
| * |
| * @param keySpecBuilder An instance of {@link KeyGenParameterSpec.Builder}. |
| */ |
| static void setEncryptionPaddingPKCS7(@NonNull KeyGenParameterSpec.Builder keySpecBuilder) { |
| keySpecBuilder.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7); |
| } |
| |
| /** |
| * Builds a key spec from the given builder. |
| * |
| * @param keySpecBuilder An instance of {@link KeyGenParameterSpec.Builder}. |
| * @return A {@link KeyGenParameterSpec} created from the given builder. |
| */ |
| @NonNull |
| static KeyGenParameterSpec buildKeyGenParameterSpec( |
| @NonNull KeyGenParameterSpec.Builder keySpecBuilder) { |
| return keySpecBuilder.build(); |
| } |
| |
| /** |
| * Calls {@link KeyGenerator#init(AlgorithmParameterSpec)} for the given key generator and |
| * spec. |
| * |
| * @param keyGenerator An instance of {@link KeyGenerator}. |
| * @param keySpec The key spec with which to initialize the generator. |
| * |
| * @throws InvalidAlgorithmParameterException If the key spec is invalid. |
| */ |
| static void initKeyGenerator( |
| @NonNull KeyGenerator keyGenerator, @NonNull KeyGenParameterSpec keySpec) |
| throws InvalidAlgorithmParameterException { |
| keyGenerator.init(keySpec); |
| } |
| } |
| } |