Biometric support for UNLOCKED_DEVICE_REQUIRED
When the device is locked, keystore is passed a list of biometric
SIDs which should allow unlock of UNLOCKED_DEVICE_REQUIRED keys.
It creates a KM key protected by these SIDs and uses it to encrypt
the UNLOCKED_DEVICE_REQUIRED secrets, and uses this key to recover
those secrets when the device is unlocked.
Test: aosp/1686345
Bug: 163866361
Change-Id: Ic73ed0089cd9567a83c38aed61e20215862aa0be
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index b78560f..50a5f31 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -19,31 +19,45 @@
database::EncryptedBy,
database::KeyEntry,
database::KeyType,
- database::{KeyMetaData, KeyMetaEntry, KeystoreDB},
+ database::{KeyIdGuard, KeyMetaData, KeyMetaEntry, KeystoreDB},
ec_crypto::ECDHPrivateKey,
enforcements::Enforcements,
error::Error,
error::ResponseCode,
- key_parameter::KeyParameter,
+ key_parameter::{KeyParameter, KeyParameterValue},
legacy_blob::LegacyBlobLoader,
legacy_migrator::LegacyMigrator,
+ raw_device::KeyMintDevice,
try_insert::TryInsert,
};
-use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BlockMode::BlockMode, HardwareAuthToken::HardwareAuthToken,
+ HardwareAuthenticatorType::HardwareAuthenticatorType, KeyFormat::KeyFormat,
+ KeyParameter::KeyParameter as KmKeyParameter, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
+ SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor,
+};
use anyhow::{Context, Result};
use keystore2_crypto::{
aes_gcm_decrypt, aes_gcm_encrypt, generate_aes256_key, generate_salt, Password, ZVec,
AES_256_KEY_LENGTH,
};
use keystore2_system_property::PropertyWatcher;
-use std::ops::Deref;
use std::{
collections::HashMap,
sync::Arc,
sync::{Mutex, Weak},
};
+use std::{convert::TryFrom, ops::Deref};
const MAX_MAX_BOOT_LEVEL: usize = 1_000_000_000;
+/// Allow up to 15 seconds between the user unlocking using a biometric, and the auth
+/// token being used to unlock in [`SuperKeyManager::try_unlock_user_with_biometric`].
+/// This seems short enough for security purposes, while long enough that even the
+/// very slowest device will present the auth token in time.
+const BIOMETRIC_AUTH_TIMEOUT_S: i32 = 15; // seconds
type UserId = u32;
@@ -154,6 +168,66 @@
}
}
+/// A SuperKey that has been encrypted with an AES-GCM key. For
+/// encryption the key is in memory, and for decryption it is in KM.
+struct LockedKey {
+ algorithm: SuperEncryptionAlgorithm,
+ id: SuperKeyIdentifier,
+ nonce: Vec<u8>,
+ ciphertext: Vec<u8>, // with tag appended
+}
+
+impl LockedKey {
+ fn new(key: &[u8], to_encrypt: &Arc<SuperKey>) -> Result<Self> {
+ let (mut ciphertext, nonce, mut tag) = aes_gcm_encrypt(&to_encrypt.key, key)?;
+ ciphertext.append(&mut tag);
+ Ok(LockedKey { algorithm: to_encrypt.algorithm, id: to_encrypt.id, nonce, ciphertext })
+ }
+
+ fn decrypt(
+ &self,
+ db: &mut KeystoreDB,
+ km_dev: &KeyMintDevice,
+ key_id_guard: &KeyIdGuard,
+ key_entry: &KeyEntry,
+ auth_token: &HardwareAuthToken,
+ reencrypt_with: Option<Arc<SuperKey>>,
+ ) -> Result<Arc<SuperKey>> {
+ let key_params = vec![
+ KeyParameterValue::Algorithm(Algorithm::AES),
+ KeyParameterValue::KeySize(256),
+ KeyParameterValue::BlockMode(BlockMode::GCM),
+ KeyParameterValue::PaddingMode(PaddingMode::NONE),
+ KeyParameterValue::Nonce(self.nonce.clone()),
+ KeyParameterValue::MacLength(128),
+ ];
+ let key_params: Vec<KmKeyParameter> = key_params.into_iter().map(|x| x.into()).collect();
+ let key = ZVec::try_from(km_dev.use_key_in_one_step(
+ db,
+ key_id_guard,
+ key_entry,
+ KeyPurpose::DECRYPT,
+ &key_params,
+ Some(auth_token),
+ &self.ciphertext,
+ )?)?;
+ Ok(Arc::new(SuperKey { algorithm: self.algorithm, key, id: self.id, reencrypt_with }))
+ }
+}
+
+/// Keys for unlocking UNLOCKED_DEVICE_REQUIRED keys, as LockedKeys, complete with
+/// a database descriptor for the encrypting key and the sids for the auth tokens
+/// that can be used to decrypt it.
+struct BiometricUnlock {
+ /// List of auth token SIDs that can be used to unlock these keys.
+ sids: Vec<i64>,
+ /// Database descriptor of key to use to unlock.
+ key_desc: KeyDescriptor,
+ /// Locked versions of the matching UserSuperKeys fields
+ screen_lock_bound: LockedKey,
+ screen_lock_bound_private: LockedKey,
+}
+
#[derive(Default)]
struct UserSuperKeys {
/// The per boot key is used for LSKF binding of authentication bound keys. There is one
@@ -168,6 +242,8 @@
/// When the device is locked, screen-lock-bound keys can still be encrypted, using
/// ECDH public-key encryption. This field holds the decryption private key.
screen_lock_bound_private: Option<Arc<SuperKey>>,
+ /// Versions of the above two keys, locked behind a biometric.
+ biometric_unlock: Option<BiometricUnlock>,
}
#[derive(Default)]
@@ -639,7 +715,7 @@
/// Check if super encryption is required and if so, super-encrypt the key to be stored in
/// the database.
- #[allow(clippy::clippy::too_many_arguments)]
+ #[allow(clippy::too_many_arguments)]
pub fn handle_super_encryption_on_key_init(
&self,
db: &mut KeystoreDB,
@@ -831,12 +907,127 @@
}
/// Wipe the screen-lock bound keys for this user from memory.
- pub fn lock_screen_lock_bound_key(&self, user_id: UserId) {
+ pub fn lock_screen_lock_bound_key(
+ &self,
+ db: &mut KeystoreDB,
+ user_id: UserId,
+ unlocking_sids: &[i64],
+ ) {
+ log::info!("Locking screen bound for user {} sids {:?}", user_id, unlocking_sids);
let mut data = self.data.lock().unwrap();
let mut entry = data.user_keys.entry(user_id).or_default();
+ if !unlocking_sids.is_empty() {
+ if let (Some(aes), Some(ecdh)) = (
+ entry.screen_lock_bound.as_ref().cloned(),
+ entry.screen_lock_bound_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("In lock_screen_lock_bound_key: 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, |dev| {
+ dev.importKey(key_params.as_slice(), KeyFormat::RAW, &encrypting_key, None)
+ })?;
+ entry.biometric_unlock = Some(BiometricUnlock {
+ sids: unlocking_sids.into(),
+ key_desc,
+ screen_lock_bound: LockedKey::new(&encrypting_key, &aes)?,
+ screen_lock_bound_private: LockedKey::new(&encrypting_key, &ecdh)?,
+ });
+ Ok(())
+ })();
+ // There is no reason to propagate an error here upwards. We must discard
+ // entry.screen_lock_bound* in any case.
+ if let Err(e) = res {
+ log::error!("Error setting up biometric unlock: {:#?}", e);
+ }
+ }
+ }
entry.screen_lock_bound = None;
entry.screen_lock_bound_private = None;
}
+
+ /// User has unlocked, not using a password. See if any of our stored auth tokens can be used
+ /// to unlock the keys protecting UNLOCKED_DEVICE_REQUIRED keys.
+ pub fn try_unlock_user_with_biometric(
+ &self,
+ db: &mut KeystoreDB,
+ user_id: UserId,
+ ) -> Result<()> {
+ let mut data = self.data.lock().unwrap();
+ let mut entry = data.user_keys.entry(user_id).or_default();
+ if let Some(biometric) = entry.biometric_unlock.as_ref() {
+ let (key_id_guard, key_entry) =
+ KeyMintDevice::lookup_from_desc(db, &biometric.key_desc)?;
+ let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
+ .context("In try_unlock_user_with_biometric: KeyMintDevice::get failed")?;
+ for sid in &biometric.sids {
+ if let Some((auth_token_entry, _)) = db.find_auth_token_entry(|entry| {
+ entry.auth_token().userId == *sid || entry.auth_token().authenticatorId == *sid
+ })? {
+ let res: Result<(Arc<SuperKey>, Arc<SuperKey>)> = (|| {
+ let slb = biometric.screen_lock_bound.decrypt(
+ db,
+ &km_dev,
+ &key_id_guard,
+ &key_entry,
+ auth_token_entry.auth_token(),
+ None,
+ )?;
+ let slbp = biometric.screen_lock_bound_private.decrypt(
+ db,
+ &km_dev,
+ &key_id_guard,
+ &key_entry,
+ auth_token_entry.auth_token(),
+ Some(slb.clone()),
+ )?;
+ Ok((slb, slbp))
+ })();
+ match res {
+ Ok((slb, slbp)) => {
+ entry.screen_lock_bound = Some(slb.clone());
+ entry.screen_lock_bound_private = Some(slbp.clone());
+ data.add_key_to_key_index(&slb)?;
+ data.add_key_to_key_index(&slbp)?;
+ log::info!(concat!(
+ "In try_unlock_user_with_biometric: ",
+ "Successfully unlocked with biometric"
+ ));
+ return Ok(());
+ }
+ Err(e) => {
+ log::warn!("In try_unlock_user_with_biometric: attempt failed: {:?}", e)
+ }
+ }
+ }
+ }
+ }
+ Ok(())
+ }
}
/// This enum represents different states of the user's life cycle in the device.