Implement dump for IKeystoreMaintenance
Sample output on Cuttlefish:
```
keystore2 running
Device info for r#TRUSTED_ENVIRONMENT with Uuid([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
HAL version: 300
Implementation name: Rust reference implementation
Implementation author: Google
Timestamp token required: false
Database size information (in bytes):
r#DATABASE : 77824
r#KEY_ENTRY : 4096 (unused 3603)
r#KEY_ENTRY_ID_INDEX : 4096 (unused 3969)
r#KEY_ENTRY_DOMAIN_NAMESPACE_INDEX : 4096 (unused 3820)
r#BLOB_ENTRY : 4096 (unused 863)
r#BLOB_ENTRY_KEY_ENTRY_ID_INDEX : 4096 (unused 3954)
r#KEY_PARAMETER : 4096 (unused 2459)
r#KEY_PARAMETER_KEY_ENTRY_ID_INDEX : 4096 (unused 3024)
r#KEY_METADATA : 4096 (unused 3826)
r#KEY_METADATA_KEY_ENTRY_ID_INDEX : 4096 (unused 3999)
r#GRANT : 4096 (unused 4088)
r#AUTH_TOKEN : 0
r#BLOB_METADATA : 4096 (unused 3572)
r#BLOB_METADATA_BLOB_ENTRY_ID_INDEX : 4096 (unused 3906)
```
Test: adb shell dumpsys android.security.maintenance
Bug: 344987718
Flag: android.security.keystore2.enable_dump
Change-Id: I231079f32648e2fab7fed4857f6d3e29755b0d19
diff --git a/keystore2/aconfig/flags.aconfig b/keystore2/aconfig/flags.aconfig
index 05dae46..ff817b7 100644
--- a/keystore2/aconfig/flags.aconfig
+++ b/keystore2/aconfig/flags.aconfig
@@ -26,6 +26,14 @@
}
flag {
+ name: "enable_dump"
+ namespace: "hardware_backed_security"
+ description: "Include support for dump() on the IKeystoreMaintenance service"
+ bug: "344987718"
+ is_fixed_read_only: true
+}
+
+flag {
name: "import_previously_emulated_keys"
namespace: "hardware_backed_security"
description: "Include support for importing keys that were previously software-emulated into KeyMint"
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 8165c54..8457603 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -1113,7 +1113,7 @@
)
}
- /// Fetches a storage statisitics atom for a given storage type. For storage
+ /// Fetches a storage statistics atom for a given storage type. For storage
/// types that map to a table, information about the table's storage is
/// returned. Requests for storage types that are not DB tables return None.
pub fn get_storage_stat(&mut self, storage_type: MetricsStorage) -> Result<StorageStats> {
diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs
index 61277f1..7749507 100644
--- a/keystore2/src/maintenance.rs
+++ b/keystore2/src/maintenance.rs
@@ -24,7 +24,7 @@
use crate::permission::{KeyPerm, KeystorePerm};
use crate::super_key::SuperKeyManager;
use crate::utils::{
- check_get_app_uids_affected_by_sid_permissions, check_key_permission,
+ check_dump_permission, check_get_app_uids_affected_by_sid_permissions, check_key_permission,
check_keystore_permission, uid_to_android_user, watchdog as wd,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
@@ -36,6 +36,9 @@
use android_security_maintenance::binder::{
BinderFeatures, Interface, Result as BinderResult, Strong, ThreadState,
};
+use android_security_metrics::aidl::android::security::metrics::{
+ KeystoreAtomPayload::KeystoreAtomPayload::StorageStats
+};
use android_system_keystore2::aidl::android::system::keystore2::KeyDescriptor::KeyDescriptor;
use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
use anyhow::{Context, Result};
@@ -264,9 +267,78 @@
DB.with(|db| db.borrow_mut().get_app_uids_affected_by_sid(user_id, secure_user_id))
.context(ks_err!("Failed to get app UIDs affected by SID"))
}
+
+ fn dump_state(&self, f: &mut dyn std::io::Write) -> std::io::Result<()> {
+ writeln!(f, "keystore2 running")?;
+ writeln!(f)?;
+
+ // Display underlying device information
+ for sec_level in &[SecurityLevel::TRUSTED_ENVIRONMENT, SecurityLevel::STRONGBOX] {
+ let Ok((_dev, hw_info, uuid)) = get_keymint_device(sec_level) else { continue };
+
+ writeln!(f, "Device info for {sec_level:?} with {uuid:?}")?;
+ writeln!(f, " HAL version: {}", hw_info.versionNumber)?;
+ writeln!(f, " Implementation name: {}", hw_info.keyMintName)?;
+ writeln!(f, " Implementation author: {}", hw_info.keyMintAuthorName)?;
+ writeln!(f, " Timestamp token required: {}", hw_info.timestampTokenRequired)?;
+ }
+ writeln!(f)?;
+
+ // Display database size information.
+ match crate::metrics_store::pull_storage_stats() {
+ Ok(atoms) => {
+ writeln!(f, "Database size information (in bytes):")?;
+ for atom in atoms {
+ if let StorageStats(stats) = &atom.payload {
+ let stype = format!("{:?}", stats.storage_type);
+ if stats.unused_size == 0 {
+ writeln!(f, " {:<40}: {:>12}", stype, stats.size)?;
+ } else {
+ writeln!(
+ f,
+ " {:<40}: {:>12} (unused {})",
+ stype, stats.size, stats.unused_size
+ )?;
+ }
+ }
+ }
+ }
+ Err(e) => {
+ writeln!(f, "Failed to retrieve storage stats: {e:?}")?;
+ }
+ }
+ writeln!(f)?;
+
+ // Reminder: any additional information added to the `dump_state()` output needs to be
+ // careful not to include confidential information (e.g. key material).
+
+ Ok(())
+ }
}
-impl Interface for Maintenance {}
+impl Interface for Maintenance {
+ fn dump(
+ &self,
+ f: &mut dyn std::io::Write,
+ _args: &[&std::ffi::CStr],
+ ) -> Result<(), binder::StatusCode> {
+ if !keystore2_flags::enable_dump() {
+ log::info!("skipping dump() as flag not enabled");
+ return Ok(());
+ }
+ log::info!("dump()");
+ let _wp = wd::watch("IKeystoreMaintenance::dump");
+ check_dump_permission().map_err(|_e| {
+ log::error!("dump permission denied");
+ binder::StatusCode::PERMISSION_DENIED
+ })?;
+
+ self.dump_state(f).map_err(|e| {
+ log::error!("dump_state failed: {e:?}");
+ binder::StatusCode::UNKNOWN_ERROR
+ })
+ }
+}
impl IKeystoreMaintenance for Maintenance {
fn onUserAdded(&self, user_id: i32) -> BinderResult<()> {
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index 81ebdab..2b69d1e 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -38,6 +38,7 @@
};
use android_system_keystore2::aidl::android::system::keystore2::{
Authorization::Authorization, Domain::Domain, KeyDescriptor::KeyDescriptor,
+ ResponseCode::ResponseCode,
};
use anyhow::{Context, Result};
use binder::{FromIBinder, StatusCode, Strong, ThreadState};
@@ -125,14 +126,20 @@
/// identifiers. It throws an error if the permissions cannot be verified or if the caller doesn't
/// have the right permissions. Otherwise it returns silently.
pub fn check_device_attestation_permissions() -> anyhow::Result<()> {
- check_android_permission("android.permission.READ_PRIVILEGED_PHONE_STATE")
+ check_android_permission(
+ "android.permission.READ_PRIVILEGED_PHONE_STATE",
+ Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+ )
}
/// This function checks whether the calling app has the Android permissions needed to attest the
/// device-unique identifier. It throws an error if the permissions cannot be verified or if the
/// caller doesn't have the right permissions. Otherwise it returns silently.
pub fn check_unique_id_attestation_permissions() -> anyhow::Result<()> {
- check_android_permission("android.permission.REQUEST_UNIQUE_ID_ATTESTATION")
+ check_android_permission(
+ "android.permission.REQUEST_UNIQUE_ID_ATTESTATION",
+ Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+ )
}
/// This function checks whether the calling app has the Android permissions needed to manage
@@ -141,10 +148,19 @@
/// It throws an error if the permissions cannot be verified or if the caller doesn't
/// have the right permissions. Otherwise it returns silently.
pub fn check_get_app_uids_affected_by_sid_permissions() -> anyhow::Result<()> {
- check_android_permission("android.permission.MANAGE_USERS")
+ check_android_permission(
+ "android.permission.MANAGE_USERS",
+ Error::Km(ErrorCode::CANNOT_ATTEST_IDS),
+ )
}
-fn check_android_permission(permission: &str) -> anyhow::Result<()> {
+/// This function checks whether the calling app has the Android permission needed to dump
+/// Keystore state to logcat.
+pub fn check_dump_permission() -> anyhow::Result<()> {
+ check_android_permission("android.permission.DUMP", Error::Rc(ResponseCode::PERMISSION_DENIED))
+}
+
+fn check_android_permission(permission: &str, err: Error) -> anyhow::Result<()> {
let permission_controller: Strong<dyn IPermissionController::IPermissionController> =
binder::get_interface("permission")?;
@@ -160,8 +176,7 @@
map_binder_status(binder_result).context(ks_err!("checkPermission failed"))?;
match has_permissions {
true => Ok(()),
- false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS))
- .context(ks_err!("caller does not have the permission to attest device IDs")),
+ false => Err(err).context(ks_err!("caller does not have the '{permission}' permission")),
}
}