Keystore 2.0: Implement list keys.
Test: keystore2_test
Change-Id: Id5b875adce95723cd837b22beca7af44621bb2cd
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index a2cd6cd..9086faf 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -390,24 +390,30 @@
key_id: &KeyIdGuard,
params: impl IntoIterator<Item = &'a KeyParameter>,
) -> Result<()> {
- let mut stmt = self
+ let tx = self
.conn
- .prepare(
- "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
+ .transaction_with_behavior(TransactionBehavior::Immediate)
+ .context("In insert_keyparameter: Failed to start transaction.")?;
+ {
+ let mut stmt = tx
+ .prepare(
+ "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
VALUES (?, ?, ?, ?);",
- )
- .context("In insert_keyparameter: Failed to prepare statement.")?;
+ )
+ .context("In insert_keyparameter: Failed to prepare statement.")?;
- let iter = params.into_iter();
- for p in iter {
- stmt.insert(params![
- key_id.0,
- p.get_tag().0,
- p.key_parameter_value(),
- p.security_level().0
- ])
- .with_context(|| format!("In insert_keyparameter: Failed to insert {:?}", p))?;
+ let iter = params.into_iter();
+ for p in iter {
+ stmt.insert(params![
+ key_id.0,
+ p.get_tag().0,
+ p.key_parameter_value(),
+ p.security_level().0
+ ])
+ .with_context(|| format!("In insert_keyparameter: Failed to insert {:?}", p))?;
+ }
}
+ tx.commit().context("In insert_keyparameter: Failed to commit transaction.")?;
Ok(())
}
@@ -759,6 +765,35 @@
))
}
+ /// Returns a list of KeyDescriptors in the selected domain/namespace.
+ /// The key descriptors will have the domain, nspace, and alias field set.
+ /// Domain must be APP or SELINUX, the caller must make sure of that.
+ pub fn list(&mut self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
+ let mut stmt = self
+ .conn
+ .prepare(
+ "SELECT alias FROM persistent.keyentry
+ WHERE domain = ? AND namespace = ? AND alias IS NOT NULL;",
+ )
+ .context("In list: Failed to prepare.")?;
+
+ let mut rows =
+ stmt.query(params![domain.0 as u32, namespace]).context("In list: Failed to query.")?;
+
+ let mut descriptors: Vec<KeyDescriptor> = Vec::new();
+ db_utils::with_rows_extract_all(&mut rows, |row| {
+ descriptors.push(KeyDescriptor {
+ domain,
+ nspace: namespace,
+ alias: Some(row.get(0).context("Trying to extract alias.")?),
+ blob: None,
+ });
+ Ok(())
+ })
+ .context("In list.")?;
+ Ok(descriptors)
+ }
+
/// Adds a grant to the grant table.
/// Like `load_key_entry` this function loads the access tuple before
/// it uses the callback for a permission check. Upon success,
@@ -1465,6 +1500,90 @@
Ok(())
}
+ #[test]
+ fn list() -> Result<()> {
+ let temp_dir = TempDir::new("list_test")?;
+ let mut db = KeystoreDB::new(temp_dir.path())?;
+ static LIST_O_ENTRIES: &[(Domain, i64, &str)] = &[
+ (Domain::APP, 1, "test1"),
+ (Domain::APP, 1, "test2"),
+ (Domain::APP, 1, "test3"),
+ (Domain::APP, 1, "test4"),
+ (Domain::APP, 1, "test5"),
+ (Domain::APP, 1, "test6"),
+ (Domain::APP, 1, "test7"),
+ (Domain::APP, 2, "test1"),
+ (Domain::APP, 2, "test2"),
+ (Domain::APP, 2, "test3"),
+ (Domain::APP, 2, "test4"),
+ (Domain::APP, 2, "test5"),
+ (Domain::APP, 2, "test6"),
+ (Domain::APP, 2, "test8"),
+ (Domain::SELINUX, 100, "test1"),
+ (Domain::SELINUX, 100, "test2"),
+ (Domain::SELINUX, 100, "test3"),
+ (Domain::SELINUX, 100, "test4"),
+ (Domain::SELINUX, 100, "test5"),
+ (Domain::SELINUX, 100, "test6"),
+ (Domain::SELINUX, 100, "test9"),
+ ];
+
+ let list_o_keys: Vec<(i64, i64)> = LIST_O_ENTRIES
+ .iter()
+ .map(|(domain, ns, alias)| {
+ let entry =
+ make_test_key_entry(&mut db, *domain, *ns, *alias).unwrap_or_else(|e| {
+ panic!("Failed to insert {:?} {} {}. Error {:?}", domain, ns, alias, e)
+ });
+ (entry.id(), *ns)
+ })
+ .collect();
+
+ for (domain, namespace) in
+ &[(Domain::APP, 1i64), (Domain::APP, 2i64), (Domain::SELINUX, 100i64)]
+ {
+ let mut list_o_descriptors: Vec<KeyDescriptor> = LIST_O_ENTRIES
+ .iter()
+ .filter_map(|(domain, ns, alias)| match ns {
+ ns if *ns == *namespace => Some(KeyDescriptor {
+ domain: *domain,
+ nspace: *ns,
+ alias: Some(alias.to_string()),
+ blob: None,
+ }),
+ _ => None,
+ })
+ .collect();
+ list_o_descriptors.sort();
+ let mut list_result = db.list(*domain, *namespace)?;
+ list_result.sort();
+ assert_eq!(list_o_descriptors, list_result);
+
+ let mut list_o_ids: Vec<i64> = list_o_descriptors
+ .into_iter()
+ .map(|d| {
+ let (_, entry) = db
+ .load_key_entry(d, KeyEntryLoadBits::NONE, *namespace as u32, |_, _| Ok(()))
+ .unwrap();
+ entry.id()
+ })
+ .collect();
+ list_o_ids.sort_unstable();
+ let mut loaded_entries: Vec<i64> = list_o_keys
+ .iter()
+ .filter_map(|(id, ns)| match ns {
+ ns if *ns == *namespace => Some(*id),
+ _ => None,
+ })
+ .collect();
+ loaded_entries.sort_unstable();
+ assert_eq!(list_o_ids, loaded_entries);
+ }
+ assert_eq!(Vec::<KeyDescriptor>::new(), db.list(Domain::SELINUX, 101)?);
+
+ Ok(())
+ }
+
// Helpers
// Checks that the given result is an error containing the given string.
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index eb0d01b..cb04031 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -22,10 +22,11 @@
use crate::error::{self, map_or_log_err, ErrorCode};
use crate::globals::DB;
use crate::permission;
-use crate::permission::KeyPerm;
+use crate::permission::{KeyPerm, KeystorePerm};
use crate::security_level::KeystoreSecurityLevel;
use crate::utils::{
- check_grant_permission, check_key_permission, key_parameters_to_authorizations, Asp,
+ check_grant_permission, check_key_permission, check_keystore_permission,
+ key_parameters_to_authorizations, Asp,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
use android_system_keystore2::aidl::android::system::keystore2::{
@@ -35,6 +36,8 @@
};
use anyhow::{anyhow, Context, Result};
use binder::{IBinder, Interface, ThreadState};
+use error::Error;
+use keystore2_selinux as selinux;
/// Implementation of the IKeystoreService.
pub struct KeystoreService {
@@ -146,8 +149,44 @@
}
fn list_entries(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
- // TODO implement.
- Err(anyhow!(error::Error::sys()))
+ let mut k = match domain {
+ Domain::APP => KeyDescriptor {
+ domain,
+ nspace: ThreadState::get_calling_uid() as u64 as i64,
+ ..Default::default()
+ },
+ Domain::SELINUX => KeyDescriptor{domain, nspace: namespace, ..Default::default()},
+ _ => return Err(Error::perm()).context(
+ "In list_entries: List entries is only supported for Domain::APP and Domain::SELINUX."
+ ),
+ };
+
+ // First we check if the caller has the info permission for the selected domain/namespace.
+ // By default we use the calling uid as namespace if domain is Domain::APP.
+ // If the first check fails we check if the caller has the list permission allowing to list
+ // any namespace. In that case we also adjust the queried namespace if a specific uid was
+ // selected.
+ match check_key_permission(KeyPerm::get_info(), &k, &None) {
+ Err(e) => {
+ if let Some(selinux::Error::PermissionDenied) =
+ e.root_cause().downcast_ref::<selinux::Error>()
+ {
+ check_keystore_permission(KeystorePerm::list())
+ .context("In list_entries: While checking keystore permission.")?;
+ if namespace != -1 {
+ k.nspace = namespace;
+ }
+ } else {
+ return Err(e).context("In list_entries: While checking key permission.")?;
+ }
+ }
+ Ok(()) => {}
+ };
+
+ DB.with(|db| {
+ let mut db = db.borrow_mut();
+ db.list(k.domain, k.nspace)
+ })
}
fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {