Add option to allow key validity after fingerprint enrollment.
Bug: 21563854
Change-Id: I4f601e59fbfcd601e6a80ddcbc7b83ced6cc18c8
diff --git a/api/current.txt b/api/current.txt
index 3a371cd..fd47ecd 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -34092,6 +34092,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -34109,6 +34110,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateSubject(javax.security.auth.x500.X500Principal);
method public android.security.keystore.KeyGenParameterSpec.Builder setDigests(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setEncryptionPaddings(java.lang.String...);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setInvalidatedByBiometricEnrollment(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeySize(int);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityEnd(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date);
@@ -34135,6 +34137,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isInsideSecureHardware();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -34198,6 +34201,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -34209,6 +34213,7 @@
method public android.security.keystore.KeyProtection.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setDigests(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setEncryptionPaddings(java.lang.String...);
+ method public android.security.keystore.KeyProtection.Builder setInvalidatedByBiometricEnrollment(boolean);
method public android.security.keystore.KeyProtection.Builder setKeyValidityEnd(java.util.Date);
method public android.security.keystore.KeyProtection.Builder setKeyValidityForConsumptionEnd(java.util.Date);
method public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
diff --git a/api/system-current.txt b/api/system-current.txt
index 7ed4742..d4bb2b4 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -36581,6 +36581,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -36598,6 +36599,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateSubject(javax.security.auth.x500.X500Principal);
method public android.security.keystore.KeyGenParameterSpec.Builder setDigests(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setEncryptionPaddings(java.lang.String...);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setInvalidatedByBiometricEnrollment(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeySize(int);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityEnd(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date);
@@ -36624,6 +36626,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isInsideSecureHardware();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -36687,6 +36690,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -36698,6 +36702,7 @@
method public android.security.keystore.KeyProtection.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setDigests(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setEncryptionPaddings(java.lang.String...);
+ method public android.security.keystore.KeyProtection.Builder setInvalidatedByBiometricEnrollment(boolean);
method public android.security.keystore.KeyProtection.Builder setKeyValidityEnd(java.util.Date);
method public android.security.keystore.KeyProtection.Builder setKeyValidityForConsumptionEnd(java.util.Date);
method public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
diff --git a/api/test-current.txt b/api/test-current.txt
index cf7563c..f08d261 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34107,6 +34107,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -34124,6 +34125,7 @@
method public android.security.keystore.KeyGenParameterSpec.Builder setCertificateSubject(javax.security.auth.x500.X500Principal);
method public android.security.keystore.KeyGenParameterSpec.Builder setDigests(java.lang.String...);
method public android.security.keystore.KeyGenParameterSpec.Builder setEncryptionPaddings(java.lang.String...);
+ method public android.security.keystore.KeyGenParameterSpec.Builder setInvalidatedByBiometricEnrollment(boolean);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeySize(int);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityEnd(java.util.Date);
method public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForConsumptionEnd(java.util.Date);
@@ -34150,6 +34152,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isInsideSecureHardware();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationRequirementEnforcedBySecureHardware();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -34213,6 +34216,7 @@
method public java.lang.String[] getSignaturePaddings();
method public int getUserAuthenticationValidityDurationSeconds();
method public boolean isDigestsSpecified();
+ method public boolean isInvalidatedByBiometricEnrollment();
method public boolean isRandomizedEncryptionRequired();
method public boolean isUserAuthenticationRequired();
method public boolean isUserAuthenticationValidWhileOnBody();
@@ -34224,6 +34228,7 @@
method public android.security.keystore.KeyProtection.Builder setBlockModes(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setDigests(java.lang.String...);
method public android.security.keystore.KeyProtection.Builder setEncryptionPaddings(java.lang.String...);
+ method public android.security.keystore.KeyProtection.Builder setInvalidatedByBiometricEnrollment(boolean);
method public android.security.keystore.KeyProtection.Builder setKeyValidityEnd(java.util.Date);
method public android.security.keystore.KeyProtection.Builder setKeyValidityForConsumptionEnd(java.util.Date);
method public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date);
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 1321a83..b234d0f 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -234,7 +234,8 @@
KeymasterUtils.addUserAuthArgs(new KeymasterArguments(),
spec.isUserAuthenticationRequired(),
spec.getUserAuthenticationValidityDurationSeconds(),
- spec.isUserAuthenticationValidWhileOnBody());
+ spec.isUserAuthenticationValidWhileOnBody(),
+ spec.isInvalidatedByBiometricEnrollment());
} catch (IllegalStateException | IllegalArgumentException e) {
throw new InvalidAlgorithmParameterException(e);
}
@@ -273,7 +274,8 @@
KeymasterUtils.addUserAuthArgs(args,
spec.isUserAuthenticationRequired(),
spec.getUserAuthenticationValidityDurationSeconds(),
- spec.isUserAuthenticationValidWhileOnBody());
+ spec.isUserAuthenticationValidWhileOnBody(),
+ spec.isInvalidatedByBiometricEnrollment());
KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
args,
mKeymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 830402a..1818f52 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -345,7 +345,8 @@
KeymasterUtils.addUserAuthArgs(new KeymasterArguments(),
mSpec.isUserAuthenticationRequired(),
mSpec.getUserAuthenticationValidityDurationSeconds(),
- mSpec.isUserAuthenticationValidWhileOnBody());
+ mSpec.isUserAuthenticationValidWhileOnBody(),
+ mSpec.isInvalidatedByBiometricEnrollment());
} catch (IllegalArgumentException | IllegalStateException e) {
throw new InvalidAlgorithmParameterException(e);
}
@@ -531,7 +532,8 @@
KeymasterUtils.addUserAuthArgs(args,
mSpec.isUserAuthenticationRequired(),
mSpec.getUserAuthenticationValidityDurationSeconds(),
- mSpec.isUserAuthenticationValidWhileOnBody());
+ mSpec.isUserAuthenticationValidWhileOnBody(),
+ mSpec.isInvalidatedByBiometricEnrollment());
args.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME, mSpec.getKeyValidityStart());
args.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
mSpec.getKeyValidityForOriginationEnd());
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
index 5f5f2c2..40298ad 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSecretKeyFactorySpi.java
@@ -17,10 +17,12 @@
package android.security.keystore;
import android.security.Credentials;
+import android.security.GateKeeper;
import android.security.KeyStore;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterDefs;
+import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.ProviderException;
import java.security.spec.InvalidKeySpecException;
@@ -170,6 +172,16 @@
boolean userAuthenticationValidWhileOnBody =
keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY);
+ boolean invalidatedByBiometricEnrollment = false;
+ if (keyCharacteristics.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE)
+ == KeymasterDefs.HW_AUTH_FINGERPRINT) {
+ // Fingerprint-only key; will be invalidated if the root SID isn't in the list.
+ BigInteger rootSid = BigInteger.valueOf(GateKeeper.getSecureUserId());
+ List<BigInteger> sids = keyCharacteristics.getUnsignedLongs(
+ KeymasterDefs.KM_TAG_USER_SECURE_ID);
+ invalidatedByBiometricEnrollment = !sids.isEmpty() && !sids.contains(rootSid);
+ }
+
return new KeyInfo(entryAlias,
insideSecureHardware,
origin,
@@ -185,7 +197,8 @@
userAuthenticationRequired,
(int) userAuthenticationValidityDurationSeconds,
userAuthenticationRequirementEnforcedBySecureHardware,
- userAuthenticationValidWhileOnBody);
+ userAuthenticationValidWhileOnBody,
+ invalidatedByBiometricEnrollment);
}
@Override
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
index d660020..d7d4f1c 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java
@@ -499,7 +499,8 @@
KeymasterUtils.addUserAuthArgs(importArgs,
spec.isUserAuthenticationRequired(),
spec.getUserAuthenticationValidityDurationSeconds(),
- spec.isUserAuthenticationValidWhileOnBody());
+ spec.isUserAuthenticationValidWhileOnBody(),
+ spec.isInvalidatedByBiometricEnrollment());
importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ACTIVE_DATETIME,
spec.getKeyValidityStart());
importArgs.addDateIfNotNull(KeymasterDefs.KM_TAG_ORIGINATION_EXPIRE_DATETIME,
@@ -694,7 +695,8 @@
KeymasterUtils.addUserAuthArgs(args,
params.isUserAuthenticationRequired(),
params.getUserAuthenticationValidityDurationSeconds(),
- params.isUserAuthenticationValidWhileOnBody());
+ params.isUserAuthenticationValidWhileOnBody(),
+ params.isInvalidatedByBiometricEnrollment());
KeymasterUtils.addMinMacLengthAuthorizationIfNecessary(
args,
keymasterAlgorithm,
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index a84e7f34..127d756 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -253,6 +253,7 @@
private final byte[] mAttestationChallenge;
private final boolean mUniqueIdIncluded;
private final boolean mUserAuthenticationValidWhileOnBody;
+ private final boolean mInvalidatedByBiometricEnrollment;
/**
* @hide should be built with Builder
@@ -279,7 +280,8 @@
int userAuthenticationValidityDurationSeconds,
byte[] attestationChallenge,
boolean uniqueIdIncluded,
- boolean userAuthenticationValidWhileOnBody) {
+ boolean userAuthenticationValidWhileOnBody,
+ boolean invalidatedByBiometricEnrollment) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -324,6 +326,7 @@
mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge);
mUniqueIdIncluded = uniqueIdIncluded;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
+ mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
}
/**
@@ -607,6 +610,19 @@
}
/**
+ * Returns {@code true} if the key is irreversibly invalidated when a new fingerprint is
+ * enrolled or all enrolled fingerprints are removed. This has effect only for keys that
+ * require fingerprint user authentication for every use.
+ *
+ * @see #isUserAuthenticationRequired()
+ * @see #getUserAuthenticationValidityDurationSeconds()
+ * @see Builder#setInvalidatedByBiometricEnrollment(boolean)
+ */
+ public boolean isInvalidatedByBiometricEnrollment() {
+ return mInvalidatedByBiometricEnrollment;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -633,6 +649,7 @@
private byte[] mAttestationChallenge = null;
private boolean mUniqueIdIncluded = false;
private boolean mUserAuthenticationValidWhileOnBody;
+ private boolean mInvalidatedByBiometricEnrollment = true;
/**
* Creates a new instance of the {@code Builder}.
@@ -966,8 +983,10 @@
* or when the secure lock screen is forcibly reset (e.g., by a Device Administrator).
* Additionally, if the key requires that user authentication takes place for every use of
* the key, it is also irreversibly invalidated once a new fingerprint is enrolled or once\
- * no more fingerprints are enrolled. Attempts to initialize cryptographic operations using
- * such keys will throw {@link KeyPermanentlyInvalidatedException}.</li>
+ * no more fingerprints are enrolled, unless {@link
+ * #setInvalidatedByBiometricEnrollment(boolean)} is used to allow validity after
+ * enrollment. Attempts to initialize cryptographic operations using such keys will throw
+ * {@link KeyPermanentlyInvalidatedException}.</li>
* </ul>
*
* <p>This authorization applies only to secret key and private key operations. Public key
@@ -1110,6 +1129,30 @@
}
/**
+ * Sets whether this key should be invalidated on fingerprint enrollment. This
+ * applies only to keys which require user authentication (see {@link
+ * #setUserAuthenticationRequired(boolean)}) and if no positive validity duration has been
+ * set (see {@link #setUserAuthenticationValidityDurationSeconds(int)}, meaning the key is
+ * valid for fingerprint authentication only.
+ *
+ * <p>By default, {@code invalidateKey} is {@code true}, so keys that are valid for
+ * fingerprint authentication only are <em>irreversibly invalidated</em> when a new
+ * fingerprint is enrolled, or when all existing fingerprints are deleted. That may be
+ * changed by calling this method with {@code invalidateKey} set to {@code false}.
+ *
+ * <p>Invalidating keys on enrollment of a new finger or unenrollment of all fingers
+ * improves security by ensuring that an unauthorized person who obtains the password can't
+ * gain the use of fingerprint-authenticated keys by enrolling their own finger. However,
+ * invalidating keys makes key-dependent operations impossible, requiring some fallback
+ * procedure to authenticate the user and set up a new key.
+ */
+ @NonNull
+ public Builder setInvalidatedByBiometricEnrollment(boolean invalidateKey) {
+ mInvalidatedByBiometricEnrollment = invalidateKey;
+ return this;
+ }
+
+ /**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@NonNull
@@ -1136,7 +1179,8 @@
mUserAuthenticationValidityDurationSeconds,
mAttestationChallenge,
mUniqueIdIncluded,
- mUserAuthenticationValidWhileOnBody);
+ mUserAuthenticationValidWhileOnBody,
+ mInvalidatedByBiometricEnrollment);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeyInfo.java b/keystore/java/android/security/keystore/KeyInfo.java
index f77b5ba..fa6d8b3 100644
--- a/keystore/java/android/security/keystore/KeyInfo.java
+++ b/keystore/java/android/security/keystore/KeyInfo.java
@@ -80,6 +80,7 @@
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware;
private final boolean mUserAuthenticationValidWhileOnBody;
+ private final boolean mInvalidatedByBiometricEnrollment;
/**
* @hide
@@ -99,7 +100,8 @@
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
boolean userAuthenticationRequirementEnforcedBySecureHardware,
- boolean userAuthenticationValidWhileOnBody) {
+ boolean userAuthenticationValidWhileOnBody,
+ boolean invalidatedByBiometricEnrollment) {
mKeystoreAlias = keystoreKeyAlias;
mInsideSecureHardware = insideSecureHardware;
mOrigin = origin;
@@ -119,6 +121,7 @@
mUserAuthenticationRequirementEnforcedBySecureHardware =
userAuthenticationRequirementEnforcedBySecureHardware;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
+ mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
}
/**
@@ -290,4 +293,12 @@
public boolean isUserAuthenticationValidWhileOnBody() {
return mUserAuthenticationValidWhileOnBody;
}
+
+ /**
+ * Returns {@code true} if the key will be invalidated by enrollment of a new fingerprint or
+ * removal of all fingerprints.
+ */
+ public boolean isInvalidatedByBiometricEnrollment() {
+ return mInvalidatedByBiometricEnrollment;
+ }
}
diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java
index 4700b68..fa57bdb 100644
--- a/keystore/java/android/security/keystore/KeyProtection.java
+++ b/keystore/java/android/security/keystore/KeyProtection.java
@@ -215,6 +215,7 @@
private final boolean mUserAuthenticationRequired;
private final int mUserAuthenticationValidityDurationSeconds;
private final boolean mUserAuthenticationValidWhileOnBody;
+ private final boolean mInvalidatedByBiometricEnrollment;
private KeyProtection(
Date keyValidityStart,
@@ -228,7 +229,8 @@
boolean randomizedEncryptionRequired,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
- boolean userAuthenticationValidWhileOnBody) {
+ boolean userAuthenticationValidWhileOnBody,
+ boolean invalidatedByBiometricEnrollment) {
mKeyValidityStart = Utils.cloneIfNotNull(keyValidityStart);
mKeyValidityForOriginationEnd = Utils.cloneIfNotNull(keyValidityForOriginationEnd);
mKeyValidityForConsumptionEnd = Utils.cloneIfNotNull(keyValidityForConsumptionEnd);
@@ -243,6 +245,7 @@
mUserAuthenticationRequired = userAuthenticationRequired;
mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds;
mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody;
+ mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment;
}
/**
@@ -412,6 +415,19 @@
}
/**
+ * Returns {@code true} if the key is irreversibly invalidated when a new fingerprint is
+ * enrolled or all enrolled fingerprints are removed. This has effect only for keys that
+ * require fingerprint user authentication for every use.
+ *
+ * @see #isUserAuthenticationRequired()
+ * @see #getUserAuthenticationValidityDurationSeconds()
+ * @see Builder#setInvalidatedByBiometricEnrollment(boolean)
+ */
+ public boolean isInvalidatedByBiometricEnrollment() {
+ return mInvalidatedByBiometricEnrollment;
+ }
+
+ /**
* Builder of {@link KeyProtection} instances.
*/
public final static class Builder {
@@ -428,6 +444,7 @@
private boolean mUserAuthenticationRequired;
private int mUserAuthenticationValidityDurationSeconds = -1;
private boolean mUserAuthenticationValidWhileOnBody;
+ private boolean mInvalidatedByBiometricEnrollment = true;
/**
* Creates a new instance of the {@code Builder}.
@@ -638,9 +655,10 @@
* or when the secure lock screen is forcibly reset (e.g., by a Device Administrator).
* Additionally, if the key requires that user authentication takes place for every use of
* the key, it is also irreversibly invalidated once a new fingerprint is enrolled or once\
- * no more fingerprints are enrolled. Attempts to initialize cryptographic operations using
- * such keys will throw {@link KeyPermanentlyInvalidatedException}.</li>
- * </ul>
+ * no more fingerprints are enrolled, unless {@link
+ * #setInvalidatedByBiometricEnrollment(boolean)} is used to allow validity after
+ * enrollment. Attempts to initialize cryptographic operations using such keys will throw
+ * {@link KeyPermanentlyInvalidatedException}.</li> </ul>
*
* <p>This authorization applies only to secret key and private key operations. Public key
* operations are not restricted.
@@ -729,6 +747,30 @@
}
/**
+ * Sets whether this key should be invalidated on fingerprint enrollment. This
+ * applies only to keys which require user authentication (see {@link
+ * #setUserAuthenticationRequired(boolean)}) and if no positive validity duration has been
+ * set (see {@link #setUserAuthenticationValidityDurationSeconds(int)}, meaning the key is
+ * valid for fingerprint authentication only.
+ *
+ * <p>By default, {@code invalidateKey} is {@code true}, so keys that are valid for
+ * fingerprint authentication only are <em>irreversibly invalidated</em> when a new
+ * fingerprint is enrolled, or when all existing fingerprints are deleted. That may be
+ * changed by calling this method with {@code invalidateKey} set to {@code false}.
+ *
+ * <p>Invalidating keys on enrollment of a new finger or unenrollment of all fingers
+ * improves security by ensuring that an unauthorized person who obtains the password can't
+ * gain the use of fingerprint-authenticated keys by enrolling their own finger. However,
+ * invalidating keys makes key-dependent operations impossible, requiring some fallback
+ * procedure to authenticate the user and set up a new key.
+ */
+ @NonNull
+ public Builder setInvalidatedByBiometricEnrollment(boolean invalidateKey) {
+ mInvalidatedByBiometricEnrollment = invalidateKey;
+ return this;
+ }
+
+ /**
* Builds an instance of {@link KeyProtection}.
*
* @throws IllegalArgumentException if a required field is missing
@@ -747,7 +789,8 @@
mRandomizedEncryptionRequired,
mUserAuthenticationRequired,
mUserAuthenticationValidityDurationSeconds,
- mUserAuthenticationValidWhileOnBody);
+ mUserAuthenticationValidWhileOnBody,
+ mInvalidatedByBiometricEnrollment);
}
}
}
diff --git a/keystore/java/android/security/keystore/KeymasterUtils.java b/keystore/java/android/security/keystore/KeymasterUtils.java
index 3a008bc..f5272aa 100644
--- a/keystore/java/android/security/keystore/KeymasterUtils.java
+++ b/keystore/java/android/security/keystore/KeymasterUtils.java
@@ -97,7 +97,8 @@
public static void addUserAuthArgs(KeymasterArguments args,
boolean userAuthenticationRequired,
int userAuthenticationValidityDurationSeconds,
- boolean userAuthenticationValidWhileOnBody) {
+ boolean userAuthenticationValidWhileOnBody,
+ boolean invalidatedByBiometricEnrollment) {
if (!userAuthenticationRequired) {
args.addBoolean(KeymasterDefs.KM_TAG_NO_AUTH_REQUIRED);
return;
@@ -117,8 +118,20 @@
"At least one fingerprint must be enrolled to create keys requiring user"
+ " authentication for every use");
}
- args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
- KeymasterArguments.toUint64(fingerprintOnlySid));
+
+ long sid;
+ if (invalidatedByBiometricEnrollment) {
+ // The fingerprint-only SID will change on fingerprint enrollment or removal of all,
+ // enrolled fingerprints, invalidating the key.
+ sid = fingerprintOnlySid;
+ } else {
+ // The root SID will *not* change on fingerprint enrollment, or removal of all
+ // enrolled fingerprints, allowing the key to remain valid.
+ sid = getRootSid();
+ }
+
+ args.addUnsignedLong(
+ KeymasterDefs.KM_TAG_USER_SECURE_ID, KeymasterArguments.toUint64(sid));
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, KeymasterDefs.HW_AUTH_FINGERPRINT);
if (userAuthenticationValidWhileOnBody) {
throw new ProviderException("Key validity extension while device is on-body is not "
@@ -127,11 +140,7 @@
} else {
// The key is authorized for use for the specified amount of time after the user has
// authenticated. Whatever unlocks the secure lock screen should authorize this key.
- long rootSid = GateKeeper.getSecureUserId();
- if (rootSid == 0) {
- throw new IllegalStateException("Secure lock screen must be enabled"
- + " to create keys requiring user authentication");
- }
+ long rootSid = getRootSid();
args.addUnsignedLong(KeymasterDefs.KM_TAG_USER_SECURE_ID,
KeymasterArguments.toUint64(rootSid));
args.addEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE,
@@ -184,4 +193,13 @@
break;
}
}
+
+ private static long getRootSid() {
+ long rootSid = GateKeeper.getSecureUserId();
+ if (rootSid == 0) {
+ throw new IllegalStateException("Secure lock screen must be enabled"
+ + " to create keys requiring user authentication");
+ }
+ return rootSid;
+ }
}