Merge "Fix UnlockedDeviceRequired with weak unlock methods" into main am: 3b862a87dd
Original change: https://android-review.googlesource.com/c/platform/system/security/+/2906736 Change-Id: I6baebd5d3d7fc2049543ab7189d058aacdce0966 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
commit
41f887b7cf
3 changed files with 225 additions and 87 deletions
|
@ -47,12 +47,26 @@ interface IKeystoreAuthorization {
|
|||
* 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 @@ interface IKeystoreAuthorization {
|
|||
* 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.
|
||||
|
|
|
@ -164,9 +164,21 @@ impl AuthorizationManager {
|
|||
}
|
||||
}
|
||||
|
||||
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 @@ impl AuthorizationManager {
|
|||
&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 @@ impl IKeystoreAuthorization for AuthorizationManager {
|
|||
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(
|
||||
|
|
|
@ -868,85 +868,114 @@ impl SuperKeyManager {
|
|||
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 @@ impl SuperKeyManager {
|
|||
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(
|
||||
|
|
Loading…
Reference in a new issue