Merge changes from topic "keystore2-metrics"
* changes:
Add support for keystore2 storage metrics
Update PropertyWatcher to wait for property create
diff --git a/keystore2/aaid/Android.bp b/keystore2/aaid/Android.bp
index d27fdf6..c04ce51 100644
--- a/keystore2/aaid/Android.bp
+++ b/keystore2/aaid/Android.bp
@@ -39,8 +39,8 @@
bindgen_flags: [
"--size_t-is-usize",
- "--whitelist-function=aaid_keystore_attestation_id",
- "--whitelist-var=KEY_ATTESTATION_APPLICATION_ID_MAX_SIZE",
+ "--allowlist-function=aaid_keystore_attestation_id",
+ "--allowlist-var=KEY_ATTESTATION_APPLICATION_ID_MAX_SIZE",
],
}
diff --git a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
index 21ddd9b..5f91e79 100644
--- a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
+++ b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl
@@ -15,6 +15,7 @@
package android.security.maintenance;
import android.system.keystore2.Domain;
+import android.system.keystore2.KeyDescriptor;
import android.security.maintenance.UserState;
/**
@@ -107,4 +108,19 @@
* `ResponseCode::SYSTEM_ERROR` - if an unexpected error occurred.
*/
void onDeviceOffBody();
+
+ /**
+ * Migrate a key from one namespace to another. The caller must have use, grant, and delete
+ * permissions on the source namespace and rebind permissions on the destination namespace.
+ * The source may be specified by Domain::APP, Domain::SELINUX, or Domain::KEY_ID. The target
+ * may be specified by Domain::APP or Domain::SELINUX.
+ *
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - If the caller lacks any of the required permissions.
+ * `ResponseCode::KEY_NOT_FOUND` - If the source did not exist.
+ * `ResponseCode::INVALID_ARGUMENT` - If the target exists or if any of the above mentioned
+ * requirements for the domain parameter are not met.
+ * `ResponseCode::SYSTEM_ERROR` - An unexpected system error occurred.
+ */
+ void migrateKeyNamespace(in KeyDescriptor source, in KeyDescriptor destination);
}
diff --git a/keystore2/apc_compat/Android.bp b/keystore2/apc_compat/Android.bp
index 9519c8e..bf21675 100644
--- a/keystore2/apc_compat/Android.bp
+++ b/keystore2/apc_compat/Android.bp
@@ -41,12 +41,12 @@
source_stem: "bindings",
bindgen_flags: [
- "--whitelist-function=tryGetUserConfirmationService",
- "--whitelist-function=promptUserConfirmation",
- "--whitelist-function=abortUserConfirmation",
- "--whitelist-function=closeUserConfirmationService",
- "--whitelist-var=INVALID_SERVICE_HANDLE",
- "--whitelist-var=APC_COMPAT_.*",
+ "--allowlist-function=tryGetUserConfirmationService",
+ "--allowlist-function=promptUserConfirmation",
+ "--allowlist-function=abortUserConfirmation",
+ "--allowlist-function=closeUserConfirmationService",
+ "--allowlist-var=INVALID_SERVICE_HANDLE",
+ "--allowlist-var=APC_COMPAT_.*",
],
}
diff --git a/keystore2/src/boot_level_keys.rs b/keystore2/src/boot_level_keys.rs
new file mode 100644
index 0000000..3d33a26
--- /dev/null
+++ b/keystore2/src/boot_level_keys.rs
@@ -0,0 +1,415 @@
+// Copyright 2021, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Offer keys based on the "boot level" for superencryption.
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, BeginResult::BeginResult, Digest::Digest, ErrorCode::ErrorCode,
+ IKeyMintDevice::IKeyMintDevice, IKeyMintOperation::IKeyMintOperation,
+ KeyParameter::KeyParameter, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
+};
+use anyhow::{Context, Result};
+use binder::Strong;
+use keystore2_crypto::{hkdf_expand, ZVec, AES_256_KEY_LENGTH};
+use std::{collections::VecDeque, convert::TryFrom};
+
+use crate::{
+ database::{
+ BlobMetaData, BlobMetaEntry, CertificateInfo, DateTime, KeyEntry, KeyEntryLoadBits,
+ KeyIdGuard, KeyMetaData, KeyMetaEntry, KeyType, KeystoreDB, SubComponentType, Uuid,
+ },
+ error::{map_km_error, Error},
+ globals::get_keymint_device,
+ key_parameter::KeyParameterValue,
+ super_key::KeyBlob,
+ utils::{key_characteristics_to_internal, Asp, AID_KEYSTORE},
+};
+
+/// Wrapper for operating directly on a KeyMint device.
+/// These methods often mirror methods in [`crate::security_level`]. However
+/// the functions in [`crate::security_level`] make assumptions that hold, and has side effects
+/// that make sense, only if called by an external client through binder.
+/// In addition we are trying to maintain a separation between interface services
+/// so that the architecture is compatible with a future move to multiple thread pools.
+/// So the simplest approach today is to write new implementations of them for internal use.
+/// Because these methods run very early, we don't even try to cooperate with
+/// the operation slot database; we assume there will be plenty of slots.
+struct KeyMintDevice {
+ asp: Asp,
+ km_uuid: Uuid,
+}
+
+impl KeyMintDevice {
+ fn get(security_level: SecurityLevel) -> Result<KeyMintDevice> {
+ let (asp, _hw_info, km_uuid) = get_keymint_device(&security_level)
+ .context("In KeyMintDevice::get: get_keymint_device failed")?;
+ Ok(KeyMintDevice { asp, km_uuid })
+ }
+
+ /// Generate a KM key and store in the database.
+ fn generate_and_store_key(
+ &self,
+ db: &mut KeystoreDB,
+ key_desc: &KeyDescriptor,
+ params: &[KeyParameter],
+ ) -> Result<()> {
+ let km_dev: Strong<dyn IKeyMintDevice> = self
+ .asp
+ .get_interface()
+ .context("In generate_and_store_key: Failed to get KeyMint device")?;
+ let creation_result = map_km_error(km_dev.generateKey(params, None))
+ .context("In generate_and_store_key: generateKey failed")?;
+ let key_parameters = key_characteristics_to_internal(creation_result.keyCharacteristics);
+
+ let creation_date =
+ DateTime::now().context("In generate_and_store_key: DateTime::now() failed")?;
+
+ let mut key_metadata = KeyMetaData::new();
+ key_metadata.add(KeyMetaEntry::CreationDate(creation_date));
+ let mut blob_metadata = BlobMetaData::new();
+ blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
+
+ db.store_new_key(
+ &key_desc,
+ &key_parameters,
+ &(&creation_result.keyBlob, &blob_metadata),
+ &CertificateInfo::new(None, None),
+ &key_metadata,
+ &self.km_uuid,
+ )
+ .context("In generate_and_store_key: store_new_key failed")?;
+ Ok(())
+ }
+
+ /// This does the lookup and store in separate transactions; caller must
+ /// hold a lock before calling.
+ fn lookup_or_generate_key(
+ &self,
+ db: &mut KeystoreDB,
+ key_desc: &KeyDescriptor,
+ params: &[KeyParameter],
+ ) -> Result<(KeyIdGuard, KeyEntry)> {
+ // We use a separate transaction for the lookup than for the store
+ // - to keep the code simple
+ // - because the caller needs to hold a lock in any case
+ // - because it avoids holding database locks during slow
+ // KeyMint operations
+ let lookup = db.load_key_entry(
+ &key_desc,
+ KeyType::Client,
+ KeyEntryLoadBits::KM,
+ AID_KEYSTORE,
+ |_, _| Ok(()),
+ );
+ match lookup {
+ Ok(result) => return Ok(result),
+ Err(e) => match e.root_cause().downcast_ref::<Error>() {
+ Some(&Error::Rc(ResponseCode::KEY_NOT_FOUND)) => {}
+ _ => return Err(e),
+ },
+ }
+ self.generate_and_store_key(db, &key_desc, ¶ms)
+ .context("In lookup_or_generate_key: generate_and_store_key failed")?;
+ db.load_key_entry(&key_desc, KeyType::Client, KeyEntryLoadBits::KM, AID_KEYSTORE, |_, _| {
+ Ok(())
+ })
+ .context("In lookup_or_generate_key: load_key_entry failed")
+ }
+
+ /// Call the passed closure; if it returns `KEY_REQUIRES_UPGRADE`, call upgradeKey, and
+ /// write the upgraded key to the database.
+ fn upgrade_keyblob_if_required_with<T, F>(
+ &self,
+ db: &mut KeystoreDB,
+ km_dev: &Strong<dyn IKeyMintDevice>,
+ key_id_guard: KeyIdGuard,
+ key_blob: &KeyBlob,
+ f: F,
+ ) -> Result<T>
+ where
+ F: Fn(&[u8]) -> Result<T, Error>,
+ {
+ match f(key_blob) {
+ Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
+ let upgraded_blob = map_km_error(km_dev.upgradeKey(key_blob, &[]))
+ .context("In upgrade_keyblob_if_required_with: Upgrade failed")?;
+
+ let mut new_blob_metadata = BlobMetaData::new();
+ new_blob_metadata.add(BlobMetaEntry::KmUuid(self.km_uuid));
+
+ db.set_blob(
+ &key_id_guard,
+ SubComponentType::KEY_BLOB,
+ Some(&upgraded_blob),
+ Some(&new_blob_metadata),
+ )
+ .context(concat!(
+ "In upgrade_keyblob_if_required_with: ",
+ "Failed to insert upgraded blob into the database"
+ ))?;
+
+ Ok(f(&upgraded_blob).context(concat!(
+ "In upgrade_keyblob_if_required_with: ",
+ "Closure failed after upgrade"
+ ))?)
+ }
+ result => Ok(result.context("In upgrade_keyblob_if_required_with: Closure failed")?),
+ }
+ }
+
+ /// Use the created key in an operation that can be done with
+ /// a call to begin followed by a call to finish.
+ fn use_key_in_one_step(
+ &self,
+ db: &mut KeystoreDB,
+ key_id_guard: KeyIdGuard,
+ key_entry: &KeyEntry,
+ purpose: KeyPurpose,
+ operation_parameters: &[KeyParameter],
+ input: &[u8],
+ ) -> Result<Vec<u8>> {
+ let km_dev: Strong<dyn IKeyMintDevice> = self
+ .asp
+ .get_interface()
+ .context("In use_key_in_one_step: Failed to get KeyMint device")?;
+
+ let (key_blob, _blob_metadata) = key_entry
+ .key_blob_info()
+ .as_ref()
+ .ok_or_else(Error::sys)
+ .context("use_key_in_one_step: Keyblob missing")?;
+ let key_blob = KeyBlob::Ref(&key_blob);
+
+ let begin_result: BeginResult = self
+ .upgrade_keyblob_if_required_with(db, &km_dev, key_id_guard, &key_blob, |blob| {
+ map_km_error(km_dev.begin(purpose, blob, operation_parameters, &Default::default()))
+ })
+ .context("In use_key_in_one_step: Failed to begin operation.")?;
+ let operation: Strong<dyn IKeyMintOperation> = begin_result
+ .operation
+ .ok_or_else(Error::sys)
+ .context("In use_key_in_one_step: Operation missing")?;
+ map_km_error(operation.finish(Some(input), None, None, None, None))
+ .context("In use_key_in_one_step: Failed to finish operation.")
+ }
+}
+
+/// This is not thread safe; caller must hold a lock before calling.
+/// In practice the caller is SuperKeyManager and the lock is the
+/// Mutex on its internal state.
+pub fn get_level_zero_key(db: &mut KeystoreDB) -> Result<ZVec> {
+ let key_desc = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: AID_KEYSTORE as i64,
+ alias: Some("boot_level_key".to_string()),
+ blob: None,
+ };
+ let params = [
+ KeyParameterValue::Algorithm(Algorithm::HMAC).into(),
+ KeyParameterValue::Digest(Digest::SHA_2_256).into(),
+ KeyParameterValue::KeySize(256).into(),
+ KeyParameterValue::MinMacLength(256).into(),
+ KeyParameterValue::KeyPurpose(KeyPurpose::SIGN).into(),
+ KeyParameterValue::MaxUsesPerBoot(1).into(),
+ ];
+ // We use TRUSTED_ENVIRONMENT here because it is the authority on when
+ // the device has rebooted.
+ let km_dev: KeyMintDevice = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
+ .context("In get_level_zero_key: KeyMintDevice::get failed")?;
+ let (key_id_guard, key_entry) = km_dev
+ .lookup_or_generate_key(db, &key_desc, ¶ms)
+ .context("In get_level_zero_key: lookup_or_generate_key failed")?;
+
+ let params = [KeyParameterValue::MacLength(256).into()];
+ let level_zero_key = km_dev
+ .use_key_in_one_step(
+ db,
+ key_id_guard,
+ &key_entry,
+ KeyPurpose::SIGN,
+ ¶ms,
+ b"Create boot level key",
+ )
+ .context("In get_level_zero_key: use_key_in_one_step failed")?;
+ // TODO: this is rather unsatisfactory, we need a better way to handle
+ // sensitive binder returns.
+ let level_zero_key = ZVec::try_from(level_zero_key)
+ .context("In get_level_zero_key: conversion to ZVec failed")?;
+ Ok(level_zero_key)
+}
+
+/// Holds the key for the current boot level, and a cache of future keys generated as required.
+/// When the boot level advances, keys prior to the current boot level are securely dropped.
+pub struct BootLevelKeyCache {
+ /// Least boot level currently accessible, if any is.
+ current: usize,
+ /// Invariant: cache entry *i*, if it exists, holds the HKDF key for boot level
+ /// *i* + `current`. If the cache is non-empty it can be grown forwards, but it cannot be
+ /// grown backwards, so keys below `current` are inaccessible.
+ /// `cache.clear()` makes all keys inaccessible.
+ cache: VecDeque<ZVec>,
+}
+
+impl BootLevelKeyCache {
+ const HKDF_ADVANCE: &'static [u8] = b"Advance KDF one step";
+ const HKDF_AES: &'static [u8] = b"Generate AES-256-GCM key";
+ const HKDF_KEY_SIZE: usize = 32;
+
+ /// Initialize the cache with the level zero key.
+ pub fn new(level_zero_key: ZVec) -> Self {
+ let mut cache: VecDeque<ZVec> = VecDeque::new();
+ cache.push_back(level_zero_key);
+ Self { current: 0, cache }
+ }
+
+ /// Report whether the key for the given level can be inferred.
+ pub fn level_accessible(&self, boot_level: usize) -> bool {
+ // If the requested boot level is lower than the current boot level
+ // or if we have reached the end (`cache.empty()`) we can't retrieve
+ // the boot key.
+ boot_level >= self.current && !self.cache.is_empty()
+ }
+
+ /// Get the HKDF key for boot level `boot_level`. The key for level *i*+1
+ /// is calculated from the level *i* key using `hkdf_expand`.
+ fn get_hkdf_key(&mut self, boot_level: usize) -> Result<Option<&ZVec>> {
+ if !self.level_accessible(boot_level) {
+ return Ok(None);
+ }
+ // `self.cache.len()` represents the first entry not in the cache,
+ // so `self.current + self.cache.len()` is the first boot level not in the cache.
+ let first_not_cached = self.current + self.cache.len();
+
+ // Grow the cache forwards until it contains the desired boot level.
+ for _level in first_not_cached..=boot_level {
+ // We check at the start that cache is non-empty and future iterations only push,
+ // so this must unwrap.
+ let highest_key = self.cache.back().unwrap();
+ let next_key = hkdf_expand(Self::HKDF_KEY_SIZE, highest_key, Self::HKDF_ADVANCE)
+ .context("In BootLevelKeyCache::get_hkdf_key: Advancing key one step")?;
+ self.cache.push_back(next_key);
+ }
+
+ // If we reach this point, we should have a key at index boot_level - current.
+ Ok(Some(self.cache.get(boot_level - self.current).unwrap()))
+ }
+
+ /// Drop keys prior to the given boot level, while retaining the ability to generate keys for
+ /// that level and later.
+ pub fn advance_boot_level(&mut self, new_boot_level: usize) -> Result<()> {
+ if !self.level_accessible(new_boot_level) {
+ log::error!(
+ concat!(
+ "In BootLevelKeyCache::advance_boot_level: ",
+ "Failed to advance boot level to {}, current is {}, cache size {}"
+ ),
+ new_boot_level,
+ self.current,
+ self.cache.len()
+ );
+ return Ok(());
+ }
+
+ // We `get` the new boot level for the side effect of advancing the cache to a point
+ // where the new boot level is present.
+ self.get_hkdf_key(new_boot_level)
+ .context("In BootLevelKeyCache::advance_boot_level: Advancing cache")?;
+
+ // Then we split the queue at the index of the new boot level and discard the front,
+ // keeping only the keys with the current boot level or higher.
+ self.cache = self.cache.split_off(new_boot_level - self.current);
+
+ // The new cache has the new boot level at index 0, so we set `current` to
+ // `new_boot_level`.
+ self.current = new_boot_level;
+
+ Ok(())
+ }
+
+ /// Drop all keys, effectively raising the current boot level to infinity; no keys can
+ /// be inferred from this point on.
+ pub fn finish(&mut self) {
+ self.cache.clear();
+ }
+
+ fn expand_key(
+ &mut self,
+ boot_level: usize,
+ out_len: usize,
+ info: &[u8],
+ ) -> Result<Option<ZVec>> {
+ self.get_hkdf_key(boot_level)
+ .context("In BootLevelKeyCache::expand_key: Looking up HKDF key")?
+ .map(|k| hkdf_expand(out_len, k, info))
+ .transpose()
+ .context("In BootLevelKeyCache::expand_key: Calling hkdf_expand")
+ }
+
+ /// Return the AES-256-GCM key for the current boot level.
+ pub fn aes_key(&mut self, boot_level: usize) -> Result<Option<ZVec>> {
+ self.expand_key(boot_level, AES_256_KEY_LENGTH, BootLevelKeyCache::HKDF_AES)
+ .context("In BootLevelKeyCache::aes_key: expand_key failed")
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_output_is_consistent() -> Result<()> {
+ let initial_key = b"initial key";
+ let mut blkc = BootLevelKeyCache::new(ZVec::try_from(initial_key as &[u8])?);
+ assert_eq!(true, blkc.level_accessible(0));
+ assert_eq!(true, blkc.level_accessible(9));
+ assert_eq!(true, blkc.level_accessible(10));
+ assert_eq!(true, blkc.level_accessible(100));
+ let v0 = blkc.aes_key(0).unwrap().unwrap();
+ let v10 = blkc.aes_key(10).unwrap().unwrap();
+ assert_eq!(Some(&v0), blkc.aes_key(0)?.as_ref());
+ assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref());
+ blkc.advance_boot_level(5)?;
+ assert_eq!(false, blkc.level_accessible(0));
+ assert_eq!(true, blkc.level_accessible(9));
+ assert_eq!(true, blkc.level_accessible(10));
+ assert_eq!(true, blkc.level_accessible(100));
+ assert_eq!(None, blkc.aes_key(0)?);
+ assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref());
+ blkc.advance_boot_level(10)?;
+ assert_eq!(false, blkc.level_accessible(0));
+ assert_eq!(false, blkc.level_accessible(9));
+ assert_eq!(true, blkc.level_accessible(10));
+ assert_eq!(true, blkc.level_accessible(100));
+ assert_eq!(None, blkc.aes_key(0)?);
+ assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref());
+ blkc.advance_boot_level(0)?;
+ assert_eq!(false, blkc.level_accessible(0));
+ assert_eq!(false, blkc.level_accessible(9));
+ assert_eq!(true, blkc.level_accessible(10));
+ assert_eq!(true, blkc.level_accessible(100));
+ assert_eq!(None, blkc.aes_key(0)?);
+ assert_eq!(Some(v10), blkc.aes_key(10)?);
+ blkc.finish();
+ assert_eq!(false, blkc.level_accessible(0));
+ assert_eq!(false, blkc.level_accessible(9));
+ assert_eq!(false, blkc.level_accessible(10));
+ assert_eq!(false, blkc.level_accessible(100));
+ assert_eq!(None, blkc.aes_key(0)?);
+ assert_eq!(None, blkc.aes_key(10)?);
+ Ok(())
+ }
+}
diff --git a/keystore2/src/crypto/Android.bp b/keystore2/src/crypto/Android.bp
index 21c9b74..3ba47cd 100644
--- a/keystore2/src/crypto/Android.bp
+++ b/keystore2/src/crypto/Android.bp
@@ -59,27 +59,27 @@
shared_libs: ["libcrypto"],
bindgen_flags: [
"--size_t-is-usize",
- "--whitelist-function", "randomBytes",
- "--whitelist-function", "AES_gcm_encrypt",
- "--whitelist-function", "AES_gcm_decrypt",
- "--whitelist-function", "CreateKeyId",
- "--whitelist-function", "generateKeyFromPassword",
- "--whitelist-function", "HKDFExtract",
- "--whitelist-function", "HKDFExpand",
- "--whitelist-function", "ECDHComputeKey",
- "--whitelist-function", "ECKEYGenerateKey",
- "--whitelist-function", "ECKEYMarshalPrivateKey",
- "--whitelist-function", "ECKEYParsePrivateKey",
- "--whitelist-function", "EC_KEY_get0_public_key",
- "--whitelist-function", "ECPOINTPoint2Oct",
- "--whitelist-function", "ECPOINTOct2Point",
- "--whitelist-function", "EC_KEY_free",
- "--whitelist-function", "EC_POINT_free",
- "--whitelist-function", "extractSubjectFromCertificate",
- "--whitelist-type", "EC_KEY",
- "--whitelist-type", "EC_POINT",
- "--whitelist-var", "EC_MAX_BYTES",
- "--whitelist-var", "EVP_MAX_MD_SIZE",
+ "--allowlist-function", "randomBytes",
+ "--allowlist-function", "AES_gcm_encrypt",
+ "--allowlist-function", "AES_gcm_decrypt",
+ "--allowlist-function", "CreateKeyId",
+ "--allowlist-function", "generateKeyFromPassword",
+ "--allowlist-function", "HKDFExtract",
+ "--allowlist-function", "HKDFExpand",
+ "--allowlist-function", "ECDHComputeKey",
+ "--allowlist-function", "ECKEYGenerateKey",
+ "--allowlist-function", "ECKEYMarshalPrivateKey",
+ "--allowlist-function", "ECKEYParsePrivateKey",
+ "--allowlist-function", "EC_KEY_get0_public_key",
+ "--allowlist-function", "ECPOINTPoint2Oct",
+ "--allowlist-function", "ECPOINTOct2Point",
+ "--allowlist-function", "EC_KEY_free",
+ "--allowlist-function", "EC_POINT_free",
+ "--allowlist-function", "extractSubjectFromCertificate",
+ "--allowlist-type", "EC_KEY",
+ "--allowlist-type", "EC_POINT",
+ "--allowlist-var", "EC_MAX_BYTES",
+ "--allowlist-var", "EVP_MAX_MD_SIZE",
],
cflags: ["-DBORINGSSL_NO_CXX"],
}
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index f645069..32e2c98 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -191,6 +191,9 @@
KmUuid(Uuid) with accessor km_uuid,
/// If the key is ECDH encrypted, this is the ephemeral public key
PublicKey(Vec<u8>) with accessor public_key,
+ /// If the key is encrypted with a MaxBootLevel key, this is the boot level
+ /// of that key
+ MaxBootLevel(i32) with accessor max_boot_level,
// --- ADD NEW META DATA FIELDS HERE ---
// For backwards compatibility add new entries only to
// end of this list and above this comment.
@@ -2131,6 +2134,69 @@
Ok(updated != 0)
}
+ /// Moves the key given by KeyIdGuard to the new location at `destination`. If the destination
+ /// is already occupied by a key, this function fails with `ResponseCode::INVALID_ARGUMENT`.
+ pub fn migrate_key_namespace(
+ &mut self,
+ key_id_guard: KeyIdGuard,
+ destination: &KeyDescriptor,
+ caller_uid: u32,
+ check_permission: impl Fn(&KeyDescriptor) -> Result<()>,
+ ) -> Result<()> {
+ let destination = match destination.domain {
+ Domain::APP => KeyDescriptor { nspace: caller_uid as i64, ..(*destination).clone() },
+ Domain::SELINUX => (*destination).clone(),
+ domain => {
+ return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context(format!("Domain {:?} must be either APP or SELINUX.", domain));
+ }
+ };
+
+ // Security critical: Must return immediately on failure. Do not remove the '?';
+ check_permission(&destination)
+ .context("In migrate_key_namespace: Trying to check permission.")?;
+
+ let alias = destination
+ .alias
+ .as_ref()
+ .ok_or(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context("In migrate_key_namespace: Alias must be specified.")?;
+
+ self.with_transaction(TransactionBehavior::Immediate, |tx| {
+ // Query the destination location. If there is a key, the migration request fails.
+ if tx
+ .query_row(
+ "SELECT id FROM persistent.keyentry
+ WHERE alias = ? AND domain = ? AND namespace = ?;",
+ params![alias, destination.domain.0, destination.nspace],
+ |_| Ok(()),
+ )
+ .optional()
+ .context("Failed to query destination.")?
+ .is_some()
+ {
+ return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context("Target already exists.");
+ }
+
+ let updated = tx
+ .execute(
+ "UPDATE persistent.keyentry
+ SET alias = ?, domain = ?, namespace = ?
+ WHERE id = ?;",
+ params![alias, destination.domain.0, destination.nspace, key_id_guard.id()],
+ )
+ .context("Failed to update key entry.")?;
+
+ if updated != 1 {
+ return Err(KsError::sys())
+ .context(format!("Update succeeded, but {} rows were updated.", updated));
+ }
+ Ok(()).no_gc()
+ })
+ .context("In migrate_key_namespace:")
+ }
+
/// Store a new key in a single transaction.
/// The function creates a new key entry, populates the blob, key parameter, and metadata
/// fields, and rebinds the given alias to the new key.
@@ -4202,6 +4268,181 @@
Ok(())
}
+ // Creates a key migrates it to a different location and then tries to access it by the old
+ // and new location.
+ #[test]
+ fn test_migrate_key_app_to_app() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let source_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(SOURCE_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let key_id = key_id_guard.id();
+
+ db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| {
+ Ok(())
+ })
+ .unwrap();
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &destination_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ DESTINATION_UID,
+ |k, av| {
+ assert_eq!(Domain::APP, k.domain);
+ assert_eq!(DESTINATION_UID as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &source_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ SOURCE_UID,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+ }
+
+ // Creates a key migrates it to a different location and then tries to access it by the old
+ // and new location.
+ #[test]
+ fn test_migrate_key_app_to_selinux() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ const DESTINATION_NAMESPACE: i64 = 1000i64;
+ static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let source_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(SOURCE_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::SELINUX,
+ nspace: DESTINATION_NAMESPACE,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ let key_id = key_id_guard.id();
+
+ db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| {
+ Ok(())
+ })
+ .unwrap();
+
+ let (_, key_entry) = db
+ .load_key_entry(
+ &destination_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::BOTH,
+ DESTINATION_UID,
+ |k, av| {
+ assert_eq!(Domain::SELINUX, k.domain);
+ assert_eq!(DESTINATION_NAMESPACE as i64, k.nspace);
+ assert!(av.is_none());
+ Ok(())
+ },
+ )
+ .unwrap();
+
+ assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None));
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)),
+ db.load_key_entry(
+ &source_descriptor,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ SOURCE_UID,
+ |_k, _av| Ok(()),
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+ }
+
+ // Creates two keys and tries to migrate the first to the location of the second which
+ // is expected to fail.
+ #[test]
+ fn test_migrate_key_destination_occupied() -> Result<()> {
+ let mut db = new_test_db()?;
+ const SOURCE_UID: u32 = 1u32;
+ const DESTINATION_UID: u32 = 2u32;
+ static SOURCE_ALIAS: &str = &"SOURCE_ALIAS";
+ static DESTINATION_ALIAS: &str = &"DESTINATION_ALIAS";
+ let key_id_guard =
+ make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+ make_test_key_entry(&mut db, Domain::APP, DESTINATION_UID as i64, DESTINATION_ALIAS, None)
+ .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?;
+
+ let destination_descriptor: KeyDescriptor = KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some(DESTINATION_ALIAS.to_string()),
+ blob: None,
+ };
+
+ assert_eq!(
+ Some(&KsError::Rc(ResponseCode::INVALID_ARGUMENT)),
+ db.migrate_key_namespace(
+ key_id_guard,
+ &destination_descriptor,
+ DESTINATION_UID,
+ |_k| Ok(())
+ )
+ .unwrap_err()
+ .root_cause()
+ .downcast_ref::<KsError>()
+ );
+
+ Ok(())
+ }
+
static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key";
#[test]
@@ -4279,7 +4520,7 @@
}
#[test]
- fn teset_database_busy_error_code() {
+ fn test_database_busy_error_code() {
let temp_dir =
TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir.");
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 3f003be..7993c88 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -14,11 +14,14 @@
//! This is the Keystore 2.0 Enforcements module.
// TODO: more description to follow.
-use crate::database::{AuthTokenEntry, MonotonicRawTime};
use crate::error::{map_binder_status, Error, ErrorCode};
use crate::globals::{get_timestamp_service, ASYNC_TASK, DB, ENFORCEMENTS};
use crate::key_parameter::{KeyParameter, KeyParameterValue};
use crate::{authorization::Error as AuthzError, super_key::SuperEncryptionType};
+use crate::{
+ database::{AuthTokenEntry, MonotonicRawTime},
+ globals::SUPER_KEY,
+};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, ErrorCode::ErrorCode as Ec, HardwareAuthToken::HardwareAuthToken,
HardwareAuthenticatorType::HardwareAuthenticatorType,
@@ -34,11 +37,9 @@
};
use android_system_keystore2::binder::Strong;
use anyhow::{Context, Result};
-use keystore2_system_property::PropertyWatcher;
use std::{
collections::{HashMap, HashSet},
sync::{
- atomic::{AtomicI32, Ordering},
mpsc::{channel, Receiver, Sender, TryRecvError},
Arc, Mutex, Weak,
},
@@ -369,8 +370,6 @@
/// The enforcement module will try to get a confirmation token from this channel whenever
/// an operation that requires confirmation finishes.
confirmation_token_receiver: Arc<Mutex<Option<Receiver<Vec<u8>>>>>,
- /// Highest boot level seen in keystore.boot_level; used to enforce MAX_BOOT_LEVEL tag.
- boot_level: AtomicI32,
}
impl Enforcements {
@@ -596,7 +595,7 @@
}
if let Some(level) = max_boot_level {
- if level < self.boot_level.load(Ordering::SeqCst) {
+ if !SUPER_KEY.level_accessible(level) {
return Err(Error::Km(Ec::BOOT_LEVEL_EXCEEDED))
.context("In authorize_create: boot level is too late.");
}
@@ -762,27 +761,35 @@
key_parameters: &[KeyParameter],
flags: Option<i32>,
) -> SuperEncryptionType {
- if *domain != Domain::APP {
- return SuperEncryptionType::None;
- }
if let Some(flags) = flags {
if (flags & KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING) != 0 {
return SuperEncryptionType::None;
}
}
- if key_parameters
- .iter()
- .any(|kp| matches!(kp.key_parameter_value(), KeyParameterValue::UnlockedDeviceRequired))
- {
- return SuperEncryptionType::ScreenLockBound;
+ // Each answer has a priority, numerically largest priority wins.
+ struct Candidate {
+ priority: u32,
+ enc_type: SuperEncryptionType,
}
- if key_parameters
- .iter()
- .any(|kp| matches!(kp.key_parameter_value(), KeyParameterValue::UserSecureID(_)))
- {
- return SuperEncryptionType::LskfBound;
+ let mut result = Candidate { priority: 0, enc_type: SuperEncryptionType::None };
+ for kp in key_parameters {
+ let t = match kp.key_parameter_value() {
+ KeyParameterValue::MaxBootLevel(level) => {
+ Candidate { priority: 3, enc_type: SuperEncryptionType::BootLevel(*level) }
+ }
+ KeyParameterValue::UnlockedDeviceRequired if *domain == Domain::APP => {
+ Candidate { priority: 2, enc_type: SuperEncryptionType::ScreenLockBound }
+ }
+ KeyParameterValue::UserSecureID(_) if *domain == Domain::APP => {
+ Candidate { priority: 1, enc_type: SuperEncryptionType::LskfBound }
+ }
+ _ => Candidate { priority: 0, enc_type: SuperEncryptionType::None },
+ };
+ if t.priority > result.priority {
+ result = t;
+ }
}
- SuperEncryptionType::None
+ result.enc_type
}
/// Finds a matching auth token along with a timestamp token.
@@ -844,35 +851,6 @@
.context("In get_auth_tokens. Error in getting timestamp token.")?;
Ok((auth_token, tst))
}
-
- /// Watch the `keystore.boot_level` system property, and keep self.boot_level up to date.
- /// Blocks waiting for system property changes, so must be run in its own thread.
- pub fn watch_boot_level(&self) -> Result<()> {
- let mut w = PropertyWatcher::new("keystore.boot_level")?;
- loop {
- fn parse_value(_name: &str, value: &str) -> Result<Option<i32>> {
- Ok(if value == "end" { None } else { Some(value.parse::<i32>()?) })
- }
- match w.read(parse_value)? {
- Some(level) => {
- let old = self.boot_level.fetch_max(level, Ordering::SeqCst);
- log::info!(
- "Read keystore.boot_level: {}; boot level {} -> {}",
- level,
- old,
- std::cmp::max(old, level)
- );
- }
- None => {
- log::info!("keystore.boot_level is `end`, finishing.");
- self.boot_level.fetch_max(i32::MAX, Ordering::SeqCst);
- break;
- }
- }
- w.wait()?;
- }
- Ok(())
- }
}
// TODO: Add tests to enforcement module (b/175578618).
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index fd8a492..4d4a718 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -71,13 +71,6 @@
ENFORCEMENTS.install_confirmation_token_receiver(confirmation_token_receiver);
- info!("Starting boot level watcher.");
- std::thread::spawn(|| {
- keystore2::globals::ENFORCEMENTS
- .watch_boot_level()
- .unwrap_or_else(|e| error!("watch_boot_level failed: {}", e));
- });
-
entropy::register_feeder();
shared_secret_negotiation::perform_shared_secret_negotiation();
diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs
index a3e440b..e631356 100644
--- a/keystore2/src/legacy_blob.rs
+++ b/keystore2/src/legacy_blob.rs
@@ -686,10 +686,18 @@
let user_id = uid_to_android_user(uid);
path.push(format!("user_{}", user_id));
let uid_str = uid.to_string();
- let dir =
- Self::with_retry_interrupted(|| fs::read_dir(path.as_path())).with_context(|| {
- format!("In list_vpn_profiles: Failed to open legacy blob database. {:?}", path)
- })?;
+ let dir = match Self::with_retry_interrupted(|| fs::read_dir(path.as_path())) {
+ Ok(dir) => dir,
+ Err(e) => match e.kind() {
+ ErrorKind::NotFound => return Ok(Default::default()),
+ _ => {
+ return Err(e).context(format!(
+ "In list_vpn_profiles: Failed to open legacy blob database. {:?}",
+ path
+ ))
+ }
+ },
+ };
let mut result: Vec<String> = Vec::new();
for entry in dir {
let file_name =
@@ -1370,4 +1378,14 @@
Ok(())
}
+
+ #[test]
+ fn list_vpn_profiles_on_non_existing_user() -> Result<()> {
+ let temp_dir = TempDir::new("list_vpn_profiles_on_non_existing_user")?;
+ let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path());
+
+ assert!(legacy_blob_loader.list_vpn_profiles(20)?.is_empty());
+
+ Ok(())
+ }
}
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 154b5b3..f851d3a 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -18,6 +18,7 @@
pub mod apc;
pub mod async_task;
pub mod authorization;
+pub mod boot_level_keys;
pub mod database;
pub mod ec_crypto;
pub mod enforcements;
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index e059a0b..1691f9d 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -14,14 +14,15 @@
//! This module implements IKeystoreMaintenance AIDL interface.
+use crate::database::{KeyEntryLoadBits, KeyType, MonotonicRawTime};
use crate::error::map_km_error;
-use crate::error::Error as KeystoreError;
+use crate::error::map_or_log_err;
+use crate::error::Error;
use crate::globals::get_keymint_device;
use crate::globals::{DB, LEGACY_MIGRATOR, SUPER_KEY};
-use crate::permission::KeystorePerm;
+use crate::permission::{KeyPerm, KeystorePerm};
use crate::super_key::UserState;
-use crate::utils::check_keystore_permission;
-use crate::{database::MonotonicRawTime, error::map_or_log_err};
+use crate::utils::{check_key_permission, check_keystore_permission};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::IKeyMintDevice::IKeyMintDevice;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
use android_security_maintenance::aidl::android::security::maintenance::{
@@ -29,10 +30,12 @@
UserState::UserState as AidlUserState,
};
use android_security_maintenance::binder::{Interface, Result as BinderResult};
-use android_system_keystore2::aidl::android::system::keystore2::Domain::Domain;
use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, KeyDescriptor::KeyDescriptor,
+};
use anyhow::{Context, Result};
-use binder::{IBinderInternal, Strong};
+use binder::{IBinderInternal, Strong, ThreadState};
use keystore2_crypto::Password;
/// This struct is defined to implement the aforementioned AIDL interface.
@@ -74,7 +77,7 @@
{
UserState::LskfLocked => {
// Error - password can not be changed when the device is locked
- Err(KeystoreError::Rc(ResponseCode::LOCKED))
+ Err(Error::Rc(ResponseCode::LOCKED))
.context("In on_user_password_changed. Device is locked.")
}
_ => {
@@ -141,6 +144,11 @@
fn early_boot_ended() -> Result<()> {
check_keystore_permission(KeystorePerm::early_boot_ended())
.context("In early_boot_ended. Checking permission")?;
+ log::info!("In early_boot_ended.");
+
+ if let Err(e) = DB.with(|db| SUPER_KEY.set_up_boot_level_cache(&mut db.borrow_mut())) {
+ log::error!("SUPER_KEY.set_up_boot_level_cache failed:\n{:?}\n:(", e);
+ }
let sec_levels = [
(SecurityLevel::TRUSTED_ENVIRONMENT, "TRUSTED_ENVIRONMENT"),
@@ -166,6 +174,43 @@
DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now()))
.context("In on_device_off_body: Trying to update last off body time.")
}
+
+ fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
+ let caller_uid = ThreadState::get_calling_uid();
+
+ DB.with(|db| {
+ let key_id_guard = match source.domain {
+ Domain::APP | Domain::SELINUX | Domain::KEY_ID => {
+ let (key_id_guard, _) = LEGACY_MIGRATOR
+ .with_try_migrate(&source, caller_uid, || {
+ db.borrow_mut().load_key_entry(
+ &source,
+ KeyType::Client,
+ KeyEntryLoadBits::NONE,
+ caller_uid,
+ |k, av| {
+ check_key_permission(KeyPerm::use_(), k, &av)?;
+ check_key_permission(KeyPerm::delete(), k, &av)?;
+ check_key_permission(KeyPerm::grant(), k, &av)
+ },
+ )
+ })
+ .context("In migrate_key_namespace: Failed to load key blob.")?;
+ key_id_guard
+ }
+ _ => {
+ return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT)).context(concat!(
+ "In migrate_key_namespace: ",
+ "Source domain must be one of APP, SELINUX, or KEY_ID."
+ ))
+ }
+ };
+
+ db.borrow_mut().migrate_key_namespace(key_id_guard, destination, caller_uid, |k| {
+ check_key_permission(KeyPerm::rebind(), k, &None)
+ })
+ })
+ }
}
impl Interface for Maintenance {}
@@ -198,4 +243,12 @@
fn onDeviceOffBody(&self) -> BinderResult<()> {
map_or_log_err(Self::on_device_off_body(), Ok)
}
+
+ fn migrateKeyNamespace(
+ &self,
+ source: &KeyDescriptor,
+ destination: &KeyDescriptor,
+ ) -> BinderResult<()> {
+ map_or_log_err(Self::migrate_key_namespace(source, destination), Ok)
+ }
}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index f574467..c654c02 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -707,7 +707,7 @@
SuperKeyManager::reencrypt_if_required(key_blob, &upgraded_blob)
.context("In store_upgraded_keyblob: Failed to handle super encryption.")?;
- let mut new_blob_metadata = new_blob_metadata.unwrap_or_else(BlobMetaData::new);
+ let mut new_blob_metadata = new_blob_metadata.unwrap_or_default();
if let Some(uuid) = km_uuid {
new_blob_metadata.add(BlobMetaEntry::KmUuid(*uuid));
}
diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs
index 3fa4cf0..b78560f 100644
--- a/keystore2/src/super_key.rs
+++ b/keystore2/src/super_key.rs
@@ -13,6 +13,7 @@
// limitations under the License.
use crate::{
+ boot_level_keys::{get_level_zero_key, BootLevelKeyCache},
database::BlobMetaData,
database::BlobMetaEntry,
database::EncryptedBy,
@@ -34,6 +35,7 @@
aes_gcm_decrypt, aes_gcm_encrypt, generate_aes256_key, generate_salt, Password, ZVec,
AES_256_KEY_LENGTH,
};
+use keystore2_system_property::PropertyWatcher;
use std::ops::Deref;
use std::{
collections::HashMap,
@@ -41,6 +43,8 @@
sync::{Mutex, Weak},
};
+const MAX_MAX_BOOT_LEVEL: usize = 1_000_000_000;
+
type UserId = u32;
/// Encryption algorithm used by a particular type of superencryption key
@@ -90,13 +94,47 @@
LskfBound,
/// Superencrypt with a key cleared from memory when the device is locked.
ScreenLockBound,
+ /// Superencrypt with a key based on the desired boot level
+ BootLevel(i32),
+}
+
+#[derive(Debug, Clone, Copy)]
+pub enum SuperKeyIdentifier {
+ /// id of the super key in the database.
+ DatabaseId(i64),
+ /// Boot level of the encrypting boot level key
+ BootLevel(i32),
+}
+
+impl SuperKeyIdentifier {
+ fn from_metadata(metadata: &BlobMetaData) -> Option<Self> {
+ if let Some(EncryptedBy::KeyId(key_id)) = metadata.encrypted_by() {
+ Some(SuperKeyIdentifier::DatabaseId(*key_id))
+ } else if let Some(boot_level) = metadata.max_boot_level() {
+ Some(SuperKeyIdentifier::BootLevel(*boot_level))
+ } else {
+ None
+ }
+ }
+
+ fn add_to_metadata(&self, metadata: &mut BlobMetaData) {
+ match self {
+ SuperKeyIdentifier::DatabaseId(id) => {
+ metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(*id)));
+ }
+ SuperKeyIdentifier::BootLevel(level) => {
+ metadata.add(BlobMetaEntry::MaxBootLevel(*level));
+ }
+ }
+ }
}
pub struct SuperKey {
algorithm: SuperEncryptionAlgorithm,
key: ZVec,
- // id of the super key in the database.
- id: i64,
+ /// Identifier of the encrypting key, used to write an encrypted blob
+ /// back to the database after re-encryption eg on a key update.
+ id: SuperKeyIdentifier,
/// ECDH is more expensive than AES. So on ECDH private keys we set the
/// reencrypt_with field to point at the corresponding AES key, and the
/// keys will be re-encrypted with AES on first use.
@@ -136,11 +174,20 @@
struct SkmState {
user_keys: HashMap<UserId, UserSuperKeys>,
key_index: HashMap<i64, Weak<SuperKey>>,
+ boot_level_key_cache: Option<BootLevelKeyCache>,
}
impl SkmState {
- fn add_key_to_key_index(&mut self, super_key: &Arc<SuperKey>) {
- self.key_index.insert(super_key.id, Arc::downgrade(super_key));
+ fn add_key_to_key_index(&mut self, super_key: &Arc<SuperKey>) -> Result<()> {
+ if let SuperKeyIdentifier::DatabaseId(id) = super_key.id {
+ self.key_index.insert(id, Arc::downgrade(super_key));
+ Ok(())
+ } else {
+ Err(Error::sys()).context(format!(
+ "In add_key_to_key_index: cannot add key with ID {:?}",
+ super_key.id
+ ))
+ }
}
}
@@ -150,19 +197,101 @@
}
impl SuperKeyManager {
+ pub fn set_up_boot_level_cache(self: &Arc<Self>, db: &mut KeystoreDB) -> Result<()> {
+ let mut data = self.data.lock().unwrap();
+ if data.boot_level_key_cache.is_some() {
+ log::info!("In set_up_boot_level_cache: called for a second time");
+ return Ok(());
+ }
+ let level_zero_key = get_level_zero_key(db)
+ .context("In set_up_boot_level_cache: get_level_zero_key failed")?;
+ data.boot_level_key_cache = Some(BootLevelKeyCache::new(level_zero_key));
+ log::info!("Starting boot level watcher.");
+ let clone = self.clone();
+ std::thread::spawn(move || {
+ clone
+ .watch_boot_level()
+ .unwrap_or_else(|e| log::error!("watch_boot_level failed:\n{:?}", e));
+ });
+ Ok(())
+ }
+
+ /// Watch the `keystore.boot_level` system property, and keep boot level up to date.
+ /// Blocks waiting for system property changes, so must be run in its own thread.
+ fn watch_boot_level(&self) -> Result<()> {
+ let mut w = PropertyWatcher::new("keystore.boot_level")
+ .context("In watch_boot_level: PropertyWatcher::new failed")?;
+ loop {
+ let level = w
+ .read(|_n, v| v.parse::<usize>().map_err(std::convert::Into::into))
+ .context("In watch_boot_level: read of property failed")?;
+ // watch_boot_level should only be called once data.boot_level_key_cache is Some,
+ // so it's safe to unwrap in the branches below.
+ if level < MAX_MAX_BOOT_LEVEL {
+ log::info!("Read keystore.boot_level value {}", level);
+ let mut data = self.data.lock().unwrap();
+ data.boot_level_key_cache
+ .as_mut()
+ .unwrap()
+ .advance_boot_level(level)
+ .context("In watch_boot_level: advance_boot_level failed")?;
+ } else {
+ log::info!(
+ "keystore.boot_level {} hits maximum {}, finishing.",
+ level,
+ MAX_MAX_BOOT_LEVEL
+ );
+ let mut data = self.data.lock().unwrap();
+ data.boot_level_key_cache.as_mut().unwrap().finish();
+ break;
+ }
+ w.wait().context("In watch_boot_level: property wait failed")?;
+ }
+ Ok(())
+ }
+
+ pub fn level_accessible(&self, boot_level: i32) -> bool {
+ self.data
+ .lock()
+ .unwrap()
+ .boot_level_key_cache
+ .as_ref()
+ .map_or(false, |c| c.level_accessible(boot_level as usize))
+ }
+
pub fn forget_all_keys_for_user(&self, user: UserId) {
let mut data = self.data.lock().unwrap();
data.user_keys.remove(&user);
}
- fn install_per_boot_key_for_user(&self, user: UserId, super_key: Arc<SuperKey>) {
+ fn install_per_boot_key_for_user(&self, user: UserId, super_key: Arc<SuperKey>) -> Result<()> {
let mut data = self.data.lock().unwrap();
- data.add_key_to_key_index(&super_key);
+ data.add_key_to_key_index(&super_key)
+ .context("In install_per_boot_key_for_user: add_key_to_key_index failed")?;
data.user_keys.entry(user).or_default().per_boot = Some(super_key);
+ Ok(())
}
- fn lookup_key(&self, key_id: &i64) -> Option<Arc<SuperKey>> {
- self.data.lock().unwrap().key_index.get(key_id).and_then(|k| k.upgrade())
+ fn lookup_key(&self, key_id: &SuperKeyIdentifier) -> Result<Option<Arc<SuperKey>>> {
+ let mut data = self.data.lock().unwrap();
+ Ok(match key_id {
+ SuperKeyIdentifier::DatabaseId(id) => data.key_index.get(id).and_then(|k| k.upgrade()),
+ SuperKeyIdentifier::BootLevel(level) => data
+ .boot_level_key_cache
+ .as_mut()
+ .map(|b| b.aes_key(*level as usize))
+ .transpose()
+ .context("In lookup_key: aes_key failed")?
+ .flatten()
+ .map(|key| {
+ Arc::new(SuperKey {
+ algorithm: SuperEncryptionAlgorithm::Aes256Gcm,
+ key,
+ id: *key_id,
+ reencrypt_with: None,
+ })
+ }),
+ })
}
pub fn get_per_boot_key_by_user_id(&self, user_id: UserId) -> Option<Arc<SuperKey>> {
@@ -215,26 +344,27 @@
Ok(())
}
- /// Unwraps an encrypted key blob given metadata identifying the encryption key.
- /// The function queries `metadata.encrypted_by()` to determine the encryption key.
- /// It then checks if the required key is memory resident, and if so decrypts the
- /// blob.
- pub fn unwrap_key<'a>(&self, blob: &'a [u8], metadata: &BlobMetaData) -> Result<KeyBlob<'a>> {
- let key_id = if let Some(EncryptedBy::KeyId(key_id)) = metadata.encrypted_by() {
- key_id
+ /// Check if a given key is super-encrypted, from its metadata. If so, unwrap the key using
+ /// the relevant super key.
+ pub fn unwrap_key_if_required<'a>(
+ &self,
+ metadata: &BlobMetaData,
+ blob: &'a [u8],
+ ) -> Result<KeyBlob<'a>> {
+ Ok(if let Some(key_id) = SuperKeyIdentifier::from_metadata(metadata) {
+ let super_key = self
+ .lookup_key(&key_id)
+ .context("In unwrap_key: lookup_key failed")?
+ .ok_or(Error::Rc(ResponseCode::LOCKED))
+ .context("In unwrap_key: Required super decryption key is not in memory.")?;
+ KeyBlob::Sensitive {
+ key: Self::unwrap_key_with_key(blob, metadata, &super_key)
+ .context("In unwrap_key: unwrap_key_with_key failed")?,
+ reencrypt_with: super_key.reencrypt_with.as_ref().unwrap_or(&super_key).clone(),
+ force_reencrypt: super_key.reencrypt_with.is_some(),
+ }
} else {
- return Err(Error::Rc(ResponseCode::VALUE_CORRUPTED))
- .context("In unwrap_key: Cannot determine wrapping key.");
- };
- let super_key = self
- .lookup_key(&key_id)
- .ok_or(Error::Rc(ResponseCode::LOCKED))
- .context("In unwrap_key: Required super decryption key is not in memory.")?;
- Ok(KeyBlob::Sensitive {
- key: Self::unwrap_key_with_key(blob, metadata, &super_key)
- .context("In unwrap_key: unwrap_key_with_key failed")?,
- reencrypt_with: super_key.reencrypt_with.as_ref().unwrap_or(&super_key).clone(),
- force_reencrypt: super_key.reencrypt_with.is_some(),
+ KeyBlob::Ref(blob)
})
}
@@ -389,7 +519,7 @@
.context(
"In populate_cache_from_super_key_blob. Failed to extract super key from key entry",
)?;
- self.install_per_boot_key_for_user(user_id, super_key.clone());
+ self.install_per_boot_key_for_user(user_id, super_key.clone())?;
Ok(super_key)
}
@@ -430,7 +560,12 @@
));
}
};
- Ok(Arc::new(SuperKey { algorithm, key, id: entry.id(), reencrypt_with }))
+ Ok(Arc::new(SuperKey {
+ algorithm,
+ key,
+ id: SuperKeyIdentifier::DatabaseId(entry.id()),
+ reencrypt_with,
+ }))
} else {
Err(Error::Rc(ResponseCode::VALUE_CORRUPTED))
.context("In extract_super_key_from_key_entry: No key blob info.")
@@ -498,7 +633,7 @@
.context("In encrypt_with_aes_super_key: Failed to encrypt new super key.")?;
metadata.add(BlobMetaEntry::Iv(iv));
metadata.add(BlobMetaEntry::AeadTag(tag));
- metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key.id)));
+ super_key.id.add_to_metadata(&mut metadata);
Ok((encrypted_key, metadata))
}
@@ -554,27 +689,23 @@
metadata.add(BlobMetaEntry::Salt(salt));
metadata.add(BlobMetaEntry::Iv(iv));
metadata.add(BlobMetaEntry::AeadTag(aead_tag));
- metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(key_id_guard.id())));
+ SuperKeyIdentifier::DatabaseId(key_id_guard.id())
+ .add_to_metadata(&mut metadata);
Ok((encrypted_key, metadata))
}
}
- }
- }
-
- /// Check if a given key is super-encrypted, from its metadata. If so, unwrap the key using
- /// the relevant super key.
- pub fn unwrap_key_if_required<'a>(
- &self,
- metadata: &BlobMetaData,
- key_blob: &'a [u8],
- ) -> Result<KeyBlob<'a>> {
- if Self::key_super_encrypted(&metadata) {
- let unwrapped_key = self
- .unwrap_key(key_blob, metadata)
- .context("In unwrap_key_if_required. Error in unwrapping the key.")?;
- Ok(unwrapped_key)
- } else {
- Ok(KeyBlob::Ref(key_blob))
+ SuperEncryptionType::BootLevel(level) => {
+ let key_id = SuperKeyIdentifier::BootLevel(level);
+ let super_key = self
+ .lookup_key(&key_id)
+ .context("In handle_super_encryption_on_key_init: lookup_key failed")?
+ .ok_or(Error::Rc(ResponseCode::LOCKED))
+ .context("In handle_super_encryption_on_key_init: Boot stage key absent")?;
+ Self::encrypt_with_aes_super_key(key_blob, &super_key).context(concat!(
+ "In handle_super_encryption_on_key_init: ",
+ "Failed to encrypt with BootLevel key."
+ ))
+ }
}
}
@@ -596,14 +727,6 @@
}
}
- // Helper function to decide if a key is super encrypted, given metadata.
- fn key_super_encrypted(metadata: &BlobMetaData) -> bool {
- if let Some(&EncryptedBy::KeyId(_)) = metadata.encrypted_by() {
- return true;
- }
- false
- }
-
/// Fetch a superencryption key from the database, or create it if it doesn't already exist.
/// When this is called, the caller must hold the lock on the SuperKeyManager.
/// So it's OK that the check and creation are different DB transactions.
@@ -663,7 +786,7 @@
Ok(Arc::new(SuperKey {
algorithm: key_type.algorithm,
key: super_key,
- id: key_entry.id(),
+ id: SuperKeyIdentifier::DatabaseId(key_entry.id()),
reencrypt_with,
}))
}
@@ -702,8 +825,8 @@
)
})?
.clone();
- data.add_key_to_key_index(&aes);
- data.add_key_to_key_index(&ecdh);
+ data.add_key_to_key_index(&aes)?;
+ data.add_key_to_key_index(&ecdh)?;
Ok(())
}
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index 48e9bfb..bca27d1 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -223,6 +223,10 @@
/// AID offset for uid space partitioning.
pub const AID_USER_OFFSET: u32 = cutils_bindgen::AID_USER_OFFSET;
+/// AID of the keystore process itself, used for keys that
+/// keystore generates for its own use.
+pub const AID_KEYSTORE: u32 = cutils_bindgen::AID_KEYSTORE;
+
/// Extracts the android user from the given uid.
pub fn uid_to_android_user(uid: u32) -> u32 {
// Safety: No memory access
diff --git a/keystore2/src/vintf/Android.bp b/keystore2/src/vintf/Android.bp
index feec8ae..3ab0ec5 100644
--- a/keystore2/src/vintf/Android.bp
+++ b/keystore2/src/vintf/Android.bp
@@ -53,11 +53,11 @@
shared_libs: ["libvintf"],
bindgen_flags: [
"--size_t-is-usize",
- "--whitelist-function", "getHalNames",
- "--whitelist-function", "getHalNamesAndVersions",
- "--whitelist-function", "getHidlInstances",
- "--whitelist-function", "getAidlInstances",
- "--whitelist-function", "freeNames",
+ "--allowlist-function", "getHalNames",
+ "--allowlist-function", "getHalNamesAndVersions",
+ "--allowlist-function", "getHidlInstances",
+ "--allowlist-function", "getAidlInstances",
+ "--allowlist-function", "freeNames",
],
}
diff --git a/keystore2/system_property/Android.bp b/keystore2/system_property/Android.bp
index 5a13c90..9e7b056 100644
--- a/keystore2/system_property/Android.bp
+++ b/keystore2/system_property/Android.bp
@@ -29,9 +29,9 @@
bindgen_flags: [
"--size_t-is-usize",
- "--whitelist-function=__system_property_find",
- "--whitelist-function=__system_property_read_callback",
- "--whitelist-function=__system_property_wait",
+ "--allowlist-function=__system_property_find",
+ "--allowlist-function=__system_property_read_callback",
+ "--allowlist-function=__system_property_wait",
],
}
diff --git a/keystore2/vpnprofilestore/lib.rs b/keystore2/vpnprofilestore/lib.rs
index f5adc1b..5123837 100644
--- a/keystore2/vpnprofilestore/lib.rs
+++ b/keystore2/vpnprofilestore/lib.rs
@@ -75,15 +75,11 @@
}
fn is_locked_error(e: &anyhow::Error) -> bool {
- matches!(e.root_cause().downcast_ref::<rusqlite::ffi::Error>(),
- Some(rusqlite::ffi::Error {
- code: rusqlite::ErrorCode::DatabaseBusy,
- ..
- })
- | Some(rusqlite::ffi::Error {
- code: rusqlite::ErrorCode::DatabaseLocked,
- ..
- }))
+ matches!(
+ e.root_cause().downcast_ref::<rusqlite::ffi::Error>(),
+ Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. })
+ | Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseLocked, .. })
+ )
}
fn init_tables(&mut self) -> Result<()> {
@@ -192,7 +188,7 @@
{
result.map_or_else(
|e| {
- log::error!("{:#?}", e);
+ log::error!("{:?}", e);
let root_cause = e.root_cause();
let rc = match root_cause.downcast_ref::<Error>() {
Some(Error::Error(e)) => *e,
diff --git a/ondevice-signing/KeystoreKey.cpp b/ondevice-signing/KeystoreKey.cpp
index 9b5e505..96e369a 100644
--- a/ondevice-signing/KeystoreKey.cpp
+++ b/ondevice-signing/KeystoreKey.cpp
@@ -151,8 +151,25 @@
KeyEntryResponse keyEntryResponse;
LOG(INFO) << "Trying to retrieve existing keystore key...";
status = mService->getKeyEntry(descriptor, &keyEntryResponse);
- if (!status.isOk()) {
- LOG(INFO) << "Existing keystore key not found, creating new key";
+ bool keyValid = false;
+
+ if (status.isOk()) {
+ // Make sure this is an early boot key
+ for (const auto& auth : keyEntryResponse.metadata.authorizations) {
+ if (auth.keyParameter.tag == Tag::MAX_BOOT_LEVEL) {
+ if (auth.keyParameter.value.get<KeyParameterValue::integer>() == kOdsignBootLevel) {
+ keyValid = true;
+ break;
+ }
+ }
+ }
+ if (!keyValid) {
+ LOG(WARNING) << "Found invalid keystore key without MAX_BOOT_LEVEL tag";
+ }
+ }
+
+ if (!keyValid) {
+ LOG(INFO) << "Existing keystore key not found or invalid, creating new key";
auto newKeyStatus = createNewKey(descriptor);
if (!newKeyStatus.ok()) {
LOG(ERROR) << "Failed to create new key";