Add test for unlocked-device-required key
Test: keystore2_client_tests
Change-Id: Icdf28ff9ff47a3133109e1ffaed9955fdbae9e83
diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs
index 2cb2aaf..a96d994 100644
--- a/keystore2/test_utils/authorizations.rs
+++ b/keystore2/test_utils/authorizations.rs
@@ -360,6 +360,15 @@
});
self
}
+
+ /// Set unlocked-device-required
+ pub fn unlocked_device_required(mut self) -> Self {
+ self.0.push(KeyParameter {
+ tag: Tag::UNLOCKED_DEVICE_REQUIRED,
+ value: KeyParameterValue::BoolValue(true),
+ });
+ self
+ }
}
impl Deref for AuthSetBuilder {
diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp
index 01ea746..ca9f5e3 100644
--- a/keystore2/tests/Android.bp
+++ b/keystore2/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_android_hardware_backed_security",
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "system_security_license"
@@ -38,9 +39,13 @@
rustlibs: [
"android.hardware.security.secureclock-V1-rust",
"android.security.authorization-rust",
+ "android.security.maintenance-rust",
"libaconfig_android_hardware_biometrics_rust",
+ "libandroid_logger",
+ "libandroid_security_flags_rust",
"libbinder_rs",
"libkeystore2_test_utils",
+ "liblog_rust",
"libnix",
"libopenssl",
"librustutils",
diff --git a/keystore2/tests/keystore2_client_tests.rs b/keystore2/tests/keystore2_client_tests.rs
index a0c140a..34ba81f 100644
--- a/keystore2/tests/keystore2_client_tests.rs
+++ b/keystore2/tests/keystore2_client_tests.rs
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// TODO: rename modules to strip text repeated from crate name ("keystore2_client_" and "_tests").
pub mod keystore2_client_3des_key_tests;
pub mod keystore2_client_aes_key_tests;
pub mod keystore2_client_attest_key_tests;
@@ -30,3 +31,5 @@
pub mod keystore2_client_rsa_key_tests;
pub mod keystore2_client_test_utils;
pub mod keystore2_client_update_subcomponent_tests;
+
+pub mod user_auth;
diff --git a/keystore2/tests/user_auth.rs b/keystore2/tests/user_auth.rs
new file mode 100644
index 0000000..263ad56
--- /dev/null
+++ b/keystore2/tests/user_auth.rs
@@ -0,0 +1,245 @@
+// Copyright 2024, 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.
+
+//! Tests for user authentication interactions (via `IKeystoreAuthorization`).
+
+use crate::keystore2_client_test_utils::BarrierReached;
+use android_security_authorization::aidl::android::security::authorization::{
+ IKeystoreAuthorization::IKeystoreAuthorization
+};
+use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::{
+ IKeystoreMaintenance,
+};
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, HardwareAuthToken::HardwareAuthToken,
+ HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel,
+ KeyPurpose::KeyPurpose
+};
+use android_system_keystore2::aidl::android::system::keystore2::{
+ CreateOperationResponse::CreateOperationResponse, Domain::Domain, KeyDescriptor::KeyDescriptor,
+ KeyMetadata::KeyMetadata,
+};
+use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
+ Timestamp::Timestamp,
+};
+use keystore2_test_utils::{
+ get_keystore_service, run_as, authorizations::AuthSetBuilder,
+};
+use log::{warn, info};
+use nix::unistd::{Gid, Uid};
+use rustutils::users::AID_USER_OFFSET;
+
+/// Test user ID.
+const TEST_USER_ID: i32 = 100;
+/// Fake password blob.
+static PASSWORD: &[u8] = &[
+ 0x42, 0x39, 0x30, 0x37, 0x44, 0x37, 0x32, 0x37, 0x39, 0x39, 0x43, 0x42, 0x39, 0x41, 0x42, 0x30,
+ 0x34, 0x31, 0x30, 0x38, 0x46, 0x44, 0x33, 0x45, 0x39, 0x42, 0x32, 0x38, 0x36, 0x35, 0x41, 0x36,
+ 0x33, 0x44, 0x42, 0x42, 0x43, 0x36, 0x33, 0x42, 0x34, 0x39, 0x37, 0x33, 0x35, 0x45, 0x41, 0x41,
+ 0x32, 0x45, 0x31, 0x35, 0x43, 0x43, 0x46, 0x32, 0x39, 0x36, 0x33, 0x34, 0x31, 0x32, 0x41, 0x39,
+];
+/// Fake SID value corresponding to Gatekeeper.
+static GK_SID: i64 = 123456;
+/// Fake SID value corresponding to a biometric authenticator.
+static BIO_SID1: i64 = 345678;
+/// Fake SID value corresponding to a biometric authenticator.
+static BIO_SID2: i64 = 456789;
+
+const WEAK_UNLOCK_ENABLED: bool = true;
+const WEAK_UNLOCK_DISABLED: bool = false;
+const UNFORCED: bool = false;
+
+fn get_authorization() -> binder::Strong<dyn IKeystoreAuthorization> {
+ binder::get_interface("android.security.authorization").unwrap()
+}
+
+fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> {
+ binder::get_interface("android.security.maintenance").unwrap()
+}
+
+fn abort_op(result: binder::Result<CreateOperationResponse>) {
+ if let Ok(rsp) = result {
+ if let Some(op) = rsp.iOperation {
+ if let Err(e) = op.abort() {
+ warn!("abort op failed: {e:?}");
+ }
+ } else {
+ warn!("can't abort op with missing iOperation");
+ }
+ } else {
+ warn!("can't abort failed op: {result:?}");
+ }
+}
+
+/// RAII structure to ensure that test users are removed at the end of a test.
+struct TestUser {
+ id: i32,
+ maint: binder::Strong<dyn IKeystoreMaintenance>,
+}
+
+impl TestUser {
+ fn new() -> Self {
+ Self::new_user(TEST_USER_ID, PASSWORD)
+ }
+ fn new_user(user_id: i32, password: &[u8]) -> Self {
+ let maint = get_maintenance();
+ maint.onUserAdded(user_id).expect("failed to add test user");
+ maint
+ .initUserSuperKeys(user_id, password, /* allowExisting= */ false)
+ .expect("failed to init test user");
+ Self { id: user_id, maint }
+ }
+}
+
+impl Drop for TestUser {
+ fn drop(&mut self) {
+ let _ = self.maint.onUserRemoved(self.id);
+ }
+}
+
+#[test]
+fn keystore2_test_unlocked_device_required() {
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("keystore2_client_tests")
+ .with_max_level(log::LevelFilter::Debug),
+ );
+ static CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20";
+ const UID: u32 = TEST_USER_ID as u32 * AID_USER_OFFSET + 1001;
+
+ // Safety: only one thread at this point, and nothing yet done with binder.
+ let mut child_handle = unsafe {
+ // Perform keystore actions while running as the test user.
+ run_as::run_as_child(
+ CTX,
+ Uid::from_raw(UID),
+ Gid::from_raw(UID),
+ move |reader, writer| -> Result<(), String> {
+ // Now we're in a new process, wait to be notified before starting.
+ reader.recv();
+
+ // Action A: create a new unlocked-device-required key (which thus requires
+ // super-encryption), while the device is unlocked.
+ let ks2 = get_keystore_service();
+ let sec_level = ks2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap();
+ let params = AuthSetBuilder::new()
+ .unlocked_device_required()
+ .algorithm(Algorithm::EC)
+ .purpose(KeyPurpose::SIGN)
+ .purpose(KeyPurpose::VERIFY)
+ .digest(Digest::SHA_2_256)
+ .ec_curve(EcCurve::P_256);
+
+ let KeyMetadata { key, .. } = sec_level
+ .generateKey(
+ &KeyDescriptor {
+ domain: Domain::APP,
+ nspace: -1,
+ alias: Some("unlocked-device-required".to_string()),
+ blob: None,
+ },
+ None,
+ ¶ms,
+ 0,
+ b"entropy",
+ )
+ .expect("key generation failed");
+ info!("A: created unlocked-device-required key while unlocked {key:?}");
+ writer.send(&BarrierReached {}); // A done.
+
+ // Action B: fail to use the unlocked-device-required key while locked.
+ reader.recv();
+ let params =
+ AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256);
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ info!("B: use unlocked-device-required key while locked => {result:?}");
+ assert!(result.is_err());
+ writer.send(&BarrierReached {}); // B done.
+
+ // Action C: try to use the unlocked-device-required key while unlocked with a
+ // password.
+ reader.recv();
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ info!("C: use unlocked-device-required key while lskf-unlocked => {result:?}");
+ assert!(result.is_ok(), "failed with {result:?}");
+ abort_op(result);
+ writer.send(&BarrierReached {}); // C done.
+
+ // Action D: try to use the unlocked-device-required key while unlocked with a weak
+ // biometric.
+ reader.recv();
+ let result = sec_level.createOperation(&key, ¶ms, UNFORCED);
+ info!("D: use unlocked-device-required key while weak-locked => {result:?}");
+ assert!(result.is_ok(), "createOperation failed: {result:?}");
+ abort_op(result);
+ writer.send(&BarrierReached {}); // D done.
+
+ let _ = sec_level.deleteKey(&key);
+ Ok(())
+ },
+ )
+ }
+ .unwrap();
+
+ // Now that the separate process has been forked off, it's safe to use binder.
+ let user = TestUser::new();
+ let user_id = user.id;
+ let auth_service = get_authorization();
+
+ // Lock and unlock to ensure super keys are already created.
+ auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_DISABLED).unwrap();
+ auth_service.onDeviceUnlocked(user_id, Some(PASSWORD)).unwrap();
+ auth_service.addAuthToken(&fake_lskf_token(GK_SID)).unwrap();
+
+ info!("trigger child process action A while unlocked and wait for completion");
+ child_handle.send(&BarrierReached {});
+ child_handle.recv();
+
+ // Move to locked and don't allow weak unlock, so super keys are wiped.
+ auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_DISABLED).unwrap();
+
+ info!("trigger child process action B while locked and wait for completion");
+ child_handle.send(&BarrierReached {});
+ child_handle.recv();
+
+ // Unlock with password => loads super key from database.
+ auth_service.onDeviceUnlocked(user_id, Some(PASSWORD)).unwrap();
+ auth_service.addAuthToken(&fake_lskf_token(GK_SID)).unwrap();
+
+ info!("trigger child process action C while lskf-unlocked and wait for completion");
+ child_handle.send(&BarrierReached {});
+ child_handle.recv();
+
+ // Move to locked and allow weak unlock, then do a weak unlock.
+ auth_service.onDeviceLocked(user_id, &[BIO_SID1, BIO_SID2], WEAK_UNLOCK_ENABLED).unwrap();
+ auth_service.onDeviceUnlocked(user_id, None).unwrap();
+
+ info!("trigger child process action D while weak-unlocked and wait for completion");
+ child_handle.send(&BarrierReached {});
+ child_handle.recv();
+
+ assert_eq!(child_handle.get_result(), Ok(()), "child process failed");
+}
+
+/// Generate a fake [`HardwareAuthToken`] for the given sid.
+fn fake_lskf_token(gk_sid: i64) -> HardwareAuthToken {
+ HardwareAuthToken {
+ challenge: 0,
+ userId: gk_sid,
+ authenticatorId: 0,
+ authenticatorType: HardwareAuthenticatorType::PASSWORD,
+ timestamp: Timestamp { milliSeconds: 123 },
+ mac: vec![1, 2, 3],
+ }
+}