Merge "Keystore 2.0: Teach keystore to decrypt generic blobs."
diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs
index 13a9143..e2d952d 100644
--- a/keystore2/legacykeystore/lib.rs
+++ b/keystore2/legacykeystore/lib.rs
@@ -25,8 +25,9 @@
};
use anyhow::{Context, Result};
use keystore2::{
- async_task::AsyncTask, error::anyhow_error_to_cstring, legacy_blob::LegacyBlobLoader,
- maintenance::DeleteListener, maintenance::Domain, utils::watchdog as wd,
+ async_task::AsyncTask, error::anyhow_error_to_cstring, globals::SUPER_KEY,
+ legacy_blob::LegacyBlobLoader, maintenance::DeleteListener, maintenance::Domain,
+ utils::uid_to_android_user, utils::watchdog as wd,
};
use rusqlite::{
params, Connection, OptionalExtension, Transaction, TransactionBehavior, NO_PARAMS,
@@ -315,8 +316,8 @@
if let Some(entry) = db.get(uid, alias).context("In get: Trying to load entry from DB.")? {
return Ok(entry);
}
- if self.get_legacy(uid, alias).context("In get: Trying to migrate legacy blob.")? {
- // If we were able to migrate a legacy blob try again.
+ if self.get_legacy(uid, alias).context("In get: Trying to import legacy blob.")? {
+ // If we were able to import a legacy blob try again.
if let Some(entry) =
db.get(uid, alias).context("In get: Trying to load entry from DB.")?
{
@@ -328,19 +329,20 @@
fn put(&self, alias: &str, uid: i32, entry: &[u8]) -> Result<()> {
let uid = Self::get_effective_uid(uid).context("In put.")?;
- // In order to make sure that we don't have stale legacy entries, make sure they are
- // migrated before replacing them.
- let _ = self.get_legacy(uid, alias);
let mut db = self.open_db().context("In put.")?;
- db.put(uid, alias, entry).context("In put: Trying to insert entry into DB.")
+ db.put(uid, alias, entry).context("In put: Trying to insert entry into DB.")?;
+ // When replacing an entry, make sure that there is no stale legacy file entry.
+ let _ = self.remove_legacy(uid, alias);
+ Ok(())
}
fn remove(&self, alias: &str, uid: i32) -> Result<()> {
let uid = Self::get_effective_uid(uid).context("In remove.")?;
let mut db = self.open_db().context("In remove.")?;
- // In order to make sure that we don't have stale legacy entries, make sure they are
- // migrated before removing them.
- let _ = self.get_legacy(uid, alias);
+
+ if self.remove_legacy(uid, alias).context("In remove: trying to remove legacy entry")? {
+ return Ok(());
+ }
let removed =
db.remove(uid, alias).context("In remove: Trying to remove entry from DB.")?;
if removed {
@@ -430,17 +432,30 @@
return Ok(true);
}
let mut db = DB::new(&state.db_path).context("In open_db: Failed to open db.")?;
- let migrated =
- Self::migrate_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db)
- .context("Trying to migrate legacy keystore entries.")?;
- if migrated {
+ let imported =
+ Self::import_one_legacy_entry(uid, &alias, &state.legacy_loader, &mut db)
+ .context("Trying to import legacy keystore entries.")?;
+ if imported {
state.recently_imported.insert((uid, alias));
}
- Ok(migrated)
+ Ok(imported)
})
.context("In get_legacy.")
}
+ fn remove_legacy(&self, uid: u32, alias: &str) -> Result<bool> {
+ let alias = alias.to_string();
+ self.do_serialized(move |state| {
+ if state.recently_imported.contains(&(uid, alias.clone())) {
+ return Ok(false);
+ }
+ state
+ .legacy_loader
+ .remove_legacy_keystore_entry(uid, &alias)
+ .context("Trying to remove legacy entry.")
+ })
+ }
+
fn bulk_delete_uid(&self, uid: u32) -> Result<()> {
self.do_serialized(move |state| {
let entries = state
@@ -473,21 +488,31 @@
})
}
- fn migrate_one_legacy_entry(
+ fn import_one_legacy_entry(
uid: u32,
alias: &str,
legacy_loader: &LegacyBlobLoader,
db: &mut DB,
) -> Result<bool> {
let blob = legacy_loader
- .read_legacy_keystore_entry(uid, alias)
- .context("In migrate_one_legacy_entry: Trying to read legacy keystore entry.")?;
+ .read_legacy_keystore_entry(uid, alias, |ciphertext, iv, tag, _salt, _key_size| {
+ if let Some(key) = SUPER_KEY
+ .read()
+ .unwrap()
+ .get_per_boot_key_by_user_id(uid_to_android_user(uid as u32))
+ {
+ key.decrypt(ciphertext, iv, tag)
+ } else {
+ Err(Error::sys()).context("No key found for user. Device may be locked.")
+ }
+ })
+ .context("In import_one_legacy_entry: Trying to read legacy keystore entry.")?;
if let Some(entry) = blob {
db.put(uid, alias, &entry)
- .context("In migrate_one_legacy_entry: Trying to insert entry into DB.")?;
+ .context("In import_one_legacy_entry: Trying to insert entry into DB.")?;
legacy_loader
.remove_legacy_keystore_entry(uid, alias)
- .context("In migrate_one_legacy_entry: Trying to delete legacy keystore entry.")?;
+ .context("In import_one_legacy_entry: Trying to delete legacy keystore entry.")?;
Ok(true)
} else {
Ok(false)
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index e68b0fd..65ee7ae 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -578,6 +578,36 @@
cert_chain: Option<Vec<u8>>,
}
+/// This type represents a Blob with its metadata and an optional superseded blob.
+#[derive(Debug)]
+pub struct BlobInfo<'a> {
+ blob: &'a [u8],
+ metadata: &'a BlobMetaData,
+ /// Superseded blobs are an artifact of legacy import. In some rare occasions
+ /// the key blob needs to be upgraded during import. In that case two
+ /// blob are imported, the superseded one will have to be imported first,
+ /// so that the garbage collector can reap it.
+ superseded_blob: Option<(&'a [u8], &'a BlobMetaData)>,
+}
+
+impl<'a> BlobInfo<'a> {
+ /// Create a new instance of blob info with blob and corresponding metadata
+ /// and no superseded blob info.
+ pub fn new(blob: &'a [u8], metadata: &'a BlobMetaData) -> Self {
+ Self { blob, metadata, superseded_blob: None }
+ }
+
+ /// Create a new instance of blob info with blob and corresponding metadata
+ /// as well as superseded blob info.
+ pub fn new_with_superseded(
+ blob: &'a [u8],
+ metadata: &'a BlobMetaData,
+ superseded_blob: Option<(&'a [u8], &'a BlobMetaData)>,
+ ) -> Self {
+ Self { blob, metadata, superseded_blob }
+ }
+}
+
impl CertificateInfo {
/// Constructs a new CertificateInfo object from `cert` and `cert_chain`
pub fn new(cert: Option<Vec<u8>>, cert_chain: Option<Vec<u8>>) -> Self {
@@ -2233,7 +2263,7 @@
key: &KeyDescriptor,
key_type: KeyType,
params: &[KeyParameter],
- blob_info: &(&[u8], &BlobMetaData),
+ blob_info: &BlobInfo,
cert_info: &CertificateInfo,
metadata: &KeyMetaData,
km_uuid: &Uuid,
@@ -2253,7 +2283,27 @@
self.with_transaction(TransactionBehavior::Immediate, |tx| {
let key_id = Self::create_key_entry_internal(tx, &domain, namespace, key_type, km_uuid)
.context("Trying to create new key entry.")?;
- let (blob, blob_metadata) = *blob_info;
+ let BlobInfo { blob, metadata: blob_metadata, superseded_blob } = *blob_info;
+
+ // In some occasions the key blob is already upgraded during the import.
+ // In order to make sure it gets properly deleted it is inserted into the
+ // database here and then immediately replaced by the superseding blob.
+ // The garbage collector will then subject the blob to deleteKey of the
+ // KM back end to permanently invalidate the key.
+ let need_gc = if let Some((blob, blob_metadata)) = superseded_blob {
+ Self::set_blob_internal(
+ tx,
+ key_id.id(),
+ SubComponentType::KEY_BLOB,
+ Some(blob),
+ Some(blob_metadata),
+ )
+ .context("Trying to insert superseded key blob.")?;
+ true
+ } else {
+ false
+ };
+
Self::set_blob_internal(
tx,
key_id.id(),
@@ -2280,7 +2330,8 @@
.context("Trying to insert key parameters.")?;
metadata.store_in_db(key_id.id(), tx).context("Trying to insert key metadata.")?;
let need_gc = Self::rebind_alias(tx, &key_id, alias, &domain, namespace, key_type)
- .context("Trying to rebind alias.")?;
+ .context("Trying to rebind alias.")?
+ || need_gc;
Ok(key_id).do_gc(need_gc)
})
.context("In store_new_key.")
@@ -3234,6 +3285,7 @@
use std::sync::{Arc, RwLock};
use std::thread;
use std::time::{Duration, SystemTime};
+ use crate::utils::AesGcm;
#[cfg(disabled)]
use std::time::Instant;
@@ -5557,8 +5609,7 @@
None,
)?;
- let decrypted_secret_bytes =
- loaded_super_key.aes_gcm_decrypt(&encrypted_secret, &iv, &tag)?;
+ let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?;
assert_eq!(secret_bytes, &*decrypted_secret_bytes);
Ok(())
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index b801ed3..cbc680d 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -17,8 +17,8 @@
use crate::{
error::{Error as KsError, ResponseCode},
key_parameter::{KeyParameter, KeyParameterValue},
- super_key::SuperKeyManager,
utils::uid_to_android_user,
+ utils::AesGcm,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
SecurityLevel::SecurityLevel, Tag::Tag, TagType::TagType,
@@ -26,6 +26,7 @@
use anyhow::{Context, Result};
use keystore2_crypto::{aes_gcm_decrypt, Password, ZVec};
use std::collections::{HashMap, HashSet};
+use std::sync::Arc;
use std::{convert::TryInto, fs::File, path::Path, path::PathBuf};
use std::{
fs,
@@ -87,6 +88,14 @@
/// an invalid alias filename encoding.
#[error("Invalid alias filename encoding.")]
BadEncoding,
+ /// A component of the requested entry other than the KM key blob itself
+ /// was encrypted and no super key was provided.
+ #[error("Locked entry component.")]
+ LockedComponent,
+ /// The uids presented to move_keystore_entry belonged to different
+ /// Android users.
+ #[error("Cannot move keys across Android users.")]
+ AndroidUserMismatch,
}
/// The blob payload, optionally with all information required to decrypt it.
@@ -96,6 +105,16 @@
Generic(Vec<u8>),
/// A legacy key characteristics file. This has only a single list of Authorizations.
Characteristics(Vec<u8>),
+ /// A legacy key characteristics file. This has only a single list of Authorizations.
+ /// Additionally, this characteristics file was encrypted with the user's super key.
+ EncryptedCharacteristics {
+ /// Initialization vector.
+ iv: Vec<u8>,
+ /// Aead tag for integrity verification.
+ tag: Vec<u8>,
+ /// Ciphertext.
+ data: Vec<u8>,
+ },
/// A key characteristics cache has both a hardware enforced and a software enforced list
/// of authorizations.
CharacteristicsCache(Vec<u8>),
@@ -124,6 +143,17 @@
/// Ciphertext.
data: Vec<u8>,
},
+ /// An encrypted blob. Includes the initialization vector, the aead tag, and the
+ /// ciphertext data. The key can be selected from context, i.e., the owner of the key
+ /// blob. This is a special case for generic encrypted blobs as opposed to key blobs.
+ EncryptedGeneric {
+ /// Initialization vector.
+ iv: Vec<u8>,
+ /// Aead tag for integrity verification.
+ tag: Vec<u8>,
+ /// Ciphertext.
+ data: Vec<u8>,
+ },
/// Holds the plaintext key blob either after unwrapping an encrypted blob or when the
/// blob was stored in "plaintext" on disk. The "plaintext" of a key blob is not actual
/// plaintext because all KeyMint blobs are encrypted with a device bound key. The key
@@ -132,6 +162,19 @@
Decrypted(ZVec),
}
+/// Keystore used two different key characteristics file formats in the past.
+/// The key characteristics cache which superseded the characteristics file.
+/// The latter stored only one list of key parameters, while the former stored
+/// a hardware enforced and a software enforced list. This Enum indicates which
+/// type was read from the file system.
+#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)]
+pub enum LegacyKeyCharacteristics {
+ /// A characteristics cache was read.
+ Cache(Vec<KeyParameter>),
+ /// A characteristics file was read.
+ File(Vec<KeyParameter>),
+}
+
/// Represents a loaded legacy key blob file.
#[derive(Debug, Eq, PartialEq)]
pub struct Blob {
@@ -169,6 +212,16 @@
}
impl Blob {
+ /// Creates a new blob from flags and value.
+ pub fn new(flags: u8, value: BlobValue) -> Self {
+ Self { flags, value }
+ }
+
+ /// Return the raw flags of this Blob.
+ pub fn get_flags(&self) -> u8 {
+ self.flags
+ }
+
/// This blob was generated with a fallback software KM device.
pub fn is_fallback(&self) -> bool {
self.flags & flags::FALLBACK != 0
@@ -212,10 +265,14 @@
// version (1 Byte)
// blob_type (1 Byte)
// flags (1 Byte)
- // info (1 Byte)
+ // info (1 Byte) Size of an info field appended to the blob.
// initialization_vector (16 Bytes)
// integrity (MD5 digest or gcm tag) (16 Bytes)
// length (4 Bytes)
+ //
+ // The info field is used to store the salt for password encrypted blobs.
+ // The beginning of the info field can be computed from the file length
+ // and the info byte from the header: <file length> - <info> bytes.
const COMMON_HEADER_SIZE: usize = 4 + Self::IV_SIZE + Self::GCM_TAG_LENGTH + 4;
const VERSION_OFFSET: usize = 0;
@@ -341,12 +398,28 @@
let tag = &buffer[Self::AEAD_TAG_OFFSET..Self::AEAD_TAG_OFFSET + Self::GCM_TAG_LENGTH];
match (blob_type, is_encrypted, salt) {
- (blob_types::GENERIC, _, _) => {
+ (blob_types::GENERIC, false, _) => {
Ok(Blob { flags, value: BlobValue::Generic(value.to_vec()) })
}
- (blob_types::KEY_CHARACTERISTICS, _, _) => {
+ (blob_types::GENERIC, true, _) => Ok(Blob {
+ flags,
+ value: BlobValue::EncryptedGeneric {
+ iv: iv.to_vec(),
+ tag: tag.to_vec(),
+ data: value.to_vec(),
+ },
+ }),
+ (blob_types::KEY_CHARACTERISTICS, false, _) => {
Ok(Blob { flags, value: BlobValue::Characteristics(value.to_vec()) })
}
+ (blob_types::KEY_CHARACTERISTICS, true, _) => Ok(Blob {
+ flags,
+ value: BlobValue::EncryptedCharacteristics {
+ iv: iv.to_vec(),
+ tag: tag.to_vec(),
+ data: value.to_vec(),
+ },
+ }),
(blob_types::KEY_CHARACTERISTICS_CACHE, _, _) => {
Ok(Blob { flags, value: BlobValue::CharacteristicsCache(value.to_vec()) })
}
@@ -427,6 +500,15 @@
.context("In new_from_stream_decrypt_with.")?,
),
}),
+ BlobValue::EncryptedGeneric { iv, tag, data } => Ok(Blob {
+ flags: blob.flags,
+ value: BlobValue::Generic(
+ decrypt(data, iv, tag, None, None)
+ .context("In new_from_stream_decrypt_with.")?[..]
+ .to_vec(),
+ ),
+ }),
+
_ => Ok(blob),
}
}
@@ -546,24 +628,91 @@
Ok(params)
}
+ /// This function takes a Blob and an optional AesGcm. Plain text blob variants are
+ /// passed through as is. If a super key is given an attempt is made to decrypt the
+ /// blob thereby mapping BlobValue variants as follows:
+ /// BlobValue::Encrypted => BlobValue::Decrypted
+ /// BlobValue::EncryptedGeneric => BlobValue::Generic
+ /// BlobValue::EncryptedCharacteristics => BlobValue::Characteristics
+ /// If now super key is given or BlobValue::PwEncrypted is encountered,
+ /// Err(Error::LockedComponent) is returned.
+ fn decrypt_if_required(super_key: &Option<Arc<dyn AesGcm>>, blob: Blob) -> Result<Blob> {
+ match blob {
+ Blob { value: BlobValue::Generic(_), .. }
+ | Blob { value: BlobValue::Characteristics(_), .. }
+ | Blob { value: BlobValue::CharacteristicsCache(_), .. }
+ | Blob { value: BlobValue::Decrypted(_), .. } => Ok(blob),
+ Blob { value: BlobValue::EncryptedCharacteristics { iv, tag, data }, flags }
+ if super_key.is_some() =>
+ {
+ Ok(Blob {
+ value: BlobValue::Characteristics(
+ super_key.as_ref().unwrap().decrypt(&data, &iv, &tag).context(
+ "In decrypt_if_required: Failed to decrypt EncryptedCharacteristics",
+ )?[..]
+ .to_vec(),
+ ),
+ flags,
+ })
+ }
+ Blob { value: BlobValue::Encrypted { iv, tag, data }, flags }
+ if super_key.is_some() =>
+ {
+ Ok(Blob {
+ value: BlobValue::Decrypted(
+ super_key
+ .as_ref()
+ .unwrap()
+ .decrypt(&data, &iv, &tag)
+ .context("In decrypt_if_required: Failed to decrypt Encrypted")?,
+ ),
+ flags,
+ })
+ }
+ Blob { value: BlobValue::EncryptedGeneric { iv, tag, data }, flags }
+ if super_key.is_some() =>
+ {
+ Ok(Blob {
+ value: BlobValue::Generic(
+ super_key
+ .as_ref()
+ .unwrap()
+ .decrypt(&data, &iv, &tag)
+ .context("In decrypt_if_required: Failed to decrypt Encrypted")?[..]
+ .to_vec(),
+ ),
+ flags,
+ })
+ }
+ // This arm catches all encrypted cases where super key is not present or cannot
+ // decrypt the blob, the latter being BlobValue::PwEncrypted.
+ _ => Err(Error::LockedComponent)
+ .context("In decrypt_if_required: Encountered encrypted blob without super key."),
+ }
+ }
+
fn read_characteristics_file(
&self,
uid: u32,
prefix: &str,
alias: &str,
hw_sec_level: SecurityLevel,
- ) -> Result<Vec<KeyParameter>> {
+ super_key: &Option<Arc<dyn AesGcm>>,
+ ) -> Result<LegacyKeyCharacteristics> {
let blob = Self::read_generic_blob(&self.make_chr_filename(uid, alias, prefix))
.context("In read_characteristics_file")?;
let blob = match blob {
- None => return Ok(Vec::new()),
+ None => return Ok(LegacyKeyCharacteristics::Cache(Vec::new())),
Some(blob) => blob,
};
- let mut stream = match blob.value() {
- BlobValue::Characteristics(data) => &data[..],
- BlobValue::CharacteristicsCache(data) => &data[..],
+ let blob = Self::decrypt_if_required(super_key, blob)
+ .context("In read_characteristics_file: Trying to decrypt blob.")?;
+
+ let (mut stream, is_cache) = match blob.value() {
+ BlobValue::Characteristics(data) => (&data[..], false),
+ BlobValue::CharacteristicsCache(data) => (&data[..], true),
_ => {
return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(concat!(
"In read_characteristics_file: ",
@@ -589,7 +738,12 @@
.into_iter()
.map(|value| KeyParameter::new(value, SecurityLevel::KEYSTORE));
- Ok(hw_list.into_iter().flatten().chain(sw_list).collect())
+ let params: Vec<KeyParameter> = hw_list.into_iter().flatten().chain(sw_list).collect();
+ if is_cache {
+ Ok(LegacyKeyCharacteristics::Cache(params))
+ } else {
+ Ok(LegacyKeyCharacteristics::File(params))
+ }
}
// This is a list of known prefixes that the Keystore 1.0 SPI used to use.
@@ -639,14 +793,40 @@
Ok(Some(Self::new_from_stream(&mut file).context("In read_generic_blob.")?))
}
+ fn read_generic_blob_decrypt_with<F>(path: &Path, decrypt: F) -> Result<Option<Blob>>
+ where
+ F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>,
+ {
+ let mut file = match Self::with_retry_interrupted(|| File::open(path)) {
+ Ok(file) => file,
+ Err(e) => match e.kind() {
+ ErrorKind::NotFound => return Ok(None),
+ _ => return Err(e).context("In read_generic_blob_decrypt_with."),
+ },
+ };
+
+ Ok(Some(
+ Self::new_from_stream_decrypt_with(&mut file, decrypt)
+ .context("In read_generic_blob_decrypt_with.")?,
+ ))
+ }
+
/// Read a legacy keystore entry blob.
- pub fn read_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<Option<Vec<u8>>> {
+ pub fn read_legacy_keystore_entry<F>(
+ &self,
+ uid: u32,
+ alias: &str,
+ decrypt: F,
+ ) -> Result<Option<Vec<u8>>>
+ where
+ F: FnOnce(&[u8], &[u8], &[u8], Option<&[u8]>, Option<usize>) -> Result<ZVec>,
+ {
let path = match self.make_legacy_keystore_entry_filename(uid, alias) {
Some(path) => path,
None => return Ok(None),
};
- let blob = Self::read_generic_blob(&path)
+ let blob = Self::read_generic_blob_decrypt_with(&path, decrypt)
.context("In read_legacy_keystore_entry: Failed to read blob.")?;
Ok(blob.and_then(|blob| match blob.value {
@@ -659,22 +839,23 @@
}
/// Remove a legacy keystore entry by the name alias with owner uid.
- pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<()> {
+ pub fn remove_legacy_keystore_entry(&self, uid: u32, alias: &str) -> Result<bool> {
let path = match self.make_legacy_keystore_entry_filename(uid, alias) {
Some(path) => path,
- None => return Ok(()),
+ None => return Ok(false),
};
if let Err(e) = Self::with_retry_interrupted(|| fs::remove_file(path.as_path())) {
match e.kind() {
- ErrorKind::NotFound => return Ok(()),
+ ErrorKind::NotFound => return Ok(false),
_ => return Err(e).context("In remove_legacy_keystore_entry."),
}
}
let user_id = uid_to_android_user(uid);
self.remove_user_dir_if_empty(user_id)
- .context("In remove_legacy_keystore_entry: Trying to remove empty user dir.")
+ .context("In remove_legacy_keystore_entry: Trying to remove empty user dir.")?;
+ Ok(true)
}
/// List all entries belonging to the given uid.
@@ -988,6 +1169,88 @@
Ok(something_was_deleted)
}
+ /// This function moves a keystore file if it exists. It constructs the source and destination
+ /// file name using the make_filename function with the arguments uid, alias, and prefix.
+ /// The function overwrites existing destination files silently. If the source does not exist,
+ /// this function has no side effect and returns successfully.
+ fn move_keystore_file_if_exists<F>(
+ src_uid: u32,
+ dest_uid: u32,
+ src_alias: &str,
+ dest_alias: &str,
+ prefix: &str,
+ make_filename: F,
+ ) -> Result<()>
+ where
+ F: Fn(u32, &str, &str) -> PathBuf,
+ {
+ let src_path = make_filename(src_uid, src_alias, prefix);
+ let dest_path = make_filename(dest_uid, dest_alias, prefix);
+ match Self::with_retry_interrupted(|| fs::rename(&src_path, &dest_path)) {
+ Err(e) if e.kind() == ErrorKind::NotFound => Ok(()),
+ r => r.context("In move_keystore_file_if_exists: Trying to rename."),
+ }
+ }
+
+ /// Moves a keystore entry from one uid to another. The uids must have the same android user
+ /// component. Moves across android users are not permitted.
+ pub fn move_keystore_entry(
+ &self,
+ src_uid: u32,
+ dest_uid: u32,
+ src_alias: &str,
+ dest_alias: &str,
+ ) -> Result<()> {
+ if src_uid == dest_uid {
+ // Nothing to do in the trivial case.
+ return Ok(());
+ }
+
+ if uid_to_android_user(src_uid) != uid_to_android_user(dest_uid) {
+ return Err(Error::AndroidUserMismatch).context("In move_keystore_entry.");
+ }
+
+ let prefixes = ["USRPKEY", "USRSKEY", "USRCERT", "CACERT"];
+ for prefix in prefixes {
+ Self::move_keystore_file_if_exists(
+ src_uid,
+ dest_uid,
+ src_alias,
+ dest_alias,
+ prefix,
+ |uid, alias, prefix| self.make_blob_filename(uid, alias, prefix),
+ )
+ .with_context(|| {
+ format!(
+ "In move_keystore_entry: Trying to move blob file with prefix: \"{}\"",
+ prefix
+ )
+ })?;
+ }
+
+ let prefixes = ["USRPKEY", "USRSKEY"];
+
+ for prefix in prefixes {
+ Self::move_keystore_file_if_exists(
+ src_uid,
+ dest_uid,
+ src_alias,
+ dest_alias,
+ prefix,
+ |uid, alias, prefix| self.make_chr_filename(uid, alias, prefix),
+ )
+ .with_context(|| {
+ format!(
+ "In move_keystore_entry: Trying to move characteristics file with \
+ prefix: \"{}\"",
+ prefix
+ )
+ })?;
+ }
+
+ Ok(())
+ }
+
fn remove_user_dir_if_empty(&self, user_id: u32) -> Result<()> {
if self
.is_empty_user(user_id)
@@ -1004,79 +1267,66 @@
&self,
uid: u32,
alias: &str,
- key_manager: Option<&SuperKeyManager>,
- ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
+ super_key: &Option<Arc<dyn AesGcm>>,
+ ) -> Result<(Option<(Blob, LegacyKeyCharacteristics)>, Option<Vec<u8>>, Option<Vec<u8>>)> {
let km_blob = self.read_km_blob_file(uid, alias).context("In load_by_uid_alias.")?;
let km_blob = match km_blob {
Some((km_blob, prefix)) => {
- let km_blob = match km_blob {
- Blob { flags: _, value: BlobValue::Decrypted(_) } => km_blob,
- // Unwrap the key blob if required and if we have key_manager.
- Blob { flags, value: BlobValue::Encrypted { ref iv, ref tag, ref data } } => {
- if let Some(key_manager) = key_manager {
- let decrypted = match key_manager
- .get_per_boot_key_by_user_id(uid_to_android_user(uid))
- {
- Some(key) => key.aes_gcm_decrypt(data, iv, tag).context(
- "In load_by_uid_alias: while trying to decrypt legacy blob.",
- )?,
- None => {
- return Err(KsError::Rc(ResponseCode::LOCKED)).context(format!(
- concat!(
- "In load_by_uid_alias: ",
- "User {} has not unlocked the keystore yet.",
- ),
- uid_to_android_user(uid)
- ))
- }
- };
- Blob { flags, value: BlobValue::Decrypted(decrypted) }
- } else {
- km_blob
- }
- }
- _ => {
- return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
+ let km_blob =
+ match km_blob {
+ Blob { flags: _, value: BlobValue::Decrypted(_) }
+ | Blob { flags: _, value: BlobValue::Encrypted { .. } } => km_blob,
+ _ => return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
"In load_by_uid_alias: Found wrong blob type in legacy key blob file.",
- )
- }
- };
+ ),
+ };
let hw_sec_level = match km_blob.is_strongbox() {
true => SecurityLevel::STRONGBOX,
false => SecurityLevel::TRUSTED_ENVIRONMENT,
};
let key_parameters = self
- .read_characteristics_file(uid, &prefix, alias, hw_sec_level)
+ .read_characteristics_file(uid, &prefix, alias, hw_sec_level, super_key)
.context("In load_by_uid_alias.")?;
Some((km_blob, key_parameters))
}
None => None,
};
- let user_cert =
- match Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT"))
- .context("In load_by_uid_alias: While loading user cert.")?
- {
- Some(Blob { value: BlobValue::Generic(data), .. }) => Some(data),
- None => None,
- _ => {
- return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED)).context(
- "In load_by_uid_alias: Found unexpected blob type in USRCERT file",
- )
- }
- };
+ let user_cert_blob =
+ Self::read_generic_blob(&self.make_blob_filename(uid, alias, "USRCERT"))
+ .context("In load_by_uid_alias: While loading user cert.")?;
- let ca_cert = match Self::read_generic_blob(&self.make_blob_filename(uid, alias, "CACERT"))
- .context("In load_by_uid_alias: While loading ca cert.")?
- {
- Some(Blob { value: BlobValue::Generic(data), .. }) => Some(data),
- None => None,
- _ => {
+ let user_cert = if let Some(blob) = user_cert_blob {
+ let blob = Self::decrypt_if_required(super_key, blob)
+ .context("In load_by_uid_alias: While decrypting user cert.")?;
+
+ if let Blob { value: BlobValue::Generic(data), .. } = blob {
+ Some(data)
+ } else {
return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In load_by_uid_alias: Found unexpected blob type in CACERT file")
+ .context("In load_by_uid_alias: Found unexpected blob type in USRCERT file");
}
+ } else {
+ None
+ };
+
+ let ca_cert_blob = Self::read_generic_blob(&self.make_blob_filename(uid, alias, "CACERT"))
+ .context("In load_by_uid_alias: While loading ca cert.")?;
+
+ let ca_cert = if let Some(blob) = ca_cert_blob {
+ let blob = Self::decrypt_if_required(super_key, blob)
+ .context("In load_by_uid_alias: While decrypting ca cert.")?;
+
+ if let Blob { value: BlobValue::Generic(data), .. } = blob {
+ Some(data)
+ } else {
+ return Err(KsError::Rc(ResponseCode::VALUE_CORRUPTED))
+ .context("In load_by_uid_alias: Found unexpected blob type in CACERT file");
+ }
+ } else {
+ None
};
Ok((km_blob, user_cert, ca_cert))
@@ -1139,15 +1389,271 @@
#[cfg(test)]
mod test {
+ #![allow(dead_code)]
use super::*;
- use anyhow::anyhow;
- use keystore2_crypto::aes_gcm_decrypt;
+ use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt};
use rand::Rng;
use std::string::FromUtf8Error;
mod legacy_blob_test_vectors;
- use crate::error;
+ use crate::legacy_blob::blob_types::{
+ GENERIC, KEY_CHARACTERISTICS, KEY_CHARACTERISTICS_CACHE, KM_BLOB, SUPER_KEY,
+ SUPER_KEY_AES256,
+ };
use crate::legacy_blob::test::legacy_blob_test_vectors::*;
+ use anyhow::{anyhow, Result};
use keystore2_test_utils::TempDir;
+ use std::convert::TryInto;
+ use std::fs::OpenOptions;
+ use std::io::Write;
+ use std::ops::Deref;
+
+ /// This function takes a blob and synchronizes the encrypted/super encrypted flags
+ /// with the blob type for the pairs Generic/EncryptedGeneric,
+ /// Characteristics/EncryptedCharacteristics and Encrypted/Decrypted.
+ /// E.g. if a non encrypted enum variant is encountered with flags::SUPER_ENCRYPTED
+ /// or flags::ENCRYPTED is set, the payload is encrypted and the corresponding
+ /// encrypted variant is returned, and vice versa. All other variants remain untouched
+ /// even if flags and BlobValue variant are inconsistent.
+ fn prepare_blob(blob: Blob, key: &[u8]) -> Result<Blob> {
+ match blob {
+ Blob { value: BlobValue::Generic(data), flags } if blob.is_encrypted() => {
+ let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap();
+ Ok(Blob { value: BlobValue::EncryptedGeneric { data: ciphertext, iv, tag }, flags })
+ }
+ Blob { value: BlobValue::Characteristics(data), flags } if blob.is_encrypted() => {
+ let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap();
+ Ok(Blob {
+ value: BlobValue::EncryptedCharacteristics { data: ciphertext, iv, tag },
+ flags,
+ })
+ }
+ Blob { value: BlobValue::Decrypted(data), flags } if blob.is_encrypted() => {
+ let (ciphertext, iv, tag) = aes_gcm_encrypt(&data, key).unwrap();
+ Ok(Blob { value: BlobValue::Encrypted { data: ciphertext, iv, tag }, flags })
+ }
+ Blob { value: BlobValue::EncryptedGeneric { data, iv, tag }, flags }
+ if !blob.is_encrypted() =>
+ {
+ let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap();
+ Ok(Blob { value: BlobValue::Generic(plaintext[..].to_vec()), flags })
+ }
+ Blob { value: BlobValue::EncryptedCharacteristics { data, iv, tag }, flags }
+ if !blob.is_encrypted() =>
+ {
+ let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap();
+ Ok(Blob { value: BlobValue::Characteristics(plaintext[..].to_vec()), flags })
+ }
+ Blob { value: BlobValue::Encrypted { data, iv, tag }, flags }
+ if !blob.is_encrypted() =>
+ {
+ let plaintext = aes_gcm_decrypt(&data, &iv, &tag, key).unwrap();
+ Ok(Blob { value: BlobValue::Decrypted(plaintext), flags })
+ }
+ _ => Ok(blob),
+ }
+ }
+
+ struct LegacyBlobHeader {
+ version: u8,
+ blob_type: u8,
+ flags: u8,
+ info: u8,
+ iv: [u8; 12],
+ tag: [u8; 16],
+ blob_size: u32,
+ }
+
+ /// This function takes a Blob and writes it to out as a legacy blob file
+ /// version 3. Note that the flags field and the values field may be
+ /// inconsistent and could be sanitized by this function. It is intentionally
+ /// not done to enable tests to construct malformed blobs.
+ fn write_legacy_blob(out: &mut dyn Write, blob: Blob) -> Result<usize> {
+ let (header, data, salt) = match blob {
+ Blob { value: BlobValue::Generic(data), flags } => (
+ LegacyBlobHeader {
+ version: 3,
+ blob_type: GENERIC,
+ flags,
+ info: 0,
+ iv: [0u8; 12],
+ tag: [0u8; 16],
+ blob_size: data.len() as u32,
+ },
+ data,
+ None,
+ ),
+ Blob { value: BlobValue::Characteristics(data), flags } => (
+ LegacyBlobHeader {
+ version: 3,
+ blob_type: KEY_CHARACTERISTICS,
+ flags,
+ info: 0,
+ iv: [0u8; 12],
+ tag: [0u8; 16],
+ blob_size: data.len() as u32,
+ },
+ data,
+ None,
+ ),
+ Blob { value: BlobValue::CharacteristicsCache(data), flags } => (
+ LegacyBlobHeader {
+ version: 3,
+ blob_type: KEY_CHARACTERISTICS_CACHE,
+ flags,
+ info: 0,
+ iv: [0u8; 12],
+ tag: [0u8; 16],
+ blob_size: data.len() as u32,
+ },
+ data,
+ None,
+ ),
+ Blob { value: BlobValue::PwEncrypted { iv, tag, data, salt, key_size }, flags } => (
+ LegacyBlobHeader {
+ version: 3,
+ blob_type: if key_size == keystore2_crypto::AES_128_KEY_LENGTH {
+ SUPER_KEY
+ } else {
+ SUPER_KEY_AES256
+ },
+ flags,
+ info: 0,
+ iv: iv.try_into().unwrap(),
+ tag: tag[..].try_into().unwrap(),
+ blob_size: data.len() as u32,
+ },
+ data,
+ Some(salt),
+ ),
+ Blob { value: BlobValue::Encrypted { iv, tag, data }, flags } => (
+ LegacyBlobHeader {
+ version: 3,
+ blob_type: KM_BLOB,
+ flags,
+ info: 0,
+ iv: iv.try_into().unwrap(),
+ tag: tag[..].try_into().unwrap(),
+ blob_size: data.len() as u32,
+ },
+ data,
+ None,
+ ),
+ Blob { value: BlobValue::EncryptedGeneric { iv, tag, data }, flags } => (
+ LegacyBlobHeader {
+ version: 3,
+ blob_type: GENERIC,
+ flags,
+ info: 0,
+ iv: iv.try_into().unwrap(),
+ tag: tag[..].try_into().unwrap(),
+ blob_size: data.len() as u32,
+ },
+ data,
+ None,
+ ),
+ Blob { value: BlobValue::EncryptedCharacteristics { iv, tag, data }, flags } => (
+ LegacyBlobHeader {
+ version: 3,
+ blob_type: KEY_CHARACTERISTICS,
+ flags,
+ info: 0,
+ iv: iv.try_into().unwrap(),
+ tag: tag[..].try_into().unwrap(),
+ blob_size: data.len() as u32,
+ },
+ data,
+ None,
+ ),
+ Blob { value: BlobValue::Decrypted(data), flags } => (
+ LegacyBlobHeader {
+ version: 3,
+ blob_type: KM_BLOB,
+ flags,
+ info: 0,
+ iv: [0u8; 12],
+ tag: [0u8; 16],
+ blob_size: data.len() as u32,
+ },
+ data[..].to_vec(),
+ None,
+ ),
+ };
+ write_legacy_blob_helper(out, &header, &data, salt.as_deref())
+ }
+
+ fn write_legacy_blob_helper(
+ out: &mut dyn Write,
+ header: &LegacyBlobHeader,
+ data: &[u8],
+ info: Option<&[u8]>,
+ ) -> Result<usize> {
+ if 1 != out.write(&[header.version])? {
+ return Err(anyhow!("Unexpected size while writing version."));
+ }
+ if 1 != out.write(&[header.blob_type])? {
+ return Err(anyhow!("Unexpected size while writing blob_type."));
+ }
+ if 1 != out.write(&[header.flags])? {
+ return Err(anyhow!("Unexpected size while writing flags."));
+ }
+ if 1 != out.write(&[header.info])? {
+ return Err(anyhow!("Unexpected size while writing info."));
+ }
+ if 12 != out.write(&header.iv)? {
+ return Err(anyhow!("Unexpected size while writing iv."));
+ }
+ if 4 != out.write(&[0u8; 4])? {
+ return Err(anyhow!("Unexpected size while writing last 4 bytes of iv."));
+ }
+ if 16 != out.write(&header.tag)? {
+ return Err(anyhow!("Unexpected size while writing tag."));
+ }
+ if 4 != out.write(&header.blob_size.to_be_bytes())? {
+ return Err(anyhow!("Unexpected size while writing blob size."));
+ }
+ if data.len() != out.write(data)? {
+ return Err(anyhow!("Unexpected size while writing blob."));
+ }
+ if let Some(info) = info {
+ if info.len() != out.write(info)? {
+ return Err(anyhow!("Unexpected size while writing inof."));
+ }
+ }
+ Ok(40 + data.len() + info.map(|v| v.len()).unwrap_or(0))
+ }
+
+ fn make_encrypted_characteristics_file<P: AsRef<Path>>(path: P, key: &[u8]) -> Result<()> {
+ let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap();
+ let blob = Blob {
+ value: BlobValue::Characteristics(KEY_PARAMETERS.to_vec()),
+ flags: flags::ENCRYPTED,
+ };
+ let blob = prepare_blob(blob, key).unwrap();
+ write_legacy_blob(&mut file, blob).unwrap();
+ Ok(())
+ }
+
+ fn make_encrypted_usr_cert_file<P: AsRef<Path>>(path: P, key: &[u8]) -> Result<()> {
+ let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap();
+ let blob = Blob {
+ value: BlobValue::Generic(LOADED_CERT_AUTHBOUND.to_vec()),
+ flags: flags::ENCRYPTED,
+ };
+ let blob = prepare_blob(blob, key).unwrap();
+ write_legacy_blob(&mut file, blob).unwrap();
+ Ok(())
+ }
+
+ fn make_encrypted_ca_cert_file<P: AsRef<Path>>(path: P, key: &[u8]) -> Result<()> {
+ let mut file = OpenOptions::new().write(true).create_new(true).open(path).unwrap();
+ let blob = Blob {
+ value: BlobValue::Generic(LOADED_CACERT_AUTHBOUND.to_vec()),
+ flags: flags::ENCRYPTED,
+ };
+ let blob = prepare_blob(blob, key).unwrap();
+ write_legacy_blob(&mut file, blob).unwrap();
+ Ok(())
+ }
#[test]
fn decode_encode_alias_test() {
@@ -1203,7 +1709,8 @@
fn read_golden_key_blob_test() -> anyhow::Result<()> {
let blob = LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*BLOB, |_, _, _, _, _| {
Err(anyhow!("should not be called"))
- })?;
+ })
+ .unwrap();
assert!(!blob.is_encrypted());
assert!(!blob.is_fallback());
assert!(!blob.is_strongbox());
@@ -1213,7 +1720,8 @@
let blob = LegacyBlobLoader::new_from_stream_decrypt_with(
&mut &*REAL_LEGACY_BLOB,
|_, _, _, _, _| Err(anyhow!("should not be called")),
- )?;
+ )
+ .unwrap();
assert!(!blob.is_encrypted());
assert!(!blob.is_fallback());
assert!(!blob.is_strongbox());
@@ -1301,62 +1809,75 @@
#[test]
fn test_legacy_blobs() -> anyhow::Result<()> {
- let temp_dir = TempDir::new("legacy_blob_test")?;
- std::fs::create_dir(&*temp_dir.build().push("user_0"))?;
+ let temp_dir = TempDir::new("legacy_blob_test").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
- std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY)?;
+ std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
std::fs::write(
&*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
USRPKEY_AUTHBOUND,
- )?;
+ )
+ .unwrap();
std::fs::write(
&*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
USRPKEY_AUTHBOUND_CHR,
- )?;
+ )
+ .unwrap();
std::fs::write(
&*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
USRCERT_AUTHBOUND,
- )?;
+ )
+ .unwrap();
std::fs::write(
&*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
CACERT_AUTHBOUND,
- )?;
+ )
+ .unwrap();
std::fs::write(
&*temp_dir.build().push("user_0").push("10223_USRPKEY_non_authbound"),
USRPKEY_NON_AUTHBOUND,
- )?;
+ )
+ .unwrap();
std::fs::write(
&*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_non_authbound"),
USRPKEY_NON_AUTHBOUND_CHR,
- )?;
+ )
+ .unwrap();
std::fs::write(
&*temp_dir.build().push("user_0").push("10223_USRCERT_non_authbound"),
USRCERT_NON_AUTHBOUND,
- )?;
+ )
+ .unwrap();
std::fs::write(
&*temp_dir.build().push("user_0").push("10223_CACERT_non_authbound"),
CACERT_NON_AUTHBOUND,
- )?;
+ )
+ .unwrap();
- let mut key_manager: SuperKeyManager = Default::default();
- let mut db = crate::database::KeystoreDB::new(temp_dir.path(), None)?;
let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
- assert_eq!(
- legacy_blob_loader
- .load_by_uid_alias(10223, "authbound", Some(&key_manager))
- .unwrap_err()
- .root_cause()
- .downcast_ref::<error::Error>(),
- Some(&error::Error::Rc(ResponseCode::LOCKED))
- );
-
- key_manager.unlock_user_key(&mut db, 0, &(PASSWORD.into()), &legacy_blob_loader)?;
+ if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
+ {
+ assert_eq!(flags, 4);
+ assert_eq!(
+ value,
+ BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ );
+ assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND);
+ assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND);
+ } else {
+ panic!("");
+ }
if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) =
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
{
assert_eq!(flags, 4);
//assert_eq!(value, BlobValue::Encrypted(..));
@@ -1366,7 +1887,7 @@
panic!("");
}
if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) =
- legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
{
assert_eq!(flags, 0);
assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?));
@@ -1383,11 +1904,11 @@
assert_eq!(
(None, None, None),
- legacy_blob_loader.load_by_uid_alias(10223, "authbound", Some(&key_manager))?
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)?
);
assert_eq!(
(None, None, None),
- legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", Some(&key_manager))?
+ legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)?
);
// The database should not be empty due to the super key.
@@ -1406,9 +1927,314 @@
Ok(())
}
+ struct TestKey(ZVec);
+
+ impl crate::utils::AesGcmKey for TestKey {
+ fn key(&self) -> &[u8] {
+ &self.0
+ }
+ }
+
+ impl Deref for TestKey {
+ type Target = [u8];
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+ }
+
+ #[test]
+ fn test_with_encrypted_characteristics() -> anyhow::Result<()> {
+ let temp_dir = TempDir::new("test_with_encrypted_characteristics").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap());
+ let super_key =
+ Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND,
+ )
+ .unwrap();
+ make_encrypted_characteristics_file(
+ &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+ &super_key,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+ USRCERT_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+ CACERT_AUTHBOUND,
+ )
+ .unwrap();
+
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert_eq!(
+ legacy_blob_loader
+ .load_by_uid_alias(10223, "authbound", &None)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>(),
+ Some(&Error::LockedComponent)
+ );
+
+ assert_eq!(
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(),
+ (
+ Some((
+ Blob {
+ flags: 4,
+ value: BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ },
+ structured_test_params()
+ )),
+ Some(LOADED_CERT_AUTHBOUND.to_vec()),
+ Some(LOADED_CACERT_AUTHBOUND.to_vec())
+ )
+ );
+
+ legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty().unwrap());
+ assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+ assert!(legacy_blob_loader.is_empty().unwrap());
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_with_encrypted_certificates() -> anyhow::Result<()> {
+ let temp_dir = TempDir::new("test_with_encrypted_certificates").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap());
+ let super_key =
+ Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND_CHR,
+ )
+ .unwrap();
+ make_encrypted_usr_cert_file(
+ &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+ &super_key,
+ )
+ .unwrap();
+ make_encrypted_ca_cert_file(
+ &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+ &super_key,
+ )
+ .unwrap();
+
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert_eq!(
+ legacy_blob_loader
+ .load_by_uid_alias(10223, "authbound", &None)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>(),
+ Some(&Error::LockedComponent)
+ );
+
+ assert_eq!(
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(),
+ (
+ Some((
+ Blob {
+ flags: 4,
+ value: BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ },
+ structured_test_params_cache()
+ )),
+ Some(LOADED_CERT_AUTHBOUND.to_vec()),
+ Some(LOADED_CACERT_AUTHBOUND.to_vec())
+ )
+ );
+
+ legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap()
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty().unwrap());
+ assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+ assert!(legacy_blob_loader.is_empty().unwrap());
+
+ Ok(())
+ }
+
+ #[test]
+ fn test_in_place_key_migration() -> anyhow::Result<()> {
+ let temp_dir = TempDir::new("test_in_place_key_migration").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ let pw: Password = PASSWORD.into();
+ let pw_key = TestKey(pw.derive_key(Some(SUPERKEY_SALT), 32).unwrap());
+ let super_key =
+ Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap()));
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap();
+
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND,
+ )
+ .unwrap();
+ std::fs::write(
+ &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"),
+ USRPKEY_AUTHBOUND_CHR,
+ )
+ .unwrap();
+ make_encrypted_usr_cert_file(
+ &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"),
+ &super_key,
+ )
+ .unwrap();
+ make_encrypted_ca_cert_file(
+ &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"),
+ &super_key,
+ )
+ .unwrap();
+
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert_eq!(
+ legacy_blob_loader
+ .load_by_uid_alias(10223, "authbound", &None)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>(),
+ Some(&Error::LockedComponent)
+ );
+
+ let super_key: Option<Arc<dyn AesGcm>> = Some(super_key);
+
+ assert_eq!(
+ legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(),
+ (
+ Some((
+ Blob {
+ flags: 4,
+ value: BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ },
+ structured_test_params_cache()
+ )),
+ Some(LOADED_CERT_AUTHBOUND.to_vec()),
+ Some(LOADED_CACERT_AUTHBOUND.to_vec())
+ )
+ );
+
+ legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap();
+
+ assert_eq!(
+ legacy_blob_loader
+ .load_by_uid_alias(10224, "boundauth", &None)
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<Error>(),
+ Some(&Error::LockedComponent)
+ );
+
+ assert_eq!(
+ legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(),
+ (
+ Some((
+ Blob {
+ flags: 4,
+ value: BlobValue::Encrypted {
+ data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(),
+ iv: USRPKEY_AUTHBOUND_IV.to_vec(),
+ tag: USRPKEY_AUTHBOUND_TAG.to_vec()
+ }
+ },
+ structured_test_params_cache()
+ )),
+ Some(LOADED_CERT_AUTHBOUND.to_vec()),
+ Some(LOADED_CACERT_AUTHBOUND.to_vec())
+ )
+ );
+
+ legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed.");
+
+ assert_eq!(
+ (None, None, None),
+ legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap()
+ );
+
+ // The database should not be empty due to the super key.
+ assert!(!legacy_blob_loader.is_empty().unwrap());
+ assert!(!legacy_blob_loader.is_empty_user(0).unwrap());
+
+ // The database should be considered empty for user 1.
+ assert!(legacy_blob_loader.is_empty_user(1).unwrap());
+
+ legacy_blob_loader.remove_super_key(0);
+
+ // Now it should be empty.
+ assert!(legacy_blob_loader.is_empty_user(0).unwrap());
+ assert!(legacy_blob_loader.is_empty().unwrap());
+
+ Ok(())
+ }
+
#[test]
fn list_non_existing_user() -> Result<()> {
- let temp_dir = TempDir::new("list_non_existing_user")?;
+ let temp_dir = TempDir::new("list_non_existing_user").unwrap();
let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
assert!(legacy_blob_loader.list_user(20)?.is_empty());
@@ -1418,11 +2244,66 @@
#[test]
fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> {
- let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user")?;
+ let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap();
let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty());
Ok(())
}
+
+ #[test]
+ fn test_move_keystore_entry() {
+ let temp_dir = TempDir::new("test_move_keystore_entry").unwrap();
+ std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap();
+
+ const SOME_CONTENT: &[u8] = b"some content";
+ const ANOTHER_CONTENT: &[u8] = b"another content";
+ const SOME_FILENAME: &str = "some_file";
+ const ANOTHER_FILENAME: &str = "another_file";
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT)
+ .unwrap();
+
+ std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT)
+ .unwrap();
+
+ // Non existent source id silently ignored.
+ assert!(LegacyBlobLoader::move_keystore_file_if_exists(
+ 1,
+ 2,
+ "non_existent",
+ ANOTHER_FILENAME,
+ "ignored",
+ |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
+ )
+ .is_ok());
+
+ // Content of another_file has not changed.
+ let another_content =
+ std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
+ assert_eq!(&another_content, ANOTHER_CONTENT);
+
+ // Check that some_file still exists.
+ assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
+ // Existing target files are silently overwritten.
+
+ assert!(LegacyBlobLoader::move_keystore_file_if_exists(
+ 1,
+ 2,
+ SOME_FILENAME,
+ ANOTHER_FILENAME,
+ "ignored",
+ |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf()
+ )
+ .is_ok());
+
+ // Content of another_file is now "some content".
+ let another_content =
+ std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap();
+ assert_eq!(&another_content, SOME_CONTENT);
+
+ // Check that some_file no longer exists.
+ assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists());
+ }
}
diff --git a/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs b/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs
index 14bd40c..2049ac2 100644
--- a/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs
+++ b/keystore2/src/legacy_blob/test/legacy_blob_test_vectors.rs
@@ -12,6 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+use crate::key_parameter::{KeyParameter, KeyParameterValue};
+use crate::legacy_blob::LegacyKeyCharacteristics;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve,
+ HardwareAuthenticatorType::HardwareAuthenticatorType, KeyOrigin::KeyOrigin,
+ KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+
pub static BLOB: &[u8] = &[
3, // version
1, // type
@@ -22,6 +30,106 @@
0, 0, 0, 4, // length in big endian
0xde, 0xed, 0xbe, 0xef, // payload
];
+
+pub fn structured_test_params() -> LegacyKeyCharacteristics {
+ LegacyKeyCharacteristics::File(vec![
+ KeyParameter::new(KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), SecurityLevel::KEYSTORE),
+ KeyParameter::new(
+ KeyParameterValue::KeyPurpose(KeyPurpose::VERIFY),
+ SecurityLevel::KEYSTORE,
+ ),
+ KeyParameter::new(KeyParameterValue::Digest(Digest::SHA_2_256), SecurityLevel::KEYSTORE),
+ KeyParameter::new(
+ KeyParameterValue::UserSecureID(2100322049669824240),
+ SecurityLevel::KEYSTORE,
+ ),
+ KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::EC), SecurityLevel::KEYSTORE),
+ KeyParameter::new(KeyParameterValue::KeySize(256), SecurityLevel::KEYSTORE),
+ KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::KEYSTORE),
+ KeyParameter::new(
+ KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::FINGERPRINT),
+ SecurityLevel::KEYSTORE,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED),
+ SecurityLevel::KEYSTORE,
+ ),
+ KeyParameter::new(KeyParameterValue::OSVersion(110000), SecurityLevel::KEYSTORE),
+ KeyParameter::new(KeyParameterValue::OSPatchLevel(202101), SecurityLevel::KEYSTORE),
+ KeyParameter::new(KeyParameterValue::BootPatchLevel(20210105), SecurityLevel::KEYSTORE),
+ KeyParameter::new(KeyParameterValue::VendorPatchLevel(20210105), SecurityLevel::KEYSTORE),
+ ])
+}
+
+pub fn structured_test_params_cache() -> LegacyKeyCharacteristics {
+ LegacyKeyCharacteristics::Cache(vec![
+ KeyParameter::new(
+ KeyParameterValue::KeyPurpose(KeyPurpose::SIGN),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::KeyPurpose(KeyPurpose::VERIFY),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::Digest(Digest::SHA_2_256),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::UserSecureID(2100322049669824240),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::Algorithm(Algorithm::EC),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::KeySize(256), SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(
+ KeyParameterValue::EcCurve(EcCurve::P_256),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::FINGERPRINT),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(KeyParameterValue::OSVersion(110000), SecurityLevel::TRUSTED_ENVIRONMENT),
+ KeyParameter::new(
+ KeyParameterValue::OSPatchLevel(202101),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::BootPatchLevel(20210105),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::VendorPatchLevel(20210105),
+ SecurityLevel::TRUSTED_ENVIRONMENT,
+ ),
+ KeyParameter::new(
+ KeyParameterValue::CreationDateTime(1607149002000),
+ SecurityLevel::KEYSTORE,
+ ),
+ KeyParameter::new(KeyParameterValue::UserID(0), SecurityLevel::KEYSTORE),
+ ])
+}
+
+// One encoded list of key parameters.
+pub static KEY_PARAMETERS: &[u8] = &[
+ 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x03, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x20,
+ 0x04, 0x00, 0x00, 0x00, 0xf6, 0x01, 0x00, 0xa0, 0xf0, 0x7e, 0x7d, 0xb4, 0xc6, 0xd7, 0x25, 0x1d,
+ 0x02, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x30, 0x00, 0x01, 0x00, 0x00,
+ 0x0a, 0x00, 0x00, 0x10, 0x01, 0x00, 0x00, 0x00, 0x2d, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0xf8, 0x01, 0x00, 0x10, 0x02, 0x00, 0x00, 0x00, 0xbe, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+ 0xc1, 0x02, 0x00, 0x30, 0xb0, 0xad, 0x01, 0x00, 0xc2, 0x02, 0x00, 0x30, 0x75, 0x15, 0x03, 0x00,
+ 0xcf, 0x02, 0x00, 0x30, 0xb9, 0x61, 0x34, 0x01, 0xce, 0x02, 0x00, 0x30, 0xb9, 0x61, 0x34, 0x01,
+ 0x30, 0x01, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00,
+];
+
pub static REAL_LEGACY_BLOB: &[u8] = &[
0x03, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@@ -164,6 +272,24 @@
0x76, 0x04, 0x2a, 0x48, 0xd1, 0xa7, 0x59, 0xd1, 0x04, 0x5b, 0xb4, 0x8a, 0x09, 0x22, 0x13, 0x0c,
0x94, 0xb6, 0x67, 0x7b, 0x39, 0x85, 0x28, 0x11,
];
+
+pub static SUPERKEY_IV: &[u8] = &[
+ 0x9a, 0x81, 0x56, 0x7d, 0xf5, 0x86, 0x7c, 0x62, 0xd7, 0xf9, 0x26, 0x06, 0x00, 0x00, 0x00, 0x00,
+];
+
+pub static SUPERKEY_TAG: &[u8] = &[
+ 0xde, 0x2a, 0xcb, 0xac, 0x98, 0x57, 0x2b, 0xe5, 0x57, 0x18, 0x78, 0x57, 0x6e, 0x10, 0x09, 0x84,
+];
+
+pub static SUPERKEY_SALT: &[u8] = &[
+ 0x04, 0x5b, 0xb4, 0x8a, 0x09, 0x22, 0x13, 0x0c, 0x94, 0xb6, 0x67, 0x7b, 0x39, 0x85, 0x28, 0x11,
+];
+
+pub static SUPERKEY_PAYLOAD: &[u8] = &[
+ 0xac, 0x6d, 0x13, 0xe6, 0xad, 0x2c, 0x89, 0x53, 0x1a, 0x99, 0xa5, 0x6c, 0x88, 0xe9, 0xeb, 0x5c,
+ 0xef, 0x68, 0x5e, 0x5b, 0x53, 0xa8, 0xe7, 0xa2, 0x76, 0x04, 0x2a, 0x48, 0xd1, 0xa7, 0x59, 0xd1,
+];
+
pub static USRPKEY_AUTHBOUND: &[u8] = &[
0x03, 0x04, 0x04, 0x00, 0x1c, 0x34, 0x87, 0x6f, 0xc8, 0x35, 0x0d, 0x34, 0x88, 0x59, 0xbc, 0xf5,
0x00, 0x00, 0x00, 0x00, 0x62, 0xe3, 0x38, 0x2d, 0xd0, 0x58, 0x40, 0xc1, 0xb0, 0xf2, 0x4a, 0xdd,
@@ -203,6 +329,52 @@
0xaf, 0x17, 0x2f, 0x21, 0x07, 0xea, 0x61, 0xff, 0x73, 0x08, 0x50, 0xb2, 0x19, 0xe8, 0x23, 0x1b,
0x83, 0x42, 0xdd, 0x4e, 0x6d,
];
+
+pub static USRPKEY_AUTHBOUND_IV: &[u8] = &[
+ 0x1c, 0x34, 0x87, 0x6f, 0xc8, 0x35, 0x0d, 0x34, 0x88, 0x59, 0xbc, 0xf5, 0x00, 0x00, 0x00, 0x00,
+];
+
+pub static USRPKEY_AUTHBOUND_TAG: &[u8] = &[
+ 0x62, 0xe3, 0x38, 0x2d, 0xd0, 0x58, 0x40, 0xc1, 0xb0, 0xf2, 0x4a, 0xdd, 0xf7, 0x81, 0x67, 0x0b,
+];
+
+pub static USRPKEY_AUTHBOUND_ENC_PAYLOAD: &[u8] = &[
+ 0x05, 0xb2, 0x5a, 0x1d, 0x1b, 0x25, 0x19, 0x48, 0xbf, 0x76, 0x0b, 0x37, 0x8c, 0x60, 0x52, 0xea,
+ 0x30, 0x2a, 0x2c, 0x89, 0x99, 0x95, 0x57, 0x5c, 0xec, 0x62, 0x3c, 0x08, 0x1a, 0xc6, 0x65, 0xf9,
+ 0xad, 0x24, 0x99, 0xf0, 0x5c, 0x44, 0xa0, 0xea, 0x9a, 0x60, 0xa2, 0xef, 0xf5, 0x27, 0x50, 0xba,
+ 0x9c, 0xef, 0xa6, 0x08, 0x88, 0x4b, 0x0f, 0xfe, 0x5d, 0x41, 0xac, 0xba, 0xef, 0x9d, 0xa4, 0xb7,
+ 0x72, 0xd3, 0xc8, 0x11, 0x92, 0x06, 0xf6, 0x26, 0xdf, 0x90, 0xe2, 0x66, 0x89, 0xf3, 0x85, 0x16,
+ 0x4a, 0xdf, 0x7f, 0xac, 0x94, 0x4a, 0x1c, 0xce, 0x18, 0xee, 0xf4, 0x1f, 0x8e, 0xd6, 0xaf, 0xfd,
+ 0x1d, 0xe5, 0x80, 0x4a, 0x6b, 0xbf, 0x91, 0xe2, 0x36, 0x1d, 0xb3, 0x53, 0x12, 0xfd, 0xc9, 0x0b,
+ 0xa6, 0x69, 0x00, 0x45, 0xcb, 0x4c, 0x40, 0x6b, 0x70, 0xcb, 0xd2, 0xa0, 0x44, 0x0b, 0x4b, 0xec,
+ 0xd6, 0x4f, 0x6f, 0x64, 0x37, 0xa7, 0xc7, 0x25, 0x54, 0xf4, 0xac, 0x6b, 0x34, 0x53, 0xea, 0x4e,
+ 0x56, 0x49, 0xba, 0xf4, 0x1e, 0xc6, 0x52, 0x8f, 0xf4, 0x85, 0xe7, 0xb5, 0xaf, 0x49, 0x68, 0xb3,
+ 0xb8, 0x7d, 0x63, 0xfc, 0x6e, 0x83, 0xa0, 0xf3, 0x91, 0x04, 0x80, 0xfd, 0xc5, 0x54, 0x7e, 0x92,
+ 0x1a, 0x87, 0x2c, 0x6e, 0xa6, 0x29, 0xb9, 0x1e, 0x3f, 0xef, 0x30, 0x12, 0x7b, 0x2f, 0xa2, 0x16,
+ 0x61, 0x8a, 0xcf, 0x14, 0x2d, 0x62, 0x98, 0x15, 0xae, 0x3b, 0xe6, 0x08, 0x1e, 0xb1, 0xf1, 0x21,
+ 0xb0, 0x50, 0xc0, 0x4b, 0x81, 0x71, 0x29, 0xe7, 0x86, 0xbf, 0x29, 0xe1, 0xeb, 0xfe, 0xbc, 0x11,
+ 0x3c, 0xc6, 0x15, 0x47, 0x9b, 0x41, 0x84, 0x61, 0x33, 0xbf, 0xca, 0xfe, 0x24, 0x92, 0x9e, 0x70,
+ 0x26, 0x36, 0x46, 0xca, 0xfe, 0xd3, 0x5a, 0x1d, 0x9e, 0x30, 0x19, 0xbd, 0x26, 0x49, 0xb4, 0x90,
+ 0x0c, 0x8d, 0xa2, 0x28, 0xa6, 0x24, 0x62, 0x6b, 0xe2, 0xfa, 0xe0, 0x53, 0xaa, 0x01, 0xeb, 0xaa,
+ 0x41, 0x2b, 0xcb, 0xb1, 0x08, 0x66, 0x9d, 0x21, 0x2d, 0x2a, 0x47, 0x44, 0xee, 0xd5, 0x06, 0xe3,
+ 0x4a, 0xb9, 0x3f, 0xcd, 0x78, 0x67, 0x89, 0x5b, 0xf7, 0x51, 0xc0, 0xc4, 0xa9, 0x68, 0xee, 0x44,
+ 0x9c, 0x47, 0xa4, 0xbd, 0x6f, 0x7b, 0xdd, 0x64, 0xa8, 0xc7, 0x1e, 0x77, 0x1d, 0x68, 0x87, 0xaa,
+ 0xae, 0x3c, 0xfc, 0x58, 0xb6, 0x3c, 0xcf, 0x58, 0xd0, 0x10, 0xaa, 0xef, 0xf0, 0x98, 0x67, 0x14,
+ 0x29, 0x4d, 0x40, 0x8b, 0xe5, 0xb1, 0xdf, 0x7f, 0x40, 0xb1, 0xd8, 0xea, 0x6c, 0xa8, 0xf7, 0x64,
+ 0xed, 0x02, 0x8d, 0xe7, 0x93, 0xfe, 0x79, 0x9a, 0x88, 0x62, 0x4f, 0xd0, 0x8a, 0x80, 0x36, 0x42,
+ 0x0a, 0xf1, 0xa2, 0x0e, 0x30, 0x39, 0xbd, 0x26, 0x1d, 0xd4, 0xf1, 0xc8, 0x6e, 0xdd, 0xc5, 0x41,
+ 0x29, 0xd8, 0xc1, 0x9e, 0x24, 0xf0, 0x25, 0x07, 0x05, 0x06, 0xc5, 0x08, 0xe3, 0x02, 0x2b, 0xe1,
+ 0x40, 0xc5, 0x67, 0xd2, 0x82, 0x96, 0x20, 0x80, 0xcf, 0x87, 0x3a, 0xc6, 0xb0, 0xbe, 0xcc, 0xbb,
+ 0x5a, 0x01, 0xab, 0xdd, 0x00, 0xc7, 0x0e, 0x7b, 0x02, 0x35, 0x27, 0xf4, 0x70, 0xfe, 0xd1, 0x19,
+ 0x6a, 0x64, 0x23, 0x9d, 0xba, 0xe9, 0x1d, 0x76, 0x90, 0xfe, 0x7f, 0xd6, 0xb5, 0xa0, 0xe7, 0xb9,
+ 0xf3, 0x56, 0x82, 0x8e, 0x57, 0x35, 0xf2, 0x69, 0xce, 0x52, 0xac, 0xc2, 0xf6, 0x5e, 0xb6, 0x54,
+ 0x95, 0x83, 0x3b, 0x9f, 0x48, 0xbb, 0x04, 0x06, 0xac, 0x55, 0xa9, 0xb9, 0xa3, 0xe7, 0x89, 0x6e,
+ 0x5c, 0x3a, 0x08, 0x67, 0x00, 0x8f, 0x1e, 0x26, 0x1b, 0x4d, 0x8a, 0xa6, 0x17, 0xa0, 0xa6, 0x18,
+ 0xe6, 0x31, 0x43, 0x15, 0xb8, 0x7f, 0x9e, 0xf5, 0x78, 0x58, 0x98, 0xb1, 0x8c, 0xf5, 0x22, 0x42,
+ 0x33, 0xc0, 0x42, 0x72, 0x4f, 0xce, 0x9f, 0x31, 0xaf, 0x17, 0x2f, 0x21, 0x07, 0xea, 0x61, 0xff,
+ 0x73, 0x08, 0x50, 0xb2, 0x19, 0xe8, 0x23, 0x1b, 0x83, 0x42, 0xdd, 0x4e, 0x6d,
+];
+
pub static USRPKEY_AUTHBOUND_CHR: &[u8] = &[
0x03, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
diff --git a/keystore2/src/legacy_importer.rs b/keystore2/src/legacy_importer.rs
index 3f37b14..81fb06c 100644
--- a/keystore2/src/legacy_importer.rs
+++ b/keystore2/src/legacy_importer.rs
@@ -14,18 +14,19 @@
//! This module acts as a bridge between the legacy key database and the keystore2 database.
-use crate::key_parameter::KeyParameterValue;
-use crate::legacy_blob::BlobValue;
-use crate::utils::{uid_to_android_user, watchdog as wd};
-use crate::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
-use crate::{database::KeyType, error::Error};
-use crate::{
- database::{
- BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, EncryptedBy, KeyMetaData,
- KeyMetaEntry, KeystoreDB, Uuid, KEYSTORE_UUID,
- },
- super_key::USER_SUPER_KEY,
+use crate::database::{
+ BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, EncryptedBy, KeyMetaData,
+ KeyMetaEntry, KeyType, KeystoreDB, Uuid, KEYSTORE_UUID,
};
+use crate::error::{map_km_error, Error};
+use crate::key_parameter::{KeyParameter, KeyParameterValue};
+use crate::legacy_blob::{self, Blob, BlobValue, LegacyKeyCharacteristics};
+use crate::super_key::USER_SUPER_KEY;
+use crate::utils::{
+ key_characteristics_to_internal, uid_to_android_user, upgrade_keyblob_if_required_with,
+ watchdog as wd, AesGcm,
+};
+use crate::{async_task::AsyncTask, legacy_blob::LegacyBlobLoader};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
@@ -279,6 +280,116 @@
result
}
+ /// This function behaves exactly like with_try_import unless the src_key has an encrypted
+ /// component (other than the key blob itself [1]) and super_key is None.
+ /// In that case the files belonging to the src_key will be renamed to be moved to the
+ /// namespace indicated by dst_key. The destination domain must be in Domain::APP.
+ ///
+ /// [1] Components that cannot be encrypted with the super key in keystore2 include the
+ /// characteristics file, which was encrypted before Android Q, and certificate entries
+ /// added by KeyChain before Android Q.
+ pub fn with_try_import_or_migrate_namespaces<F, T>(
+ &self,
+ src: (u32, &KeyDescriptor),
+ dest: (u32, &KeyDescriptor),
+ super_key: Option<Arc<dyn AesGcm + Send + Sync>>,
+ has_migrate_any_permission: bool,
+ key_accessor: F,
+ ) -> Result<Option<T>>
+ where
+ F: Fn() -> Result<T>,
+ {
+ let _wp = wd::watch_millis("LegacyImporter::with_try_import_or_migrate_namespaces", 500);
+
+ let (src_uid, src_key) = src;
+ let (dest_uid, dest_key) = dest;
+
+ // Access the key and return on success.
+ match key_accessor() {
+ Ok(result) => return Ok(Some(result)),
+ Err(e) => {
+ if e.root_cause().downcast_ref::<Error>()
+ != Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ {
+ return Err(e);
+ }
+ }
+ }
+
+ // Filter inputs. We can only load legacy app domain keys as well
+ // as the SELINUX WIFI_NAMESPACE, which will be populated from AID_WIFI.
+ let src_uid = match src_key {
+ KeyDescriptor { domain: Domain::APP, alias: Some(_), .. } => src_uid,
+ KeyDescriptor { domain: Domain::SELINUX, nspace, alias: Some(_), .. } => {
+ match *nspace {
+ Self::WIFI_NAMESPACE => Self::AID_WIFI,
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context(format!("No legacy keys for namespace {}", nspace))
+ }
+ }
+ }
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("No legacy keys for key descriptor.")
+ }
+ };
+
+ let dest_uid = match dest_key {
+ KeyDescriptor { domain: Domain::APP, alias: Some(_), .. } => Some(dest_uid),
+ KeyDescriptor { domain: Domain::SELINUX, alias: Some(_), .. } => {
+ // Domain::SELINUX cannot be migrated in place, but we cannot fail at this point
+ // because the import may succeed at which point the actual migration will
+ // be performed by the caller.
+ None
+ }
+ _ => {
+ return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("No legacy keys for key descriptor.")
+ }
+ };
+
+ let src_key_clone = src_key.clone();
+ let dest_key_clone = dest_key.clone();
+ let result = self.do_serialized(move |importer_state| {
+ let super_key = super_key.map(|sk| -> Arc<dyn AesGcm> { sk });
+ match (
+ importer_state.check_and_import(src_uid, src_key_clone.clone(), super_key),
+ dest_uid,
+ ) {
+ // The import into the database was successful. Return Ok(true)
+ (Ok(()), _) => Ok(true),
+ // The import failed because a certificate and/or characteristics
+ // file was encrypted and no super_key was available. Migration within the
+ // legacy database is attempted and Ok(false) is returned on success.
+ (Err(e), Some(dest_uid))
+ if has_migrate_any_permission
+ && e.root_cause().downcast_ref::<Error>()
+ == Some(&Error::Rc(ResponseCode::LOCKED)) =>
+ {
+ importer_state
+ .migrate_namespaces(src_uid, dest_uid, src_key_clone, dest_key_clone)
+ .map(|_| false)
+ }
+ (Err(e), _) => Err(e),
+ }
+ });
+
+ match result {
+ None => {
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND)).context("Legacy database is empty.")
+ }
+
+ Some(Ok(true)) => {
+ // After successful import try again.
+ key_accessor().map(|v| Some(v))
+ }
+ // The entry was successfully migrated within the legacy database.
+ Some(Ok(false)) => Ok(None),
+ Some(Err(e)) => Err(e),
+ }
+ }
+
/// Runs the key_accessor function and returns its result. If it returns an error and the
/// root cause was KEY_NOT_FOUND, tries to import a key with the given parameters from
/// the legacy database to the new database and runs the key_accessor function again if
@@ -287,6 +398,7 @@
&self,
key: &KeyDescriptor,
caller_uid: u32,
+ super_key: Option<Arc<dyn AesGcm + Send + Sync>>,
key_accessor: F,
) -> Result<T>
where
@@ -323,8 +435,10 @@
};
let key_clone = key.clone();
- let result = self
- .do_serialized(move |importer_state| importer_state.check_and_import(uid, key_clone));
+ let result = self.do_serialized(move |importer_state| {
+ let super_key = super_key.map(|sk| -> Arc<dyn AesGcm> { sk });
+ importer_state.check_and_import(uid, key_clone, super_key)
+ });
if let Some(result) = result {
result?;
@@ -430,9 +544,199 @@
.context("In list_uid: Trying to list legacy entries.")
}
+ fn migrate_namespaces(
+ &mut self,
+ src_uid: u32,
+ dest_uid: u32,
+ src_key: KeyDescriptor,
+ dest_key: KeyDescriptor,
+ ) -> Result<()> {
+ let src_alias = src_key.alias.ok_or_else(|| {
+ anyhow::anyhow!(Error::sys()).context(
+ "In legacy_migrator::migrate_namespace: src_key.alias must be Some because \
+ our caller must not have called us otherwise.",
+ )
+ })?;
+
+ if dest_key.domain != Domain::APP {
+ return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(
+ "In legacy_migrator::migrate_namespace: \
+ Legacy in-place migration to SELinux namespace is not supported.",
+ );
+ }
+
+ let dest_alias = dest_key.alias.ok_or_else(|| {
+ anyhow::anyhow!(Error::sys()).context(concat!(
+ "In legacy_migrator::migrate_namespace: dest_key.alias must be Some because ",
+ "our caller must not have called us otherwise."
+ ))
+ })?;
+
+ self.legacy_loader
+ .move_keystore_entry(src_uid, dest_uid, &src_alias, &dest_alias)
+ .context("In legacy_migrator::migrate_namespace: Moving key entry files.")
+ }
+
+ /// Checks if the key can potentially be unlocked. And deletes the key entry otherwise.
+ /// If the super_key has already been imported, the super key database id is returned.
+ fn get_super_key_id_check_unlockable_or_delete(
+ &mut self,
+ uid: u32,
+ alias: &str,
+ ) -> Result<i64> {
+ let user_id = uid_to_android_user(uid);
+
+ match self
+ .db
+ .load_super_key(&USER_SUPER_KEY, user_id)
+ .context("In get_super_key_id_check_unlockable_or_delete: Failed to load super key")?
+ {
+ Some((_, entry)) => Ok(entry.id()),
+ None => {
+ // This might be the first time we access the super key,
+ // and it may not have been imported. We cannot import
+ // the legacy super_key key now, because we need to reencrypt
+ // it which we cannot do if we are not unlocked, which we are
+ // not because otherwise the key would have been imported.
+ // We can check though if the key exists. If it does,
+ // we can return Locked. Otherwise, we can delete the
+ // key and return NotFound, because the key will never
+ // be unlocked again.
+ if self.legacy_loader.has_super_key(user_id) {
+ Err(Error::Rc(ResponseCode::LOCKED)).context(
+ "In get_super_key_id_check_unlockable_or_delete: \
+ Cannot import super key of this key while user is locked.",
+ )
+ } else {
+ self.legacy_loader.remove_keystore_entry(uid, alias).context(
+ "In get_super_key_id_check_unlockable_or_delete: \
+ Trying to remove obsolete key.",
+ )?;
+ Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
+ .context("In get_super_key_id_check_unlockable_or_delete: Obsolete key.")
+ }
+ }
+ }
+ }
+
+ fn characteristics_file_to_cache(
+ &mut self,
+ km_blob_params: Option<(Blob, LegacyKeyCharacteristics)>,
+ super_key: &Option<Arc<dyn AesGcm>>,
+ uid: u32,
+ alias: &str,
+ ) -> Result<(Option<(Blob, Vec<KeyParameter>)>, Option<(LegacyBlob<'static>, BlobMetaData)>)>
+ {
+ let (km_blob, params) = match km_blob_params {
+ Some((km_blob, LegacyKeyCharacteristics::File(params))) => (km_blob, params),
+ Some((km_blob, LegacyKeyCharacteristics::Cache(params))) => {
+ return Ok((Some((km_blob, params)), None))
+ }
+ None => return Ok((None, None)),
+ };
+
+ let km_uuid = self
+ .get_km_uuid(km_blob.is_strongbox())
+ .context("In characteristics_file_to_cache: Trying to get KM UUID")?;
+
+ let blob = match (&km_blob.value(), super_key.as_ref()) {
+ (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => {
+ let blob = super_key
+ .decrypt(data, iv, tag)
+ .context("In characteristics_file_to_cache: Decryption failed.")?;
+ LegacyBlob::ZVec(blob)
+ }
+ (BlobValue::Encrypted { .. }, None) => {
+ return Err(Error::Rc(ResponseCode::LOCKED)).context(
+ "In characteristics_file_to_cache: Oh uh, so close. \
+ This ancient key cannot be imported unless the user is unlocked.",
+ );
+ }
+ (BlobValue::Decrypted(data), _) => LegacyBlob::Ref(data),
+ _ => {
+ return Err(Error::sys())
+ .context("In characteristics_file_to_cache: Unexpected blob type.")
+ }
+ };
+
+ let (km_params, upgraded_blob) = get_key_characteristics_without_app_data(&km_uuid, &*blob)
+ .context(
+ "In characteristics_file_to_cache: Failed to get key characteristics from device.",
+ )?;
+
+ let flags = km_blob.get_flags();
+
+ let (current_blob, superseded_blob) = if let Some(upgraded_blob) = upgraded_blob {
+ match (km_blob.take_value(), super_key.as_ref()) {
+ (BlobValue::Encrypted { iv, tag, data }, Some(super_key)) => {
+ let super_key_id =
+ self.get_super_key_id_check_unlockable_or_delete(uid, alias).context(
+ "In characteristics_file_to_cache: \
+ How is there a super key but no super key id?",
+ )?;
+
+ let mut superseded_metadata = BlobMetaData::new();
+ superseded_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
+ superseded_metadata.add(BlobMetaEntry::AeadTag(tag.to_vec()));
+ superseded_metadata
+ .add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id)));
+ superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+ let superseded_blob = (LegacyBlob::Vec(data), superseded_metadata);
+
+ let (data, iv, tag) = super_key.encrypt(&upgraded_blob).context(
+ "In characteristics_file_to_cache: \
+ Failed to encrypt upgraded key blob.",
+ )?;
+ (
+ Blob::new(flags, BlobValue::Encrypted { data, iv, tag }),
+ Some(superseded_blob),
+ )
+ }
+ (BlobValue::Encrypted { .. }, None) => {
+ return Err(Error::sys()).context(
+ "In characteristics_file_to_cache: This should not be reachable. \
+ The blob could not have been decrypted above.",
+ );
+ }
+ (BlobValue::Decrypted(data), _) => {
+ let mut superseded_metadata = BlobMetaData::new();
+ superseded_metadata.add(BlobMetaEntry::KmUuid(km_uuid));
+ let superseded_blob = (LegacyBlob::ZVec(data), superseded_metadata);
+ (
+ Blob::new(
+ flags,
+ BlobValue::Decrypted(upgraded_blob.try_into().context(
+ "In characteristics_file_to_cache: \
+ Failed to convert upgraded blob to ZVec.",
+ )?),
+ ),
+ Some(superseded_blob),
+ )
+ }
+ _ => {
+ return Err(Error::sys()).context(
+ "In characteristics_file_to_cache: This should not be reachable. \
+ Any other variant should have resulted in a different error.",
+ )
+ }
+ }
+ } else {
+ (km_blob, None)
+ };
+
+ let params =
+ augment_legacy_characteristics_file_with_key_characteristics(km_params, params);
+ Ok((Some((current_blob, params)), superseded_blob))
+ }
+
/// This is a key import request that must run in the importer thread. This must
/// be passed to do_serialized.
- fn check_and_import(&mut self, uid: u32, mut key: KeyDescriptor) -> Result<()> {
+ fn check_and_import(
+ &mut self,
+ uid: u32,
+ mut key: KeyDescriptor,
+ super_key: Option<Arc<dyn AesGcm>>,
+ ) -> Result<()> {
let alias = key.alias.clone().ok_or_else(|| {
anyhow::anyhow!(Error::sys()).context(
"In check_and_import: Must be Some because \
@@ -451,49 +755,42 @@
// If the key is not found in the cache, try to load from the legacy database.
let (km_blob_params, user_cert, ca_cert) = self
.legacy_loader
- .load_by_uid_alias(uid, &alias, None)
+ .load_by_uid_alias(uid, &alias, &super_key)
+ .map_err(|e| {
+ if e.root_cause().downcast_ref::<legacy_blob::Error>()
+ == Some(&legacy_blob::Error::LockedComponent)
+ {
+ // There is no chance to succeed at this point. We just check if there is
+ // a super key so that this entry might be unlockable in the future.
+ // If not the entry will be deleted and KEY_NOT_FOUND is returned.
+ // If a super key id was returned we still have to return LOCKED but the key
+ // may be imported when the user unlocks the device.
+ self.get_super_key_id_check_unlockable_or_delete(uid, &alias)
+ .and_then::<i64, _>(|_| {
+ Err(Error::Rc(ResponseCode::LOCKED))
+ .context("Super key present but locked.")
+ })
+ .unwrap_err()
+ } else {
+ e
+ }
+ })
.context("In check_and_import: Trying to load legacy blob.")?;
+
+ let (km_blob_params, superseded_blob) = self
+ .characteristics_file_to_cache(km_blob_params, &super_key, uid, &alias)
+ .context("In check_and_import: Trying to update legacy charateristics.")?;
+
let result = match km_blob_params {
Some((km_blob, params)) => {
let is_strongbox = km_blob.is_strongbox();
+
let (blob, mut blob_metadata) = match km_blob.take_value() {
BlobValue::Encrypted { iv, tag, data } => {
// Get super key id for user id.
- let user_id = uid_to_android_user(uid as u32);
-
- let super_key_id = match self
- .db
- .load_super_key(&USER_SUPER_KEY, user_id)
- .context("In check_and_import: Failed to load super key")?
- {
- Some((_, entry)) => entry.id(),
- None => {
- // This might be the first time we access the super key,
- // and it may not have been imported. We cannot import
- // the legacy super_key key now, because we need to reencrypt
- // it which we cannot do if we are not unlocked, which we are
- // not because otherwise the key would have been imported.
- // We can check though if the key exists. If it does,
- // we can return Locked. Otherwise, we can delete the
- // key and return NotFound, because the key will never
- // be unlocked again.
- if self.legacy_loader.has_super_key(user_id) {
- return Err(Error::Rc(ResponseCode::LOCKED)).context(concat!(
- "In check_and_import: Cannot import super key of this ",
- "key while user is locked."
- ));
- } else {
- self.legacy_loader.remove_keystore_entry(uid, &alias).context(
- concat!(
- "In check_and_import: ",
- "Trying to remove obsolete key."
- ),
- )?;
- return Err(Error::Rc(ResponseCode::KEY_NOT_FOUND))
- .context("In check_and_import: Obsolete key.");
- }
- }
- };
+ let super_key_id = self
+ .get_super_key_id_check_unlockable_or_delete(uid, &alias)
+ .context("In check_and_import: Failed to get super key id.")?;
let mut blob_metadata = BlobMetaData::new();
blob_metadata.add(BlobMetaEntry::Iv(iv.to_vec()));
@@ -519,13 +816,18 @@
.context("In check_and_import: Trying to make creation time.")?;
metadata.add(KeyMetaEntry::CreationDate(creation_date));
+ let blob_info = BlobInfo::new_with_superseded(
+ &blob,
+ &blob_metadata,
+ superseded_blob.as_ref().map(|(b, m)| (&**b, m)),
+ );
// Store legacy key in the database.
self.db
.store_new_key(
&key,
KeyType::Client,
¶ms,
- &(&blob, &blob_metadata),
+ &blob_info,
&CertificateInfo::new(user_cert, ca_cert),
&metadata,
&km_uuid,
@@ -635,13 +937,17 @@
{
let (km_blob_params, _, _) = self
.legacy_loader
- .load_by_uid_alias(uid, &alias, None)
+ .load_by_uid_alias(uid, &alias, &None)
.context("In bulk_delete: Trying to load legacy blob.")?;
// Determine if the key needs special handling to be deleted.
let (need_gc, is_super_encrypted) = km_blob_params
.as_ref()
.map(|(blob, params)| {
+ let params = match params {
+ LegacyKeyCharacteristics::Cache(params)
+ | LegacyKeyCharacteristics::File(params) => params,
+ };
(
params.iter().any(|kp| {
KeyParameterValue::RollbackResistance == *kp.key_parameter_value()
@@ -714,18 +1020,68 @@
}
}
-enum LegacyBlob {
+enum LegacyBlob<'a> {
Vec(Vec<u8>),
ZVec(ZVec),
+ Ref(&'a [u8]),
}
-impl Deref for LegacyBlob {
+impl Deref for LegacyBlob<'_> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
match self {
Self::Vec(v) => v,
Self::ZVec(v) => v,
+ Self::Ref(v) => v,
}
}
}
+
+/// This function takes two KeyParameter lists. The first is assumed to have been retrieved from the
+/// KM back end using km_dev.getKeyCharacteristics. The second is assumed to have been retrieved
+/// from a legacy key characteristics file (not cache) as used in Android P and older. The function
+/// augments the former with entries from the latter only if no equivalent entry is present ignoring.
+/// the security level of enforcement. All entries in the latter are assumed to have security level
+/// KEYSTORE.
+fn augment_legacy_characteristics_file_with_key_characteristics<T>(
+ mut from_km: Vec<KeyParameter>,
+ legacy: T,
+) -> Vec<KeyParameter>
+where
+ T: IntoIterator<Item = KeyParameter>,
+{
+ for legacy_kp in legacy.into_iter() {
+ if !from_km
+ .iter()
+ .any(|km_kp| km_kp.key_parameter_value() == legacy_kp.key_parameter_value())
+ {
+ from_km.push(legacy_kp);
+ }
+ }
+ from_km
+}
+
+/// Attempts to retrieve the key characteristics for the given blob from the KM back end with the
+/// given UUID. It may upgrade the key blob in the process. In that case the upgraded blob is
+/// returned as the second tuple member.
+fn get_key_characteristics_without_app_data(
+ uuid: &Uuid,
+ blob: &[u8],
+) -> Result<(Vec<KeyParameter>, Option<Vec<u8>>)> {
+ let (km_dev, _) = crate::globals::get_keymint_dev_by_uuid(uuid)
+ .with_context(|| format!("In foo: Trying to get km device for id {:?}", uuid))?;
+
+ let (characteristics, upgraded_blob) = upgrade_keyblob_if_required_with(
+ &*km_dev,
+ blob,
+ &[],
+ |blob| {
+ let _wd = wd::watch_millis("In foo: Calling GetKeyCharacteristics.", 500);
+ map_km_error(km_dev.getKeyCharacteristics(blob, &[], &[]))
+ },
+ |_| Ok(()),
+ )
+ .context("In foo.")?;
+ Ok((key_characteristics_to_internal(characteristics), upgraded_blob))
+}
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index 71f43d6..0d637d8 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -23,7 +23,8 @@
use crate::permission::{KeyPerm, KeystorePerm};
use crate::super_key::{SuperKeyManager, UserState};
use crate::utils::{
- check_key_permission, check_keystore_permission, list_key_entries, watchdog as wd,
+ check_key_permission, check_keystore_permission, list_key_entries, uid_to_android_user,
+ watchdog as wd,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel,
@@ -255,34 +256,55 @@
}
};
- DB.with(|db| {
- let (key_id_guard, _) = LEGACY_IMPORTER
- .with_try_import(source, src_uid, || {
- db.borrow_mut().load_key_entry(
- source,
- KeyType::Client,
- KeyEntryLoadBits::NONE,
- src_uid,
- |k, av| {
- if migrate_any_key_permission {
- Ok(())
- } else {
- 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.")?;
+ let user_id = uid_to_android_user(dest_uid);
- db.borrow_mut().migrate_key_namespace(key_id_guard, destination, dest_uid, |k| {
- if migrate_any_key_permission {
- Ok(())
- } else {
- check_key_permission(KeyPerm::Rebind, k, &None)
- }
- })
+ if user_id != uid_to_android_user(src_uid)
+ && (source.domain == Domain::APP || destination.domain == Domain::APP)
+ {
+ return Err(Error::sys()).context(
+ "In migrate_key_namespace: Keys cannot be migrated across android users.",
+ );
+ }
+
+ let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id);
+
+ DB.with(|db| {
+ if let Some((key_id_guard, _)) = LEGACY_IMPORTER
+ .with_try_import_or_migrate_namespaces(
+ (src_uid, source),
+ (dest_uid, destination),
+ super_key,
+ migrate_any_key_permission,
+ || {
+ db.borrow_mut().load_key_entry(
+ source,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ src_uid,
+ |k, av| {
+ if migrate_any_key_permission {
+ Ok(())
+ } else {
+ 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.")?
+ {
+ db.borrow_mut().migrate_key_namespace(key_id_guard, destination, dest_uid, |k| {
+ if migrate_any_key_permission {
+ Ok(())
+ } else {
+ check_key_permission(KeyPerm::Rebind, k, &None)
+ }
+ })
+ } else {
+ Ok(())
+ }
})
}
diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs
index 0ee3db0..4ce9dce 100644
--- a/keystore2/src/raw_device.rs
+++ b/keystore2/src/raw_device.rs
@@ -16,8 +16,9 @@
use crate::{
database::{
- BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits,
- KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB, SubComponentType, Uuid,
+ BlobInfo, BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry,
+ KeyEntryLoadBits, KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB,
+ SubComponentType, Uuid,
},
error::{map_km_error, Error, ErrorCode},
globals::get_keymint_device,
@@ -125,7 +126,7 @@
key_desc,
key_type,
&key_parameters,
- &(&creation_result.keyBlob, &blob_metadata),
+ &BlobInfo::new(&creation_result.keyBlob, &blob_metadata),
&CertificateInfo::new(None, None),
&key_metadata,
&self.km_uuid,
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index eefbc20..4cf41c5 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -18,7 +18,7 @@
use crate::audit_log::{
log_key_deleted, log_key_generated, log_key_imported, log_key_integrity_violation,
};
-use crate::database::{CertificateInfo, KeyIdGuard};
+use crate::database::{BlobInfo, CertificateInfo, KeyIdGuard};
use crate::error::{self, map_km_error, map_or_log_err, Error, ErrorCode};
use crate::globals::{DB, ENFORCEMENTS, LEGACY_IMPORTER, SUPER_KEY};
use crate::key_parameter::KeyParameter as KsKeyParam;
@@ -182,7 +182,7 @@
&key,
KeyType::Client,
&key_parameters,
- &(&key_blob, &blob_metadata),
+ &BlobInfo::new(&key_blob, &blob_metadata),
&cert_info,
&key_metadata,
&self.km_uuid,
@@ -243,9 +243,13 @@
)
}
_ => {
+ let super_key = SUPER_KEY
+ .read()
+ .unwrap()
+ .get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
let (key_id_guard, mut key_entry) = DB
.with::<_, Result<(KeyIdGuard, KeyEntry)>>(|db| {
- LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().load_key_entry(
key,
KeyType::Client,
@@ -721,9 +725,11 @@
// Import_wrapped_key requires the rebind permission for the new key.
check_key_permission(KeyPerm::Rebind, &key, &None).context("In import_wrapped_key.")?;
+ let super_key = SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(user_id);
+
let (wrapping_key_id_guard, mut wrapping_key_entry) = DB
.with(|db| {
- LEGACY_IMPORTER.with_try_import(&key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(&key, caller_uid, super_key, || {
db.borrow_mut().load_key_entry(
wrapping_key,
KeyType::Client,
@@ -829,7 +835,7 @@
fn upgrade_keyblob_if_required_with<T, F>(
&self,
km_dev: &dyn IKeyMintDevice,
- key_id_guard: Option<KeyIdGuard>,
+ mut key_id_guard: Option<KeyIdGuard>,
key_blob: &KeyBlob,
blob_metadata: &BlobMetaData,
params: &[KeyParameter],
@@ -838,60 +844,42 @@
where
F: Fn(&[u8]) -> Result<T, Error>,
{
- match f(key_blob) {
- Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
- let upgraded_blob = {
- let _wp = self.watch_millis(
- concat!(
- "In KeystoreSecurityLevel::upgrade_keyblob_if_required_with: ",
- "calling upgradeKey."
- ),
- 500,
- );
- map_km_error(km_dev.upgradeKey(key_blob, params))
- }
- .context("In upgrade_keyblob_if_required_with: Upgrade failed.")?;
-
- if let Some(kid) = key_id_guard {
+ let (v, upgraded_blob) = crate::utils::upgrade_keyblob_if_required_with(
+ km_dev,
+ key_blob,
+ params,
+ f,
+ |upgraded_blob| {
+ if key_id_guard.is_some() {
+ // Unwrap cannot panic, because the is_some was true.
+ let kid = key_id_guard.take().unwrap();
Self::store_upgraded_keyblob(
kid,
blob_metadata.km_uuid(),
key_blob,
- &upgraded_blob,
+ upgraded_blob,
)
- .context(
- "In upgrade_keyblob_if_required_with: store_upgraded_keyblob failed",
- )?;
+ .context("In upgrade_keyblob_if_required_with: store_upgraded_keyblob failed")
+ } else {
+ Ok(())
}
+ },
+ )
+ .context("In KeystoreSecurityLevel::upgrade_keyblob_if_required_with.")?;
- match f(&upgraded_blob) {
- Ok(v) => Ok((v, Some(upgraded_blob))),
- Err(e) => Err(e).context(concat!(
+ // If no upgrade was needed, use the opportunity to reencrypt the blob if required
+ // and if the a key_id_guard is held. Note: key_id_guard can only be Some if no
+ // upgrade was performed above and if one was given in the first place.
+ if key_blob.force_reencrypt() {
+ if let Some(kid) = key_id_guard {
+ Self::store_upgraded_keyblob(kid, blob_metadata.km_uuid(), key_blob, key_blob)
+ .context(concat!(
"In upgrade_keyblob_if_required_with: ",
- "Failed to perform operation on second try."
- )),
- }
- }
- result => {
- if let Some(kid) = key_id_guard {
- if key_blob.force_reencrypt() {
- Self::store_upgraded_keyblob(
- kid,
- blob_metadata.km_uuid(),
- key_blob,
- key_blob,
- )
- .context(concat!(
- "In upgrade_keyblob_if_required_with: ",
- "store_upgraded_keyblob failed in forced reencrypt"
- ))?;
- }
- }
- result
- .map(|v| (v, None))
- .context("In upgrade_keyblob_if_required_with: Called closure failed.")
+ "store_upgraded_keyblob failed in forced reencrypt"
+ ))?;
}
}
+ Ok((v, upgraded_blob))
}
fn convert_storage_key_to_ephemeral(
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index 46bc8b0..79e7692 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -22,11 +22,11 @@
use crate::security_level::KeystoreSecurityLevel;
use crate::utils::{
check_grant_permission, check_key_permission, check_keystore_permission,
- key_parameters_to_authorizations, list_key_entries, watchdog as wd,
+ key_parameters_to_authorizations, list_key_entries, uid_to_android_user, watchdog as wd,
};
use crate::{
database::Uuid,
- globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_IMPORTER},
+ globals::{create_thread_local_db, DB, LEGACY_BLOB_LOADER, LEGACY_IMPORTER, SUPER_KEY},
};
use crate::{database::KEYSTORE_UUID, permission};
use crate::{
@@ -130,9 +130,13 @@
fn get_key_entry(&self, key: &KeyDescriptor) -> Result<KeyEntryResponse> {
let caller_uid = ThreadState::get_calling_uid();
+
+ let super_key =
+ SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
let (key_id_guard, mut key_entry) = DB
.with(|db| {
- LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().load_key_entry(
key,
KeyType::Client,
@@ -182,8 +186,11 @@
certificate_chain: Option<&[u8]>,
) -> Result<()> {
let caller_uid = ThreadState::get_calling_uid();
+ let super_key =
+ SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
DB.with::<_, Result<()>>(|db| {
- let entry = match LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+ let entry = match LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().load_key_entry(
key,
KeyType::Client,
@@ -291,8 +298,11 @@
fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {
let caller_uid = ThreadState::get_calling_uid();
+ let super_key =
+ SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
DB.with(|db| {
- LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().unbind_key(key, KeyType::Client, caller_uid, |k, av| {
check_key_permission(KeyPerm::Delete, k, &av).context("During delete_key.")
})
@@ -309,8 +319,11 @@
access_vector: permission::KeyPermSet,
) -> Result<KeyDescriptor> {
let caller_uid = ThreadState::get_calling_uid();
+ let super_key =
+ SUPER_KEY.read().unwrap().get_per_boot_key_by_user_id(uid_to_android_user(caller_uid));
+
DB.with(|db| {
- LEGACY_IMPORTER.with_try_import(key, caller_uid, || {
+ LEGACY_IMPORTER.with_try_import(key, caller_uid, super_key, || {
db.borrow_mut().grant(
key,
caller_uid,
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 2fb4991..376d44a 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -28,8 +28,7 @@
legacy_blob::LegacyBlobLoader,
legacy_importer::LegacyImporter,
raw_device::KeyMintDevice,
- utils::watchdog as wd,
- utils::AID_KEYSTORE,
+ utils::{watchdog as wd, AesGcm, AID_KEYSTORE},
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, BlockMode::BlockMode, HardwareAuthToken::HardwareAuthToken,
@@ -154,15 +153,22 @@
reencrypt_with: Option<Arc<SuperKey>>,
}
-impl SuperKey {
- /// For most purposes `unwrap_key` handles decryption,
- /// but legacy handling and some tests need to assume AES and decrypt directly.
- pub fn aes_gcm_decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> {
+impl AesGcm for SuperKey {
+ fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> {
if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm {
aes_gcm_decrypt(data, iv, tag, &self.key)
- .context("In aes_gcm_decrypt: decryption failed")
+ .context("In SuperKey::decrypt: Decryption failed.")
} else {
- Err(Error::sys()).context("In aes_gcm_decrypt: Key is not an AES key")
+ Err(Error::sys()).context("In SuperKey::decrypt: Key is not an AES key.")
+ }
+ }
+
+ fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
+ if self.algorithm == SuperEncryptionAlgorithm::Aes256Gcm {
+ aes_gcm_encrypt(plaintext, &self.key)
+ .context("In SuperKey::encrypt: Encryption failed.")
+ } else {
+ Err(Error::sys()).context("In SuperKey::encrypt: Key is not an AES key.")
}
}
}
@@ -386,7 +392,15 @@
})
}
- pub fn get_per_boot_key_by_user_id(&self, user_id: UserId) -> Option<Arc<SuperKey>> {
+ pub fn get_per_boot_key_by_user_id(
+ &self,
+ user_id: UserId,
+ ) -> Option<Arc<dyn AesGcm + Send + Sync>> {
+ self.get_per_boot_key_by_user_id_internal(user_id)
+ .map(|sk| -> Arc<dyn AesGcm + Send + Sync> { sk })
+ }
+
+ fn get_per_boot_key_by_user_id_internal(&self, user_id: UserId) -> Option<Arc<SuperKey>> {
self.data.user_keys.get(&user_id).and_then(|e| e.per_boot.as_ref().cloned())
}
@@ -464,7 +478,7 @@
match key.algorithm {
SuperEncryptionAlgorithm::Aes256Gcm => match (metadata.iv(), metadata.aead_tag()) {
(Some(iv), Some(tag)) => key
- .aes_gcm_decrypt(blob, iv, tag)
+ .decrypt(blob, iv, tag)
.context("In unwrap_key_with_key: Failed to decrypt the key blob."),
(iv, tag) => Err(Error::Rc(ResponseCode::VALUE_CORRUPTED)).context(format!(
concat!(
@@ -1093,7 +1107,7 @@
legacy_importer: &LegacyImporter,
user_id: UserId,
) -> Result<UserState> {
- match self.get_per_boot_key_by_user_id(user_id) {
+ match self.get_per_boot_key_by_user_id_internal(user_id) {
Some(super_key) => Ok(UserState::LskfUnlocked(super_key)),
None => {
// Check if a super key exists in the database or legacy database.
@@ -1127,7 +1141,7 @@
user_id: UserId,
password: Option<&Password>,
) -> Result<UserState> {
- match self.get_per_boot_key_by_user_id(user_id) {
+ match self.get_per_boot_key_by_user_id_internal(user_id) {
Some(_) if password.is_none() => {
// Transitioning to swiping, delete only the super key in database and cache,
// and super-encrypted keys in database (and in KM).
@@ -1162,7 +1176,7 @@
user_id: UserId,
password: &Password,
) -> Result<UserState> {
- match self.get_per_boot_key_by_user_id(user_id) {
+ match self.get_per_boot_key_by_user_id_internal(user_id) {
Some(super_key) => {
log::info!("In unlock_and_get_user_state. Trying to unlock when already unlocked.");
Ok(UserState::LskfUnlocked(super_key))
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index c924bef..a312c4b 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -15,7 +15,8 @@
//! This module implements utility functions used by the Keystore 2.0 service
//! implementation.
-use crate::error::{map_binder_status, Error, ErrorCode};
+use crate::error::{map_binder_status, map_km_error, Error, ErrorCode};
+use crate::key_parameter::KeyParameter;
use crate::permission;
use crate::permission::{KeyPerm, KeyPermSet, KeystorePerm};
use crate::{
@@ -23,7 +24,8 @@
globals::LEGACY_IMPORTER,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- KeyCharacteristics::KeyCharacteristics, Tag::Tag,
+ IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics,
+ KeyParameter::KeyParameter as KmKeyParameter, Tag::Tag,
};
use android_os_permissions_aidl::aidl::android::os::IPermissionController;
use android_security_apc::aidl::android::security::apc::{
@@ -40,6 +42,8 @@
APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
APC_COMPAT_ERROR_SYSTEM_ERROR,
};
+use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt, ZVec};
+use std::iter::IntoIterator;
/// This function uses its namesake in the permission module and in
/// combination with with_calling_sid from the binder crate to check
@@ -135,18 +139,60 @@
/// representation of the keystore service.
pub fn key_characteristics_to_internal(
key_characteristics: Vec<KeyCharacteristics>,
-) -> Vec<crate::key_parameter::KeyParameter> {
+) -> Vec<KeyParameter> {
key_characteristics
.into_iter()
.flat_map(|aidl_key_char| {
let sec_level = aidl_key_char.securityLevel;
- aidl_key_char.authorizations.into_iter().map(move |aidl_kp| {
- crate::key_parameter::KeyParameter::new(aidl_kp.into(), sec_level)
- })
+ aidl_key_char
+ .authorizations
+ .into_iter()
+ .map(move |aidl_kp| KeyParameter::new(aidl_kp.into(), sec_level))
})
.collect()
}
+/// This function can be used to upgrade key blobs on demand. The return value of
+/// `km_op` is inspected and if ErrorCode::KEY_REQUIRES_UPGRADE is encountered,
+/// an attempt is made to upgrade the key blob. On success `new_blob_handler` is called
+/// with the upgraded blob as argument. Then `km_op` is called a second time with the
+/// upgraded blob as argument. On success a tuple of the `km_op`s result and the
+/// optional upgraded blob is returned.
+pub fn upgrade_keyblob_if_required_with<T, KmOp, NewBlobHandler>(
+ km_dev: &dyn IKeyMintDevice,
+ key_blob: &[u8],
+ upgrade_params: &[KmKeyParameter],
+ km_op: KmOp,
+ new_blob_handler: NewBlobHandler,
+) -> Result<(T, Option<Vec<u8>>)>
+where
+ KmOp: Fn(&[u8]) -> Result<T, Error>,
+ NewBlobHandler: FnOnce(&[u8]) -> Result<()>,
+{
+ match km_op(key_blob) {
+ Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
+ let upgraded_blob = {
+ let _wp = watchdog::watch_millis(
+ "In utils::upgrade_keyblob_if_required_with: calling upgradeKey.",
+ 500,
+ );
+ map_km_error(km_dev.upgradeKey(key_blob, upgrade_params))
+ }
+ .context("In utils::upgrade_keyblob_if_required_with: Upgrade failed.")?;
+
+ new_blob_handler(&upgraded_blob)
+ .context("In utils::upgrade_keyblob_if_required_with: calling new_blob_handler.")?;
+
+ km_op(&upgraded_blob)
+ .map(|v| (v, Some(upgraded_blob)))
+ .context("In utils::upgrade_keyblob_if_required_with: Calling km_op after upgrade.")
+ }
+ r => r
+ .map(|v| (v, None))
+ .context("In utils::upgrade_keyblob_if_required_with: Calling km_op."),
+ }
+}
+
/// Converts a set of key characteristics from the internal representation into a set of
/// Authorizations as they are used to convey key characteristics to the clients of keystore.
pub fn key_parameters_to_authorizations(
@@ -255,6 +301,36 @@
}
}
+/// Trait implemented by objects that can be used to decrypt cipher text using AES-GCM.
+pub trait AesGcm {
+ /// Deciphers `data` using the initialization vector `iv` and AEAD tag `tag`
+ /// and AES-GCM. The implementation provides the key material and selects
+ /// the implementation variant, e.g., AES128 or AES265.
+ fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec>;
+
+ /// Encrypts `data` and returns the ciphertext, the initialization vector `iv`
+ /// and AEAD tag `tag`. The implementation provides the key material and selects
+ /// the implementation variant, e.g., AES128 or AES265.
+ fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)>;
+}
+
+/// Marks an object as AES-GCM key.
+pub trait AesGcmKey {
+ /// Provides access to the raw key material.
+ fn key(&self) -> &[u8];
+}
+
+impl<T: AesGcmKey> AesGcm for T {
+ fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> {
+ aes_gcm_decrypt(data, iv, tag, self.key())
+ .context("In AesGcm<T>::decrypt: Decryption failed")
+ }
+
+ fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
+ aes_gcm_encrypt(plaintext, self.key()).context("In AesGcm<T>::encrypt: Encryption failed.")
+ }
+}
+
/// This module provides empty/noop implementations of the watch dog utility functions.
#[cfg(not(feature = "watchdog"))]
pub mod watchdog {