diff options
11 files changed, 185 insertions, 25 deletions
diff --git a/api/current.txt b/api/current.txt index 45869adf04b2..51e40ecf3432 100644 --- a/api/current.txt +++ b/api/current.txt @@ -34112,6 +34112,7 @@ package android.security.keystore { 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(); @@ -34129,6 +34130,7 @@ package android.security.keystore { 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); @@ -34155,6 +34157,7 @@ package android.security.keystore { 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(); @@ -34218,6 +34221,7 @@ package android.security.keystore { 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(); @@ -34229,6 +34233,7 @@ package android.security.keystore { 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 8345980bf46d..c4748b046c3f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -36608,6 +36608,7 @@ package android.security.keystore { 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(); @@ -36625,6 +36626,7 @@ package android.security.keystore { 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); @@ -36651,6 +36653,7 @@ package android.security.keystore { 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(); @@ -36714,6 +36717,7 @@ package android.security.keystore { 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(); @@ -36725,6 +36729,7 @@ package android.security.keystore { 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 d0650edec264..0c890f02d1bf 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -34127,6 +34127,7 @@ package android.security.keystore { 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(); @@ -34144,6 +34145,7 @@ package android.security.keystore { 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); @@ -34170,6 +34172,7 @@ package android.security.keystore { 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(); @@ -34233,6 +34236,7 @@ package android.security.keystore { 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(); @@ -34244,6 +34248,7 @@ package android.security.keystore { 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 1321a833acad..b234d0f81a89 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java @@ -234,7 +234,8 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { 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 @@ public abstract class AndroidKeyStoreKeyGeneratorSpi extends KeyGeneratorSpi { 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 830402a6a383..1818f52c4fda 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -345,7 +345,8 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato 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 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato 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 5f5f2c244116..0379863e8ca1 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; @@ -91,6 +93,7 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { @KeyProperties.BlockModeEnum String[] blockModes; int keymasterSwEnforcedUserAuthenticators; int keymasterHwEnforcedUserAuthenticators; + List<BigInteger> keymasterSecureUserIds; try { if (keyCharacteristics.hwEnforced.containsTag(KeymasterDefs.KM_TAG_ORIGIN)) { insideSecureHardware = true; @@ -147,6 +150,8 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { keyCharacteristics.swEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); keymasterHwEnforcedUserAuthenticators = keyCharacteristics.hwEnforced.getEnum(KeymasterDefs.KM_TAG_USER_AUTH_TYPE, 0); + keymasterSecureUserIds = + keyCharacteristics.getUnsignedLongs(KeymasterDefs.KM_TAG_USER_SECURE_ID); } catch (IllegalArgumentException e) { throw new ProviderException("Unsupported key characteristic", e); } @@ -170,6 +175,15 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { boolean userAuthenticationValidWhileOnBody = keyCharacteristics.hwEnforced.getBoolean(KeymasterDefs.KM_TAG_ALLOW_WHILE_ON_BODY); + boolean invalidatedByBiometricEnrollment = false; + if (keymasterSwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT + || keymasterHwEnforcedUserAuthenticators == KeymasterDefs.HW_AUTH_FINGERPRINT) { + // Fingerprint-only key; will be invalidated if the root SID isn't in the SID list. + invalidatedByBiometricEnrollment = keymasterSecureUserIds != null + && !keymasterSecureUserIds.isEmpty() + && !keymasterSecureUserIds.contains(getGateKeeperSecureUserId()); + } + return new KeyInfo(entryAlias, insideSecureHardware, origin, @@ -185,7 +199,16 @@ public class AndroidKeyStoreSecretKeyFactorySpi extends SecretKeyFactorySpi { userAuthenticationRequired, (int) userAuthenticationValidityDurationSeconds, userAuthenticationRequirementEnforcedBySecureHardware, - userAuthenticationValidWhileOnBody); + userAuthenticationValidWhileOnBody, + invalidatedByBiometricEnrollment); + } + + private static BigInteger getGateKeeperSecureUserId() throws ProviderException { + try { + return BigInteger.valueOf(GateKeeper.getSecureUserId()); + } catch (IllegalStateException e) { + throw new ProviderException("Failed to get GateKeeper secure user ID", e); + } } @Override diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java index d6600208ecee..d7d4f1c50e32 100644 --- a/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore/AndroidKeyStoreSpi.java @@ -499,7 +499,8 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { 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 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { 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 a84e7f34be8d..127d756a2cff 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -253,6 +253,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { 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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { 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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { mAttestationChallenge = Utils.cloneIfNotNull(attestationChallenge); mUniqueIdIncluded = uniqueIdIncluded; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; + mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; } /** @@ -607,6 +610,19 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } /** + * 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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { 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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { * 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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { } /** + * 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 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec { 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 f77b5bac0363..fa6d8b3517f6 100644 --- a/keystore/java/android/security/keystore/KeyInfo.java +++ b/keystore/java/android/security/keystore/KeyInfo.java @@ -80,6 +80,7 @@ public class KeyInfo implements KeySpec { private final int mUserAuthenticationValidityDurationSeconds; private final boolean mUserAuthenticationRequirementEnforcedBySecureHardware; private final boolean mUserAuthenticationValidWhileOnBody; + private final boolean mInvalidatedByBiometricEnrollment; /** * @hide @@ -99,7 +100,8 @@ public class KeyInfo implements KeySpec { boolean userAuthenticationRequired, int userAuthenticationValidityDurationSeconds, boolean userAuthenticationRequirementEnforcedBySecureHardware, - boolean userAuthenticationValidWhileOnBody) { + boolean userAuthenticationValidWhileOnBody, + boolean invalidatedByBiometricEnrollment) { mKeystoreAlias = keystoreKeyAlias; mInsideSecureHardware = insideSecureHardware; mOrigin = origin; @@ -119,6 +121,7 @@ public class KeyInfo implements KeySpec { mUserAuthenticationRequirementEnforcedBySecureHardware = userAuthenticationRequirementEnforcedBySecureHardware; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; + mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; } /** @@ -290,4 +293,12 @@ public class KeyInfo implements KeySpec { 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 4700b68261db..fa57bdb52b32 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -215,6 +215,7 @@ public final class KeyProtection implements ProtectionParameter { private final boolean mUserAuthenticationRequired; private final int mUserAuthenticationValidityDurationSeconds; private final boolean mUserAuthenticationValidWhileOnBody; + private final boolean mInvalidatedByBiometricEnrollment; private KeyProtection( Date keyValidityStart, @@ -228,7 +229,8 @@ public final class KeyProtection implements ProtectionParameter { 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 @@ public final class KeyProtection implements ProtectionParameter { mUserAuthenticationRequired = userAuthenticationRequired; mUserAuthenticationValidityDurationSeconds = userAuthenticationValidityDurationSeconds; mUserAuthenticationValidWhileOnBody = userAuthenticationValidWhileOnBody; + mInvalidatedByBiometricEnrollment = invalidatedByBiometricEnrollment; } /** @@ -412,6 +415,19 @@ public final class KeyProtection implements ProtectionParameter { } /** + * 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 @@ public final class KeyProtection implements ProtectionParameter { 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 @@ public final class KeyProtection implements ProtectionParameter { * 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 @@ public final class KeyProtection implements ProtectionParameter { } /** + * 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 @@ public final class KeyProtection implements ProtectionParameter { 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 3a008bcf6832..f5272aa233e9 100644 --- a/keystore/java/android/security/keystore/KeymasterUtils.java +++ b/keystore/java/android/security/keystore/KeymasterUtils.java @@ -97,7 +97,8 @@ public abstract class KeymasterUtils { 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 @@ public abstract class KeymasterUtils { "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 @@ public abstract class KeymasterUtils { } 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 @@ public abstract class KeymasterUtils { 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; + } } |