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/authorization.rs b/keystore2/src/authorization.rs
index ec1edff..cac75c0 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -130,7 +130,15 @@
lock_screen_event: LockScreenEvent,
user_id: i32,
password: Option<Password>,
+ unlocking_sids: Option<&[i64]>,
) -> Result<()> {
+ log::info!(
+ "on_lock_screen_event({:?}, user_id={:?}, password.is_some()={}, unlocking_sids={:?})",
+ lock_screen_event,
+ user_id,
+ password.is_some(),
+ unlocking_sids
+ );
match (lock_screen_event, password) {
(LockScreenEvent::UNLOCK, Some(password)) => {
// This corresponds to the unlock() method in legacy keystore API.
@@ -172,14 +180,23 @@
check_keystore_permission(KeystorePerm::unlock())
.context("In on_lock_screen_event: Unlock.")?;
ENFORCEMENTS.set_device_locked(user_id, false);
+ DB.with(|db| {
+ SUPER_KEY.try_unlock_user_with_biometric(&mut db.borrow_mut(), user_id as u32)
+ })
+ .context("In on_lock_screen_event: try_unlock_user_with_biometric failed")?;
Ok(())
}
(LockScreenEvent::LOCK, None) => {
check_keystore_permission(KeystorePerm::lock())
.context("In on_lock_screen_event: Lock")?;
ENFORCEMENTS.set_device_locked(user_id, true);
- SUPER_KEY.lock_screen_lock_bound_key(user_id as u32);
-
+ DB.with(|db| {
+ SUPER_KEY.lock_screen_lock_bound_key(
+ &mut db.borrow_mut(),
+ user_id as u32,
+ unlocking_sids.unwrap_or(&[]),
+ );
+ });
Ok(())
}
_ => {
@@ -225,9 +242,15 @@
lock_screen_event: LockScreenEvent,
user_id: i32,
password: Option<&[u8]>,
+ unlocking_sids: Option<&[i64]>,
) -> BinderResult<()> {
map_or_log_err(
- self.on_lock_screen_event(lock_screen_event, user_id, password.map(|pw| pw.into())),
+ self.on_lock_screen_event(
+ lock_screen_event,
+ user_id,
+ password.map(|pw| pw.into()),
+ unlocking_sids,
+ ),
Ok,
)
}
diff --git a/keystore2/src/boot_level_keys.rs b/keystore2/src/boot_level_keys.rs
index a658c02..3084195 100644
--- a/keystore2/src/boot_level_keys.rs
+++ b/keystore2/src/boot_level_keys.rs
@@ -17,28 +17,17 @@
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, Digest::Digest, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
};
-use android_system_keystore2::aidl::android::system::keystore2::{
- Domain::Domain, KeyDescriptor::KeyDescriptor,
-};
use anyhow::{Context, Result};
use keystore2_crypto::{hkdf_expand, ZVec, AES_256_KEY_LENGTH};
use std::{collections::VecDeque, convert::TryFrom};
-use crate::{
- database::KeystoreDB, key_parameter::KeyParameterValue, raw_device::KeyMintDevice,
- utils::AID_KEYSTORE,
-};
+use crate::{database::KeystoreDB, key_parameter::KeyParameterValue, raw_device::KeyMintDevice};
/// This is not thread safe; caller must hold a lock before calling.
/// In practice the caller is SuperKeyManager and the lock is the
/// Mutex on its internal state.
pub fn get_level_zero_key(db: &mut KeystoreDB) -> Result<ZVec> {
- let key_desc = KeyDescriptor {
- domain: Domain::APP,
- nspace: AID_KEYSTORE as i64,
- alias: Some("boot_level_key".to_string()),
- blob: None,
- };
+ let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string());
let params = [
KeyParameterValue::Algorithm(Algorithm::HMAC).into(),
KeyParameterValue::Digest(Digest::SHA_2_256).into(),
@@ -60,10 +49,11 @@
let level_zero_key = km_dev
.use_key_in_one_step(
db,
- key_id_guard,
+ &key_id_guard,
&key_entry,
KeyPurpose::SIGN,
¶ms,
+ None,
b"Create boot level key",
)
.context("In get_level_zero_key: use_key_in_one_step failed")?;
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
index cdec79b..06432fe 100644
--- a/keystore2/src/raw_device.rs
+++ b/keystore2/src/raw_device.rs
@@ -25,12 +25,13 @@
utils::{key_characteristics_to_internal, Asp, AID_KEYSTORE},
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- BeginResult::BeginResult, ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice,
- IKeyMintOperation::IKeyMintOperation, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose,
+ BeginResult::BeginResult, ErrorCode::ErrorCode, HardwareAuthToken::HardwareAuthToken,
+ IKeyMintDevice::IKeyMintDevice, IKeyMintOperation::IKeyMintOperation,
+ KeyCreationResult::KeyCreationResult, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose,
SecurityLevel::SecurityLevel,
};
use android_system_keystore2::aidl::android::system::keystore2::{
- KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
};
use anyhow::{Context, Result};
use binder::Strong;
@@ -57,23 +58,26 @@
Ok(KeyMintDevice { asp, km_uuid })
}
- /// Generate a KM key and store in the database.
- fn generate_and_store_key(
+ /// Create a KM key and store in the database.
+ pub fn create_and_store_key<F>(
&self,
db: &mut KeystoreDB,
key_desc: &KeyDescriptor,
- params: &[KeyParameter],
- ) -> Result<()> {
+ creator: F,
+ ) -> Result<()>
+ where
+ F: FnOnce(Strong<dyn IKeyMintDevice>) -> Result<KeyCreationResult, binder::Status>,
+ {
let km_dev: Strong<dyn IKeyMintDevice> = self
.asp
.get_interface()
- .context("In generate_and_store_key: Failed to get KeyMint device")?;
- let creation_result = map_km_error(km_dev.generateKey(params, None))
- .context("In generate_and_store_key: generateKey failed")?;
+ .context("In create_and_store_key: Failed to get KeyMint device")?;
+ let creation_result =
+ map_km_error(creator(km_dev)).context("In create_and_store_key: creator failed")?;
let key_parameters = key_characteristics_to_internal(creation_result.keyCharacteristics);
let creation_date =
- DateTime::now().context("In generate_and_store_key: DateTime::now() failed")?;
+ DateTime::now().context("In create_and_store_key: DateTime::now() failed")?;
let mut key_metadata = KeyMetaData::new();
key_metadata.add(KeyMetaEntry::CreationDate(creation_date));
@@ -88,10 +92,44 @@
&key_metadata,
&self.km_uuid,
)
- .context("In generate_and_store_key: store_new_key failed")?;
+ .context("In create_and_store_key: store_new_key failed")?;
Ok(())
}
+ /// Generate a KeyDescriptor for internal-use keys.
+ pub fn internal_descriptor(alias: String) -> KeyDescriptor {
+ KeyDescriptor {
+ domain: Domain::APP,
+ nspace: AID_KEYSTORE as i64,
+ alias: Some(alias),
+ blob: None,
+ }
+ }
+
+ /// Look up an internal-use key in the database given a key descriptor.
+ pub fn lookup_from_desc(
+ db: &mut KeystoreDB,
+ key_desc: &KeyDescriptor,
+ ) -> Result<(KeyIdGuard, KeyEntry)> {
+ db.load_key_entry(&key_desc, KeyType::Client, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| {
+ Ok(())
+ })
+ .context("In lookup_from_desc: load_key_entry failed")
+ }
+
+ /// Look up the key in the database, and return None if it is absent.
+ pub fn not_found_is_none(
+ lookup: Result<(KeyIdGuard, KeyEntry)>,
+ ) -> Result<Option<(KeyIdGuard, KeyEntry)>> {
+ match lookup {
+ Ok(result) => Ok(Some(result)),
+ Err(e) => match e.root_cause().downcast_ref::<Error>() {
+ Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None),
+ _ => Err(e),
+ },
+ }
+ }
+
/// This does the lookup and store in separate transactions; caller must
/// hold a lock before calling.
pub fn lookup_or_generate_key(
@@ -105,26 +143,16 @@
// - because the caller needs to hold a lock in any case
// - because it avoids holding database locks during slow
// KeyMint operations
- let lookup = db.load_key_entry(
- &key_desc,
- KeyType::Client,
- KeyEntryLoadBits::KM,
- AID_KEYSTORE,
- |_, _| Ok(()),
- );
- match lookup {
- Ok(result) => return Ok(result),
- Err(e) => match e.root_cause().downcast_ref::<Error>() {
- Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => {}
- _ => return Err(e),
- },
+ let lookup = Self::not_found_is_none(Self::lookup_from_desc(db, key_desc))
+ .context("In lookup_or_generate_key: first lookup failed")?;
+ if let Some(result) = lookup {
+ Ok(result)
+ } else {
+ self.create_and_store_key(db, &key_desc, |km_dev| km_dev.generateKey(¶ms, None))
+ .context("In lookup_or_generate_key: generate_and_store_key failed")?;
+ Self::lookup_from_desc(db, key_desc)
+ .context("In lookup_or_generate_key: secpnd lookup failed")
}
- self.generate_and_store_key(db, &key_desc, ¶ms)
- .context("In lookup_or_generate_key: generate_and_store_key failed")?;
- db.load_key_entry(&key_desc, KeyType::Client, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| {
- Ok(())
- })
- .context("In lookup_or_generate_key: load_key_entry failed")
}
/// Call the passed closure; if it returns `KEY_REQUIRES_UPGRADE`, call upgradeKey, and
@@ -133,7 +161,7 @@
&self,
db: &mut KeystoreDB,
km_dev: &Strong<dyn IKeyMintDevice>,
- key_id_guard: KeyIdGuard,
+ key_id_guard: &KeyIdGuard,
key_blob: &KeyBlob,
f: F,
) -> Result<T>
@@ -149,7 +177,7 @@
new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
db.set_blob(
- &key_id_guard,
+ key_id_guard,
SubComponentType::KEY_BLOB,
Some(&upgraded_blob),
Some(&new_blob_metadata),
@@ -170,13 +198,15 @@
/// Use the created key in an operation that can be done with
/// a call to begin followed by a call to finish.
+ #[allow(clippy::too_many_arguments)]
pub fn use_key_in_one_step(
&self,
db: &mut KeystoreDB,
- key_id_guard: KeyIdGuard,
+ key_id_guard: &KeyIdGuard,
key_entry: &KeyEntry,
purpose: KeyPurpose,
operation_parameters: &[KeyParameter],
+ auth_token: Option<&HardwareAuthToken>,
input: &[u8],
) -> Result<Vec<u8>> {
let km_dev: Strong<dyn IKeyMintDevice> = self
@@ -193,7 +223,7 @@
let begin_result: BeginResult = self
.upgrade_keyblob_if_required_with(db, &km_dev, key_id_guard, &key_blob, |blob| {
- map_km_error(km_dev.begin(purpose, blob, operation_parameters, None))
+ map_km_error(km_dev.begin(purpose, blob, operation_parameters, auth_token))
})
.context("In use_key_in_one_step: Failed to begin operation.")?;
let operation: Strong<dyn IKeyMintOperation> = begin_result
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.