Adding tests to verify getNumberOfEntries and listEntriesBatched.

1. Try to list large number of aliases such that aliases list would
   exceed the binder transaction size limit. Test should successfully
   list the aliases using `listEntriesBatched` API.

2. Import keys from multiple processes having same user context. Try to
   list the aliases in all the processes with and without providing
   `startingPastAlias`. Test should list aliases using
   `listEntriesBatched` in all the processes using any of the alias as
   `startingPastAlias` and match with expected list of aliases. Test
   should also list all the aliases without providing
   `startingPastAlias`.

3. Try to list aliases with empty keystore using `listEntriesBatched`
   API. Test should successfully query the Keystore for aliases and
   vrify that keystore is empty.

4. Test to list aliases using domain as SELINUX using
   `listEntriesBatched` API.

5. Import multiple number of keys in an app context and try to list the
   aliases using imported keys aliases as `startingPastAlias` and verify
   the retrived the list of aliases matches the expected list of alises
   in all the cases.

6.  Try to list the key entries with domain SELINUX from user context
    where user doesn't possesses `GET_INFO` permission for specified
    namespace. Test should fail to list key entries with error response
    code `PERMISSION_DENIED`.

7. Try to list key entries with domain BLOB. Test should fail with error
   response code `INVALID_ARGUMENT`.

8.  Try to get the total number of keystore entries with domain SELINUX
    from user context where user doesn't possesses `GET_INFO` permission
    for specified namespace. Test should fail to get the count with
    error response code `PERMISSION_DENIED`.

9. Try to get the count of total number of entries in keystore with
   domain BLOB. Test should fail with error response code
   `INVALID_ARGUMENT`.

Bug: 194359114
Test: atest keystore2_client_test
Change-Id: I7dd52230cd602a1ae33e3f9f2a22d2dd2c447df7
This commit is contained in:
Rajesh Nyamagoud 2023-04-07 02:47:27 +00:00
parent 4b7b5986f6
commit 6a82349afb
3 changed files with 513 additions and 1 deletions

View file

@ -16,6 +16,10 @@
use anyhow::Result; use anyhow::Result;
use core::ops::Range;
use std::collections::HashSet;
use std::fmt::Write;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve,
ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType, ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType,
@ -1082,3 +1086,23 @@ pub fn generate_ec_agree_key(
Err(e) => Err(e), Err(e) => Err(e),
} }
} }
/// Helper method to import AES keys `total_count` of times.
pub fn import_aes_keys(
sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
alias_prefix: String,
total_count: Range<i32>,
) -> binder::Result<HashSet<String>> {
let mut imported_key_aliases = HashSet::new();
// Import Total number of keys with given alias prefix.
for count in total_count {
let mut alias = String::new();
write!(alias, "{}_{}", alias_prefix, count).unwrap();
imported_key_aliases.insert(alias.clone());
import_aes_key(sec_level, Domain::APP, -1, Some(alias))?;
}
Ok(imported_key_aliases)
}

View file

