Merge "Fix UnlockedDeviceRequired with weak unlock methods" into main
diff --git a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
index 54894ff..a9de026 100644
--- a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
+++ b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
@@ -47,12 +47,26 @@
* disabled the use of such keys. In addition, once per boot, this method must be called with a
* password before keys that require user authentication can be used.
*
- * To enable access to these keys, this method attempts to decrypt and cache the user's super
- * keys. If the password is given, i.e. if the unlock occurred using an LSKF-equivalent
- * mechanism, then both the AfterFirstUnlock and UnlockedDeviceRequired super keys are decrypted
- * (if not already done). Otherwise, only the UnlockedDeviceRequired super keys are decrypted,
- * and this only works if a valid HardwareAuthToken has been added to Keystore for one of the
- * 'unlockingSids' that was passed to the last call to onDeviceLocked() for the user.
+ * This method does two things to restore access to UnlockedDeviceRequired keys. First, it sets
+ * a flag that indicates the user is unlocked. This is always done, and it makes Keystore's
+ * logical enforcement of UnlockedDeviceRequired start passing. Second, it recovers and caches
+ * the user's UnlockedDeviceRequired super keys. This succeeds only in the following cases:
+ *
+ * - The (correct) password is provided, proving that the user has authenticated using LSKF or
+ * equivalent. This is the most powerful type of unlock. Keystore uses the password to
+ * decrypt the user's UnlockedDeviceRequired super keys from disk. It also uses the password
+ * to decrypt the user's AfterFirstUnlock super key from disk, if not already done.
+ *
+ * - The user's UnlockedDeviceRequired super keys are cached in biometric-encrypted form, and a
+ * matching valid HardwareAuthToken has been added to Keystore. I.e., class 3 biometric
+ * unlock is enabled and the user recently authenticated using a class 3 biometric. The keys
+ * are cached in biometric-encrypted form if onDeviceLocked() was called with a nonempty list
+ * of unlockingSids, and onNonLskfUnlockMethodsExpired() was not called later.
+ *
+ * - The user's UnlockedDeviceRequired super keys are already cached in plaintext. This is the
+ * case if onDeviceLocked() was called with weakUnlockEnabled=true, and
+ * onWeakUnlockMethodsExpired() was not called later. This case provides only
+ * Keystore-enforced logical security for UnlockedDeviceRequired.
*
* ## Error conditions:
* `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'Unlock' permission.
@@ -69,22 +83,59 @@
* Tells Keystore that the device is now locked for a user. Requires the 'Lock' permission.
*
* This method makes Keystore stop allowing the use of the given user's keys that require an
- * unlocked device. This is done through logical enforcement, and also through cryptographic
- * enforcement by wiping the UnlockedDeviceRequired super keys from memory.
+ * unlocked device. This is enforced logically, and when possible it's also enforced
+ * cryptographically by wiping the UnlockedDeviceRequired super keys from memory.
*
- * unlockingSids is the list of SIDs of the user's biometrics with which the device may be
- * unlocked later. If this list is non-empty, then instead of completely wiping the
- * UnlockedDeviceRequired super keys from memory, this method re-encrypts these super keys with
- * a new AES key that is imported into KeyMint and bound to the given SIDs. This allows the
- * UnlockedDeviceRequired super keys to be recovered if the device is unlocked with a biometric.
+ * unlockingSids and weakUnlockEnabled specify the methods by which the device can become
+ * unlocked for the user, in addition to LSKF-equivalent authentication.
+ *
+ * unlockingSids is the list of SIDs of class 3 (strong) biometrics that can unlock. If
+ * unlockingSids is non-empty, then this method saves a copy of the UnlockedDeviceRequired super
+ * keys in memory encrypted by a new AES key that is imported into KeyMint and configured to be
+ * usable only when user authentication has occurred using any of the SIDs. This allows the
+ * keys to be recovered if the device is unlocked using a class 3 biometric.
+ *
+ * weakUnlockEnabled is true if the unlock can happen using a method that does not have an
+ * associated SID, such as a class 1 (convenience) biometric, class 2 (weak) biometric, or trust
+ * agent. These methods don't count as "authentication" from Keystore's perspective. In this
+ * case, Keystore keeps a copy of the UnlockedDeviceRequired super keys in memory in plaintext,
+ * providing only logical security for UnlockedDeviceRequired.
*
* ## Error conditions:
* `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'Lock' permission.
*
* @param userId The Android user ID of the user for which the device is now locked
- * @param unlockingSids The user's list of biometric SIDs
+ * @param unlockingSids SIDs of class 3 biometrics that can unlock the device for the user
+ * @param weakUnlockEnabled Whether a weak unlock method can unlock the device for the user
*/
- void onDeviceLocked(in int userId, in long[] unlockingSids);
+ void onDeviceLocked(in int userId, in long[] unlockingSids, in boolean weakUnlockEnabled);
+
+ /**
+ * Tells Keystore that weak unlock methods can no longer unlock the device for the given user.
+ * This is intended to be called after an earlier call to onDeviceLocked() with
+ * weakUnlockEnabled=true. It upgrades the security level of UnlockedDeviceRequired keys to
+ * that which would have resulted from calling onDeviceLocked() with weakUnlockEnabled=false.
+ *
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'Lock' permission.
+ *
+ * @param userId The Android user ID of the user for which weak unlock methods have expired
+ */
+ void onWeakUnlockMethodsExpired(in int userId);
+
+ /**
+ * Tells Keystore that non-LSKF-equivalent unlock methods can no longer unlock the device for
+ * the given user. This is intended to be called after an earlier call to onDeviceLocked() with
+ * nonempty unlockingSids. It upgrades the security level of UnlockedDeviceRequired keys to
+ * that which would have resulted from calling onDeviceLocked() with unlockingSids=[] and
+ * weakUnlockEnabled=false.
+ *
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'Lock' permission.
+ *
+ * @param userId The Android user ID of the user for which non-LSKF unlock methods have expired
+ */
+ void onNonLskfUnlockMethodsExpired(in int userId);
/**
* Allows Credstore to retrieve a HardwareAuthToken and a TimestampToken.
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index 36f94e9..f956787 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -164,9 +164,21 @@
}
}
- fn on_device_locked(&self, user_id: i32, unlocking_sids: &[i64]) -> Result<()> {
- log::info!("on_device_locked(user_id={}, unlocking_sids={:?})", user_id, unlocking_sids);
-
+ fn on_device_locked(
+ &self,
+ user_id: i32,
+ unlocking_sids: &[i64],
+ mut weak_unlock_enabled: bool,
+ ) -> Result<()> {
+ log::info!(
+ "on_device_locked(user_id={}, unlocking_sids={:?}, weak_unlock_enabled={})",
+ user_id,
+ unlocking_sids,
+ weak_unlock_enabled
+ );
+ if !android_security_flags::fix_unlocked_device_required_keys_v2() {
+ weak_unlock_enabled = false;
+ }
check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?;
ENFORCEMENTS.set_device_locked(user_id, true);
let mut skm = SUPER_KEY.write().unwrap();
@@ -175,11 +187,32 @@
&mut db.borrow_mut(),
user_id as u32,
unlocking_sids,
+ weak_unlock_enabled,
);
});
Ok(())
}
+ fn on_weak_unlock_methods_expired(&self, user_id: i32) -> Result<()> {
+ log::info!("on_weak_unlock_methods_expired(user_id={})", user_id);
+ if !android_security_flags::fix_unlocked_device_required_keys_v2() {
+ return Ok(());
+ }
+ check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?;
+ SUPER_KEY.write().unwrap().wipe_plaintext_unlocked_device_required_keys(user_id as u32);
+ Ok(())
+ }
+
+ fn on_non_lskf_unlock_methods_expired(&self, user_id: i32) -> Result<()> {
+ log::info!("on_non_lskf_unlock_methods_expired(user_id={})", user_id);
+ if !android_security_flags::fix_unlocked_device_required_keys_v2() {
+ return Ok(());
+ }
+ check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?;
+ SUPER_KEY.write().unwrap().wipe_all_unlocked_device_required_keys(user_id as u32);
+ Ok(())
+ }
+
fn get_auth_tokens_for_credstore(
&self,
challenge: i64,
@@ -240,9 +273,24 @@
map_or_log_err(self.on_device_unlocked(user_id, password.map(|pw| pw.into())), Ok)
}
- fn onDeviceLocked(&self, user_id: i32, unlocking_sids: &[i64]) -> BinderResult<()> {
+ fn onDeviceLocked(
+ &self,
+ user_id: i32,
+ unlocking_sids: &[i64],
+ weak_unlock_enabled: bool,
+ ) -> BinderResult<()> {
let _wp = wd::watch_millis("IKeystoreAuthorization::onDeviceLocked", 500);
- map_or_log_err(self.on_device_locked(user_id, unlocking_sids), Ok)
+ map_or_log_err(self.on_device_locked(user_id, unlocking_sids, weak_unlock_enabled), Ok)
+ }
+
+ fn onWeakUnlockMethodsExpired(&self, user_id: i32) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreAuthorization::onWeakUnlockMethodsExpired", 500);
+ map_or_log_err(self.on_weak_unlock_methods_expired(user_id), Ok)
+ }
+
+ fn onNonLskfUnlockMethodsExpired(&self, user_id: i32) -> BinderResult<()> {
+ let _wp = wd::watch_millis("IKeystoreAuthorization::onNonLskfUnlockMethodsExpired", 500);
+ map_or_log_err(self.on_non_lskf_unlock_methods_expired(user_id), Ok)
}
fn getAuthTokensForCredStore(
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 9c17ccb..44ce9ab 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -868,85 +868,114 @@
Ok(())
}
- /// Wipe the user's UnlockedDeviceRequired super keys from memory.
+ /// Protects the user's UnlockedDeviceRequired super keys in a way such that they can only be
+ /// unlocked by the enabled unlock methods.
pub fn lock_unlocked_device_required_keys(
&mut self,
db: &mut KeystoreDB,
user_id: UserId,
unlocking_sids: &[i64],
+ weak_unlock_enabled: bool,
) {
- log::info!(
- "Locking UnlockedDeviceRequired super keys for user {}; unlocking_sids={:?}",
- user_id,
- unlocking_sids
- );
let entry = self.data.user_keys.entry(user_id).or_default();
- if !unlocking_sids.is_empty() {
- if let (Some(aes), Some(ecdh)) = (
- entry.unlocked_device_required_symmetric.as_ref().cloned(),
- entry.unlocked_device_required_private.as_ref().cloned(),
- ) {
- let res = (|| -> Result<()> {
- let key_desc = KeyMintDevice::internal_descriptor(format!(
- "biometric_unlock_key_{}",
- user_id
- ));
- let encrypting_key = generate_aes256_key()?;
- let km_dev: KeyMintDevice =
- KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
- .context(ks_err!("KeyMintDevice::get failed"))?;
- let mut key_params = vec![
- KeyParameterValue::Algorithm(Algorithm::AES),
- KeyParameterValue::KeySize(256),
- KeyParameterValue::BlockMode(BlockMode::GCM),
- KeyParameterValue::PaddingMode(PaddingMode::NONE),
- KeyParameterValue::CallerNonce,
- KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT),
- KeyParameterValue::MinMacLength(128),
- KeyParameterValue::AuthTimeout(BIOMETRIC_AUTH_TIMEOUT_S),
- KeyParameterValue::HardwareAuthenticatorType(
- HardwareAuthenticatorType::FINGERPRINT,
- ),
- ];
- for sid in unlocking_sids {
- key_params.push(KeyParameterValue::UserSecureID(*sid));
- }
- let key_params: Vec<KmKeyParameter> =
- key_params.into_iter().map(|x| x.into()).collect();
- km_dev.create_and_store_key(
- db,
- &key_desc,
- KeyType::Client, /* TODO Should be Super b/189470584 */
- |dev| {
- let _wp = wd::watch_millis(
- "In lock_unlocked_device_required_keys: calling importKey.",
- 500,
- );
- dev.importKey(
- key_params.as_slice(),
- KeyFormat::RAW,
- &encrypting_key,
- None,
- )
- },
- )?;
- entry.biometric_unlock = Some(BiometricUnlock {
- sids: unlocking_sids.into(),
- key_desc,
- symmetric: LockedKey::new(&encrypting_key, &aes)?,
- private: LockedKey::new(&encrypting_key, &ecdh)?,
- });
- Ok(())
- })();
- // There is no reason to propagate an error here upwards. We must clear the keys
- // from memory in any case.
- if let Err(e) = res {
- log::error!("Error setting up biometric unlock: {:#?}", e);
+ if unlocking_sids.is_empty() {
+ if android_security_flags::fix_unlocked_device_required_keys_v2() {
+ entry.biometric_unlock = None;
+ }
+ } else if let (Some(aes), Some(ecdh)) = (
+ entry.unlocked_device_required_symmetric.as_ref().cloned(),
+ entry.unlocked_device_required_private.as_ref().cloned(),
+ ) {
+ // If class 3 biometric unlock methods are enabled, create a biometric-encrypted copy of
+ // the keys. Do this even if weak unlock methods are enabled too; in that case we'll
+ // also retain a plaintext copy of the keys, but that copy will be wiped later if weak
+ // unlock methods expire. So we need the biometric-encrypted copy too just in case.
+ let res = (|| -> Result<()> {
+ let key_desc =
+ KeyMintDevice::internal_descriptor(format!("biometric_unlock_key_{}", user_id));
+ let encrypting_key = generate_aes256_key()?;
+ let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
+ .context(ks_err!("KeyMintDevice::get failed"))?;
+ let mut key_params = vec![
+ KeyParameterValue::Algorithm(Algorithm::AES),
+ KeyParameterValue::KeySize(256),
+ KeyParameterValue::BlockMode(BlockMode::GCM),
+ KeyParameterValue::PaddingMode(PaddingMode::NONE),
+ KeyParameterValue::CallerNonce,
+ KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT),
+ KeyParameterValue::MinMacLength(128),
+ KeyParameterValue::AuthTimeout(BIOMETRIC_AUTH_TIMEOUT_S),
+ KeyParameterValue::HardwareAuthenticatorType(
+ HardwareAuthenticatorType::FINGERPRINT,
+ ),
+ ];
+ for sid in unlocking_sids {
+ key_params.push(KeyParameterValue::UserSecureID(*sid));
}
+ let key_params: Vec<KmKeyParameter> =
+ key_params.into_iter().map(|x| x.into()).collect();
+ km_dev.create_and_store_key(
+ db,
+ &key_desc,
+ KeyType::Client, /* TODO Should be Super b/189470584 */
+ |dev| {
+ let _wp = wd::watch_millis(
+ "In lock_unlocked_device_required_keys: calling importKey.",
+ 500,
+ );
+ dev.importKey(key_params.as_slice(), KeyFormat::RAW, &encrypting_key, None)
+ },
+ )?;
+ entry.biometric_unlock = Some(BiometricUnlock {
+ sids: unlocking_sids.into(),
+ key_desc,
+ symmetric: LockedKey::new(&encrypting_key, &aes)?,
+ private: LockedKey::new(&encrypting_key, &ecdh)?,
+ });
+ Ok(())
+ })();
+ if let Err(e) = res {
+ log::error!("Error setting up biometric unlock: {:#?}", e);
+ // The caller can't do anything about the error, and for security reasons we still
+ // wipe the keys (unless a weak unlock method is enabled). So just log the error.
}
}
+ // Wipe the plaintext copy of the keys, unless a weak unlock method is enabled.
+ if !weak_unlock_enabled {
+ entry.unlocked_device_required_symmetric = None;
+ entry.unlocked_device_required_private = None;
+ }
+ Self::log_status_of_unlocked_device_required_keys(user_id, entry);
+ }
+
+ pub fn wipe_plaintext_unlocked_device_required_keys(&mut self, user_id: UserId) {
+ let entry = self.data.user_keys.entry(user_id).or_default();
entry.unlocked_device_required_symmetric = None;
entry.unlocked_device_required_private = None;
+ Self::log_status_of_unlocked_device_required_keys(user_id, entry);
+ }
+
+ pub fn wipe_all_unlocked_device_required_keys(&mut self, user_id: UserId) {
+ let entry = self.data.user_keys.entry(user_id).or_default();
+ entry.unlocked_device_required_symmetric = None;
+ entry.unlocked_device_required_private = None;
+ entry.biometric_unlock = None;
+ Self::log_status_of_unlocked_device_required_keys(user_id, entry);
+ }
+
+ fn log_status_of_unlocked_device_required_keys(user_id: UserId, entry: &UserSuperKeys) {
+ let status = match (
+ // Note: the status of the symmetric and private keys should always be in sync.
+ // So we only check one here.
+ entry.unlocked_device_required_symmetric.is_some(),
+ entry.biometric_unlock.is_some(),
+ ) {
+ (false, false) => "fully protected",
+ (false, true) => "biometric-encrypted",
+ (true, false) => "retained in plaintext",
+ (true, true) => "retained in plaintext, with biometric-encrypted copy too",
+ };
+ log::info!("UnlockedDeviceRequired super keys for user {user_id} are {status}.");
}
/// User has unlocked, not using a password. See if any of our stored auth tokens can be used
@@ -957,6 +986,16 @@
user_id: UserId,
) -> Result<()> {
let entry = self.data.user_keys.entry(user_id).or_default();
+ if android_security_flags::fix_unlocked_device_required_keys_v2()
+ && entry.unlocked_device_required_symmetric.is_some()
+ && entry.unlocked_device_required_private.is_some()
+ {
+ // If the keys are already cached in plaintext, then there is no need to decrypt the
+ // biometric-encrypted copy. Both copies can be present here if the user has both
+ // class 3 biometric and weak unlock methods enabled, and the device was unlocked before
+ // the weak unlock methods expired.
+ return Ok(());
+ }
if let Some(biometric) = entry.biometric_unlock.as_ref() {
let (key_id_guard, key_entry) = db
.load_key_entry(