Merge changes from topic "biometric-unlocked-required"
* changes:
Biometric support for UNLOCKED_DEVICE_REQUIRED
Move KeyMintDevice into its own module
Fix GCM IV length
diff --git a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
index 01616b1..3f33431 100644
--- a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
+++ b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
@@ -27,7 +27,6 @@
*/
@SensitiveData
interface IKeystoreAuthorization {
-
/**
* Allows the Android authenticators to hand over an auth token to Keystore.
* Callers require 'AddAuth' permission.
@@ -58,9 +57,29 @@
* @param userId - Android user id
*
* @param password - synthetic password derived by the user denoted by the user id
+ *
+ * @param unlockingSids - list of biometric SIDs for this user. This will be null when
+ * lockScreenEvent is UNLOCK, but may be non-null when
+ * lockScreenEvent is LOCK.
+ *
+ * When the device is unlocked, Keystore stores in memory
+ * a super-encryption key that protects UNLOCKED_DEVICE_REQUIRED
+ * keys; this key is wiped from memory when the device is locked.
+ *
+ * If unlockingSids is non-empty on lock, then before the
+ * super-encryption key is wiped from memory, a copy of it
+ * is stored in memory encrypted with a fresh AES key.
+ * This key is then imported into KM, tagged such that it can be
+ * used given a valid, recent auth token for any of the
+ * unlockingSids.
+ *
+ * Then, when the device is unlocked again, if a suitable auth token
+ * has been sent to keystore, it is used to recover the
+ * super-encryption key, so that UNLOCKED_DEVICE_REQUIRED keys can
+ * be used once again.
*/
void onLockScreenEvent(in LockScreenEvent lockScreenEvent, in int userId,
- in @nullable byte[] password);
+ in @nullable byte[] password, in @nullable long[] unlockingSids);
/**
* Allows Credstore to retrieve a HardwareAuthToken and a TimestampToken.
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 686f5c4..3084195 100644
--- a/keystore2/src/boot_level_keys.rs
+++ b/keystore2/src/boot_level_keys.rs
@@ -15,209 +15,19 @@
//! Offer keys based on the "boot level" for superencryption.
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- Algorithm::Algorithm, BeginResult::BeginResult, Digest::Digest, ErrorCode::ErrorCode,
- IKeyMintDevice::IKeyMintDevice, IKeyMintOperation::IKeyMintOperation,
- KeyParameter::KeyParameter, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
-};
-use android_system_keystore2::aidl::android::system::keystore2::{
- Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
+ Algorithm::Algorithm, Digest::Digest, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
};
use anyhow::{Context, Result};
-use binder::Strong;
use keystore2_crypto::{hkdf_expand, ZVec, AES_256_KEY_LENGTH};
use std::{collections::VecDeque, convert::TryFrom};
-use crate::{
- database::{
- BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits,
- KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB, SubComponentType, Uuid,
- },
- error::{map_km_error, Error},
- globals::get_keymint_device,
- key_parameter::KeyParameterValue,
- super_key::KeyBlob,
- utils::{key_characteristics_to_internal, Asp, AID_KEYSTORE},
-};
-
-/// Wrapper for operating directly on a KeyMint device.
-/// These methods often mirror methods in [`crate::security_level`]. However
-/// the functions in [`crate::security_level`] make assumptions that hold, and has side effects
-/// that make sense, only if called by an external client through binder.
-/// In addition we are trying to maintain a separation between interface services
-/// so that the architecture is compatible with a future move to multiple thread pools.
-/// So the simplest approach today is to write new implementations of them for internal use.
-/// Because these methods run very early, we don't even try to cooperate with
-/// the operation slot database; we assume there will be plenty of slots.
-struct KeyMintDevice {
- asp: Asp,
- km_uuid: Uuid,
-}
-
-impl KeyMintDevice {
- fn get(security_level: SecurityLevel) -> Result<KeyMintDevice> {
- let (asp, _hw_info, km_uuid) = get_keymint_device(&security_level)
- .context("In KeyMintDevice::get: get_keymint_device failed")?;
- Ok(KeyMintDevice { asp, km_uuid })
- }
-
- /// Generate a KM key and store in the database.
- fn generate_and_store_key(
- &self,
- db: &mut KeystoreDB,
- key_desc: &KeyDescriptor,
- params: &[KeyParameter],
- ) -> Result<()> {
- 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")?;
- let key_parameters = key_characteristics_to_internal(creation_result.keyCharacteristics);
-
- let creation_date =
- DateTime::now().context("In generate_and_store_key: DateTime::now() failed")?;
-
- let mut key_metadata = KeyMetaData::new();
- key_metadata.add(KeyMetaEntry::CreationDate(creation_date));
- let mut blob_metadata = BlobMetaData::new();
- blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
-
- db.store_new_key(
- &key_desc,
- &key_parameters,
- &(&creation_result.keyBlob, &blob_metadata),
- &CertificateInfo::new(None, None),
- &key_metadata,
- &self.km_uuid,
- )
- .context("In generate_and_store_key: store_new_key failed")?;
- Ok(())
- }
-
- /// This does the lookup and store in separate transactions; caller must
- /// hold a lock before calling.
- fn lookup_or_generate_key(
- &self,
- db: &mut KeystoreDB,
- key_desc: &KeyDescriptor,
- params: &[KeyParameter],
- ) -> Result<(KeyIdGuard, KeyEntry)> {
- // We use a separate transaction for the lookup than for the store
- // - to keep the code simple
- // - 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),
- },
- }
- 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
- /// write the upgraded key to the database.
- fn upgrade_keyblob_if_required_with<T, F>(
- &self,
- db: &mut KeystoreDB,
- km_dev: &Strong<dyn IKeyMintDevice>,
- key_id_guard: KeyIdGuard,
- key_blob: &KeyBlob,
- f: F,
- ) -> Result<T>
- where
- F: Fn(&[u8]) -> Result<T, Error>,
- {
- match f(key_blob) {
- Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
- let upgraded_blob = map_km_error(km_dev.upgradeKey(key_blob, &[]))
- .context("In upgrade_keyblob_if_required_with: Upgrade failed")?;
-
- let mut new_blob_metadata = BlobMetaData::new();
- new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
-
- db.set_blob(
- &key_id_guard,
- SubComponentType::KEY_BLOB,
- Some(&upgraded_blob),
- Some(&new_blob_metadata),
- )
- .context(concat!(
- "In upgrade_keyblob_if_required_with: ",
- "Failed to insert upgraded blob into the database"
- ))?;
-
- Ok(f(&upgraded_blob).context(concat!(
- "In upgrade_keyblob_if_required_with: ",
- "Closure failed after upgrade"
- ))?)
- }
- result => Ok(result.context("In upgrade_keyblob_if_required_with: Closure failed")?),
- }
- }
-
- /// Use the created key in an operation that can be done with
- /// a call to begin followed by a call to finish.
- fn use_key_in_one_step(
- &self,
- db: &mut KeystoreDB,
- key_id_guard: KeyIdGuard,
- key_entry: &KeyEntry,
- purpose: KeyPurpose,
- operation_parameters: &[KeyParameter],
- input: &[u8],
- ) -> Result<Vec<u8>> {
- let km_dev: Strong<dyn IKeyMintDevice> = self
- .asp
- .get_interface()
- .context("In use_key_in_one_step: Failed to get KeyMint device")?;
-
- let (key_blob, _blob_metadata) = key_entry
- .key_blob_info()
- .as_ref()
- .ok_or_else(Error::sys)
- .context("use_key_in_one_step: Keyblob missing")?;
- let key_blob = KeyBlob::Ref(&key_blob);
-
- 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))
- })
- .context("In use_key_in_one_step: Failed to begin operation.")?;
- let operation: Strong<dyn IKeyMintOperation> = begin_result
- .operation
- .ok_or_else(Error::sys)
- .context("In use_key_in_one_step: Operation missing")?;
- map_km_error(operation.finish(Some(input), None, None, None, None))
- .context("In use_key_in_one_step: Failed to finish operation.")
- }
-}
+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(),
@@ -239,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/crypto/lib.rs b/keystore2/src/crypto/lib.rs
index 3523a9d..db23d1f 100644
--- a/keystore2/src/crypto/lib.rs
+++ b/keystore2/src/crypto/lib.rs
@@ -30,7 +30,7 @@
pub use zvec::ZVec;
/// Length of the expected initialization vector.
-pub const IV_LENGTH: usize = 16;
+pub const GCM_IV_LENGTH: usize = 12;
/// Length of the expected AEAD TAG.
pub const TAG_LENGTH: usize = 16;
/// Length of an AES 256 key in bytes.
@@ -40,9 +40,9 @@
/// Length of the expected salt for key from password generation.
pub const SALT_LENGTH: usize = 16;
-// This is the number of bytes of the GCM IV that is expected to be initialized
-// with random bytes.
-const GCM_IV_LENGTH: usize = 12;
+/// Older versions of keystore produced IVs with four extra
+/// ignored zero bytes at the end; recognise and trim those.
+pub const LEGACY_IV_LENGTH: usize = 16;
/// Generate an AES256 key, essentially 32 random bytes from the underlying
/// boringssl library discretely stuffed into a ZVec.
@@ -80,10 +80,13 @@
/// freed. Input key is taken as a slice for flexibility, but it is recommended that it is held
/// in a ZVec as well.
pub fn aes_gcm_decrypt(data: &[u8], iv: &[u8], tag: &[u8], key: &[u8]) -> Result<ZVec, Error> {
- if iv.len() != IV_LENGTH {
- return Err(Error::InvalidIvLength);
- }
-
+ // Old versions of aes_gcm_encrypt produced 16 byte IVs, but the last four bytes were ignored
+ // so trim these to the correct size.
+ let iv = match iv.len() {
+ GCM_IV_LENGTH => iv,
+ LEGACY_IV_LENGTH => &iv[..GCM_IV_LENGTH],
+ _ => return Err(Error::InvalidIvLength),
+ };
if tag.len() != TAG_LENGTH {
return Err(Error::InvalidAeadTagLength);
}
@@ -96,8 +99,8 @@
let mut result = ZVec::new(data.len())?;
// Safety: The first two arguments must point to buffers with a size given by the third
- // argument. The key must have a size of 16 or 32 bytes which we check above.
- // The iv and tag arguments must be 16 bytes, which we also check above.
+ // argument. We pass the length of the key buffer along with the key.
+ // The `iv` buffer must be 12 bytes and the `tag` buffer 16, which we check above.
match unsafe {
AES_gcm_decrypt(
data.as_ptr(),
@@ -118,10 +121,9 @@
/// This function accepts 128 and 256-bit keys and uses AES128 and AES256 respectively based on
/// the key length. The function generates an initialization vector. The return value is a tuple
/// of `(ciphertext, iv, tag)`.
-pub fn aes_gcm_encrypt(data: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), Error> {
- let mut iv = vec![0; IV_LENGTH];
- // Safety: iv is longer than GCM_IV_LENGTH, which is 12 while IV_LENGTH is 16.
- // The iv needs to be 16 bytes long, but the last 4 bytes remain zeroed.
+pub fn aes_gcm_encrypt(plaintext: &[u8], key: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>), Error> {
+ let mut iv = vec![0; GCM_IV_LENGTH];
+ // Safety: iv is GCM_IV_LENGTH bytes long.
if !unsafe { randomBytes(iv.as_mut_ptr(), GCM_IV_LENGTH) } {
return Err(Error::RandomNumberGenerationFailed);
}
@@ -131,21 +133,25 @@
_ => return Err(Error::InvalidKeyLength),
}
- let mut result: Vec<u8> = vec![0; data.len()];
+ let mut ciphertext: Vec<u8> = vec![0; plaintext.len()];
let mut tag: Vec<u8> = vec![0; TAG_LENGTH];
- match unsafe {
+ // Safety: The first two arguments must point to buffers with a size given by the third
+ // argument. We pass the length of the key buffer along with the key.
+ // The `iv` buffer must be 12 bytes and the `tag` buffer 16, which we check above.
+ if unsafe {
AES_gcm_encrypt(
- data.as_ptr(),
- result.as_mut_ptr(),
- data.len(),
+ plaintext.as_ptr(),
+ ciphertext.as_mut_ptr(),
+ plaintext.len(),
key.as_ptr(),
key.len(),
iv.as_ptr(),
tag.as_mut_ptr(),
)
} {
- true => Ok((result, iv, tag)),
- false => Err(Error::EncryptionFailed),
+ Ok((ciphertext, iv, tag))
+ } else {
+ Err(Error::EncryptionFailed)
}
}
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index e631356..29d46ad 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -206,7 +206,7 @@
}
impl LegacyBlobLoader {
- const IV_SIZE: usize = keystore2_crypto::IV_LENGTH;
+ const IV_SIZE: usize = keystore2_crypto::LEGACY_IV_LENGTH;
const GCM_TAG_LENGTH: usize = keystore2_crypto::TAG_LENGTH;
const SALT_SIZE: usize = keystore2_crypto::SALT_LENGTH;
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 0ce83e6..3332b83 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -34,6 +34,7 @@
pub mod metrics;
pub mod operation;
pub mod permission;
+pub mod raw_device;
pub mod remote_provisioning;
pub mod security_level;
pub mod service;
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
new file mode 100644
index 0000000..06432fe
--- /dev/null
+++ b/keystore2/src/raw_device.rs
@@ -0,0 +1,236 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Provide the [`KeyMintDevice`] wrapper for operating directly on a KeyMint device.
+
+use crate::{
+ database::{
+ BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits,
+ KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB, SubComponentType, Uuid,
+ },
+ error::{map_km_error, Error},
+ globals::get_keymint_device,
+ super_key::KeyBlob,
+ utils::{key_characteristics_to_internal, Asp, AID_KEYSTORE},
+};
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ 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::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
+};
+use anyhow::{Context, Result};
+use binder::Strong;
+
+/// Wrapper for operating directly on a KeyMint device.
+/// These methods often mirror methods in [`crate::security_level`]. However
+/// the functions in [`crate::security_level`] make assumptions that hold, and has side effects
+/// that make sense, only if called by an external client through binder.
+/// In addition we are trying to maintain a separation between interface services
+/// so that the architecture is compatible with a future move to multiple thread pools.
+/// So the simplest approach today is to write new implementations of them for internal use.
+/// Because these methods run very early, we don't even try to cooperate with
+/// the operation slot database; we assume there will be plenty of slots.
+pub struct KeyMintDevice {
+ asp: Asp,
+ km_uuid: Uuid,
+}
+
+impl KeyMintDevice {
+ /// Get a [`KeyMintDevice`] for the given [`SecurityLevel`]
+ pub fn get(security_level: SecurityLevel) -> Result<KeyMintDevice> {
+ let (asp, _hw_info, km_uuid) = get_keymint_device(&security_level)
+ .context("In KeyMintDevice::get: get_keymint_device failed")?;
+ Ok(KeyMintDevice { asp, km_uuid })
+ }
+
+ /// Create a KM key and store in the database.
+ pub fn create_and_store_key<F>(
+ &self,
+ db: &mut KeystoreDB,
+ key_desc: &KeyDescriptor,
+ 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 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 create_and_store_key: DateTime::now() failed")?;
+
+ let mut key_metadata = KeyMetaData::new();
+ key_metadata.add(KeyMetaEntry::CreationDate(creation_date));
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
+
+ db.store_new_key(
+ &key_desc,
+ &key_parameters,
+ &(&creation_result.keyBlob, &blob_metadata),
+ &CertificateInfo::new(None, None),
+ &key_metadata,
+ &self.km_uuid,
+ )
+ .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(
+ &self,
+ db: &mut KeystoreDB,
+ key_desc: &KeyDescriptor,
+ params: &[KeyParameter],
+ ) -> Result<(KeyIdGuard, KeyEntry)> {
+ // We use a separate transaction for the lookup than for the store
+ // - to keep the code simple
+ // - because the caller needs to hold a lock in any case
+ // - because it avoids holding database locks during slow
+ // KeyMint operations
+ 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")
+ }
+ }
+
+ /// Call the passed closure; if it returns `KEY_REQUIRES_UPGRADE`, call upgradeKey, and
+ /// write the upgraded key to the database.
+ fn upgrade_keyblob_if_required_with<T, F>(
+ &self,
+ db: &mut KeystoreDB,
+ km_dev: &Strong<dyn IKeyMintDevice>,
+ key_id_guard: &KeyIdGuard,
+ key_blob: &KeyBlob,
+ f: F,
+ ) -> Result<T>
+ where
+ F: Fn(&[u8]) -> Result<T, Error>,
+ {
+ match f(key_blob) {
+ Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
+ let upgraded_blob = map_km_error(km_dev.upgradeKey(key_blob, &[]))
+ .context("In upgrade_keyblob_if_required_with: Upgrade failed")?;
+
+ let mut new_blob_metadata = BlobMetaData::new();
+ new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
+
+ db.set_blob(
+ key_id_guard,
+ SubComponentType::KEY_BLOB,
+ Some(&upgraded_blob),
+ Some(&new_blob_metadata),
+ )
+ .context(concat!(
+ "In upgrade_keyblob_if_required_with: ",
+ "Failed to insert upgraded blob into the database"
+ ))?;
+
+ Ok(f(&upgraded_blob).context(concat!(
+ "In upgrade_keyblob_if_required_with: ",
+ "Closure failed after upgrade"
+ ))?)
+ }
+ result => Ok(result.context("In upgrade_keyblob_if_required_with: Closure failed")?),
+ }
+ }
+
+ /// 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_entry: &KeyEntry,
+ purpose: KeyPurpose,
+ operation_parameters: &[KeyParameter],
+ auth_token: Option<&HardwareAuthToken>,
+ input: &[u8],
+ ) -> Result<Vec<u8>> {
+ let km_dev: Strong<dyn IKeyMintDevice> = self
+ .asp
+ .get_interface()
+ .context("In use_key_in_one_step: Failed to get KeyMint device")?;
+
+ let (key_blob, _blob_metadata) = key_entry
+ .key_blob_info()
+ .as_ref()
+ .ok_or_else(Error::sys)
+ .context("use_key_in_one_step: Keyblob missing")?;
+ let key_blob = KeyBlob::Ref(&key_blob);
+
+ 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, auth_token))
+ })
+ .context("In use_key_in_one_step: Failed to begin operation.")?;
+ let operation: Strong<dyn IKeyMintOperation> = begin_result
+ .operation
+ .ok_or_else(Error::sys)
+ .context("In use_key_in_one_step: Operation missing")?;
+ map_km_error(operation.finish(Some(input), None, None, None, None))
+ .context("In use_key_in_one_step: Failed to finish operation.")
+ }
+}
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.