blob: 051a392b37235978b607a19ecec478e53ed6e1fc [file] [log] [blame]
/*
* 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);
}
}
}