Merge "Deleted clang property in Android.bp files"
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index e6cb4fb..43419b7 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -89,6 +89,8 @@
"librand",
"libserde",
"libserde_cbor",
+ "libthiserror",
+ "libanyhow",
],
}
@@ -122,6 +124,8 @@
"librand",
"libserde",
"libserde_cbor",
+ "libthiserror",
+ "libanyhow",
],
}
diff --git a/keystore2/aidl/Android.bp b/keystore2/aidl/Android.bp
index ae08567..d70f210 100644
--- a/keystore2/aidl/Android.bp
+++ b/keystore2/aidl/Android.bp
@@ -199,3 +199,13 @@
"android.system.keystore2-V2-ndk",
],
}
+
+// A rust_defaults that includes the latest Keystore2 AIDL library.
+// Modules that depend on Keystore2 directly can include this rust_defaults to avoid
+// managing dependency versions explicitly.
+rust_defaults {
+ name: "keystore2_use_latest_aidl_rust",
+ rustlibs: [
+ "android.system.keystore2-V2-rust",
+ ],
+}
diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs
index 4fbe124..d5a7b7b 100644
--- a/keystore2/test_utils/authorizations.rs
+++ b/keystore2/test_utils/authorizations.rs
@@ -22,6 +22,7 @@
};
/// Helper struct to create set of Authorizations.
+#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AuthSetBuilder(Vec<KeyParameter>);
impl Default for AuthSetBuilder {
@@ -77,6 +78,15 @@
});
self
}
+
+ /// Add No_auth_required.
+ pub fn no_auth_required(mut self) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::NO_AUTH_REQUIRED,
+ value: KeyParameterValue::BoolValue(true),
+ });
+ self
+ }
}
impl Deref for AuthSetBuilder {
diff --git a/keystore2/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs
index f49aa9f..d917fa1 100644
--- a/keystore2/test_utils/key_generations.rs
+++ b/keystore2/test_utils/key_generations.rs
@@ -14,43 +14,94 @@
//! This module implements test utils to generate various types of keys.
+use anyhow::Result;
+
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
- Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose,
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode,
+ KeyPurpose::KeyPurpose,
};
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor,
- KeyMetadata::KeyMetadata,
+ KeyMetadata::KeyMetadata, ResponseCode::ResponseCode,
};
use crate::authorizations::AuthSetBuilder;
+use android_system_keystore2::binder::{ExceptionCode, Result as BinderResult};
-const SELINUX_SHELL_NAMESPACE: i64 = 1;
+/// Shell namespace.
+pub const SELINUX_SHELL_NAMESPACE: i64 = 1;
-/// Generate attested EC Key blob using given security level with below key parameters -
+/// To map Keystore errors.
+#[derive(thiserror::Error, Debug, Eq, PartialEq)]
+pub enum Error {
+ /// Keystore2 error code
+ #[error("ResponseCode {0:?}")]
+ Rc(ResponseCode),
+ /// Keymint error code
+ #[error("ErrorCode {0:?}")]
+ Km(ErrorCode),
+ /// Exception
+ #[error("Binder exception {0:?}")]
+ Binder(ExceptionCode),
+}
+
+/// Keystore2 error mapping.
+pub fn map_ks_error<T>(r: BinderResult<T>) -> Result<T, Error> {
+ r.map_err(|s| {
+ match s.exception_code() {
+ ExceptionCode::SERVICE_SPECIFIC => {
+ match s.service_specific_error() {
+ se if se < 0 => {
+ // Negative service specific errors are KM error codes.
+ Error::Km(ErrorCode(se))
+ }
+ se => {
+ // Positive service specific errors are KS response codes.
+ Error::Rc(ResponseCode(se))
+ }
+ }
+ }
+ // We create `Error::Binder` to preserve the exception code
+ // for logging.
+ e_code => Error::Binder(e_code),
+ }
+ })
+}
+
+/// Generate EC Key using given security level and domain with below key parameters and
+/// optionally allow the generated key to be attested with factory provisioned attest key using
+/// given challenge and application id -
/// Purposes: SIGN and VERIFY
/// Digest: SHA_2_256
/// Curve: P_256
-pub fn generate_ec_p256_signing_key_with_attestation(
+pub fn generate_ec_p256_signing_key(
sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>,
+ domain: Domain,
+ nspace: i64,
+ alias: Option<String>,
+ att_challenge: Option<&[u8]>,
+ att_app_id: Option<&[u8]>,
) -> binder::Result<KeyMetadata> {
- let att_challenge: &[u8] = b"foo";
- let att_app_id: &[u8] = b"bar";
- let gen_params = AuthSetBuilder::new()
+ let mut key_attest = false;
+ let mut gen_params = AuthSetBuilder::new()
.algorithm(Algorithm::EC)
.purpose(KeyPurpose::SIGN)
.purpose(KeyPurpose::VERIFY)
.digest(Digest::SHA_2_256)
- .ec_curve(EcCurve::P_256)
- .attestation_challenge(att_challenge.to_vec())
- .attestation_app_id(att_app_id.to_vec());
+ .ec_curve(EcCurve::P_256);
+
+ if let Some(challenge) = att_challenge {
+ key_attest = true;
+ gen_params = gen_params.clone().attestation_challenge(challenge.to_vec());
+ }
+
+ if let Some(app_id) = att_app_id {
+ key_attest = true;
+ gen_params = gen_params.clone().attestation_app_id(app_id.to_vec());
+ }
match sec_level.generateKey(
- &KeyDescriptor {
- domain: Domain::BLOB,
- nspace: SELINUX_SHELL_NAMESPACE,
- alias: None,
- blob: None,
- },
+ &KeyDescriptor { domain, nspace, alias, blob: None },
None,
&gen_params,
0,
@@ -58,8 +109,12 @@
) {
Ok(key_metadata) => {
assert!(key_metadata.certificate.is_some());
- assert!(key_metadata.certificateChain.is_some());
- assert!(key_metadata.key.blob.is_some());
+ if key_attest {
+ assert!(key_metadata.certificateChain.is_some());
+ }
+ if domain == Domain::BLOB {
+ assert!(key_metadata.key.blob.is_some());
+ }
Ok(key_metadata)
}
diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp
new file mode 100644
index 0000000..21784a1
--- /dev/null
+++ b/keystore2/tests/Android.bp
@@ -0,0 +1,39 @@
+// Copyright 2022, 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.
+
+rust_test {
+ name: "keystore2_client_tests",
+ defaults: [
+ "keymint_use_latest_hal_aidl_rust",
+ "keystore2_use_latest_aidl_rust",
+ ],
+ srcs: ["keystore2_client_tests.rs"],
+ test_suites: [
+ "general-tests",
+ ],
+ test_config: "AndroidTest.xml",
+
+ rustlibs: [
+ "librustutils",
+ "libkeystore2_test_utils",
+ "libnix",
+ "libanyhow",
+ "libbinder_rs",
+ "liblazy_static",
+ "liblibc",
+ "libserde",
+ "libthiserror",
+ ],
+ require_root: true,
+}
diff --git a/keystore2/tests/AndroidTest.xml b/keystore2/tests/AndroidTest.xml
new file mode 100644
index 0000000..7db36f7
--- /dev/null
+++ b/keystore2/tests/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<configuration description="Config to run keystore2_client_tests device tests.">
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+ </target_preparer>
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option
+ name="push"
+ value="keystore2_client_tests->/data/local/tmp/keystore2_client_tests"
+ />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+ <option name="test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="keystore2_client_tests" />
+ <!-- When we run run multiple tests by default they run in parallel.
+ This will create issue as we create various child/user contexts
+ in a test leading to issues with IPC.
+ Serializing tests with below configuration to avoid IPC issues.
+ -->
+ <option name="native-test-flag" value="--test-threads=1" />
+ </test>
+</configuration>
diff --git a/keystore2/tests/keystore2_client_tests.rs b/keystore2/tests/keystore2_client_tests.rs
new file mode 100644
index 0000000..246f204
--- /dev/null
+++ b/keystore2/tests/keystore2_client_tests.rs
@@ -0,0 +1,140 @@
+// Copyright 2022, 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.
+
+use nix::unistd::{getuid, Gid, Uid};
+use rustutils::users::AID_USER_OFFSET;
+use serde::{Deserialize, Serialize};
+
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Digest::Digest, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ Domain::Domain, ResponseCode::ResponseCode,
+};
+
+use keystore2_test_utils::authorizations;
+use keystore2_test_utils::get_keystore_service;
+use keystore2_test_utils::key_generations;
+use keystore2_test_utils::run_as;
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+enum TestOutcome {
+ Ok,
+ BackendBusy,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+struct BarrierReached;
+
+fn create_op_run_as_child(
+ target_ctx: &'static str,
+ auid: Uid,
+ agid: Gid,
+ forced_op: bool,
+) -> run_as::ChildHandle<TestOutcome, BarrierReached> {
+ unsafe {
+ run_as::run_as_child(target_ctx, auid, agid, move |reader, writer| {
+ let keystore2 = get_keystore_service();
+ let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let alias = format!("ks_prune_op_test_key_{}", getuid());
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::APP,
+ key_generations::SELINUX_SHELL_NAMESPACE,
+ Some(alias),
+ None,
+ None,
+ )
+ .unwrap();
+
+ let result = sec_level.createOperation(
+ &key_metadata.key,
+ &authorizations::AuthSetBuilder::new()
+ .purpose(KeyPurpose::SIGN)
+ .digest(Digest::SHA_2_256),
+ forced_op,
+ );
+
+ // At this point the result must be `BACKEND_BUSY` or `Ok`.
+ let outcome = match &result {
+ Ok(_) => TestOutcome::Ok,
+ Err(s) if s.service_specific_error() == ResponseCode::BACKEND_BUSY.0 => {
+ TestOutcome::BackendBusy
+ }
+ Err(e) => panic!("createOperation returned unexpected err: {:?}", e),
+ };
+
+ // Let the parent know that an operation has been started, then
+ // wait until the parent notifies us to continue, so the operation
+ // remains open.
+ writer.send(&BarrierReached {});
+ reader.recv();
+
+ outcome
+ })
+ .expect("Failed to create child proc.")
+ }
+}
+
+fn create_operations(
+ target_ctx: &'static str,
+ forced_op: bool,
+ max_ops: i32,
+) -> Vec<run_as::ChildHandle<TestOutcome, BarrierReached>> {
+ let base_gid = 99 * AID_USER_OFFSET + 10001;
+ let base_uid = 99 * AID_USER_OFFSET + 10001;
+ (0..max_ops)
+ .into_iter()
+ .map(|i| {
+ create_op_run_as_child(
+ target_ctx,
+ Uid::from_raw(base_uid + (i as u32)),
+ Gid::from_raw(base_gid + (i as u32)),
+ forced_op,
+ )
+ })
+ .collect()
+}
+
+/// This test verifies that backend service throws BACKEND_BUSY error when all
+/// operations slots are full. This test creates operations in child processes and
+/// collects the status of operations performed in each child proc and determines
+/// whether any child proc exited with error status.
+#[test]
+fn keystore2_backend_busy_test() {
+ const MAX_OPS: i32 = 100;
+ static TARGET_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ let forced_op = false;
+
+ let mut child_handles = create_operations(TARGET_CTX, forced_op, MAX_OPS);
+
+ // Wait until all child procs notifies us to continue,
+ // so that there are definitely enough operations outstanding to trigger a BACKEND_BUSY.
+ for ch in child_handles.iter_mut() {
+ ch.recv();
+ }
+ // Notify each child to resume and finish.
+ for ch in child_handles.iter_mut() {
+ ch.send(&BarrierReached {});
+ }
+
+ // Collect the result and validate whether backend busy has occurred.
+ let mut busy_count = 0;
+ for ch in child_handles.into_iter() {
+ if ch.get_result() == TestOutcome::BackendBusy {
+ busy_count += 1;
+ }
+ }
+ assert!(busy_count > 0)
+}
diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
index cf9f1a9..48275ae 100644
--- a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
+++ b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs
@@ -164,9 +164,17 @@
.getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT)
.unwrap();
// Generate Key BLOB and prepare legacy keystore blob files.
- let key_metadata =
- key_generations::generate_ec_p256_signing_key_with_attestation(&sec_level)
- .expect("Failed to generate key blob");
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::BLOB,
+ SELINUX_SHELL_NAMESPACE,
+ None,
+ Some(att_challenge),
+ Some(att_app_id),
+ )
+ .expect("Failed to generate key blob");
// Create keystore file layout for user_99.
let pw: Password = PASSWORD.into();
@@ -415,9 +423,17 @@
.getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT)
.unwrap();
// Generate Key BLOB and prepare legacy keystore blob files.
- let key_metadata =
- key_generations::generate_ec_p256_signing_key_with_attestation(&sec_level)
- .expect("Failed to generate key blob");
+ let att_challenge: &[u8] = b"foo";
+ let att_app_id: &[u8] = b"bar";
+ let key_metadata = key_generations::generate_ec_p256_signing_key(
+ &sec_level,
+ Domain::BLOB,
+ SELINUX_SHELL_NAMESPACE,
+ None,
+ Some(att_challenge),
+ Some(att_app_id),
+ )
+ .expect("Failed to generate key blob");
// Create keystore file layout for user_98.
let pw: Password = PASSWORD.into();