Merge "Keystore 2.0: Add API for key migration to IKeystoreMaintenance"

This commit is contained in:
Treehugger Robot 2021-04-16 16:13:33 +00:00 committed by Gerrit Code Review
commit 2295dae962
3 changed files with 310 additions and 8 deletions

View file

@ -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);
}

View file

@ -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.");

View file

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