Merge "Keystore 2.0: Add API for key migration to IKeystoreMaintenance"
This commit is contained in:
commit
2295dae962
3 changed files with 310 additions and 8 deletions
|
@ -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 @@ interface IKeystoreMaintenance {
|
|||
* `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);
|
||||
}
|
||||
|
|
|
@ -2034,6 +2034,69 @@ impl KeystoreDB {
|
|||
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.
|
||||
|
@ -4103,6 +4166,181 @@ mod tests {
|
|||
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]
|
||||
|
@ -4180,7 +4418,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[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.");
|
||||
|
||||
|
|
|
@ -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 @@ use android_security_maintenance::aidl::android::security::maintenance::{
|
|||
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 @@ impl Maintenance {
|
|||
{
|
||||
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.")
|
||||
}
|
||||
_ => {
|
||||
|
@ -171,6 +174,43 @@ impl Maintenance {
|
|||
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 {}
|
||||
|
@ -203,4 +243,12 @@ impl IKeystoreMaintenance for Maintenance {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue