Keystore 2.0: Add API for key migration to IKeystoreMaintenance
Bug: 184664830
Test: atest keystore2_test
Change-Id: Ic61cd403365841ba2202ac3ed8bcb01c97063d45
diff --git a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
index 21ddd9b..5f91e79 100644
--- a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
+++ b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
@@ -15,6 +15,7 @@
package android.security.maintenance;
import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
import android.security.maintenance.UserState;
/**
@@ -107,4 +108,19 @@
* `ResponseCode::SYSTEM_ERROR` - if an unexpected error occurred.
*/
void onDeviceOffBody();
+
+ /**
+ * Migrate a key from one namespace to another. The caller must have use, grant, and delete
+ * permissions on the source namespace and rebind permissions on the destination namespace.
+ * The source may be specified by Domain::APP, Domain::SELINUX, or Domain::KEY_ID. The target
+ * may be specified by Domain::APP or Domain::SELINUX.
+ *
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - If the caller lacks any of the required permissions.
+ * `ResponseCode::KEY_NOT_FOUND` - If the source did not exist.
+ * `ResponseCode::INVALID_ARGUMENT` - If the target exists or if any of the above mentioned
+ * requirements for the domain parameter are not met.
+ * `ResponseCode::SYSTEM_ERROR` - An unexpected system error occurred.
+ */
+ void migrateKeyNamespace(in KeyDescriptor source, in KeyDescriptor destination);
}
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index da482f1..ce760eb 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -2031,6 +2031,69 @@
Ok(updated != 0)
}
+ /// Moves the key given by KeyIdGuard to the new location at `destination`. If the destination
+ /// is already occupied by a key, this function fails with `ResponseCode::INVALID_ARGUMENT`.
+ pub fn migrate_key_namespace(
+ &mut self,
+ key_id_guard: KeyIdGuard,
+ destination: &KeyDescriptor,
+ caller_uid: u32,
+ check_permission: impl Fn(&KeyDescriptor) -> Result<()>,
+ ) -> Result<()> {
+ let destination = match destination.domain {
+ Domain::APP => KeyDescriptor { nspace: caller_uid as i64, ..(*destination).clone() },
+ Domain::SELINUX => (*destination).clone(),
+ domain => {
+ return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context(format!("Domain {:?} must be either APP or SELINUX.", domain));
+ }
+ };
+
+ // Security critical: Must return immediately on failure. Do not remove the '?';
+ check_permission(&destination)
+ .context("In migrate_key_namespace: Trying to check permission.")?;
+
+ let alias = destination
+ .alias
+ .as_ref()
+ .ok_or(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context("In migrate_key_namespace: Alias must be specified.")?;
+
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ // Query the destination location. If there is a key, the migration request fails.
+ if tx
+ .query_row(
+ "SELECT id FROM persistent.keyentry
+ WHERE alias = ? AND domain = ? AND namespace = ?;",
+ params![alias, destination.domain.0, destination.nspace],
+ |_| Ok(()),
+ )
+ .optional()
+ .context("Failed to query destination.")?
+ .is_some()
+ {
+ return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context("Target already exists.");
+ }
+
+ let updated = tx
+ .execute(
+ "UPDATE persistent.keyentry
+ SET alias = ?, domain = ?, namespace = ?
+ WHERE id = ?;",
+ params![alias, destination.domain.0, destination.nspace, key_id_guard.id()],
+ )
+ .context("Failed to update key entry.")?;
+
+ if updated != 1 {
+ return Err(KsError::sys())
+ .context(format!("Update succeeded, but {} rows were updated.", updated));
+ }
+ Ok(()).no_gc()
+ })
+ .context("In migrate_key_namespace:")
+ }
+
/// Store a new key in a single transaction.
/// The function creates a new key entry, populates the blob, key parameter, and metadata
/// fields, and rebinds the given alias to the new key.
@@ -4100,6 +4163,181 @@
Ok(())
}
+ // Creates a key migrates it to a different location and then tries to access it by the old
+ // and new location.
+ #[test]
+ fn test_migrate_key_app_to_app() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let source_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(SOURCE_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let key_id = key_id_guard.id();
+
+ db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| {
+ Ok(())
+ })
+ .unwrap();
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &destination_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ DESTINATION_UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(DESTINATION_UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &source_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ SOURCE_UID,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+ }
+
+ // Creates a key migrates it to a different location and then tries to access it by the old
+ // and new location.
+ #[test]
+ fn test_migrate_key_app_to_selinux() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ const DESTINATION_NAMESPACE: i64 = 1000i64;
+ static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let source_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(SOURCE_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: DESTINATION_NAMESPACE,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let key_id = key_id_guard.id();
+
+ db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| {
+ Ok(())
+ })
+ .unwrap();
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &destination_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ DESTINATION_UID,
+ |k, av| {
+ assert_eq!(Domain::SELINUX, k.domain);
+ assert_eq!(DESTINATION_NAMESPACE as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &source_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ SOURCE_UID,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+ }
+
+ // Creates two keys and tries to migrate the first to the location of the second which
+ // is expected to fail.
+ #[test]
+ fn test_migrate_key_destination_occupied() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+ make_test_key_entry(&mut db, Domain::APP, DESTINATION_UID as i64, DESTINATION_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::INVALID_ARGUMENT)),
+ db.migrate_key_namespace(
+ key_id_guard,
+ &destination_descriptor,
+ DESTINATION_UID,
+ |_k| Ok(())
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+ }
+
static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key";
#[test]
@@ -4177,7 +4415,7 @@
}
#[test]
- fn teset_database_busy_error_code() {
+ fn test_database_busy_error_code() {
let temp_dir =
TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir.");
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index e059a0b..9ceb01a 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -14,14 +14,15 @@
//! This module implements IKeystoreMaintenance AIDL interface.
+use crate::database::{KeyEntryLoadBits, KeyType, MonotonicRawTime};
use crate::error::map_km_error;
-use crate::error::Error as KeystoreError;
+use crate::error::map_or_log_err;
+use crate::error::Error;
use crate::globals::get_keymint_device;
use crate::globals::{DB, LEGACY_MIGRATOR, SUPER_KEY};
-use crate::permission::KeystorePerm;
+use crate::permission::{KeyPerm, KeystorePerm};
use crate::super_key::UserState;
-use crate::utils::check_keystore_permission;
-use crate::{database::MonotonicRawTime, error::map_or_log_err};
+use crate::utils::{check_key_permission, check_keystore_permission};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::IKeyMintDevice::IKeyMintDevice;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
use android_security_maintenance::aidl::android::security::maintenance::{
@@ -29,10 +30,12 @@
UserState::UserState as AidlUserState,
};
use android_security_maintenance::binder::{Interface, Result as BinderResult};
-use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor,
+};
use anyhow::{Context, Result};
-use binder::{IBinderInternal, Strong};
+use binder::{IBinderInternal, Strong, ThreadState};
use keystore2_crypto::Password;
/// This struct is defined to implement the aforementioned AIDL interface.
@@ -74,7 +77,7 @@
{
UserState::LskfLocked => {
// Error - password can not be changed when the device is locked
- Err(KeystoreError::Rc(ResponseCode::LOCKED))
+ Err(Error::Rc(ResponseCode::LOCKED))
.context("In on_user_password_changed. Device is locked.")
}
_ => {
@@ -166,6 +169,43 @@
DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now()))
.context("In on_device_off_body: Trying to update last off body time.")
}
+
+ fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
+ let caller_uid = ThreadState::get_calling_uid();
+
+ DB.with(|db| {
+ let key_id_guard = match source.domain {
+ Domain::APP | Domain::SELINUX | Domain::KEY_ID => {
+ let (key_id_guard, _) = LEGACY_MIGRATOR
+ .with_try_migrate(&source, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &source,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ caller_uid,
+ |k, av| {
+ check_key_permission(KeyPerm::use_(), k, &av)?;
+ check_key_permission(KeyPerm::delete(), k, &av)?;
+ check_key_permission(KeyPerm::grant(), k, &av)
+ },
+ )
+ })
+ .context("In migrate_key_namespace: Failed to load key blob.")?;
+ key_id_guard
+ }
+ _ => {
+ return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(concat!(
+ "In migrate_key_namespace: ",
+ "Source domain must be one of APP, SELINUX, or KEY_ID."
+ ))
+ }
+ };
+
+ db.borrow_mut().migrate_key_namespace(key_id_guard, destination, caller_uid, |k| {
+ check_key_permission(KeyPerm::rebind(), k, &None)
+ })
+ })
+ }
}
impl Interface for Maintenance {}
@@ -198,4 +238,12 @@
fn onDeviceOffBody(&self) -> BinderResult<()> {
map_or_log_err(Self::on_device_off_body(), Ok)
}
+
+ fn migrateKeyNamespace(
+ &self,
+ source: &KeyDescriptor,
+ destination: &KeyDescriptor,
+ ) -> BinderResult<()> {
+ map_or_log_err(Self::migrate_key_namespace(source, destination), Ok)
+ }
}