blob: fe044ee471b8fd4280c364232ae3468a398e0085 [file] [log] [blame]
// 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.
//! Provide the [`KeyMintDevice`] wrapper for operating directly on a KeyMint device.
use crate::{
database::{
BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry,
KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB,
SubComponentType, Uuid,
},
error::{map_km_error, Error, ErrorCode},
globals::get_keymint_device,
ks_err,
super_key::KeyBlob,
utils::{key_characteristics_to_internal, watchdog as wd, AID_KEYSTORE},
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken, IKeyMintDevice::IKeyMintDevice,
IKeyMintOperation::IKeyMintOperation, KeyCharacteristics::KeyCharacteristics,
KeyCreationResult::KeyCreationResult, KeyParameter::KeyParameter, KeyPurpose::KeyPurpose,
SecurityLevel::SecurityLevel,
};
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
};
use anyhow::{Context, Result};
use binder::Strong;
/// Wrapper for operating directly on a KeyMint device.
/// These methods often mirror methods in [`crate::security_level`]. However
/// the functions in [`crate::security_level`] make assumptions that hold, and has side effects
/// that make sense, only if called by an external client through binder.
/// In addition we are trying to maintain a separation between interface services
/// so that the architecture is compatible with a future move to multiple thread pools.
/// So the simplest approach today is to write new implementations of them for internal use.
/// Because these methods run very early, we don't even try to cooperate with
/// the operation slot database; we assume there will be plenty of slots.
pub struct KeyMintDevice {
km_dev: Strong<dyn IKeyMintDevice>,
km_uuid: Uuid,
version: i32,
security_level: SecurityLevel,
}
impl KeyMintDevice {
/// Version number of KeyMasterDevice@V4_0
pub const KEY_MASTER_V4_0: i32 = 40;
/// Version number of KeyMasterDevice@V4_1
pub const KEY_MASTER_V4_1: i32 = 41;
/// Version number of KeyMintDevice@V1
pub const KEY_MINT_V1: i32 = 100;
/// Version number of KeyMintDevice@V2
pub const KEY_MINT_V2: i32 = 200;
/// Get a [`KeyMintDevice`] for the given [`SecurityLevel`]
pub fn get(security_level: SecurityLevel) -> Result<KeyMintDevice> {
let (km_dev, hw_info, km_uuid) = get_keymint_device(&security_level)
.context("In KeyMintDevice::get: get_keymint_device failed")?;
Ok(KeyMintDevice {
km_dev,
km_uuid,
version: hw_info.versionNumber,
security_level: hw_info.securityLevel,
})
}
/// Get a [`KeyMintDevice`] for the given [`SecurityLevel`], return
/// [`None`] if the error `HARDWARE_TYPE_UNAVAILABLE` is returned
pub fn get_or_none(security_level: SecurityLevel) -> Result<Option<KeyMintDevice>> {
KeyMintDevice::get(security_level).map(Some).or_else(|e| {
match e.root_cause().downcast_ref::<Error>() {
Some(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) => Ok(None),
_ => Err(e),
}
})
}
/// Returns the version of the underlying KeyMint/KeyMaster device.
pub fn version(&self) -> i32 {
self.version
}
/// Returns the self advertised security level of the KeyMint device.
/// This may differ from the requested security level if the best security level
/// on the device is Software.
pub fn security_level(&self) -> SecurityLevel {
self.security_level
}
/// Create a KM key and store in the database.
pub fn create_and_store_key<F>(
&self,
db: &mut KeystoreDB,
key_desc: &KeyDescriptor,
key_type: KeyType,
creator: F,
) -> Result<()>
where
F: FnOnce(&Strong<dyn IKeyMintDevice>) -> Result<KeyCreationResult, binder::Status>,
{
let creation_result = map_km_error(creator(&self.km_dev))
.context("In create_and_store_key: creator failed")?;
let key_parameters = key_characteristics_to_internal(creation_result.keyCharacteristics);
let creation_date =
DateTime::now().context("In create_and_store_key: DateTime::now() failed")?;
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));
db.store_new_key(
key_desc,
key_type,
&key_parameters,
&BlobInfo::new(&creation_result.keyBlob, &blob_metadata),
&CertificateInfo::new(None, None),
&key_metadata,
&self.km_uuid,
)
.context("In create_and_store_key: store_new_key failed")?;
Ok(())
}
/// Generate a KeyDescriptor for internal-use keys.
pub fn internal_descriptor(alias: String) -> KeyDescriptor {
KeyDescriptor {
domain: Domain::APP,
nspace: AID_KEYSTORE as i64,
alias: Some(alias),
blob: None,
}
}
/// Look up an internal-use key in the database given a key descriptor.
fn lookup_from_desc(
db: &mut KeystoreDB,
key_desc: &KeyDescriptor,
key_type: KeyType,
) -> Result<(KeyIdGuard, KeyEntry)> {
db.load_key_entry(key_desc, key_type, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| Ok(()))
.context(ks_err!("load_key_entry failed."))
}
/// Look up the key in the database, and return None if it is absent.
fn not_found_is_none(
lookup: Result<(KeyIdGuard, KeyEntry)>,
) -> Result<Option<(KeyIdGuard, KeyEntry)>> {
match lookup {
Ok(result) => Ok(Some(result)),
Err(e) => match e.root_cause().downcast_ref::<Error>() {
Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => Ok(None),
_ => Err(e),
},
}
}
/// This does the lookup and store in separate transactions; caller must
/// hold a lock before calling.
pub fn lookup_or_generate_key<F>(
&self,
db: &mut KeystoreDB,
key_desc: &KeyDescriptor,
key_type: KeyType,
params: &[KeyParameter],
validate_characteristics: F,
) -> Result<(KeyIdGuard, KeyBlob)>
where
F: FnOnce(&[KeyCharacteristics]) -> bool,
{
// We use a separate transaction for the lookup than for the store
// - to keep the code simple
// - because the caller needs to hold a lock in any case
// - because it avoids holding database locks during slow
// KeyMint operations
let lookup = Self::not_found_is_none(Self::lookup_from_desc(db, key_desc, key_type))
.context(ks_err!("first lookup failed"))?;
if let Some((key_id_guard, mut key_entry)) = lookup {
// If the key is associated with a different km instance
// or if there is no blob metadata for some reason the key entry
// is considered corrupted and needs to be replaced with a new one.
let key_blob = key_entry.take_key_blob_info().and_then(|(key_blob, blob_metadata)| {
if Some(&self.km_uuid) == blob_metadata.km_uuid() {
Some(key_blob)
} else {
None
}
});
if let Some(key_blob_vec) = key_blob {
let (key_characteristics, key_blob) = self
.upgrade_keyblob_if_required_with(
db,
&key_id_guard,
KeyBlob::NonSensitive(key_blob_vec),
|key_blob| {
map_km_error({
let _wp = wd::watch_millis(
concat!(
"In KeyMintDevice::lookup_or_generate_key: ",
"calling getKeyCharacteristics."
),
500,
);
self.km_dev.getKeyCharacteristics(key_blob, &[], &[])
})
},
)
.context(ks_err!("calling getKeyCharacteristics"))?;
if validate_characteristics(&key_characteristics) {
return Ok((key_id_guard, key_blob));
}
// If this point is reached the existing key is considered outdated or corrupted
// in some way. It will be replaced with a new key below.
};
}
self.create_and_store_key(db, key_desc, key_type, |km_dev| {
km_dev.generateKey(params, None)
})
.context(ks_err!("generate_and_store_key failed"))?;
Self::lookup_from_desc(db, key_desc, key_type)
.and_then(|(key_id_guard, mut key_entry)| {
Ok((
key_id_guard,
key_entry
.take_key_blob_info()
.ok_or(Error::Rc(ResponseCode::KEY_NOT_FOUND))
.map(|(key_blob, _)| KeyBlob::NonSensitive(key_blob))
.context("Missing key blob info.")?,
))
})
.context(ks_err!("second lookup failed"))
}
/// Call the passed closure; if it returns `KEY_REQUIRES_UPGRADE`, call upgradeKey, and
/// write the upgraded key to the database.
fn upgrade_keyblob_if_required_with<'a, T, F>(
&self,
db: &mut KeystoreDB,
key_id_guard: &KeyIdGuard,
key_blob: KeyBlob<'a>,
f: F,
) -> Result<(T, KeyBlob<'a>)>
where
F: Fn(&[u8]) -> Result<T, Error>,
{
match f(&key_blob) {
Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
let upgraded_blob = map_km_error({
let _wp = wd::watch_millis(
"In KeyMintDevice::upgrade_keyblob_if_required_with: calling upgradeKey.",
500,
);
self.km_dev.upgradeKey(&key_blob, &[])
})
.context(ks_err!("Upgrade failed"))?;
let mut new_blob_metadata = BlobMetaData::new();
new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
db.set_blob(
key_id_guard,
SubComponentType::KEY_BLOB,
Some(&upgraded_blob),
Some(&new_blob_metadata),
)
.context(ks_err!("Failed to insert upgraded blob into the database"))?;
Ok((
f(&upgraded_blob).context(ks_err!("Closure failed after upgrade"))?,
KeyBlob::NonSensitive(upgraded_blob),
))
}
result => Ok((result.context(ks_err!("Closure failed"))?, key_blob)),
}
}
/// Use the created key in an operation that can be done with
/// a call to begin followed by a call to finish.
#[allow(clippy::too_many_arguments)]
pub fn use_key_in_one_step(
&self,
db: &mut KeystoreDB,
key_id_guard: &KeyIdGuard,
key_blob: &[u8],
purpose: KeyPurpose,
operation_parameters: &[KeyParameter],
auth_token: Option<&HardwareAuthToken>,
input: &[u8],
) -> Result<Vec<u8>> {
let key_blob = KeyBlob::Ref(key_blob);
let (begin_result, _) = self
.upgrade_keyblob_if_required_with(db, key_id_guard, key_blob, |blob| {
map_km_error({
let _wp = wd::watch_millis("In use_key_in_one_step: calling: begin", 500);
self.km_dev.begin(purpose, blob, operation_parameters, auth_token)
})
})
.context(ks_err!("Failed to begin operation."))?;
let operation: Strong<dyn IKeyMintOperation> =
begin_result.operation.ok_or_else(Error::sys).context(ks_err!("Operation missing"))?;
map_km_error({
let _wp = wd::watch_millis("In use_key_in_one_step: calling: finish", 500);
operation.finish(Some(input), None, None, None, None)
})
.context(ks_err!("Failed to finish operation."))
}
}