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)
+    }
 }