@ -23,7 +23,7 @@ use android_system_keystore2::aidl::android::system::keystore2::{
KeyPermission::KeyPermission, ResponseCode::ResponseCode, KeyPermission::KeyPermission, ResponseCode::ResponseCode,
}; };
use crate::keystore2_client_test_utils::delete_app_key; use crate::keystore2_client_test_utils::{delete_all_entries, delete_app_key, verify_aliases};
use keystore2_test_utils::{get_keystore_service, key_generations, key_generations::Error, run_as}; use keystore2_test_utils::{get_keystore_service, key_generations, key_generations::Error, run_as};
/// Try to find a key with given key parameters using `listEntries` API. /// Try to find a key with given key parameters using `listEntries` API.
@ -251,3 +251,464 @@ fn keystore2_list_entries_with_long_aliases_success() {
}) })
}; };
} }
/// Import large number of Keystore entries with long aliases such that the
/// aliases list would exceed the binder transaction size limit.
/// Try to list aliases of all the entries in the keystore using `listEntriesBatched` API.
#[test]
fn keystore2_list_entries_batched_with_long_aliases_success() {
static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
unsafe {
run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
let keystore2 = get_keystore_service();
let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
// Make sure there are no keystore entries exist before adding new entries.
delete_all_entries(&keystore2);
// Import 100 keys with aliases of length 6000.
let mut imported_key_aliases =
key_generations::import_aes_keys(&sec_level, "X".repeat(6000), 1..101).unwrap();
assert_eq!(
keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
100,
"Error while importing keys"
);
let mut start_past_alias = None;
let mut alias;
while !imported_key_aliases.is_empty() {
let key_descriptors =
keystore2.listEntriesBatched(Domain::APP, -1, start_past_alias).unwrap();
// Check retrieved key entries list is a subset of imported keys list.
assert!(key_descriptors
.iter()
.all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
alias = key_descriptors.last().unwrap().alias.clone().unwrap();
start_past_alias = Some(alias.as_ref());
// Delete the listed key entries from imported keys list.
key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
assert!(imported_key_aliases.remove(&alias));
});
}
assert!(imported_key_aliases.is_empty());
delete_all_entries(&keystore2);
assert_eq!(
keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
0,
"Error while doing cleanup"
);
})
};
}
/// Import keys from multiple processes with same user context and try to list the keystore entries
/// using `listEntriesBatched` API.
/// - Create two processes sharing user-id.
/// - From process-1, import 3 keys and try to list the keys using `listEntriesBatched`
/// without `startingPastAlias`, it should list all the 3 entries.
/// - From process-2, import another 5 keys and try to list the keys using `listEntriesBatched`
/// with the alias of the last key listed in process-1 as `startingPastAlias`. It should list
/// all the entries whose alias is greater than the provided `startingPastAlias`.
/// - From process-2 try to list all entries accessible to it by using `listEntriesBatched` with
/// `startingPastAlias` as None. It should list all the keys imported in process-1 and process-2.
#[test]
fn keystore2_list_entries_batched_with_multi_procs_success() {
static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
static ALIAS_PREFIX: &str = "key_test_batch_list";
unsafe {
run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
let keystore2 = get_keystore_service();
let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
// Make sure there are no keystore entries exist before adding new entries.
delete_all_entries(&keystore2);
// Import 3 keys with below aliases -
// [key_test_batch_list_1, key_test_batch_list_2, key_test_batch_list_3]
let imported_key_aliases =
key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 1..4)
.unwrap();
assert_eq!(
keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
3,
"Error while importing keys"
);
// List all entries in keystore for this user-id.
let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
assert_eq!(key_descriptors.len(), 3);
// Makes sure all listed aliases are matching with imported keys aliases.
assert!(key_descriptors
.iter()
.all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
})
};
unsafe {
run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
let keystore2 = get_keystore_service();
let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
// Import another 5 keys with below aliases -
// [ key_test_batch_list_4, key_test_batch_list_5, key_test_batch_list_6,
// key_test_batch_list_7, key_test_batch_list_8 ]
let mut imported_key_aliases =
key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 4..9)
.unwrap();
// Above context already 3 keys are imported, in this context 5 keys are imported,
// total 8 keystore entries are expected to be present in Keystore for this user-id.
assert_eq!(
keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
8,
"Error while importing keys"
);
// List keystore entries with `start_past_alias` as "key_test_batch_list_3".
// `listEntriesBatched` should list all the keystore entries with
// alias > "key_test_batch_list_3".
let key_descriptors = keystore2
.listEntriesBatched(Domain::APP, -1, Some("key_test_batch_list_3"))
.unwrap();
assert_eq!(key_descriptors.len(), 5);
// Make sure above listed aliases are matching with imported keys aliases.
assert!(key_descriptors
.iter()
.all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
// List all keystore entries with `start_past_alias` as `None`.
// `listEntriesBatched` should list all the keystore entries.
let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
assert_eq!(key_descriptors.len(), 8);
// Include previously imported keys aliases as well
imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_1");
imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_2");
imported_key_aliases.insert(ALIAS_PREFIX.to_owned() + "_3");
// Make sure all the above listed aliases are matching with imported keys aliases.
assert!(key_descriptors
.iter()
.all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap())));
delete_all_entries(&keystore2);
assert_eq!(
keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
0,
"Error while doing cleanup"
);
})
};
}
#[test]
fn keystore2_list_entries_batched_with_empty_keystore_success() {
static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
unsafe {
run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
let keystore2 = get_keystore_service();
// Make sure there are no keystore entries exist before adding new entries.
delete_all_entries(&keystore2);
// List all entries in keystore for this user-id, pass startingPastAlias = None
let key_descriptors = keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap();
assert_eq!(key_descriptors.len(), 0);
// List all entries in keystore for this user-id, pass startingPastAlias = <random value>
let key_descriptors =
keystore2.listEntriesBatched(Domain::APP, -1, Some("startingPastAlias")).unwrap();
assert_eq!(key_descriptors.len(), 0);
})
};
}
/// Import a key with SELINUX as domain, list aliases using `listEntriesBatched`.
/// Test should successfully list the imported key.
#[test]
fn keystore2_list_entries_batched_with_selinux_domain_success() {
let keystore2 = get_keystore_service();
let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
let alias = "test_selinux_key_list_alias_batched";
let _result = keystore2.deleteKey(&KeyDescriptor {
domain: Domain::SELINUX,
nspace: key_generations::SELINUX_SHELL_NAMESPACE,
alias: Some(alias.to_string()),
blob: None,
});
let initial_count = keystore2
.getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE)
.unwrap();
key_generations::import_aes_key(
&sec_level,
Domain::SELINUX,
key_generations::SELINUX_SHELL_NAMESPACE,
Some(alias.to_string()),
)
.unwrap();
assert_eq!(
keystore2
.getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE)
.unwrap(),
initial_count + 1,
"Error while getting number of keystore entries accessible."
);
let key_descriptors = keystore2
.listEntriesBatched(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, None)
.unwrap();
assert_eq!(key_descriptors.len(), (initial_count + 1) as usize);
let count =
key_descriptors.into_iter().map(|key| key.alias.unwrap()).filter(|a| a == alias).count();
assert_eq!(count, 1);
keystore2
.deleteKey(&KeyDescriptor {
domain: Domain::SELINUX,
nspace: key_generations::SELINUX_SHELL_NAMESPACE,
alias: Some(alias.to_string()),
blob: None,
})
.unwrap();
}
#[test]
fn keystore2_list_entries_batched_validate_count_and_order_success() {
static CLIENT_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
const USER_ID: u32 = 92;
const APPLICATION_ID: u32 = 10002;
static CLIENT_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID;
static CLIENT_GID: u32 = CLIENT_UID;
static ALIAS_PREFIX: &str = "key_test_batch_list";
unsafe {
run_as::run_as(CLIENT_CTX, Uid::from_raw(CLIENT_UID), Gid::from_raw(CLIENT_GID), || {
let keystore2 = get_keystore_service();
let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
// Make sure there are no keystore entries exist before adding new entries.
delete_all_entries(&keystore2);
// Import keys with below mentioned aliases -
// [
// key_test_batch_list_1,
// key_test_batch_list_2,
// key_test_batch_list_3,
// key_test_batch_list_4,
// key_test_batch_list_5,
// key_test_batch_list_10,
// key_test_batch_list_11,
// key_test_batch_list_12,
// key_test_batch_list_21,
// key_test_batch_list_22,
// ]
let _imported_key_aliases =
key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 1..6)
.unwrap();
assert_eq!(
keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
5,
"Error while importing keys"
);
let _imported_key_aliases =
key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 10..13)
.unwrap();
assert_eq!(
keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
8,
"Error while importing keys"
);
let _imported_key_aliases =
key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 21..23)
.unwrap();
assert_eq!(
keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(),
10,
"Error while importing keys"
);
// List the aliases using given `startingPastAlias` and verify the listed
// aliases with the expected list of aliases.
verify_aliases(&keystore2, Some(format!("{}{}", ALIAS_PREFIX, "_5").as_str()), vec![]);
verify_aliases(
&keystore2,
Some(format!("{}{}", ALIAS_PREFIX, "_4").as_str()),
vec![ALIAS_PREFIX.to_owned() + "_5"],
);
verify_aliases(
&keystore2,
Some(format!("{}{}", ALIAS_PREFIX, "_3").as_str()),
vec![ALIAS_PREFIX.to_owned() + "_4", ALIAS_PREFIX.to_owned() + "_5"],
);
verify_aliases(
&keystore2,
Some(format!("{}{}", ALIAS_PREFIX, "_2").as_str()),
vec![
ALIAS_PREFIX.to_owned() + "_21",
ALIAS_PREFIX.to_owned() + "_22",
ALIAS_PREFIX.to_owned() + "_3",
ALIAS_PREFIX.to_owned() + "_4",
ALIAS_PREFIX.to_owned() + "_5",
],
);
verify_aliases(
&keystore2,
Some(format!("{}{}", ALIAS_PREFIX, "_1").as_str()),
vec![
ALIAS_PREFIX.to_owned() + "_10",
ALIAS_PREFIX.to_owned() + "_11",
ALIAS_PREFIX.to_owned() + "_12",
ALIAS_PREFIX.to_owned() + "_2",
ALIAS_PREFIX.to_owned() + "_21",
ALIAS_PREFIX.to_owned() + "_22",
ALIAS_PREFIX.to_owned() + "_3",
ALIAS_PREFIX.to_owned() + "_4",
ALIAS_PREFIX.to_owned() + "_5",
],
);
verify_aliases(
&keystore2,
Some(ALIAS_PREFIX),
vec![
ALIAS_PREFIX.to_owned() + "_1",
ALIAS_PREFIX.to_owned() + "_10",
ALIAS_PREFIX.to_owned() + "_11",
ALIAS_PREFIX.to_owned() + "_12",
ALIAS_PREFIX.to_owned() + "_2",
ALIAS_PREFIX.to_owned() + "_21",
ALIAS_PREFIX.to_owned() + "_22",
ALIAS_PREFIX.to_owned() + "_3",
ALIAS_PREFIX.to_owned() + "_4",
ALIAS_PREFIX.to_owned() + "_5",
],
);
verify_aliases(
&keystore2,
None,
vec![
ALIAS_PREFIX.to_owned() + "_1",
ALIAS_PREFIX.to_owned() + "_10",
ALIAS_PREFIX.to_owned() + "_11",
ALIAS_PREFIX.to_owned() + "_12",
ALIAS_PREFIX.to_owned() + "_2",
ALIAS_PREFIX.to_owned() + "_21",
ALIAS_PREFIX.to_owned() + "_22",
ALIAS_PREFIX.to_owned() + "_3",
ALIAS_PREFIX.to_owned() + "_4",
ALIAS_PREFIX.to_owned() + "_5",
],
);
})
};
}
/// Try to list the key entries with domain SELINUX from user context where user doesn't possesses
/// `GET_INFO` permission for specified namespace. Test should fail to list key entries with error
/// response code `PERMISSION_DENIED`.
#[test]
fn keystore2_list_entries_batched_fails_perm_denied() {
let auid = 91 * AID_USER_OFFSET + 10001;
let agid = 91 * AID_USER_OFFSET + 10001;
static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
unsafe {
run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
let keystore2 = get_keystore_service();
let result = key_generations::map_ks_error(keystore2.listEntriesBatched(
Domain::SELINUX,
key_generations::SELINUX_SHELL_NAMESPACE,
None,
));
assert!(result.is_err());
assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
})
};
}
/// Try to list key entries with domain BLOB. Test should fail with error response code
/// `INVALID_ARGUMENT`.
#[test]
fn keystore2_list_entries_batched_fails_invalid_arg() {
let keystore2 = get_keystore_service();
let result = key_generations::map_ks_error(keystore2.listEntriesBatched(
Domain::BLOB,
key_generations::SELINUX_SHELL_NAMESPACE,
None,
));
assert!(result.is_err());
assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
}
/// Try to get the number of key entries with domain SELINUX from user context where user doesn't
/// possesses `GET_INFO` permission for specified namespace. Test should fail to list key entries
/// with error response code `PERMISSION_DENIED`.
#[test]
fn keystore2_get_number_of_entries_fails_perm_denied() {
let auid = 91 * AID_USER_OFFSET + 10001;
let agid = 91 * AID_USER_OFFSET + 10001;
static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
unsafe {
run_as::run_as(TARGET_CTX, Uid::from_raw(auid), Gid::from_raw(agid), move || {
let keystore2 = get_keystore_service();
let result = key_generations::map_ks_error(
keystore2
.getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE),
);
assert!(result.is_err());
assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err());
})
};
}
/// Try to get number of key entries with domain BLOB. Test should fail with error response code
/// `INVALID_ARGUMENT`.
#[test]
fn keystore2_get_number_of_entries_fails_invalid_arg() {
let keystore2 = get_keystore_service();
let result = key_generations::map_ks_error(
keystore2.getNumberOfEntries(Domain::BLOB, key_generations::SELINUX_SHELL_NAMESPACE),
);
assert!(result.is_err());
assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err());
}

