Merge changes from topic "permissions_UM_aidl"
* changes:
Keystore 2.0: Implement legacy blob support.
Make 'password unlock' depend on user management infrastructure.
Super encrypted keys
Implement user manager AIDL.
Populate user states.
Add keystore permissions required by user manager AIDL.
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 05bf699..812d5e6 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -33,6 +33,7 @@
"android.security.authorization-rust",
"android.security.compat-rust",
"android.security.remoteprovisioning-rust",
+ "android.security.usermanager-rust",
"android.system.keystore2-V1-rust",
"libanyhow",
"libbinder_rs",
@@ -75,6 +76,7 @@
"android.security.authorization-rust",
"android.security.compat-rust",
"android.security.remoteprovisioning-rust",
+ "android.security.usermanager-rust",
"android.system.keystore2-V1-rust",
"libandroid_logger",
"libanyhow",
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index e446e78..02b19c4 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -12,18 +12,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! This module implements IKeyAuthorization AIDL interface.
+//! This module implements IKeystoreAuthorization AIDL interface.
use crate::error::Error as KeystoreError;
use crate::error::map_or_log_err;
-use crate::globals::{DB, ENFORCEMENTS, LEGACY_BLOB_LOADER, SUPER_KEY};
+use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_MIGRATOR};
use crate::permission::KeystorePerm;
+use crate::super_key::UserState;
use crate::utils::check_keystore_permission;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
-};
-use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
- Timestamp::Timestamp,
+ HardwareAuthToken::HardwareAuthToken,
};
use android_security_authorization::binder::{Interface, Result as BinderResult, Strong};
use android_security_authorization::aidl::android::security::authorization::IKeystoreAuthorization::{
@@ -50,16 +48,7 @@
//check keystore permission
check_keystore_permission(KeystorePerm::add_auth()).context("In add_auth_token.")?;
- //TODO: Keymint's HardwareAuthToken aidl needs to implement Copy/Clone
- let auth_token_copy = HardwareAuthToken {
- challenge: auth_token.challenge,
- userId: auth_token.userId,
- authenticatorId: auth_token.authenticatorId,
- authenticatorType: HardwareAuthenticatorType(auth_token.authenticatorType.0),
- timestamp: Timestamp { milliSeconds: auth_token.timestamp.milliSeconds },
- mac: auth_token.mac.clone(),
- };
- ENFORCEMENTS.add_auth_token(auth_token_copy)?;
+ ENFORCEMENTS.add_auth_token(auth_token.clone())?;
Ok(())
}
@@ -77,22 +66,22 @@
.context("In on_lock_screen_event: Unlock with password.")?;
ENFORCEMENTS.set_device_locked(user_id, false);
// Unlock super key.
- DB.with::<_, Result<()>>(|db| {
- let mut db = db.borrow_mut();
- //TODO - b/176123105 - Once the user management API is implemented, unlock is
- //allowed only if the user is added. Then the two tasks handled by the
- //unlock_user_key will be split into two methods. For now, unlock_user_key
- //method is used as it is, which created a super key for the user if one does
- //not exists, in addition to unlocking the existing super key of the user/
- SUPER_KEY.unlock_user_key(
- user_id as u32,
- user_password,
- &mut db,
- &LEGACY_BLOB_LOADER,
- )?;
- Ok(())
- })
- .context("In on_lock_screen_event.")?;
+ if let UserState::Uninitialized = DB
+ .with(|db| {
+ UserState::get_with_password_unlock(
+ &mut db.borrow_mut(),
+ &LEGACY_MIGRATOR,
+ &SUPER_KEY,
+ user_id as u32,
+ user_password,
+ )
+ })
+ .context("In on_lock_screen_event: Unlock with password.")?
+ {
+ log::info!(
+ "In on_lock_screen_event. Trying to unlock when LSKF is uninitialized."
+ );
+ }
Ok(())
}
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index dc6d7a0..3217857 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -45,7 +45,7 @@
use crate::impl_metadata; // This is in db_utils.rs
use crate::key_parameter::{KeyParameter, Tag};
use crate::permission::KeyPermSet;
-use crate::utils::get_current_time_in_seconds;
+use crate::utils::{get_current_time_in_seconds, AID_USER_OFFSET};
use crate::{
db_utils::{self, SqlField},
gc::Gc,
@@ -144,7 +144,7 @@
fn store_in_db(&self, key_id: i64, tx: &Transaction) -> Result<()> {
let mut stmt = tx
.prepare(
- "INSERT into persistent.keymetadata (keyentryid, tag, data)
+ "INSERT or REPLACE INTO persistent.keymetadata (keyentryid, tag, data)
VALUES (?, ?, ?);",
)
.context("In KeyMetaData::store_in_db: Failed to prepare statement.")?;
@@ -653,6 +653,10 @@
pub fn pure_cert(&self) -> bool {
self.pure_cert
}
+ /// Consumes this key entry and extracts the keyparameters and metadata from it.
+ pub fn into_key_parameters_and_metadata(self) -> (Vec<KeyParameter>, KeyMetaData) {
+ (self.parameters, self.metadata)
+ }
}
/// Indicates the sub component of a key entry for persistent storage.
@@ -792,6 +796,9 @@
impl KeystoreDB {
const PERBOOT_DB_FILE_NAME: &'static str = &"file:perboot.sqlite?mode=memory&cache=shared";
+ /// The alias of the user super key.
+ pub const USER_SUPER_KEY_ALIAS: &'static str = &"USER_SUPER_KEY";
+
/// This creates a PerBootDbKeepAlive object to keep the per boot database alive.
pub fn keep_perboot_db_alive() -> Result<PerBootDbKeepAlive> {
let conn = Connection::open_in_memory()
@@ -912,7 +919,8 @@
"CREATE TABLE IF NOT EXISTS persistent.keymetadata (
keyentryid INTEGER,
tag INTEGER,
- data ANY);",
+ data ANY,
+ UNIQUE (keyentryid, tag));",
NO_PARAMS,
)
.context("Failed to initialize \"keymetadata\" table.")?;
@@ -1093,6 +1101,98 @@
.context("In cleanup_leftovers.")
}
+ /// Checks if a key exists with given key type and key descriptor properties.
+ pub fn key_exists(
+ &mut self,
+ domain: Domain,
+ nspace: i64,
+ alias: &str,
+ key_type: KeyType,
+ ) -> Result<bool> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let key_descriptor =
+ KeyDescriptor { domain, nspace, alias: Some(alias.to_string()), blob: None };
+ let result = Self::load_key_entry_id(&tx, &key_descriptor, key_type);
+ match result {
+ Ok(_) => Ok(true),
+ Err(error) => match error.root_cause().downcast_ref::<KsError>() {
+ Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(false),
+ _ => Err(error).context("In key_exists: Failed to find if the key exists."),
+ },
+ }
+ .no_gc()
+ })
+ .context("In key_exists.")
+ }
+
+ /// Stores a super key in the database.
+ pub fn store_super_key(
+ &mut self,
+ user_id: u32,
+ blob_info: &(&[u8], &BlobMetaData),
+ ) -> Result<KeyEntry> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let key_id = Self::insert_with_retry(|id| {
+ tx.execute(
+ "INSERT into persistent.keyentry
+ (id, key_type, domain, namespace, alias, state, km_uuid)
+ VALUES(?, ?, ?, ?, ?, ?, ?);",
+ params![
+ id,
+ KeyType::Super,
+ Domain::APP.0,
+ user_id as i64,
+ Self::USER_SUPER_KEY_ALIAS,
+ KeyLifeCycle::Live,
+ &KEYSTORE_UUID,
+ ],
+ )
+ })
+ .context("Failed to insert into keyentry table.")?;
+
+ let (blob, blob_metadata) = *blob_info;
+ Self::set_blob_internal(
+ &tx,
+ key_id,
+ SubComponentType::KEY_BLOB,
+ Some(blob),
+ Some(blob_metadata),
+ )
+ .context("Failed to store key blob.")?;
+
+ Self::load_key_components(tx, KeyEntryLoadBits::KM, key_id)
+ .context("Trying to load key components.")
+ .no_gc()
+ })
+ .context("In store_super_key.")
+ }
+
+ /// Loads super key of a given user, if exists
+ pub fn load_super_key(&mut self, user_id: u32) -> Result<Option<(KeyIdGuard, KeyEntry)>> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let key_descriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: user_id as i64,
+ alias: Some(String::from("USER_SUPER_KEY")),
+ blob: None,
+ };
+ let id = Self::load_key_entry_id(&tx, &key_descriptor, KeyType::Super);
+ match id {
+ Ok(id) => {
+ let key_entry = Self::load_key_components(&tx, KeyEntryLoadBits::KM, id)
+ .context("In load_super_key. Failed to load key entry.")?;
+ Ok(Some((KEY_ID_LOCK.get(id), key_entry)))
+ }
+ Err(error) => match error.root_cause().downcast_ref::<KsError>() {
+ Some(KsError::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None),
+ _ => Err(error).context("In load_super_key."),
+ },
+ }
+ .no_gc()
+ })
+ .context("In load_super_key.")
+ }
+
/// Atomically loads a key entry and associated metadata or creates it using the
/// callback create_new_key callback. The callback is called during a database
/// transaction. This means that implementers should be mindful about using
@@ -1965,7 +2065,7 @@
.prepare(
"SELECT id FROM persistent.keyentry
WHERE
- key_type = ?
+ key_type = ?
AND domain = ?
AND namespace = ?
AND alias = ?
@@ -2392,6 +2492,82 @@
.context("In get_key_km_uuid.")
}
+ /// Delete the keys created on behalf of the user, denoted by the user id.
+ /// Delete all the keys unless 'keep_non_super_encrypted_keys' set to true.
+ /// Returned boolean is to hint the garbage collector to delete the unbound keys.
+ /// The caller of this function should notify the gc if the returned value is true.
+ pub fn unbind_keys_for_user(
+ &mut self,
+ user_id: u32,
+ keep_non_super_encrypted_keys: bool,
+ ) -> Result<()> {
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ let mut stmt = tx
+ .prepare(&format!(
+ "SELECT id from persistent.keyentry
+ WHERE (
+ key_type = ?
+ AND domain = ?
+ AND cast ( (namespace/{aid_user_offset}) as int) = ?
+ AND state = ?
+ ) OR (
+ key_type = ?
+ AND namespace = ?
+ AND alias = ?
+ AND state = ?
+ );",
+ aid_user_offset = AID_USER_OFFSET
+ ))
+ .context(concat!(
+ "In unbind_keys_for_user. ",
+ "Failed to prepare the query to find the keys created by apps."
+ ))?;
+
+ let mut rows = stmt
+ .query(params![
+ // WHERE client key:
+ KeyType::Client,
+ Domain::APP.0 as u32,
+ user_id,
+ KeyLifeCycle::Live,
+ // OR super key:
+ KeyType::Super,
+ user_id,
+ Self::USER_SUPER_KEY_ALIAS,
+ KeyLifeCycle::Live
+ ])
+ .context("In unbind_keys_for_user. Failed to query the keys created by apps.")?;
+
+ let mut key_ids: Vec<i64> = Vec::new();
+ db_utils::with_rows_extract_all(&mut rows, |row| {
+ key_ids
+ .push(row.get(0).context("Failed to read key id of a key created by an app.")?);
+ Ok(())
+ })
+ .context("In unbind_keys_for_user.")?;
+
+ let mut notify_gc = false;
+ for key_id in key_ids {
+ if keep_non_super_encrypted_keys {
+ // Load metadata and filter out non-super-encrypted keys.
+ if let (_, Some((_, blob_metadata)), _, _) =
+ Self::load_blob_components(key_id, KeyEntryLoadBits::KM, tx)
+ .context("In unbind_keys_for_user: Trying to load blob info.")?
+ {
+ if blob_metadata.encrypted_by().is_none() {
+ continue;
+ }
+ }
+ }
+ notify_gc = Self::mark_unreferenced(&tx, key_id)
+ .context("In unbind_keys_for_user.")?
+ || notify_gc;
+ }
+ Ok(()).do_gc(notify_gc)
+ })
+ .context("In unbind_keys_for_user.")
+ }
+
fn load_key_components(
tx: &Transaction,
load_bits: KeyEntryLoadBits,
@@ -2684,6 +2860,7 @@
};
use crate::key_perm_set;
use crate::permission::{KeyPerm, KeyPermSet};
+ use crate::super_key::SuperKeyManager;
use keystore2_test_utils::TempDir;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken,
@@ -2716,8 +2893,10 @@
where
F: Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static,
{
+ let super_key = Arc::new(SuperKeyManager::new());
+
let gc_db = KeystoreDB::new(path, None).expect("Failed to open test gc db_connection.");
- let gc = Gc::new_init_with(Default::default(), move || (Box::new(cb), gc_db));
+ let gc = Gc::new_init_with(Default::default(), move || (Box::new(cb), gc_db, super_key));
KeystoreDB::new(path, Some(gc))
}
@@ -4541,4 +4720,53 @@
assert!(last_off_body_1.seconds() < last_off_body_2.seconds());
Ok(())
}
+
+ #[test]
+ fn test_unbind_keys_for_user() -> Result<()> {
+ let mut db = new_test_db()?;
+ db.unbind_keys_for_user(1, false)?;
+
+ make_test_key_entry(&mut db, Domain::APP, 210000, TEST_ALIAS, None)?;
+ make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?;
+ db.unbind_keys_for_user(2, false)?;
+
+ assert_eq!(1, db.list(Domain::APP, 110000)?.len());
+ assert_eq!(0, db.list(Domain::APP, 210000)?.len());
+
+ db.unbind_keys_for_user(1, true)?;
+ assert_eq!(0, db.list(Domain::APP, 110000)?.len());
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_store_super_key() -> Result<()> {
+ let mut db = new_test_db()?;
+ let pw = "xyzabc".as_bytes();
+ let super_key = keystore2_crypto::generate_aes256_key()?;
+ let secret = String::from("keystore2 is great.");
+ let secret_bytes = secret.into_bytes();
+ let (encrypted_secret, iv, tag) =
+ keystore2_crypto::aes_gcm_encrypt(&secret_bytes, &super_key)?;
+
+ let (encrypted_super_key, metadata) =
+ SuperKeyManager::encrypt_with_password(&super_key, &pw)?;
+ db.store_super_key(1, &(&encrypted_super_key, &metadata))?;
+
+ //check if super key exists
+ assert!(db.key_exists(Domain::APP, 1, "USER_SUPER_KEY", KeyType::Super)?);
+
+ let (_, key_entry) = db.load_super_key(1)?.unwrap();
+ let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry(key_entry, &pw)?;
+
+ let decrypted_secret_bytes = keystore2_crypto::aes_gcm_decrypt(
+ &encrypted_secret,
+ &iv,
+ &tag,
+ &loaded_super_key.get_key(),
+ )?;
+ let decrypted_secret = String::from_utf8((&decrypted_secret_bytes).to_vec())?;
+ assert_eq!(String::from("keystore2 is great."), decrypted_secret);
+ Ok(())
+ }
}
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 9c3bc89..cc59c32 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -26,7 +26,10 @@
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
ISecureClock::ISecureClock, TimeStampToken::TimeStampToken,
};
-use android_system_keystore2::aidl::android::system::keystore2::OperationChallenge::OperationChallenge;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ IKeystoreSecurityLevel::KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING,
+ OperationChallenge::OperationChallenge,
+};
use android_system_keystore2::binder::Strong;
use anyhow::{Context, Result};
use std::sync::{
@@ -744,6 +747,19 @@
fn register_op_auth_receiver(&self, challenge: i64, recv: TokenReceiver) {
self.op_auth_map.add_receiver(challenge, recv);
}
+
+ /// Given the set of key parameters and flags, check if super encryption is required.
+ pub fn super_encryption_required(key_parameters: &[KeyParameter], flags: Option<i32>) -> bool {
+ let auth_bound = key_parameters.iter().any(|kp| kp.get_tag() == Tag::USER_SECURE_ID);
+
+ let skip_lskf_binding = if let Some(flags) = flags {
+ (flags & KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING) != 0
+ } else {
+ false
+ };
+
+ auth_bound && !skip_lskf_binding
+ }
}
impl Default for Enforcements {
diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs
index b5b1c6c..6cc0f27 100644
--- a/keystore2/src/gc.rs
+++ b/keystore2/src/gc.rs
@@ -21,6 +21,7 @@
use crate::{
async_task,
database::{KeystoreDB, Uuid},
+ super_key::SuperKeyManager,
};
use anyhow::{Context, Result};
use async_task::AsyncTask;
@@ -37,19 +38,23 @@
/// time a garbage collector was initialized with the given AsyncTask instance.
pub fn new_init_with<F>(async_task: Arc<AsyncTask>, init: F) -> Self
where
- F: FnOnce() -> (Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, KeystoreDB)
- + Send
+ F: FnOnce() -> (
+ Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>,
+ KeystoreDB,
+ Arc<SuperKeyManager>,
+ ) + Send
+ 'static,
{
let weak_at = Arc::downgrade(&async_task);
// Initialize the task's shelf.
async_task.queue_hi(move |shelf| {
- let (invalidate_key, db) = init();
+ let (invalidate_key, db, super_key) = init();
shelf.get_or_put_with(|| GcInternal {
blob_id_to_delete: None,
invalidate_key,
db,
async_task: weak_at,
+ super_key,
});
});
Self { async_task }
@@ -68,6 +73,7 @@
invalidate_key: Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>,
db: KeystoreDB,
async_task: std::sync::Weak<AsyncTask>,
+ super_key: Arc<SuperKeyManager>,
}
impl GcInternal {
@@ -91,6 +97,10 @@
// (At this time keys may get deleted without having the super encryption
// key in this case we can only delete the key from the database.)
if let Some(uuid) = blob_metadata.km_uuid() {
+ let blob = self
+ .super_key
+ .unwrap_key_if_required(&blob_metadata, &blob)
+ .context("In process_one_key: Trying to unwrap to-be-deleted blob.")?;
(self.invalidate_key)(&uuid, &*blob)
.context("In process_one_key: Trying to invalidate key.")?;
}
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index 3037a03..8cc0106 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -18,6 +18,7 @@
use crate::gc::Gc;
use crate::legacy_blob::LegacyBlobLoader;
+use crate::legacy_migrator::LegacyMigrator;
use crate::super_key::SuperKeyManager;
use crate::utils::Asp;
use crate::{async_task::AsyncTask, database::MonotonicRawTime};
@@ -49,7 +50,7 @@
/// a gc. Although one GC is created for each thread local database connection, this closure
/// is run only once, as long as the ASYNC_TASK instance is the same. So only one additional
/// database connection is created for the garbage collector worker.
-fn create_thread_local_db() -> KeystoreDB {
+pub fn create_thread_local_db() -> KeystoreDB {
let gc = Gc::new_init_with(ASYNC_TASK.clone(), || {
(
Box::new(|uuid, blob| {
@@ -60,6 +61,7 @@
}),
KeystoreDB::new(&DB_PATH.lock().expect("Could not get the database directory."), None)
.expect("Failed to open database."),
+ SUPER_KEY.clone(),
)
});
@@ -143,8 +145,11 @@
pub static ref ENFORCEMENTS: Enforcements = Enforcements::new();
/// LegacyBlobLoader is initialized and exists globally.
/// The same directory used by the database is used by the LegacyBlobLoader as well.
- pub static ref LEGACY_BLOB_LOADER: LegacyBlobLoader = LegacyBlobLoader::new(
- &DB_PATH.lock().expect("Could not get the database path for legacy blob loader."));
+ pub static ref LEGACY_BLOB_LOADER: Arc<LegacyBlobLoader> = Arc::new(LegacyBlobLoader::new(
+ &DB_PATH.lock().expect("Could not get the database path for legacy blob loader.")));
+ /// Legacy migrator. Atomically migrates legacy blobs to the database.
+ pub static ref LEGACY_MIGRATOR: Arc<LegacyMigrator> =
+ Arc::new(LegacyMigrator::new(ASYNC_TASK.clone()));
}
static KEYMINT_SERVICE_NAME: &str = "android.hardware.security.keymint.IKeyMintDevice";
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index 75475e1..30e3e22 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -18,12 +18,14 @@
use keystore2::authorization::AuthorizationManager;
use keystore2::globals::ENFORCEMENTS;
use keystore2::service::KeystoreService;
+use keystore2::user_manager::UserManager;
use log::{error, info};
use std::{panic, path::Path, sync::mpsc::channel};
static KS2_SERVICE_NAME: &str = "android.system.keystore2";
static APC_SERVICE_NAME: &str = "android.security.apc";
static AUTHORIZATION_SERVICE_NAME: &str = "android.security.authorization";
+static USER_MANAGER_SERVICE_NAME: &str = "android.security.usermanager";
/// Keystore 2.0 takes one argument which is a path indicating its designated working directory.
fn main() {
@@ -87,6 +89,15 @@
panic!("Failed to register service {} because of {:?}.", AUTHORIZATION_SERVICE_NAME, e);
});
+ let usermanager_service = UserManager::new_native_binder().unwrap_or_else(|e| {
+ panic!("Failed to create service {} because of {:?}.", USER_MANAGER_SERVICE_NAME, e);
+ });
+ binder::add_service(USER_MANAGER_SERVICE_NAME, usermanager_service.as_binder()).unwrap_or_else(
+ |e| {
+ panic!("Failed to register service {} because of {:?}.", USER_MANAGER_SERVICE_NAME, e);
+ },
+ );
+
info!("Successfully registered Keystore 2.0 service.");
info!("Joining thread pool now.");
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index ebd063c..1981022 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -17,7 +17,6 @@
//! This module implements methods to load legacy keystore key blob files.
use crate::{
- database::KeyMetaData,
error::{Error as KsError, ResponseCode},
key_parameter::{KeyParameter, KeyParameterValue},
super_key::SuperKeyManager,
@@ -28,8 +27,11 @@
};
use anyhow::{Context, Result};
use keystore2_crypto::{aes_gcm_decrypt, derive_key_from_password, ZVec};
-use std::io::{ErrorKind, Read};
use std::{convert::TryInto, fs::File, path::Path, path::PathBuf};
+use std::{
+ fs,
+ io::{ErrorKind, Read, Result as IoResult},
+};
const SUPPORTED_LEGACY_BLOB_VERSION: u8 = 3;
@@ -231,6 +233,7 @@
pub fn new(path: &Path) -> Self {
Self { path: path.to_owned() }
}
+
/// Encodes an alias string as ascii character sequence in the range
/// ['+' .. '.'] and ['0' .. '~'].
/// Bytes with values in the range ['0' .. '~'] are represented as they are.
@@ -587,7 +590,7 @@
let sw_list = Self::read_key_parameters(&mut stream)
.context("In read_characteristics_file.")?
.into_iter()
- .map(|value| KeyParameter::new(value, SecurityLevel::SOFTWARE));
+ .map(|value| KeyParameter::new(value, SecurityLevel::KEYSTORE));
Ok(hw_list.into_iter().flatten().chain(sw_list).collect())
}
@@ -600,7 +603,7 @@
// used this for user installed certificates without private key material.
fn read_km_blob_file(&self, uid: u32, alias: &str) -> Result<Option<(Blob, String)>> {
- let mut iter = ["USRPKEY", "USERSKEY"].iter();
+ let mut iter = ["USRPKEY", "USRSKEY"].iter();
let (blob, prefix) = loop {
if let Some(prefix) = iter.next() {
@@ -619,7 +622,7 @@
}
fn read_generic_blob(path: &Path) -> Result<Option<Blob>> {
- let mut file = match File::open(path) {
+ let mut file = match Self::with_retry_interrupted(|| File::open(path)) {
Ok(file) => file,
Err(e) => match e.kind() {
ErrorKind::NotFound => return Ok(None),
@@ -633,47 +636,215 @@
/// This function constructs the blob file name which has the form:
/// user_<android user id>/<uid>_<alias>.
fn make_blob_filename(&self, uid: u32, alias: &str, prefix: &str) -> PathBuf {
- let mut path = self.path.clone();
let user_id = uid_to_android_user(uid);
let encoded_alias = Self::encode_alias(&format!("{}_{}", prefix, alias));
- path.push(format!("user_{}", user_id));
+ let mut path = self.make_user_path_name(user_id);
path.push(format!("{}_{}", uid, encoded_alias));
path
}
/// This function constructs the characteristics file name which has the form:
- /// user_<android user id>/.<uid>_chr_<alias>.
+ /// user_<android user id>/.<uid>_chr_<prefix>_<alias>.
fn make_chr_filename(&self, uid: u32, alias: &str, prefix: &str) -> PathBuf {
- let mut path = self.path.clone();
let user_id = uid_to_android_user(uid);
let encoded_alias = Self::encode_alias(&format!("{}_{}", prefix, alias));
- path.push(format!("user_{}", user_id));
+ let mut path = self.make_user_path_name(user_id);
path.push(format!(".{}_chr_{}", uid, encoded_alias));
path
}
- fn load_by_uid_alias(
+ fn make_super_key_filename(&self, user_id: u32) -> PathBuf {
+ let mut path = self.make_user_path_name(user_id);
+ path.push(".masterkey");
+ path
+ }
+
+ fn make_user_path_name(&self, user_id: u32) -> PathBuf {
+ let mut path = self.path.clone();
+ path.push(&format!("user_{}", user_id));
+ path
+ }
+
+ /// Returns if the legacy blob database is empty, i.e., there are no entries matching "user_*"
+ /// in the database dir.
+ pub fn is_empty(&self) -> Result<bool> {
+ let dir = Self::with_retry_interrupted(|| fs::read_dir(self.path.as_path()))
+ .context("In is_empty: Failed to open legacy blob database.")?;
+ for entry in dir {
+ if (*entry.context("In is_empty: Trying to access dir entry")?.file_name())
+ .to_str()
+ .map_or(false, |f| f.starts_with("user_"))
+ {
+ return Ok(false);
+ }
+ }
+ Ok(true)
+ }
+
+ /// Returns if the legacy blob database is empty for a given user, i.e., there are no entries
+ /// matching "user_*" in the database dir.
+ pub fn is_empty_user(&self, user_id: u32) -> Result<bool> {
+ let mut user_path = self.path.clone();
+ user_path.push(format!("user_{}", user_id));
+ if !user_path.as_path().is_dir() {
+ return Ok(true);
+ }
+ Ok(Self::with_retry_interrupted(|| user_path.read_dir())
+ .context("In is_empty_user: Failed to open legacy user dir.")?
+ .next()
+ .is_none())
+ }
+
+ fn extract_alias(encoded_alias: &str) -> Option<String> {
+ // We can check the encoded alias because the prefixes we are interested
+ // in are all in the printable range that don't get mangled.
+ for prefix in &["USRPKEY_", "USRSKEY_", "USRCERT_", "CACERT_"] {
+ if let Some(alias) = encoded_alias.strip_prefix(prefix) {
+ return Self::decode_alias(&alias).ok();
+ }
+ }
+ None
+ }
+
+ /// List all entries for a given user. The strings are unchanged file names, i.e.,
+ /// encoded with UID prefix.
+ fn list_user(&self, user_id: u32) -> Result<Vec<String>> {
+ let path = self.make_user_path_name(user_id);
+ let dir =
+ Self::with_retry_interrupted(|| fs::read_dir(path.as_path())).with_context(|| {
+ format!("In list_user: Failed to open legacy blob database. {:?}", path)
+ })?;
+ let mut result: Vec<String> = Vec::new();
+ for entry in dir {
+ let file_name = entry.context("In list_user: Trying to access dir entry")?.file_name();
+ if let Some(f) = file_name.to_str() {
+ result.push(f.to_string())
+ }
+ }
+ Ok(result)
+ }
+
+ /// List all keystore entries belonging to the given uid.
+ pub fn list_keystore_entries_for_uid(&self, uid: u32) -> Result<Vec<String>> {
+ let user_id = uid_to_android_user(uid);
+
+ let user_entries = self
+ .list_user(user_id)
+ .context("In list_keystore_entries_for_uid: Trying to list user.")?;
+
+ let uid_str = format!("{}_", uid);
+
+ let mut result: Vec<String> = user_entries
+ .into_iter()
+ .filter_map(|v| {
+ if !v.starts_with(&uid_str) {
+ return None;
+ }
+ let encoded_alias = &v[uid_str.len()..];
+ Self::extract_alias(encoded_alias)
+ })
+ .collect();
+
+ result.sort_unstable();
+ result.dedup();
+ Ok(result)
+ }
+
+ fn with_retry_interrupted<F, T>(f: F) -> IoResult<T>
+ where
+ F: Fn() -> IoResult<T>,
+ {
+ loop {
+ match f() {
+ Ok(v) => return Ok(v),
+ Err(e) => match e.kind() {
+ ErrorKind::Interrupted => continue,
+ _ => return Err(e),
+ },
+ }
+ }
+ }
+
+ /// Deletes a keystore entry. Also removes the user_<uid> directory on the
+ /// last migration.
+ pub fn remove_keystore_entry(&self, uid: u32, alias: &str) -> Result<bool> {
+ let mut something_was_deleted = false;
+ let prefixes = ["USRPKEY", "USRSKEY"];
+ for prefix in &prefixes {
+ let path = self.make_blob_filename(uid, alias, prefix);
+ if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
+ match e.kind() {
+ // Only a subset of keys are expected.
+ ErrorKind::NotFound => continue,
+ // Log error but ignore.
+ _ => log::error!("Error while deleting key blob entries. {:?}", e),
+ }
+ }
+ let path = self.make_chr_filename(uid, alias, prefix);
+ if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
+ match e.kind() {
+ ErrorKind::NotFound => {
+ log::info!("No characteristics file found for legacy key blob.")
+ }
+ // Log error but ignore.
+ _ => log::error!("Error while deleting key blob entries. {:?}", e),
+ }
+ }
+ something_was_deleted = true;
+ // Only one of USRPKEY and USRSKEY can be present. So we can end the loop
+ // if we reach this point.
+ break;
+ }
+
+ let prefixes = ["USRCERT", "CACERT"];
+ for prefix in &prefixes {
+ let path = self.make_blob_filename(uid, alias, prefix);
+ if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
+ match e.kind() {
+ // USRCERT and CACERT are optional either or both may or may not be present.
+ ErrorKind::NotFound => continue,
+ // Log error but ignore.
+ _ => log::error!("Error while deleting key blob entries. {:?}", e),
+ }
+ something_was_deleted = true;
+ }
+ }
+
+ if something_was_deleted {
+ let user_id = uid_to_android_user(uid);
+ if self
+ .is_empty_user(user_id)
+ .context("In remove_keystore_entry: Trying to check for empty user dir.")?
+ {
+ let user_path = self.make_user_path_name(user_id);
+ Self::with_retry_interrupted(|| fs::remove_dir(user_path.as_path())).ok();
+ }
+ }
+
+ Ok(something_was_deleted)
+ }
+
+ /// Load a legacy key blob entry by uid and alias.
+ pub fn load_by_uid_alias(
&self,
uid: u32,
alias: &str,
- key_manager: &SuperKeyManager,
- ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>, KeyMetaData)>
- {
- let metadata = KeyMetaData::new();
-
+ key_manager: Option<&SuperKeyManager>,
+ ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
let km_blob = self.read_km_blob_file(uid, alias).context("In load_by_uid_alias.")?;
let km_blob = match km_blob {
Some((km_blob, prefix)) => {
- let km_blob =
- match km_blob {
- Blob { flags: _, value: BlobValue::Decrypted(_) } => km_blob,
- // Unwrap the key blob if required.
- Blob { flags, value: BlobValue::Encrypted { iv, tag, data } } => {
+ let km_blob = match km_blob {
+ Blob { flags: _, value: BlobValue::Decrypted(_) } => km_blob,
+ // Unwrap the key blob if required and if we have key_manager.
+ Blob { flags, value: BlobValue::Encrypted { ref iv, ref tag, ref data } } => {
+ if let Some(key_manager) = key_manager {
let decrypted = match key_manager
.get_per_boot_key_by_user_id(uid_to_android_user(uid))
{
- Some(key) => aes_gcm_decrypt(&data, &iv, &tag, &key).context(
+ Some(key) => aes_gcm_decrypt(data, iv, tag, &(key.get_key()))
+ .context(
"In load_by_uid_alias: while trying to decrypt legacy blob.",
)?,
None => {
@@ -687,11 +858,16 @@
}
};
Blob { flags, value: BlobValue::Decrypted(decrypted) }
+ } else {
+ km_blob
}
- _ => return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
+ }
+ _ => {
+ return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
"In load_by_uid_alias: Found wrong blob type in legacy key blob file.",
- ),
- };
+ )
+ }
+ };
let hw_sec_level = match km_blob.is_strongbox() {
true => SecurityLevel::STRONGBOX,
@@ -729,14 +905,17 @@
}
};
- Ok((km_blob, user_cert, ca_cert, metadata))
+ Ok((km_blob, user_cert, ca_cert))
+ }
+
+ /// Returns true if the given user has a super key.
+ pub fn has_super_key(&self, user_id: u32) -> bool {
+ self.make_super_key_filename(user_id).is_file()
}
/// Load and decrypt legacy super key blob.
pub fn load_super_key(&self, user_id: u32, pw: &[u8]) -> Result<Option<ZVec>> {
- let mut path = self.path.clone();
- path.push(&format!("user_{}", user_id));
- path.push(".masterkey");
+ let path = self.make_super_key_filename(user_id);
let blob = Self::read_generic_blob(&path)
.context("In load_super_key: While loading super key.")?;
@@ -763,6 +942,18 @@
Ok(blob)
}
+
+ /// Removes the super key for the given user from the legacy database.
+ /// If this was the last entry in the user's database, this function removes
+ /// the user_<uid> directory as well.
+ pub fn remove_super_key(&self, user_id: u32) {
+ let path = self.make_super_key_filename(user_id);
+ Self::with_retry_interrupted(|| fs::remove_file(path.as_path())).ok();
+ if self.is_empty_user(user_id).ok().unwrap_or(false) {
+ let path = self.make_user_path_name(user_id);
+ Self::with_retry_interrupted(|| fs::remove_dir(path.as_path())).ok();
+ }
+ }
}
#[cfg(test)]
@@ -897,6 +1088,37 @@
}
#[test]
+ fn test_is_empty() {
+ let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir.");
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty."));
+
+ let _db = crate::database::KeystoreDB::new(temp_dir.path(), None)
+ .expect("Failed to open database.");
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty."));
+
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty."));
+
+ std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
+
+ std::fs::remove_dir_all(&*temp_dir.build().push("user_0"))
+ .expect("Failed to remove user_0.");
+
+ assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty."));
+
+ std::fs::remove_dir_all(&*temp_dir.build().push("user_10"))
+ .expect("Failed to remove user_10.");
+
+ assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again."));
+ }
+
+ #[test]
fn test_legacy_blobs() -> anyhow::Result<()> {
let temp_dir = TempDir::new("legacy_blob_test")?;
std::fs::create_dir(&*temp_dir.build().push("user_0"))?;
@@ -943,27 +1165,27 @@
assert_eq!(
legacy_blob_loader
- .load_by_uid_alias(10223, "authbound", &key_manager)
+ .load_by_uid_alias(10223, "authbound", Some(&key_manager))
.unwrap_err()
.root_cause()
.downcast_ref::<error::Error>(),
Some(&error::Error::Rc(ResponseCode::LOCKED))
);
- key_manager.unlock_user_key(0, PASSWORD, &mut db, &legacy_blob_loader)?;
+ key_manager.unlock_user_key(&mut db, 0, PASSWORD, &legacy_blob_loader)?;
- if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain), _kp) =
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", &key_manager)?
+ if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) =
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
{
assert_eq!(flags, 4);
- assert_eq!(value, BlobValue::Decrypted(DECRYPTED_USRPKEY_AUTHBOUND.try_into()?));
+ //assert_eq!(value, BlobValue::Encrypted(..));
assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
} else {
panic!("");
}
- if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain), _kp) =
- legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &key_manager)?
+ if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
{
assert_eq!(flags, 0);
assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
@@ -973,6 +1195,33 @@
panic!("");
}
+ legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+ legacy_blob_loader
+ .remove_keystore_entry(10223, "non_authbound")
+ .expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
+ );
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty()?);
+ assert!(!legacy_blob_loader.is_empty_user(0)?);
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1)?);
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0)?);
+ assert!(legacy_blob_loader.is_empty()?);
+
Ok(())
}
}
diff --git a/keystore2/src/legacy_migrator.rs b/keystore2/src/legacy_migrator.rs
new file mode 100644
index 0000000..60c6bca
--- /dev/null
+++ b/keystore2/src/legacy_migrator.rs
@@ -0,0 +1,570 @@
+// 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.
+
+//! This module acts as a bridge between the legacy key database and the keystore2 database.
+
+use crate::database::{
+ BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, EncryptedBy, KeyMetaData, KeyMetaEntry,
+ KeystoreDB, Uuid, KEYSTORE_UUID,
+};
+use crate::error::Error;
+use crate::legacy_blob::BlobValue;
+use crate::utils::uid_to_android_user;
+use crate::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
+};
+use anyhow::{Context, Result};
+use core::ops::Deref;
+use keystore2_crypto::ZVec;
+use std::collections::{HashMap, HashSet};
+use std::convert::TryInto;
+use std::sync::atomic::{AtomicU8, Ordering};
+use std::sync::mpsc::channel;
+use std::sync::{Arc, Mutex};
+
+/// Represents LegacyMigrator.
+pub struct LegacyMigrator {
+ async_task: Arc<AsyncTask>,
+ initializer: Mutex<
+ Option<
+ Box<
+ dyn FnOnce() -> (KeystoreDB, HashMap<SecurityLevel, Uuid>, Arc<LegacyBlobLoader>)
+ + Send
+ + 'static,
+ >,
+ >,
+ >,
+ /// This atomic is used for cheap interior mutability. It is intended to prevent
+ /// expensive calls into the legacy migrator when the legacy database is empty.
+ /// When transitioning from READY to EMPTY, spurious calls may occur for a brief period
+ /// of time. This is tolerable in favor of the common case.
+ state: AtomicU8,
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+struct RecentMigration {
+ uid: u32,
+ alias: String,
+}
+
+impl RecentMigration {
+ fn new(uid: u32, alias: String) -> Self {
+ Self { uid, alias }
+ }
+}
+
+struct LegacyMigratorState {
+ recently_migrated: HashSet<RecentMigration>,
+ recently_migrated_super_key: HashSet<u32>,
+ legacy_loader: Arc<LegacyBlobLoader>,
+ sec_level_to_km_uuid: HashMap<SecurityLevel, Uuid>,
+ db: KeystoreDB,
+}
+
+impl LegacyMigrator {
+ const WIFI_NAMESPACE: i64 = 102;
+ const AID_WIFI: u32 = 1010;
+
+ const STATE_UNINITIALIZED: u8 = 0;
+ const STATE_READY: u8 = 1;
+ const STATE_EMPTY: u8 = 2;
+
+ /// Constructs a new LegacyMigrator using the given AsyncTask object as migration
+ /// worker.
+ pub fn new(async_task: Arc<AsyncTask>) -> Self {
+ Self {
+ async_task,
+ initializer: Default::default(),
+ state: AtomicU8::new(Self::STATE_UNINITIALIZED),
+ }
+ }
+
+ /// The legacy migrator must be initialized deferred, because keystore starts very early.
+ /// At this time the data partition may not be mounted. So we cannot open database connections
+ /// until we get actual key load requests. This sets the function that the legacy loader
+ /// uses to connect to the database.
+ pub fn set_init<F>(&self, f_init: F) -> Result<()>
+ where
+ F: FnOnce() -> (KeystoreDB, HashMap<SecurityLevel, Uuid>, Arc<LegacyBlobLoader>)
+ + Send
+ + 'static,
+ {
+ let mut initializer = self.initializer.lock().expect("Failed to lock initializer.");
+
+ // If we are not uninitialized we have no business setting the initializer.
+ if self.state.load(Ordering::Relaxed) != Self::STATE_UNINITIALIZED {
+ return Ok(());
+ }
+
+ // Only set the initializer if it hasn't been set before.
+ if initializer.is_none() {
+ *initializer = Some(Box::new(f_init))
+ }
+
+ Ok(())
+ }
+
+ /// This function is called by the migration requestor to check if it is worth
+ /// making a migration request. It also transitions the state from UNINITIALIZED
+ /// to READY or EMPTY on first use. The deferred initialization is necessary, because
+ /// Keystore 2.0 runs early during boot, where data may not yet be mounted.
+ /// Returns Ok(STATE_READY) if a migration request is worth undertaking and
+ /// Ok(STATE_EMPTY) if the database is empty. An error is returned if the loader
+ /// was not initialized and cannot be initialized.
+ fn check_state(&self) -> Result<u8> {
+ let mut first_try = true;
+ loop {
+ match (self.state.load(Ordering::Relaxed), first_try) {
+ (Self::STATE_EMPTY, _) => {
+ return Ok(Self::STATE_EMPTY);
+ }
+ (Self::STATE_UNINITIALIZED, true) => {
+ // If we find the legacy loader uninitialized, we grab the initializer lock,
+ // check if the legacy database is empty, and if not, schedule an initialization
+ // request. Coming out of the initializer lock, the state is either EMPTY or
+ // READY.
+ let mut initializer = self.initializer.lock().unwrap();
+
+ if let Some(initializer) = initializer.take() {
+ let (db, sec_level_to_km_uuid, legacy_loader) = (initializer)();
+
+ if legacy_loader.is_empty().context(
+ "In check_state: Trying to check if the legacy database is empty.",
+ )? {
+ self.state.store(Self::STATE_EMPTY, Ordering::Relaxed);
+ return Ok(Self::STATE_EMPTY);
+ }
+
+ self.async_task.queue_hi(move |shelf| {
+ shelf.get_or_put_with(|| LegacyMigratorState {
+ recently_migrated: Default::default(),
+ recently_migrated_super_key: Default::default(),
+ legacy_loader,
+ sec_level_to_km_uuid,
+ db,
+ });
+ });
+
+ // It is safe to set this here even though the async task may not yet have
+ // run because any thread observing this will not be able to schedule a
+ // task that can run before the initialization.
+ // Also we can only transition out of this state while having the
+ // initializer lock and having found an initializer.
+ self.state.store(Self::STATE_READY, Ordering::Relaxed);
+ return Ok(Self::STATE_READY);
+ } else {
+ // There is a chance that we just lost the race from state.load() to
+ // grabbing the initializer mutex. If that is the case the state must
+ // be EMPTY or READY after coming out of the lock. So we can give it
+ // one more try.
+ first_try = false;
+ continue;
+ }
+ }
+ (Self::STATE_UNINITIALIZED, false) => {
+ // Okay, tough luck. The legacy loader was really completely uninitialized.
+ return Err(Error::sys()).context(
+ "In check_state: Legacy loader should not be called uninitialized.",
+ );
+ }
+ (Self::STATE_READY, _) => return Ok(Self::STATE_READY),
+ (s, _) => panic!("Unknown legacy migrator state. {} ", s),
+ }
+ }
+ }
+
+ /// List all aliases for uid in the legacy database.
+ pub fn list_uid(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
+ let uid = match (domain, namespace) {
+ (Domain::APP, namespace) => namespace as u32,
+ (Domain::SELINUX, Self::WIFI_NAMESPACE) => Self::AID_WIFI,
+ _ => return Ok(Vec::new()),
+ };
+ self.do_serialized(move |state| state.list_uid(uid)).unwrap_or_else(|| Ok(Vec::new())).map(
+ |v| {
+ v.into_iter()
+ .map(|alias| KeyDescriptor {
+ domain,
+ nspace: namespace,
+ alias: Some(alias),
+ blob: None,
+ })
+ .collect()
+ },
+ )
+ }
+
+ /// Sends the given closure to the migrator thread for execution after calling check_state.
+ /// Returns None if the database was empty and the request was not executed.
+ /// Otherwise returns Some with the result produced by the migration request.
+ /// The loader state may transition to STATE_EMPTY during the execution of this function.
+ fn do_serialized<F, T: Send + 'static>(&self, f: F) -> Option<Result<T>>
+ where
+ F: FnOnce(&mut LegacyMigratorState) -> Result<T> + Send + 'static,
+ {
+ // Short circuit if the database is empty or not initialized (error case).
+ match self.check_state().context("In do_serialized: Checking state.") {
+ Ok(LegacyMigrator::STATE_EMPTY) => return None,
+ Ok(LegacyMigrator::STATE_READY) => {}
+ Err(e) => return Some(Err(e)),
+ Ok(s) => panic!("Unknown legacy migrator state. {} ", s),
+ }
+
+ // We have established that there may be a key in the legacy database.
+ // Now we schedule a migration request.
+ let (sender, receiver) = channel();
+ self.async_task.queue_hi(move |shelf| {
+ // Get the migrator state from the shelf.
+ // There may not be a state. This can happen if this migration request was scheduled
+ // before a previous request established that the legacy database was empty
+ // and removed the state from the shelf. Since we know now that the database
+ // is empty, we can return None here.
+ let (new_state, result) = if let Some(legacy_migrator_state) =
+ shelf.get_downcast_mut::<LegacyMigratorState>()
+ {
+ let result = f(legacy_migrator_state);
+ (legacy_migrator_state.check_empty(), Some(result))
+ } else {
+ (Self::STATE_EMPTY, None)
+ };
+
+ // If the migration request determined that the database is now empty, we discard
+ // the state from the shelf to free up the resources we won't need any longer.
+ if result.is_some() && new_state == Self::STATE_EMPTY {
+ shelf.remove_downcast_ref::<LegacyMigratorState>();
+ }
+
+ // Send the result to the requester.
+ if let Err(e) = sender.send((new_state, result)) {
+ log::error!("In do_serialized. Error in sending the result. {:?}", e);
+ }
+ });
+
+ let (new_state, result) = match receiver.recv() {
+ Err(e) => {
+ return Some(Err(e).context("In do_serialized. Failed to receive from the sender."))
+ }
+ Ok(r) => r,
+ };
+
+ // We can only transition to EMPTY but never back.
+ // The migrator never creates any legacy blobs.
+ if new_state == Self::STATE_EMPTY {
+ self.state.store(Self::STATE_EMPTY, Ordering::Relaxed)
+ }
+
+ result
+ }
+
+ /// Runs the key_accessor function and returns its result. If it returns an error and the
+ /// root cause was KEY_NOT_FOUND, tries to migrate a key with the given parameters from
+ /// the legacy database to the new database and runs the key_accessor function again if
+ /// the migration request was successful.
+ pub fn with_try_migrate<F, T>(
+ &self,
+ key: &KeyDescriptor,
+ caller_uid: u32,
+ key_accessor: F,
+ ) -> Result<T>
+ where
+ F: Fn() -> Result<T>,
+ {
+ // Access the key and return on success.
+ match key_accessor() {
+ Ok(result) => return Ok(result),
+ Err(e) => match e.root_cause().downcast_ref::<Error>() {
+ Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => {}
+ _ => return Err(e),
+ },
+ }
+
+ // Filter inputs. We can only load legacy app domain keys and some special rules due
+ // to which we migrate keys transparently to an SELINUX domain.
+ let uid = match key {
+ KeyDescriptor { domain: Domain::APP, alias: Some(_), .. } => caller_uid,
+ KeyDescriptor { domain: Domain::SELINUX, nspace, alias: Some(_), .. } => {
+ match *nspace {
+ Self::WIFI_NAMESPACE => Self::AID_WIFI,
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context(format!("No legacy keys for namespace {}", nspace))
+ }
+ }
+ }
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("No legacy keys for key descriptor.")
+ }
+ };
+
+ let key_clone = key.clone();
+ let result = self
+ .do_serialized(move |migrator_state| migrator_state.check_and_migrate(uid, key_clone));
+
+ if let Some(result) = result {
+ result?;
+ // After successful migration try again.
+ key_accessor()
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context("Legacy database is empty.")
+ }
+ }
+
+ /// Calls key_accessor and returns the result on success. In the case of a KEY_NOT_FOUND error
+ /// this function makes a migration request and on success retries the key_accessor.
+ pub fn with_try_migrate_super_key<F, T>(
+ &self,
+ user_id: u32,
+ pw: &[u8],
+ mut key_accessor: F,
+ ) -> Result<Option<T>>
+ where
+ F: FnMut() -> Result<Option<T>>,
+ {
+ match key_accessor() {
+ Ok(Some(result)) => return Ok(Some(result)),
+ Ok(None) => {}
+ Err(e) => return Err(e),
+ }
+
+ let pw: ZVec = pw
+ .try_into()
+ .context("In with_try_migrate_super_key: copying the password into a zvec.")?;
+ let result = self.do_serialized(move |migrator_state| {
+ migrator_state.check_and_migrate_super_key(user_id, pw)
+ });
+
+ if let Some(result) = result {
+ result?;
+ // After successful migration try again.
+ key_accessor()
+ } else {
+ Ok(None)
+ }
+ }
+
+ /// Queries the legacy database for the presence of a super key for the given user.
+ pub fn has_super_key(&self, user_id: u32) -> Result<bool> {
+ let result =
+ self.do_serialized(move |migrator_state| migrator_state.has_super_key(user_id));
+ result.unwrap_or(Ok(false))
+ }
+}
+
+impl LegacyMigratorState {
+ fn get_km_uuid(&self, is_strongbox: bool) -> Result<Uuid> {
+ let sec_level = if is_strongbox {
+ SecurityLevel::STRONGBOX
+ } else {
+ SecurityLevel::TRUSTED_ENVIRONMENT
+ };
+
+ self.sec_level_to_km_uuid.get(&sec_level).copied().ok_or_else(|| {
+ anyhow::anyhow!(Error::sys()).context("In get_km_uuid: No KM instance for blob.")
+ })
+ }
+
+ fn list_uid(&mut self, uid: u32) -> Result<Vec<String>> {
+ self.legacy_loader
+ .list_keystore_entries_for_uid(uid)
+ .context("In list_uid: Trying to list legacy entries.")
+ }
+
+ /// This is a key migration request that can run in the migrator thread. This should
+ /// be passed to do_serialized.
+ fn check_and_migrate(&mut self, uid: u32, mut key: KeyDescriptor) -> Result<()> {
+ let alias = key.alias.clone().ok_or_else(|| {
+ anyhow::anyhow!(Error::sys()).context(concat!(
+ "In check_and_migrate: Must be Some because ",
+ "our caller must not have called us otherwise."
+ ))
+ })?;
+
+ if self.recently_migrated.contains(&RecentMigration::new(uid, alias.clone())) {
+ return Ok(());
+ }
+
+ if key.domain == Domain::APP {
+ key.nspace = uid as i64;
+ }
+
+ // If the key is not found in the cache, try to load from the legacy database.
+ let (km_blob_params, user_cert, ca_cert) = self
+ .legacy_loader
+ .load_by_uid_alias(uid, &alias, None)
+ .context("In check_and_migrate: Trying to load legacy blob.")?;
+ let result = match km_blob_params {
+ Some((km_blob, params)) => {
+ let is_strongbox = km_blob.is_strongbox();
+ let (blob, mut blob_metadata) = match km_blob.take_value() {
+ BlobValue::Encrypted { iv, tag, data } => {
+ // Get super key id for user id.
+ let user_id = uid_to_android_user(uid as u32);
+
+ let super_key_id = match self
+ .db
+ .load_super_key(user_id)
+ .context("In check_and_migrate: Failed to load super key")?
+ {
+ Some((_, entry)) => entry.id(),
+ None => {
+ // This might be the first time we access the super key,
+ // and it may not have been migrated. We cannot import
+ // the legacy super_key key now, because we need to reencrypt
+ // it which we cannot do if we are not unlocked, which we are
+ // not because otherwise the key would have been migrated.
+ // We can check though if the key exists. If it does,
+ // we can return Locked. Otherwise, we can delete the
+ // key and return NotFound, because the key will never
+ // be unlocked again.
+ if self.legacy_loader.has_super_key(user_id) {
+ return Err(Error::Rc(ResponseCode::LOCKED)).context(concat!(
+ "In check_and_migrate: Cannot migrate super key of this ",
+ "key while user is locked."
+ ));
+ } else {
+ self.legacy_loader.remove_keystore_entry(uid, &alias).context(
+ concat!(
+ "In check_and_migrate: ",
+ "Trying to remove obsolete key."
+ ),
+ )?;
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In check_and_migrate: Obsolete key.");
+ }
+ }
+ };
+
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
+ blob_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
+ blob_metadata
+ .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+ (LegacyBlob::Vec(data), blob_metadata)
+ }
+ BlobValue::Decrypted(data) => (LegacyBlob::ZVec(data), BlobMetaData::new()),
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In check_and_migrate: Legacy key has unexpected type.")
+ }
+ };
+
+ let km_uuid = self
+ .get_km_uuid(is_strongbox)
+ .context("In check_and_migrate: Trying to get KM UUID")?;
+ blob_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+
+ let mut metadata = KeyMetaData::new();
+ let creation_date = DateTime::now()
+ .context("In check_and_migrate: Trying to make creation time.")?;
+ metadata.add(KeyMetaEntry::CreationDate(creation_date));
+
+ // Store legacy key in the database.
+ self.db
+ .store_new_key(
+ &key,
+ ¶ms,
+ &(&blob, &blob_metadata),
+ &CertificateInfo::new(user_cert, ca_cert),
+ &metadata,
+ &km_uuid,
+ )
+ .context("In check_and_migrate.")?;
+ Ok(())
+ }
+ None => {
+ if let Some(ca_cert) = ca_cert {
+ self.db
+ .store_new_certificate(&key, &ca_cert, &KEYSTORE_UUID)
+ .context("In check_and_migrate: Failed to insert new certificate.")?;
+ Ok(())
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In check_and_migrate: Legacy key not found.")
+ }
+ }
+ };
+
+ match result {
+ Ok(()) => {
+ // Add the key to the migrated_keys list.
+ self.recently_migrated.insert(RecentMigration::new(uid, alias.clone()));
+ // Delete legacy key from the file system
+ self.legacy_loader
+ .remove_keystore_entry(uid, &alias)
+ .context("In check_and_migrate: Trying to remove migrated key.")?;
+ Ok(())
+ }
+ Err(e) => Err(e),
+ }
+ }
+
+ fn check_and_migrate_super_key(&mut self, user_id: u32, pw: ZVec) -> Result<()> {
+ if self.recently_migrated_super_key.contains(&user_id) {
+ return Ok(());
+ }
+
+ if let Some(super_key) = self
+ .legacy_loader
+ .load_super_key(user_id, &pw)
+ .context("In check_and_migrate_super_key: Trying to load legacy super key.")?
+ {
+ let (blob, blob_metadata) =
+ crate::super_key::SuperKeyManager::encrypt_with_password(&super_key, &pw)
+ .context("In check_and_migrate_super_key: Trying to encrypt super key.")?;
+
+ self.db.store_super_key(user_id, &(&blob, &blob_metadata)).context(concat!(
+ "In check_and_migrate_super_key: ",
+ "Trying to insert legacy super_key into the database."
+ ))?;
+ self.legacy_loader.remove_super_key(user_id);
+ self.recently_migrated_super_key.insert(user_id);
+ Ok(())
+ } else {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In check_and_migrate_super_key: No key found do migrate.")
+ }
+ }
+
+ fn has_super_key(&mut self, user_id: u32) -> Result<bool> {
+ Ok(self.recently_migrated_super_key.contains(&user_id)
+ || self.legacy_loader.has_super_key(user_id))
+ }
+
+ fn check_empty(&self) -> u8 {
+ if self.legacy_loader.is_empty().unwrap_or(false) {
+ LegacyMigrator::STATE_EMPTY
+ } else {
+ LegacyMigrator::STATE_READY
+ }
+ }
+}
+
+enum LegacyBlob {
+ Vec(Vec<u8>),
+ ZVec(ZVec),
+}
+
+impl Deref for LegacyBlob {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ Self::Vec(v) => &v,
+ Self::ZVec(v) => &v,
+ }
+ }
+}
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index f9554ea..358fce8 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -24,11 +24,13 @@
/// Internal Representation of Key Parameter and convenience functions.
pub mod key_parameter;
pub mod legacy_blob;
+pub mod legacy_migrator;
pub mod operation;
pub mod permission;
pub mod remote_provisioning;
pub mod security_level;
pub mod service;
+pub mod user_manager;
pub mod utils;
mod async_task;
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index 0f0ca04..576ac3f 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -299,9 +299,15 @@
/// Checked when Keystore 2.0 gets locked.
Lock = 0x10, selinux name: lock;
/// Checked when Keystore 2.0 shall be reset.
- Reset = 0x20, selinux name: reset;
+ Reset = 0x20, selinux name: reset;
/// Checked when Keystore 2.0 shall be unlocked.
- Unlock = 0x40, selinux name: unlock;
+ Unlock = 0x40, selinux name: unlock;
+ /// Checked when user is added or removed.
+ ChangeUser = 0x80, selinux name: change_user;
+ /// Checked when password of the user is changed.
+ ChangePassword = 0x100, selinux name: change_password;
+ /// Checked when a UID is cleared.
+ ClearUID = 0x200, selinux name: clear_uid;
}
);
@@ -659,6 +665,11 @@
assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::lock()).is_ok());
assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::reset()).is_ok());
assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::unlock()).is_ok());
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::change_user()).is_ok());
+ assert!(
+ check_keystore_permission(&system_server_ctx, KeystorePerm::change_password()).is_ok()
+ );
+ assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::clear_uid()).is_ok());
let shell_ctx = Context::new("u:r:shell:s0")?;
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::add_auth()));
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_ns()));
@@ -667,6 +678,9 @@
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::lock()));
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::reset()));
assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::unlock()));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_user()));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::change_password()));
+ assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::clear_uid()));
Ok(())
}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index 37e43c6..5e1ce84 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -32,9 +32,11 @@
KeyMetadata::KeyMetadata, KeyParameters::KeyParameters,
};
-use crate::globals::ENFORCEMENTS;
+use crate::database::{CertificateInfo, KeyIdGuard};
+use crate::globals::{DB, ENFORCEMENTS, LEGACY_MIGRATOR, SUPER_KEY};
use crate::key_parameter::KeyParameter as KsKeyParam;
use crate::key_parameter::KeyParameterValue as KsKeyParamValue;
+use crate::super_key::{KeyBlob, SuperKeyManager};
use crate::utils::{check_key_permission, uid_to_android_user, Asp};
use crate::{
database::{
@@ -46,10 +48,6 @@
permission::KeyPerm,
};
use crate::{
- database::{CertificateInfo, KeyIdGuard},
- globals::DB,
-};
-use crate::{
error::{self, map_km_error, map_or_log_err, Error, ErrorCode},
utils::key_characteristics_to_internal,
};
@@ -100,6 +98,7 @@
key: KeyDescriptor,
creation_result: KeyCreationResult,
user_id: u32,
+ flags: Option<i32>,
) -> Result<KeyMetadata> {
let KeyCreationResult {
keyBlob: key_blob,
@@ -132,6 +131,20 @@
SecurityLevel::SOFTWARE,
));
+ let (key_blob, mut blob_metadata) = DB
+ .with(|db| {
+ SUPER_KEY.handle_super_encryption_on_key_init(
+ &mut db.borrow_mut(),
+ &LEGACY_MIGRATOR,
+ &(key.domain),
+ &key_parameters,
+ flags,
+ user_id,
+ &key_blob,
+ )
+ })
+ .context("In store_new_key. Failed to handle super encryption.")?;
+
let creation_date = DateTime::now().context("Trying to make creation time.")?;
let key = match key.domain {
@@ -142,7 +155,6 @@
.with::<_, Result<KeyDescriptor>>(|db| {
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));
let mut db = db.borrow_mut();
@@ -202,19 +214,21 @@
},
None,
None,
- None,
+ BlobMetaData::new(),
)
}
_ => {
let (key_id_guard, mut key_entry) = DB
.with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| {
- db.borrow_mut().load_key_entry(
- &key,
- KeyType::Client,
- KeyEntryLoadBits::KM,
- caller_uid,
- |k, av| check_key_permission(KeyPerm::use_(), k, &av),
- )
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &key,
+ KeyType::Client,
+ KeyEntryLoadBits::KM,
+ caller_uid,
+ |k, av| check_key_permission(KeyPerm::use_(), k, &av),
+ )
+ })
})
.context("In create_operation: Failed to load key blob.")?;
@@ -229,7 +243,7 @@
&scoping_blob,
Some((key_id_guard.id(), key_entry.into_key_parameters())),
Some(key_id_guard),
- Some(blob_metadata),
+ blob_metadata,
)
}
};
@@ -258,6 +272,12 @@
let immediate_hat = immediate_hat.unwrap_or_default();
+ let user_id = uid_to_android_user(caller_uid);
+
+ let km_blob = SUPER_KEY
+ .unwrap_key_if_required(&blob_metadata, km_blob)
+ .context("In create_operation. Failed to handle super encryption.")?;
+
let km_dev: Strong<dyn IKeyMintDevice> = self
.keymint
.get_interface()
@@ -267,7 +287,7 @@
.upgrade_keyblob_if_required_with(
&*km_dev,
key_id_guard,
- &(km_blob, blob_metadata.as_ref()),
+ &(&km_blob, &blob_metadata),
&operation_parameters,
|blob| loop {
match map_km_error(km_dev.begin(
@@ -403,7 +423,7 @@
.context("In generate_key: While generating Key")?;
let user_id = uid_to_android_user(caller_uid);
- self.store_new_key(key, creation_result, user_id).context("In generate_key.")
+ self.store_new_key(key, creation_result, user_id, Some(flags)).context("In generate_key.")
}
fn get_attest_key(&self, key: &KeyDescriptor, caller_uid: u32) -> Result<AttestationKey> {
@@ -510,7 +530,7 @@
.context("In import_key: Trying to call importKey")?;
let user_id = uid_to_android_user(caller_uid);
- self.store_new_key(key, creation_result, user_id).context("In import_key.")
+ self.store_new_key(key, creation_result, user_id, Some(flags)).context("In import_key.")
}
fn import_wrapped_key(
@@ -544,6 +564,8 @@
}
let caller_uid = ThreadState::get_calling_uid();
+ let user_id = uid_to_android_user(caller_uid);
+
let key = match key.domain {
Domain::APP => KeyDescriptor {
domain: key.domain,
@@ -563,26 +585,29 @@
// Import_wrapped_key requires the rebind permission for the new key.
check_key_permission(KeyPerm::rebind(), &key, &None).context("In import_wrapped_key.")?;
- let (wrapping_key_id_guard, wrapping_key_entry) = DB
+ let (wrapping_key_id_guard, mut wrapping_key_entry) = DB
.with(|db| {
- db.borrow_mut().load_key_entry(
- &wrapping_key,
- KeyType::Client,
- KeyEntryLoadBits::KM,
- caller_uid,
- |k, av| check_key_permission(KeyPerm::use_(), k, &av),
- )
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &wrapping_key,
+ KeyType::Client,
+ KeyEntryLoadBits::KM,
+ caller_uid,
+ |k, av| check_key_permission(KeyPerm::use_(), k, &av),
+ )
+ })
})
.context("Failed to load wrapping key.")?;
- let (wrapping_key_blob, wrapping_blob_metadata) = match wrapping_key_entry.key_blob_info() {
- Some((blob, metadata)) => (blob, metadata),
- None => {
- return Err(error::Error::sys()).context(concat!(
- "No km_blob after successfully loading key.",
- " This should never happen."
- ))
- }
- };
+
+ let (wrapping_key_blob, wrapping_blob_metadata) = wrapping_key_entry
+ .take_key_blob_info()
+ .ok_or_else(error::Error::sys)
+ .context("No km_blob after successfully loading key. This should never happen.")?;
+
+ let wrapping_key_blob =
+ SUPER_KEY.unwrap_key_if_required(&wrapping_blob_metadata, &wrapping_key_blob).context(
+ "In import_wrapped_key. Failed to handle super encryption for wrapping key.",
+ )?;
// km_dev.importWrappedKey does not return a certificate chain.
// TODO Do we assume that all wrapped keys are symmetric?
@@ -611,7 +636,7 @@
.upgrade_keyblob_if_required_with(
&*km_dev,
Some(wrapping_key_id_guard),
- &(&wrapping_key_blob, Some(&wrapping_blob_metadata)),
+ &(&wrapping_key_blob, &wrapping_blob_metadata),
&[],
|wrapping_blob| {
let creation_result = map_km_error(km_dev.importWrappedKey(
@@ -627,8 +652,7 @@
)
.context("In import_wrapped_key.")?;
- let user_id = uid_to_android_user(caller_uid);
- self.store_new_key(key, creation_result, user_id)
+ self.store_new_key(key, creation_result, user_id, None)
.context("In import_wrapped_key: Trying to store the new key.")
}
@@ -636,7 +660,7 @@
&self,
km_dev: &dyn IKeyMintDevice,
key_id_guard: Option<KeyIdGuard>,
- blob_info: &(&[u8], Option<&BlobMetaData>),
+ blob_info: &(&KeyBlob, &BlobMetaData),
params: &[KeyParameter],
f: F,
) -> Result<(T, Option<Vec<u8>>)>
@@ -647,13 +671,26 @@
Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
let upgraded_blob = map_km_error(km_dev.upgradeKey(blob_info.0, params))
.context("In upgrade_keyblob_if_required_with: Upgrade failed.")?;
+
+ let (upgraded_blob_to_be_stored, blob_metadata) =
+ SuperKeyManager::reencrypt_on_upgrade_if_required(blob_info.0, &upgraded_blob)
+ .context(
+ "In upgrade_keyblob_if_required_with: Failed to handle super encryption.",
+ )?;
+
+ let mut blob_metadata = blob_metadata.unwrap_or_else(BlobMetaData::new);
+ if let Some(uuid) = blob_info.1.km_uuid() {
+ blob_metadata.add(BlobMetaEntry::KmUuid(*uuid));
+ }
+
key_id_guard.map_or(Ok(()), |key_id_guard| {
DB.with(|db| {
- db.borrow_mut().set_blob(
+ let mut db = db.borrow_mut();
+ db.set_blob(
&key_id_guard,
SubComponentType::KEY_BLOB,
- Some(&upgraded_blob),
- blob_info.1,
+ Some(&upgraded_blob_to_be_stored),
+ Some(&blob_metadata),
)
})
.context(concat!(
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index efd62e3..3a4bf82 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -27,7 +27,10 @@
check_grant_permission, check_key_permission, check_keystore_permission,
key_parameters_to_authorizations, Asp,
};
-use crate::{database::Uuid, globals::DB};
+use crate::{
+ database::Uuid,
+ globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_MIGRATOR},
+};
use crate::{database::KEYSTORE_UUID, permission};
use crate::{
database::{KeyEntryLoadBits, KeyType, SubComponentType},
@@ -73,6 +76,15 @@
result.uuid_by_sec_level.insert(SecurityLevel::STRONGBOX, uuid);
}
+ let uuid_by_sec_level = result.uuid_by_sec_level.clone();
+ LEGACY_MIGRATOR
+ .set_init(move || {
+ (create_thread_local_db(), uuid_by_sec_level, LEGACY_BLOB_LOADER.clone())
+ })
+ .context(
+ "In KeystoreService::new_native_binder: Trying to initialize the legacy migrator.",
+ )?;
+
let result = BnKeystoreService::new_binder(result);
result.as_binder().set_requesting_sid(true);
Ok(result)
@@ -112,15 +124,18 @@
}
fn get_key_entry(&self, key: &KeyDescriptor) -> Result<KeyEntryResponse> {
+ let caller_uid = ThreadState::get_calling_uid();
let (key_id_guard, mut key_entry) = DB
.with(|db| {
- db.borrow_mut().load_key_entry(
- &key,
- KeyType::Client,
- KeyEntryLoadBits::PUBLIC,
- ThreadState::get_calling_uid(),
- |k, av| check_key_permission(KeyPerm::get_info(), k, &av),
- )
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &key,
+ KeyType::Client,
+ KeyEntryLoadBits::PUBLIC,
+ caller_uid,
+ |k, av| check_key_permission(KeyPerm::get_info(), k, &av),
+ )
+ })
})
.context("In get_key_entry, while trying to load key info.")?;
@@ -161,18 +176,20 @@
public_cert: Option<&[u8]>,
certificate_chain: Option<&[u8]>,
) -> Result<()> {
+ let caller_uid = ThreadState::get_calling_uid();
DB.with::<_, Result<()>>(|db| {
- let mut db = db.borrow_mut();
- let entry = match db.load_key_entry(
- &key,
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- ThreadState::get_calling_uid(),
- |k, av| {
- check_key_permission(KeyPerm::update(), k, &av)
- .context("In update_subcomponent.")
- },
- ) {
+ let entry = match LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &key,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ caller_uid,
+ |k, av| {
+ check_key_permission(KeyPerm::update(), k, &av)
+ .context("In update_subcomponent.")
+ },
+ )
+ }) {
Err(e) => match e.root_cause().downcast_ref::<Error>() {
Some(Error::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None),
_ => Err(e),
@@ -181,6 +198,7 @@
}
.context("Failed to load key entry.")?;
+ let mut db = db.borrow_mut();
if let Some((key_id_guard, key_entry)) = entry {
db.set_blob(&key_id_guard, SubComponentType::CERT, public_cert, None)
.context("Failed to update cert subcomponent.")?;
@@ -258,17 +276,31 @@
Ok(()) => {}
};
- DB.with(|db| {
- let mut db = db.borrow_mut();
- db.list(k.domain, k.nspace)
- })
+ let mut result = LEGACY_MIGRATOR
+ .list_uid(k.domain, k.nspace)
+ .context("In list_entries: Trying to list legacy keys.")?;
+
+ result.append(
+ &mut DB
+ .with(|db| {
+ let mut db = db.borrow_mut();
+ db.list(k.domain, k.nspace)
+ })
+ .context("In list_entries: Trying to list keystore database.")?,
+ );
+
+ result.sort_unstable();
+ result.dedup();
+ Ok(result)
}
fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {
let caller_uid = ThreadState::get_calling_uid();
DB.with(|db| {
- db.borrow_mut().unbind_key(&key, KeyType::Client, caller_uid, |k, av| {
- check_key_permission(KeyPerm::delete(), k, &av).context("During delete_key.")
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().unbind_key(&key, KeyType::Client, caller_uid, |k, av| {
+ check_key_permission(KeyPerm::delete(), k, &av).context("During delete_key.")
+ })
})
})
.context("In delete_key: Trying to unbind the key.")?;
@@ -281,14 +313,17 @@
grantee_uid: i32,
access_vector: permission::KeyPermSet,
) -> Result<KeyDescriptor> {
+ let caller_uid = ThreadState::get_calling_uid();
DB.with(|db| {
- db.borrow_mut().grant(
- &key,
- ThreadState::get_calling_uid(),
- grantee_uid as u32,
- access_vector,
- |k, av| check_grant_permission(*av, k).context("During grant."),
- )
+ LEGACY_MIGRATOR.with_try_migrate(&key, caller_uid, || {
+ db.borrow_mut().grant(
+ &key,
+ caller_uid,
+ grantee_uid as u32,
+ access_vector,
+ |k, av| check_grant_permission(*av, k).context("During grant."),
+ )
+ })
})
.context("In KeystoreService::grant.")
}
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 79f53bf..156d20d 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -15,15 +15,18 @@
#![allow(dead_code)]
use crate::{
- database::BlobMetaData, database::BlobMetaEntry, database::EncryptedBy, database::KeystoreDB,
- error::Error, error::ResponseCode, legacy_blob::LegacyBlobLoader,
+ database::BlobMetaData, database::BlobMetaEntry, database::EncryptedBy, database::KeyEntry,
+ database::KeyType, database::KeystoreDB, enforcements::Enforcements, error::Error,
+ error::ResponseCode, key_parameter::KeyParameter, legacy_blob::LegacyBlobLoader,
+ legacy_migrator::LegacyMigrator,
};
use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
use anyhow::{Context, Result};
use keystore2_crypto::{
- aes_gcm_decrypt, aes_gcm_encrypt, derive_key_from_password, generate_salt, ZVec,
- AES_256_KEY_LENGTH,
+ aes_gcm_decrypt, aes_gcm_encrypt, derive_key_from_password, generate_aes256_key, generate_salt,
+ ZVec, AES_256_KEY_LENGTH,
};
+use std::ops::Deref;
use std::{
collections::HashMap,
sync::Arc,
@@ -39,13 +42,30 @@
/// secret, that is itself derived from the user's lock screen knowledge factor (LSKF).
/// When the user unlocks the device for the first time, this key is unlocked, i.e., decrypted,
/// and stays memory resident until the device reboots.
- per_boot: Option<Arc<ZVec>>,
+ per_boot: Option<SuperKey>,
/// The screen lock key works like the per boot key with the distinction that it is cleared
/// from memory when the screen lock is engaged.
/// TODO the life cycle is not fully implemented at this time.
screen_lock: Option<Arc<ZVec>>,
}
+#[derive(Default, Clone)]
+pub struct SuperKey {
+ key: Arc<ZVec>,
+ // id of the super key in the database.
+ id: i64,
+}
+
+impl SuperKey {
+ pub fn get_key(&self) -> &Arc<ZVec> {
+ &self.key
+ }
+
+ pub fn get_id(&self) -> i64 {
+ self.id
+ }
+}
+
#[derive(Default)]
struct SkmState {
user_keys: HashMap<UserId, UserSuperKeys>,
@@ -87,18 +107,17 @@
data.key_index.clear();
}
- fn install_per_boot_key_for_user(&self, user: UserId, key_id: i64, key: ZVec) {
+ fn install_per_boot_key_for_user(&self, user: UserId, super_key: SuperKey) {
let mut data = self.data.lock().unwrap();
- let key = Arc::new(key);
- data.key_index.insert(key_id, Arc::downgrade(&key));
- data.user_keys.entry(user).or_default().per_boot = Some(key);
+ data.key_index.insert(super_key.id, Arc::downgrade(&(super_key.key)));
+ data.user_keys.entry(user).or_default().per_boot = Some(super_key);
}
fn get_key(&self, key_id: &i64) -> Option<Arc<ZVec>> {
self.data.lock().unwrap().key_index.get(key_id).and_then(|k| k.upgrade())
}
- pub fn get_per_boot_key_by_user_id(&self, user_id: u32) -> Option<Arc<ZVec>> {
+ pub fn get_per_boot_key_by_user_id(&self, user_id: u32) -> Option<SuperKey> {
let data = self.data.lock().unwrap();
data.user_keys.get(&user_id).map(|e| e.per_boot.clone()).flatten()
}
@@ -109,16 +128,16 @@
/// a key derived from the given password and stored in the database.
pub fn unlock_user_key(
&self,
+ db: &mut KeystoreDB,
user: UserId,
pw: &[u8],
- db: &mut KeystoreDB,
legacy_blob_loader: &LegacyBlobLoader,
) -> Result<()> {
let (_, entry) = db
.get_or_create_key_with(
Domain::APP,
user as u64 as i64,
- &"USER_SUPER_KEY",
+ KeystoreDB::USER_SUPER_KEY_ALIAS,
crate::database::KEYSTORE_UUID,
|| {
// For backward compatibility we need to check if there is a super key present.
@@ -128,64 +147,22 @@
let super_key = match super_key {
None => {
// No legacy file was found. So we generate a new key.
- keystore2_crypto::generate_aes256_key()
+ generate_aes256_key()
.context("In create_new_key: Failed to generate AES 256 key.")?
}
Some(key) => key,
};
- // Regardless of whether we loaded an old AES128 key or a new AES256 key,
- // we derive a AES256 key and re-encrypt the key before we insert it in the
- // database. The length of the key is preserved by the encryption so we don't
- // need any extra flags to inform us which algorithm to use it with.
- let salt =
- generate_salt().context("In create_new_key: Failed to generate salt.")?;
- let derived_key = derive_key_from_password(pw, Some(&salt), AES_256_KEY_LENGTH)
- .context("In create_new_key: Failed to derive password.")?;
- let mut metadata = BlobMetaData::new();
- metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
- metadata.add(BlobMetaEntry::Salt(salt));
- let (encrypted_key, iv, tag) = aes_gcm_encrypt(&super_key, &derived_key)
- .context("In create_new_key: Failed to encrypt new super key.")?;
- metadata.add(BlobMetaEntry::Iv(iv));
- metadata.add(BlobMetaEntry::AeadTag(tag));
- Ok((encrypted_key, metadata))
+ // Regardless of whether we loaded an old AES128 key or generated a new AES256
+ // key as the super key, we derive a AES256 key from the password and re-encrypt
+ // the super key before we insert it in the database. The length of the key is
+ // preserved by the encryption so we don't need any extra flags to inform us
+ // which algorithm to use it with.
+ Self::encrypt_with_password(&super_key, pw).context("In create_new_key.")
},
)
.context("In unlock_user_key: Failed to get key id.")?;
- if let Some((ref blob, ref metadata)) = entry.key_blob_info() {
- let super_key = match (
- metadata.encrypted_by(),
- metadata.salt(),
- metadata.iv(),
- metadata.aead_tag(),
- ) {
- (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => {
- let key = derive_key_from_password(pw, Some(salt), AES_256_KEY_LENGTH)
- .context("In unlock_user_key: Failed to generate key from password.")?;
-
- aes_gcm_decrypt(blob, iv, tag, &key)
- .context("In unlock_user_key: Failed to decrypt key blob.")?
- }
- (enc_by, salt, iv, tag) => {
- return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
- concat!(
- "In unlock_user_key: Super key has incomplete metadata.",
- "Present: encrypted_by: {}, salt: {}, iv: {}, aead_tag: {}."
- ),
- enc_by.is_some(),
- salt.is_some(),
- iv.is_some(),
- tag.is_some(),
- ));
- }
- };
- self.install_per_boot_key_for_user(user, entry.id(), super_key);
- } else {
- return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In unlock_user_key: Key entry has no key blob.");
- }
-
+ self.populate_cache_from_super_key_blob(user, entry, pw).context("In unlock_user_key.")?;
Ok(())
}
@@ -193,12 +170,13 @@
/// The function queries `metadata.encrypted_by()` to determine the encryption key.
/// It then check if the required key is memory resident, and if so decrypts the
/// blob.
- pub fn unwrap_key(&self, blob: &[u8], metadata: &BlobMetaData) -> Result<ZVec> {
+ pub fn unwrap_key<'a>(&self, blob: &'a [u8], metadata: &BlobMetaData) -> Result<KeyBlob<'a>> {
match metadata.encrypted_by() {
Some(EncryptedBy::KeyId(key_id)) => match self.get_key(key_id) {
- Some(key) => {
- Self::unwrap_key_with_key(blob, metadata, &key).context("In unwrap_key.")
- }
+ Some(key) => Ok(KeyBlob::Sensitive(
+ Self::unwrap_key_with_key(blob, metadata, &key).context("In unwrap_key.")?,
+ SuperKey { key: key.clone(), id: *key_id },
+ )),
None => Err(Error::Rc(ResponseCode::LOCKED))
.context("In unwrap_key: Key is not usable until the user entered their LSKF."),
},
@@ -222,4 +200,409 @@
)),
}
}
+
+ /// Checks if user has setup LSKF, even when super key cache is empty for the user.
+ pub fn super_key_exists_in_db_for_user(
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ ) -> Result<bool> {
+ let key_in_db = db
+ .key_exists(
+ Domain::APP,
+ user_id as u64 as i64,
+ KeystoreDB::USER_SUPER_KEY_ALIAS,
+ KeyType::Super,
+ )
+ .context("In super_key_exists_in_db_for_user.")?;
+
+ if key_in_db {
+ Ok(key_in_db)
+ } else {
+ legacy_migrator
+ .has_super_key(user_id)
+ .context("In super_key_exists_in_db_for_user: Trying to query legacy db.")
+ }
+ }
+
+ /// Checks if user has already setup LSKF (i.e. a super key is persisted in the database or the
+ /// legacy database). If not, return Uninitialized state.
+ /// Otherwise, decrypt the super key from the password and return LskfUnlocked state.
+ pub fn check_and_unlock_super_key(
+ &self,
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ pw: &[u8],
+ ) -> Result<UserState> {
+ let result = legacy_migrator
+ .with_try_migrate_super_key(user_id, pw, || db.load_super_key(user_id))
+ .context("In check_and_unlock_super_key. Failed to load super key")?;
+
+ match result {
+ Some((_, entry)) => {
+ let super_key = self
+ .populate_cache_from_super_key_blob(user_id, entry, pw)
+ .context("In check_and_unlock_super_key.")?;
+ Ok(UserState::LskfUnlocked(super_key))
+ }
+ None => Ok(UserState::Uninitialized),
+ }
+ }
+
+ /// Checks if user has already setup LSKF (i.e. a super key is persisted in the database or the
+ /// legacy database). If so, return LskfLocked state.
+ /// If the password is provided, generate a new super key, encrypt with the password,
+ /// store in the database and populate the super key cache for the new user
+ /// and return LskfUnlocked state.
+ /// If the password is not provided, return Uninitialized state.
+ pub fn check_and_initialize_super_key(
+ &self,
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ pw: Option<&[u8]>,
+ ) -> Result<UserState> {
+ let super_key_exists_in_db =
+ Self::super_key_exists_in_db_for_user(db, legacy_migrator, user_id).context(
+ "In check_and_initialize_super_key. Failed to check if super key exists.",
+ )?;
+ if super_key_exists_in_db {
+ Ok(UserState::LskfLocked)
+ } else if let Some(pw) = pw {
+ //generate a new super key.
+ let super_key = generate_aes256_key()
+ .context("In check_and_initialize_super_key: Failed to generate AES 256 key.")?;
+ //derive an AES256 key from the password and re-encrypt the super key
+ //before we insert it in the database.
+ let (encrypted_super_key, blob_metadata) = Self::encrypt_with_password(&super_key, pw)
+ .context("In check_and_initialize_super_key.")?;
+
+ let key_entry = db
+ .store_super_key(user_id, &(&encrypted_super_key, &blob_metadata))
+ .context("In check_and_initialize_super_key. Failed to store super key.")?;
+
+ let super_key = self
+ .populate_cache_from_super_key_blob(user_id, key_entry, pw)
+ .context("In check_and_initialize_super_key.")?;
+ Ok(UserState::LskfUnlocked(super_key))
+ } else {
+ Ok(UserState::Uninitialized)
+ }
+ }
+
+ //helper function to populate super key cache from the super key blob loaded from the database
+ fn populate_cache_from_super_key_blob(
+ &self,
+ user_id: u32,
+ entry: KeyEntry,
+ pw: &[u8],
+ ) -> Result<SuperKey> {
+ let super_key = Self::extract_super_key_from_key_entry(entry, pw).context(
+ "In populate_cache_from_super_key_blob. Failed to extract super key from key entry",
+ )?;
+ self.install_per_boot_key_for_user(user_id, super_key.clone());
+ Ok(super_key)
+ }
+
+ /// Extracts super key from the entry loaded from the database
+ pub fn extract_super_key_from_key_entry(entry: KeyEntry, pw: &[u8]) -> Result<SuperKey> {
+ if let Some((blob, metadata)) = entry.key_blob_info() {
+ let key = match (
+ metadata.encrypted_by(),
+ metadata.salt(),
+ metadata.iv(),
+ metadata.aead_tag(),
+ ) {
+ (Some(&EncryptedBy::Password), Some(salt), Some(iv), Some(tag)) => {
+ let key = derive_key_from_password(pw, Some(salt), AES_256_KEY_LENGTH).context(
+ "In extract_super_key_from_key_entry: Failed to generate key from password.",
+ )?;
+
+ aes_gcm_decrypt(blob, iv, tag, &key).context(
+ "In extract_super_key_from_key_entry: Failed to decrypt key blob.",
+ )?
+ }
+ (enc_by, salt, iv, tag) => {
+ return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
+ concat!(
+ "In extract_super_key_from_key_entry: Super key has incomplete metadata.",
+ "Present: encrypted_by: {}, salt: {}, iv: {}, aead_tag: {}."
+ ),
+ enc_by.is_some(),
+ salt.is_some(),
+ iv.is_some(),
+ tag.is_some()
+ ));
+ }
+ };
+ Ok(SuperKey { key: Arc::new(key), id: entry.id() })
+ } else {
+ Err(Error::Rc(ResponseCode::VALUE_CORRUPTED))
+ .context("In extract_super_key_from_key_entry: No key blob info.")
+ }
+ }
+
+ /// Encrypts the super key from a key derived from the password, before storing in the database.
+ pub fn encrypt_with_password(super_key: &[u8], pw: &[u8]) -> Result<(Vec<u8>, BlobMetaData)> {
+ let salt = generate_salt().context("In encrypt_with_password: Failed to generate salt.")?;
+ let derived_key = derive_key_from_password(pw, Some(&salt), AES_256_KEY_LENGTH)
+ .context("In encrypt_with_password: Failed to derive password.")?;
+ let mut metadata = BlobMetaData::new();
+ metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password));
+ metadata.add(BlobMetaEntry::Salt(salt));
+ let (encrypted_key, iv, tag) = aes_gcm_encrypt(super_key, &derived_key)
+ .context("In encrypt_with_password: Failed to encrypt new super key.")?;
+ metadata.add(BlobMetaEntry::Iv(iv));
+ metadata.add(BlobMetaEntry::AeadTag(tag));
+ Ok((encrypted_key, metadata))
+ }
+
+ // Encrypt the given key blob with the user's super key, if the super key exists and the device
+ // is unlocked. If the super key exists and the device is locked, or LSKF is not setup,
+ // return error. Note that it is out of the scope of this function to check if super encryption
+ // is required. Such check should be performed before calling this function.
+ fn super_encrypt_on_key_init(
+ &self,
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ user_id: u32,
+ key_blob: &[u8],
+ ) -> Result<(Vec<u8>, BlobMetaData)> {
+ match UserState::get(db, legacy_migrator, self, user_id)
+ .context("In super_encrypt. Failed to get user state.")?
+ {
+ UserState::LskfUnlocked(super_key) => {
+ Self::encrypt_with_super_key(key_blob, &super_key)
+ .context("In super_encrypt_on_key_init. Failed to encrypt the key.")
+ }
+ UserState::LskfLocked => {
+ Err(Error::Rc(ResponseCode::LOCKED)).context("In super_encrypt. Device is locked.")
+ }
+ UserState::Uninitialized => Err(Error::Rc(ResponseCode::UNINITIALIZED))
+ .context("In super_encrypt. LSKF is not setup for the user."),
+ }
+ }
+
+ //Helper function to encrypt a key with the given super key. Callers should select which super
+ //key to be used. This is called when a key is super encrypted at its creation as well as at its
+ //upgrade.
+ fn encrypt_with_super_key(
+ key_blob: &[u8],
+ super_key: &SuperKey,
+ ) -> Result<(Vec<u8>, BlobMetaData)> {
+ let mut metadata = BlobMetaData::new();
+ let (encrypted_key, iv, tag) = aes_gcm_encrypt(key_blob, &(super_key.key))
+ .context("In encrypt_with_super_key: Failed to encrypt new super key.")?;
+ metadata.add(BlobMetaEntry::Iv(iv));
+ metadata.add(BlobMetaEntry::AeadTag(tag));
+ metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key.id)));
+ Ok((encrypted_key, metadata))
+ }
+
+ /// 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)]
+ pub fn handle_super_encryption_on_key_init(
+ &self,
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ domain: &Domain,
+ key_parameters: &[KeyParameter],
+ flags: Option<i32>,
+ user_id: u32,
+ key_blob: &[u8],
+ ) -> Result<(Vec<u8>, BlobMetaData)> {
+ match (*domain, Enforcements::super_encryption_required(key_parameters, flags)) {
+ (Domain::APP, true) => {
+ self.super_encrypt_on_key_init(db, legacy_migrator, user_id, &key_blob).context(
+ "In handle_super_encryption_on_key_init.
+ Failed to super encrypt the key.",
+ )
+ }
+ _ => Ok((key_blob.to_vec(), BlobMetaData::new())),
+ }
+ }
+
+ /// Check if a given key is super-encrypted, from its metadata. If so, unwrap the key using
+ /// the relevant super key.
+ pub fn unwrap_key_if_required<'a>(
+ &self,
+ metadata: &BlobMetaData,
+ key_blob: &'a [u8],
+ ) -> Result<KeyBlob<'a>> {
+ if Self::key_super_encrypted(&metadata) {
+ let unwrapped_key = self
+ .unwrap_key(key_blob, metadata)
+ .context("In unwrap_key_if_required. Error in unwrapping the key.")?;
+ Ok(unwrapped_key)
+ } else {
+ Ok(KeyBlob::Ref(key_blob))
+ }
+ }
+
+ /// Check if a given key needs re-super-encryption, from its KeyBlob type.
+ /// If so, re-super-encrypt the key and return a new set of metadata,
+ /// containing the new super encryption information.
+ pub fn reencrypt_on_upgrade_if_required<'a>(
+ key_blob_before_upgrade: &KeyBlob,
+ key_after_upgrade: &'a [u8],
+ ) -> Result<(KeyBlob<'a>, Option<BlobMetaData>)> {
+ match key_blob_before_upgrade {
+ KeyBlob::Sensitive(_, super_key) => {
+ let (key, metadata) = Self::encrypt_with_super_key(key_after_upgrade, super_key)
+ .context(
+ "In reencrypt_on_upgrade_if_required. Failed to re-super-encrypt key on key upgrade.",
+ )?;
+ Ok((KeyBlob::NonSensitive(key), Some(metadata)))
+ }
+ _ => Ok((KeyBlob::Ref(key_after_upgrade), None)),
+ }
+ }
+
+ // Helper function to decide if a key is super encrypted, given metadata.
+ fn key_super_encrypted(metadata: &BlobMetaData) -> bool {
+ if let Some(&EncryptedBy::KeyId(_)) = metadata.encrypted_by() {
+ return true;
+ }
+ false
+ }
+}
+
+/// This enum represents different states of the user's life cycle in the device.
+/// For now, only three states are defined. More states may be added later.
+pub enum UserState {
+ // The user has registered LSKF and has unlocked the device by entering PIN/Password,
+ // and hence the per-boot super key is available in the cache.
+ LskfUnlocked(SuperKey),
+ // The user has registered LSKF, but has not unlocked the device using password, after reboot.
+ // Hence the per-boot super-key(s) is not available in the cache.
+ // However, the encrypted super key is available in the database.
+ LskfLocked,
+ // There's no user in the device for the given user id, or the user with the user id has not
+ // setup LSKF.
+ Uninitialized,
+}
+
+impl UserState {
+ pub fn get(
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ skm: &SuperKeyManager,
+ user_id: u32,
+ ) -> Result<UserState> {
+ match skm.get_per_boot_key_by_user_id(user_id) {
+ Some(super_key) => Ok(UserState::LskfUnlocked(super_key)),
+ None => {
+ //Check if a super key exists in the database or legacy database.
+ //If so, return locked user state.
+ if SuperKeyManager::super_key_exists_in_db_for_user(db, legacy_migrator, user_id)
+ .context("In get.")?
+ {
+ Ok(UserState::LskfLocked)
+ } else {
+ Ok(UserState::Uninitialized)
+ }
+ }
+ }
+ }
+
+ /// Queries user state when serving password change requests.
+ pub fn get_with_password_changed(
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ skm: &SuperKeyManager,
+ user_id: u32,
+ password: Option<&[u8]>,
+ ) -> Result<UserState> {
+ match skm.get_per_boot_key_by_user_id(user_id) {
+ Some(super_key) => {
+ if password.is_none() {
+ //transitioning to swiping, delete only the super key in database and cache, and
+ //super-encrypted keys in database (and in KM)
+ Self::reset_user(db, skm, user_id, true)
+ .context("In get_with_password_changed.")?;
+ //Lskf is now removed in Keystore
+ Ok(UserState::Uninitialized)
+ } else {
+ //Keystore won't be notified when changing to a new password when LSKF is
+ //already setup. Therefore, ideally this path wouldn't be reached.
+ Ok(UserState::LskfUnlocked(super_key))
+ }
+ }
+ None => {
+ //Check if a super key exists in the database or legacy database.
+ //If so, return LskfLocked state.
+ //Otherwise, i) if the password is provided, initialize the super key and return
+ //LskfUnlocked state ii) if password is not provided, return Uninitialized state.
+ skm.check_and_initialize_super_key(db, legacy_migrator, user_id, password)
+ }
+ }
+ }
+
+ /// Queries user state when serving password unlock requests.
+ pub fn get_with_password_unlock(
+ db: &mut KeystoreDB,
+ legacy_migrator: &LegacyMigrator,
+ skm: &SuperKeyManager,
+ user_id: u32,
+ password: &[u8],
+ ) -> Result<UserState> {
+ match skm.get_per_boot_key_by_user_id(user_id) {
+ Some(super_key) => {
+ log::info!("In get_with_password_unlock. Trying to unlock when already unlocked.");
+ Ok(UserState::LskfUnlocked(super_key))
+ }
+ None => {
+ //Check if a super key exists in the database or legacy database.
+ //If not, return Uninitialized state.
+ //Otherwise, try to unlock the super key and if successful,
+ //return LskfUnlocked state
+ skm.check_and_unlock_super_key(db, legacy_migrator, user_id, password)
+ .context("In get_with_password_unlock. Failed to unlock super key.")
+ }
+ }
+ }
+
+ /// Delete all the keys created on behalf of the user.
+ /// If 'keep_non_super_encrypted_keys' is set to true, delete only the super key and super
+ /// encrypted keys.
+ pub fn reset_user(
+ db: &mut KeystoreDB,
+ skm: &SuperKeyManager,
+ user_id: u32,
+ keep_non_super_encrypted_keys: bool,
+ ) -> Result<()> {
+ // mark keys created on behalf of the user as unreferenced.
+ db.unbind_keys_for_user(user_id as u32, keep_non_super_encrypted_keys)
+ .context("In reset user. Error in unbinding keys.")?;
+
+ //delete super key in cache, if exists
+ skm.forget_all_keys_for_user(user_id as u32);
+ Ok(())
+ }
+}
+
+/// This enum represents two states a Keymint Blob can be in, w.r.t super encryption.
+/// Sensitive variant represents a Keymint blob that is supposed to be super encrypted,
+/// but unwrapped during usage. Therefore, it has the super key along with the unwrapped key.
+/// Ref variant represents a Keymint blob that is not required to super encrypt or that is
+/// already super encrypted.
+pub enum KeyBlob<'a> {
+ Sensitive(ZVec, SuperKey),
+ NonSensitive(Vec<u8>),
+ Ref(&'a [u8]),
+}
+
+/// Deref returns a reference to the key material in both variants.
+impl<'a> Deref for KeyBlob<'a> {
+ type Target = [u8];
+
+ fn deref(&self) -> &Self::Target {
+ match self {
+ Self::Sensitive(key, _) => &key,
+ Self::NonSensitive(key) => &key,
+ Self::Ref(key) => key,
+ }
+ }
}
diff --git a/keystore2/src/user_manager.rs b/keystore2/src/user_manager.rs
new file mode 100644
index 0000000..8b7aad9
--- /dev/null
+++ b/keystore2/src/user_manager.rs
@@ -0,0 +1,95 @@
+// 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.
+
+//! This module implements IKeystoreUserManager AIDL interface.
+
+use crate::error::map_or_log_err;
+use crate::error::Error as KeystoreError;
+use crate::globals::{DB, LEGACY_MIGRATOR, SUPER_KEY};
+use crate::permission::KeystorePerm;
+use crate::super_key::UserState;
+use crate::utils::check_keystore_permission;
+use android_security_usermanager::aidl::android::security::usermanager::IKeystoreUserManager::{
+ BnKeystoreUserManager, IKeystoreUserManager,
+};
+use android_security_usermanager::binder::{Interface, Result as BinderResult};
+use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
+use anyhow::{Context, Result};
+use binder::{IBinder, Strong};
+
+/// This struct is defined to implement the aforementioned AIDL interface.
+/// As of now, it is an empty struct.
+pub struct UserManager;
+
+impl UserManager {
+ /// Create a new instance of Keystore User Manager service.
+ pub fn new_native_binder() -> Result<Strong<dyn IKeystoreUserManager>> {
+ let result = BnKeystoreUserManager::new_binder(Self);
+ result.as_binder().set_requesting_sid(true);
+ Ok(result)
+ }
+
+ fn on_user_password_changed(user_id: i32, password: Option<&[u8]>) -> Result<()> {
+ //Check permission. Function should return if this failed. Therefore having '?' at the end
+ //is very important.
+ check_keystore_permission(KeystorePerm::change_password())
+ .context("In on_user_password_changed.")?;
+
+ match DB
+ .with(|db| {
+ UserState::get_with_password_changed(
+ &mut db.borrow_mut(),
+ &LEGACY_MIGRATOR,
+ &SUPER_KEY,
+ user_id as u32,
+ password,
+ )
+ })
+ .context("In on_user_password_changed.")?
+ {
+ UserState::LskfLocked => {
+ //error - password can not be changed when the device is locked
+ Err(KeystoreError::Rc(ResponseCode::LOCKED))
+ .context("In on_user_password_changed. Device is locked.")
+ }
+ _ => {
+ //LskfLocked is the only error case for password change
+ Ok(())
+ }
+ }
+ }
+
+ fn add_or_remove_user(user_id: i32) -> Result<()> {
+ // Check permission. Function should return if this failed. Therefore having '?' at the end
+ // is very important.
+ check_keystore_permission(KeystorePerm::change_user()).context("In add_or_remove_user.")?;
+ DB.with(|db| UserState::reset_user(&mut db.borrow_mut(), &SUPER_KEY, user_id as u32, false))
+ }
+}
+
+impl Interface for UserManager {}
+
+impl IKeystoreUserManager for UserManager {
+ fn onUserPasswordChanged(&self, user_id: i32, password: Option<&[u8]>) -> BinderResult<()> {
+ map_or_log_err(Self::on_user_password_changed(user_id, password), Ok)
+ }
+
+ fn onUserAdded(&self, user_id: i32) -> BinderResult<()> {
+ map_or_log_err(Self::add_or_remove_user(user_id), Ok)
+ }
+
+ fn onUserRemoved(&self, user_id: i32) -> BinderResult<()> {
+ map_or_log_err(Self::add_or_remove_user(user_id), Ok)
+ }
+}