View file

@ -376,6 +376,17 @@ pub fn delete_app_key(
}) })
} }
/// Deletes all entries from keystore.
pub fn delete_all_entries(keystore2: &binder::Strong<dyn IKeystoreService>) {
while keystore2.getNumberOfEntries(Domain::APP, -1).unwrap() != 0 {
let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap();
key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| {
delete_app_key(keystore2, &alias).unwrap();
});
}
assert!(keystore2.getNumberOfEntries(Domain::APP, -1).unwrap() == 0);
}
/// Encrypt the secure key with given transport key. /// Encrypt the secure key with given transport key.
pub fn encrypt_secure_key( pub fn encrypt_secure_key(
sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
@ -417,3 +428,19 @@ pub fn encrypt_transport_key(
Ok(encoded.to_vec()) Ok(encoded.to_vec())
} }
/// List aliases using given `startingPastAlias` and verify that the fetched list is matching with
/// the expected list of aliases.
pub fn verify_aliases(
keystore2: &binder::Strong<dyn IKeystoreService>,
starting_past_alias: Option<&str>,
expected_aliases: Vec<String>,
) {
let key_descriptors =
keystore2.listEntriesBatched(Domain::APP, -1, starting_past_alias).unwrap();
assert_eq!(key_descriptors.len(), expected_aliases.len());
assert!(key_descriptors
.iter()
.all(|key| expected_aliases.contains(key.alias.as_ref().unwrap())));
}