diff options
Diffstat (limited to 'keystore2')
79 files changed, 8031 insertions, 8514 deletions
diff --git a/keystore2/Android.bp b/keystore2/Android.bp index 7cb7c37a..28bdfea9 100644 --- a/keystore2/Android.bp +++ b/keystore2/Android.bp @@ -28,6 +28,7 @@ rust_defaults { defaults: [ "keymint_use_latest_hal_aidl_rust", "keystore2_use_latest_aidl_rust", + "structured_log_rust_defaults", ], rustlibs: [ @@ -54,7 +55,6 @@ rust_defaults { "libkeystore2_selinux", "liblazy_static", "liblibc", - "liblog_event_list", "liblog_rust", "libmessage_macro", "librand", @@ -162,6 +162,11 @@ aconfig_declarations { srcs: ["aconfig/flags.aconfig"], } +java_aconfig_library { + name: "keystore2_flags_java", + aconfig_declarations: "keystore2_flags", +} + rust_aconfig_library { name: "libkeystore2_flags_rust", crate_name: "keystore2_flags", diff --git a/keystore2/OWNERS b/keystore2/OWNERS index 6b1a95bd..bf9d61b3 100644 --- a/keystore2/OWNERS +++ b/keystore2/OWNERS @@ -5,5 +5,4 @@ drysdale@google.com hasinitg@google.com jbires@google.com sethmo@google.com -trong@google.com swillden@google.com diff --git a/keystore2/aconfig/flags.aconfig b/keystore2/aconfig/flags.aconfig index b67bc6cb..05dae462 100644 --- a/keystore2/aconfig/flags.aconfig +++ b/keystore2/aconfig/flags.aconfig @@ -18,9 +18,17 @@ flag { } flag { + name: "disable_legacy_keystore_get" + namespace: "hardware_backed_security" + description: "This flag disables legacy keystore get and makes it so that get returns an error" + bug: "307460850" + 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" bug: "283077822" is_fixed_read_only: true -}
\ No newline at end of file +} diff --git a/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl b/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl index 5b22be01..277b9dd8 100644 --- a/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl +++ b/keystore2/aidl/android/security/apc/IConfirmationCallback.aidl @@ -27,10 +27,6 @@ interface IConfirmationCallback { /** * This callback gets called by the implementing service when a pending confirmation prompt * gets finalized. - * @deprecated Android Protected Confirmation had a low adoption rate among Android device - * makers and developers alike. Given the lack of devices supporting the feature, - * it is deprecated. Developers can use auth-bound Keystore keys as a partial - * replacement. * * @param result * - ResponseCode.OK On success. In this case dataConfirmed must be non null. diff --git a/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl b/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl index 9f978479..3162224f 100644 --- a/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl +++ b/keystore2/aidl/android/security/apc/IProtectedConfirmation.aidl @@ -35,10 +35,6 @@ interface IProtectedConfirmation { /** * Present the confirmation prompt. The caller must implement IConfirmationCallback and pass * it to this function as listener. - * @deprecated Android Protected Confirmation had a low adoption rate among Android device - * makers and developers alike. Given the lack of devices supporting the - * feature, it is deprecated. Developers can use auth-bound Keystore keys - * as a partial replacement. * * @param listener Must implement IConfirmationCallback. Doubles as session identifier when * passed to cancelPrompt. @@ -59,11 +55,6 @@ interface IProtectedConfirmation { /** * Cancel an ongoing prompt. - * @deprecated Android Protected Confirmation had a low adoption rate among Android device - * makers and developers alike. Given the lack of devices supporting the - * feature, it is deprecated. Developers can use auth-bound Keystore keys as - * a partial replacement. - * * * @param listener Must implement IConfirmationCallback, although in this context this binder * token is only used to identify the session that is to be cancelled. @@ -75,10 +66,6 @@ interface IProtectedConfirmation { /** * Returns true if the device supports Android Protected Confirmation. - * @deprecated Android Protected Confirmation had a low adoption rate among Android device - * makers and developers alike. Given the lack of devices supporting the - * feature, it is deprecated. Developers can use auth-bound Keystore keys - * as a partial replacement. */ boolean isSupported(); } diff --git a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl index a9de0263..fd532f62 100644 --- a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl +++ b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl @@ -18,8 +18,6 @@ import android.hardware.security.keymint.HardwareAuthToken; import android.hardware.security.keymint.HardwareAuthenticatorType; import android.security.authorization.AuthorizationTokens; -// TODO: mark the interface with @SensitiveData when the annotation is ready (b/176110256). - /** * IKeystoreAuthorization interface exposes the methods for other system components to * provide keystore with the information required to enforce authorizations on key usage. diff --git a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl index abea9582..ecc1f4b1 100644 --- a/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl +++ b/keystore2/aidl/android/security/maintenance/IKeystoreMaintenance.aidl @@ -77,21 +77,6 @@ interface IKeystoreMaintenance { void onUserLskfRemoved(in int userId); /** - * Allows LockSettingsService to inform keystore about password change of a user. - * Callers require 'ChangePassword' permission. - * - * ## Error conditions: - * `ResponseCode::PERMISSION_DENIED` - if the callers does not have the 'ChangePassword' - * permission. - * `ResponseCode::SYSTEM_ERROR` - if failed to delete the super encrypted keys of the user. - * `ResponseCode::Locked' - if the keystore is locked for the given user. - * - * @param userId - Android user id - * @param password - a secret derived from the synthetic password of the user - */ - void onUserPasswordChanged(in int userId, in @nullable byte[] password); - - /** * This function deletes all keys within a namespace. It mainly gets called when an app gets * removed and all resources of this app need to be cleaned up. * @@ -112,16 +97,6 @@ interface IKeystoreMaintenance { void earlyBootEnded(); /** - * Informs Keystore 2.0 that the an off body event was detected. - * - * ## Error conditions: - * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the `ReportOffBody` - * permission. - * `ResponseCode::SYSTEM_ERROR` - if an unexpected error occurred. - */ - void onDeviceOffBody(); - - /** * Migrate a key from one namespace to another. The caller must have use, grant, and delete * permissions on the source namespace and rebind permissions on the destination namespace. * The source may be specified by Domain::APP, Domain::SELINUX, or Domain::KEY_ID. The target diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs index f7a81983..b173da83 100644 --- a/keystore2/legacykeystore/lib.rs +++ b/keystore2/legacykeystore/lib.rs @@ -55,7 +55,7 @@ impl DB { F: Fn(&Transaction) -> Result<T>, { loop { - match self + let result = self .conn .transaction_with_behavior(behavior) .context("In with_transaction.") @@ -63,7 +63,8 @@ impl DB { .and_then(|(result, tx)| { tx.commit().context("In with_transaction: Failed to commit transaction.")?; Ok(result) - }) { + }); + match result { Ok(result) => break Ok(result), Err(e) => { if Self::is_locked_error(&e) { @@ -133,6 +134,7 @@ impl DB { } fn get(&mut self, caller_uid: u32, alias: &str) -> Result<Option<Vec<u8>>> { + ensure_keystore_get_is_enabled()?; self.with_transaction(TransactionBehavior::Deferred, |tx| { tx.query_row( "SELECT profile FROM profiles WHERE owner = ? AND alias = ?;", @@ -209,41 +211,22 @@ impl Error { } } -/// This function should be used by legacykeystore service calls to translate error conditions -/// into service specific exceptions. +/// Translate an error into a service specific exception, logging along the way. /// -/// All error conditions get logged by this function, except for ERROR_ENTRY_NOT_FOUND error. -/// -/// `Error::Error(x)` variants get mapped onto a service specific error code of `x`. -/// -/// All non `Error` error conditions get mapped onto `ERROR_SYSTEM_ERROR`. -/// -/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed -/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it -/// typically returns Ok(value). -fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> -where - F: FnOnce(U) -> BinderResult<T>, -{ - result.map_or_else( - |e| { - let root_cause = e.root_cause(); - let (rc, log_error) = match root_cause.downcast_ref::<Error>() { - // Make the entry not found errors silent. - Some(Error::Error(ERROR_ENTRY_NOT_FOUND)) => (ERROR_ENTRY_NOT_FOUND, false), - Some(Error::Error(e)) => (*e, true), - Some(Error::Binder(_, _)) | None => (ERROR_SYSTEM_ERROR, true), - }; - if log_error { - log::error!("{:?}", e); - } - Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )) - }, - handle_ok, - ) +/// `Error::Error(x)` variants get mapped onto a service specific error code of `x`, other errors +/// are mapped to `ERROR_SYSTEM_ERROR`. +fn into_logged_binder(e: anyhow::Error) -> BinderStatus { + let root_cause = e.root_cause(); + let (rc, log_error) = match root_cause.downcast_ref::<Error>() { + // Make the entry not found errors silent. + Some(Error::Error(ERROR_ENTRY_NOT_FOUND)) => (ERROR_ENTRY_NOT_FOUND, false), + Some(Error::Error(e)) => (*e, true), + Some(Error::Binder(_, _)) | None => (ERROR_SYSTEM_ERROR, true), + }; + if log_error { + log::error!("{:?}", e); + } + BinderStatus::new_service_specific_error(rc, anyhow_error_to_cstring(&e).as_deref()) } fn ensure_keystore_put_is_enabled() -> Result<()> { @@ -257,6 +240,17 @@ fn ensure_keystore_put_is_enabled() -> Result<()> { } } +fn ensure_keystore_get_is_enabled() -> Result<()> { + if keystore2_flags::disable_legacy_keystore_get() { + Err(Error::deprecated()).context(concat!( + "Retrieving from Keystore's legacy database is ", + "no longer supported, store in an app-specific database instead" + )) + } else { + Ok(()) + } +} + struct LegacyKeystoreDeleteListener { legacy_keystore: Arc<LegacyKeystore>, } @@ -331,6 +325,7 @@ impl LegacyKeystore { } fn get(&self, alias: &str, uid: i32) -> Result<Vec<u8>> { + ensure_keystore_get_is_enabled()?; let mut db = self.open_db().context("In get.")?; let uid = Self::get_effective_uid(uid).context("In get.")?; @@ -550,20 +545,20 @@ impl binder::Interface for LegacyKeystoreService {} impl ILegacyKeystore for LegacyKeystoreService { fn get(&self, alias: &str, uid: i32) -> BinderResult<Vec<u8>> { - let _wp = wd::watch_millis("ILegacyKeystore::get", 500); - map_or_log_err(self.legacy_keystore.get(alias, uid), Ok) + let _wp = wd::watch("ILegacyKeystore::get"); + self.legacy_keystore.get(alias, uid).map_err(into_logged_binder) } fn put(&self, alias: &str, uid: i32, entry: &[u8]) -> BinderResult<()> { - let _wp = wd::watch_millis("ILegacyKeystore::put", 500); - map_or_log_err(self.legacy_keystore.put(alias, uid, entry), Ok) + let _wp = wd::watch("ILegacyKeystore::put"); + self.legacy_keystore.put(alias, uid, entry).map_err(into_logged_binder) } fn remove(&self, alias: &str, uid: i32) -> BinderResult<()> { - let _wp = wd::watch_millis("ILegacyKeystore::remove", 500); - map_or_log_err(self.legacy_keystore.remove(alias, uid), Ok) + let _wp = wd::watch("ILegacyKeystore::remove"); + self.legacy_keystore.remove(alias, uid).map_err(into_logged_binder) } fn list(&self, prefix: &str, uid: i32) -> BinderResult<Vec<String>> { - let _wp = wd::watch_millis("ILegacyKeystore::list", 500); - map_or_log_err(self.legacy_keystore.list(prefix, uid), Ok) + let _wp = wd::watch("ILegacyKeystore::list"); + self.legacy_keystore.list(prefix, uid).map_err(into_logged_binder) } } diff --git a/keystore2/rkpd_client/src/lib.rs b/keystore2/rkpd_client/src/lib.rs index d8a5276c..936fe3d6 100644 --- a/keystore2/rkpd_client/src/lib.rs +++ b/keystore2/rkpd_client/src/lib.rs @@ -310,331 +310,4 @@ pub fn store_rkpd_attestation_key( } #[cfg(test)] -mod tests { - use super::*; - use android_security_rkp_aidl::aidl::android::security::rkp::IRegistration::BnRegistration; - use std::sync::atomic::{AtomicU32, Ordering}; - use std::sync::{Arc, Mutex}; - - const DEFAULT_RPC_SERVICE_NAME: &str = - "android.hardware.security.keymint.IRemotelyProvisionedComponent/default"; - - struct MockRegistrationValues { - key: RemotelyProvisionedKey, - latency: Option<Duration>, - thread_join_handles: Vec<Option<std::thread::JoinHandle<()>>>, - } - - struct MockRegistration(Arc<Mutex<MockRegistrationValues>>); - - impl MockRegistration { - pub fn new_native_binder( - key: &RemotelyProvisionedKey, - latency: Option<Duration>, - ) -> Strong<dyn IRegistration> { - let result = Self(Arc::new(Mutex::new(MockRegistrationValues { - key: RemotelyProvisionedKey { - keyBlob: key.keyBlob.clone(), - encodedCertChain: key.encodedCertChain.clone(), - }, - latency, - thread_join_handles: Vec::new(), - }))); - BnRegistration::new_binder(result, BinderFeatures::default()) - } - } - - impl Drop for MockRegistration { - fn drop(&mut self) { - let mut values = self.0.lock().unwrap(); - for handle in values.thread_join_handles.iter_mut() { - // These are test threads. So, no need to worry too much about error handling. - handle.take().unwrap().join().unwrap(); - } - } - } - - impl Interface for MockRegistration {} - - impl IRegistration for MockRegistration { - fn getKey(&self, _: i32, cb: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> { - let mut values = self.0.lock().unwrap(); - let key = RemotelyProvisionedKey { - keyBlob: values.key.keyBlob.clone(), - encodedCertChain: values.key.encodedCertChain.clone(), - }; - let latency = values.latency; - let get_key_cb = cb.clone(); - - // Need a separate thread to trigger timeout in the caller. - let join_handle = std::thread::spawn(move || { - if let Some(duration) = latency { - std::thread::sleep(duration); - } - get_key_cb.onSuccess(&key).unwrap(); - }); - values.thread_join_handles.push(Some(join_handle)); - Ok(()) - } - - fn cancelGetKey(&self, _: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> { - Ok(()) - } - - fn storeUpgradedKeyAsync( - &self, - _: &[u8], - _: &[u8], - cb: &Strong<dyn IStoreUpgradedKeyCallback>, - ) -> binder::Result<()> { - // We are primarily concerned with timing out correctly. Storing the key in this mock - // registration isn't particularly interesting, so skip that part. - let values = self.0.lock().unwrap(); - let store_cb = cb.clone(); - let latency = values.latency; - - std::thread::spawn(move || { - if let Some(duration) = latency { - std::thread::sleep(duration); - } - store_cb.onSuccess().unwrap(); - }); - Ok(()) - } - } - - fn get_mock_registration( - key: &RemotelyProvisionedKey, - latency: Option<Duration>, - ) -> Result<binder::Strong<dyn IRegistration>> { - let (tx, rx) = oneshot::channel(); - let cb = GetRegistrationCallback::new_native_binder(tx); - let mock_registration = MockRegistration::new_native_binder(key, latency); - - assert!(cb.onSuccess(&mock_registration).is_ok()); - tokio_rt().block_on(rx).unwrap() - } - - // Using the same key ID makes test cases race with each other. So, we use separate key IDs for - // different test cases. - fn get_next_key_id() -> u32 { - static ID: AtomicU32 = AtomicU32::new(0); - ID.fetch_add(1, Ordering::Relaxed) - } - - #[test] - fn test_get_registration_cb_success() { - let key: RemotelyProvisionedKey = Default::default(); - let registration = get_mock_registration(&key, /*latency=*/ None); - assert!(registration.is_ok()); - } - - #[test] - fn test_get_registration_cb_cancel() { - let (tx, rx) = oneshot::channel(); - let cb = GetRegistrationCallback::new_native_binder(tx); - assert!(cb.onCancel().is_ok()); - - let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled); - } - - #[test] - fn test_get_registration_cb_error() { - let (tx, rx) = oneshot::channel(); - let cb = GetRegistrationCallback::new_native_binder(tx); - assert!(cb.onError("error").is_ok()); - - let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::GetRegistrationFailed); - } - - #[test] - fn test_get_key_cb_success() { - let mock_key = - RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; - let (tx, rx) = oneshot::channel(); - let cb = GetKeyCallback::new_native_binder(tx); - assert!(cb.onSuccess(&mock_key).is_ok()); - - let key = tokio_rt().block_on(rx).unwrap().unwrap(); - assert_eq!(key, mock_key); - } - - #[test] - fn test_get_key_cb_cancel() { - let (tx, rx) = oneshot::channel(); - let cb = GetKeyCallback::new_native_binder(tx); - assert!(cb.onCancel().is_ok()); - - let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled); - } - - #[test] - fn test_get_key_cb_error() { - for get_key_error in GetKeyErrorCode::enum_values() { - let (tx, rx) = oneshot::channel(); - let cb = GetKeyCallback::new_native_binder(tx); - assert!(cb.onError(get_key_error, "error").is_ok()); - - let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!( - result.unwrap_err().downcast::<Error>().unwrap(), - Error::GetKeyFailed(get_key_error), - ); - } - } - - #[test] - fn test_store_upgraded_cb_success() { - let (tx, rx) = oneshot::channel(); - let cb = StoreUpgradedKeyCallback::new_native_binder(tx); - assert!(cb.onSuccess().is_ok()); - - tokio_rt().block_on(rx).unwrap().unwrap(); - } - - #[test] - fn test_store_upgraded_key_cb_error() { - let (tx, rx) = oneshot::channel(); - let cb = StoreUpgradedKeyCallback::new_native_binder(tx); - assert!(cb.onError("oh no! it failed").is_ok()); - - let result = tokio_rt().block_on(rx).unwrap(); - assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::StoreUpgradedKeyFailed); - } - - #[test] - fn test_get_mock_key_success() { - let mock_key = - RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; - let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap(); - - let key = tokio_rt() - .block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0)) - .unwrap(); - assert_eq!(key, mock_key); - } - - #[test] - fn test_get_mock_key_timeout() { - let mock_key = - RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; - let latency = RKPD_TIMEOUT + Duration::from_secs(1); - let registration = get_mock_registration(&mock_key, Some(latency)).unwrap(); - - let result = - tokio_rt().block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0)); - assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RetryableTimeout); - } - - #[test] - fn test_store_mock_key_success() { - let mock_key = - RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; - let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap(); - tokio_rt() - .block_on(store_rkpd_attestation_key_with_registration_async(®istration, &[], &[])) - .unwrap(); - } - - #[test] - fn test_store_mock_key_timeout() { - let mock_key = - RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; - let latency = RKPD_TIMEOUT + Duration::from_secs(1); - let registration = get_mock_registration(&mock_key, Some(latency)).unwrap(); - - let result = tokio_rt().block_on(store_rkpd_attestation_key_with_registration_async( - ®istration, - &[], - &[], - )); - assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::Timeout); - } - - #[test] - fn test_get_rkpd_attestation_key() { - binder::ProcessState::start_thread_pool(); - let key_id = get_next_key_id(); - let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); - assert!(!key.keyBlob.is_empty()); - assert!(!key.encodedCertChain.is_empty()); - } - - #[test] - fn test_get_rkpd_attestation_key_same_caller() { - binder::ProcessState::start_thread_pool(); - let key_id = get_next_key_id(); - - // Multiple calls should return the same key. - let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); - let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); - - assert_eq!(first_key.keyBlob, second_key.keyBlob); - assert_eq!(first_key.encodedCertChain, second_key.encodedCertChain); - } - - #[test] - fn test_get_rkpd_attestation_key_different_caller() { - binder::ProcessState::start_thread_pool(); - let first_key_id = get_next_key_id(); - let second_key_id = get_next_key_id(); - - // Different callers should be getting different keys. - let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, first_key_id).unwrap(); - let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, second_key_id).unwrap(); - - assert_ne!(first_key.keyBlob, second_key.keyBlob); - assert_ne!(first_key.encodedCertChain, second_key.encodedCertChain); - } - - #[test] - // Couple of things to note: - // 1. This test must never run with UID of keystore. Otherwise, it can mess up keys stored by - // keystore. - // 2. Storing and reading the stored key is prone to race condition. So, we only do this in one - // test case. - fn test_store_rkpd_attestation_key() { - binder::ProcessState::start_thread_pool(); - let key_id = get_next_key_id(); - let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); - let new_blob: [u8; 8] = rand::random(); - - assert!( - store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &key.keyBlob, &new_blob).is_ok() - ); - - let new_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); - - // Restore original key so that we don't leave RKPD with invalid blobs. - assert!( - store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &new_blob, &key.keyBlob).is_ok() - ); - assert_eq!(new_key.keyBlob, new_blob); - } - - #[test] - fn test_stress_get_rkpd_attestation_key() { - binder::ProcessState::start_thread_pool(); - let key_id = get_next_key_id(); - let mut threads = vec![]; - const NTHREADS: u32 = 10; - const NCALLS: u32 = 1000; - - for _ in 0..NTHREADS { - threads.push(std::thread::spawn(move || { - for _ in 0..NCALLS { - let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); - assert!(!key.keyBlob.is_empty()); - assert!(!key.encodedCertChain.is_empty()); - } - })); - } - - for t in threads { - assert!(t.join().is_ok()); - } - } -} +mod tests; diff --git a/keystore2/rkpd_client/src/tests.rs b/keystore2/rkpd_client/src/tests.rs new file mode 100644 index 00000000..fd0468f7 --- /dev/null +++ b/keystore2/rkpd_client/src/tests.rs @@ -0,0 +1,338 @@ +// 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. + +//! RKPD tests. + +use super::*; +use android_security_rkp_aidl::aidl::android::security::rkp::IRegistration::BnRegistration; +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::{Arc, Mutex}; + +const DEFAULT_RPC_SERVICE_NAME: &str = + "android.hardware.security.keymint.IRemotelyProvisionedComponent/default"; + +struct MockRegistrationValues { + key: RemotelyProvisionedKey, + latency: Option<Duration>, + thread_join_handles: Vec<Option<std::thread::JoinHandle<()>>>, +} + +struct MockRegistration(Arc<Mutex<MockRegistrationValues>>); + +impl MockRegistration { + pub fn new_native_binder( + key: &RemotelyProvisionedKey, + latency: Option<Duration>, + ) -> Strong<dyn IRegistration> { + let result = Self(Arc::new(Mutex::new(MockRegistrationValues { + key: RemotelyProvisionedKey { + keyBlob: key.keyBlob.clone(), + encodedCertChain: key.encodedCertChain.clone(), + }, + latency, + thread_join_handles: Vec::new(), + }))); + BnRegistration::new_binder(result, BinderFeatures::default()) + } +} + +impl Drop for MockRegistration { + fn drop(&mut self) { + let mut values = self.0.lock().unwrap(); + for handle in values.thread_join_handles.iter_mut() { + // These are test threads. So, no need to worry too much about error handling. + handle.take().unwrap().join().unwrap(); + } + } +} + +impl Interface for MockRegistration {} + +impl IRegistration for MockRegistration { + fn getKey(&self, _: i32, cb: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> { + let mut values = self.0.lock().unwrap(); + let key = RemotelyProvisionedKey { + keyBlob: values.key.keyBlob.clone(), + encodedCertChain: values.key.encodedCertChain.clone(), + }; + let latency = values.latency; + let get_key_cb = cb.clone(); + + // Need a separate thread to trigger timeout in the caller. + let join_handle = std::thread::spawn(move || { + if let Some(duration) = latency { + std::thread::sleep(duration); + } + get_key_cb.onSuccess(&key).unwrap(); + }); + values.thread_join_handles.push(Some(join_handle)); + Ok(()) + } + + fn cancelGetKey(&self, _: &Strong<dyn IGetKeyCallback>) -> binder::Result<()> { + Ok(()) + } + + fn storeUpgradedKeyAsync( + &self, + _: &[u8], + _: &[u8], + cb: &Strong<dyn IStoreUpgradedKeyCallback>, + ) -> binder::Result<()> { + // We are primarily concerned with timing out correctly. Storing the key in this mock + // registration isn't particularly interesting, so skip that part. + let values = self.0.lock().unwrap(); + let store_cb = cb.clone(); + let latency = values.latency; + + std::thread::spawn(move || { + if let Some(duration) = latency { + std::thread::sleep(duration); + } + store_cb.onSuccess().unwrap(); + }); + Ok(()) + } +} + +fn get_mock_registration( + key: &RemotelyProvisionedKey, + latency: Option<Duration>, +) -> Result<binder::Strong<dyn IRegistration>> { + let (tx, rx) = oneshot::channel(); + let cb = GetRegistrationCallback::new_native_binder(tx); + let mock_registration = MockRegistration::new_native_binder(key, latency); + + assert!(cb.onSuccess(&mock_registration).is_ok()); + tokio_rt().block_on(rx).unwrap() +} + +// Using the same key ID makes test cases race with each other. So, we use separate key IDs for +// different test cases. +fn get_next_key_id() -> u32 { + static ID: AtomicU32 = AtomicU32::new(0); + ID.fetch_add(1, Ordering::Relaxed) +} + +#[test] +fn test_get_registration_cb_success() { + let key: RemotelyProvisionedKey = Default::default(); + let registration = get_mock_registration(&key, /*latency=*/ None); + assert!(registration.is_ok()); +} + +#[test] +fn test_get_registration_cb_cancel() { + let (tx, rx) = oneshot::channel(); + let cb = GetRegistrationCallback::new_native_binder(tx); + assert!(cb.onCancel().is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled); +} + +#[test] +fn test_get_registration_cb_error() { + let (tx, rx) = oneshot::channel(); + let cb = GetRegistrationCallback::new_native_binder(tx); + assert!(cb.onError("error").is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::GetRegistrationFailed); +} + +#[test] +fn test_get_key_cb_success() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let (tx, rx) = oneshot::channel(); + let cb = GetKeyCallback::new_native_binder(tx); + assert!(cb.onSuccess(&mock_key).is_ok()); + + let key = tokio_rt().block_on(rx).unwrap().unwrap(); + assert_eq!(key, mock_key); +} + +#[test] +fn test_get_key_cb_cancel() { + let (tx, rx) = oneshot::channel(); + let cb = GetKeyCallback::new_native_binder(tx); + assert!(cb.onCancel().is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RequestCancelled); +} + +#[test] +fn test_get_key_cb_error() { + for get_key_error in GetKeyErrorCode::enum_values() { + let (tx, rx) = oneshot::channel(); + let cb = GetKeyCallback::new_native_binder(tx); + assert!(cb.onError(get_key_error, "error").is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!( + result.unwrap_err().downcast::<Error>().unwrap(), + Error::GetKeyFailed(get_key_error), + ); + } +} + +#[test] +fn test_store_upgraded_cb_success() { + let (tx, rx) = oneshot::channel(); + let cb = StoreUpgradedKeyCallback::new_native_binder(tx); + assert!(cb.onSuccess().is_ok()); + + tokio_rt().block_on(rx).unwrap().unwrap(); +} + +#[test] +fn test_store_upgraded_key_cb_error() { + let (tx, rx) = oneshot::channel(); + let cb = StoreUpgradedKeyCallback::new_native_binder(tx); + assert!(cb.onError("oh no! it failed").is_ok()); + + let result = tokio_rt().block_on(rx).unwrap(); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::StoreUpgradedKeyFailed); +} + +#[test] +fn test_get_mock_key_success() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap(); + + let key = tokio_rt() + .block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0)) + .unwrap(); + assert_eq!(key, mock_key); +} + +#[test] +fn test_get_mock_key_timeout() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let latency = RKPD_TIMEOUT + Duration::from_secs(1); + let registration = get_mock_registration(&mock_key, Some(latency)).unwrap(); + + let result = + tokio_rt().block_on(get_rkpd_attestation_key_from_registration_async(®istration, 0)); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::RetryableTimeout); +} + +#[test] +fn test_store_mock_key_success() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let registration = get_mock_registration(&mock_key, /*latency=*/ None).unwrap(); + tokio_rt() + .block_on(store_rkpd_attestation_key_with_registration_async(®istration, &[], &[])) + .unwrap(); +} + +#[test] +fn test_store_mock_key_timeout() { + let mock_key = + RemotelyProvisionedKey { keyBlob: vec![1, 2, 3], encodedCertChain: vec![4, 5, 6] }; + let latency = RKPD_TIMEOUT + Duration::from_secs(1); + let registration = get_mock_registration(&mock_key, Some(latency)).unwrap(); + + let result = tokio_rt().block_on(store_rkpd_attestation_key_with_registration_async( + ®istration, + &[], + &[], + )); + assert_eq!(result.unwrap_err().downcast::<Error>().unwrap(), Error::Timeout); +} + +#[test] +fn test_get_rkpd_attestation_key() { + binder::ProcessState::start_thread_pool(); + let key_id = get_next_key_id(); + let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); + assert!(!key.keyBlob.is_empty()); + assert!(!key.encodedCertChain.is_empty()); +} + +#[test] +fn test_get_rkpd_attestation_key_same_caller() { + binder::ProcessState::start_thread_pool(); + let key_id = get_next_key_id(); + + // Multiple calls should return the same key. + let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); + let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); + + assert_eq!(first_key.keyBlob, second_key.keyBlob); + assert_eq!(first_key.encodedCertChain, second_key.encodedCertChain); +} + +#[test] +fn test_get_rkpd_attestation_key_different_caller() { + binder::ProcessState::start_thread_pool(); + let first_key_id = get_next_key_id(); + let second_key_id = get_next_key_id(); + + // Different callers should be getting different keys. + let first_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, first_key_id).unwrap(); + let second_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, second_key_id).unwrap(); + + assert_ne!(first_key.keyBlob, second_key.keyBlob); + assert_ne!(first_key.encodedCertChain, second_key.encodedCertChain); +} + +#[test] +// Couple of things to note: +// 1. This test must never run with UID of keystore. Otherwise, it can mess up keys stored by +// keystore. +// 2. Storing and reading the stored key is prone to race condition. So, we only do this in one +// test case. +fn test_store_rkpd_attestation_key() { + binder::ProcessState::start_thread_pool(); + let key_id = get_next_key_id(); + let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); + let new_blob: [u8; 8] = rand::random(); + + assert!(store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &key.keyBlob, &new_blob).is_ok()); + + let new_key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); + + // Restore original key so that we don't leave RKPD with invalid blobs. + assert!(store_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, &new_blob, &key.keyBlob).is_ok()); + assert_eq!(new_key.keyBlob, new_blob); +} + +#[test] +fn test_stress_get_rkpd_attestation_key() { + binder::ProcessState::start_thread_pool(); + let key_id = get_next_key_id(); + let mut threads = vec![]; + const NTHREADS: u32 = 10; + const NCALLS: u32 = 1000; + + for _ in 0..NTHREADS { + threads.push(std::thread::spawn(move || { + for _ in 0..NCALLS { + let key = get_rkpd_attestation_key(DEFAULT_RPC_SERVICE_NAME, key_id).unwrap(); + assert!(!key.keyBlob.is_empty()); + assert!(!key.encodedCertChain.is_empty()); + } + })); + } + + for t in threads { + assert!(t.join().is_ok()); + } +} diff --git a/keystore2/src/apc.rs b/keystore2/src/apc.rs index fbf94649..fc36a0c1 100644 --- a/keystore2/src/apc.rs +++ b/keystore2/src/apc.rs @@ -62,16 +62,6 @@ impl Error { Error::Rc(ResponseCode::OPERATION_PENDING) } - /// Short hand for `Error::Rc(ResponseCode::CANCELLED)` - pub fn cancelled() -> Self { - Error::Rc(ResponseCode::CANCELLED) - } - - /// Short hand for `Error::Rc(ResponseCode::ABORTED)` - pub fn aborted() -> Self { - Error::Rc(ResponseCode::ABORTED) - } - /// Short hand for `Error::Rc(ResponseCode::IGNORED)` pub fn ignored() -> Self { Error::Rc(ResponseCode::IGNORED) @@ -83,42 +73,24 @@ impl Error { } } -/// This function should be used by confirmation service calls to translate error conditions -/// into service specific exceptions. -/// -/// All error conditions get logged by this function. +/// Translate an error into a service-specific exception, logging along the way. /// /// `Error::Rc(x)` variants get mapped onto a service specific error code of `x`. /// `selinux::Error::perm()` is mapped on `ResponseCode::PERMISSION_DENIED`. /// /// All non `Error` error conditions get mapped onto ResponseCode::SYSTEM_ERROR`. -/// -/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed -/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it -/// typically returns Ok(value). -pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> -where - F: FnOnce(U) -> BinderResult<T>, -{ - result.map_or_else( - |e| { - log::error!("{:#?}", e); - let root_cause = e.root_cause(); - let rc = match root_cause.downcast_ref::<Error>() { - Some(Error::Rc(rcode)) => rcode.0, - Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0, - None => match root_cause.downcast_ref::<selinux::Error>() { - Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0, - _ => ResponseCode::SYSTEM_ERROR.0, - }, - }; - Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )) +pub fn into_logged_binder(e: anyhow::Error) -> BinderStatus { + log::error!("{:#?}", e); + let root_cause = e.root_cause(); + let rc = match root_cause.downcast_ref::<Error>() { + Some(Error::Rc(rcode)) => rcode.0, + Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0, + None => match root_cause.downcast_ref::<selinux::Error>() { + Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0, + _ => ResponseCode::SYSTEM_ERROR.0, }, - handle_ok, - ) + }; + BinderStatus::new_service_specific_error(rc, anyhow_error_to_cstring(&e).as_deref()) } /// Rate info records how many failed attempts a client has made to display a protected @@ -364,20 +336,18 @@ impl IProtectedConfirmation for ApcManager { ) -> BinderResult<()> { // presentPrompt can take more time than other operations. let _wp = wd::watch_millis("IProtectedConfirmation::presentPrompt", 3000); - map_or_log_err( - self.present_prompt(listener, prompt_text, extra_data, locale, ui_option_flags), - Ok, - ) + self.present_prompt(listener, prompt_text, extra_data, locale, ui_option_flags) + .map_err(into_logged_binder) } fn cancelPrompt( &self, listener: &binder::Strong<dyn IConfirmationCallback>, ) -> BinderResult<()> { - let _wp = wd::watch_millis("IProtectedConfirmation::cancelPrompt", 500); - map_or_log_err(self.cancel_prompt(listener), Ok) + let _wp = wd::watch("IProtectedConfirmation::cancelPrompt"); + self.cancel_prompt(listener).map_err(into_logged_binder) } fn isSupported(&self) -> BinderResult<bool> { - let _wp = wd::watch_millis("IProtectedConfirmation::isSupported", 500); - map_or_log_err(Self::is_supported(), Ok) + let _wp = wd::watch("IProtectedConfirmation::isSupported"); + Self::is_supported().map_err(into_logged_binder) } } diff --git a/keystore2/src/async_task.rs b/keystore2/src/async_task.rs index 6548445f..16401a4e 100644 --- a/keystore2/src/async_task.rs +++ b/keystore2/src/async_task.rs @@ -27,6 +27,9 @@ use std::{ thread, }; +#[cfg(test)] +mod tests; + #[derive(Debug, PartialEq, Eq)] enum State { Exiting, @@ -256,279 +259,3 @@ impl AsyncTask { state.state = State::Running; } } - -#[cfg(test)] -mod tests { - use super::{AsyncTask, Shelf}; - use std::sync::{ - mpsc::{channel, sync_channel, RecvTimeoutError}, - Arc, - }; - use std::time::Duration; - - #[test] - fn test_shelf() { - let mut shelf = Shelf::default(); - - let s = "A string".to_string(); - assert_eq!(shelf.put(s), None); - - let s2 = "Another string".to_string(); - assert_eq!(shelf.put(s2), Some("A string".to_string())); - - // Put something of a different type on the shelf. - #[derive(Debug, PartialEq, Eq)] - struct Elf { - pub name: String, - } - let e1 = Elf { name: "Glorfindel".to_string() }; - assert_eq!(shelf.put(e1), None); - - // The String value is still on the shelf. - let s3 = shelf.get_downcast_ref::<String>().unwrap(); - assert_eq!(s3, "Another string"); - - // As is the Elf. - { - let e2 = shelf.get_downcast_mut::<Elf>().unwrap(); - assert_eq!(e2.name, "Glorfindel"); - e2.name = "Celeborn".to_string(); - } - - // Take the Elf off the shelf. - let e3 = shelf.remove_downcast_ref::<Elf>().unwrap(); - assert_eq!(e3.name, "Celeborn"); - - assert_eq!(shelf.remove_downcast_ref::<Elf>(), None); - - // No u64 value has been put on the shelf, so getting one gives the default value. - { - let i = shelf.get_mut::<u64>(); - assert_eq!(*i, 0); - *i = 42; - } - let i2 = shelf.get_downcast_ref::<u64>().unwrap(); - assert_eq!(*i2, 42); - - // No i32 value has ever been seen near the shelf. - assert_eq!(shelf.get_downcast_ref::<i32>(), None); - assert_eq!(shelf.get_downcast_mut::<i32>(), None); - assert_eq!(shelf.remove_downcast_ref::<i32>(), None); - } - - #[test] - fn test_async_task() { - let at = AsyncTask::default(); - - // First queue up a job that blocks until we release it, to avoid - // unpredictable synchronization. - let (start_sender, start_receiver) = channel(); - at.queue_hi(move |shelf| { - start_receiver.recv().unwrap(); - // Put a trace vector on the shelf - shelf.put(Vec::<String>::new()); - }); - - // Queue up some high-priority and low-priority jobs. - for i in 0..3 { - let j = i; - at.queue_lo(move |shelf| { - let trace = shelf.get_mut::<Vec<String>>(); - trace.push(format!("L{}", j)); - }); - let j = i; - at.queue_hi(move |shelf| { - let trace = shelf.get_mut::<Vec<String>>(); - trace.push(format!("H{}", j)); - }); - } - - // Finally queue up a low priority job that emits the trace. - let (trace_sender, trace_receiver) = channel(); - at.queue_lo(move |shelf| { - let trace = shelf.get_downcast_ref::<Vec<String>>().unwrap(); - trace_sender.send(trace.clone()).unwrap(); - }); - - // Ready, set, go. - start_sender.send(()).unwrap(); - let trace = trace_receiver.recv().unwrap(); - - assert_eq!(trace, vec!["H0", "H1", "H2", "L0", "L1", "L2"]); - } - - #[test] - fn test_async_task_chain() { - let at = Arc::new(AsyncTask::default()); - let (sender, receiver) = channel(); - // Queue up a job that will queue up another job. This confirms - // that the job is not invoked with any internal AsyncTask locks held. - let at_clone = at.clone(); - at.queue_hi(move |_shelf| { - at_clone.queue_lo(move |_shelf| { - sender.send(()).unwrap(); - }); - }); - receiver.recv().unwrap(); - } - - #[test] - #[should_panic] - fn test_async_task_panic() { - let at = AsyncTask::default(); - at.queue_hi(|_shelf| { - panic!("Panic from queued job"); - }); - // Queue another job afterwards to ensure that the async thread gets joined. - let (done_sender, done_receiver) = channel(); - at.queue_hi(move |_shelf| { - done_sender.send(()).unwrap(); - }); - done_receiver.recv().unwrap(); - } - - #[test] - fn test_async_task_idle() { - let at = AsyncTask::new(Duration::from_secs(3)); - // Need a SyncSender as it is Send+Sync. - let (idle_done_sender, idle_done_receiver) = sync_channel::<()>(3); - at.add_idle(move |_shelf| { - idle_done_sender.send(()).unwrap(); - }); - - // Queue up some high-priority and low-priority jobs that take time. - for _i in 0..3 { - at.queue_lo(|_shelf| { - std::thread::sleep(Duration::from_millis(500)); - }); - at.queue_hi(|_shelf| { - std::thread::sleep(Duration::from_millis(500)); - }); - } - // Final low-priority job. - let (done_sender, done_receiver) = channel(); - at.queue_lo(move |_shelf| { - done_sender.send(()).unwrap(); - }); - - // Nothing happens until the last job completes. - assert_eq!( - idle_done_receiver.recv_timeout(Duration::from_secs(1)), - Err(RecvTimeoutError::Timeout) - ); - done_receiver.recv().unwrap(); - // Now that the last low-priority job has completed, the idle task should - // fire pretty much immediately. - idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap(); - - // Idle callback not executed again even if we wait for a while. - assert_eq!( - idle_done_receiver.recv_timeout(Duration::from_secs(3)), - Err(RecvTimeoutError::Timeout) - ); - - // However, if more work is done then there's another chance to go idle. - let (done_sender, done_receiver) = channel(); - at.queue_hi(move |_shelf| { - std::thread::sleep(Duration::from_millis(500)); - done_sender.send(()).unwrap(); - }); - // Idle callback not immediately executed, because the high priority - // job is taking a while. - assert_eq!( - idle_done_receiver.recv_timeout(Duration::from_millis(1)), - Err(RecvTimeoutError::Timeout) - ); - done_receiver.recv().unwrap(); - idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap(); - } - - #[test] - fn test_async_task_multiple_idle() { - let at = AsyncTask::new(Duration::from_secs(3)); - let (idle_sender, idle_receiver) = sync_channel::<i32>(5); - // Queue a high priority job to start things off - at.queue_hi(|_shelf| { - std::thread::sleep(Duration::from_millis(500)); - }); - - // Multiple idle callbacks. - for i in 0..3 { - let idle_sender = idle_sender.clone(); - at.add_idle(move |_shelf| { - idle_sender.send(i).unwrap(); - }); - } - - // Nothing happens immediately. - assert_eq!( - idle_receiver.recv_timeout(Duration::from_millis(1)), - Err(RecvTimeoutError::Timeout) - ); - // Wait for a moment and the idle jobs should have run. - std::thread::sleep(Duration::from_secs(1)); - - let mut results = Vec::new(); - while let Ok(i) = idle_receiver.recv_timeout(Duration::from_millis(1)) { - results.push(i); - } - assert_eq!(results, [0, 1, 2]); - } - - #[test] - fn test_async_task_idle_queues_job() { - let at = Arc::new(AsyncTask::new(Duration::from_secs(1))); - let at_clone = at.clone(); - let (idle_sender, idle_receiver) = sync_channel::<i32>(100); - // Add an idle callback that queues a low-priority job. - at.add_idle(move |shelf| { - at_clone.queue_lo(|_shelf| { - // Slow things down so the channel doesn't fill up. - std::thread::sleep(Duration::from_millis(50)); - }); - let i = shelf.get_mut::<i32>(); - idle_sender.send(*i).unwrap(); - *i += 1; - }); - - // Nothing happens immediately. - assert_eq!( - idle_receiver.recv_timeout(Duration::from_millis(1500)), - Err(RecvTimeoutError::Timeout) - ); - - // Once we queue a normal job, things start. - at.queue_hi(|_shelf| {}); - assert_eq!(0, idle_receiver.recv_timeout(Duration::from_millis(200)).unwrap()); - - // The idle callback queues a job, and completion of that job - // means the task is going idle again...so the idle callback will - // be called repeatedly. - assert_eq!(1, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap()); - assert_eq!(2, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap()); - assert_eq!(3, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap()); - } - - #[test] - #[should_panic] - fn test_async_task_idle_panic() { - let at = AsyncTask::new(Duration::from_secs(1)); - let (idle_sender, idle_receiver) = sync_channel::<()>(3); - // Add an idle callback that panics. - at.add_idle(move |_shelf| { - idle_sender.send(()).unwrap(); - panic!("Panic from idle callback"); - }); - // Queue a job to trigger idleness and ensuing panic. - at.queue_hi(|_shelf| {}); - idle_receiver.recv().unwrap(); - - // Queue another job afterwards to ensure that the async thread gets joined - // and the panic detected. - let (done_sender, done_receiver) = channel(); - at.queue_hi(move |_shelf| { - done_sender.send(()).unwrap(); - }); - done_receiver.recv().unwrap(); - } -} diff --git a/keystore2/src/async_task/tests.rs b/keystore2/src/async_task/tests.rs new file mode 100644 index 00000000..e67303e6 --- /dev/null +++ b/keystore2/src/async_task/tests.rs @@ -0,0 +1,287 @@ +// Copyright 2020, 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. + +//! Async task tests. +use super::{AsyncTask, Shelf}; +use std::sync::{ + mpsc::{channel, sync_channel, RecvTimeoutError}, + Arc, +}; +use std::time::Duration; + +#[test] +fn test_shelf() { + let mut shelf = Shelf::default(); + + let s = "A string".to_string(); + assert_eq!(shelf.put(s), None); + + let s2 = "Another string".to_string(); + assert_eq!(shelf.put(s2), Some("A string".to_string())); + + // Put something of a different type on the shelf. + #[derive(Debug, PartialEq, Eq)] + struct Elf { + pub name: String, + } + let e1 = Elf { name: "Glorfindel".to_string() }; + assert_eq!(shelf.put(e1), None); + + // The String value is still on the shelf. + let s3 = shelf.get_downcast_ref::<String>().unwrap(); + assert_eq!(s3, "Another string"); + + // As is the Elf. + { + let e2 = shelf.get_downcast_mut::<Elf>().unwrap(); + assert_eq!(e2.name, "Glorfindel"); + e2.name = "Celeborn".to_string(); + } + + // Take the Elf off the shelf. + let e3 = shelf.remove_downcast_ref::<Elf>().unwrap(); + assert_eq!(e3.name, "Celeborn"); + + assert_eq!(shelf.remove_downcast_ref::<Elf>(), None); + + // No u64 value has been put on the shelf, so getting one gives the default value. + { + let i = shelf.get_mut::<u64>(); + assert_eq!(*i, 0); + *i = 42; + } + let i2 = shelf.get_downcast_ref::<u64>().unwrap(); + assert_eq!(*i2, 42); + + // No i32 value has ever been seen near the shelf. + assert_eq!(shelf.get_downcast_ref::<i32>(), None); + assert_eq!(shelf.get_downcast_mut::<i32>(), None); + assert_eq!(shelf.remove_downcast_ref::<i32>(), None); +} + +#[test] +fn test_async_task() { + let at = AsyncTask::default(); + + // First queue up a job that blocks until we release it, to avoid + // unpredictable synchronization. + let (start_sender, start_receiver) = channel(); + at.queue_hi(move |shelf| { + start_receiver.recv().unwrap(); + // Put a trace vector on the shelf + shelf.put(Vec::<String>::new()); + }); + + // Queue up some high-priority and low-priority jobs. + for i in 0..3 { + let j = i; + at.queue_lo(move |shelf| { + let trace = shelf.get_mut::<Vec<String>>(); + trace.push(format!("L{}", j)); + }); + let j = i; + at.queue_hi(move |shelf| { + let trace = shelf.get_mut::<Vec<String>>(); + trace.push(format!("H{}", j)); + }); + } + + // Finally queue up a low priority job that emits the trace. + let (trace_sender, trace_receiver) = channel(); + at.queue_lo(move |shelf| { + let trace = shelf.get_downcast_ref::<Vec<String>>().unwrap(); + trace_sender.send(trace.clone()).unwrap(); + }); + + // Ready, set, go. + start_sender.send(()).unwrap(); + let trace = trace_receiver.recv().unwrap(); + + assert_eq!(trace, vec!["H0", "H1", "H2", "L0", "L1", "L2"]); +} + +#[test] +fn test_async_task_chain() { + let at = Arc::new(AsyncTask::default()); + let (sender, receiver) = channel(); + // Queue up a job that will queue up another job. This confirms + // that the job is not invoked with any internal AsyncTask locks held. + let at_clone = at.clone(); + at.queue_hi(move |_shelf| { + at_clone.queue_lo(move |_shelf| { + sender.send(()).unwrap(); + }); + }); + receiver.recv().unwrap(); +} + +#[test] +#[should_panic] +fn test_async_task_panic() { + let at = AsyncTask::default(); + at.queue_hi(|_shelf| { + panic!("Panic from queued job"); + }); + // Queue another job afterwards to ensure that the async thread gets joined. + let (done_sender, done_receiver) = channel(); + at.queue_hi(move |_shelf| { + done_sender.send(()).unwrap(); + }); + done_receiver.recv().unwrap(); +} + +#[test] +fn test_async_task_idle() { + let at = AsyncTask::new(Duration::from_secs(3)); + // Need a SyncSender as it is Send+Sync. + let (idle_done_sender, idle_done_receiver) = sync_channel::<()>(3); + at.add_idle(move |_shelf| { + idle_done_sender.send(()).unwrap(); + }); + + // Queue up some high-priority and low-priority jobs that take time. + for _i in 0..3 { + at.queue_lo(|_shelf| { + std::thread::sleep(Duration::from_millis(500)); + }); + at.queue_hi(|_shelf| { + std::thread::sleep(Duration::from_millis(500)); + }); + } + // Final low-priority job. + let (done_sender, done_receiver) = channel(); + at.queue_lo(move |_shelf| { + done_sender.send(()).unwrap(); + }); + + // Nothing happens until the last job completes. + assert_eq!( + idle_done_receiver.recv_timeout(Duration::from_secs(1)), + Err(RecvTimeoutError::Timeout) + ); + done_receiver.recv().unwrap(); + // Now that the last low-priority job has completed, the idle task should + // fire pretty much immediately. + idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap(); + + // Idle callback not executed again even if we wait for a while. + assert_eq!( + idle_done_receiver.recv_timeout(Duration::from_secs(3)), + Err(RecvTimeoutError::Timeout) + ); + + // However, if more work is done then there's another chance to go idle. + let (done_sender, done_receiver) = channel(); + at.queue_hi(move |_shelf| { + std::thread::sleep(Duration::from_millis(500)); + done_sender.send(()).unwrap(); + }); + // Idle callback not immediately executed, because the high priority + // job is taking a while. + assert_eq!( + idle_done_receiver.recv_timeout(Duration::from_millis(1)), + Err(RecvTimeoutError::Timeout) + ); + done_receiver.recv().unwrap(); + idle_done_receiver.recv_timeout(Duration::from_millis(50)).unwrap(); +} + +#[test] +fn test_async_task_multiple_idle() { + let at = AsyncTask::new(Duration::from_secs(3)); + let (idle_sender, idle_receiver) = sync_channel::<i32>(5); + // Queue a high priority job to start things off + at.queue_hi(|_shelf| { + std::thread::sleep(Duration::from_millis(500)); + }); + + // Multiple idle callbacks. + for i in 0..3 { + let idle_sender = idle_sender.clone(); + at.add_idle(move |_shelf| { + idle_sender.send(i).unwrap(); + }); + } + + // Nothing happens immediately. + assert_eq!( + idle_receiver.recv_timeout(Duration::from_millis(1)), + Err(RecvTimeoutError::Timeout) + ); + // Wait for a moment and the idle jobs should have run. + std::thread::sleep(Duration::from_secs(1)); + + let mut results = Vec::new(); + while let Ok(i) = idle_receiver.recv_timeout(Duration::from_millis(1)) { + results.push(i); + } + assert_eq!(results, [0, 1, 2]); +} + +#[test] +fn test_async_task_idle_queues_job() { + let at = Arc::new(AsyncTask::new(Duration::from_secs(1))); + let at_clone = at.clone(); + let (idle_sender, idle_receiver) = sync_channel::<i32>(100); + // Add an idle callback that queues a low-priority job. + at.add_idle(move |shelf| { + at_clone.queue_lo(|_shelf| { + // Slow things down so the channel doesn't fill up. + std::thread::sleep(Duration::from_millis(50)); + }); + let i = shelf.get_mut::<i32>(); + idle_sender.send(*i).unwrap(); + *i += 1; + }); + + // Nothing happens immediately. + assert_eq!( + idle_receiver.recv_timeout(Duration::from_millis(1500)), + Err(RecvTimeoutError::Timeout) + ); + + // Once we queue a normal job, things start. + at.queue_hi(|_shelf| {}); + assert_eq!(0, idle_receiver.recv_timeout(Duration::from_millis(200)).unwrap()); + + // The idle callback queues a job, and completion of that job + // means the task is going idle again...so the idle callback will + // be called repeatedly. + assert_eq!(1, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap()); + assert_eq!(2, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap()); + assert_eq!(3, idle_receiver.recv_timeout(Duration::from_millis(100)).unwrap()); +} + +#[test] +#[should_panic] +fn test_async_task_idle_panic() { + let at = AsyncTask::new(Duration::from_secs(1)); + let (idle_sender, idle_receiver) = sync_channel::<()>(3); + // Add an idle callback that panics. + at.add_idle(move |_shelf| { + idle_sender.send(()).unwrap(); + panic!("Panic from idle callback"); + }); + // Queue a job to trigger idleness and ensuing panic. + at.queue_hi(|_shelf| {}); + idle_receiver.recv().unwrap(); + + // Queue another job afterwards to ensure that the async thread gets joined + // and the panic detected. + let (done_sender, done_receiver) = channel(); + at.queue_hi(move |_shelf| { + done_sender.send(()).unwrap(); + }); + done_receiver.recv().unwrap(); +} diff --git a/keystore2/src/audit_log.rs b/keystore2/src/audit_log.rs index 0e5dfeb6..4952b3bf 100644 --- a/keystore2/src/audit_log.rs +++ b/keystore2/src/audit_log.rs @@ -20,7 +20,7 @@ use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, }; use libc::uid_t; -use log_event_list::{LogContext, LogContextError, LogIdSecurity}; +use structured_log::{structured_log, LOG_ID_SECURITY}; const TAG_KEY_GENERATED: u32 = 210024; const TAG_KEY_IMPORTED: u32 = 210025; @@ -34,8 +34,8 @@ fn key_owner(domain: Domain, nspace: i64, uid: i32) -> i32 { match domain { Domain::APP => uid, Domain::SELINUX => (nspace | FLAG_NAMESPACE) as i32, - _ => { - log::info!("Not logging audit event for key with unexpected domain"); + d => { + log::info!("Not logging audit event for key with domain {d:?}"); 0 } } @@ -58,30 +58,19 @@ pub fn log_key_deleted(key: &KeyDescriptor, calling_app: uid_t, success: bool) { /// Logs key integrity violation to NIAP audit log. pub fn log_key_integrity_violation(key: &KeyDescriptor) { - with_log_context(TAG_KEY_INTEGRITY_VIOLATION, |ctx| { - let owner = key_owner(key.domain, key.nspace, key.nspace as i32); - ctx.append_str(key.alias.as_ref().map_or("none", String::as_str))?.append_i32(owner) - }) + let owner = key_owner(key.domain, key.nspace, key.nspace as i32); + let alias = String::from(key.alias.as_ref().map_or("none", String::as_str)); + LOGS_HANDLER.queue_lo(move |_| { + let _result = + structured_log!(log_id: LOG_ID_SECURITY, TAG_KEY_INTEGRITY_VIOLATION, alias, owner); + }); } fn log_key_event(tag: u32, key: &KeyDescriptor, calling_app: uid_t, success: bool) { - with_log_context(tag, |ctx| { - let owner = key_owner(key.domain, key.nspace, calling_app as i32); - ctx.append_i32(i32::from(success))? - .append_str(key.alias.as_ref().map_or("none", String::as_str))? - .append_i32(owner) - }) -} - -fn with_log_context<F>(tag: u32, f: F) -where - F: Fn(LogContext) -> Result<LogContext, LogContextError>, -{ - if let Some(ctx) = LogContext::new(LogIdSecurity, tag) { - if let Ok(event) = f(ctx) { - LOGS_HANDLER.queue_lo(move |_| { - let _result = event.write(); - }); - } - } + let owner = key_owner(key.domain, key.nspace, calling_app as i32); + let alias = String::from(key.alias.as_ref().map_or("none", String::as_str)); + LOGS_HANDLER.queue_lo(move |_| { + let _result = + structured_log!(log_id: LOG_ID_SECURITY, tag, i32::from(success), alias, owner); + }); } diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs index f9567875..c76f86b0 100644 --- a/keystore2/src/authorization.rs +++ b/keystore2/src/authorization.rs @@ -51,10 +51,7 @@ pub enum Error { Binder(ExceptionCode, i32), } -/// This function should be used by authorization service calls to translate error conditions -/// into service specific exceptions. -/// -/// All error conditions get logged by this function. +/// Translate an error into a service specific exception, logging along the way. /// /// `Error::Rc(x)` variants get mapped onto a service specific error code of `x`. /// Certain response codes may be returned from keystore/ResponseCode.aidl by the keystore2 modules, @@ -64,53 +61,36 @@ pub enum Error { /// `selinux::Error::perm()` is mapped on `ResponseCode::PERMISSION_DENIED`. /// /// All non `Error` error conditions get mapped onto ResponseCode::SYSTEM_ERROR`. -/// -/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed -/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it -/// typically returns Ok(value). -pub fn map_or_log_err<T, U, F>(result: Result<U>, handle_ok: F) -> BinderResult<T> -where - F: FnOnce(U) -> BinderResult<T>, -{ - result.map_or_else( - |e| { - log::error!("{:#?}", e); - let root_cause = e.root_cause(); - if let Some(KeystoreError::Rc(ks_rcode)) = root_cause.downcast_ref::<KeystoreError>() { - let rc = match *ks_rcode { - // Although currently keystore2/ResponseCode.aidl and - // authorization/ResponseCode.aidl share the same integer values for the - // common response codes, this may deviate in the future, hence the - // conversion here. - KsResponseCode::SYSTEM_ERROR => ResponseCode::SYSTEM_ERROR.0, - KsResponseCode::KEY_NOT_FOUND => ResponseCode::KEY_NOT_FOUND.0, - KsResponseCode::VALUE_CORRUPTED => ResponseCode::VALUE_CORRUPTED.0, - KsResponseCode::INVALID_ARGUMENT => ResponseCode::INVALID_ARGUMENT.0, - // If the code paths of IKeystoreAuthorization aidl's methods happen to return - // other error codes from KsResponseCode in the future, they should be converted - // as well. - _ => ResponseCode::SYSTEM_ERROR.0, - }; - return Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )); - } - let rc = match root_cause.downcast_ref::<Error>() { - Some(Error::Rc(rcode)) => rcode.0, - Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0, - None => match root_cause.downcast_ref::<selinux::Error>() { - Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0, - _ => ResponseCode::SYSTEM_ERROR.0, - }, - }; - Err(BinderStatus::new_service_specific_error( - rc, - anyhow_error_to_cstring(&e).as_deref(), - )) - }, - handle_ok, - ) +pub fn into_logged_binder(e: anyhow::Error) -> BinderStatus { + log::error!("{:#?}", e); + let root_cause = e.root_cause(); + if let Some(KeystoreError::Rc(ks_rcode)) = root_cause.downcast_ref::<KeystoreError>() { + let rc = match *ks_rcode { + // Although currently keystore2/ResponseCode.aidl and + // authorization/ResponseCode.aidl share the same integer values for the + // common response codes, this may deviate in the future, hence the + // conversion here. + KsResponseCode::SYSTEM_ERROR => ResponseCode::SYSTEM_ERROR.0, + KsResponseCode::KEY_NOT_FOUND => ResponseCode::KEY_NOT_FOUND.0, + KsResponseCode::VALUE_CORRUPTED => ResponseCode::VALUE_CORRUPTED.0, + KsResponseCode::INVALID_ARGUMENT => ResponseCode::INVALID_ARGUMENT.0, + // If the code paths of IKeystoreAuthorization aidl's methods happen to return + // other error codes from KsResponseCode in the future, they should be converted + // as well. + _ => ResponseCode::SYSTEM_ERROR.0, + }; + BinderStatus::new_service_specific_error(rc, anyhow_error_to_cstring(&e).as_deref()) + } else { + let rc = match root_cause.downcast_ref::<Error>() { + Some(Error::Rc(rcode)) => rcode.0, + Some(Error::Binder(_, _)) => ResponseCode::SYSTEM_ERROR.0, + None => match root_cause.downcast_ref::<selinux::Error>() { + Some(selinux::Error::PermissionDenied) => ResponseCode::PERMISSION_DENIED.0, + _ => ResponseCode::SYSTEM_ERROR.0, + }, + }; + BinderStatus::new_service_specific_error(rc, anyhow_error_to_cstring(&e).as_deref()) + } } /// This struct is defined to implement the aforementioned AIDL interface. @@ -128,7 +108,8 @@ impl AuthorizationManager { fn add_auth_token(&self, auth_token: &HardwareAuthToken) -> Result<()> { // Check keystore permission. - check_keystore_permission(KeystorePerm::AddAuth).context(ks_err!())?; + check_keystore_permission(KeystorePerm::AddAuth) + .context(ks_err!("caller missing AddAuth permissions"))?; log::info!( "add_auth_token(challenge={}, userId={}, authId={}, authType={:#x}, timestamp={}ms)", @@ -149,7 +130,8 @@ impl AuthorizationManager { user_id, password.is_some(), ); - check_keystore_permission(KeystorePerm::Unlock).context(ks_err!("Unlock."))?; + check_keystore_permission(KeystorePerm::Unlock) + .context(ks_err!("caller missing Unlock permissions"))?; ENFORCEMENTS.set_device_locked(user_id, false); let mut skm = SUPER_KEY.write().unwrap(); @@ -160,7 +142,7 @@ impl AuthorizationManager { .context(ks_err!("Unlock with password.")) } else { DB.with(|db| skm.try_unlock_user_with_biometric(&mut db.borrow_mut(), user_id as u32)) - .context(ks_err!("try_unlock_user_with_biometric failed")) + .context(ks_err!("try_unlock_user_with_biometric failed user_id={user_id}")) } } @@ -168,7 +150,7 @@ impl AuthorizationManager { &self, user_id: i32, unlocking_sids: &[i64], - mut weak_unlock_enabled: bool, + weak_unlock_enabled: bool, ) -> Result<()> { log::info!( "on_device_locked(user_id={}, unlocking_sids={:?}, weak_unlock_enabled={})", @@ -176,10 +158,8 @@ impl AuthorizationManager { unlocking_sids, weak_unlock_enabled ); - if !android_security_flags::fix_unlocked_device_required_keys_v2() { - weak_unlock_enabled = false; - } - check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?; + check_keystore_permission(KeystorePerm::Lock) + .context(ks_err!("caller missing Lock permission"))?; ENFORCEMENTS.set_device_locked(user_id, true); let mut skm = SUPER_KEY.write().unwrap(); DB.with(|db| { @@ -195,20 +175,16 @@ impl AuthorizationManager { fn on_weak_unlock_methods_expired(&self, user_id: i32) -> Result<()> { log::info!("on_weak_unlock_methods_expired(user_id={})", user_id); - if !android_security_flags::fix_unlocked_device_required_keys_v2() { - return Ok(()); - } - check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?; + check_keystore_permission(KeystorePerm::Lock) + .context(ks_err!("caller missing Lock permission"))?; SUPER_KEY.write().unwrap().wipe_plaintext_unlocked_device_required_keys(user_id as u32); Ok(()) } fn on_non_lskf_unlock_methods_expired(&self, user_id: i32) -> Result<()> { log::info!("on_non_lskf_unlock_methods_expired(user_id={})", user_id); - if !android_security_flags::fix_unlocked_device_required_keys_v2() { - return Ok(()); - } - check_keystore_permission(KeystorePerm::Lock).context(ks_err!("Lock"))?; + check_keystore_permission(KeystorePerm::Lock) + .context(ks_err!("caller missing Lock permission"))?; SUPER_KEY.write().unwrap().wipe_all_unlocked_device_required_keys(user_id as u32); Ok(()) } @@ -221,7 +197,8 @@ impl AuthorizationManager { ) -> Result<AuthorizationTokens> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. - check_keystore_permission(KeystorePerm::GetAuthToken).context(ks_err!("GetAuthToken"))?; + check_keystore_permission(KeystorePerm::GetAuthToken) + .context(ks_err!("caller missing GetAuthToken permission"))?; // If the challenge is zero, return error if challenge == 0 { @@ -240,7 +217,8 @@ impl AuthorizationManager { auth_types: &[HardwareAuthenticatorType], ) -> Result<i64> { // Check keystore permission. - check_keystore_permission(KeystorePerm::GetLastAuthTime).context(ks_err!())?; + check_keystore_permission(KeystorePerm::GetLastAuthTime) + .context(ks_err!("caller missing GetLastAuthTime permission"))?; let mut max_time: i64 = -1; for auth_type in auth_types.iter() { @@ -264,13 +242,13 @@ impl Interface for AuthorizationManager {} impl IKeystoreAuthorization for AuthorizationManager { fn addAuthToken(&self, auth_token: &HardwareAuthToken) -> BinderResult<()> { - let _wp = wd::watch_millis("IKeystoreAuthorization::addAuthToken", 500); - map_or_log_err(self.add_auth_token(auth_token), Ok) + let _wp = wd::watch("IKeystoreAuthorization::addAuthToken"); + self.add_auth_token(auth_token).map_err(into_logged_binder) } fn onDeviceUnlocked(&self, user_id: i32, password: Option<&[u8]>) -> BinderResult<()> { - let _wp = wd::watch_millis("IKeystoreAuthorization::onDeviceUnlocked", 500); - map_or_log_err(self.on_device_unlocked(user_id, password.map(|pw| pw.into())), Ok) + let _wp = wd::watch("IKeystoreAuthorization::onDeviceUnlocked"); + self.on_device_unlocked(user_id, password.map(|pw| pw.into())).map_err(into_logged_binder) } fn onDeviceLocked( @@ -279,18 +257,19 @@ impl IKeystoreAuthorization for AuthorizationManager { unlocking_sids: &[i64], weak_unlock_enabled: bool, ) -> BinderResult<()> { - let _wp = wd::watch_millis("IKeystoreAuthorization::onDeviceLocked", 500); - map_or_log_err(self.on_device_locked(user_id, unlocking_sids, weak_unlock_enabled), Ok) + let _wp = wd::watch("IKeystoreAuthorization::onDeviceLocked"); + self.on_device_locked(user_id, unlocking_sids, weak_unlock_enabled) + .map_err(into_logged_binder) } fn onWeakUnlockMethodsExpired(&self, user_id: i32) -> BinderResult<()> { - let _wp = wd::watch_millis("IKeystoreAuthorization::onWeakUnlockMethodsExpired", 500); - map_or_log_err(self.on_weak_unlock_methods_expired(user_id), Ok) + let _wp = wd::watch("IKeystoreAuthorization::onWeakUnlockMethodsExpired"); + self.on_weak_unlock_methods_expired(user_id).map_err(into_logged_binder) } fn onNonLskfUnlockMethodsExpired(&self, user_id: i32) -> BinderResult<()> { - let _wp = wd::watch_millis("IKeystoreAuthorization::onNonLskfUnlockMethodsExpired", 500); - map_or_log_err(self.on_non_lskf_unlock_methods_expired(user_id), Ok) + let _wp = wd::watch("IKeystoreAuthorization::onNonLskfUnlockMethodsExpired"); + self.on_non_lskf_unlock_methods_expired(user_id).map_err(into_logged_binder) } fn getAuthTokensForCredStore( @@ -299,15 +278,9 @@ impl IKeystoreAuthorization for AuthorizationManager { secure_user_id: i64, auth_token_max_age_millis: i64, ) -> binder::Result<AuthorizationTokens> { - let _wp = wd::watch_millis("IKeystoreAuthorization::getAuthTokensForCredStore", 500); - map_or_log_err( - self.get_auth_tokens_for_credstore( - challenge, - secure_user_id, - auth_token_max_age_millis, - ), - Ok, - ) + let _wp = wd::watch("IKeystoreAuthorization::getAuthTokensForCredStore"); + self.get_auth_tokens_for_credstore(challenge, secure_user_id, auth_token_max_age_millis) + .map_err(into_logged_binder) } fn getLastAuthTime( @@ -316,7 +289,7 @@ impl IKeystoreAuthorization for AuthorizationManager { auth_types: &[HardwareAuthenticatorType], ) -> binder::Result<i64> { if aconfig_android_hardware_biometrics_rust::last_authentication_time() { - map_or_log_err(self.get_last_auth_time(secure_user_id, auth_types), Ok) + self.get_last_auth_time(secure_user_id, auth_types).map_err(into_logged_binder) } else { Err(BinderStatus::new_service_specific_error( ResponseCode::PERMISSION_DENIED.0, diff --git a/keystore2/src/crypto/zvec.rs b/keystore2/src/crypto/zvec.rs index c917a898..00cbb1c8 100644 --- a/keystore2/src/crypto/zvec.rs +++ b/keystore2/src/crypto/zvec.rs @@ -20,6 +20,7 @@ use std::convert::TryFrom; use std::fmt; use std::ops::{Deref, DerefMut}; use std::ptr::write_volatile; +use std::ptr::NonNull; /// A semi fixed size u8 vector that is zeroed when dropped. It can shrink in /// size but cannot grow larger than the original size (and if it shrinks it @@ -46,7 +47,7 @@ impl ZVec { let b = v.into_boxed_slice(); if size > 0 { // SAFETY: The address range is part of our address space. - unsafe { mlock(b.as_ptr() as *const std::ffi::c_void, b.len()) }?; + unsafe { mlock(NonNull::from(&b).cast(), b.len()) }?; } Ok(Self { elems: b, len: size }) } @@ -79,9 +80,7 @@ impl Drop for ZVec { if let Err(e) = // SAFETY: The address range is part of our address space, and was previously locked // by `mlock` in `ZVec::new` or the `TryFrom<Vec<u8>>` implementation. - unsafe { - munlock(self.elems.as_ptr() as *const std::ffi::c_void, self.elems.len()) - } + unsafe { munlock(NonNull::from(&self.elems).cast(), self.elems.len()) } { log::error!("In ZVec::drop: `munlock` failed: {:?}.", e); } @@ -137,7 +136,7 @@ impl TryFrom<Vec<u8>> for ZVec { let b = v.into_boxed_slice(); if !b.is_empty() { // SAFETY: The address range is part of our address space. - unsafe { mlock(b.as_ptr() as *const std::ffi::c_void, b.len()) }?; + unsafe { mlock(NonNull::from(&b).cast(), b.len()) }?; } Ok(Self { elems: b, len }) } diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs index b526daac..9ce6506b 100644 --- a/keystore2/src/database.rs +++ b/keystore2/src/database.rs @@ -45,8 +45,11 @@ mod perboot; pub(crate) mod utils; mod versioning; +#[cfg(test)] +pub mod tests; + use crate::gc::Gc; -use crate::impl_metadata; // This is in db_utils.rs +use crate::impl_metadata; // This is in database/utils.rs use crate::key_parameter::{KeyParameter, KeyParameterValue, Tag}; use crate::ks_err; use crate::permission::KeyPermSet; @@ -82,7 +85,7 @@ use rusqlite::{ types::FromSqlResult, types::ToSqlOutput, types::{FromSqlError, Value, ValueRef}, - Connection, OptionalExtension, ToSql, Transaction, TransactionBehavior, + Connection, OptionalExtension, ToSql, Transaction, }; use std::{ @@ -92,9 +95,40 @@ use std::{ time::{Duration, SystemTime}, }; +use TransactionBehavior::Immediate; + #[cfg(test)] use tests::random; +/// Wrapper for `rusqlite::TransactionBehavior` which includes information about the transaction +/// being performed. +#[derive(Clone, Copy)] +enum TransactionBehavior { + Deferred, + Immediate(&'static str), +} + +impl From<TransactionBehavior> for rusqlite::TransactionBehavior { + fn from(val: TransactionBehavior) -> Self { + match val { + TransactionBehavior::Deferred => rusqlite::TransactionBehavior::Deferred, + TransactionBehavior::Immediate(_) => rusqlite::TransactionBehavior::Immediate, + } + } +} + +impl TransactionBehavior { + fn name(&self) -> Option<&'static str> { + match self { + TransactionBehavior::Deferred => None, + TransactionBehavior::Immediate(v) => Some(v), + } + } +} + +/// If the database returns a busy error code, retry after this interval. +const DB_BUSY_RETRY_INTERVAL: Duration = Duration::from_micros(500); + impl_metadata!( /// A set of metadata for key entries. #[derive(Debug, Default, Eq, PartialEq)] @@ -382,11 +416,6 @@ impl DateTime { pub fn to_millis_epoch(self) -> i64 { self.0 } - - /// Returns unix epoch time in seconds. - pub fn to_secs_epoch(self) -> i64 { - self.0 / 1000 - } } impl ToSql for DateTime { @@ -523,7 +552,7 @@ impl KeyIdLockDb { /// This function blocks until an exclusive lock for the given key entry id can /// be acquired. It returns a guard object, that represents the lifecycle of the /// acquired lock. - pub fn get(&self, key_id: i64) -> KeyIdGuard { + fn get(&self, key_id: i64) -> KeyIdGuard { let mut locked_keys = self.locked_keys.lock().unwrap(); while locked_keys.contains(&key_id) { locked_keys = self.cond_var.wait(locked_keys).unwrap(); @@ -536,7 +565,7 @@ impl KeyIdLockDb { /// given key id is already taken the function returns None immediately. If a lock /// can be acquired this function returns a guard object, that represents the /// lifecycle of the acquired lock. - pub fn try_get(&self, key_id: i64) -> Option<KeyIdGuard> { + fn try_get(&self, key_id: i64) -> Option<KeyIdGuard> { let mut locked_keys = self.locked_keys.lock().unwrap(); if locked_keys.insert(key_id) { Some(KeyIdGuard(key_id)) @@ -665,10 +694,6 @@ impl KeyEntry { pub fn take_cert(&mut self) -> Option<Vec<u8>> { self.cert.take() } - /// Exposes the optional public certificate chain. - pub fn cert_chain(&self) -> &Option<Vec<u8>> { - &self.cert_chain - } /// Extracts the optional public certificate_chain. pub fn take_cert_chain(&mut self) -> Option<Vec<u8>> { self.cert_chain.take() @@ -677,10 +702,6 @@ impl KeyEntry { pub fn km_uuid(&self) -> &Uuid { &self.km_uuid } - /// Exposes the key parameters of this key entry. - pub fn key_parameters(&self) -> &Vec<KeyParameter> { - &self.parameters - } /// Consumes this key entry and extracts the keyparameters from it. pub fn into_key_parameters(self) -> Vec<KeyParameter> { self.parameters @@ -694,10 +715,6 @@ impl KeyEntry { pub fn pure_cert(&self) -> bool { self.pure_cert } - /// Consumes this key entry and extracts the keyparameters and metadata from it. - pub fn into_key_parameters_and_metadata(self) -> (Vec<KeyParameter>, KeyMetaData) { - (self.parameters, self.metadata) - } } /// Indicates the sub component of a key entry for persistent storage. @@ -842,10 +859,17 @@ impl AuthTokenEntry { } } -/// Shared in-memory databases get destroyed as soon as the last connection to them gets closed. -/// This object does not allow access to the database connection. But it keeps a database -/// connection alive in order to keep the in memory per boot database alive. -pub struct PerBootDbKeepAlive(Connection); +/// Information about a superseded blob (a blob that is no longer the +/// most recent blob of that type for a given key, due to upgrade or +/// replacement). +pub struct SupersededBlob { + /// ID + pub blob_id: i64, + /// Contents. + pub blob: Vec<u8>, + /// Metadata. + pub metadata: BlobMetaData, +} impl KeystoreDB { const UNASSIGNED_KEY_ID: i64 = -1i64; @@ -861,13 +885,13 @@ impl KeystoreDB { /// KeystoreDB cannot be used by multiple threads. /// Each thread should open their own connection using `thread_local!`. pub fn new(db_root: &Path, gc: Option<Arc<Gc>>) -> Result<Self> { - let _wp = wd::watch_millis("KeystoreDB::new", 500); + let _wp = wd::watch("KeystoreDB::new"); let persistent_path = Self::make_persistent_path(db_root)?; let conn = Self::make_connection(&persistent_path)?; let mut db = Self { conn, gc, perboot: perboot::PERBOOT_DB.clone() }; - db.with_transaction(TransactionBehavior::Immediate, |tx| { + db.with_transaction(Immediate("TX_new"), |tx| { versioning::upgrade_database(tx, Self::CURRENT_DB_VERSION, Self::UPGRADERS) .context(ks_err!("KeystoreDB::new: trying to upgrade database."))?; Self::init_tables(tx).context("Trying to initialize tables.").no_gc() @@ -1035,7 +1059,7 @@ impl KeystoreDB { .context("Failed to attach database persistent.") { if Self::is_locked_error(&e) { - std::thread::sleep(std::time::Duration::from_micros(500)); + std::thread::sleep(DB_BUSY_RETRY_INTERVAL); continue; } else { return Err(e); @@ -1096,7 +1120,7 @@ impl KeystoreDB { /// 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> { - let _wp = wd::watch_millis("KeystoreDB::get_storage_stat", 500); + let _wp = wd::watch("KeystoreDB::get_storage_stat"); match storage_type { MetricsStorage::DATABASE => self.get_total_size(), @@ -1158,9 +1182,9 @@ impl KeystoreDB { &mut self, blob_ids_to_delete: &[i64], max_blobs: usize, - ) -> Result<Vec<(i64, Vec<u8>, BlobMetaData)>> { - let _wp = wd::watch_millis("KeystoreDB::handle_next_superseded_blob", 500); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + ) -> Result<Vec<SupersededBlob>> { + let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob"); + self.with_transaction(Immediate("TX_handle_next_superseded_blob"), |tx| { // Delete the given blobs. for blob_id in blob_ids_to_delete { tx.execute( @@ -1174,8 +1198,9 @@ impl KeystoreDB { Self::cleanup_unreferenced(tx).context("Trying to cleanup unreferenced.")?; - // Find up to max_blobx more superseded key blobs, load their metadata and return it. + // Find up to `max_blobs` more superseded key blobs, load their metadata and return it. let result: Vec<(i64, Vec<u8>)> = { + let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob find_next"); let mut stmt = tx .prepare( "SELECT id, blob FROM persistent.blobentry @@ -1206,12 +1231,17 @@ impl KeystoreDB { .context("Trying to extract superseded blobs.")? }; + let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob load_metadata"); let result = result .into_iter() .map(|(blob_id, blob)| { - Ok((blob_id, blob, BlobMetaData::load_from_db(blob_id, tx)?)) + Ok(SupersededBlob { + blob_id, + blob, + metadata: BlobMetaData::load_from_db(blob_id, tx)?, + }) }) - .collect::<Result<Vec<(i64, Vec<u8>, BlobMetaData)>>>() + .collect::<Result<Vec<_>>>() .context("Trying to load blob metadata.")?; if !result.is_empty() { return Ok(result).no_gc(); @@ -1219,6 +1249,7 @@ impl KeystoreDB { // We did not find any superseded key blob, so let's remove other superseded blob in // one transaction. + let _wp = wd::watch("KeystoreDB::handle_next_superseded_blob delete"); tx.execute( "DELETE FROM persistent.blobentry WHERE NOT subcomponent_type = ? @@ -1247,9 +1278,9 @@ impl KeystoreDB { /// Unlike with `mark_unreferenced`, we don't need to purge grants, because only keys that made /// it to `KeyLifeCycle::Live` may have grants. pub fn cleanup_leftovers(&mut self) -> Result<usize> { - let _wp = wd::watch_millis("KeystoreDB::cleanup_leftovers", 500); + let _wp = wd::watch("KeystoreDB::cleanup_leftovers"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_cleanup_leftovers"), |tx| { tx.execute( "UPDATE persistent.keyentry SET state = ? WHERE state = ?;", params![KeyLifeCycle::Unreferenced, KeyLifeCycle::Existing], @@ -1268,9 +1299,9 @@ impl KeystoreDB { alias: &str, key_type: KeyType, ) -> Result<bool> { - let _wp = wd::watch_millis("KeystoreDB::key_exists", 500); + let _wp = wd::watch("KeystoreDB::key_exists"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_key_exists"), |tx| { let key_descriptor = KeyDescriptor { domain, nspace, alias: Some(alias.to_string()), blob: None }; let result = Self::load_key_entry_id(tx, &key_descriptor, key_type); @@ -1295,9 +1326,9 @@ impl KeystoreDB { blob_metadata: &BlobMetaData, key_metadata: &KeyMetaData, ) -> Result<KeyEntry> { - let _wp = wd::watch_millis("KeystoreDB::store_super_key", 500); + let _wp = wd::watch("KeystoreDB::store_super_key"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_store_super_key"), |tx| { let key_id = Self::insert_with_retry(|id| { tx.execute( "INSERT into persistent.keyentry @@ -1340,9 +1371,9 @@ impl KeystoreDB { key_type: &SuperKeyType, user_id: u32, ) -> Result<Option<(KeyIdGuard, KeyEntry)>> { - let _wp = wd::watch_millis("KeystoreDB::load_super_key", 500); + let _wp = wd::watch("KeystoreDB::load_super_key"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_load_super_key"), |tx| { let key_descriptor = KeyDescriptor { domain: Domain::APP, nspace: user_id as i64, @@ -1366,99 +1397,6 @@ impl KeystoreDB { .context(ks_err!()) } - /// Atomically loads a key entry and associated metadata or creates it using the - /// callback create_new_key callback. The callback is called during a database - /// transaction. This means that implementers should be mindful about using - /// blocking operations such as IPC or grabbing mutexes. - pub fn get_or_create_key_with<F>( - &mut self, - domain: Domain, - namespace: i64, - alias: &str, - km_uuid: Uuid, - create_new_key: F, - ) -> Result<(KeyIdGuard, KeyEntry)> - where - F: Fn() -> Result<(Vec<u8>, BlobMetaData)>, - { - let _wp = wd::watch_millis("KeystoreDB::get_or_create_key_with", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - let id = { - let mut stmt = tx - .prepare( - "SELECT id FROM persistent.keyentry - WHERE - key_type = ? - AND domain = ? - AND namespace = ? - AND alias = ? - AND state = ?;", - ) - .context(ks_err!("Failed to select from keyentry table."))?; - let mut rows = stmt - .query(params![KeyType::Super, domain.0, namespace, alias, KeyLifeCycle::Live]) - .context(ks_err!("Failed to query from keyentry table."))?; - - db_utils::with_rows_extract_one(&mut rows, |row| { - Ok(match row { - Some(r) => r.get(0).context("Failed to unpack id.")?, - None => None, - }) - }) - .context(ks_err!())? - }; - - let (id, entry) = match id { - Some(id) => ( - id, - Self::load_key_components(tx, KeyEntryLoadBits::KM, id).context(ks_err!())?, - ), - - None => { - let id = Self::insert_with_retry(|id| { - tx.execute( - "INSERT into persistent.keyentry - (id, key_type, domain, namespace, alias, state, km_uuid) - VALUES(?, ?, ?, ?, ?, ?, ?);", - params![ - id, - KeyType::Super, - domain.0, - namespace, - alias, - KeyLifeCycle::Live, - km_uuid, - ], - ) - }) - .context(ks_err!())?; - - let (blob, metadata) = create_new_key().context(ks_err!())?; - Self::set_blob_internal( - tx, - id, - SubComponentType::KEY_BLOB, - Some(&blob), - Some(&metadata), - ) - .context(ks_err!())?; - ( - id, - KeyEntry { - id, - key_blob_info: Some((blob, metadata)), - pure_cert: false, - ..Default::default() - }, - ) - } - }; - Ok((KEY_ID_LOCK.get(id), entry)).no_gc() - }) - .context(ks_err!()) - } - /// Creates a transaction with the given behavior and executes f with the new transaction. /// The transaction is committed only if f returns Ok and retried if DatabaseBusy /// or DatabaseLocked is encountered. @@ -1466,20 +1404,25 @@ impl KeystoreDB { where F: Fn(&Transaction) -> Result<(bool, T)>, { + let name = behavior.name(); loop { - match self + let result = self .conn - .transaction_with_behavior(behavior) + .transaction_with_behavior(behavior.into()) .context(ks_err!()) - .and_then(|tx| f(&tx).map(|result| (result, tx))) + .and_then(|tx| { + let _wp = name.map(wd::watch); + f(&tx).map(|result| (result, tx)) + }) .and_then(|(result, tx)| { tx.commit().context(ks_err!("Failed to commit transaction."))?; Ok(result) - }) { + }); + match result { Ok(result) => break Ok(result), Err(e) => { if Self::is_locked_error(&e) { - std::thread::sleep(std::time::Duration::from_micros(500)); + std::thread::sleep(DB_BUSY_RETRY_INTERVAL); continue; } else { return Err(e).context(ks_err!()); @@ -1505,27 +1448,6 @@ impl KeystoreDB { ) } - /// Creates a new key entry and allocates a new randomized id for the new key. - /// The key id gets associated with a domain and namespace but not with an alias. - /// To complete key generation `rebind_alias` should be called after all of the - /// key artifacts, i.e., blobs and parameters have been associated with the new - /// key id. Finalizing with `rebind_alias` makes the creation of a new key entry - /// atomic even if key generation is not. - pub fn create_key_entry( - &mut self, - domain: &Domain, - namespace: &i64, - key_type: KeyType, - km_uuid: &Uuid, - ) -> Result<KeyIdGuard> { - let _wp = wd::watch_millis("KeystoreDB::create_key_entry", 500); - - self.with_transaction(TransactionBehavior::Immediate, |tx| { - Self::create_key_entry_internal(tx, domain, namespace, key_type, km_uuid).no_gc() - }) - .context(ks_err!()) - } - fn create_key_entry_internal( tx: &Transaction, domain: &Domain, @@ -1574,9 +1496,9 @@ impl KeystoreDB { blob: Option<&[u8]>, blob_metadata: Option<&BlobMetaData>, ) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::set_blob", 500); + let _wp = wd::watch("KeystoreDB::set_blob"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_set_blob"), |tx| { Self::set_blob_internal(tx, key_id.0, sc_type, blob, blob_metadata).need_gc() }) .context(ks_err!()) @@ -1587,9 +1509,9 @@ impl KeystoreDB { /// We use this to insert key blobs into the database which can then be garbage collected /// lazily by the key garbage collector. pub fn set_deleted_blob(&mut self, blob: &[u8], blob_metadata: &BlobMetaData) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::set_deleted_blob", 500); + let _wp = wd::watch("KeystoreDB::set_deleted_blob"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_set_deleted_blob"), |tx| { Self::set_blob_internal( tx, Self::UNASSIGNED_KEY_ID, @@ -1648,7 +1570,7 @@ impl KeystoreDB { /// and associates them with the given `key_id`. #[cfg(test)] fn insert_keyparameter(&mut self, key_id: &KeyIdGuard, params: &[KeyParameter]) -> Result<()> { - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_insert_keyparameter"), |tx| { Self::insert_keyparameter_internal(tx, key_id, params).no_gc() }) .context(ks_err!()) @@ -1681,7 +1603,7 @@ impl KeystoreDB { /// Insert a set of key entry specific metadata into the database. #[cfg(test)] fn insert_key_metadata(&mut self, key_id: &KeyIdGuard, metadata: &KeyMetaData) -> Result<()> { - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_insert_key_metadata"), |tx| { metadata.store_in_db(key_id.0, tx).no_gc() }) .context(ks_err!()) @@ -1749,7 +1671,7 @@ impl KeystoreDB { caller_uid: u32, check_permission: impl Fn(&KeyDescriptor) -> Result<()>, ) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::migrate_key_namespace", 500); + let _wp = wd::watch("KeystoreDB::migrate_key_namespace"); let destination = match destination.domain { Domain::APP => KeyDescriptor { nspace: caller_uid as i64, ..(*destination).clone() }, @@ -1769,7 +1691,7 @@ impl KeystoreDB { .ok_or(KsError::Rc(ResponseCode::INVALID_ARGUMENT)) .context(ks_err!("Alias must be specified."))?; - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_migrate_key_namespace"), |tx| { // Query the destination location. If there is a key, the migration request fails. if tx .query_row( @@ -1820,7 +1742,7 @@ impl KeystoreDB { metadata: &KeyMetaData, km_uuid: &Uuid, ) -> Result<KeyIdGuard> { - let _wp = wd::watch_millis("KeystoreDB::store_new_key", 500); + let _wp = wd::watch("KeystoreDB::store_new_key"); let (alias, domain, namespace) = match key { KeyDescriptor { alias: Some(alias), domain: Domain::APP, nspace, blob: None } @@ -1832,7 +1754,7 @@ impl KeystoreDB { .context(ks_err!("Need alias and domain must be APP or SELINUX.")); } }; - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_store_new_key"), |tx| { let key_id = Self::create_key_entry_internal(tx, &domain, namespace, key_type, km_uuid) .context("Trying to create new key entry.")?; let BlobInfo { blob, metadata: blob_metadata, superseded_blob } = *blob_info; @@ -1899,7 +1821,7 @@ impl KeystoreDB { cert: &[u8], km_uuid: &Uuid, ) -> Result<KeyIdGuard> { - let _wp = wd::watch_millis("KeystoreDB::store_new_certificate", 500); + let _wp = wd::watch("KeystoreDB::store_new_certificate"); let (alias, domain, namespace) = match key { KeyDescriptor { alias: Some(alias), domain: Domain::APP, nspace, blob: None } @@ -1911,7 +1833,7 @@ impl KeystoreDB { .context(ks_err!("Need alias and domain must be APP or SELINUX.")); } }; - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_store_new_certificate"), |tx| { let key_id = Self::create_key_entry_internal(tx, &domain, namespace, key_type, km_uuid) .context("Trying to create new key entry.")?; @@ -2179,9 +2101,9 @@ impl KeystoreDB { /// zero, the key also gets marked unreferenced and scheduled for deletion. /// Returns Ok(true) if the key was marked unreferenced as a hint to the garbage collector. pub fn check_and_update_key_usage_count(&mut self, key_id: i64) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::check_and_update_key_usage_count", 500); + let _wp = wd::watch("KeystoreDB::check_and_update_key_usage_count"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_check_and_update_key_usage_count"), |tx| { let limit: Option<i32> = tx .query_row( "SELECT data FROM persistent.keyparameter WHERE keyentryid = ? AND tag = ?;", @@ -2227,7 +2149,7 @@ impl KeystoreDB { caller_uid: u32, check_permission: impl Fn(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>, ) -> Result<(KeyIdGuard, KeyEntry)> { - let _wp = wd::watch_millis("KeystoreDB::load_key_entry", 500); + let _wp = wd::watch("KeystoreDB::load_key_entry"); loop { match self.load_key_entry_internal( @@ -2240,7 +2162,7 @@ impl KeystoreDB { Ok(result) => break Ok(result), Err(e) => { if Self::is_locked_error(&e) { - std::thread::sleep(std::time::Duration::from_micros(500)); + std::thread::sleep(DB_BUSY_RETRY_INTERVAL); continue; } else { return Err(e).context(ks_err!()); @@ -2355,9 +2277,9 @@ impl KeystoreDB { caller_uid: u32, check_permission: impl Fn(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>, ) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::unbind_key", 500); + let _wp = wd::watch("KeystoreDB::unbind_key"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_unbind_key"), |tx| { let (key_id, access_key_descriptor, access_vector) = Self::load_access_tuple(tx, key, key_type, caller_uid) .context("Trying to get access tuple.")?; @@ -2386,12 +2308,12 @@ impl KeystoreDB { /// Delete all artifacts belonging to the namespace given by the domain-namespace tuple. /// This leaves all of the blob entries orphaned for subsequent garbage collection. pub fn unbind_keys_for_namespace(&mut self, domain: Domain, namespace: i64) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::unbind_keys_for_namespace", 500); + let _wp = wd::watch("KeystoreDB::unbind_keys_for_namespace"); if !(domain == Domain::APP || domain == Domain::SELINUX) { return Err(KsError::Rc(ResponseCode::INVALID_ARGUMENT)).context(ks_err!()); } - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_unbind_keys_for_namespace"), |tx| { tx.execute( "DELETE FROM persistent.keymetadata WHERE keyentryid IN ( @@ -2431,7 +2353,7 @@ impl KeystoreDB { } fn cleanup_unreferenced(tx: &Transaction) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::cleanup_unreferenced", 500); + let _wp = wd::watch("KeystoreDB::cleanup_unreferenced"); { tx.execute( "DELETE FROM persistent.keymetadata @@ -2471,18 +2393,11 @@ impl KeystoreDB { .context(ks_err!()) } - /// Delete the keys created on behalf of the user, denoted by the user id. - /// Delete all the keys unless 'keep_non_super_encrypted_keys' set to true. - /// Returned boolean is to hint the garbage collector to delete the unbound keys. - /// The caller of this function should notify the gc if the returned value is true. - pub fn unbind_keys_for_user( - &mut self, - user_id: u32, - keep_non_super_encrypted_keys: bool, - ) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::unbind_keys_for_user", 500); + /// Deletes all keys for the given user, including both client keys and super keys. + pub fn unbind_keys_for_user(&mut self, user_id: u32) -> Result<()> { + let _wp = wd::watch("KeystoreDB::unbind_keys_for_user"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_unbind_keys_for_user"), |tx| { let mut stmt = tx .prepare(&format!( "SELECT id from persistent.keyentry @@ -2527,17 +2442,6 @@ impl KeystoreDB { let mut notify_gc = false; for key_id in key_ids { - if keep_non_super_encrypted_keys { - // Load metadata and filter out non-super-encrypted keys. - if let (_, Some((_, blob_metadata)), _, _) = - Self::load_blob_components(key_id, KeyEntryLoadBits::KM, tx) - .context(ks_err!("Trying to load blob info."))? - { - if blob_metadata.encrypted_by().is_none() { - continue; - } - } - } notify_gc = Self::mark_unreferenced(tx, key_id) .context("In unbind_keys_for_user.")? || notify_gc; @@ -2557,9 +2461,9 @@ impl KeystoreDB { /// be unlocked should remain usable when the lock screen is set to Swipe or None, as the device /// is always considered "unlocked" in that case. pub fn unbind_auth_bound_keys_for_user(&mut self, user_id: u32) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::unbind_auth_bound_keys_for_user", 500); + let _wp = wd::watch("KeystoreDB::unbind_auth_bound_keys_for_user"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_unbind_auth_bound_keys_for_user"), |tx| { let mut stmt = tx .prepare(&format!( "SELECT id from persistent.keyentry @@ -2652,7 +2556,7 @@ impl KeystoreDB { key_type: KeyType, start_past_alias: Option<&str>, ) -> Result<Vec<KeyDescriptor>> { - let _wp = wd::watch_millis("KeystoreDB::list_past_alias", 500); + let _wp = wd::watch("KeystoreDB::list_past_alias"); let query = format!( "SELECT DISTINCT alias FROM persistent.keyentry @@ -2707,7 +2611,7 @@ impl KeystoreDB { namespace: i64, key_type: KeyType, ) -> Result<usize> { - let _wp = wd::watch_millis("KeystoreDB::countKeys", 500); + let _wp = wd::watch("KeystoreDB::countKeys"); let num_keys = self.with_transaction(TransactionBehavior::Deferred, |tx| { tx.query_row( @@ -2740,9 +2644,9 @@ impl KeystoreDB { access_vector: KeyPermSet, check_permission: impl Fn(&KeyDescriptor, &KeyPermSet) -> Result<()>, ) -> Result<KeyDescriptor> { - let _wp = wd::watch_millis("KeystoreDB::grant", 500); + let _wp = wd::watch("KeystoreDB::grant"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_grant"), |tx| { // Load the key_id and complete the access control tuple. // We ignore the access vector here because grants cannot be granted. // The access vector returned here expresses the permissions the @@ -2806,9 +2710,9 @@ impl KeystoreDB { grantee_uid: u32, check_permission: impl Fn(&KeyDescriptor) -> Result<()>, ) -> Result<()> { - let _wp = wd::watch_millis("KeystoreDB::ungrant", 500); + let _wp = wd::watch("KeystoreDB::ungrant"); - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_ungrant"), |tx| { // Load the key_id and complete the access control tuple. // We ignore the access vector here because grants cannot be granted. let (key_id, access_key_descriptor, _) = @@ -2863,31 +2767,16 @@ impl KeystoreDB { } /// Find the newest auth token matching the given predicate. - pub fn find_auth_token_entry<F>(&self, p: F) -> Option<(AuthTokenEntry, BootTime)> + pub fn find_auth_token_entry<F>(&self, p: F) -> Option<AuthTokenEntry> where F: Fn(&AuthTokenEntry) -> bool, { - self.perboot.find_auth_token_entry(p).map(|entry| (entry, self.get_last_off_body())) - } - - /// Insert last_off_body into the metadata table at the initialization of auth token table - pub fn insert_last_off_body(&self, last_off_body: BootTime) { - self.perboot.set_last_off_body(last_off_body) - } - - /// Update last_off_body when on_device_off_body is called - pub fn update_last_off_body(&self, last_off_body: BootTime) { - self.perboot.set_last_off_body(last_off_body) - } - - /// Get last_off_body time when finding auth tokens - fn get_last_off_body(&self) -> BootTime { - self.perboot.get_last_off_body() + self.perboot.find_auth_token_entry(p) } /// Load descriptor of a key by key id pub fn load_key_descriptor(&mut self, key_id: i64) -> Result<Option<KeyDescriptor>> { - let _wp = wd::watch_millis("KeystoreDB::load_key_descriptor", 500); + let _wp = wd::watch("KeystoreDB::load_key_descriptor"); self.with_transaction(TransactionBehavior::Deferred, |tx| { tx.query_row( @@ -2918,9 +2807,9 @@ impl KeystoreDB { user_id: i32, secure_user_id: i64, ) -> Result<Vec<i64>> { - let _wp = wd::watch_millis("KeystoreDB::get_app_uids_affected_by_sid", 500); + let _wp = wd::watch("KeystoreDB::get_app_uids_affected_by_sid"); - let key_ids_and_app_uids = self.with_transaction(TransactionBehavior::Immediate, |tx| { + let ids = self.with_transaction(Immediate("TX_get_app_uids_affected_by_sid"), |tx| { let mut stmt = tx .prepare(&format!( "SELECT id, namespace from persistent.keyentry @@ -2949,13 +2838,13 @@ impl KeystoreDB { Ok(key_ids_and_app_uids).no_gc() })?; let mut app_uids_affected_by_sid: HashSet<i64> = Default::default(); - for (key_id, app_uid) in key_ids_and_app_uids { + for (key_id, app_uid) in ids { // Read the key parameters for each key in its own transaction. It is OK to ignore // an error to get the properties of a particular key since it might have been deleted // under our feet after the previous transaction concluded. If the key was deleted // then it is no longer applicable if it was auth-bound or not. if let Ok(is_key_bound_to_sid) = - self.with_transaction(TransactionBehavior::Immediate, |tx| { + self.with_transaction(Immediate("TX_get_app_uids_affects_by_sid 2"), |tx| { let params = Self::load_key_parameters(key_id, tx) .context("Failed to load key parameters.")?; // Check if the key is bound to this secure user ID. @@ -2978,2642 +2867,3 @@ impl KeystoreDB { Ok(app_uids_vec) } } - -#[cfg(test)] -pub mod tests { - - use super::*; - use crate::key_parameter::{ - Algorithm, BlockMode, Digest, EcCurve, HardwareAuthenticatorType, KeyOrigin, KeyParameter, - KeyParameterValue, KeyPurpose, PaddingMode, SecurityLevel, - }; - use crate::key_perm_set; - use crate::permission::{KeyPerm, KeyPermSet}; - use crate::super_key::{SuperKeyManager, USER_AFTER_FIRST_UNLOCK_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType}; - use keystore2_test_utils::TempDir; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - HardwareAuthToken::HardwareAuthToken, - HardwareAuthenticatorType::HardwareAuthenticatorType as kmhw_authenticator_type, - }; - use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ - Timestamp::Timestamp, - }; - use rusqlite::TransactionBehavior; - use std::cell::RefCell; - use std::collections::BTreeMap; - use std::fmt::Write; - use std::sync::atomic::{AtomicU8, Ordering}; - use std::sync::Arc; - use std::thread; - use std::time::{Duration, SystemTime}; - use crate::utils::AesGcm; - #[cfg(disabled)] - use std::time::Instant; - - pub fn new_test_db() -> Result<KeystoreDB> { - let conn = KeystoreDB::make_connection("file::memory:")?; - - let mut db = KeystoreDB { conn, gc: None, perboot: Arc::new(perboot::PerbootDB::new()) }; - db.with_transaction(TransactionBehavior::Immediate, |tx| { - KeystoreDB::init_tables(tx).context("Failed to initialize tables.").no_gc() - })?; - Ok(db) - } - - fn rebind_alias( - db: &mut KeystoreDB, - newid: &KeyIdGuard, - alias: &str, - domain: Domain, - namespace: i64, - ) -> Result<bool> { - db.with_transaction(TransactionBehavior::Immediate, |tx| { - KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace, KeyType::Client).no_gc() - }) - .context(ks_err!()) - } - - #[test] - fn datetime() -> Result<()> { - let conn = Connection::open_in_memory()?; - conn.execute("CREATE TABLE test (ts DATETIME);", [])?; - let now = SystemTime::now(); - let duration = Duration::from_secs(1000); - let then = now.checked_sub(duration).unwrap(); - let soon = now.checked_add(duration).unwrap(); - conn.execute( - "INSERT INTO test (ts) VALUES (?), (?), (?);", - params![DateTime::try_from(now)?, DateTime::try_from(then)?, DateTime::try_from(soon)?], - )?; - let mut stmt = conn.prepare("SELECT ts FROM test ORDER BY ts ASC;")?; - let mut rows = stmt.query([])?; - assert_eq!(DateTime::try_from(then)?, rows.next()?.unwrap().get(0)?); - assert_eq!(DateTime::try_from(now)?, rows.next()?.unwrap().get(0)?); - assert_eq!(DateTime::try_from(soon)?, rows.next()?.unwrap().get(0)?); - assert!(rows.next()?.is_none()); - assert!(DateTime::try_from(then)? < DateTime::try_from(now)?); - assert!(DateTime::try_from(then)? < DateTime::try_from(soon)?); - assert!(DateTime::try_from(now)? < DateTime::try_from(soon)?); - Ok(()) - } - - // Ensure that we're using the "injected" random function, not the real one. - #[test] - fn test_mocked_random() { - let rand1 = random(); - let rand2 = random(); - let rand3 = random(); - if rand1 == rand2 { - assert_eq!(rand2 + 1, rand3); - } else { - assert_eq!(rand1 + 1, rand2); - assert_eq!(rand2, rand3); - } - } - - // Test that we have the correct tables. - #[test] - fn test_tables() -> Result<()> { - let db = new_test_db()?; - let tables = db - .conn - .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")? - .query_map(params![], |row| row.get(0))? - .collect::<rusqlite::Result<Vec<String>>>()?; - assert_eq!(tables.len(), 6); - assert_eq!(tables[0], "blobentry"); - assert_eq!(tables[1], "blobmetadata"); - assert_eq!(tables[2], "grant"); - assert_eq!(tables[3], "keyentry"); - assert_eq!(tables[4], "keymetadata"); - assert_eq!(tables[5], "keyparameter"); - Ok(()) - } - - #[test] - fn test_auth_token_table_invariant() -> Result<()> { - let mut db = new_test_db()?; - let auth_token1 = HardwareAuthToken { - challenge: i64::MAX, - userId: 200, - authenticatorId: 200, - authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0), - timestamp: Timestamp { milliSeconds: 500 }, - mac: String::from("mac").into_bytes(), - }; - db.insert_auth_token(&auth_token1); - let auth_tokens_returned = get_auth_tokens(&db); - assert_eq!(auth_tokens_returned.len(), 1); - - // insert another auth token with the same values for the columns in the UNIQUE constraint - // of the auth token table and different value for timestamp - let auth_token2 = HardwareAuthToken { - challenge: i64::MAX, - userId: 200, - authenticatorId: 200, - authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0), - timestamp: Timestamp { milliSeconds: 600 }, - mac: String::from("mac").into_bytes(), - }; - - db.insert_auth_token(&auth_token2); - let mut auth_tokens_returned = get_auth_tokens(&db); - assert_eq!(auth_tokens_returned.len(), 1); - - if let Some(auth_token) = auth_tokens_returned.pop() { - assert_eq!(auth_token.auth_token.timestamp.milliSeconds, 600); - } - - // insert another auth token with the different values for the columns in the UNIQUE - // constraint of the auth token table - let auth_token3 = HardwareAuthToken { - challenge: i64::MAX, - userId: 201, - authenticatorId: 200, - authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0), - timestamp: Timestamp { milliSeconds: 600 }, - mac: String::from("mac").into_bytes(), - }; - - db.insert_auth_token(&auth_token3); - let auth_tokens_returned = get_auth_tokens(&db); - assert_eq!(auth_tokens_returned.len(), 2); - - Ok(()) - } - - // utility function for test_auth_token_table_invariant() - fn get_auth_tokens(db: &KeystoreDB) -> Vec<AuthTokenEntry> { - db.perboot.get_all_auth_token_entries() - } - - #[test] - fn test_persistence_for_files() -> Result<()> { - let temp_dir = TempDir::new("persistent_db_test")?; - let mut db = KeystoreDB::new(temp_dir.path(), None)?; - - db.create_key_entry(&Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?; - let entries = get_keyentry(&db)?; - assert_eq!(entries.len(), 1); - - let db = KeystoreDB::new(temp_dir.path(), None)?; - - let entries_new = get_keyentry(&db)?; - assert_eq!(entries, entries_new); - Ok(()) - } - - #[test] - fn test_create_key_entry() -> Result<()> { - fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>, Uuid) { - (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref(), ke.km_uuid.unwrap()) - } - - let mut db = new_test_db()?; - - db.create_key_entry(&Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?; - db.create_key_entry(&Domain::SELINUX, &101, KeyType::Client, &KEYSTORE_UUID)?; - - let entries = get_keyentry(&db)?; - assert_eq!(entries.len(), 2); - assert_eq!(extractor(&entries[0]), (Domain::APP, 100, None, KEYSTORE_UUID)); - assert_eq!(extractor(&entries[1]), (Domain::SELINUX, 101, None, KEYSTORE_UUID)); - - // Test that we must pass in a valid Domain. - check_result_is_error_containing_string( - db.create_key_entry(&Domain::GRANT, &102, KeyType::Client, &KEYSTORE_UUID), - &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT), - ); - check_result_is_error_containing_string( - db.create_key_entry(&Domain::BLOB, &103, KeyType::Client, &KEYSTORE_UUID), - &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB), - ); - check_result_is_error_containing_string( - db.create_key_entry(&Domain::KEY_ID, &104, KeyType::Client, &KEYSTORE_UUID), - &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID), - ); - - Ok(()) - } - - #[test] - fn test_rebind_alias() -> Result<()> { - fn extractor( - ke: &KeyEntryRow, - ) -> (Option<Domain>, Option<i64>, Option<&str>, Option<Uuid>) { - (ke.domain, ke.namespace, ke.alias.as_deref(), ke.km_uuid) - } - - let mut db = new_test_db()?; - db.create_key_entry(&Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?; - db.create_key_entry(&Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?; - let entries = get_keyentry(&db)?; - assert_eq!(entries.len(), 2); - assert_eq!( - extractor(&entries[0]), - (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)) - ); - assert_eq!( - extractor(&entries[1]), - (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)) - ); - - // Test that the first call to rebind_alias sets the alias. - rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[0].id), "foo", Domain::APP, 42)?; - let entries = get_keyentry(&db)?; - assert_eq!(entries.len(), 2); - assert_eq!( - extractor(&entries[0]), - (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID)) - ); - assert_eq!( - extractor(&entries[1]), - (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID)) - ); - - // Test that the second call to rebind_alias also empties the old one. - rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[1].id), "foo", Domain::APP, 42)?; - let entries = get_keyentry(&db)?; - assert_eq!(entries.len(), 2); - assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID))); - assert_eq!( - extractor(&entries[1]), - (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID)) - ); - - // Test that we must pass in a valid Domain. - check_result_is_error_containing_string( - rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::GRANT, 42), - &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT), - ); - check_result_is_error_containing_string( - rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::BLOB, 42), - &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB), - ); - check_result_is_error_containing_string( - rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::KEY_ID, 42), - &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID), - ); - - // Test that we correctly handle setting an alias for something that does not exist. - check_result_is_error_containing_string( - rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::SELINUX, 42), - "Expected to update a single entry but instead updated 0", - ); - // Test that we correctly abort the transaction in this case. - let entries = get_keyentry(&db)?; - assert_eq!(entries.len(), 2); - assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID))); - assert_eq!( - extractor(&entries[1]), - (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID)) - ); - - Ok(()) - } - - #[test] - fn test_grant_ungrant() -> Result<()> { - const CALLER_UID: u32 = 15; - const GRANTEE_UID: u32 = 12; - const SELINUX_NAMESPACE: i64 = 7; - - let mut db = new_test_db()?; - db.conn.execute( - "INSERT INTO persistent.keyentry (id, key_type, domain, namespace, alias, state, km_uuid) - VALUES (1, 0, 0, 15, 'key', 1, ?), (2, 0, 2, 7, 'yek', 1, ?);", - params![KEYSTORE_UUID, KEYSTORE_UUID], - )?; - let app_key = KeyDescriptor { - domain: super::Domain::APP, - nspace: 0, - alias: Some("key".to_string()), - blob: None, - }; - const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo]; - const PVEC2: KeyPermSet = key_perm_set![KeyPerm::Use]; - - // Reset totally predictable random number generator in case we - // are not the first test running on this thread. - reset_random(); - let next_random = 0i64; - - let app_granted_key = db - .grant(&app_key, CALLER_UID, GRANTEE_UID, PVEC1, |k, a| { - assert_eq!(*a, PVEC1); - assert_eq!( - *k, - KeyDescriptor { - domain: super::Domain::APP, - // namespace must be set to the caller_uid. - nspace: CALLER_UID as i64, - alias: Some("key".to_string()), - blob: None, - } - ); - Ok(()) - }) - .unwrap(); - - assert_eq!( - app_granted_key, - KeyDescriptor { - domain: super::Domain::GRANT, - // The grantid is next_random due to the mock random number generator. - nspace: next_random, - alias: None, - blob: None, - } - ); - - let selinux_key = KeyDescriptor { - domain: super::Domain::SELINUX, - nspace: SELINUX_NAMESPACE, - alias: Some("yek".to_string()), - blob: None, - }; - - let selinux_granted_key = db - .grant(&selinux_key, CALLER_UID, 12, PVEC1, |k, a| { - assert_eq!(*a, PVEC1); - assert_eq!( - *k, - KeyDescriptor { - domain: super::Domain::SELINUX, - // namespace must be the supplied SELinux - // namespace. - nspace: SELINUX_NAMESPACE, - alias: Some("yek".to_string()), - blob: None, - } - ); - Ok(()) - }) - .unwrap(); - - assert_eq!( - selinux_granted_key, - KeyDescriptor { - domain: super::Domain::GRANT, - // The grantid is next_random + 1 due to the mock random number generator. - nspace: next_random + 1, - alias: None, - blob: None, - } - ); - - // This should update the existing grant with PVEC2. - let selinux_granted_key = db - .grant(&selinux_key, CALLER_UID, 12, PVEC2, |k, a| { - assert_eq!(*a, PVEC2); - assert_eq!( - *k, - KeyDescriptor { - domain: super::Domain::SELINUX, - // namespace must be the supplied SELinux - // namespace. - nspace: SELINUX_NAMESPACE, - alias: Some("yek".to_string()), - blob: None, - } - ); - Ok(()) - }) - .unwrap(); - - assert_eq!( - selinux_granted_key, - KeyDescriptor { - domain: super::Domain::GRANT, - // Same grant id as before. The entry was only updated. - nspace: next_random + 1, - alias: None, - blob: None, - } - ); - - { - // Limiting scope of stmt, because it borrows db. - let mut stmt = db - .conn - .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?; - let mut rows = stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>([], |row| { - Ok((row.get(0)?, row.get(1)?, row.get(2)?, KeyPermSet::from(row.get::<_, i32>(3)?))) - })?; - - let r = rows.next().unwrap().unwrap(); - assert_eq!(r, (next_random, GRANTEE_UID, 1, PVEC1)); - let r = rows.next().unwrap().unwrap(); - assert_eq!(r, (next_random + 1, GRANTEE_UID, 2, PVEC2)); - assert!(rows.next().is_none()); - } - - debug_dump_keyentry_table(&mut db)?; - println!("app_key {:?}", app_key); - println!("selinux_key {:?}", selinux_key); - - db.ungrant(&app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?; - db.ungrant(&selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?; - - Ok(()) - } - - static TEST_KEY_BLOB: &[u8] = b"my test blob"; - static TEST_CERT_BLOB: &[u8] = b"my test cert"; - static TEST_CERT_CHAIN_BLOB: &[u8] = b"my test cert_chain"; - - #[test] - fn test_set_blob() -> Result<()> { - let key_id = KEY_ID_LOCK.get(3000); - let mut db = new_test_db()?; - let mut blob_metadata = BlobMetaData::new(); - blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); - db.set_blob( - &key_id, - SubComponentType::KEY_BLOB, - Some(TEST_KEY_BLOB), - Some(&blob_metadata), - )?; - db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?; - db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?; - drop(key_id); - - let mut stmt = db.conn.prepare( - "SELECT subcomponent_type, keyentryid, blob, id FROM persistent.blobentry - ORDER BY subcomponent_type ASC;", - )?; - let mut rows = stmt - .query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>([], |row| { - Ok(((row.get(0)?, row.get(1)?, row.get(2)?), row.get(3)?)) - })?; - let (r, id) = rows.next().unwrap().unwrap(); - assert_eq!(r, (SubComponentType::KEY_BLOB, 3000, TEST_KEY_BLOB.to_vec())); - let (r, _) = rows.next().unwrap().unwrap(); - assert_eq!(r, (SubComponentType::CERT, 3000, TEST_CERT_BLOB.to_vec())); - let (r, _) = rows.next().unwrap().unwrap(); - assert_eq!(r, (SubComponentType::CERT_CHAIN, 3000, TEST_CERT_CHAIN_BLOB.to_vec())); - - drop(rows); - drop(stmt); - - assert_eq!( - db.with_transaction(TransactionBehavior::Immediate, |tx| { - BlobMetaData::load_from_db(id, tx).no_gc() - }) - .expect("Should find blob metadata."), - blob_metadata - ); - Ok(()) - } - - static TEST_ALIAS: &str = "my super duper key"; - - #[test] - fn test_insert_and_load_full_keyentry_domain_app() -> Result<()> { - let mut db = new_test_db()?; - let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None) - .context("test_insert_and_load_full_keyentry_domain_app")? - .0; - let (_key_guard, key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - 1, - |_k, _av| Ok(()), - ) - .unwrap(); - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - - db.unbind_key( - &KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - 1, - |_, _| Ok(()), - ) - .unwrap(); - - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::NONE, - 1, - |_k, _av| Ok(()), - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - #[test] - fn test_insert_and_load_certificate_entry_domain_app() -> Result<()> { - let mut db = new_test_db()?; - - db.store_new_certificate( - &KeyDescriptor { - domain: Domain::APP, - nspace: 1, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - TEST_CERT_BLOB, - &KEYSTORE_UUID, - ) - .expect("Trying to insert cert."); - - let (_key_guard, mut key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: 1, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::PUBLIC, - 1, - |_k, _av| Ok(()), - ) - .expect("Trying to read certificate entry."); - - assert!(key_entry.pure_cert()); - assert!(key_entry.cert().is_none()); - assert_eq!(key_entry.take_cert_chain(), Some(TEST_CERT_BLOB.to_vec())); - - db.unbind_key( - &KeyDescriptor { - domain: Domain::APP, - nspace: 1, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - 1, - |_, _| Ok(()), - ) - .unwrap(); - - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: 1, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::NONE, - 1, - |_k, _av| Ok(()), - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - #[test] - fn test_insert_and_load_full_keyentry_domain_selinux() -> Result<()> { - let mut db = new_test_db()?; - let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None) - .context("test_insert_and_load_full_keyentry_domain_selinux")? - .0; - let (_key_guard, key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::SELINUX, - nspace: 1, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - 1, - |_k, _av| Ok(()), - ) - .unwrap(); - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - - db.unbind_key( - &KeyDescriptor { - domain: Domain::SELINUX, - nspace: 1, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - 1, - |_, _| Ok(()), - ) - .unwrap(); - - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &KeyDescriptor { - domain: Domain::SELINUX, - nspace: 1, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::NONE, - 1, - |_k, _av| Ok(()), - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - #[test] - fn test_insert_and_load_full_keyentry_domain_key_id() -> Result<()> { - let mut db = new_test_db()?; - let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None) - .context("test_insert_and_load_full_keyentry_domain_key_id")? - .0; - let (_, key_entry) = db - .load_key_entry( - &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - 1, - |_k, _av| Ok(()), - ) - .unwrap(); - - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - - db.unbind_key( - &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None }, - KeyType::Client, - 1, - |_, _| Ok(()), - ) - .unwrap(); - - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None }, - KeyType::Client, - KeyEntryLoadBits::NONE, - 1, - |_k, _av| Ok(()), - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - #[test] - fn test_check_and_update_key_usage_count_with_limited_use_key() -> Result<()> { - let mut db = new_test_db()?; - let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(123)) - .context("test_check_and_update_key_usage_count_with_limited_use_key")? - .0; - // Update the usage count of the limited use key. - db.check_and_update_key_usage_count(key_id)?; - - let (_key_guard, key_entry) = db.load_key_entry( - &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - 1, - |_k, _av| Ok(()), - )?; - - // The usage count is decremented now. - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, Some(122))); - - Ok(()) - } - - #[test] - fn test_check_and_update_key_usage_count_with_exhausted_limited_use_key() -> Result<()> { - let mut db = new_test_db()?; - let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(1)) - .context("test_check_and_update_key_usage_count_with_exhausted_limited_use_key")? - .0; - // Update the usage count of the limited use key. - db.check_and_update_key_usage_count(key_id).expect(concat!( - "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ", - "This should succeed." - )); - - // Try to update the exhausted limited use key. - let e = db.check_and_update_key_usage_count(key_id).expect_err(concat!( - "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ", - "This should fail." - )); - assert_eq!( - &KsError::Km(ErrorCode::INVALID_KEY_BLOB), - e.root_cause().downcast_ref::<KsError>().unwrap() - ); - - Ok(()) - } - - #[test] - fn test_insert_and_load_full_keyentry_from_grant() -> Result<()> { - let mut db = new_test_db()?; - let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None) - .context("test_insert_and_load_full_keyentry_from_grant")? - .0; - - let granted_key = db - .grant( - &KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - 1, - 2, - key_perm_set![KeyPerm::Use], - |_k, _av| Ok(()), - ) - .unwrap(); - - debug_dump_grant_table(&mut db)?; - - let (_key_guard, key_entry) = db - .load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| { - assert_eq!(Domain::GRANT, k.domain); - assert!(av.unwrap().includes(KeyPerm::Use)); - Ok(()) - }) - .unwrap(); - - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - - db.unbind_key(&granted_key, KeyType::Client, 2, |_, _| Ok(())).unwrap(); - - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &granted_key, - KeyType::Client, - KeyEntryLoadBits::NONE, - 2, - |_k, _av| Ok(()), - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - // This test attempts to load a key by key id while the caller is not the owner - // but a grant exists for the given key and the caller. - #[test] - fn test_insert_and_load_full_keyentry_from_grant_by_key_id() -> Result<()> { - let mut db = new_test_db()?; - const OWNER_UID: u32 = 1u32; - const GRANTEE_UID: u32 = 2u32; - const SOMEONE_ELSE_UID: u32 = 3u32; - let key_id = make_test_key_entry(&mut db, Domain::APP, OWNER_UID as i64, TEST_ALIAS, None) - .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")? - .0; - - db.grant( - &KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - OWNER_UID, - GRANTEE_UID, - key_perm_set![KeyPerm::Use], - |_k, _av| Ok(()), - ) - .unwrap(); - - debug_dump_grant_table(&mut db)?; - - let id_descriptor = - KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, ..Default::default() }; - - let (_, key_entry) = db - .load_key_entry( - &id_descriptor, - KeyType::Client, - KeyEntryLoadBits::BOTH, - GRANTEE_UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(OWNER_UID as i64, k.nspace); - assert!(av.unwrap().includes(KeyPerm::Use)); - Ok(()) - }, - ) - .unwrap(); - - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - - let (_, key_entry) = db - .load_key_entry( - &id_descriptor, - KeyType::Client, - KeyEntryLoadBits::BOTH, - SOMEONE_ELSE_UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(OWNER_UID as i64, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap(); - - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - - db.unbind_key(&id_descriptor, KeyType::Client, OWNER_UID, |_, _| Ok(())).unwrap(); - - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &id_descriptor, - KeyType::Client, - KeyEntryLoadBits::NONE, - GRANTEE_UID, - |_k, _av| Ok(()), - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - // Creates a key migrates it to a different location and then tries to access it by the old - // and new location. - #[test] - fn test_migrate_key_app_to_app() -> Result<()> { - let mut db = new_test_db()?; - const SOURCE_UID: u32 = 1u32; - const DESTINATION_UID: u32 = 2u32; - static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; - static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; - let key_id_guard = - make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) - .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; - - let source_descriptor: KeyDescriptor = KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(SOURCE_ALIAS.to_string()), - blob: None, - }; - - let destination_descriptor: KeyDescriptor = KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(DESTINATION_ALIAS.to_string()), - blob: None, - }; - - let key_id = key_id_guard.id(); - - db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| { - Ok(()) - }) - .unwrap(); - - let (_, key_entry) = db - .load_key_entry( - &destination_descriptor, - KeyType::Client, - KeyEntryLoadBits::BOTH, - DESTINATION_UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(DESTINATION_UID as i64, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap(); - - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &source_descriptor, - KeyType::Client, - KeyEntryLoadBits::NONE, - SOURCE_UID, - |_k, _av| Ok(()), - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - // Creates a key migrates it to a different location and then tries to access it by the old - // and new location. - #[test] - fn test_migrate_key_app_to_selinux() -> Result<()> { - let mut db = new_test_db()?; - const SOURCE_UID: u32 = 1u32; - const DESTINATION_UID: u32 = 2u32; - const DESTINATION_NAMESPACE: i64 = 1000i64; - static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; - static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; - let key_id_guard = - make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) - .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; - - let source_descriptor: KeyDescriptor = KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(SOURCE_ALIAS.to_string()), - blob: None, - }; - - let destination_descriptor: KeyDescriptor = KeyDescriptor { - domain: Domain::SELINUX, - nspace: DESTINATION_NAMESPACE, - alias: Some(DESTINATION_ALIAS.to_string()), - blob: None, - }; - - let key_id = key_id_guard.id(); - - db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| { - Ok(()) - }) - .unwrap(); - - let (_, key_entry) = db - .load_key_entry( - &destination_descriptor, - KeyType::Client, - KeyEntryLoadBits::BOTH, - DESTINATION_UID, - |k, av| { - assert_eq!(Domain::SELINUX, k.domain); - assert_eq!(DESTINATION_NAMESPACE, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap(); - - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &source_descriptor, - KeyType::Client, - KeyEntryLoadBits::NONE, - SOURCE_UID, - |_k, _av| Ok(()), - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - // Creates two keys and tries to migrate the first to the location of the second which - // is expected to fail. - #[test] - fn test_migrate_key_destination_occupied() -> Result<()> { - let mut db = new_test_db()?; - const SOURCE_UID: u32 = 1u32; - const DESTINATION_UID: u32 = 2u32; - static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; - static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; - let key_id_guard = - make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) - .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; - make_test_key_entry(&mut db, Domain::APP, DESTINATION_UID as i64, DESTINATION_ALIAS, None) - .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; - - let destination_descriptor: KeyDescriptor = KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(DESTINATION_ALIAS.to_string()), - blob: None, - }; - - assert_eq!( - Some(&KsError::Rc(ResponseCode::INVALID_ARGUMENT)), - db.migrate_key_namespace( - key_id_guard, - &destination_descriptor, - DESTINATION_UID, - |_k| Ok(()) - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - - Ok(()) - } - - #[test] - fn test_upgrade_0_to_1() { - const ALIAS1: &str = "test_upgrade_0_to_1_1"; - const ALIAS2: &str = "test_upgrade_0_to_1_2"; - const ALIAS3: &str = "test_upgrade_0_to_1_3"; - const UID: u32 = 33; - let temp_dir = Arc::new(TempDir::new("test_upgrade_0_to_1").unwrap()); - let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap(); - let key_id_untouched1 = - make_test_key_entry(&mut db, Domain::APP, UID as i64, ALIAS1, None).unwrap().id(); - let key_id_untouched2 = - make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS2, false).unwrap().id(); - let key_id_deleted = - make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS3, true).unwrap().id(); - - let (_, key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(ALIAS1.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(UID as i64, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap(); - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None)); - let (_, key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(ALIAS2.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(UID as i64, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap(); - assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false)); - let (_, key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(ALIAS3.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(UID as i64, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap(); - assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_deleted, true)); - - db.with_transaction(TransactionBehavior::Immediate, |tx| { - KeystoreDB::from_0_to_1(tx).no_gc() - }) - .unwrap(); - - let (_, key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(ALIAS1.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(UID as i64, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap(); - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None)); - let (_, key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(ALIAS2.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(UID as i64, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap(); - assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false)); - assert_eq!( - Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), - db.load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(ALIAS3.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - UID, - |k, av| { - assert_eq!(Domain::APP, k.domain); - assert_eq!(UID as i64, k.nspace); - assert!(av.is_none()); - Ok(()) - }, - ) - .unwrap_err() - .root_cause() - .downcast_ref::<KsError>() - ); - } - - static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key"; - - #[test] - fn test_insert_and_load_full_keyentry_domain_app_concurrently() -> Result<()> { - let handle = { - let temp_dir = Arc::new(TempDir::new("id_lock_test")?); - let temp_dir_clone = temp_dir.clone(); - let mut db = KeystoreDB::new(temp_dir.path(), None)?; - let key_id = make_test_key_entry(&mut db, Domain::APP, 33, KEY_LOCK_TEST_ALIAS, None) - .context("test_insert_and_load_full_keyentry_domain_app")? - .0; - let (_key_guard, key_entry) = db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(KEY_LOCK_TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - 33, - |_k, _av| Ok(()), - ) - .unwrap(); - assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); - let state = Arc::new(AtomicU8::new(1)); - let state2 = state.clone(); - - // Spawning a second thread that attempts to acquire the key id lock - // for the same key as the primary thread. The primary thread then - // waits, thereby forcing the secondary thread into the second stage - // of acquiring the lock (see KEY ID LOCK 2/2 above). - // The test succeeds if the secondary thread observes the transition - // of `state` from 1 to 2, despite having a whole second to overtake - // the primary thread. - let handle = thread::spawn(move || { - let temp_dir = temp_dir_clone; - let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap(); - assert!(db - .load_key_entry( - &KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(KEY_LOCK_TEST_ALIAS.to_string()), - blob: None, - }, - KeyType::Client, - KeyEntryLoadBits::BOTH, - 33, - |_k, _av| Ok(()), - ) - .is_ok()); - // We should only see a 2 here because we can only return - // from load_key_entry when the `_key_guard` expires, - // which happens at the end of the scope. - assert_eq!(2, state2.load(Ordering::Relaxed)); - }); - - thread::sleep(std::time::Duration::from_millis(1000)); - - assert_eq!(Ok(1), state.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed)); - - // Return the handle from this scope so we can join with the - // secondary thread after the key id lock has expired. - handle - // This is where the `_key_guard` goes out of scope, - // which is the reason for concurrent load_key_entry on the same key - // to unblock. - }; - // Join with the secondary thread and unwrap, to propagate failing asserts to the - // main test thread. We will not see failing asserts in secondary threads otherwise. - handle.join().unwrap(); - Ok(()) - } - - #[test] - fn test_database_busy_error_code() { - let temp_dir = - TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir."); - - let mut db1 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database1."); - let mut db2 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database2."); - - let _tx1 = db1 - .conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .expect("Failed to create first transaction."); - - let error = db2 - .conn - .transaction_with_behavior(TransactionBehavior::Immediate) - .context("Transaction begin failed.") - .expect_err("This should fail."); - let root_cause = error.root_cause(); - if let Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. }) = - root_cause.downcast_ref::<rusqlite::ffi::Error>() - { - return; - } - panic!( - "Unexpected error {:?} \n{:?} \n{:?}", - error, - root_cause, - root_cause.downcast_ref::<rusqlite::ffi::Error>() - ) - } - - #[cfg(disabled)] - #[test] - fn test_large_number_of_concurrent_db_manipulations() -> Result<()> { - let temp_dir = Arc::new( - TempDir::new("test_large_number_of_concurrent_db_manipulations_") - .expect("Failed to create temp dir."), - ); - - let test_begin = Instant::now(); - - const KEY_COUNT: u32 = 500u32; - let mut db = - new_test_db_with_gc(temp_dir.path(), |_, _| Ok(())).expect("Failed to open database."); - const OPEN_DB_COUNT: u32 = 50u32; - - let mut actual_key_count = KEY_COUNT; - // First insert KEY_COUNT keys. - for count in 0..KEY_COUNT { - if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) { - actual_key_count = count; - break; - } - let alias = format!("test_alias_{}", count); - make_test_key_entry(&mut db, Domain::APP, 1, &alias, None) - .expect("Failed to make key entry."); - } - - // Insert more keys from a different thread and into a different namespace. - let temp_dir1 = temp_dir.clone(); - let handle1 = thread::spawn(move || { - let mut db = new_test_db_with_gc(temp_dir1.path(), |_, _| Ok(())) - .expect("Failed to open database."); - - for count in 0..actual_key_count { - if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { - return; - } - let alias = format!("test_alias_{}", count); - make_test_key_entry(&mut db, Domain::APP, 2, &alias, None) - .expect("Failed to make key entry."); - } - - // then unbind them again. - for count in 0..actual_key_count { - if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { - return; - } - let key = KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(format!("test_alias_{}", count)), - blob: None, - }; - db.unbind_key(&key, KeyType::Client, 2, |_, _| Ok(())).expect("Unbind Failed."); - } - }); - - // And start unbinding the first set of keys. - let temp_dir2 = temp_dir.clone(); - let handle2 = thread::spawn(move || { - let mut db = new_test_db_with_gc(temp_dir2.path(), |_, _| Ok(())) - .expect("Failed to open database."); - - for count in 0..actual_key_count { - if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { - return; - } - let key = KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(format!("test_alias_{}", count)), - blob: None, - }; - db.unbind_key(&key, KeyType::Client, 1, |_, _| Ok(())).expect("Unbind Failed."); - } - }); - - // While a lot of inserting and deleting is going on we have to open database connections - // successfully and use them. - // This clone is not redundant, because temp_dir needs to be kept alive until db goes - // out of scope. - #[allow(clippy::redundant_clone)] - let temp_dir4 = temp_dir.clone(); - let handle4 = thread::spawn(move || { - for count in 0..OPEN_DB_COUNT { - if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { - return; - } - let mut db = new_test_db_with_gc(temp_dir4.path(), |_, _| Ok(())) - .expect("Failed to open database."); - - let alias = format!("test_alias_{}", count); - make_test_key_entry(&mut db, Domain::APP, 3, &alias, None) - .expect("Failed to make key entry."); - let key = KeyDescriptor { - domain: Domain::APP, - nspace: -1, - alias: Some(alias), - blob: None, - }; - db.unbind_key(&key, KeyType::Client, 3, |_, _| Ok(())).expect("Unbind Failed."); - } - }); - - handle1.join().expect("Thread 1 panicked."); - handle2.join().expect("Thread 2 panicked."); - handle4.join().expect("Thread 4 panicked."); - - Ok(()) - } - - #[test] - fn list() -> Result<()> { - let temp_dir = TempDir::new("list_test")?; - let mut db = KeystoreDB::new(temp_dir.path(), None)?; - 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, None).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_past_alias(*domain, *namespace, KeyType::Client, None)?; - 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, - KeyType::Client, - 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_past_alias(Domain::SELINUX, 101, KeyType::Client, None)? - ); - - Ok(()) - } - - // Helpers - - // Checks that the given result is an error containing the given string. - fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) { - let error_str = format!( - "{:#?}", - result.err().unwrap_or_else(|| panic!("Expected the error: {}", target)) - ); - assert!( - error_str.contains(target), - "The string \"{}\" should contain \"{}\"", - error_str, - target - ); - } - - #[derive(Debug, PartialEq)] - struct KeyEntryRow { - id: i64, - key_type: KeyType, - domain: Option<Domain>, - namespace: Option<i64>, - alias: Option<String>, - state: KeyLifeCycle, - km_uuid: Option<Uuid>, - } - - fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> { - db.conn - .prepare("SELECT * FROM persistent.keyentry;")? - .query_map([], |row| { - Ok(KeyEntryRow { - id: row.get(0)?, - key_type: row.get(1)?, - domain: row.get::<_, Option<_>>(2)?.map(Domain), - namespace: row.get(3)?, - alias: row.get(4)?, - state: row.get(5)?, - km_uuid: row.get(6)?, - }) - })? - .map(|r| r.context("Could not read keyentry row.")) - .collect::<Result<Vec<_>>>() - } - - fn make_test_params(max_usage_count: Option<i32>) -> Vec<KeyParameter> { - make_test_params_with_sids(max_usage_count, &[42]) - } - - // Note: The parameters and SecurityLevel associations are nonsensical. This - // collection is only used to check if the parameters are preserved as expected by the - // database. - fn make_test_params_with_sids( - max_usage_count: Option<i32>, - user_secure_ids: &[i64], - ) -> Vec<KeyParameter> { - let mut params = vec![ - KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::TRUSTED_ENVIRONMENT), - KeyParameter::new( - KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::Algorithm(Algorithm::RSA), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::TRUSTED_ENVIRONMENT), - KeyParameter::new( - KeyParameterValue::BlockMode(BlockMode::ECB), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::BlockMode(BlockMode::GCM), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::Digest(Digest::NONE), SecurityLevel::STRONGBOX), - KeyParameter::new( - KeyParameterValue::Digest(Digest::MD5), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::Digest(Digest::SHA_2_224), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::Digest(Digest::SHA_2_256), - SecurityLevel::STRONGBOX, - ), - KeyParameter::new( - KeyParameterValue::PaddingMode(PaddingMode::NONE), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::PaddingMode(PaddingMode::RSA_OAEP), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS), - SecurityLevel::STRONGBOX, - ), - KeyParameter::new( - KeyParameterValue::PaddingMode(PaddingMode::RSA_PKCS1_1_5_SIGN), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::TRUSTED_ENVIRONMENT), - KeyParameter::new(KeyParameterValue::MinMacLength(256), SecurityLevel::STRONGBOX), - KeyParameter::new( - KeyParameterValue::EcCurve(EcCurve::P_224), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::STRONGBOX), - KeyParameter::new( - KeyParameterValue::EcCurve(EcCurve::P_384), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::EcCurve(EcCurve::P_521), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::RSAPublicExponent(3), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::IncludeUniqueID, - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::BootLoaderOnly, SecurityLevel::STRONGBOX), - KeyParameter::new(KeyParameterValue::RollbackResistance, SecurityLevel::STRONGBOX), - KeyParameter::new( - KeyParameterValue::ActiveDateTime(1234567890), - SecurityLevel::STRONGBOX, - ), - KeyParameter::new( - KeyParameterValue::OriginationExpireDateTime(1234567890), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::UsageExpireDateTime(1234567890), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::MinSecondsBetweenOps(1234567890), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::MaxUsesPerBoot(1234567890), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::UserID(1), SecurityLevel::STRONGBOX), - KeyParameter::new( - KeyParameterValue::NoAuthRequired, - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::PASSWORD), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::AuthTimeout(1234567890), SecurityLevel::SOFTWARE), - KeyParameter::new(KeyParameterValue::AllowWhileOnBody, SecurityLevel::SOFTWARE), - KeyParameter::new( - KeyParameterValue::TrustedUserPresenceRequired, - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::TrustedConfirmationRequired, - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::UnlockedDeviceRequired, - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::ApplicationID(vec![1u8, 2u8, 3u8, 4u8]), - SecurityLevel::SOFTWARE, - ), - KeyParameter::new( - KeyParameterValue::ApplicationData(vec![4u8, 3u8, 2u8, 1u8]), - SecurityLevel::SOFTWARE, - ), - KeyParameter::new( - KeyParameterValue::CreationDateTime(12345677890), - SecurityLevel::SOFTWARE, - ), - KeyParameter::new( - KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::RootOfTrust(vec![3u8, 2u8, 1u8, 4u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new(KeyParameterValue::OSVersion(1), SecurityLevel::TRUSTED_ENVIRONMENT), - KeyParameter::new(KeyParameterValue::OSPatchLevel(2), SecurityLevel::SOFTWARE), - KeyParameter::new( - KeyParameterValue::UniqueID(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::SOFTWARE, - ), - KeyParameter::new( - KeyParameterValue::AttestationChallenge(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationApplicationID(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdBrand(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdDevice(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdProduct(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdSerial(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdIMEI(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdSecondIMEI(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdMEID(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdManufacturer(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AttestationIdModel(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::VendorPatchLevel(3), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::BootPatchLevel(4), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::AssociatedData(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::Nonce(vec![4u8, 3u8, 1u8, 2u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::MacLength(256), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::ResetSinceIdRotation, - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - KeyParameter::new( - KeyParameterValue::ConfirmationToken(vec![5u8, 5u8, 5u8, 5u8]), - SecurityLevel::TRUSTED_ENVIRONMENT, - ), - ]; - if let Some(value) = max_usage_count { - params.push(KeyParameter::new( - KeyParameterValue::UsageCountLimit(value), - SecurityLevel::SOFTWARE, - )); - } - - for sid in user_secure_ids.iter() { - params.push(KeyParameter::new( - KeyParameterValue::UserSecureID(*sid), - SecurityLevel::STRONGBOX, - )); - } - params - } - - pub fn make_test_key_entry( - db: &mut KeystoreDB, - domain: Domain, - namespace: i64, - alias: &str, - max_usage_count: Option<i32>, - ) -> Result<KeyIdGuard> { - make_test_key_entry_with_sids(db, domain, namespace, alias, max_usage_count, &[42]) - } - - pub fn make_test_key_entry_with_sids( - db: &mut KeystoreDB, - domain: Domain, - namespace: i64, - alias: &str, - max_usage_count: Option<i32>, - sids: &[i64], - ) -> Result<KeyIdGuard> { - let key_id = db.create_key_entry(&domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?; - let mut blob_metadata = BlobMetaData::new(); - blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); - blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3])); - blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1])); - blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2])); - blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); - - db.set_blob( - &key_id, - SubComponentType::KEY_BLOB, - Some(TEST_KEY_BLOB), - Some(&blob_metadata), - )?; - db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?; - db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?; - - let params = make_test_params_with_sids(max_usage_count, sids); - db.insert_keyparameter(&key_id, ¶ms)?; - - let mut metadata = KeyMetaData::new(); - metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); - db.insert_key_metadata(&key_id, &metadata)?; - rebind_alias(db, &key_id, alias, domain, namespace)?; - Ok(key_id) - } - - fn make_test_key_entry_test_vector(key_id: i64, max_usage_count: Option<i32>) -> KeyEntry { - let params = make_test_params(max_usage_count); - - let mut blob_metadata = BlobMetaData::new(); - blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); - blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3])); - blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1])); - blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2])); - blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); - - let mut metadata = KeyMetaData::new(); - metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); - - KeyEntry { - id: key_id, - key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)), - cert: Some(TEST_CERT_BLOB.to_vec()), - cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()), - km_uuid: KEYSTORE_UUID, - parameters: params, - metadata, - pure_cert: false, - } - } - - pub fn make_bootlevel_key_entry( - db: &mut KeystoreDB, - domain: Domain, - namespace: i64, - alias: &str, - logical_only: bool, - ) -> Result<KeyIdGuard> { - let key_id = db.create_key_entry(&domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?; - let mut blob_metadata = BlobMetaData::new(); - if !logical_only { - blob_metadata.add(BlobMetaEntry::MaxBootLevel(3)); - } - blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); - - db.set_blob( - &key_id, - SubComponentType::KEY_BLOB, - Some(TEST_KEY_BLOB), - Some(&blob_metadata), - )?; - db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?; - db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?; - - let mut params = make_test_params(None); - params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE)); - - db.insert_keyparameter(&key_id, ¶ms)?; - - let mut metadata = KeyMetaData::new(); - metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); - db.insert_key_metadata(&key_id, &metadata)?; - rebind_alias(db, &key_id, alias, domain, namespace)?; - Ok(key_id) - } - - // Creates an app key that is marked as being superencrypted by the given - // super key ID and that has the given authentication and unlocked device - // parameters. This does not actually superencrypt the key blob. - fn make_superencrypted_key_entry( - db: &mut KeystoreDB, - namespace: i64, - alias: &str, - requires_authentication: bool, - requires_unlocked_device: bool, - super_key_id: i64, - ) -> Result<KeyIdGuard> { - let domain = Domain::APP; - let key_id = db.create_key_entry(&domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?; - - let mut blob_metadata = BlobMetaData::new(); - blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); - blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id))); - db.set_blob( - &key_id, - SubComponentType::KEY_BLOB, - Some(TEST_KEY_BLOB), - Some(&blob_metadata), - )?; - - let mut params = vec![]; - if requires_unlocked_device { - params.push(KeyParameter::new( - KeyParameterValue::UnlockedDeviceRequired, - SecurityLevel::TRUSTED_ENVIRONMENT, - )); - } - if requires_authentication { - params.push(KeyParameter::new( - KeyParameterValue::UserSecureID(42), - SecurityLevel::TRUSTED_ENVIRONMENT, - )); - } - db.insert_keyparameter(&key_id, ¶ms)?; - - let mut metadata = KeyMetaData::new(); - metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); - db.insert_key_metadata(&key_id, &metadata)?; - - rebind_alias(db, &key_id, alias, domain, namespace)?; - Ok(key_id) - } - - fn make_bootlevel_test_key_entry_test_vector(key_id: i64, logical_only: bool) -> KeyEntry { - let mut params = make_test_params(None); - params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE)); - - let mut blob_metadata = BlobMetaData::new(); - if !logical_only { - blob_metadata.add(BlobMetaEntry::MaxBootLevel(3)); - } - blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); - - let mut metadata = KeyMetaData::new(); - metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); - - KeyEntry { - id: key_id, - key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)), - cert: Some(TEST_CERT_BLOB.to_vec()), - cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()), - km_uuid: KEYSTORE_UUID, - parameters: params, - metadata, - pure_cert: false, - } - } - - fn debug_dump_keyentry_table(db: &mut KeystoreDB) -> Result<()> { - let mut stmt = db.conn.prepare( - "SELECT id, key_type, domain, namespace, alias, state, km_uuid FROM persistent.keyentry;", - )?; - let rows = stmt.query_map::<(i64, KeyType, i32, i64, String, KeyLifeCycle, Uuid), _, _>( - [], - |row| { - Ok(( - row.get(0)?, - row.get(1)?, - row.get(2)?, - row.get(3)?, - row.get(4)?, - row.get(5)?, - row.get(6)?, - )) - }, - )?; - - println!("Key entry table rows:"); - for r in rows { - let (id, key_type, domain, namespace, alias, state, km_uuid) = r.unwrap(); - println!( - " id: {} KeyType: {:?} Domain: {} Namespace: {} Alias: {} State: {:?} KmUuid: {:?}", - id, key_type, domain, namespace, alias, state, km_uuid - ); - } - Ok(()) - } - - fn debug_dump_grant_table(db: &mut KeystoreDB) -> Result<()> { - let mut stmt = db - .conn - .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?; - let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>([], |row| { - Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) - })?; - - println!("Grant table rows:"); - for r in rows { - let (id, gt, ki, av) = r.unwrap(); - println!(" id: {} grantee: {} key_id: {} access_vector: {}", id, gt, ki, av); - } - Ok(()) - } - - // Use a custom random number generator that repeats each number once. - // This allows us to test repeated elements. - - thread_local! { - static RANDOM_COUNTER: RefCell<i64> = RefCell::new(0); - } - - fn reset_random() { - RANDOM_COUNTER.with(|counter| { - *counter.borrow_mut() = 0; - }) - } - - pub fn random() -> i64 { - RANDOM_COUNTER.with(|counter| { - let result = *counter.borrow() / 2; - *counter.borrow_mut() += 1; - result - }) - } - - #[test] - fn test_last_off_body() -> Result<()> { - let mut db = new_test_db()?; - db.insert_last_off_body(BootTime::now()); - let tx = db.conn.transaction_with_behavior(TransactionBehavior::Immediate)?; - tx.commit()?; - let last_off_body_1 = db.get_last_off_body(); - let one_second = Duration::from_secs(1); - thread::sleep(one_second); - db.update_last_off_body(BootTime::now()); - let tx2 = db.conn.transaction_with_behavior(TransactionBehavior::Immediate)?; - tx2.commit()?; - let last_off_body_2 = db.get_last_off_body(); - assert!(last_off_body_1 < last_off_body_2); - Ok(()) - } - - #[test] - fn test_unbind_keys_for_user() -> Result<()> { - let mut db = new_test_db()?; - db.unbind_keys_for_user(1, false)?; - - make_test_key_entry(&mut db, Domain::APP, 210000, TEST_ALIAS, None)?; - make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?; - db.unbind_keys_for_user(2, false)?; - - assert_eq!(1, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len()); - assert_eq!(0, db.list_past_alias(Domain::APP, 210000, KeyType::Client, None)?.len()); - - db.unbind_keys_for_user(1, true)?; - assert_eq!(0, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len()); - - Ok(()) - } - - #[test] - fn test_unbind_keys_for_user_removes_superkeys() -> Result<()> { - let mut db = new_test_db()?; - let super_key = keystore2_crypto::generate_aes256_key()?; - let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); - let (encrypted_super_key, metadata) = - SuperKeyManager::encrypt_with_password(&super_key, &pw)?; - - let key_name_enc = SuperKeyType { - alias: "test_super_key_1", - algorithm: SuperEncryptionAlgorithm::Aes256Gcm, - name: "test_super_key_1", - }; - - let key_name_nonenc = SuperKeyType { - alias: "test_super_key_2", - algorithm: SuperEncryptionAlgorithm::Aes256Gcm, - name: "test_super_key_2", - }; - - // Install two super keys. - db.store_super_key( - 1, - &key_name_nonenc, - &super_key, - &BlobMetaData::new(), - &KeyMetaData::new(), - )?; - db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?; - - // Check that both can be found in the database. - assert!(db.load_super_key(&key_name_enc, 1)?.is_some()); - assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some()); - - // Install the same keys for a different user. - db.store_super_key( - 2, - &key_name_nonenc, - &super_key, - &BlobMetaData::new(), - &KeyMetaData::new(), - )?; - db.store_super_key(2, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?; - - // Check that the second pair of keys can be found in the database. - assert!(db.load_super_key(&key_name_enc, 2)?.is_some()); - assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some()); - - // Delete only encrypted keys. - db.unbind_keys_for_user(1, true)?; - - // The encrypted superkey should be gone now. - assert!(db.load_super_key(&key_name_enc, 1)?.is_none()); - assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some()); - - // Reinsert the encrypted key. - db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?; - - // Check that both can be found in the database, again.. - assert!(db.load_super_key(&key_name_enc, 1)?.is_some()); - assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some()); - - // Delete all even unencrypted keys. - db.unbind_keys_for_user(1, false)?; - - // Both should be gone now. - assert!(db.load_super_key(&key_name_enc, 1)?.is_none()); - assert!(db.load_super_key(&key_name_nonenc, 1)?.is_none()); - - // Check that the second pair of keys was untouched. - assert!(db.load_super_key(&key_name_enc, 2)?.is_some()); - assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some()); - - Ok(()) - } - - fn app_key_exists(db: &mut KeystoreDB, nspace: i64, alias: &str) -> Result<bool> { - db.key_exists(Domain::APP, nspace, alias, KeyType::Client) - } - - // Tests the unbind_auth_bound_keys_for_user() function. - #[test] - fn test_unbind_auth_bound_keys_for_user() -> Result<()> { - let mut db = new_test_db()?; - let user_id = 1; - let nspace: i64 = (user_id * AID_USER_OFFSET).into(); - let other_user_id = 2; - let other_user_nspace: i64 = (other_user_id * AID_USER_OFFSET).into(); - let super_key_type = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY; - - // Create a superencryption key. - let super_key = keystore2_crypto::generate_aes256_key()?; - let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); - let (encrypted_super_key, blob_metadata) = - SuperKeyManager::encrypt_with_password(&super_key, &pw)?; - db.store_super_key( - user_id, - super_key_type, - &encrypted_super_key, - &blob_metadata, - &KeyMetaData::new(), - )?; - let super_key_id = db.load_super_key(super_key_type, user_id)?.unwrap().0 .0; - - // Store 4 superencrypted app keys, one for each possible combination of - // (authentication required, unlocked device required). - make_superencrypted_key_entry(&mut db, nspace, "noauth_noud", false, false, super_key_id)?; - make_superencrypted_key_entry(&mut db, nspace, "noauth_ud", false, true, super_key_id)?; - make_superencrypted_key_entry(&mut db, nspace, "auth_noud", true, false, super_key_id)?; - make_superencrypted_key_entry(&mut db, nspace, "auth_ud", true, true, super_key_id)?; - assert!(app_key_exists(&mut db, nspace, "noauth_noud")?); - assert!(app_key_exists(&mut db, nspace, "noauth_ud")?); - assert!(app_key_exists(&mut db, nspace, "auth_noud")?); - assert!(app_key_exists(&mut db, nspace, "auth_ud")?); - - // Also store a key for a different user that requires authentication. - make_superencrypted_key_entry( - &mut db, - other_user_nspace, - "auth_ud", - true, - true, - super_key_id, - )?; - - db.unbind_auth_bound_keys_for_user(user_id)?; - - // Verify that only the user's app keys that require authentication were - // deleted. Keys that require an unlocked device but not authentication - // should *not* have been deleted, nor should the super key have been - // deleted, nor should other users' keys have been deleted. - assert!(db.load_super_key(super_key_type, user_id)?.is_some()); - assert!(app_key_exists(&mut db, nspace, "noauth_noud")?); - assert!(app_key_exists(&mut db, nspace, "noauth_ud")?); - assert!(!app_key_exists(&mut db, nspace, "auth_noud")?); - assert!(!app_key_exists(&mut db, nspace, "auth_ud")?); - assert!(app_key_exists(&mut db, other_user_nspace, "auth_ud")?); - - Ok(()) - } - - #[test] - fn test_store_super_key() -> Result<()> { - let mut db = new_test_db()?; - let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); - let super_key = keystore2_crypto::generate_aes256_key()?; - let secret_bytes = b"keystore2 is great."; - let (encrypted_secret, iv, tag) = - keystore2_crypto::aes_gcm_encrypt(secret_bytes, &super_key)?; - - let (encrypted_super_key, metadata) = - SuperKeyManager::encrypt_with_password(&super_key, &pw)?; - db.store_super_key( - 1, - &USER_AFTER_FIRST_UNLOCK_SUPER_KEY, - &encrypted_super_key, - &metadata, - &KeyMetaData::new(), - )?; - - // Check if super key exists. - assert!(db.key_exists( - Domain::APP, - 1, - USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias, - KeyType::Super - )?); - - let (_, key_entry) = db.load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, 1)?.unwrap(); - let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry( - USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm, - key_entry, - &pw, - None, - )?; - - let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?; - assert_eq!(secret_bytes, &*decrypted_secret_bytes); - - Ok(()) - } - - fn get_valid_statsd_storage_types() -> Vec<MetricsStorage> { - vec![ - MetricsStorage::KEY_ENTRY, - MetricsStorage::KEY_ENTRY_ID_INDEX, - MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX, - MetricsStorage::BLOB_ENTRY, - MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX, - MetricsStorage::KEY_PARAMETER, - MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX, - MetricsStorage::KEY_METADATA, - MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX, - MetricsStorage::GRANT, - MetricsStorage::AUTH_TOKEN, - MetricsStorage::BLOB_METADATA, - MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX, - ] - } - - /// Perform a simple check to ensure that we can query all the storage types - /// that are supported by the DB. Check for reasonable values. - #[test] - fn test_query_all_valid_table_sizes() -> Result<()> { - const PAGE_SIZE: i32 = 4096; - - let mut db = new_test_db()?; - - for t in get_valid_statsd_storage_types() { - let stat = db.get_storage_stat(t)?; - // AuthToken can be less than a page since it's in a btree, not sqlite - // TODO(b/187474736) stop using if-let here - if let MetricsStorage::AUTH_TOKEN = t { - } else { - assert!(stat.size >= PAGE_SIZE); - } - assert!(stat.size >= stat.unused_size); - } - - Ok(()) - } - - fn get_storage_stats_map(db: &mut KeystoreDB) -> BTreeMap<i32, StorageStats> { - get_valid_statsd_storage_types() - .into_iter() - .map(|t| (t.0, db.get_storage_stat(t).unwrap())) - .collect() - } - - fn assert_storage_increased( - db: &mut KeystoreDB, - increased_storage_types: Vec<MetricsStorage>, - baseline: &mut BTreeMap<i32, StorageStats>, - ) { - for storage in increased_storage_types { - // Verify the expected storage increased. - let new = db.get_storage_stat(storage).unwrap(); - let old = &baseline[&storage.0]; - assert!(new.size >= old.size, "{}: {} >= {}", storage.0, new.size, old.size); - assert!( - new.unused_size <= old.unused_size, - "{}: {} <= {}", - storage.0, - new.unused_size, - old.unused_size - ); - - // Update the baseline with the new value so that it succeeds in the - // later comparison. - baseline.insert(storage.0, new); - } - - // Get an updated map of the storage and verify there were no unexpected changes. - let updated_stats = get_storage_stats_map(db); - assert_eq!(updated_stats.len(), baseline.len()); - - for &k in baseline.keys() { - let stringify = |map: &BTreeMap<i32, StorageStats>| -> String { - let mut s = String::new(); - for &k in map.keys() { - writeln!(&mut s, " {}: {}, {}", &k, map[&k].size, map[&k].unused_size) - .expect("string concat failed"); - } - s - }; - - assert!( - updated_stats[&k].size == baseline[&k].size - && updated_stats[&k].unused_size == baseline[&k].unused_size, - "updated_stats:\n{}\nbaseline:\n{}", - stringify(&updated_stats), - stringify(baseline) - ); - } - } - - #[test] - fn test_verify_key_table_size_reporting() -> Result<()> { - let mut db = new_test_db()?; - let mut working_stats = get_storage_stats_map(&mut db); - - let key_id = db.create_key_entry(&Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?; - assert_storage_increased( - &mut db, - vec![ - MetricsStorage::KEY_ENTRY, - MetricsStorage::KEY_ENTRY_ID_INDEX, - MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX, - ], - &mut working_stats, - ); - - let mut blob_metadata = BlobMetaData::new(); - blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); - db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), None)?; - assert_storage_increased( - &mut db, - vec![ - MetricsStorage::BLOB_ENTRY, - MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX, - MetricsStorage::BLOB_METADATA, - MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX, - ], - &mut working_stats, - ); - - let params = make_test_params(None); - db.insert_keyparameter(&key_id, ¶ms)?; - assert_storage_increased( - &mut db, - vec![MetricsStorage::KEY_PARAMETER, MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX], - &mut working_stats, - ); - - let mut metadata = KeyMetaData::new(); - metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); - db.insert_key_metadata(&key_id, &metadata)?; - assert_storage_increased( - &mut db, - vec![MetricsStorage::KEY_METADATA, MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX], - &mut working_stats, - ); - - let mut sum = 0; - for stat in working_stats.values() { - sum += stat.size; - } - let total = db.get_storage_stat(MetricsStorage::DATABASE)?.size; - assert!(sum <= total, "Expected sum <= total. sum: {}, total: {}", sum, total); - - Ok(()) - } - - #[test] - fn test_verify_auth_table_size_reporting() -> Result<()> { - let mut db = new_test_db()?; - let mut working_stats = get_storage_stats_map(&mut db); - db.insert_auth_token(&HardwareAuthToken { - challenge: 123, - userId: 456, - authenticatorId: 789, - authenticatorType: kmhw_authenticator_type::ANY, - timestamp: Timestamp { milliSeconds: 10 }, - mac: b"mac".to_vec(), - }); - assert_storage_increased(&mut db, vec![MetricsStorage::AUTH_TOKEN], &mut working_stats); - Ok(()) - } - - #[test] - fn test_verify_grant_table_size_reporting() -> Result<()> { - const OWNER: i64 = 1; - let mut db = new_test_db()?; - make_test_key_entry(&mut db, Domain::APP, OWNER, TEST_ALIAS, None)?; - - let mut working_stats = get_storage_stats_map(&mut db); - db.grant( - &KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(TEST_ALIAS.to_string()), - blob: None, - }, - OWNER as u32, - 123, - key_perm_set![KeyPerm::Use], - |_, _| Ok(()), - )?; - - assert_storage_increased(&mut db, vec![MetricsStorage::GRANT], &mut working_stats); - - Ok(()) - } - - #[test] - fn find_auth_token_entry_returns_latest() -> Result<()> { - let mut db = new_test_db()?; - db.insert_auth_token(&HardwareAuthToken { - challenge: 123, - userId: 456, - authenticatorId: 789, - authenticatorType: kmhw_authenticator_type::ANY, - timestamp: Timestamp { milliSeconds: 10 }, - mac: b"mac0".to_vec(), - }); - std::thread::sleep(std::time::Duration::from_millis(1)); - db.insert_auth_token(&HardwareAuthToken { - challenge: 123, - userId: 457, - authenticatorId: 789, - authenticatorType: kmhw_authenticator_type::ANY, - timestamp: Timestamp { milliSeconds: 12 }, - mac: b"mac1".to_vec(), - }); - std::thread::sleep(std::time::Duration::from_millis(1)); - db.insert_auth_token(&HardwareAuthToken { - challenge: 123, - userId: 458, - authenticatorId: 789, - authenticatorType: kmhw_authenticator_type::ANY, - timestamp: Timestamp { milliSeconds: 3 }, - mac: b"mac2".to_vec(), - }); - // All three entries are in the database - assert_eq!(db.perboot.auth_tokens_len(), 3); - // It selected the most recent timestamp - assert_eq!(db.find_auth_token_entry(|_| true).unwrap().0.auth_token.mac, b"mac2".to_vec()); - Ok(()) - } - - #[test] - fn test_load_key_descriptor() -> Result<()> { - let mut db = new_test_db()?; - let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)?.0; - - let key = db.load_key_descriptor(key_id)?.unwrap(); - - assert_eq!(key.domain, Domain::APP); - assert_eq!(key.nspace, 1); - assert_eq!(key.alias, Some(TEST_ALIAS.to_string())); - - // No such id - assert_eq!(db.load_key_descriptor(key_id + 1)?, None); - Ok(()) - } - - #[test] - fn test_get_list_app_uids_for_sid() -> Result<()> { - let uid: i32 = 1; - let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64); - let first_sid = 667; - let second_sid = 669; - let first_app_id: i64 = 123 + uid_offset; - let second_app_id: i64 = 456 + uid_offset; - let third_app_id: i64 = 789 + uid_offset; - let unrelated_app_id: i64 = 1011 + uid_offset; - let mut db = new_test_db()?; - make_test_key_entry_with_sids( - &mut db, - Domain::APP, - first_app_id, - TEST_ALIAS, - None, - &[first_sid], - ) - .context("test_get_list_app_uids_for_sid")?; - make_test_key_entry_with_sids( - &mut db, - Domain::APP, - second_app_id, - "alias2", - None, - &[first_sid], - ) - .context("test_get_list_app_uids_for_sid")?; - make_test_key_entry_with_sids( - &mut db, - Domain::APP, - second_app_id, - TEST_ALIAS, - None, - &[second_sid], - ) - .context("test_get_list_app_uids_for_sid")?; - make_test_key_entry_with_sids( - &mut db, - Domain::APP, - third_app_id, - "alias3", - None, - &[second_sid], - ) - .context("test_get_list_app_uids_for_sid")?; - make_test_key_entry_with_sids( - &mut db, - Domain::APP, - unrelated_app_id, - TEST_ALIAS, - None, - &[], - ) - .context("test_get_list_app_uids_for_sid")?; - - let mut first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?; - first_sid_apps.sort(); - assert_eq!(first_sid_apps, vec![first_app_id, second_app_id]); - let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?; - second_sid_apps.sort(); - assert_eq!(second_sid_apps, vec![second_app_id, third_app_id]); - Ok(()) - } - - #[test] - fn test_get_list_app_uids_with_multiple_sids() -> Result<()> { - let uid: i32 = 1; - let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64); - let first_sid = 667; - let second_sid = 669; - let third_sid = 772; - let first_app_id: i64 = 123 + uid_offset; - let second_app_id: i64 = 456 + uid_offset; - let mut db = new_test_db()?; - make_test_key_entry_with_sids( - &mut db, - Domain::APP, - first_app_id, - TEST_ALIAS, - None, - &[first_sid, second_sid], - ) - .context("test_get_list_app_uids_for_sid")?; - make_test_key_entry_with_sids( - &mut db, - Domain::APP, - second_app_id, - "alias2", - None, - &[second_sid, third_sid], - ) - .context("test_get_list_app_uids_for_sid")?; - - let first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?; - assert_eq!(first_sid_apps, vec![first_app_id]); - - let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?; - second_sid_apps.sort(); - assert_eq!(second_sid_apps, vec![first_app_id, second_app_id]); - - let third_sid_apps = db.get_app_uids_affected_by_sid(uid, third_sid)?; - assert_eq!(third_sid_apps, vec![second_app_id]); - Ok(()) - } -} diff --git a/keystore2/src/database/perboot.rs b/keystore2/src/database/perboot.rs index 1b7c80d6..4727015f 100644 --- a/keystore2/src/database/perboot.rs +++ b/keystore2/src/database/perboot.rs @@ -13,15 +13,14 @@ // limitations under the License. //! This module implements a per-boot, shared, in-memory storage of auth tokens -//! and last-time-on-body for the main Keystore 2.0 database module. +//! for the main Keystore 2.0 database module. -use super::{AuthTokenEntry, BootTime}; +use super::AuthTokenEntry; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType, }; use lazy_static::lazy_static; use std::collections::HashSet; -use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::Arc; use std::sync::RwLock; @@ -62,17 +61,13 @@ impl PartialEq<AuthTokenEntryWrap> for AuthTokenEntryWrap { impl Eq for AuthTokenEntryWrap {} -/// Per-boot state structure. Currently only used to track auth tokens and -/// last-off-body. +/// Per-boot state structure. Currently only used to track auth tokens. #[derive(Default)] pub struct PerbootDB { // We can use a .unwrap() discipline on this lock, because only panicking // while holding a .write() lock will poison it. The only write usage is // an insert call which inserts a pre-constructed pair. auth_tokens: RwLock<HashSet<AuthTokenEntryWrap>>, - // Ordering::Relaxed is appropriate for accessing this atomic, since it - // does not currently need to be synchronized with anything else. - last_off_body: AtomicI64, } lazy_static! { @@ -102,14 +97,6 @@ impl PerbootDB { matches.sort_by_key(|x| x.0.time_received); matches.last().map(|x| x.0.clone()) } - /// Get the last time the device was off the user's body - pub fn get_last_off_body(&self) -> BootTime { - BootTime(self.last_off_body.load(Ordering::Relaxed)) - } - /// Set the last time the device was off the user's body - pub fn set_last_off_body(&self, last_off_body: BootTime) { - self.last_off_body.store(last_off_body.0, Ordering::Relaxed) - } /// Return how many auth tokens are currently tracked. pub fn auth_tokens_len(&self) -> usize { self.auth_tokens.read().unwrap().len() diff --git a/keystore2/src/database/tests.rs b/keystore2/src/database/tests.rs new file mode 100644 index 00000000..031d749b --- /dev/null +++ b/keystore2/src/database/tests.rs @@ -0,0 +1,2528 @@ +// Copyright 2020, 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. + +//! Database tests. + +use super::*; +use crate::key_parameter::{ + Algorithm, BlockMode, Digest, EcCurve, HardwareAuthenticatorType, KeyOrigin, KeyParameter, + KeyParameterValue, KeyPurpose, PaddingMode, SecurityLevel, +}; +use crate::key_perm_set; +use crate::permission::{KeyPerm, KeyPermSet}; +use crate::super_key::{SuperKeyManager, USER_AFTER_FIRST_UNLOCK_SUPER_KEY, SuperEncryptionAlgorithm, SuperKeyType}; +use keystore2_test_utils::TempDir; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + HardwareAuthToken::HardwareAuthToken, + HardwareAuthenticatorType::HardwareAuthenticatorType as kmhw_authenticator_type, +}; +use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ + Timestamp::Timestamp, +}; +use std::cell::RefCell; +use std::collections::BTreeMap; +use std::fmt::Write; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::Arc; +use std::thread; +use std::time::{Duration, SystemTime}; +use crate::utils::AesGcm; +#[cfg(disabled)] +use std::time::Instant; + +pub fn new_test_db() -> Result<KeystoreDB> { + let conn = KeystoreDB::make_connection("file::memory:")?; + + let mut db = KeystoreDB { conn, gc: None, perboot: Arc::new(perboot::PerbootDB::new()) }; + db.with_transaction(Immediate("TX_new_test_db"), |tx| { + KeystoreDB::init_tables(tx).context("Failed to initialize tables.").no_gc() + })?; + Ok(db) +} + +fn rebind_alias( + db: &mut KeystoreDB, + newid: &KeyIdGuard, + alias: &str, + domain: Domain, + namespace: i64, +) -> Result<bool> { + db.with_transaction(Immediate("TX_rebind_alias"), |tx| { + KeystoreDB::rebind_alias(tx, newid, alias, &domain, &namespace, KeyType::Client).no_gc() + }) + .context(ks_err!()) +} + +#[test] +fn datetime() -> Result<()> { + let conn = Connection::open_in_memory()?; + conn.execute("CREATE TABLE test (ts DATETIME);", [])?; + let now = SystemTime::now(); + let duration = Duration::from_secs(1000); + let then = now.checked_sub(duration).unwrap(); + let soon = now.checked_add(duration).unwrap(); + conn.execute( + "INSERT INTO test (ts) VALUES (?), (?), (?);", + params![DateTime::try_from(now)?, DateTime::try_from(then)?, DateTime::try_from(soon)?], + )?; + let mut stmt = conn.prepare("SELECT ts FROM test ORDER BY ts ASC;")?; + let mut rows = stmt.query([])?; + assert_eq!(DateTime::try_from(then)?, rows.next()?.unwrap().get(0)?); + assert_eq!(DateTime::try_from(now)?, rows.next()?.unwrap().get(0)?); + assert_eq!(DateTime::try_from(soon)?, rows.next()?.unwrap().get(0)?); + assert!(rows.next()?.is_none()); + assert!(DateTime::try_from(then)? < DateTime::try_from(now)?); + assert!(DateTime::try_from(then)? < DateTime::try_from(soon)?); + assert!(DateTime::try_from(now)? < DateTime::try_from(soon)?); + Ok(()) +} + +// Ensure that we're using the "injected" random function, not the real one. +#[test] +fn test_mocked_random() { + let rand1 = random(); + let rand2 = random(); + let rand3 = random(); + if rand1 == rand2 { + assert_eq!(rand2 + 1, rand3); + } else { + assert_eq!(rand1 + 1, rand2); + assert_eq!(rand2, rand3); + } +} + +// Test that we have the correct tables. +#[test] +fn test_tables() -> Result<()> { + let db = new_test_db()?; + let tables = db + .conn + .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")? + .query_map(params![], |row| row.get(0))? + .collect::<rusqlite::Result<Vec<String>>>()?; + assert_eq!(tables.len(), 6); + assert_eq!(tables[0], "blobentry"); + assert_eq!(tables[1], "blobmetadata"); + assert_eq!(tables[2], "grant"); + assert_eq!(tables[3], "keyentry"); + assert_eq!(tables[4], "keymetadata"); + assert_eq!(tables[5], "keyparameter"); + Ok(()) +} + +#[test] +fn test_auth_token_table_invariant() -> Result<()> { + let mut db = new_test_db()?; + let auth_token1 = HardwareAuthToken { + challenge: i64::MAX, + userId: 200, + authenticatorId: 200, + authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0), + timestamp: Timestamp { milliSeconds: 500 }, + mac: String::from("mac").into_bytes(), + }; + db.insert_auth_token(&auth_token1); + let auth_tokens_returned = get_auth_tokens(&db); + assert_eq!(auth_tokens_returned.len(), 1); + + // insert another auth token with the same values for the columns in the UNIQUE constraint + // of the auth token table and different value for timestamp + let auth_token2 = HardwareAuthToken { + challenge: i64::MAX, + userId: 200, + authenticatorId: 200, + authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0), + timestamp: Timestamp { milliSeconds: 600 }, + mac: String::from("mac").into_bytes(), + }; + + db.insert_auth_token(&auth_token2); + let mut auth_tokens_returned = get_auth_tokens(&db); + assert_eq!(auth_tokens_returned.len(), 1); + + if let Some(auth_token) = auth_tokens_returned.pop() { + assert_eq!(auth_token.auth_token.timestamp.milliSeconds, 600); + } + + // insert another auth token with the different values for the columns in the UNIQUE + // constraint of the auth token table + let auth_token3 = HardwareAuthToken { + challenge: i64::MAX, + userId: 201, + authenticatorId: 200, + authenticatorType: kmhw_authenticator_type(kmhw_authenticator_type::PASSWORD.0), + timestamp: Timestamp { milliSeconds: 600 }, + mac: String::from("mac").into_bytes(), + }; + + db.insert_auth_token(&auth_token3); + let auth_tokens_returned = get_auth_tokens(&db); + assert_eq!(auth_tokens_returned.len(), 2); + + Ok(()) +} + +// utility function for test_auth_token_table_invariant() +fn get_auth_tokens(db: &KeystoreDB) -> Vec<AuthTokenEntry> { + db.perboot.get_all_auth_token_entries() +} + +fn create_key_entry( + db: &mut KeystoreDB, + domain: &Domain, + namespace: &i64, + key_type: KeyType, + km_uuid: &Uuid, +) -> Result<KeyIdGuard> { + db.with_transaction(Immediate("TX_create_key_entry"), |tx| { + KeystoreDB::create_key_entry_internal(tx, domain, namespace, key_type, km_uuid).no_gc() + }) +} + +#[test] +fn test_persistence_for_files() -> Result<()> { + let temp_dir = TempDir::new("persistent_db_test")?; + let mut db = KeystoreDB::new(temp_dir.path(), None)?; + + create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?; + let entries = get_keyentry(&db)?; + assert_eq!(entries.len(), 1); + + let db = KeystoreDB::new(temp_dir.path(), None)?; + + let entries_new = get_keyentry(&db)?; + assert_eq!(entries, entries_new); + Ok(()) +} + +#[test] +fn test_create_key_entry() -> Result<()> { + fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>, Uuid) { + (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref(), ke.km_uuid.unwrap()) + } + + let mut db = new_test_db()?; + + create_key_entry(&mut db, &Domain::APP, &100, KeyType::Client, &KEYSTORE_UUID)?; + create_key_entry(&mut db, &Domain::SELINUX, &101, KeyType::Client, &KEYSTORE_UUID)?; + + let entries = get_keyentry(&db)?; + assert_eq!(entries.len(), 2); + assert_eq!(extractor(&entries[0]), (Domain::APP, 100, None, KEYSTORE_UUID)); + assert_eq!(extractor(&entries[1]), (Domain::SELINUX, 101, None, KEYSTORE_UUID)); + + // Test that we must pass in a valid Domain. + check_result_is_error_containing_string( + create_key_entry(&mut db, &Domain::GRANT, &102, KeyType::Client, &KEYSTORE_UUID), + &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT), + ); + check_result_is_error_containing_string( + create_key_entry(&mut db, &Domain::BLOB, &103, KeyType::Client, &KEYSTORE_UUID), + &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB), + ); + check_result_is_error_containing_string( + create_key_entry(&mut db, &Domain::KEY_ID, &104, KeyType::Client, &KEYSTORE_UUID), + &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID), + ); + + Ok(()) +} + +#[test] +fn test_rebind_alias() -> Result<()> { + fn extractor(ke: &KeyEntryRow) -> (Option<Domain>, Option<i64>, Option<&str>, Option<Uuid>) { + (ke.domain, ke.namespace, ke.alias.as_deref(), ke.km_uuid) + } + + let mut db = new_test_db()?; + create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?; + create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?; + let entries = get_keyentry(&db)?; + assert_eq!(entries.len(), 2); + assert_eq!(extractor(&entries[0]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))); + assert_eq!(extractor(&entries[1]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))); + + // Test that the first call to rebind_alias sets the alias. + rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[0].id), "foo", Domain::APP, 42)?; + let entries = get_keyentry(&db)?; + assert_eq!(entries.len(), 2); + assert_eq!( + extractor(&entries[0]), + (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID)) + ); + assert_eq!(extractor(&entries[1]), (Some(Domain::APP), Some(42), None, Some(KEYSTORE_UUID))); + + // Test that the second call to rebind_alias also empties the old one. + rebind_alias(&mut db, &KEY_ID_LOCK.get(entries[1].id), "foo", Domain::APP, 42)?; + let entries = get_keyentry(&db)?; + assert_eq!(entries.len(), 2); + assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID))); + assert_eq!( + extractor(&entries[1]), + (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID)) + ); + + // Test that we must pass in a valid Domain. + check_result_is_error_containing_string( + rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::GRANT, 42), + &format!("Domain {:?} must be either App or SELinux.", Domain::GRANT), + ); + check_result_is_error_containing_string( + rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::BLOB, 42), + &format!("Domain {:?} must be either App or SELinux.", Domain::BLOB), + ); + check_result_is_error_containing_string( + rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::KEY_ID, 42), + &format!("Domain {:?} must be either App or SELinux.", Domain::KEY_ID), + ); + + // Test that we correctly handle setting an alias for something that does not exist. + check_result_is_error_containing_string( + rebind_alias(&mut db, &KEY_ID_LOCK.get(0), "foo", Domain::SELINUX, 42), + "Expected to update a single entry but instead updated 0", + ); + // Test that we correctly abort the transaction in this case. + let entries = get_keyentry(&db)?; + assert_eq!(entries.len(), 2); + assert_eq!(extractor(&entries[0]), (None, None, None, Some(KEYSTORE_UUID))); + assert_eq!( + extractor(&entries[1]), + (Some(Domain::APP), Some(42), Some("foo"), Some(KEYSTORE_UUID)) + ); + + Ok(()) +} + +#[test] +fn test_grant_ungrant() -> Result<()> { + const CALLER_UID: u32 = 15; + const GRANTEE_UID: u32 = 12; + const SELINUX_NAMESPACE: i64 = 7; + + let mut db = new_test_db()?; + db.conn.execute( + "INSERT INTO persistent.keyentry (id, key_type, domain, namespace, alias, state, km_uuid) + VALUES (1, 0, 0, 15, 'key', 1, ?), (2, 0, 2, 7, 'yek', 1, ?);", + params![KEYSTORE_UUID, KEYSTORE_UUID], + )?; + let app_key = KeyDescriptor { + domain: super::Domain::APP, + nspace: 0, + alias: Some("key".to_string()), + blob: None, + }; + const PVEC1: KeyPermSet = key_perm_set![KeyPerm::Use, KeyPerm::GetInfo]; + const PVEC2: KeyPermSet = key_perm_set![KeyPerm::Use]; + + // Reset totally predictable random number generator in case we + // are not the first test running on this thread. + reset_random(); + let next_random = 0i64; + + let app_granted_key = db + .grant(&app_key, CALLER_UID, GRANTEE_UID, PVEC1, |k, a| { + assert_eq!(*a, PVEC1); + assert_eq!( + *k, + KeyDescriptor { + domain: super::Domain::APP, + // namespace must be set to the caller_uid. + nspace: CALLER_UID as i64, + alias: Some("key".to_string()), + blob: None, + } + ); + Ok(()) + }) + .unwrap(); + + assert_eq!( + app_granted_key, + KeyDescriptor { + domain: super::Domain::GRANT, + // The grantid is next_random due to the mock random number generator. + nspace: next_random, + alias: None, + blob: None, + } + ); + + let selinux_key = KeyDescriptor { + domain: super::Domain::SELINUX, + nspace: SELINUX_NAMESPACE, + alias: Some("yek".to_string()), + blob: None, + }; + + let selinux_granted_key = db + .grant(&selinux_key, CALLER_UID, 12, PVEC1, |k, a| { + assert_eq!(*a, PVEC1); + assert_eq!( + *k, + KeyDescriptor { + domain: super::Domain::SELINUX, + // namespace must be the supplied SELinux + // namespace. + nspace: SELINUX_NAMESPACE, + alias: Some("yek".to_string()), + blob: None, + } + ); + Ok(()) + }) + .unwrap(); + + assert_eq!( + selinux_granted_key, + KeyDescriptor { + domain: super::Domain::GRANT, + // The grantid is next_random + 1 due to the mock random number generator. + nspace: next_random + 1, + alias: None, + blob: None, + } + ); + + // This should update the existing grant with PVEC2. + let selinux_granted_key = db + .grant(&selinux_key, CALLER_UID, 12, PVEC2, |k, a| { + assert_eq!(*a, PVEC2); + assert_eq!( + *k, + KeyDescriptor { + domain: super::Domain::SELINUX, + // namespace must be the supplied SELinux + // namespace. + nspace: SELINUX_NAMESPACE, + alias: Some("yek".to_string()), + blob: None, + } + ); + Ok(()) + }) + .unwrap(); + + assert_eq!( + selinux_granted_key, + KeyDescriptor { + domain: super::Domain::GRANT, + // Same grant id as before. The entry was only updated. + nspace: next_random + 1, + alias: None, + blob: None, + } + ); + + { + // Limiting scope of stmt, because it borrows db. + let mut stmt = db + .conn + .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?; + let mut rows = stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>([], |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, KeyPermSet::from(row.get::<_, i32>(3)?))) + })?; + + let r = rows.next().unwrap().unwrap(); + assert_eq!(r, (next_random, GRANTEE_UID, 1, PVEC1)); + let r = rows.next().unwrap().unwrap(); + assert_eq!(r, (next_random + 1, GRANTEE_UID, 2, PVEC2)); + assert!(rows.next().is_none()); + } + + debug_dump_keyentry_table(&mut db)?; + println!("app_key {:?}", app_key); + println!("selinux_key {:?}", selinux_key); + + db.ungrant(&app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?; + db.ungrant(&selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?; + + Ok(()) +} + +static TEST_KEY_BLOB: &[u8] = b"my test blob"; +static TEST_CERT_BLOB: &[u8] = b"my test cert"; +static TEST_CERT_CHAIN_BLOB: &[u8] = b"my test cert_chain"; + +#[test] +fn test_set_blob() -> Result<()> { + let key_id = KEY_ID_LOCK.get(3000); + let mut db = new_test_db()?; + let mut blob_metadata = BlobMetaData::new(); + blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); + db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?; + db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?; + db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?; + drop(key_id); + + let mut stmt = db.conn.prepare( + "SELECT subcomponent_type, keyentryid, blob, id FROM persistent.blobentry + ORDER BY subcomponent_type ASC;", + )?; + let mut rows = stmt.query_map::<((SubComponentType, i64, Vec<u8>), i64), _, _>([], |row| { + Ok(((row.get(0)?, row.get(1)?, row.get(2)?), row.get(3)?)) + })?; + let (r, id) = rows.next().unwrap().unwrap(); + assert_eq!(r, (SubComponentType::KEY_BLOB, 3000, TEST_KEY_BLOB.to_vec())); + let (r, _) = rows.next().unwrap().unwrap(); + assert_eq!(r, (SubComponentType::CERT, 3000, TEST_CERT_BLOB.to_vec())); + let (r, _) = rows.next().unwrap().unwrap(); + assert_eq!(r, (SubComponentType::CERT_CHAIN, 3000, TEST_CERT_CHAIN_BLOB.to_vec())); + + drop(rows); + drop(stmt); + + assert_eq!( + db.with_transaction(Immediate("TX_test"), |tx| { + BlobMetaData::load_from_db(id, tx).no_gc() + }) + .expect("Should find blob metadata."), + blob_metadata + ); + Ok(()) +} + +static TEST_ALIAS: &str = "my super duper key"; + +#[test] +fn test_insert_and_load_full_keyentry_domain_app() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None) + .context("test_insert_and_load_full_keyentry_domain_app")? + .0; + let (_key_guard, key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + 1, + |_k, _av| Ok(()), + ) + .unwrap(); + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + + db.unbind_key( + &KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + 1, + |_, _| Ok(()), + ) + .unwrap(); + + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::NONE, + 1, + |_k, _av| Ok(()), + ) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +#[test] +fn test_insert_and_load_certificate_entry_domain_app() -> Result<()> { + let mut db = new_test_db()?; + + db.store_new_certificate( + &KeyDescriptor { + domain: Domain::APP, + nspace: 1, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + TEST_CERT_BLOB, + &KEYSTORE_UUID, + ) + .expect("Trying to insert cert."); + + let (_key_guard, mut key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: 1, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::PUBLIC, + 1, + |_k, _av| Ok(()), + ) + .expect("Trying to read certificate entry."); + + assert!(key_entry.pure_cert()); + assert!(key_entry.cert().is_none()); + assert_eq!(key_entry.take_cert_chain(), Some(TEST_CERT_BLOB.to_vec())); + + db.unbind_key( + &KeyDescriptor { + domain: Domain::APP, + nspace: 1, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + 1, + |_, _| Ok(()), + ) + .unwrap(); + + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: 1, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::NONE, + 1, + |_k, _av| Ok(()), + ) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +#[test] +fn test_insert_and_load_full_keyentry_domain_selinux() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None) + .context("test_insert_and_load_full_keyentry_domain_selinux")? + .0; + let (_key_guard, key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::SELINUX, + nspace: 1, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + 1, + |_k, _av| Ok(()), + ) + .unwrap(); + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + + db.unbind_key( + &KeyDescriptor { + domain: Domain::SELINUX, + nspace: 1, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + 1, + |_, _| Ok(()), + ) + .unwrap(); + + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry( + &KeyDescriptor { + domain: Domain::SELINUX, + nspace: 1, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::NONE, + 1, + |_k, _av| Ok(()), + ) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +#[test] +fn test_insert_and_load_full_keyentry_domain_key_id() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, None) + .context("test_insert_and_load_full_keyentry_domain_key_id")? + .0; + let (_, key_entry) = db + .load_key_entry( + &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + 1, + |_k, _av| Ok(()), + ) + .unwrap(); + + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + + db.unbind_key( + &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None }, + KeyType::Client, + 1, + |_, _| Ok(()), + ) + .unwrap(); + + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry( + &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None }, + KeyType::Client, + KeyEntryLoadBits::NONE, + 1, + |_k, _av| Ok(()), + ) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +#[test] +fn test_check_and_update_key_usage_count_with_limited_use_key() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(123)) + .context("test_check_and_update_key_usage_count_with_limited_use_key")? + .0; + // Update the usage count of the limited use key. + db.check_and_update_key_usage_count(key_id)?; + + let (_key_guard, key_entry) = db.load_key_entry( + &KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, alias: None, blob: None }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + 1, + |_k, _av| Ok(()), + )?; + + // The usage count is decremented now. + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, Some(122))); + + Ok(()) +} + +#[test] +fn test_check_and_update_key_usage_count_with_exhausted_limited_use_key() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::SELINUX, 1, TEST_ALIAS, Some(1)) + .context("test_check_and_update_key_usage_count_with_exhausted_limited_use_key")? + .0; + // Update the usage count of the limited use key. + db.check_and_update_key_usage_count(key_id).expect(concat!( + "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ", + "This should succeed." + )); + + // Try to update the exhausted limited use key. + let e = db.check_and_update_key_usage_count(key_id).expect_err(concat!( + "In test_check_and_update_key_usage_count_with_exhausted_limited_use_key: ", + "This should fail." + )); + assert_eq!( + &KsError::Km(ErrorCode::INVALID_KEY_BLOB), + e.root_cause().downcast_ref::<KsError>().unwrap() + ); + + Ok(()) +} + +#[test] +fn test_insert_and_load_full_keyentry_from_grant() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None) + .context("test_insert_and_load_full_keyentry_from_grant")? + .0; + + let granted_key = db + .grant( + &KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + 1, + 2, + key_perm_set![KeyPerm::Use], + |_k, _av| Ok(()), + ) + .unwrap(); + + debug_dump_grant_table(&mut db)?; + + let (_key_guard, key_entry) = db + .load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::BOTH, 2, |k, av| { + assert_eq!(Domain::GRANT, k.domain); + assert!(av.unwrap().includes(KeyPerm::Use)); + Ok(()) + }) + .unwrap(); + + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + + db.unbind_key(&granted_key, KeyType::Client, 2, |_, _| Ok(())).unwrap(); + + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry(&granted_key, KeyType::Client, KeyEntryLoadBits::NONE, 2, |_k, _av| Ok( + () + ),) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +// This test attempts to load a key by key id while the caller is not the owner +// but a grant exists for the given key and the caller. +#[test] +fn test_insert_and_load_full_keyentry_from_grant_by_key_id() -> Result<()> { + let mut db = new_test_db()?; + const OWNER_UID: u32 = 1u32; + const GRANTEE_UID: u32 = 2u32; + const SOMEONE_ELSE_UID: u32 = 3u32; + let key_id = make_test_key_entry(&mut db, Domain::APP, OWNER_UID as i64, TEST_ALIAS, None) + .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")? + .0; + + db.grant( + &KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + OWNER_UID, + GRANTEE_UID, + key_perm_set![KeyPerm::Use], + |_k, _av| Ok(()), + ) + .unwrap(); + + debug_dump_grant_table(&mut db)?; + + let id_descriptor = + KeyDescriptor { domain: Domain::KEY_ID, nspace: key_id, ..Default::default() }; + + let (_, key_entry) = db + .load_key_entry( + &id_descriptor, + KeyType::Client, + KeyEntryLoadBits::BOTH, + GRANTEE_UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(OWNER_UID as i64, k.nspace); + assert!(av.unwrap().includes(KeyPerm::Use)); + Ok(()) + }, + ) + .unwrap(); + + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + + let (_, key_entry) = db + .load_key_entry( + &id_descriptor, + KeyType::Client, + KeyEntryLoadBits::BOTH, + SOMEONE_ELSE_UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(OWNER_UID as i64, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap(); + + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + + db.unbind_key(&id_descriptor, KeyType::Client, OWNER_UID, |_, _| Ok(())).unwrap(); + + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry( + &id_descriptor, + KeyType::Client, + KeyEntryLoadBits::NONE, + GRANTEE_UID, + |_k, _av| Ok(()), + ) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +// Creates a key migrates it to a different location and then tries to access it by the old +// and new location. +#[test] +fn test_migrate_key_app_to_app() -> Result<()> { + let mut db = new_test_db()?; + const SOURCE_UID: u32 = 1u32; + const DESTINATION_UID: u32 = 2u32; + static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; + static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; + let key_id_guard = + make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) + .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; + + let source_descriptor: KeyDescriptor = KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(SOURCE_ALIAS.to_string()), + blob: None, + }; + + let destination_descriptor: KeyDescriptor = KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(DESTINATION_ALIAS.to_string()), + blob: None, + }; + + let key_id = key_id_guard.id(); + + db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok(())) + .unwrap(); + + let (_, key_entry) = db + .load_key_entry( + &destination_descriptor, + KeyType::Client, + KeyEntryLoadBits::BOTH, + DESTINATION_UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(DESTINATION_UID as i64, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap(); + + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry( + &source_descriptor, + KeyType::Client, + KeyEntryLoadBits::NONE, + SOURCE_UID, + |_k, _av| Ok(()), + ) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +// Creates a key migrates it to a different location and then tries to access it by the old +// and new location. +#[test] +fn test_migrate_key_app_to_selinux() -> Result<()> { + let mut db = new_test_db()?; + const SOURCE_UID: u32 = 1u32; + const DESTINATION_UID: u32 = 2u32; + const DESTINATION_NAMESPACE: i64 = 1000i64; + static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; + static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; + let key_id_guard = + make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) + .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; + + let source_descriptor: KeyDescriptor = KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(SOURCE_ALIAS.to_string()), + blob: None, + }; + + let destination_descriptor: KeyDescriptor = KeyDescriptor { + domain: Domain::SELINUX, + nspace: DESTINATION_NAMESPACE, + alias: Some(DESTINATION_ALIAS.to_string()), + blob: None, + }; + + let key_id = key_id_guard.id(); + + db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok(())) + .unwrap(); + + let (_, key_entry) = db + .load_key_entry( + &destination_descriptor, + KeyType::Client, + KeyEntryLoadBits::BOTH, + DESTINATION_UID, + |k, av| { + assert_eq!(Domain::SELINUX, k.domain); + assert_eq!(DESTINATION_NAMESPACE, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap(); + + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry( + &source_descriptor, + KeyType::Client, + KeyEntryLoadBits::NONE, + SOURCE_UID, + |_k, _av| Ok(()), + ) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +// Creates two keys and tries to migrate the first to the location of the second which +// is expected to fail. +#[test] +fn test_migrate_key_destination_occupied() -> Result<()> { + let mut db = new_test_db()?; + const SOURCE_UID: u32 = 1u32; + const DESTINATION_UID: u32 = 2u32; + static SOURCE_ALIAS: &str = "SOURCE_ALIAS"; + static DESTINATION_ALIAS: &str = "DESTINATION_ALIAS"; + let key_id_guard = + make_test_key_entry(&mut db, Domain::APP, SOURCE_UID as i64, SOURCE_ALIAS, None) + .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; + make_test_key_entry(&mut db, Domain::APP, DESTINATION_UID as i64, DESTINATION_ALIAS, None) + .context("test_insert_and_load_full_keyentry_from_grant_by_key_id")?; + + let destination_descriptor: KeyDescriptor = KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(DESTINATION_ALIAS.to_string()), + blob: None, + }; + + assert_eq!( + Some(&KsError::Rc(ResponseCode::INVALID_ARGUMENT)), + db.migrate_key_namespace(key_id_guard, &destination_descriptor, DESTINATION_UID, |_k| Ok( + () + )) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); + + Ok(()) +} + +#[test] +fn test_upgrade_0_to_1() { + const ALIAS1: &str = "test_upgrade_0_to_1_1"; + const ALIAS2: &str = "test_upgrade_0_to_1_2"; + const ALIAS3: &str = "test_upgrade_0_to_1_3"; + const UID: u32 = 33; + let temp_dir = Arc::new(TempDir::new("test_upgrade_0_to_1").unwrap()); + let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap(); + let key_id_untouched1 = + make_test_key_entry(&mut db, Domain::APP, UID as i64, ALIAS1, None).unwrap().id(); + let key_id_untouched2 = + make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS2, false).unwrap().id(); + let key_id_deleted = + make_bootlevel_key_entry(&mut db, Domain::APP, UID as i64, ALIAS3, true).unwrap().id(); + + let (_, key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(ALIAS1.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(UID as i64, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap(); + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None)); + let (_, key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(ALIAS2.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(UID as i64, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap(); + assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false)); + let (_, key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(ALIAS3.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(UID as i64, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap(); + assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_deleted, true)); + + db.with_transaction(Immediate("TX_test"), |tx| KeystoreDB::from_0_to_1(tx).no_gc()).unwrap(); + + let (_, key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(ALIAS1.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(UID as i64, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap(); + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id_untouched1, None)); + let (_, key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(ALIAS2.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(UID as i64, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap(); + assert_eq!(key_entry, make_bootlevel_test_key_entry_test_vector(key_id_untouched2, false)); + assert_eq!( + Some(&KsError::Rc(ResponseCode::KEY_NOT_FOUND)), + db.load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(ALIAS3.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + UID, + |k, av| { + assert_eq!(Domain::APP, k.domain); + assert_eq!(UID as i64, k.nspace); + assert!(av.is_none()); + Ok(()) + }, + ) + .unwrap_err() + .root_cause() + .downcast_ref::<KsError>() + ); +} + +static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key"; + +#[test] +fn test_insert_and_load_full_keyentry_domain_app_concurrently() -> Result<()> { + let handle = { + let temp_dir = Arc::new(TempDir::new("id_lock_test")?); + let temp_dir_clone = temp_dir.clone(); + let mut db = KeystoreDB::new(temp_dir.path(), None)?; + let key_id = make_test_key_entry(&mut db, Domain::APP, 33, KEY_LOCK_TEST_ALIAS, None) + .context("test_insert_and_load_full_keyentry_domain_app")? + .0; + let (_key_guard, key_entry) = db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(KEY_LOCK_TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + 33, + |_k, _av| Ok(()), + ) + .unwrap(); + assert_eq!(key_entry, make_test_key_entry_test_vector(key_id, None)); + let state = Arc::new(AtomicU8::new(1)); + let state2 = state.clone(); + + // Spawning a second thread that attempts to acquire the key id lock + // for the same key as the primary thread. The primary thread then + // waits, thereby forcing the secondary thread into the second stage + // of acquiring the lock (see KEY ID LOCK 2/2 above). + // The test succeeds if the secondary thread observes the transition + // of `state` from 1 to 2, despite having a whole second to overtake + // the primary thread. + let handle = thread::spawn(move || { + let temp_dir = temp_dir_clone; + let mut db = KeystoreDB::new(temp_dir.path(), None).unwrap(); + assert!(db + .load_key_entry( + &KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(KEY_LOCK_TEST_ALIAS.to_string()), + blob: None, + }, + KeyType::Client, + KeyEntryLoadBits::BOTH, + 33, + |_k, _av| Ok(()), + ) + .is_ok()); + // We should only see a 2 here because we can only return + // from load_key_entry when the `_key_guard` expires, + // which happens at the end of the scope. + assert_eq!(2, state2.load(Ordering::Relaxed)); + }); + + thread::sleep(std::time::Duration::from_millis(1000)); + + assert_eq!(Ok(1), state.compare_exchange(1, 2, Ordering::Relaxed, Ordering::Relaxed)); + + // Return the handle from this scope so we can join with the + // secondary thread after the key id lock has expired. + handle + // This is where the `_key_guard` goes out of scope, + // which is the reason for concurrent load_key_entry on the same key + // to unblock. + }; + // Join with the secondary thread and unwrap, to propagate failing asserts to the + // main test thread. We will not see failing asserts in secondary threads otherwise. + handle.join().unwrap(); + Ok(()) +} + +#[test] +fn test_database_busy_error_code() { + let temp_dir = + TempDir::new("test_database_busy_error_code_").expect("Failed to create temp dir."); + + let mut db1 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database1."); + let mut db2 = KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database2."); + + let _tx1 = db1 + .conn + .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate) + .expect("Failed to create first transaction."); + + let error = db2 + .conn + .transaction_with_behavior(rusqlite::TransactionBehavior::Immediate) + .context("Transaction begin failed.") + .expect_err("This should fail."); + let root_cause = error.root_cause(); + if let Some(rusqlite::ffi::Error { code: rusqlite::ErrorCode::DatabaseBusy, .. }) = + root_cause.downcast_ref::<rusqlite::ffi::Error>() + { + return; + } + panic!( + "Unexpected error {:?} \n{:?} \n{:?}", + error, + root_cause, + root_cause.downcast_ref::<rusqlite::ffi::Error>() + ) +} + +#[cfg(disabled)] +#[test] +fn test_large_number_of_concurrent_db_manipulations() -> Result<()> { + let temp_dir = Arc::new( + TempDir::new("test_large_number_of_concurrent_db_manipulations_") + .expect("Failed to create temp dir."), + ); + + let test_begin = Instant::now(); + + const KEY_COUNT: u32 = 500u32; + let mut db = + new_test_db_with_gc(temp_dir.path(), |_, _| Ok(())).expect("Failed to open database."); + const OPEN_DB_COUNT: u32 = 50u32; + + let mut actual_key_count = KEY_COUNT; + // First insert KEY_COUNT keys. + for count in 0..KEY_COUNT { + if Instant::now().duration_since(test_begin) >= Duration::from_secs(15) { + actual_key_count = count; + break; + } + let alias = format!("test_alias_{}", count); + make_test_key_entry(&mut db, Domain::APP, 1, &alias, None) + .expect("Failed to make key entry."); + } + + // Insert more keys from a different thread and into a different namespace. + let temp_dir1 = temp_dir.clone(); + let handle1 = thread::spawn(move || { + let mut db = + new_test_db_with_gc(temp_dir1.path(), |_, _| Ok(())).expect("Failed to open database."); + + for count in 0..actual_key_count { + if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { + return; + } + let alias = format!("test_alias_{}", count); + make_test_key_entry(&mut db, Domain::APP, 2, &alias, None) + .expect("Failed to make key entry."); + } + + // then unbind them again. + for count in 0..actual_key_count { + if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { + return; + } + let key = KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(format!("test_alias_{}", count)), + blob: None, + }; + db.unbind_key(&key, KeyType::Client, 2, |_, _| Ok(())).expect("Unbind Failed."); + } + }); + + // And start unbinding the first set of keys. + let temp_dir2 = temp_dir.clone(); + let handle2 = thread::spawn(move || { + let mut db = + new_test_db_with_gc(temp_dir2.path(), |_, _| Ok(())).expect("Failed to open database."); + + for count in 0..actual_key_count { + if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { + return; + } + let key = KeyDescriptor { + domain: Domain::APP, + nspace: -1, + alias: Some(format!("test_alias_{}", count)), + blob: None, + }; + db.unbind_key(&key, KeyType::Client, 1, |_, _| Ok(())).expect("Unbind Failed."); + } + }); + + // While a lot of inserting and deleting is going on we have to open database connections + // successfully and use them. + // This clone is not redundant, because temp_dir needs to be kept alive until db goes + // out of scope. + #[allow(clippy::redundant_clone)] + let temp_dir4 = temp_dir.clone(); + let handle4 = thread::spawn(move || { + for count in 0..OPEN_DB_COUNT { + if Instant::now().duration_since(test_begin) >= Duration::from_secs(40) { + return; + } + let mut db = new_test_db_with_gc(temp_dir4.path(), |_, _| Ok(())) + .expect("Failed to open database."); + + let alias = format!("test_alias_{}", count); + make_test_key_entry(&mut db, Domain::APP, 3, &alias, None) + .expect("Failed to make key entry."); + let key = + KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }; + db.unbind_key(&key, KeyType::Client, 3, |_, _| Ok(())).expect("Unbind Failed."); + } + }); + + handle1.join().expect("Thread 1 panicked."); + handle2.join().expect("Thread 2 panicked."); + handle4.join().expect("Thread 4 panicked."); + + Ok(()) +} + +#[test] +fn list() -> Result<()> { + let temp_dir = TempDir::new("list_test")?; + let mut db = KeystoreDB::new(temp_dir.path(), None)?; + 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, None).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_past_alias(*domain, *namespace, KeyType::Client, None)?; + 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, + KeyType::Client, + 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_past_alias(Domain::SELINUX, 101, KeyType::Client, None)? + ); + + Ok(()) +} + +// Helpers + +// Checks that the given result is an error containing the given string. +fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) { + let error_str = + format!("{:#?}", result.err().unwrap_or_else(|| panic!("Expected the error: {}", target))); + assert!( + error_str.contains(target), + "The string \"{}\" should contain \"{}\"", + error_str, + target + ); +} + +#[derive(Debug, PartialEq)] +struct KeyEntryRow { + id: i64, + key_type: KeyType, + domain: Option<Domain>, + namespace: Option<i64>, + alias: Option<String>, + state: KeyLifeCycle, + km_uuid: Option<Uuid>, +} + +fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> { + db.conn + .prepare("SELECT * FROM persistent.keyentry;")? + .query_map([], |row| { + Ok(KeyEntryRow { + id: row.get(0)?, + key_type: row.get(1)?, + domain: row.get::<_, Option<_>>(2)?.map(Domain), + namespace: row.get(3)?, + alias: row.get(4)?, + state: row.get(5)?, + km_uuid: row.get(6)?, + }) + })? + .map(|r| r.context("Could not read keyentry row.")) + .collect::<Result<Vec<_>>>() +} + +fn make_test_params(max_usage_count: Option<i32>) -> Vec<KeyParameter> { + make_test_params_with_sids(max_usage_count, &[42]) +} + +// Note: The parameters and SecurityLevel associations are nonsensical. This +// collection is only used to check if the parameters are preserved as expected by the +// database. +fn make_test_params_with_sids( + max_usage_count: Option<i32>, + user_secure_ids: &[i64], +) -> Vec<KeyParameter> { + let mut params = vec![ + KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::TRUSTED_ENVIRONMENT), + KeyParameter::new( + KeyParameterValue::KeyPurpose(KeyPurpose::SIGN), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::KeyPurpose(KeyPurpose::DECRYPT), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::Algorithm(Algorithm::RSA), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::TRUSTED_ENVIRONMENT), + KeyParameter::new( + KeyParameterValue::BlockMode(BlockMode::ECB), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::BlockMode(BlockMode::GCM), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::Digest(Digest::NONE), SecurityLevel::STRONGBOX), + KeyParameter::new( + KeyParameterValue::Digest(Digest::MD5), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::Digest(Digest::SHA_2_224), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::Digest(Digest::SHA_2_256), SecurityLevel::STRONGBOX), + KeyParameter::new( + KeyParameterValue::PaddingMode(PaddingMode::NONE), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::PaddingMode(PaddingMode::RSA_OAEP), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::PaddingMode(PaddingMode::RSA_PSS), + SecurityLevel::STRONGBOX, + ), + KeyParameter::new( + KeyParameterValue::PaddingMode(PaddingMode::RSA_PKCS1_1_5_SIGN), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::TRUSTED_ENVIRONMENT), + KeyParameter::new(KeyParameterValue::MinMacLength(256), SecurityLevel::STRONGBOX), + KeyParameter::new( + KeyParameterValue::EcCurve(EcCurve::P_224), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::EcCurve(EcCurve::P_256), SecurityLevel::STRONGBOX), + KeyParameter::new( + KeyParameterValue::EcCurve(EcCurve::P_384), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::EcCurve(EcCurve::P_521), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::RSAPublicExponent(3), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::IncludeUniqueID, SecurityLevel::TRUSTED_ENVIRONMENT), + KeyParameter::new(KeyParameterValue::BootLoaderOnly, SecurityLevel::STRONGBOX), + KeyParameter::new(KeyParameterValue::RollbackResistance, SecurityLevel::STRONGBOX), + KeyParameter::new(KeyParameterValue::ActiveDateTime(1234567890), SecurityLevel::STRONGBOX), + KeyParameter::new( + KeyParameterValue::OriginationExpireDateTime(1234567890), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::UsageExpireDateTime(1234567890), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::MinSecondsBetweenOps(1234567890), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::MaxUsesPerBoot(1234567890), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::UserID(1), SecurityLevel::STRONGBOX), + KeyParameter::new(KeyParameterValue::NoAuthRequired, SecurityLevel::TRUSTED_ENVIRONMENT), + KeyParameter::new( + KeyParameterValue::HardwareAuthenticatorType(HardwareAuthenticatorType::PASSWORD), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::AuthTimeout(1234567890), SecurityLevel::SOFTWARE), + KeyParameter::new(KeyParameterValue::AllowWhileOnBody, SecurityLevel::SOFTWARE), + KeyParameter::new( + KeyParameterValue::TrustedUserPresenceRequired, + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::TrustedConfirmationRequired, + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::UnlockedDeviceRequired, + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::ApplicationID(vec![1u8, 2u8, 3u8, 4u8]), + SecurityLevel::SOFTWARE, + ), + KeyParameter::new( + KeyParameterValue::ApplicationData(vec![4u8, 3u8, 2u8, 1u8]), + SecurityLevel::SOFTWARE, + ), + KeyParameter::new( + KeyParameterValue::CreationDateTime(12345677890), + SecurityLevel::SOFTWARE, + ), + KeyParameter::new( + KeyParameterValue::KeyOrigin(KeyOrigin::GENERATED), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::RootOfTrust(vec![3u8, 2u8, 1u8, 4u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::OSVersion(1), SecurityLevel::TRUSTED_ENVIRONMENT), + KeyParameter::new(KeyParameterValue::OSPatchLevel(2), SecurityLevel::SOFTWARE), + KeyParameter::new( + KeyParameterValue::UniqueID(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::SOFTWARE, + ), + KeyParameter::new( + KeyParameterValue::AttestationChallenge(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationApplicationID(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdBrand(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdDevice(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdProduct(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdSerial(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdIMEI(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdSecondIMEI(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdMEID(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdManufacturer(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::AttestationIdModel(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::VendorPatchLevel(3), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::BootPatchLevel(4), SecurityLevel::TRUSTED_ENVIRONMENT), + KeyParameter::new( + KeyParameterValue::AssociatedData(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::Nonce(vec![4u8, 3u8, 1u8, 2u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new(KeyParameterValue::MacLength(256), SecurityLevel::TRUSTED_ENVIRONMENT), + KeyParameter::new( + KeyParameterValue::ResetSinceIdRotation, + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + KeyParameter::new( + KeyParameterValue::ConfirmationToken(vec![5u8, 5u8, 5u8, 5u8]), + SecurityLevel::TRUSTED_ENVIRONMENT, + ), + ]; + if let Some(value) = max_usage_count { + params.push(KeyParameter::new( + KeyParameterValue::UsageCountLimit(value), + SecurityLevel::SOFTWARE, + )); + } + + for sid in user_secure_ids.iter() { + params.push(KeyParameter::new( + KeyParameterValue::UserSecureID(*sid), + SecurityLevel::STRONGBOX, + )); + } + params +} + +pub fn make_test_key_entry( + db: &mut KeystoreDB, + domain: Domain, + namespace: i64, + alias: &str, + max_usage_count: Option<i32>, +) -> Result<KeyIdGuard> { + make_test_key_entry_with_sids(db, domain, namespace, alias, max_usage_count, &[42]) +} + +pub fn make_test_key_entry_with_sids( + db: &mut KeystoreDB, + domain: Domain, + namespace: i64, + alias: &str, + max_usage_count: Option<i32>, + sids: &[i64], +) -> Result<KeyIdGuard> { + let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?; + let mut blob_metadata = BlobMetaData::new(); + blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); + blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3])); + blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1])); + blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2])); + blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); + + db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?; + db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?; + db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?; + + let params = make_test_params_with_sids(max_usage_count, sids); + db.insert_keyparameter(&key_id, ¶ms)?; + + let mut metadata = KeyMetaData::new(); + metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); + db.insert_key_metadata(&key_id, &metadata)?; + rebind_alias(db, &key_id, alias, domain, namespace)?; + Ok(key_id) +} + +fn make_test_key_entry_test_vector(key_id: i64, max_usage_count: Option<i32>) -> KeyEntry { + let params = make_test_params(max_usage_count); + + let mut blob_metadata = BlobMetaData::new(); + blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); + blob_metadata.add(BlobMetaEntry::Salt(vec![1, 2, 3])); + blob_metadata.add(BlobMetaEntry::Iv(vec![2, 3, 1])); + blob_metadata.add(BlobMetaEntry::AeadTag(vec![3, 1, 2])); + blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); + + let mut metadata = KeyMetaData::new(); + metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); + + KeyEntry { + id: key_id, + key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)), + cert: Some(TEST_CERT_BLOB.to_vec()), + cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()), + km_uuid: KEYSTORE_UUID, + parameters: params, + metadata, + pure_cert: false, + } +} + +pub fn make_bootlevel_key_entry( + db: &mut KeystoreDB, + domain: Domain, + namespace: i64, + alias: &str, + logical_only: bool, +) -> Result<KeyIdGuard> { + let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?; + let mut blob_metadata = BlobMetaData::new(); + if !logical_only { + blob_metadata.add(BlobMetaEntry::MaxBootLevel(3)); + } + blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); + + db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?; + db.set_blob(&key_id, SubComponentType::CERT, Some(TEST_CERT_BLOB), None)?; + db.set_blob(&key_id, SubComponentType::CERT_CHAIN, Some(TEST_CERT_CHAIN_BLOB), None)?; + + let mut params = make_test_params(None); + params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE)); + + db.insert_keyparameter(&key_id, ¶ms)?; + + let mut metadata = KeyMetaData::new(); + metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); + db.insert_key_metadata(&key_id, &metadata)?; + rebind_alias(db, &key_id, alias, domain, namespace)?; + Ok(key_id) +} + +// Creates an app key that is marked as being superencrypted by the given +// super key ID and that has the given authentication and unlocked device +// parameters. This does not actually superencrypt the key blob. +fn make_superencrypted_key_entry( + db: &mut KeystoreDB, + namespace: i64, + alias: &str, + requires_authentication: bool, + requires_unlocked_device: bool, + super_key_id: i64, +) -> Result<KeyIdGuard> { + let domain = Domain::APP; + let key_id = create_key_entry(db, &domain, &namespace, KeyType::Client, &KEYSTORE_UUID)?; + + let mut blob_metadata = BlobMetaData::new(); + blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); + blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::KeyId(super_key_id))); + db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), Some(&blob_metadata))?; + + let mut params = vec![]; + if requires_unlocked_device { + params.push(KeyParameter::new( + KeyParameterValue::UnlockedDeviceRequired, + SecurityLevel::TRUSTED_ENVIRONMENT, + )); + } + if requires_authentication { + params.push(KeyParameter::new( + KeyParameterValue::UserSecureID(42), + SecurityLevel::TRUSTED_ENVIRONMENT, + )); + } + db.insert_keyparameter(&key_id, ¶ms)?; + + let mut metadata = KeyMetaData::new(); + metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); + db.insert_key_metadata(&key_id, &metadata)?; + + rebind_alias(db, &key_id, alias, domain, namespace)?; + Ok(key_id) +} + +fn make_bootlevel_test_key_entry_test_vector(key_id: i64, logical_only: bool) -> KeyEntry { + let mut params = make_test_params(None); + params.push(KeyParameter::new(KeyParameterValue::MaxBootLevel(3), SecurityLevel::KEYSTORE)); + + let mut blob_metadata = BlobMetaData::new(); + if !logical_only { + blob_metadata.add(BlobMetaEntry::MaxBootLevel(3)); + } + blob_metadata.add(BlobMetaEntry::KmUuid(KEYSTORE_UUID)); + + let mut metadata = KeyMetaData::new(); + metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); + + KeyEntry { + id: key_id, + key_blob_info: Some((TEST_KEY_BLOB.to_vec(), blob_metadata)), + cert: Some(TEST_CERT_BLOB.to_vec()), + cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()), + km_uuid: KEYSTORE_UUID, + parameters: params, + metadata, + pure_cert: false, + } +} + +fn debug_dump_keyentry_table(db: &mut KeystoreDB) -> Result<()> { + let mut stmt = db.conn.prepare( + "SELECT id, key_type, domain, namespace, alias, state, km_uuid FROM persistent.keyentry;", + )?; + let rows = + stmt.query_map::<(i64, KeyType, i32, i64, String, KeyLifeCycle, Uuid), _, _>([], |row| { + Ok(( + row.get(0)?, + row.get(1)?, + row.get(2)?, + row.get(3)?, + row.get(4)?, + row.get(5)?, + row.get(6)?, + )) + })?; + + println!("Key entry table rows:"); + for r in rows { + let (id, key_type, domain, namespace, alias, state, km_uuid) = r.unwrap(); + println!( + " id: {} KeyType: {:?} Domain: {} Namespace: {} Alias: {} State: {:?} KmUuid: {:?}", + id, key_type, domain, namespace, alias, state, km_uuid + ); + } + Ok(()) +} + +fn debug_dump_grant_table(db: &mut KeystoreDB) -> Result<()> { + let mut stmt = + db.conn.prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?; + let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>([], |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) + })?; + + println!("Grant table rows:"); + for r in rows { + let (id, gt, ki, av) = r.unwrap(); + println!(" id: {} grantee: {} key_id: {} access_vector: {}", id, gt, ki, av); + } + Ok(()) +} + +// Use a custom random number generator that repeats each number once. +// This allows us to test repeated elements. + +thread_local! { + static RANDOM_COUNTER: RefCell<i64> = const { RefCell::new(0) }; +} + +fn reset_random() { + RANDOM_COUNTER.with(|counter| { + *counter.borrow_mut() = 0; + }) +} + +pub fn random() -> i64 { + RANDOM_COUNTER.with(|counter| { + let result = *counter.borrow() / 2; + *counter.borrow_mut() += 1; + result + }) +} + +#[test] +fn test_unbind_keys_for_user() -> Result<()> { + let mut db = new_test_db()?; + db.unbind_keys_for_user(1)?; + + make_test_key_entry(&mut db, Domain::APP, 210000, TEST_ALIAS, None)?; + make_test_key_entry(&mut db, Domain::APP, 110000, TEST_ALIAS, None)?; + db.unbind_keys_for_user(2)?; + + assert_eq!(1, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len()); + assert_eq!(0, db.list_past_alias(Domain::APP, 210000, KeyType::Client, None)?.len()); + + db.unbind_keys_for_user(1)?; + assert_eq!(0, db.list_past_alias(Domain::APP, 110000, KeyType::Client, None)?.len()); + + Ok(()) +} + +#[test] +fn test_unbind_keys_for_user_removes_superkeys() -> Result<()> { + let mut db = new_test_db()?; + let super_key = keystore2_crypto::generate_aes256_key()?; + let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); + let (encrypted_super_key, metadata) = SuperKeyManager::encrypt_with_password(&super_key, &pw)?; + + let key_name_enc = SuperKeyType { + alias: "test_super_key_1", + algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + name: "test_super_key_1", + }; + + let key_name_nonenc = SuperKeyType { + alias: "test_super_key_2", + algorithm: SuperEncryptionAlgorithm::Aes256Gcm, + name: "test_super_key_2", + }; + + // Install two super keys. + db.store_super_key(1, &key_name_nonenc, &super_key, &BlobMetaData::new(), &KeyMetaData::new())?; + db.store_super_key(1, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?; + + // Check that both can be found in the database. + assert!(db.load_super_key(&key_name_enc, 1)?.is_some()); + assert!(db.load_super_key(&key_name_nonenc, 1)?.is_some()); + + // Install the same keys for a different user. + db.store_super_key(2, &key_name_nonenc, &super_key, &BlobMetaData::new(), &KeyMetaData::new())?; + db.store_super_key(2, &key_name_enc, &encrypted_super_key, &metadata, &KeyMetaData::new())?; + + // Check that the second pair of keys can be found in the database. + assert!(db.load_super_key(&key_name_enc, 2)?.is_some()); + assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some()); + + // Delete all keys for user 1. + db.unbind_keys_for_user(1)?; + + // All of user 1's keys should be gone. + assert!(db.load_super_key(&key_name_enc, 1)?.is_none()); + assert!(db.load_super_key(&key_name_nonenc, 1)?.is_none()); + + // User 2's keys should not have been touched. + assert!(db.load_super_key(&key_name_enc, 2)?.is_some()); + assert!(db.load_super_key(&key_name_nonenc, 2)?.is_some()); + + Ok(()) +} + +fn app_key_exists(db: &mut KeystoreDB, nspace: i64, alias: &str) -> Result<bool> { + db.key_exists(Domain::APP, nspace, alias, KeyType::Client) +} + +// Tests the unbind_auth_bound_keys_for_user() function. +#[test] +fn test_unbind_auth_bound_keys_for_user() -> Result<()> { + let mut db = new_test_db()?; + let user_id = 1; + let nspace: i64 = (user_id * AID_USER_OFFSET).into(); + let other_user_id = 2; + let other_user_nspace: i64 = (other_user_id * AID_USER_OFFSET).into(); + let super_key_type = &USER_AFTER_FIRST_UNLOCK_SUPER_KEY; + + // Create a superencryption key. + let super_key = keystore2_crypto::generate_aes256_key()?; + let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); + let (encrypted_super_key, blob_metadata) = + SuperKeyManager::encrypt_with_password(&super_key, &pw)?; + db.store_super_key( + user_id, + super_key_type, + &encrypted_super_key, + &blob_metadata, + &KeyMetaData::new(), + )?; + let super_key_id = db.load_super_key(super_key_type, user_id)?.unwrap().0 .0; + + // Store 4 superencrypted app keys, one for each possible combination of + // (authentication required, unlocked device required). + make_superencrypted_key_entry(&mut db, nspace, "noauth_noud", false, false, super_key_id)?; + make_superencrypted_key_entry(&mut db, nspace, "noauth_ud", false, true, super_key_id)?; + make_superencrypted_key_entry(&mut db, nspace, "auth_noud", true, false, super_key_id)?; + make_superencrypted_key_entry(&mut db, nspace, "auth_ud", true, true, super_key_id)?; + assert!(app_key_exists(&mut db, nspace, "noauth_noud")?); + assert!(app_key_exists(&mut db, nspace, "noauth_ud")?); + assert!(app_key_exists(&mut db, nspace, "auth_noud")?); + assert!(app_key_exists(&mut db, nspace, "auth_ud")?); + + // Also store a key for a different user that requires authentication. + make_superencrypted_key_entry(&mut db, other_user_nspace, "auth_ud", true, true, super_key_id)?; + + db.unbind_auth_bound_keys_for_user(user_id)?; + + // Verify that only the user's app keys that require authentication were + // deleted. Keys that require an unlocked device but not authentication + // should *not* have been deleted, nor should the super key have been + // deleted, nor should other users' keys have been deleted. + assert!(db.load_super_key(super_key_type, user_id)?.is_some()); + assert!(app_key_exists(&mut db, nspace, "noauth_noud")?); + assert!(app_key_exists(&mut db, nspace, "noauth_ud")?); + assert!(!app_key_exists(&mut db, nspace, "auth_noud")?); + assert!(!app_key_exists(&mut db, nspace, "auth_ud")?); + assert!(app_key_exists(&mut db, other_user_nspace, "auth_ud")?); + + Ok(()) +} + +#[test] +fn test_store_super_key() -> Result<()> { + let mut db = new_test_db()?; + let pw: keystore2_crypto::Password = (&b"xyzabc"[..]).into(); + let super_key = keystore2_crypto::generate_aes256_key()?; + let secret_bytes = b"keystore2 is great."; + let (encrypted_secret, iv, tag) = keystore2_crypto::aes_gcm_encrypt(secret_bytes, &super_key)?; + + let (encrypted_super_key, metadata) = SuperKeyManager::encrypt_with_password(&super_key, &pw)?; + db.store_super_key( + 1, + &USER_AFTER_FIRST_UNLOCK_SUPER_KEY, + &encrypted_super_key, + &metadata, + &KeyMetaData::new(), + )?; + + // Check if super key exists. + assert!(db.key_exists( + Domain::APP, + 1, + USER_AFTER_FIRST_UNLOCK_SUPER_KEY.alias, + KeyType::Super + )?); + + let (_, key_entry) = db.load_super_key(&USER_AFTER_FIRST_UNLOCK_SUPER_KEY, 1)?.unwrap(); + let loaded_super_key = SuperKeyManager::extract_super_key_from_key_entry( + USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm, + key_entry, + &pw, + None, + )?; + + let decrypted_secret_bytes = loaded_super_key.decrypt(&encrypted_secret, &iv, &tag)?; + assert_eq!(secret_bytes, &*decrypted_secret_bytes); + + Ok(()) +} + +fn get_valid_statsd_storage_types() -> Vec<MetricsStorage> { + vec![ + MetricsStorage::KEY_ENTRY, + MetricsStorage::KEY_ENTRY_ID_INDEX, + MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX, + MetricsStorage::BLOB_ENTRY, + MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX, + MetricsStorage::KEY_PARAMETER, + MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX, + MetricsStorage::KEY_METADATA, + MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX, + MetricsStorage::GRANT, + MetricsStorage::AUTH_TOKEN, + MetricsStorage::BLOB_METADATA, + MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX, + ] +} + +/// Perform a simple check to ensure that we can query all the storage types +/// that are supported by the DB. Check for reasonable values. +#[test] +fn test_query_all_valid_table_sizes() -> Result<()> { + const PAGE_SIZE: i32 = 4096; + + let mut db = new_test_db()?; + + for t in get_valid_statsd_storage_types() { + let stat = db.get_storage_stat(t)?; + // AuthToken can be less than a page since it's in a btree, not sqlite + // TODO(b/187474736) stop using if-let here + if let MetricsStorage::AUTH_TOKEN = t { + } else { + assert!(stat.size >= PAGE_SIZE); + } + assert!(stat.size >= stat.unused_size); + } + + Ok(()) +} + +fn get_storage_stats_map(db: &mut KeystoreDB) -> BTreeMap<i32, StorageStats> { + get_valid_statsd_storage_types() + .into_iter() + .map(|t| (t.0, db.get_storage_stat(t).unwrap())) + .collect() +} + +fn assert_storage_increased( + db: &mut KeystoreDB, + increased_storage_types: Vec<MetricsStorage>, + baseline: &mut BTreeMap<i32, StorageStats>, +) { + for storage in increased_storage_types { + // Verify the expected storage increased. + let new = db.get_storage_stat(storage).unwrap(); + let old = &baseline[&storage.0]; + assert!(new.size >= old.size, "{}: {} >= {}", storage.0, new.size, old.size); + assert!( + new.unused_size <= old.unused_size, + "{}: {} <= {}", + storage.0, + new.unused_size, + old.unused_size + ); + + // Update the baseline with the new value so that it succeeds in the + // later comparison. + baseline.insert(storage.0, new); + } + + // Get an updated map of the storage and verify there were no unexpected changes. + let updated_stats = get_storage_stats_map(db); + assert_eq!(updated_stats.len(), baseline.len()); + + for &k in baseline.keys() { + let stringify = |map: &BTreeMap<i32, StorageStats>| -> String { + let mut s = String::new(); + for &k in map.keys() { + writeln!(&mut s, " {}: {}, {}", &k, map[&k].size, map[&k].unused_size) + .expect("string concat failed"); + } + s + }; + + assert!( + updated_stats[&k].size == baseline[&k].size + && updated_stats[&k].unused_size == baseline[&k].unused_size, + "updated_stats:\n{}\nbaseline:\n{}", + stringify(&updated_stats), + stringify(baseline) + ); + } +} + +#[test] +fn test_verify_key_table_size_reporting() -> Result<()> { + let mut db = new_test_db()?; + let mut working_stats = get_storage_stats_map(&mut db); + + let key_id = create_key_entry(&mut db, &Domain::APP, &42, KeyType::Client, &KEYSTORE_UUID)?; + assert_storage_increased( + &mut db, + vec![ + MetricsStorage::KEY_ENTRY, + MetricsStorage::KEY_ENTRY_ID_INDEX, + MetricsStorage::KEY_ENTRY_DOMAIN_NAMESPACE_INDEX, + ], + &mut working_stats, + ); + + let mut blob_metadata = BlobMetaData::new(); + blob_metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); + db.set_blob(&key_id, SubComponentType::KEY_BLOB, Some(TEST_KEY_BLOB), None)?; + assert_storage_increased( + &mut db, + vec![ + MetricsStorage::BLOB_ENTRY, + MetricsStorage::BLOB_ENTRY_KEY_ENTRY_ID_INDEX, + MetricsStorage::BLOB_METADATA, + MetricsStorage::BLOB_METADATA_BLOB_ENTRY_ID_INDEX, + ], + &mut working_stats, + ); + + let params = make_test_params(None); + db.insert_keyparameter(&key_id, ¶ms)?; + assert_storage_increased( + &mut db, + vec![MetricsStorage::KEY_PARAMETER, MetricsStorage::KEY_PARAMETER_KEY_ENTRY_ID_INDEX], + &mut working_stats, + ); + + let mut metadata = KeyMetaData::new(); + metadata.add(KeyMetaEntry::CreationDate(DateTime::from_millis_epoch(123456789))); + db.insert_key_metadata(&key_id, &metadata)?; + assert_storage_increased( + &mut db, + vec![MetricsStorage::KEY_METADATA, MetricsStorage::KEY_METADATA_KEY_ENTRY_ID_INDEX], + &mut working_stats, + ); + + let mut sum = 0; + for stat in working_stats.values() { + sum += stat.size; + } + let total = db.get_storage_stat(MetricsStorage::DATABASE)?.size; + assert!(sum <= total, "Expected sum <= total. sum: {}, total: {}", sum, total); + + Ok(()) +} + +#[test] +fn test_verify_auth_table_size_reporting() -> Result<()> { + let mut db = new_test_db()?; + let mut working_stats = get_storage_stats_map(&mut db); + db.insert_auth_token(&HardwareAuthToken { + challenge: 123, + userId: 456, + authenticatorId: 789, + authenticatorType: kmhw_authenticator_type::ANY, + timestamp: Timestamp { milliSeconds: 10 }, + mac: b"mac".to_vec(), + }); + assert_storage_increased(&mut db, vec![MetricsStorage::AUTH_TOKEN], &mut working_stats); + Ok(()) +} + +#[test] +fn test_verify_grant_table_size_reporting() -> Result<()> { + const OWNER: i64 = 1; + let mut db = new_test_db()?; + make_test_key_entry(&mut db, Domain::APP, OWNER, TEST_ALIAS, None)?; + + let mut working_stats = get_storage_stats_map(&mut db); + db.grant( + &KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + OWNER as u32, + 123, + key_perm_set![KeyPerm::Use], + |_, _| Ok(()), + )?; + + assert_storage_increased(&mut db, vec![MetricsStorage::GRANT], &mut working_stats); + + Ok(()) +} + +#[test] +fn find_auth_token_entry_returns_latest() -> Result<()> { + let mut db = new_test_db()?; + db.insert_auth_token(&HardwareAuthToken { + challenge: 123, + userId: 456, + authenticatorId: 789, + authenticatorType: kmhw_authenticator_type::ANY, + timestamp: Timestamp { milliSeconds: 10 }, + mac: b"mac0".to_vec(), + }); + std::thread::sleep(std::time::Duration::from_millis(1)); + db.insert_auth_token(&HardwareAuthToken { + challenge: 123, + userId: 457, + authenticatorId: 789, + authenticatorType: kmhw_authenticator_type::ANY, + timestamp: Timestamp { milliSeconds: 12 }, + mac: b"mac1".to_vec(), + }); + std::thread::sleep(std::time::Duration::from_millis(1)); + db.insert_auth_token(&HardwareAuthToken { + challenge: 123, + userId: 458, + authenticatorId: 789, + authenticatorType: kmhw_authenticator_type::ANY, + timestamp: Timestamp { milliSeconds: 3 }, + mac: b"mac2".to_vec(), + }); + // All three entries are in the database + assert_eq!(db.perboot.auth_tokens_len(), 3); + // It selected the most recent timestamp + assert_eq!(db.find_auth_token_entry(|_| true).unwrap().auth_token.mac, b"mac2".to_vec()); + Ok(()) +} + +#[test] +fn test_load_key_descriptor() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::APP, 1, TEST_ALIAS, None)?.0; + + let key = db.load_key_descriptor(key_id)?.unwrap(); + + assert_eq!(key.domain, Domain::APP); + assert_eq!(key.nspace, 1); + assert_eq!(key.alias, Some(TEST_ALIAS.to_string())); + + // No such id + assert_eq!(db.load_key_descriptor(key_id + 1)?, None); + Ok(()) +} + +#[test] +fn test_get_list_app_uids_for_sid() -> Result<()> { + let uid: i32 = 1; + let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64); + let first_sid = 667; + let second_sid = 669; + let first_app_id: i64 = 123 + uid_offset; + let second_app_id: i64 = 456 + uid_offset; + let third_app_id: i64 = 789 + uid_offset; + let unrelated_app_id: i64 = 1011 + uid_offset; + let mut db = new_test_db()?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + first_app_id, + TEST_ALIAS, + None, + &[first_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + second_app_id, + "alias2", + None, + &[first_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + second_app_id, + TEST_ALIAS, + None, + &[second_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + third_app_id, + "alias3", + None, + &[second_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids(&mut db, Domain::APP, unrelated_app_id, TEST_ALIAS, None, &[]) + .context("test_get_list_app_uids_for_sid")?; + + let mut first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?; + first_sid_apps.sort(); + assert_eq!(first_sid_apps, vec![first_app_id, second_app_id]); + let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?; + second_sid_apps.sort(); + assert_eq!(second_sid_apps, vec![second_app_id, third_app_id]); + Ok(()) +} + +#[test] +fn test_get_list_app_uids_with_multiple_sids() -> Result<()> { + let uid: i32 = 1; + let uid_offset: i64 = (uid as i64) * (AID_USER_OFFSET as i64); + let first_sid = 667; + let second_sid = 669; + let third_sid = 772; + let first_app_id: i64 = 123 + uid_offset; + let second_app_id: i64 = 456 + uid_offset; + let mut db = new_test_db()?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + first_app_id, + TEST_ALIAS, + None, + &[first_sid, second_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + make_test_key_entry_with_sids( + &mut db, + Domain::APP, + second_app_id, + "alias2", + None, + &[second_sid, third_sid], + ) + .context("test_get_list_app_uids_for_sid")?; + + let first_sid_apps = db.get_app_uids_affected_by_sid(uid, first_sid)?; + assert_eq!(first_sid_apps, vec![first_app_id]); + + let mut second_sid_apps = db.get_app_uids_affected_by_sid(uid, second_sid)?; + second_sid_apps.sort(); + assert_eq!(second_sid_apps, vec![first_app_id, second_app_id]); + + let third_sid_apps = db.get_app_uids_affected_by_sid(uid, third_sid)?; + assert_eq!(third_sid_apps, vec![second_app_id]); + Ok(()) +} diff --git a/keystore2/src/database/versioning.rs b/keystore2/src/database/versioning.rs index 2c816f44..bc68f159 100644 --- a/keystore2/src/database/versioning.rs +++ b/keystore2/src/database/versioning.rs @@ -15,7 +15,7 @@ use anyhow::{anyhow, Context, Result}; use rusqlite::{params, OptionalExtension, Transaction}; -pub fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32> { +fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u32> { tx.execute( "CREATE TABLE IF NOT EXISTS persistent.version ( id INTEGER PRIMARY KEY, @@ -61,7 +61,7 @@ pub fn create_or_get_version(tx: &Transaction, current_version: u32) -> Result<u Ok(version) } -pub fn update_version(tx: &Transaction, new_version: u32) -> Result<()> { +fn update_version(tx: &Transaction, new_version: u32) -> Result<()> { let updated = tx .execute("UPDATE persistent.version SET version = ? WHERE id = 0;", params![new_version]) .context("In update_version: Failed to update row.")?; diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs index 55c9591f..70383237 100644 --- a/keystore2/src/enforcements.rs +++ b/keystore2/src/enforcements.rs @@ -50,8 +50,6 @@ use std::{ enum AuthRequestState { /// An outstanding per operation authorization request. OpAuth, - /// An outstanding request for per operation authorization and secure timestamp. - TimeStampedOpAuth(Mutex<Receiver<Result<TimeStampToken, Error>>>), /// An outstanding request for a timestamp token. TimeStamp(Mutex<Receiver<Result<TimeStampToken, Error>>>), } @@ -59,8 +57,7 @@ enum AuthRequestState { #[derive(Debug)] struct AuthRequest { state: AuthRequestState, - /// This need to be set to Some to fulfill a AuthRequestState::OpAuth or - /// AuthRequestState::TimeStampedOpAuth. + /// This need to be set to Some to fulfill an AuthRequestState::OpAuth. hat: Mutex<Option<HardwareAuthToken>>, } @@ -69,13 +66,6 @@ impl AuthRequest { Arc::new(Self { state: AuthRequestState::OpAuth, hat: Mutex::new(None) }) } - fn timestamped_op_auth(receiver: Receiver<Result<TimeStampToken, Error>>) -> Arc<Self> { - Arc::new(Self { - state: AuthRequestState::TimeStampedOpAuth(Mutex::new(receiver)), - hat: Mutex::new(None), - }) - } - fn timestamp( hat: HardwareAuthToken, receiver: Receiver<Result<TimeStampToken, Error>>, @@ -100,7 +90,7 @@ impl AuthRequest { .context(ks_err!("No operation auth token received."))?; let tst = match &self.state { - AuthRequestState::TimeStampedOpAuth(recv) | AuthRequestState::TimeStamp(recv) => { + AuthRequestState::TimeStamp(recv) => { let result = recv .lock() .unwrap() @@ -132,9 +122,6 @@ enum DeferredAuthState { /// loaded from the database, but it has to be accompanied by a time stamp token to inform /// the target KM with a different clock about the time on the authenticators. TimeStampRequired(HardwareAuthToken), - /// Indicates that both an operation bound auth token and a verification token are - /// before the operation can commence. - TimeStampedOpAuthRequired, /// In this state the auth info is waiting for the deferred authorizations to come in. /// We block on timestamp tokens, because we can always make progress on these requests. /// The per-op auth tokens might never come, which means we fail if the client calls @@ -254,16 +241,6 @@ impl AuthInfo { self.state = DeferredAuthState::Waiting(auth_request); Some(OperationChallenge { challenge }) } - DeferredAuthState::TimeStampedOpAuthRequired => { - let (sender, receiver) = channel::<Result<TimeStampToken, Error>>(); - let auth_request = AuthRequest::timestamped_op_auth(receiver); - let token_receiver = TokenReceiver(Arc::downgrade(&auth_request)); - ENFORCEMENTS.register_op_auth_receiver(challenge, token_receiver); - - ASYNC_TASK.queue_hi(move |_| timestamp_token_request(challenge, sender)); - self.state = DeferredAuthState::Waiting(auth_request); - Some(OperationChallenge { challenge }) - } DeferredAuthState::TimeStampRequired(hat) => { let hat = (*hat).clone(); let (sender, receiver) = channel::<Result<TimeStampToken, Error>>(); @@ -349,9 +326,7 @@ impl AuthInfo { match &self.state { DeferredAuthState::NoAuthRequired => Ok((None, None)), DeferredAuthState::Token(hat, tst) => Ok((Some((*hat).clone()), (*tst).clone())), - DeferredAuthState::OpAuthRequired - | DeferredAuthState::TimeStampedOpAuthRequired - | DeferredAuthState::TimeStampRequired(_) => { + DeferredAuthState::OpAuthRequired | DeferredAuthState::TimeStampRequired(_) => { Err(Error::Km(ErrorCode::KEY_USER_NOT_AUTHENTICATED)).context(ks_err!( "No operation auth token requested??? \ This should not happen." @@ -476,7 +451,6 @@ impl Enforcements { let mut user_id: i32 = -1; let mut user_secure_ids = Vec::<i64>::new(); let mut key_time_out: Option<i64> = None; - let mut allow_while_on_body = false; let mut unlocked_device_required = false; let mut key_usage_limited: Option<i64> = None; let mut confirmation_token_receiver: Option<Arc<Mutex<Option<Receiver<Vec<u8>>>>>> = None; @@ -533,9 +507,6 @@ impl Enforcements { KeyParameterValue::UnlockedDeviceRequired => { unlocked_device_required = true; } - KeyParameterValue::AllowWhileOnBody => { - allow_while_on_body = true; - } KeyParameterValue::UsageCountLimit(_) => { // We don't examine the limit here because this is enforced on finish. // Instead, we store the key_id so that finish can look up the key @@ -603,132 +574,39 @@ impl Enforcements { } } - if android_security_flags::fix_unlocked_device_required_keys_v2() { - let (hat, state) = if user_secure_ids.is_empty() { - (None, DeferredAuthState::NoAuthRequired) - } else if let Some(key_time_out) = key_time_out { - let (hat, last_off_body) = - Self::find_auth_token(|hat: &AuthTokenEntry| match user_auth_type { - Some(auth_type) => hat.satisfies(&user_secure_ids, auth_type), - None => false, // not reachable due to earlier check - }) - .ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) - .context(ks_err!("No suitable auth token found."))?; - let now = BootTime::now(); - let token_age = now - .checked_sub(&hat.time_received()) - .ok_or_else(Error::sys) - .context(ks_err!( - "Overflow while computing Auth token validity. \ - Validity cannot be established." - ))?; - - let on_body_extended = allow_while_on_body && last_off_body < hat.time_received(); - - if token_age.seconds() > key_time_out && !on_body_extended { - return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) - .context(ks_err!("matching auth token is expired.")); - } - let state = if requires_timestamp { - DeferredAuthState::TimeStampRequired(hat.auth_token().clone()) - } else { - DeferredAuthState::NoAuthRequired - }; - (Some(hat.take_auth_token()), state) + let (hat, state) = if user_secure_ids.is_empty() { + (None, DeferredAuthState::NoAuthRequired) + } else if let Some(key_time_out) = key_time_out { + let hat = Self::find_auth_token(|hat: &AuthTokenEntry| match user_auth_type { + Some(auth_type) => hat.satisfies(&user_secure_ids, auth_type), + None => false, // not reachable due to earlier check + }) + .ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) + .context(ks_err!("No suitable auth token found."))?; + let now = BootTime::now(); + let token_age = + now.checked_sub(&hat.time_received()).ok_or_else(Error::sys).context(ks_err!( + "Overflow while computing Auth token validity. \ + Validity cannot be established." + ))?; + + if token_age.seconds() > key_time_out { + return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) + .context(ks_err!("matching auth token is expired.")); + } + let state = if requires_timestamp { + DeferredAuthState::TimeStampRequired(hat.auth_token().clone()) } else { - (None, DeferredAuthState::OpAuthRequired) + DeferredAuthState::NoAuthRequired }; - return Ok((hat, AuthInfo { state, key_usage_limited, confirmation_token_receiver })); - } - - if !unlocked_device_required && no_auth_required { - return Ok(( - None, - AuthInfo { - state: DeferredAuthState::NoAuthRequired, - key_usage_limited, - confirmation_token_receiver, - }, - )); - } - - let has_sids = !user_secure_ids.is_empty(); - - let timeout_bound = key_time_out.is_some() && has_sids; - - let per_op_bound = key_time_out.is_none() && has_sids; - - let need_auth_token = timeout_bound || unlocked_device_required; - - let hat_and_last_off_body = if need_auth_token { - let hat_and_last_off_body = Self::find_auth_token(|hat: &AuthTokenEntry| { - if let (Some(auth_type), true) = (user_auth_type, timeout_bound) { - hat.satisfies(&user_secure_ids, auth_type) - } else { - unlocked_device_required - } - }); - Some( - hat_and_last_off_body - .ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) - .context(ks_err!("No suitable auth token found."))?, - ) + (Some(hat.take_auth_token()), state) } else { - None + (None, DeferredAuthState::OpAuthRequired) }; - - // Now check the validity of the auth token if the key is timeout bound. - let hat = match (hat_and_last_off_body, key_time_out) { - (Some((hat, last_off_body)), Some(key_time_out)) => { - let now = BootTime::now(); - let token_age = now - .checked_sub(&hat.time_received()) - .ok_or_else(Error::sys) - .context(ks_err!( - "Overflow while computing Auth token validity. \ - Validity cannot be established." - ))?; - - let on_body_extended = allow_while_on_body && last_off_body < hat.time_received(); - - if token_age.seconds() > key_time_out && !on_body_extended { - return Err(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED)) - .context(ks_err!("matching auth token is expired.")); - } - Some(hat) - } - (Some((hat, _)), None) => Some(hat), - // If timeout_bound is true, above code must have retrieved a HAT or returned with - // KEY_USER_NOT_AUTHENTICATED. This arm should not be reachable. - (None, Some(_)) => panic!("Logical error."), - _ => None, - }; - - Ok(match (hat, requires_timestamp, per_op_bound) { - // Per-op-bound and Some(hat) can only happen if we are both per-op bound and unlocked - // device required. In addition, this KM instance needs a timestamp token. - // So the HAT cannot be presented on create. So on update/finish we present both - // an per-op-bound auth token and a timestamp token. - (Some(_), true, true) => (None, DeferredAuthState::TimeStampedOpAuthRequired), - (Some(hat), true, false) => ( - Some(hat.auth_token().clone()), - DeferredAuthState::TimeStampRequired(hat.take_auth_token()), - ), - (Some(hat), false, true) => { - (Some(hat.take_auth_token()), DeferredAuthState::OpAuthRequired) - } - (Some(hat), false, false) => { - (Some(hat.take_auth_token()), DeferredAuthState::NoAuthRequired) - } - (None, _, true) => (None, DeferredAuthState::OpAuthRequired), - (None, _, false) => (None, DeferredAuthState::NoAuthRequired), - }) - .map(|(hat, state)| { - (hat, AuthInfo { state, key_usage_limited, confirmation_token_receiver }) - }) + Ok((hat, AuthInfo { state, key_usage_limited, confirmation_token_receiver })) } - fn find_auth_token<F>(p: F) -> Option<(AuthTokenEntry, BootTime)> + fn find_auth_token<F>(p: F) -> Option<AuthTokenEntry> where F: Fn(&AuthTokenEntry) -> bool, { @@ -848,7 +726,7 @@ impl Enforcements { (challenge == hat.challenge()) && hat.satisfies(&sids, auth_type) }); - let auth_token = if let Some((auth_token_entry, _)) = result { + let auth_token = if let Some(auth_token_entry) = result { auth_token_entry.take_auth_token() } else { // Filter the matching auth tokens by age. @@ -863,7 +741,7 @@ impl Enforcements { token_valid && auth_token_entry.satisfies(&sids, auth_type) }); - if let Some((auth_token_entry, _)) = result { + if let Some(auth_token_entry) = result { auth_token_entry.take_auth_token() } else { return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND)) @@ -895,11 +773,7 @@ impl Enforcements { let result = Self::find_auth_token(|entry: &AuthTokenEntry| entry.satisfies(&sids, auth_type)); - if let Some((auth_token_entry, _)) = result { - Some(auth_token_entry.time_received()) - } else { - None - } + result.map(|auth_token_entry| auth_token_entry.time_received()) } } diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs index f0d0d27b..5e80266e 100644 --- a/keystore2/src/error.rs +++ b/keystore2/src/error.rs @@ -21,8 +21,8 @@ //! //! `SerializedError` is used send error codes on the wire. //! -//! `map_or_log_err` is a convenience method used to convert `anyhow::Error` into `SerializedError` -//! wire type. +//! `into_[logged_]binder` is a convenience method used to convert `anyhow::Error` into +//! `SerializedError` wire type. //! //! Keystore functions should use `anyhow::Result` to return error conditions, and context should //! be added every time an error is forwarded. @@ -38,6 +38,9 @@ use rkpd_client::Error as RkpdError; use std::cmp::PartialEq; use std::ffi::CString; +#[cfg(test)] +pub mod tests; + /// This is the main Keystore error type. It wraps the Keystore `ResponseCode` generated /// from AIDL in the `Rc` variant and Keymint `ErrorCode` in the Km variant. #[derive(Debug, thiserror::Error, PartialEq, Eq)] @@ -128,14 +131,11 @@ pub fn map_km_error<T>(r: BinderResult<T>) -> Result<T, Error> { // Non negative error codes cannot be KM error codes. // So we create an `Error::Binder` variant to preserve // the service specific error code for logging. - // `map_or_log_err` will map this on a system error, - // but not before logging the details to logcat. Error::Binder(ExceptionCode::SERVICE_SPECIFIC, se) } } // We create `Error::Binder` to preserve the exception code // for logging. - // `map_or_log_err` will map this on a system error. e_code => Error::Binder(e_code, 0), } }) @@ -163,46 +163,17 @@ pub fn map_binder_status_code<T>(r: Result<T, StatusCode>) -> Result<T, Error> { r.map_err(Error::BinderTransaction) } -/// This function should be used by Keystore service calls to translate error conditions -/// into service specific exceptions. -/// -/// All error conditions get logged by this function, except for KEY_NOT_FOUND error. -/// -/// `handle_ok` will be called if `result` is `Ok(value)` where `value` will be passed -/// as argument to `handle_ok`. `handle_ok` must generate a `BinderResult<T>`, but it -/// typically returns Ok(value). -/// -/// # Examples -/// -/// ``` -/// fn loadKey() -> anyhow::Result<Vec<u8>> { -/// if (good_but_auth_required) { -/// Ok(vec!['k', 'e', 'y']) -/// } else { -/// Err(anyhow!(Error::Rc(ResponseCode::KEY_NOT_FOUND))) -/// } -/// } -/// -/// map_or_log_err(loadKey(), Ok) -/// ``` -pub fn map_or_log_err<T, U, F>(result: anyhow::Result<U>, handle_ok: F) -> BinderResult<T> -where - F: FnOnce(U) -> BinderResult<T>, -{ - map_err_with( - result, - |e| { - // Make the key not found errors silent. - if !matches!( - e.root_cause().downcast_ref::<Error>(), - Some(Error::Rc(ResponseCode::KEY_NOT_FOUND)) - ) { - log::error!("{:?}", e); - } - e - }, - handle_ok, - ) +/// Convert an [`anyhow::Error`] to a [`binder::Status`], logging the value +/// along the way (except if it is `KEY_NOT_FOUND`). +pub fn into_logged_binder(e: anyhow::Error) -> BinderStatus { + // Log everything except key not found. + if !matches!( + e.root_cause().downcast_ref::<Error>(), + Some(Error::Rc(ResponseCode::KEY_NOT_FOUND)) + ) { + log::error!("{:?}", e); + } + into_binder(e) } /// This function turns an anyhow error into an optional CString. @@ -219,29 +190,10 @@ pub fn anyhow_error_to_cstring(e: &anyhow::Error) -> Option<CString> { } } -/// This function behaves similar to map_or_log_error, but it does not log the errors, instead -/// it calls map_err on the error before mapping it to a binder result allowing callers to -/// log or transform the error before mapping it. -pub fn map_err_with<T, U, F1, F2>( - result: anyhow::Result<U>, - map_err: F1, - handle_ok: F2, -) -> BinderResult<T> -where - F1: FnOnce(anyhow::Error) -> anyhow::Error, - F2: FnOnce(U) -> BinderResult<T>, -{ - result.map_or_else( - |e| { - let e = map_err(e); - let rc = anyhow_error_to_serialized_error(&e); - Err(BinderStatus::new_service_specific_error( - rc.0, - anyhow_error_to_cstring(&e).as_deref(), - )) - }, - handle_ok, - ) +/// Convert an [`anyhow::Error`] to a [`binder::Status`]. +pub fn into_binder(e: anyhow::Error) -> binder::Status { + let rc = anyhow_error_to_serialized_error(&e); + BinderStatus::new_service_specific_error(rc.0, anyhow_error_to_cstring(&e).as_deref()) } /// This type is used to send error codes on the wire. @@ -283,206 +235,3 @@ pub fn anyhow_error_to_serialized_error(e: &anyhow::Error) -> SerializedError { }, } } - -#[cfg(test)] -pub mod tests { - - use super::*; - use android_system_keystore2::binder::{ - ExceptionCode, Result as BinderResult, Status as BinderStatus, - }; - use anyhow::{anyhow, Context}; - - fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> { - Err(anyhow!(Error::Rc(rc))).context("nested nested rc") - } - - fn nested_rc(rc: ResponseCode) -> anyhow::Result<()> { - nested_nested_rc(rc).context("nested rc") - } - - fn nested_nested_ec(ec: ErrorCode) -> anyhow::Result<()> { - Err(anyhow!(Error::Km(ec))).context("nested nested ec") - } - - fn nested_ec(ec: ErrorCode) -> anyhow::Result<()> { - nested_nested_ec(ec).context("nested ec") - } - - fn nested_nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> { - Ok(rc) - } - - fn nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> { - nested_nested_ok(rc).context("nested ok") - } - - fn nested_nested_selinux_perm() -> anyhow::Result<()> { - Err(anyhow!(selinux::Error::perm())).context("nested nexted selinux permission denied") - } - - fn nested_selinux_perm() -> anyhow::Result<()> { - nested_nested_selinux_perm().context("nested selinux permission denied") - } - - #[derive(Debug, thiserror::Error)] - enum TestError { - #[error("TestError::Fail")] - Fail = 0, - } - - fn nested_nested_other_error() -> anyhow::Result<()> { - Err(anyhow!(TestError::Fail)).context("nested nested other error") - } - - fn nested_other_error() -> anyhow::Result<()> { - nested_nested_other_error().context("nested other error") - } - - fn binder_sse_error(sse: i32) -> BinderResult<()> { - Err(BinderStatus::new_service_specific_error(sse, None)) - } - - fn binder_exception(ex: ExceptionCode) -> BinderResult<()> { - Err(BinderStatus::new_exception(ex, None)) - } - - #[test] - fn keystore_error_test() -> anyhow::Result<(), String> { - android_logger::init_once( - android_logger::Config::default() - .with_tag("keystore_error_tests") - .with_max_level(log::LevelFilter::Debug), - ); - // All Error::Rc(x) get mapped on a service specific error - // code of x. - for rc in ResponseCode::LOCKED.0..ResponseCode::BACKEND_BUSY.0 { - assert_eq!( - Result::<(), i32>::Err(rc), - map_or_log_err(nested_rc(ResponseCode(rc)), |_| Err(BinderStatus::ok())) - .map_err(|s| s.service_specific_error()) - ); - } - - // All Keystore Error::Km(x) get mapped on a service - // specific error of x. - for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 { - assert_eq!( - Result::<(), i32>::Err(ec), - map_or_log_err(nested_ec(ErrorCode(ec)), |_| Err(BinderStatus::ok())) - .map_err(|s| s.service_specific_error()) - ); - } - - // All Keymint errors x received through a Binder Result get mapped on - // a service specific error of x. - for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 { - assert_eq!( - Result::<(), i32>::Err(ec), - map_or_log_err( - map_km_error(binder_sse_error(ec)) - .with_context(|| format!("Km error code: {}.", ec)), - |_| Err(BinderStatus::ok()) - ) - .map_err(|s| s.service_specific_error()) - ); - } - - // map_km_error creates an Error::Binder variant storing - // ExceptionCode::SERVICE_SPECIFIC and the given - // service specific error. - let sse = map_km_error(binder_sse_error(1)); - assert_eq!(Err(Error::Binder(ExceptionCode::SERVICE_SPECIFIC, 1)), sse); - // map_or_log_err then maps it on a service specific error of ResponseCode::SYSTEM_ERROR. - assert_eq!( - Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR), - map_or_log_err(sse.context("Non negative service specific error."), |_| Err( - BinderStatus::ok() - )) - .map_err(|s| ResponseCode(s.service_specific_error())) - ); - - // map_km_error creates a Error::Binder variant storing the given exception code. - let binder_exception = map_km_error(binder_exception(ExceptionCode::TRANSACTION_FAILED)); - assert_eq!(Err(Error::Binder(ExceptionCode::TRANSACTION_FAILED, 0)), binder_exception); - // map_or_log_err then maps it on a service specific error of ResponseCode::SYSTEM_ERROR. - assert_eq!( - Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR), - map_or_log_err(binder_exception.context("Binder Exception."), |_| Err( - BinderStatus::ok() - )) - .map_err(|s| ResponseCode(s.service_specific_error())) - ); - - // selinux::Error::Perm() needs to be mapped to ResponseCode::PERMISSION_DENIED - assert_eq!( - Result::<(), ResponseCode>::Err(ResponseCode::PERMISSION_DENIED), - map_or_log_err(nested_selinux_perm(), |_| Err(BinderStatus::ok())) - .map_err(|s| ResponseCode(s.service_specific_error())) - ); - - // All other errors get mapped on System Error. - assert_eq!( - Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR), - map_or_log_err(nested_other_error(), |_| Err(BinderStatus::ok())) - .map_err(|s| ResponseCode(s.service_specific_error())) - ); - - // Result::Ok variants get passed to the ok handler. - assert_eq!(Ok(ResponseCode::LOCKED), map_or_log_err(nested_ok(ResponseCode::LOCKED), Ok)); - assert_eq!( - Ok(ResponseCode::SYSTEM_ERROR), - map_or_log_err(nested_ok(ResponseCode::SYSTEM_ERROR), Ok) - ); - - Ok(()) - } - - //Helper function to test whether error cases are handled as expected. - pub fn check_result_contains_error_string<T>( - result: anyhow::Result<T>, - expected_error_string: &str, - ) { - let error_str = format!( - "{:#?}", - result.err().unwrap_or_else(|| panic!("Expected the error: {}", expected_error_string)) - ); - assert!( - error_str.contains(expected_error_string), - "The string \"{}\" should contain \"{}\"", - error_str, - expected_error_string - ); - } - - #[test] - fn rkpd_error_is_in_sync_with_response_code() { - let error_mapping = [ - (RkpdError::RequestCancelled, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), - (RkpdError::GetRegistrationFailed, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), - ( - RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_UNKNOWN), - ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR, - ), - ( - RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PERMANENT), - ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR, - ), - ( - RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY), - ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY, - ), - ( - RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH), - ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE, - ), - (RkpdError::StoreUpgradedKeyFailed, ResponseCode::SYSTEM_ERROR), - (RkpdError::RetryableTimeout, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), - (RkpdError::Timeout, ResponseCode::SYSTEM_ERROR), - ]; - for (rkpd_error, expected_response_code) in error_mapping { - let e: Error = rkpd_error.into(); - assert_eq!(e, Error::Rc(expected_response_code)); - } - } -} // mod tests diff --git a/keystore2/src/error/tests.rs b/keystore2/src/error/tests.rs new file mode 100644 index 00000000..d50091b0 --- /dev/null +++ b/keystore2/src/error/tests.rs @@ -0,0 +1,218 @@ +// Copyright 2020, 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. + +//! Error handling tests. + +use super::*; +use android_system_keystore2::binder::{ + ExceptionCode, Result as BinderResult, Status as BinderStatus, +}; +use anyhow::{anyhow, Context}; + +fn nested_nested_rc(rc: ResponseCode) -> anyhow::Result<()> { + Err(anyhow!(Error::Rc(rc))).context("nested nested rc") +} + +fn nested_rc(rc: ResponseCode) -> anyhow::Result<()> { + nested_nested_rc(rc).context("nested rc") +} + +fn nested_nested_ec(ec: ErrorCode) -> anyhow::Result<()> { + Err(anyhow!(Error::Km(ec))).context("nested nested ec") +} + +fn nested_ec(ec: ErrorCode) -> anyhow::Result<()> { + nested_nested_ec(ec).context("nested ec") +} + +fn nested_nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> { + Ok(rc) +} + +fn nested_ok(rc: ResponseCode) -> anyhow::Result<ResponseCode> { + nested_nested_ok(rc).context("nested ok") +} + +fn nested_nested_selinux_perm() -> anyhow::Result<()> { + Err(anyhow!(selinux::Error::perm())).context("nested nexted selinux permission denied") +} + +fn nested_selinux_perm() -> anyhow::Result<()> { + nested_nested_selinux_perm().context("nested selinux permission denied") +} + +#[derive(Debug, thiserror::Error)] +enum TestError { + #[error("TestError::Fail")] + Fail = 0, +} + +fn nested_nested_other_error() -> anyhow::Result<()> { + Err(anyhow!(TestError::Fail)).context("nested nested other error") +} + +fn nested_other_error() -> anyhow::Result<()> { + nested_nested_other_error().context("nested other error") +} + +fn binder_sse_error(sse: i32) -> BinderResult<()> { + Err(BinderStatus::new_service_specific_error(sse, None)) +} + +fn binder_exception(ex: ExceptionCode) -> BinderResult<()> { + Err(BinderStatus::new_exception(ex, None)) +} + +#[test] +fn keystore_error_test() -> anyhow::Result<(), String> { + android_logger::init_once( + android_logger::Config::default() + .with_tag("keystore_error_tests") + .with_max_level(log::LevelFilter::Debug), + ); + // All Error::Rc(x) get mapped on a service specific error + // code of x. + for rc in ResponseCode::LOCKED.0..ResponseCode::BACKEND_BUSY.0 { + assert_eq!( + Result::<(), i32>::Err(rc), + nested_rc(ResponseCode(rc)) + .map_err(into_logged_binder) + .map_err(|s| s.service_specific_error()) + ); + } + + // All Keystore Error::Km(x) get mapped on a service + // specific error of x. + for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 { + assert_eq!( + Result::<(), i32>::Err(ec), + nested_ec(ErrorCode(ec)) + .map_err(into_logged_binder) + .map_err(|s| s.service_specific_error()) + ); + } + + // All Keymint errors x received through a Binder Result get mapped on + // a service specific error of x. + for ec in ErrorCode::UNKNOWN_ERROR.0..ErrorCode::ROOT_OF_TRUST_ALREADY_SET.0 { + assert_eq!( + Result::<(), i32>::Err(ec), + map_km_error(binder_sse_error(ec)) + .with_context(|| format!("Km error code: {}.", ec)) + .map_err(into_logged_binder) + .map_err(|s| s.service_specific_error()) + ); + } + + // map_km_error creates an Error::Binder variant storing + // ExceptionCode::SERVICE_SPECIFIC and the given + // service specific error. + let sse = map_km_error(binder_sse_error(1)); + assert_eq!(Err(Error::Binder(ExceptionCode::SERVICE_SPECIFIC, 1)), sse); + // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR. + assert_eq!( + Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR), + sse.context("Non negative service specific error.") + .map_err(into_logged_binder) + .map_err(|s| ResponseCode(s.service_specific_error())) + ); + + // map_km_error creates a Error::Binder variant storing the given exception code. + let binder_exception = map_km_error(binder_exception(ExceptionCode::TRANSACTION_FAILED)); + assert_eq!(Err(Error::Binder(ExceptionCode::TRANSACTION_FAILED, 0)), binder_exception); + // into_binder then maps it on a service specific error of ResponseCode::SYSTEM_ERROR. + assert_eq!( + Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR), + binder_exception + .context("Binder Exception.") + .map_err(into_logged_binder) + .map_err(|s| ResponseCode(s.service_specific_error())) + ); + + // selinux::Error::Perm() needs to be mapped to ResponseCode::PERMISSION_DENIED + assert_eq!( + Result::<(), ResponseCode>::Err(ResponseCode::PERMISSION_DENIED), + nested_selinux_perm() + .map_err(into_logged_binder) + .map_err(|s| ResponseCode(s.service_specific_error())) + ); + + // All other errors get mapped on System Error. + assert_eq!( + Result::<(), ResponseCode>::Err(ResponseCode::SYSTEM_ERROR), + nested_other_error() + .map_err(into_logged_binder) + .map_err(|s| ResponseCode(s.service_specific_error())) + ); + + // Result::Ok variants get passed to the ok handler. + assert_eq!( + Ok(ResponseCode::LOCKED), + nested_ok(ResponseCode::LOCKED).map_err(into_logged_binder) + ); + assert_eq!( + Ok(ResponseCode::SYSTEM_ERROR), + nested_ok(ResponseCode::SYSTEM_ERROR).map_err(into_logged_binder) + ); + + Ok(()) +} + +//Helper function to test whether error cases are handled as expected. +pub fn check_result_contains_error_string<T>( + result: anyhow::Result<T>, + expected_error_string: &str, +) { + let error_str = format!( + "{:#?}", + result.err().unwrap_or_else(|| panic!("Expected the error: {}", expected_error_string)) + ); + assert!( + error_str.contains(expected_error_string), + "The string \"{}\" should contain \"{}\"", + error_str, + expected_error_string + ); +} + +#[test] +fn rkpd_error_is_in_sync_with_response_code() { + let error_mapping = [ + (RkpdError::RequestCancelled, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), + (RkpdError::GetRegistrationFailed, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), + ( + RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_UNKNOWN), + ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR, + ), + ( + RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PERMANENT), + ResponseCode::OUT_OF_KEYS_PERMANENT_ERROR, + ), + ( + RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_PENDING_INTERNET_CONNECTIVITY), + ResponseCode::OUT_OF_KEYS_PENDING_INTERNET_CONNECTIVITY, + ), + ( + RkpdError::GetKeyFailed(GetKeyErrorCode::ERROR_REQUIRES_SECURITY_PATCH), + ResponseCode::OUT_OF_KEYS_REQUIRES_SYSTEM_UPGRADE, + ), + (RkpdError::StoreUpgradedKeyFailed, ResponseCode::SYSTEM_ERROR), + (RkpdError::RetryableTimeout, ResponseCode::OUT_OF_KEYS_TRANSIENT_ERROR), + (RkpdError::Timeout, ResponseCode::SYSTEM_ERROR), + ]; + for (rkpd_error, expected_response_code) in error_mapping { + let e: Error = rkpd_error.into(); + assert_eq!(e, Error::Rc(expected_response_code)); + } +} diff --git a/keystore2/src/gc.rs b/keystore2/src/gc.rs index a0333568..f2341e3c 100644 --- a/keystore2/src/gc.rs +++ b/keystore2/src/gc.rs @@ -21,7 +21,7 @@ use crate::ks_err; use crate::{ async_task, - database::{BlobMetaData, KeystoreDB, Uuid}, + database::{KeystoreDB, SupersededBlob, Uuid}, super_key::SuperKeyManager, }; use anyhow::{Context, Result}; @@ -84,7 +84,7 @@ impl Gc { struct GcInternal { deleted_blob_ids: Vec<i64>, - superseded_blobs: Vec<(i64, Vec<u8>, BlobMetaData)>, + superseded_blobs: Vec<SupersededBlob>, invalidate_key: Box<dyn Fn(&Uuid, &[u8]) -> Result<()> + Send + 'static>, db: KeystoreDB, async_task: std::sync::Weak<AsyncTask>, @@ -109,7 +109,7 @@ impl GcInternal { self.superseded_blobs = blobs; } - if let Some((blob_id, blob, blob_metadata)) = self.superseded_blobs.pop() { + if let Some(SupersededBlob { blob_id, blob, metadata }) = self.superseded_blobs.pop() { // Add the next blob_id to the deleted blob ids list. So it will be // removed from the database regardless of whether the following // succeeds or not. @@ -119,12 +119,12 @@ impl GcInternal { // and delete the key, unwrapping if necessary and possible. // (At this time keys may get deleted without having the super encryption // key in this case we can only delete the key from the database.) - if let Some(uuid) = blob_metadata.km_uuid() { + if let Some(uuid) = metadata.km_uuid() { let blob = self .super_key .read() .unwrap() - .unwrap_key_if_required(&blob_metadata, &blob) + .unwrap_key_if_required(&metadata, &blob) .context(ks_err!("Trying to unwrap to-be-deleted blob.",))?; (self.invalidate_key)(uuid, &blob).context(ks_err!("Trying to invalidate key."))?; } diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs index eb755a6b..a90ba798 100644 --- a/keystore2/src/globals.rs +++ b/keystore2/src/globals.rs @@ -16,6 +16,7 @@ //! database connections and connections to services that Keystore needs //! to talk to. +use crate::async_task::AsyncTask; use crate::gc::Gc; use crate::km_compat::{BacklevelKeyMintWrapper, KeyMintV1}; use crate::ks_err; @@ -23,7 +24,6 @@ use crate::legacy_blob::LegacyBlobLoader; use crate::legacy_importer::LegacyImporter; use crate::super_key::SuperKeyManager; use crate::utils::watchdog as wd; -use crate::{async_task::AsyncTask, database::BootTime}; use crate::{ database::KeystoreDB, database::Uuid, @@ -44,8 +44,8 @@ use android_hardware_security_secureclock::aidl::android::hardware::security::se }; use android_security_compat::aidl::android::security::compat::IKeystoreCompatService::IKeystoreCompatService; use anyhow::{Context, Result}; -use binder::get_declared_instances; use binder::FromIBinder; +use binder::{get_declared_instances, is_declared}; use lazy_static::lazy_static; use std::sync::{Arc, Mutex, RwLock}; use std::{cell::RefCell, sync::Once}; @@ -62,22 +62,25 @@ static DB_INIT: Once = Once::new(); /// is run only once, as long as the ASYNC_TASK instance is the same. So only one additional /// database connection is created for the garbage collector worker. pub fn create_thread_local_db() -> KeystoreDB { - let db_path = DB_PATH.read().expect("Could not get the database directory."); - - let mut db = KeystoreDB::new(&db_path, Some(GC.clone())).expect("Failed to open database."); + let db_path = DB_PATH.read().expect("Could not get the database directory"); + + let result = KeystoreDB::new(&db_path, Some(GC.clone())); + let mut db = match result { + Ok(db) => db, + Err(e) => { + log::error!("Failed to open Keystore database at {db_path:?}: {e:?}"); + log::error!("Has /data been mounted correctly?"); + panic!("Failed to open database for Keystore, cannot continue: {e:?}") + } + }; DB_INIT.call_once(|| { log::info!("Touching Keystore 2.0 database for this first time since boot."); - db.insert_last_off_body(BootTime::now()); log::info!("Calling cleanup leftovers."); - let n = db.cleanup_leftovers().expect("Failed to cleanup database on startup."); + let n = db.cleanup_leftovers().expect("Failed to cleanup database on startup"); if n != 0 { log::info!( - concat!( - "Cleaned up {} failed entries. ", - "This indicates keystore crashed during key generation." - ), - n + "Cleaned up {n} failed entries, indicating keystore crash on key generation" ); } }); @@ -89,8 +92,7 @@ thread_local! { /// same database multiple times is safe as long as each connection is /// used by only one thread. So we store one database connection per /// thread in this thread local key. - pub static DB: RefCell<KeystoreDB> = - RefCell::new(create_thread_local_db()); + pub static DB: RefCell<KeystoreDB> = RefCell::new(create_thread_local_db()); } struct DevicesMap<T: FromIBinder + ?Sized> { @@ -155,7 +157,7 @@ lazy_static! { /// LegacyBlobLoader is initialized and exists globally. /// The same directory used by the database is used by the LegacyBlobLoader as well. pub static ref LEGACY_BLOB_LOADER: Arc<LegacyBlobLoader> = Arc::new(LegacyBlobLoader::new( - &DB_PATH.read().expect("Could not get the database path for legacy blob loader."))); + &DB_PATH.read().expect("Could not determine database path for legacy blob loader"))); /// Legacy migrator. Atomically migrates legacy blobs to the database. pub static ref LEGACY_IMPORTER: Arc<LegacyImporter> = Arc::new(LegacyImporter::new(Arc::new(Default::default()))); @@ -166,12 +168,12 @@ lazy_static! { ( Box::new(|uuid, blob| { let km_dev = get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?; - let _wp = wd::watch_millis("In invalidate key closure: calling deleteKey", 500); + let _wp = wd::watch("invalidate key closure: calling IKeyMintDevice::deleteKey"); map_km_error(km_dev.deleteKey(blob)) .context(ks_err!("Trying to invalidate key blob.")) }), - KeystoreDB::new(&DB_PATH.read().expect("Could not get the database directory."), None) - .expect("Failed to open database."), + KeystoreDB::new(&DB_PATH.read().expect("Could not determine database path for GC"), None) + .expect("Failed to open database"), SUPER_KEY.clone(), ) })); @@ -247,7 +249,11 @@ fn connect_keymint( } e => e, }) - .context(ks_err!("Trying to get Legacy wrapper."))?, + .context(ks_err!( + "Trying to get Legacy wrapper. Attempt to get keystore \ + compat service for security level {:?}", + *security_level + ))?, None, ) }; @@ -303,7 +309,7 @@ fn connect_keymint( } }; - let wp = wd::watch_millis("In connect_keymint: calling getHardwareInfo()", 500); + let wp = wd::watch("connect_keymint: calling IKeyMintDevice::getHardwareInfo()"); let mut hw_info = map_km_error(keymint.getHardwareInfo()).context(ks_err!("Failed to get hardware info."))?; drop(wp); @@ -394,7 +400,7 @@ fn connect_secureclock() -> Result<Strong<dyn ISecureClock>> { } e => e, }) - .context(ks_err!("Trying to get Legacy wrapper.")) + .context(ks_err!("Failed attempt to get legacy secure clock.")) }?; Ok(secureclock) @@ -417,19 +423,20 @@ pub fn get_timestamp_service() -> Result<Strong<dyn ISecureClock>> { pub fn get_remotely_provisioned_component_name(security_level: &SecurityLevel) -> Result<String> { let remote_prov_descriptor: &str = <BpRemotelyProvisionedComponent as IRemotelyProvisionedComponent>::get_descriptor(); - let remotely_prov_instances = get_declared_instances(remote_prov_descriptor).unwrap(); match *security_level { SecurityLevel::TRUSTED_ENVIRONMENT => { - if remotely_prov_instances.iter().any(|instance| *instance == "default") { - Some(format!("{}/default", remote_prov_descriptor)) + let instance = format!("{}/default", remote_prov_descriptor); + if is_declared(&instance)? { + Some(instance) } else { None } } SecurityLevel::STRONGBOX => { - if remotely_prov_instances.iter().any(|instance| *instance == "strongbox") { - Some(format!("{}/strongbox", remote_prov_descriptor)) + let instance = format!("{}/strongbox", remote_prov_descriptor); + if is_declared(&instance)? { + Some(instance) } else { None } @@ -437,5 +444,5 @@ pub fn get_remotely_provisioned_component_name(security_level: &SecurityLevel) - _ => None, } .ok_or(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) - .context(ks_err!()) + .context(ks_err!("Failed to get rpc for sec level {:?}", *security_level)) } diff --git a/keystore2/src/key_parameter.rs b/keystore2/src/key_parameter.rs index 02a1f16c..466fb505 100644 --- a/keystore2/src/key_parameter.rs +++ b/keystore2/src/key_parameter.rs @@ -111,6 +111,18 @@ use serde::de::Deserializer; use serde::ser::Serializer; use serde::{Deserialize, Serialize}; +#[cfg(test)] +mod generated_key_parameter_tests; + +#[cfg(test)] +mod basic_tests; + +#[cfg(test)] +mod storage_tests; + +#[cfg(test)] +mod wire_tests; + /// This trait is used to associate a primitive to any type that can be stored inside a /// KeyParameterValue, especially the AIDL enum types, e.g., keymint::{Algorithm, Digest, ...}. /// This allows for simplifying the macro rules, e.g., for reading from the SQL database. @@ -912,7 +924,8 @@ pub enum KeyParameterValue { /// The time in seconds for which the key is authorized for use, after user authentication #[key_param(tag = AUTH_TIMEOUT, field = Integer)] AuthTimeout(i32), - /// The key may be used after authentication timeout if device is still on-body + /// The key's authentication timeout, if it has one, is automatically expired when the device is + /// removed from the user's body. No longer implemented; this tag is no longer enforced. #[key_param(tag = ALLOW_WHILE_ON_BODY, field = BoolValue)] AllowWhileOnBody, /// The key must be unusable except when the user has provided proof of physical presence @@ -1090,490 +1103,3 @@ impl KeyParameter { Authorization { securityLevel: self.security_level, keyParameter: self.value.into() } } } - -#[cfg(test)] -mod generated_key_parameter_tests { - use super::*; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::TagType::TagType; - - fn get_field_by_tag_type(tag: Tag) -> KmKeyParameterValue { - let tag_type = TagType((tag.0 as u32 & 0xF0000000) as i32); - match tag { - Tag::ALGORITHM => return KmKeyParameterValue::Algorithm(Default::default()), - Tag::BLOCK_MODE => return KmKeyParameterValue::BlockMode(Default::default()), - Tag::PADDING => return KmKeyParameterValue::PaddingMode(Default::default()), - Tag::DIGEST => return KmKeyParameterValue::Digest(Default::default()), - Tag::RSA_OAEP_MGF_DIGEST => return KmKeyParameterValue::Digest(Default::default()), - Tag::EC_CURVE => return KmKeyParameterValue::EcCurve(Default::default()), - Tag::ORIGIN => return KmKeyParameterValue::Origin(Default::default()), - Tag::PURPOSE => return KmKeyParameterValue::KeyPurpose(Default::default()), - Tag::USER_AUTH_TYPE => { - return KmKeyParameterValue::HardwareAuthenticatorType(Default::default()) - } - Tag::HARDWARE_TYPE => return KmKeyParameterValue::SecurityLevel(Default::default()), - _ => {} - } - match tag_type { - TagType::INVALID => return KmKeyParameterValue::Invalid(Default::default()), - TagType::ENUM | TagType::ENUM_REP => {} - TagType::UINT | TagType::UINT_REP => { - return KmKeyParameterValue::Integer(Default::default()) - } - TagType::ULONG | TagType::ULONG_REP => { - return KmKeyParameterValue::LongInteger(Default::default()) - } - TagType::DATE => return KmKeyParameterValue::DateTime(Default::default()), - TagType::BOOL => return KmKeyParameterValue::BoolValue(Default::default()), - TagType::BIGNUM | TagType::BYTES => { - return KmKeyParameterValue::Blob(Default::default()) - } - _ => {} - } - panic!("Unknown tag/tag_type: {:?} {:?}", tag, tag_type); - } - - fn check_field_matches_tag_type(list_o_parameters: &[KmKeyParameter]) { - for kp in list_o_parameters.iter() { - match (&kp.value, get_field_by_tag_type(kp.tag)) { - (&KmKeyParameterValue::Algorithm(_), KmKeyParameterValue::Algorithm(_)) - | (&KmKeyParameterValue::BlockMode(_), KmKeyParameterValue::BlockMode(_)) - | (&KmKeyParameterValue::PaddingMode(_), KmKeyParameterValue::PaddingMode(_)) - | (&KmKeyParameterValue::Digest(_), KmKeyParameterValue::Digest(_)) - | (&KmKeyParameterValue::EcCurve(_), KmKeyParameterValue::EcCurve(_)) - | (&KmKeyParameterValue::Origin(_), KmKeyParameterValue::Origin(_)) - | (&KmKeyParameterValue::KeyPurpose(_), KmKeyParameterValue::KeyPurpose(_)) - | ( - &KmKeyParameterValue::HardwareAuthenticatorType(_), - KmKeyParameterValue::HardwareAuthenticatorType(_), - ) - | (&KmKeyParameterValue::SecurityLevel(_), KmKeyParameterValue::SecurityLevel(_)) - | (&KmKeyParameterValue::Invalid(_), KmKeyParameterValue::Invalid(_)) - | (&KmKeyParameterValue::Integer(_), KmKeyParameterValue::Integer(_)) - | (&KmKeyParameterValue::LongInteger(_), KmKeyParameterValue::LongInteger(_)) - | (&KmKeyParameterValue::DateTime(_), KmKeyParameterValue::DateTime(_)) - | (&KmKeyParameterValue::BoolValue(_), KmKeyParameterValue::BoolValue(_)) - | (&KmKeyParameterValue::Blob(_), KmKeyParameterValue::Blob(_)) => {} - (actual, expected) => panic!( - "Tag {:?} associated with variant {:?} expected {:?}", - kp.tag, actual, expected - ), - } - } - } - - #[test] - fn key_parameter_value_field_matches_tag_type() { - check_field_matches_tag_type(&KeyParameterValue::make_field_matches_tag_type_test_vector()); - } - - #[test] - fn key_parameter_serialization_test() { - let params = KeyParameterValue::make_key_parameter_defaults_vector(); - let mut out_buffer: Vec<u8> = Default::default(); - serde_cbor::to_writer(&mut out_buffer, ¶ms) - .expect("Failed to serialize key parameters."); - let deserialized_params: Vec<KeyParameter> = - serde_cbor::from_reader(&mut out_buffer.as_slice()) - .expect("Failed to deserialize key parameters."); - assert_eq!(params, deserialized_params); - } -} - -#[cfg(test)] -mod basic_tests { - use crate::key_parameter::*; - - // Test basic functionality of KeyParameter. - #[test] - fn test_key_parameter() { - let key_parameter = KeyParameter::new( - KeyParameterValue::Algorithm(Algorithm::RSA), - SecurityLevel::STRONGBOX, - ); - - assert_eq!(key_parameter.get_tag(), Tag::ALGORITHM); - - assert_eq!( - *key_parameter.key_parameter_value(), - KeyParameterValue::Algorithm(Algorithm::RSA) - ); - - assert_eq!(*key_parameter.security_level(), SecurityLevel::STRONGBOX); - } -} - -/// The storage_tests module first tests the 'new_from_sql' method for KeyParameters of different -/// data types and then tests 'to_sql' method for KeyParameters of those -/// different data types. The five different data types for KeyParameter values are: -/// i) enums of u32 -/// ii) u32 -/// iii) u64 -/// iv) Vec<u8> -/// v) bool -#[cfg(test)] -mod storage_tests { - use crate::error::*; - use crate::key_parameter::*; - use anyhow::Result; - use rusqlite::types::ToSql; - use rusqlite::{params, Connection}; - - /// Test initializing a KeyParameter (with key parameter value corresponding to an enum of i32) - /// from a database table row. - #[test] - fn test_new_from_sql_enum_i32() -> Result<()> { - let db = init_db()?; - insert_into_keyparameter( - &db, - 1, - Tag::ALGORITHM.0, - &Algorithm::RSA.0, - SecurityLevel::STRONGBOX.0, - )?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(Tag::ALGORITHM, key_param.get_tag()); - assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA)); - assert_eq!(*key_param.security_level(), SecurityLevel::STRONGBOX); - Ok(()) - } - - /// Test initializing a KeyParameter (with key parameter value which is of i32) - /// from a database table row. - #[test] - fn test_new_from_sql_i32() -> Result<()> { - let db = init_db()?; - insert_into_keyparameter(&db, 1, Tag::KEY_SIZE.0, &1024, SecurityLevel::STRONGBOX.0)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(Tag::KEY_SIZE, key_param.get_tag()); - assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::KeySize(1024)); - Ok(()) - } - - /// Test initializing a KeyParameter (with key parameter value which is of i64) - /// from a database table row. - #[test] - fn test_new_from_sql_i64() -> Result<()> { - let db = init_db()?; - // max value for i64, just to test corner cases - insert_into_keyparameter( - &db, - 1, - Tag::RSA_PUBLIC_EXPONENT.0, - &(i64::MAX), - SecurityLevel::STRONGBOX.0, - )?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(Tag::RSA_PUBLIC_EXPONENT, key_param.get_tag()); - assert_eq!( - *key_param.key_parameter_value(), - KeyParameterValue::RSAPublicExponent(i64::MAX) - ); - Ok(()) - } - - /// Test initializing a KeyParameter (with key parameter value which is of bool) - /// from a database table row. - #[test] - fn test_new_from_sql_bool() -> Result<()> { - let db = init_db()?; - insert_into_keyparameter(&db, 1, Tag::CALLER_NONCE.0, &Null, SecurityLevel::STRONGBOX.0)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(Tag::CALLER_NONCE, key_param.get_tag()); - assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::CallerNonce); - Ok(()) - } - - /// Test initializing a KeyParameter (with key parameter value which is of Vec<u8>) - /// from a database table row. - #[test] - fn test_new_from_sql_vec_u8() -> Result<()> { - let db = init_db()?; - let app_id = String::from("MyAppID"); - let app_id_bytes = app_id.into_bytes(); - insert_into_keyparameter( - &db, - 1, - Tag::APPLICATION_ID.0, - &app_id_bytes, - SecurityLevel::STRONGBOX.0, - )?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(Tag::APPLICATION_ID, key_param.get_tag()); - assert_eq!( - *key_param.key_parameter_value(), - KeyParameterValue::ApplicationID(app_id_bytes) - ); - Ok(()) - } - - /// Test storing a KeyParameter (with key parameter value which corresponds to an enum of i32) - /// in the database - #[test] - fn test_to_sql_enum_i32() -> Result<()> { - let db = init_db()?; - let kp = KeyParameter::new( - KeyParameterValue::Algorithm(Algorithm::RSA), - SecurityLevel::STRONGBOX, - ); - store_keyparameter(&db, 1, &kp)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(kp.get_tag(), key_param.get_tag()); - assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); - assert_eq!(kp.security_level(), key_param.security_level()); - Ok(()) - } - - /// Test storing a KeyParameter (with key parameter value which is of i32) in the database - #[test] - fn test_to_sql_i32() -> Result<()> { - let db = init_db()?; - let kp = KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::STRONGBOX); - store_keyparameter(&db, 1, &kp)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(kp.get_tag(), key_param.get_tag()); - assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); - assert_eq!(kp.security_level(), key_param.security_level()); - Ok(()) - } - - /// Test storing a KeyParameter (with key parameter value which is of i64) in the database - #[test] - fn test_to_sql_i64() -> Result<()> { - let db = init_db()?; - // max value for i64, just to test corner cases - let kp = KeyParameter::new( - KeyParameterValue::RSAPublicExponent(i64::MAX), - SecurityLevel::STRONGBOX, - ); - store_keyparameter(&db, 1, &kp)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(kp.get_tag(), key_param.get_tag()); - assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); - assert_eq!(kp.security_level(), key_param.security_level()); - Ok(()) - } - - /// Test storing a KeyParameter (with key parameter value which is of Vec<u8>) in the database - #[test] - fn test_to_sql_vec_u8() -> Result<()> { - let db = init_db()?; - let kp = KeyParameter::new( - KeyParameterValue::ApplicationID(String::from("MyAppID").into_bytes()), - SecurityLevel::STRONGBOX, - ); - store_keyparameter(&db, 1, &kp)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(kp.get_tag(), key_param.get_tag()); - assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); - assert_eq!(kp.security_level(), key_param.security_level()); - Ok(()) - } - - /// Test storing a KeyParameter (with key parameter value which is of i32) in the database - #[test] - fn test_to_sql_bool() -> Result<()> { - let db = init_db()?; - let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX); - store_keyparameter(&db, 1, &kp)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(kp.get_tag(), key_param.get_tag()); - assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); - assert_eq!(kp.security_level(), key_param.security_level()); - Ok(()) - } - - #[test] - /// Test Tag::Invalid - fn test_invalid_tag() -> Result<()> { - let db = init_db()?; - insert_into_keyparameter(&db, 1, 0, &123, 1)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(Tag::INVALID, key_param.get_tag()); - Ok(()) - } - - #[test] - fn test_non_existing_enum_variant() -> Result<()> { - let db = init_db()?; - insert_into_keyparameter(&db, 1, 100, &123, 1)?; - let key_param = query_from_keyparameter(&db)?; - assert_eq!(Tag::INVALID, key_param.get_tag()); - Ok(()) - } - - #[test] - fn test_invalid_conversion_from_sql() -> Result<()> { - let db = init_db()?; - insert_into_keyparameter(&db, 1, Tag::ALGORITHM.0, &Null, 1)?; - tests::check_result_contains_error_string( - query_from_keyparameter(&db), - "Failed to read sql data for tag: ALGORITHM.", - ); - Ok(()) - } - - /// Helper method to init database table for key parameter - fn init_db() -> Result<Connection> { - let db = Connection::open_in_memory().context("Failed to initialize sqlite connection.")?; - db.execute("ATTACH DATABASE ? as 'persistent';", params![""]) - .context("Failed to attach databases.")?; - db.execute( - "CREATE TABLE IF NOT EXISTS persistent.keyparameter ( - keyentryid INTEGER, - tag INTEGER, - data ANY, - security_level INTEGER);", - [], - ) - .context("Failed to initialize \"keyparameter\" table.")?; - Ok(db) - } - - /// Helper method to insert an entry into key parameter table, with individual parameters - fn insert_into_keyparameter<T: ToSql>( - db: &Connection, - key_id: i64, - tag: i32, - value: &T, - security_level: i32, - ) -> Result<()> { - db.execute( - "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level) - VALUES(?, ?, ?, ?);", - params![key_id, tag, *value, security_level], - )?; - Ok(()) - } - - /// Helper method to store a key parameter instance. - fn store_keyparameter(db: &Connection, key_id: i64, kp: &KeyParameter) -> Result<()> { - db.execute( - "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level) - VALUES(?, ?, ?, ?);", - params![key_id, kp.get_tag().0, kp.key_parameter_value(), kp.security_level().0], - )?; - Ok(()) - } - - /// Helper method to query a row from keyparameter table - fn query_from_keyparameter(db: &Connection) -> Result<KeyParameter> { - let mut stmt = - db.prepare("SELECT tag, data, security_level FROM persistent.keyparameter")?; - let mut rows = stmt.query([])?; - let row = rows.next()?.unwrap(); - KeyParameter::new_from_sql( - Tag(row.get(0)?), - &SqlField::new(1, row), - SecurityLevel(row.get(2)?), - ) - } -} - -/// The wire_tests module tests the 'convert_to_wire' and 'convert_from_wire' methods for -/// KeyParameter, for the four different types used in KmKeyParameter, in addition to Invalid -/// key parameter. -/// i) bool -/// ii) integer -/// iii) longInteger -/// iv) blob -#[cfg(test)] -mod wire_tests { - use crate::key_parameter::*; - /// unit tests for to conversions - #[test] - fn test_convert_to_wire_invalid() { - let kp = KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::STRONGBOX); - assert_eq!( - KmKeyParameter { tag: Tag::INVALID, value: KmKeyParameterValue::Invalid(0) }, - kp.value.into() - ); - } - #[test] - fn test_convert_to_wire_bool() { - let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX); - assert_eq!( - KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) }, - kp.value.into() - ); - } - #[test] - fn test_convert_to_wire_integer() { - let kp = KeyParameter::new( - KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), - SecurityLevel::STRONGBOX, - ); - assert_eq!( - KmKeyParameter { - tag: Tag::PURPOSE, - value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT) - }, - kp.value.into() - ); - } - #[test] - fn test_convert_to_wire_long_integer() { - let kp = - KeyParameter::new(KeyParameterValue::UserSecureID(i64::MAX), SecurityLevel::STRONGBOX); - assert_eq!( - KmKeyParameter { - tag: Tag::USER_SECURE_ID, - value: KmKeyParameterValue::LongInteger(i64::MAX) - }, - kp.value.into() - ); - } - #[test] - fn test_convert_to_wire_blob() { - let kp = KeyParameter::new( - KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()), - SecurityLevel::STRONGBOX, - ); - assert_eq!( - KmKeyParameter { - tag: Tag::CONFIRMATION_TOKEN, - value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes()) - }, - kp.value.into() - ); - } - - /// unit tests for from conversion - #[test] - fn test_convert_from_wire_invalid() { - let aidl_kp = KmKeyParameter { tag: Tag::INVALID, ..Default::default() }; - assert_eq!(KeyParameterValue::Invalid, aidl_kp.into()); - } - #[test] - fn test_convert_from_wire_bool() { - let aidl_kp = - KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) }; - assert_eq!(KeyParameterValue::CallerNonce, aidl_kp.into()); - } - #[test] - fn test_convert_from_wire_integer() { - let aidl_kp = KmKeyParameter { - tag: Tag::PURPOSE, - value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), - }; - assert_eq!(KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), aidl_kp.into()); - } - #[test] - fn test_convert_from_wire_long_integer() { - let aidl_kp = KmKeyParameter { - tag: Tag::USER_SECURE_ID, - value: KmKeyParameterValue::LongInteger(i64::MAX), - }; - assert_eq!(KeyParameterValue::UserSecureID(i64::MAX), aidl_kp.into()); - } - #[test] - fn test_convert_from_wire_blob() { - let aidl_kp = KmKeyParameter { - tag: Tag::CONFIRMATION_TOKEN, - value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes()), - }; - assert_eq!( - KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()), - aidl_kp.into() - ); - } -} diff --git a/keystore2/src/key_parameter/basic_tests.rs b/keystore2/src/key_parameter/basic_tests.rs new file mode 100644 index 00000000..2bb37246 --- /dev/null +++ b/keystore2/src/key_parameter/basic_tests.rs @@ -0,0 +1,28 @@ +// Copyright 2020, 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 crate::key_parameter::*; + +// Test basic functionality of KeyParameter. +#[test] +fn test_key_parameter() { + let key_parameter = + KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::RSA), SecurityLevel::STRONGBOX); + + assert_eq!(key_parameter.get_tag(), Tag::ALGORITHM); + + assert_eq!(*key_parameter.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA)); + + assert_eq!(*key_parameter.security_level(), SecurityLevel::STRONGBOX); +} diff --git a/keystore2/src/key_parameter/generated_key_parameter_tests.rs b/keystore2/src/key_parameter/generated_key_parameter_tests.rs new file mode 100644 index 00000000..a5c0a8ba --- /dev/null +++ b/keystore2/src/key_parameter/generated_key_parameter_tests.rs @@ -0,0 +1,95 @@ +// Copyright 2020, 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 super::*; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::TagType::TagType; + +fn get_field_by_tag_type(tag: Tag) -> KmKeyParameterValue { + let tag_type = TagType((tag.0 as u32 & 0xF0000000) as i32); + match tag { + Tag::ALGORITHM => return KmKeyParameterValue::Algorithm(Default::default()), + Tag::BLOCK_MODE => return KmKeyParameterValue::BlockMode(Default::default()), + Tag::PADDING => return KmKeyParameterValue::PaddingMode(Default::default()), + Tag::DIGEST => return KmKeyParameterValue::Digest(Default::default()), + Tag::RSA_OAEP_MGF_DIGEST => return KmKeyParameterValue::Digest(Default::default()), + Tag::EC_CURVE => return KmKeyParameterValue::EcCurve(Default::default()), + Tag::ORIGIN => return KmKeyParameterValue::Origin(Default::default()), + Tag::PURPOSE => return KmKeyParameterValue::KeyPurpose(Default::default()), + Tag::USER_AUTH_TYPE => { + return KmKeyParameterValue::HardwareAuthenticatorType(Default::default()) + } + Tag::HARDWARE_TYPE => return KmKeyParameterValue::SecurityLevel(Default::default()), + _ => {} + } + match tag_type { + TagType::INVALID => return KmKeyParameterValue::Invalid(Default::default()), + TagType::ENUM | TagType::ENUM_REP => {} + TagType::UINT | TagType::UINT_REP => { + return KmKeyParameterValue::Integer(Default::default()) + } + TagType::ULONG | TagType::ULONG_REP => { + return KmKeyParameterValue::LongInteger(Default::default()) + } + TagType::DATE => return KmKeyParameterValue::DateTime(Default::default()), + TagType::BOOL => return KmKeyParameterValue::BoolValue(Default::default()), + TagType::BIGNUM | TagType::BYTES => return KmKeyParameterValue::Blob(Default::default()), + _ => {} + } + panic!("Unknown tag/tag_type: {:?} {:?}", tag, tag_type); +} + +fn check_field_matches_tag_type(list_o_parameters: &[KmKeyParameter]) { + for kp in list_o_parameters.iter() { + match (&kp.value, get_field_by_tag_type(kp.tag)) { + (&KmKeyParameterValue::Algorithm(_), KmKeyParameterValue::Algorithm(_)) + | (&KmKeyParameterValue::BlockMode(_), KmKeyParameterValue::BlockMode(_)) + | (&KmKeyParameterValue::PaddingMode(_), KmKeyParameterValue::PaddingMode(_)) + | (&KmKeyParameterValue::Digest(_), KmKeyParameterValue::Digest(_)) + | (&KmKeyParameterValue::EcCurve(_), KmKeyParameterValue::EcCurve(_)) + | (&KmKeyParameterValue::Origin(_), KmKeyParameterValue::Origin(_)) + | (&KmKeyParameterValue::KeyPurpose(_), KmKeyParameterValue::KeyPurpose(_)) + | ( + &KmKeyParameterValue::HardwareAuthenticatorType(_), + KmKeyParameterValue::HardwareAuthenticatorType(_), + ) + | (&KmKeyParameterValue::SecurityLevel(_), KmKeyParameterValue::SecurityLevel(_)) + | (&KmKeyParameterValue::Invalid(_), KmKeyParameterValue::Invalid(_)) + | (&KmKeyParameterValue::Integer(_), KmKeyParameterValue::Integer(_)) + | (&KmKeyParameterValue::LongInteger(_), KmKeyParameterValue::LongInteger(_)) + | (&KmKeyParameterValue::DateTime(_), KmKeyParameterValue::DateTime(_)) + | (&KmKeyParameterValue::BoolValue(_), KmKeyParameterValue::BoolValue(_)) + | (&KmKeyParameterValue::Blob(_), KmKeyParameterValue::Blob(_)) => {} + (actual, expected) => panic!( + "Tag {:?} associated with variant {:?} expected {:?}", + kp.tag, actual, expected + ), + } + } +} + +#[test] +fn key_parameter_value_field_matches_tag_type() { + check_field_matches_tag_type(&KeyParameterValue::make_field_matches_tag_type_test_vector()); +} + +#[test] +fn key_parameter_serialization_test() { + let params = KeyParameterValue::make_key_parameter_defaults_vector(); + let mut out_buffer: Vec<u8> = Default::default(); + serde_cbor::to_writer(&mut out_buffer, ¶ms).expect("Failed to serialize key parameters."); + let deserialized_params: Vec<KeyParameter> = + serde_cbor::from_reader(&mut out_buffer.as_slice()) + .expect("Failed to deserialize key parameters."); + assert_eq!(params, deserialized_params); +} diff --git a/keystore2/src/key_parameter/storage_tests.rs b/keystore2/src/key_parameter/storage_tests.rs new file mode 100644 index 00000000..38a57e41 --- /dev/null +++ b/keystore2/src/key_parameter/storage_tests.rs @@ -0,0 +1,263 @@ +// Copyright 2020, 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. + +//! The storage_tests module first tests the 'new_from_sql' method for KeyParameters of different +//! data types and then tests 'to_sql' method for KeyParameters of those +//! different data types. The five different data types for KeyParameter values are: +//! i) enums of u32 +//! ii) u32 +//! iii) u64 +//! iv) Vec<u8> +//! v) bool + +use crate::error::*; +use crate::key_parameter::*; +use anyhow::Result; +use rusqlite::types::ToSql; +use rusqlite::{params, Connection}; + +/// Test initializing a KeyParameter (with key parameter value corresponding to an enum of i32) +/// from a database table row. +#[test] +fn test_new_from_sql_enum_i32() -> Result<()> { + let db = init_db()?; + insert_into_keyparameter( + &db, + 1, + Tag::ALGORITHM.0, + &Algorithm::RSA.0, + SecurityLevel::STRONGBOX.0, + )?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(Tag::ALGORITHM, key_param.get_tag()); + assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::Algorithm(Algorithm::RSA)); + assert_eq!(*key_param.security_level(), SecurityLevel::STRONGBOX); + Ok(()) +} + +/// Test initializing a KeyParameter (with key parameter value which is of i32) +/// from a database table row. +#[test] +fn test_new_from_sql_i32() -> Result<()> { + let db = init_db()?; + insert_into_keyparameter(&db, 1, Tag::KEY_SIZE.0, &1024, SecurityLevel::STRONGBOX.0)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(Tag::KEY_SIZE, key_param.get_tag()); + assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::KeySize(1024)); + Ok(()) +} + +/// Test initializing a KeyParameter (with key parameter value which is of i64) +/// from a database table row. +#[test] +fn test_new_from_sql_i64() -> Result<()> { + let db = init_db()?; + // max value for i64, just to test corner cases + insert_into_keyparameter( + &db, + 1, + Tag::RSA_PUBLIC_EXPONENT.0, + &(i64::MAX), + SecurityLevel::STRONGBOX.0, + )?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(Tag::RSA_PUBLIC_EXPONENT, key_param.get_tag()); + assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::RSAPublicExponent(i64::MAX)); + Ok(()) +} + +/// Test initializing a KeyParameter (with key parameter value which is of bool) +/// from a database table row. +#[test] +fn test_new_from_sql_bool() -> Result<()> { + let db = init_db()?; + insert_into_keyparameter(&db, 1, Tag::CALLER_NONCE.0, &Null, SecurityLevel::STRONGBOX.0)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(Tag::CALLER_NONCE, key_param.get_tag()); + assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::CallerNonce); + Ok(()) +} + +/// Test initializing a KeyParameter (with key parameter value which is of Vec<u8>) +/// from a database table row. +#[test] +fn test_new_from_sql_vec_u8() -> Result<()> { + let db = init_db()?; + let app_id = String::from("MyAppID"); + let app_id_bytes = app_id.into_bytes(); + insert_into_keyparameter( + &db, + 1, + Tag::APPLICATION_ID.0, + &app_id_bytes, + SecurityLevel::STRONGBOX.0, + )?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(Tag::APPLICATION_ID, key_param.get_tag()); + assert_eq!(*key_param.key_parameter_value(), KeyParameterValue::ApplicationID(app_id_bytes)); + Ok(()) +} + +/// Test storing a KeyParameter (with key parameter value which corresponds to an enum of i32) +/// in the database +#[test] +fn test_to_sql_enum_i32() -> Result<()> { + let db = init_db()?; + let kp = + KeyParameter::new(KeyParameterValue::Algorithm(Algorithm::RSA), SecurityLevel::STRONGBOX); + store_keyparameter(&db, 1, &kp)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(kp.get_tag(), key_param.get_tag()); + assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); + assert_eq!(kp.security_level(), key_param.security_level()); + Ok(()) +} + +/// Test storing a KeyParameter (with key parameter value which is of i32) in the database +#[test] +fn test_to_sql_i32() -> Result<()> { + let db = init_db()?; + let kp = KeyParameter::new(KeyParameterValue::KeySize(1024), SecurityLevel::STRONGBOX); + store_keyparameter(&db, 1, &kp)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(kp.get_tag(), key_param.get_tag()); + assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); + assert_eq!(kp.security_level(), key_param.security_level()); + Ok(()) +} + +/// Test storing a KeyParameter (with key parameter value which is of i64) in the database +#[test] +fn test_to_sql_i64() -> Result<()> { + let db = init_db()?; + // max value for i64, just to test corner cases + let kp = + KeyParameter::new(KeyParameterValue::RSAPublicExponent(i64::MAX), SecurityLevel::STRONGBOX); + store_keyparameter(&db, 1, &kp)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(kp.get_tag(), key_param.get_tag()); + assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); + assert_eq!(kp.security_level(), key_param.security_level()); + Ok(()) +} + +/// Test storing a KeyParameter (with key parameter value which is of Vec<u8>) in the database +#[test] +fn test_to_sql_vec_u8() -> Result<()> { + let db = init_db()?; + let kp = KeyParameter::new( + KeyParameterValue::ApplicationID(String::from("MyAppID").into_bytes()), + SecurityLevel::STRONGBOX, + ); + store_keyparameter(&db, 1, &kp)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(kp.get_tag(), key_param.get_tag()); + assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); + assert_eq!(kp.security_level(), key_param.security_level()); + Ok(()) +} + +/// Test storing a KeyParameter (with key parameter value which is of i32) in the database +#[test] +fn test_to_sql_bool() -> Result<()> { + let db = init_db()?; + let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX); + store_keyparameter(&db, 1, &kp)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(kp.get_tag(), key_param.get_tag()); + assert_eq!(kp.key_parameter_value(), key_param.key_parameter_value()); + assert_eq!(kp.security_level(), key_param.security_level()); + Ok(()) +} + +#[test] +/// Test Tag::Invalid +fn test_invalid_tag() -> Result<()> { + let db = init_db()?; + insert_into_keyparameter(&db, 1, 0, &123, 1)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(Tag::INVALID, key_param.get_tag()); + Ok(()) +} + +#[test] +fn test_non_existing_enum_variant() -> Result<()> { + let db = init_db()?; + insert_into_keyparameter(&db, 1, 100, &123, 1)?; + let key_param = query_from_keyparameter(&db)?; + assert_eq!(Tag::INVALID, key_param.get_tag()); + Ok(()) +} + +#[test] +fn test_invalid_conversion_from_sql() -> Result<()> { + let db = init_db()?; + insert_into_keyparameter(&db, 1, Tag::ALGORITHM.0, &Null, 1)?; + tests::check_result_contains_error_string( + query_from_keyparameter(&db), + "Failed to read sql data for tag: ALGORITHM.", + ); + Ok(()) +} + +/// Helper method to init database table for key parameter +fn init_db() -> Result<Connection> { + let db = Connection::open_in_memory().context("Failed to initialize sqlite connection.")?; + db.execute("ATTACH DATABASE ? as 'persistent';", params![""]) + .context("Failed to attach databases.")?; + db.execute( + "CREATE TABLE IF NOT EXISTS persistent.keyparameter ( + keyentryid INTEGER, + tag INTEGER, + data ANY, + security_level INTEGER);", + [], + ) + .context("Failed to initialize \"keyparameter\" table.")?; + Ok(db) +} + +/// Helper method to insert an entry into key parameter table, with individual parameters +fn insert_into_keyparameter<T: ToSql>( + db: &Connection, + key_id: i64, + tag: i32, + value: &T, + security_level: i32, +) -> Result<()> { + db.execute( + "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level) + VALUES(?, ?, ?, ?);", + params![key_id, tag, *value, security_level], + )?; + Ok(()) +} + +/// Helper method to store a key parameter instance. +fn store_keyparameter(db: &Connection, key_id: i64, kp: &KeyParameter) -> Result<()> { + db.execute( + "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level) + VALUES(?, ?, ?, ?);", + params![key_id, kp.get_tag().0, kp.key_parameter_value(), kp.security_level().0], + )?; + Ok(()) +} + +/// Helper method to query a row from keyparameter table +fn query_from_keyparameter(db: &Connection) -> Result<KeyParameter> { + let mut stmt = db.prepare("SELECT tag, data, security_level FROM persistent.keyparameter")?; + let mut rows = stmt.query([])?; + let row = rows.next()?.unwrap(); + KeyParameter::new_from_sql(Tag(row.get(0)?), &SqlField::new(1, row), SecurityLevel(row.get(2)?)) +} diff --git a/keystore2/src/key_parameter/wire_tests.rs b/keystore2/src/key_parameter/wire_tests.rs new file mode 100644 index 00000000..278b7669 --- /dev/null +++ b/keystore2/src/key_parameter/wire_tests.rs @@ -0,0 +1,119 @@ +// Copyright 2020, 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. + +//! The wire_tests module tests the 'convert_to_wire' and 'convert_from_wire' methods for +//! KeyParameter, for the four different types used in KmKeyParameter, in addition to Invalid +//! key parameter. +//! i) bool +//! ii) integer +//! iii) longInteger +//! iv) blob + +use crate::key_parameter::*; +/// unit tests for to conversions +#[test] +fn test_convert_to_wire_invalid() { + let kp = KeyParameter::new(KeyParameterValue::Invalid, SecurityLevel::STRONGBOX); + assert_eq!( + KmKeyParameter { tag: Tag::INVALID, value: KmKeyParameterValue::Invalid(0) }, + kp.value.into() + ); +} +#[test] +fn test_convert_to_wire_bool() { + let kp = KeyParameter::new(KeyParameterValue::CallerNonce, SecurityLevel::STRONGBOX); + assert_eq!( + KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) }, + kp.value.into() + ); +} +#[test] +fn test_convert_to_wire_integer() { + let kp = KeyParameter::new( + KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), + SecurityLevel::STRONGBOX, + ); + assert_eq!( + KmKeyParameter { + tag: Tag::PURPOSE, + value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT) + }, + kp.value.into() + ); +} +#[test] +fn test_convert_to_wire_long_integer() { + let kp = KeyParameter::new(KeyParameterValue::UserSecureID(i64::MAX), SecurityLevel::STRONGBOX); + assert_eq!( + KmKeyParameter { + tag: Tag::USER_SECURE_ID, + value: KmKeyParameterValue::LongInteger(i64::MAX) + }, + kp.value.into() + ); +} +#[test] +fn test_convert_to_wire_blob() { + let kp = KeyParameter::new( + KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()), + SecurityLevel::STRONGBOX, + ); + assert_eq!( + KmKeyParameter { + tag: Tag::CONFIRMATION_TOKEN, + value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes()) + }, + kp.value.into() + ); +} + +/// unit tests for from conversion +#[test] +fn test_convert_from_wire_invalid() { + let aidl_kp = KmKeyParameter { tag: Tag::INVALID, ..Default::default() }; + assert_eq!(KeyParameterValue::Invalid, aidl_kp.into()); +} +#[test] +fn test_convert_from_wire_bool() { + let aidl_kp = + KmKeyParameter { tag: Tag::CALLER_NONCE, value: KmKeyParameterValue::BoolValue(true) }; + assert_eq!(KeyParameterValue::CallerNonce, aidl_kp.into()); +} +#[test] +fn test_convert_from_wire_integer() { + let aidl_kp = KmKeyParameter { + tag: Tag::PURPOSE, + value: KmKeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), + }; + assert_eq!(KeyParameterValue::KeyPurpose(KeyPurpose::ENCRYPT), aidl_kp.into()); +} +#[test] +fn test_convert_from_wire_long_integer() { + let aidl_kp = KmKeyParameter { + tag: Tag::USER_SECURE_ID, + value: KmKeyParameterValue::LongInteger(i64::MAX), + }; + assert_eq!(KeyParameterValue::UserSecureID(i64::MAX), aidl_kp.into()); +} +#[test] +fn test_convert_from_wire_blob() { + let aidl_kp = KmKeyParameter { + tag: Tag::CONFIRMATION_TOKEN, + value: KmKeyParameterValue::Blob(String::from("ConfirmationToken").into_bytes()), + }; + assert_eq!( + KeyParameterValue::ConfirmationToken(String::from("ConfirmationToken").into_bytes()), + aidl_kp.into() + ); +} diff --git a/keystore2/src/km_compat.rs b/keystore2/src/km_compat.rs index 03c9d027..5e3bdfa7 100644 --- a/keystore2/src/km_compat.rs +++ b/keystore2/src/km_compat.rs @@ -16,7 +16,7 @@ //! be emulated on back-level devices. use crate::ks_err; -use crate::error::{map_binder_status, map_binder_status_code, map_or_log_err, Error, ErrorCode}; +use crate::error::{map_binder_status, map_binder_status_code, into_logged_binder, Error, ErrorCode}; use android_hardware_security_keymint::binder::{BinderFeatures, StatusCode, Strong}; use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::TimeStampToken::TimeStampToken; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ @@ -226,7 +226,7 @@ where ) -> binder::Result<KeyCreationResult> { if self.emu.emulation_required(key_params, &KeyImportData::None) { let mut result = self.soft.generateKey(key_params, attestation_key)?; - result.keyBlob = map_or_log_err(wrap_keyblob(&result.keyBlob), Ok)?; + result.keyBlob = wrap_keyblob(&result.keyBlob).map_err(into_logged_binder)?; Ok(result) } else { self.real.generateKey(key_params, attestation_key) @@ -242,7 +242,7 @@ where if self.emu.emulation_required(key_params, &KeyImportData::new(key_format, key_data)?) { let mut result = self.soft.importKey(key_params, key_format, key_data, attestation_key)?; - result.keyBlob = map_or_log_err(wrap_keyblob(&result.keyBlob), Ok)?; + result.keyBlob = wrap_keyblob(&result.keyBlob).map_err(into_logged_binder)?; Ok(result) } else { self.real.importKey(key_params, key_format, key_data, attestation_key) @@ -281,7 +281,7 @@ where KeyBlob::Wrapped(keyblob) => { // Re-wrap the upgraded keyblob. let upgraded_keyblob = self.soft.upgradeKey(keyblob, upgrade_params)?; - map_or_log_err(wrap_keyblob(&upgraded_keyblob), Ok) + wrap_keyblob(&upgraded_keyblob).map_err(into_logged_binder) } } } diff --git a/keystore2/src/legacy_blob.rs b/keystore2/src/legacy_blob.rs index 2bb7f27b..c27a0508 100644 --- a/keystore2/src/legacy_blob.rs +++ b/keystore2/src/legacy_blob.rs @@ -36,6 +36,9 @@ use std::{ const SUPPORTED_LEGACY_BLOB_VERSION: u8 = 3; +#[cfg(test)] +mod tests; + mod flags { /// This flag is deprecated. It is here to support keys that have been written with this flag /// set, but we don't create any new keys with this flag. @@ -1645,675 +1648,3 @@ pub mod test_utils { Ok(()) } } - -#[cfg(test)] -mod test { - #![allow(dead_code)] - use super::*; - use crate::legacy_blob::test_utils::legacy_blob_test_vectors::*; - use crate::legacy_blob::test_utils::*; - use anyhow::{anyhow, Result}; - use keystore2_crypto::aes_gcm_decrypt; - use keystore2_test_utils::TempDir; - use rand::Rng; - use std::convert::TryInto; - use std::ops::Deref; - use std::string::FromUtf8Error; - - #[test] - fn decode_encode_alias_test() { - static ALIAS: &str = "#({}test[])😗"; - static ENCODED_ALIAS: &str = "+S+X{}test[]+Y.`-O-H-G"; - // Second multi byte out of range ------v - static ENCODED_ALIAS_ERROR1: &str = "+S+{}test[]+Y"; - // Incomplete multi byte ------------------------v - static ENCODED_ALIAS_ERROR2: &str = "+S+X{}test[]+"; - // Our encoding: ".`-O-H-G" - // is UTF-8: 0xF0 0x9F 0x98 0x97 - // is UNICODE: U+1F617 - // is 😗 - // But +H below is a valid encoding for 0x18 making this sequence invalid UTF-8. - static ENCODED_ALIAS_ERROR_UTF8: &str = ".`-O+H-G"; - - assert_eq!(ENCODED_ALIAS, &LegacyBlobLoader::encode_alias(ALIAS)); - assert_eq!(ALIAS, &LegacyBlobLoader::decode_alias(ENCODED_ALIAS).unwrap()); - assert_eq!( - Some(&Error::BadEncoding), - LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR1) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>() - ); - assert_eq!( - Some(&Error::BadEncoding), - LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR2) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>() - ); - assert!(LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR_UTF8) - .unwrap_err() - .root_cause() - .downcast_ref::<FromUtf8Error>() - .is_some()); - - for _i in 0..100 { - // Any valid UTF-8 string should be en- and decoded without loss. - let alias_str = rand::thread_rng().gen::<[char; 20]>().iter().collect::<String>(); - let random_alias = alias_str.as_bytes(); - let encoded = LegacyBlobLoader::encode_alias(&alias_str); - let decoded = match LegacyBlobLoader::decode_alias(&encoded) { - Ok(d) => d, - Err(_) => panic!("random_alias: {:x?}\nencoded {}", random_alias, encoded), - }; - assert_eq!(random_alias.to_vec(), decoded.bytes().collect::<Vec<u8>>()); - } - } - - #[test] - fn read_golden_key_blob_test() -> anyhow::Result<()> { - let blob = LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*BLOB, |_, _, _, _, _| { - Err(anyhow!("should not be called")) - }) - .unwrap(); - assert!(!blob.is_encrypted()); - assert!(!blob.is_fallback()); - assert!(!blob.is_strongbox()); - assert!(!blob.is_critical_to_device_encryption()); - assert_eq!(blob.value(), &BlobValue::Generic([0xde, 0xed, 0xbe, 0xef].to_vec())); - - let blob = LegacyBlobLoader::new_from_stream_decrypt_with( - &mut &*REAL_LEGACY_BLOB, - |_, _, _, _, _| Err(anyhow!("should not be called")), - ) - .unwrap(); - assert!(!blob.is_encrypted()); - assert!(!blob.is_fallback()); - assert!(!blob.is_strongbox()); - assert!(!blob.is_critical_to_device_encryption()); - assert_eq!( - blob.value(), - &BlobValue::Decrypted(REAL_LEGACY_BLOB_PAYLOAD.try_into().unwrap()) - ); - Ok(()) - } - - #[test] - fn read_aes_gcm_encrypted_key_blob_test() { - let blob = LegacyBlobLoader::new_from_stream_decrypt_with( - &mut &*AES_GCM_ENCRYPTED_BLOB, - |d, iv, tag, salt, key_size| { - assert_eq!(salt, None); - assert_eq!(key_size, None); - assert_eq!( - iv, - &[ - 0xbd, 0xdb, 0x8d, 0x69, 0x72, 0x56, 0xf0, 0xf5, 0xa4, 0x02, 0x88, 0x7f, - 0x00, 0x00, 0x00, 0x00, - ] - ); - assert_eq!( - tag, - &[ - 0x50, 0xd9, 0x97, 0x95, 0x37, 0x6e, 0x28, 0x6a, 0x28, 0x9d, 0x51, 0xb9, - 0xb9, 0xe0, 0x0b, 0xc3 - ][..] - ); - aes_gcm_decrypt(d, iv, tag, AES_KEY).context("Trying to decrypt blob.") - }, - ) - .unwrap(); - assert!(blob.is_encrypted()); - assert!(!blob.is_fallback()); - assert!(!blob.is_strongbox()); - assert!(!blob.is_critical_to_device_encryption()); - - assert_eq!(blob.value(), &BlobValue::Decrypted(DECRYPTED_PAYLOAD.try_into().unwrap())); - } - - #[test] - fn read_golden_key_blob_too_short_test() { - let error = - LegacyBlobLoader::new_from_stream_decrypt_with(&mut &BLOB[0..15], |_, _, _, _, _| { - Err(anyhow!("should not be called")) - }) - .unwrap_err(); - assert_eq!(Some(&Error::BadLen), error.root_cause().downcast_ref::<Error>()); - } - - #[test] - fn test_is_empty() { - let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir."); - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty.")); - - let _db = crate::database::KeystoreDB::new(temp_dir.path(), None) - .expect("Failed to open database."); - - assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty.")); - - std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0."); - - assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty.")); - - std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10."); - - assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty.")); - - std::fs::remove_dir_all(&*temp_dir.build().push("user_0")) - .expect("Failed to remove user_0."); - - assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty.")); - - std::fs::remove_dir_all(&*temp_dir.build().push("user_10")) - .expect("Failed to remove user_10."); - - assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again.")); - } - - #[test] - fn test_legacy_blobs() -> anyhow::Result<()> { - let temp_dir = TempDir::new("legacy_blob_test").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); - - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), - USRPKEY_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), - USRPKEY_AUTHBOUND_CHR, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), - USRCERT_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), - CACERT_AUTHBOUND, - ) - .unwrap(); - - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRPKEY_non_authbound"), - USRPKEY_NON_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_non_authbound"), - USRPKEY_NON_AUTHBOUND_CHR, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRCERT_non_authbound"), - USRCERT_NON_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_CACERT_non_authbound"), - CACERT_NON_AUTHBOUND, - ) - .unwrap(); - - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) = - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? - { - assert_eq!(flags, 4); - assert_eq!( - value, - BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - ); - assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND); - assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND); - } else { - panic!(""); - } - - if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) = - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? - { - assert_eq!(flags, 4); - //assert_eq!(value, BlobValue::Encrypted(..)); - assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND); - assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND); - } else { - panic!(""); - } - if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) = - legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)? - { - assert_eq!(flags, 0); - assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?)); - assert_eq!(&cert[..], LOADED_CERT_NON_AUTHBOUND); - assert_eq!(&chain[..], LOADED_CACERT_NON_AUTHBOUND); - } else { - panic!(""); - } - - legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed."); - legacy_blob_loader - .remove_keystore_entry(10223, "non_authbound") - .expect("This should succeed."); - - assert_eq!( - (None, None, None), - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? - ); - assert_eq!( - (None, None, None), - legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)? - ); - - // The database should not be empty due to the super key. - assert!(!legacy_blob_loader.is_empty()?); - assert!(!legacy_blob_loader.is_empty_user(0)?); - - // The database should be considered empty for user 1. - assert!(legacy_blob_loader.is_empty_user(1)?); - - legacy_blob_loader.remove_super_key(0); - - // Now it should be empty. - assert!(legacy_blob_loader.is_empty_user(0)?); - assert!(legacy_blob_loader.is_empty()?); - - Ok(()) - } - - struct TestKey(ZVec); - - impl crate::utils::AesGcmKey for TestKey { - fn key(&self) -> &[u8] { - &self.0 - } - } - - impl Deref for TestKey { - type Target = [u8]; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - - #[test] - fn test_with_encrypted_characteristics() -> anyhow::Result<()> { - let temp_dir = TempDir::new("test_with_encrypted_characteristics").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); - let super_key = - Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); - - std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); - - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), - USRPKEY_AUTHBOUND, - ) - .unwrap(); - make_encrypted_characteristics_file( - &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), - &super_key, - KEY_PARAMETERS, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), - USRCERT_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), - CACERT_AUTHBOUND, - ) - .unwrap(); - - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert_eq!( - legacy_blob_loader - .load_by_uid_alias(10223, "authbound", &None) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>(), - Some(&Error::LockedComponent) - ); - - assert_eq!( - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(), - ( - Some(( - Blob { - flags: 4, - value: BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - }, - structured_test_params() - )), - Some(LOADED_CERT_AUTHBOUND.to_vec()), - Some(LOADED_CACERT_AUTHBOUND.to_vec()) - ) - ); - - legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed."); - - assert_eq!( - (None, None, None), - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap() - ); - - // The database should not be empty due to the super key. - assert!(!legacy_blob_loader.is_empty().unwrap()); - assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); - - // The database should be considered empty for user 1. - assert!(legacy_blob_loader.is_empty_user(1).unwrap()); - - legacy_blob_loader.remove_super_key(0); - - // Now it should be empty. - assert!(legacy_blob_loader.is_empty_user(0).unwrap()); - assert!(legacy_blob_loader.is_empty().unwrap()); - - Ok(()) - } - - #[test] - fn test_with_encrypted_certificates() -> anyhow::Result<()> { - let temp_dir = TempDir::new("test_with_encrypted_certificates").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); - let super_key = - Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); - - std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); - - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), - USRPKEY_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), - USRPKEY_AUTHBOUND_CHR, - ) - .unwrap(); - make_encrypted_usr_cert_file( - &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), - &super_key, - LOADED_CERT_AUTHBOUND, - ) - .unwrap(); - make_encrypted_ca_cert_file( - &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), - &super_key, - LOADED_CACERT_AUTHBOUND, - ) - .unwrap(); - - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert_eq!( - legacy_blob_loader - .load_by_uid_alias(10223, "authbound", &None) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>(), - Some(&Error::LockedComponent) - ); - - assert_eq!( - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(), - ( - Some(( - Blob { - flags: 4, - value: BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - }, - structured_test_params_cache() - )), - Some(LOADED_CERT_AUTHBOUND.to_vec()), - Some(LOADED_CACERT_AUTHBOUND.to_vec()) - ) - ); - - legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed."); - - assert_eq!( - (None, None, None), - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap() - ); - - // The database should not be empty due to the super key. - assert!(!legacy_blob_loader.is_empty().unwrap()); - assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); - - // The database should be considered empty for user 1. - assert!(legacy_blob_loader.is_empty_user(1).unwrap()); - - legacy_blob_loader.remove_super_key(0); - - // Now it should be empty. - assert!(legacy_blob_loader.is_empty_user(0).unwrap()); - assert!(legacy_blob_loader.is_empty().unwrap()); - - Ok(()) - } - - #[test] - fn test_in_place_key_migration() -> anyhow::Result<()> { - let temp_dir = TempDir::new("test_in_place_key_migration").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - let pw: Password = PASSWORD.into(); - let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); - let super_key = - Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); - - std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); - - std::fs::write( - &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), - USRPKEY_AUTHBOUND, - ) - .unwrap(); - std::fs::write( - &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), - USRPKEY_AUTHBOUND_CHR, - ) - .unwrap(); - make_encrypted_usr_cert_file( - &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), - &super_key, - LOADED_CERT_AUTHBOUND, - ) - .unwrap(); - make_encrypted_ca_cert_file( - &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), - &super_key, - LOADED_CACERT_AUTHBOUND, - ) - .unwrap(); - - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert_eq!( - legacy_blob_loader - .load_by_uid_alias(10223, "authbound", &None) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>(), - Some(&Error::LockedComponent) - ); - - let super_key: Option<Arc<dyn AesGcm>> = Some(super_key); - - assert_eq!( - legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(), - ( - Some(( - Blob { - flags: 4, - value: BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - }, - structured_test_params_cache() - )), - Some(LOADED_CERT_AUTHBOUND.to_vec()), - Some(LOADED_CACERT_AUTHBOUND.to_vec()) - ) - ); - - legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap(); - - assert_eq!( - legacy_blob_loader - .load_by_uid_alias(10224, "boundauth", &None) - .unwrap_err() - .root_cause() - .downcast_ref::<Error>(), - Some(&Error::LockedComponent) - ); - - assert_eq!( - legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(), - ( - Some(( - Blob { - flags: 4, - value: BlobValue::Encrypted { - data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), - iv: USRPKEY_AUTHBOUND_IV.to_vec(), - tag: USRPKEY_AUTHBOUND_TAG.to_vec() - } - }, - structured_test_params_cache() - )), - Some(LOADED_CERT_AUTHBOUND.to_vec()), - Some(LOADED_CACERT_AUTHBOUND.to_vec()) - ) - ); - - legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed."); - - assert_eq!( - (None, None, None), - legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap() - ); - - // The database should not be empty due to the super key. - assert!(!legacy_blob_loader.is_empty().unwrap()); - assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); - - // The database should be considered empty for user 1. - assert!(legacy_blob_loader.is_empty_user(1).unwrap()); - - legacy_blob_loader.remove_super_key(0); - - // Now it should be empty. - assert!(legacy_blob_loader.is_empty_user(0).unwrap()); - assert!(legacy_blob_loader.is_empty().unwrap()); - - Ok(()) - } - - #[test] - fn list_non_existing_user() -> Result<()> { - let temp_dir = TempDir::new("list_non_existing_user").unwrap(); - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert!(legacy_blob_loader.list_user(20)?.is_empty()); - - Ok(()) - } - - #[test] - fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> { - let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap(); - let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); - - assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty()); - - Ok(()) - } - - #[test] - fn test_move_keystore_entry() { - let temp_dir = TempDir::new("test_move_keystore_entry").unwrap(); - std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); - - const SOME_CONTENT: &[u8] = b"some content"; - const ANOTHER_CONTENT: &[u8] = b"another content"; - const SOME_FILENAME: &str = "some_file"; - const ANOTHER_FILENAME: &str = "another_file"; - - std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT) - .unwrap(); - - std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT) - .unwrap(); - - // Non existent source id silently ignored. - assert!(LegacyBlobLoader::move_keystore_file_if_exists( - 1, - 2, - "non_existent", - ANOTHER_FILENAME, - "ignored", - |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf() - ) - .is_ok()); - - // Content of another_file has not changed. - let another_content = - std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap(); - assert_eq!(&another_content, ANOTHER_CONTENT); - - // Check that some_file still exists. - assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists()); - // Existing target files are silently overwritten. - - assert!(LegacyBlobLoader::move_keystore_file_if_exists( - 1, - 2, - SOME_FILENAME, - ANOTHER_FILENAME, - "ignored", - |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf() - ) - .is_ok()); - - // Content of another_file is now "some content". - let another_content = - std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap(); - assert_eq!(&another_content, SOME_CONTENT); - - // Check that some_file no longer exists. - assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists()); - } -} diff --git a/keystore2/src/legacy_blob/tests.rs b/keystore2/src/legacy_blob/tests.rs new file mode 100644 index 00000000..53fe03ff --- /dev/null +++ b/keystore2/src/legacy_blob/tests.rs @@ -0,0 +1,676 @@ +// Copyright 2020, 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 legacy keyblob processing. + +#![allow(dead_code)] +use super::*; +use crate::legacy_blob::test_utils::legacy_blob_test_vectors::*; +use crate::legacy_blob::test_utils::*; +use anyhow::{anyhow, Result}; +use keystore2_crypto::aes_gcm_decrypt; +use keystore2_test_utils::TempDir; +use rand::Rng; +use std::convert::TryInto; +use std::ops::Deref; +use std::string::FromUtf8Error; + +#[test] +fn decode_encode_alias_test() { + static ALIAS: &str = "#({}test[])😗"; + static ENCODED_ALIAS: &str = "+S+X{}test[]+Y.`-O-H-G"; + // Second multi byte out of range ------v + static ENCODED_ALIAS_ERROR1: &str = "+S+{}test[]+Y"; + // Incomplete multi byte ------------------------v + static ENCODED_ALIAS_ERROR2: &str = "+S+X{}test[]+"; + // Our encoding: ".`-O-H-G" + // is UTF-8: 0xF0 0x9F 0x98 0x97 + // is UNICODE: U+1F617 + // is 😗 + // But +H below is a valid encoding for 0x18 making this sequence invalid UTF-8. + static ENCODED_ALIAS_ERROR_UTF8: &str = ".`-O+H-G"; + + assert_eq!(ENCODED_ALIAS, &LegacyBlobLoader::encode_alias(ALIAS)); + assert_eq!(ALIAS, &LegacyBlobLoader::decode_alias(ENCODED_ALIAS).unwrap()); + assert_eq!( + Some(&Error::BadEncoding), + LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR1) + .unwrap_err() + .root_cause() + .downcast_ref::<Error>() + ); + assert_eq!( + Some(&Error::BadEncoding), + LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR2) + .unwrap_err() + .root_cause() + .downcast_ref::<Error>() + ); + assert!(LegacyBlobLoader::decode_alias(ENCODED_ALIAS_ERROR_UTF8) + .unwrap_err() + .root_cause() + .downcast_ref::<FromUtf8Error>() + .is_some()); + + for _i in 0..100 { + // Any valid UTF-8 string should be en- and decoded without loss. + let alias_str = rand::thread_rng().gen::<[char; 20]>().iter().collect::<String>(); + let random_alias = alias_str.as_bytes(); + let encoded = LegacyBlobLoader::encode_alias(&alias_str); + let decoded = match LegacyBlobLoader::decode_alias(&encoded) { + Ok(d) => d, + Err(_) => panic!("random_alias: {:x?}\nencoded {}", random_alias, encoded), + }; + assert_eq!(random_alias.to_vec(), decoded.bytes().collect::<Vec<u8>>()); + } +} + +#[test] +fn read_golden_key_blob_test() -> anyhow::Result<()> { + let blob = LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*BLOB, |_, _, _, _, _| { + Err(anyhow!("should not be called")) + }) + .unwrap(); + assert!(!blob.is_encrypted()); + assert!(!blob.is_fallback()); + assert!(!blob.is_strongbox()); + assert!(!blob.is_critical_to_device_encryption()); + assert_eq!(blob.value(), &BlobValue::Generic([0xde, 0xed, 0xbe, 0xef].to_vec())); + + let blob = + LegacyBlobLoader::new_from_stream_decrypt_with(&mut &*REAL_LEGACY_BLOB, |_, _, _, _, _| { + Err(anyhow!("should not be called")) + }) + .unwrap(); + assert!(!blob.is_encrypted()); + assert!(!blob.is_fallback()); + assert!(!blob.is_strongbox()); + assert!(!blob.is_critical_to_device_encryption()); + assert_eq!(blob.value(), &BlobValue::Decrypted(REAL_LEGACY_BLOB_PAYLOAD.try_into().unwrap())); + Ok(()) +} + +#[test] +fn read_aes_gcm_encrypted_key_blob_test() { + let blob = LegacyBlobLoader::new_from_stream_decrypt_with( + &mut &*AES_GCM_ENCRYPTED_BLOB, + |d, iv, tag, salt, key_size| { + assert_eq!(salt, None); + assert_eq!(key_size, None); + assert_eq!( + iv, + &[ + 0xbd, 0xdb, 0x8d, 0x69, 0x72, 0x56, 0xf0, 0xf5, 0xa4, 0x02, 0x88, 0x7f, 0x00, + 0x00, 0x00, 0x00, + ] + ); + assert_eq!( + tag, + &[ + 0x50, 0xd9, 0x97, 0x95, 0x37, 0x6e, 0x28, 0x6a, 0x28, 0x9d, 0x51, 0xb9, 0xb9, + 0xe0, 0x0b, 0xc3 + ][..] + ); + aes_gcm_decrypt(d, iv, tag, AES_KEY).context("Trying to decrypt blob.") + }, + ) + .unwrap(); + assert!(blob.is_encrypted()); + assert!(!blob.is_fallback()); + assert!(!blob.is_strongbox()); + assert!(!blob.is_critical_to_device_encryption()); + + assert_eq!(blob.value(), &BlobValue::Decrypted(DECRYPTED_PAYLOAD.try_into().unwrap())); +} + +#[test] +fn read_golden_key_blob_too_short_test() { + let error = + LegacyBlobLoader::new_from_stream_decrypt_with(&mut &BLOB[0..15], |_, _, _, _, _| { + Err(anyhow!("should not be called")) + }) + .unwrap_err(); + assert_eq!(Some(&Error::BadLen), error.root_cause().downcast_ref::<Error>()); +} + +#[test] +fn test_is_empty() { + let temp_dir = TempDir::new("test_is_empty").expect("Failed to create temp dir."); + let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); + + assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty.")); + + let _db = + crate::database::KeystoreDB::new(temp_dir.path(), None).expect("Failed to open database."); + + assert!(legacy_blob_loader.is_empty().expect("Should succeed and still be empty.")); + + std::fs::create_dir(&*temp_dir.build().push("user_0")).expect("Failed to create user_0."); + + assert!(!legacy_blob_loader.is_empty().expect("Should succeed but not be empty.")); + + std::fs::create_dir(&*temp_dir.build().push("user_10")).expect("Failed to create user_10."); + + assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty.")); + + std::fs::remove_dir_all(&*temp_dir.build().push("user_0")).expect("Failed to remove user_0."); + + assert!(!legacy_blob_loader.is_empty().expect("Should succeed but still not be empty.")); + + std::fs::remove_dir_all(&*temp_dir.build().push("user_10")).expect("Failed to remove user_10."); + + assert!(legacy_blob_loader.is_empty().expect("Should succeed and be empty again.")); +} + +#[test] +fn test_legacy_blobs() -> anyhow::Result<()> { + let temp_dir = TempDir::new("legacy_blob_test").unwrap(); + std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); + + std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); + + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), + USRPKEY_AUTHBOUND, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), + USRPKEY_AUTHBOUND_CHR, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), + USRCERT_AUTHBOUND, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), + CACERT_AUTHBOUND, + ) + .unwrap(); + + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_USRPKEY_non_authbound"), + USRPKEY_NON_AUTHBOUND, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_non_authbound"), + USRPKEY_NON_AUTHBOUND_CHR, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_USRCERT_non_authbound"), + USRCERT_NON_AUTHBOUND, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_CACERT_non_authbound"), + CACERT_NON_AUTHBOUND, + ) + .unwrap(); + + let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); + + if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) = + legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? + { + assert_eq!(flags, 4); + assert_eq!( + value, + BlobValue::Encrypted { + data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), + iv: USRPKEY_AUTHBOUND_IV.to_vec(), + tag: USRPKEY_AUTHBOUND_TAG.to_vec() + } + ); + assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND); + assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND); + } else { + panic!(""); + } + + if let (Some((Blob { flags, value: _ }, _params)), Some(cert), Some(chain)) = + legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? + { + assert_eq!(flags, 4); + //assert_eq!(value, BlobValue::Encrypted(..)); + assert_eq!(&cert[..], LOADED_CERT_AUTHBOUND); + assert_eq!(&chain[..], LOADED_CACERT_AUTHBOUND); + } else { + panic!(""); + } + if let (Some((Blob { flags, value }, _params)), Some(cert), Some(chain)) = + legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)? + { + assert_eq!(flags, 0); + assert_eq!(value, BlobValue::Decrypted(LOADED_USRPKEY_NON_AUTHBOUND.try_into()?)); + assert_eq!(&cert[..], LOADED_CERT_NON_AUTHBOUND); + assert_eq!(&chain[..], LOADED_CACERT_NON_AUTHBOUND); + } else { + panic!(""); + } + + legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed."); + legacy_blob_loader.remove_keystore_entry(10223, "non_authbound").expect("This should succeed."); + + assert_eq!( + (None, None, None), + legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None)? + ); + assert_eq!( + (None, None, None), + legacy_blob_loader.load_by_uid_alias(10223, "non_authbound", &None)? + ); + + // The database should not be empty due to the super key. + assert!(!legacy_blob_loader.is_empty()?); + assert!(!legacy_blob_loader.is_empty_user(0)?); + + // The database should be considered empty for user 1. + assert!(legacy_blob_loader.is_empty_user(1)?); + + legacy_blob_loader.remove_super_key(0); + + // Now it should be empty. + assert!(legacy_blob_loader.is_empty_user(0)?); + assert!(legacy_blob_loader.is_empty()?); + + Ok(()) +} + +struct TestKey(ZVec); + +impl crate::utils::AesGcmKey for TestKey { + fn key(&self) -> &[u8] { + &self.0 + } +} + +impl Deref for TestKey { + type Target = [u8]; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[test] +fn test_with_encrypted_characteristics() -> anyhow::Result<()> { + let temp_dir = TempDir::new("test_with_encrypted_characteristics").unwrap(); + std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); + + let pw: Password = PASSWORD.into(); + let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); + let super_key = + Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); + + std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); + + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), + USRPKEY_AUTHBOUND, + ) + .unwrap(); + make_encrypted_characteristics_file( + &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), + &super_key, + KEY_PARAMETERS, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), + USRCERT_AUTHBOUND, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), + CACERT_AUTHBOUND, + ) + .unwrap(); + + let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); + + assert_eq!( + legacy_blob_loader + .load_by_uid_alias(10223, "authbound", &None) + .unwrap_err() + .root_cause() + .downcast_ref::<Error>(), + Some(&Error::LockedComponent) + ); + + assert_eq!( + legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(), + ( + Some(( + Blob { + flags: 4, + value: BlobValue::Encrypted { + data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), + iv: USRPKEY_AUTHBOUND_IV.to_vec(), + tag: USRPKEY_AUTHBOUND_TAG.to_vec() + } + }, + structured_test_params() + )), + Some(LOADED_CERT_AUTHBOUND.to_vec()), + Some(LOADED_CACERT_AUTHBOUND.to_vec()) + ) + ); + + legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed."); + + assert_eq!( + (None, None, None), + legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap() + ); + + // The database should not be empty due to the super key. + assert!(!legacy_blob_loader.is_empty().unwrap()); + assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); + + // The database should be considered empty for user 1. + assert!(legacy_blob_loader.is_empty_user(1).unwrap()); + + legacy_blob_loader.remove_super_key(0); + + // Now it should be empty. + assert!(legacy_blob_loader.is_empty_user(0).unwrap()); + assert!(legacy_blob_loader.is_empty().unwrap()); + + Ok(()) +} + +#[test] +fn test_with_encrypted_certificates() -> anyhow::Result<()> { + let temp_dir = TempDir::new("test_with_encrypted_certificates").unwrap(); + std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); + + let pw: Password = PASSWORD.into(); + let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); + let super_key = + Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); + + std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); + + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), + USRPKEY_AUTHBOUND, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), + USRPKEY_AUTHBOUND_CHR, + ) + .unwrap(); + make_encrypted_usr_cert_file( + &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), + &super_key, + LOADED_CERT_AUTHBOUND, + ) + .unwrap(); + make_encrypted_ca_cert_file( + &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), + &super_key, + LOADED_CACERT_AUTHBOUND, + ) + .unwrap(); + + let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); + + assert_eq!( + legacy_blob_loader + .load_by_uid_alias(10223, "authbound", &None) + .unwrap_err() + .root_cause() + .downcast_ref::<Error>(), + Some(&Error::LockedComponent) + ); + + assert_eq!( + legacy_blob_loader.load_by_uid_alias(10223, "authbound", &Some(super_key)).unwrap(), + ( + Some(( + Blob { + flags: 4, + value: BlobValue::Encrypted { + data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), + iv: USRPKEY_AUTHBOUND_IV.to_vec(), + tag: USRPKEY_AUTHBOUND_TAG.to_vec() + } + }, + structured_test_params_cache() + )), + Some(LOADED_CERT_AUTHBOUND.to_vec()), + Some(LOADED_CACERT_AUTHBOUND.to_vec()) + ) + ); + + legacy_blob_loader.remove_keystore_entry(10223, "authbound").expect("This should succeed."); + + assert_eq!( + (None, None, None), + legacy_blob_loader.load_by_uid_alias(10223, "authbound", &None).unwrap() + ); + + // The database should not be empty due to the super key. + assert!(!legacy_blob_loader.is_empty().unwrap()); + assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); + + // The database should be considered empty for user 1. + assert!(legacy_blob_loader.is_empty_user(1).unwrap()); + + legacy_blob_loader.remove_super_key(0); + + // Now it should be empty. + assert!(legacy_blob_loader.is_empty_user(0).unwrap()); + assert!(legacy_blob_loader.is_empty().unwrap()); + + Ok(()) +} + +#[test] +fn test_in_place_key_migration() -> anyhow::Result<()> { + let temp_dir = TempDir::new("test_in_place_key_migration").unwrap(); + std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); + + let pw: Password = PASSWORD.into(); + let pw_key = TestKey(pw.derive_key_pbkdf2(SUPERKEY_SALT, 32).unwrap()); + let super_key = + Arc::new(TestKey(pw_key.decrypt(SUPERKEY_PAYLOAD, SUPERKEY_IV, SUPERKEY_TAG).unwrap())); + + std::fs::write(&*temp_dir.build().push("user_0").push(".masterkey"), SUPERKEY).unwrap(); + + std::fs::write( + &*temp_dir.build().push("user_0").push("10223_USRPKEY_authbound"), + USRPKEY_AUTHBOUND, + ) + .unwrap(); + std::fs::write( + &*temp_dir.build().push("user_0").push(".10223_chr_USRPKEY_authbound"), + USRPKEY_AUTHBOUND_CHR, + ) + .unwrap(); + make_encrypted_usr_cert_file( + &*temp_dir.build().push("user_0").push("10223_USRCERT_authbound"), + &super_key, + LOADED_CERT_AUTHBOUND, + ) + .unwrap(); + make_encrypted_ca_cert_file( + &*temp_dir.build().push("user_0").push("10223_CACERT_authbound"), + &super_key, + LOADED_CACERT_AUTHBOUND, + ) + .unwrap(); + + let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); + + assert_eq!( + legacy_blob_loader + .load_by_uid_alias(10223, "authbound", &None) + .unwrap_err() + .root_cause() + .downcast_ref::<Error>(), + Some(&Error::LockedComponent) + ); + + let super_key: Option<Arc<dyn AesGcm>> = Some(super_key); + + assert_eq!( + legacy_blob_loader.load_by_uid_alias(10223, "authbound", &super_key).unwrap(), + ( + Some(( + Blob { + flags: 4, + value: BlobValue::Encrypted { + data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), + iv: USRPKEY_AUTHBOUND_IV.to_vec(), + tag: USRPKEY_AUTHBOUND_TAG.to_vec() + } + }, + structured_test_params_cache() + )), + Some(LOADED_CERT_AUTHBOUND.to_vec()), + Some(LOADED_CACERT_AUTHBOUND.to_vec()) + ) + ); + + legacy_blob_loader.move_keystore_entry(10223, 10224, "authbound", "boundauth").unwrap(); + + assert_eq!( + legacy_blob_loader + .load_by_uid_alias(10224, "boundauth", &None) + .unwrap_err() + .root_cause() + .downcast_ref::<Error>(), + Some(&Error::LockedComponent) + ); + + assert_eq!( + legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &super_key).unwrap(), + ( + Some(( + Blob { + flags: 4, + value: BlobValue::Encrypted { + data: USRPKEY_AUTHBOUND_ENC_PAYLOAD.to_vec(), + iv: USRPKEY_AUTHBOUND_IV.to_vec(), + tag: USRPKEY_AUTHBOUND_TAG.to_vec() + } + }, + structured_test_params_cache() + )), + Some(LOADED_CERT_AUTHBOUND.to_vec()), + Some(LOADED_CACERT_AUTHBOUND.to_vec()) + ) + ); + + legacy_blob_loader.remove_keystore_entry(10224, "boundauth").expect("This should succeed."); + + assert_eq!( + (None, None, None), + legacy_blob_loader.load_by_uid_alias(10224, "boundauth", &None).unwrap() + ); + + // The database should not be empty due to the super key. + assert!(!legacy_blob_loader.is_empty().unwrap()); + assert!(!legacy_blob_loader.is_empty_user(0).unwrap()); + + // The database should be considered empty for user 1. + assert!(legacy_blob_loader.is_empty_user(1).unwrap()); + + legacy_blob_loader.remove_super_key(0); + + // Now it should be empty. + assert!(legacy_blob_loader.is_empty_user(0).unwrap()); + assert!(legacy_blob_loader.is_empty().unwrap()); + + Ok(()) +} + +#[test] +fn list_non_existing_user() -> Result<()> { + let temp_dir = TempDir::new("list_non_existing_user").unwrap(); + let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); + + assert!(legacy_blob_loader.list_user(20)?.is_empty()); + + Ok(()) +} + +#[test] +fn list_legacy_keystore_entries_on_non_existing_user() -> Result<()> { + let temp_dir = TempDir::new("list_legacy_keystore_entries_on_non_existing_user").unwrap(); + let legacy_blob_loader = LegacyBlobLoader::new(temp_dir.path()); + + assert!(legacy_blob_loader.list_legacy_keystore_entries_for_user(20)?.is_empty()); + + Ok(()) +} + +#[test] +fn test_move_keystore_entry() { + let temp_dir = TempDir::new("test_move_keystore_entry").unwrap(); + std::fs::create_dir(&*temp_dir.build().push("user_0")).unwrap(); + + const SOME_CONTENT: &[u8] = b"some content"; + const ANOTHER_CONTENT: &[u8] = b"another content"; + const SOME_FILENAME: &str = "some_file"; + const ANOTHER_FILENAME: &str = "another_file"; + + std::fs::write(&*temp_dir.build().push("user_0").push(SOME_FILENAME), SOME_CONTENT).unwrap(); + + std::fs::write(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME), ANOTHER_CONTENT) + .unwrap(); + + // Non existent source id silently ignored. + assert!(LegacyBlobLoader::move_keystore_file_if_exists( + 1, + 2, + "non_existent", + ANOTHER_FILENAME, + "ignored", + |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf() + ) + .is_ok()); + + // Content of another_file has not changed. + let another_content = + std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap(); + assert_eq!(&another_content, ANOTHER_CONTENT); + + // Check that some_file still exists. + assert!(temp_dir.build().push("user_0").push(SOME_FILENAME).exists()); + // Existing target files are silently overwritten. + + assert!(LegacyBlobLoader::move_keystore_file_if_exists( + 1, + 2, + SOME_FILENAME, + ANOTHER_FILENAME, + "ignored", + |_, alias, _| temp_dir.build().push("user_0").push(alias).to_path_buf() + ) + .is_ok()); + + // Content of another_file is now "some content". + let another_content = + std::fs::read(&*temp_dir.build().push("user_0").push(ANOTHER_FILENAME)).unwrap(); + assert_eq!(&another_content, SOME_CONTENT); + + // Check that some_file no longer exists. + assert!(!temp_dir.build().push("user_0").push(SOME_FILENAME).exists()); +} diff --git a/keystore2/src/legacy_importer.rs b/keystore2/src/legacy_importer.rs index 7dcb98d6..24f32637 100644 --- a/keystore2/src/legacy_importer.rs +++ b/keystore2/src/legacy_importer.rs @@ -202,7 +202,7 @@ impl LegacyImporter { /// List all aliases for uid in the legacy database. pub fn list_uid(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> { - let _wp = wd::watch_millis("LegacyImporter::list_uid", 500); + let _wp = wd::watch("LegacyImporter::list_uid"); let uid = match (domain, namespace) { (Domain::APP, namespace) => namespace as u32, @@ -299,7 +299,7 @@ impl LegacyImporter { where F: Fn() -> Result<T>, { - let _wp = wd::watch_millis("LegacyImporter::with_try_import", 500); + let _wp = wd::watch("LegacyImporter::with_try_import"); // Access the key and return on success. match key_accessor() { @@ -355,7 +355,7 @@ impl LegacyImporter { where F: FnMut() -> Result<Option<T>>, { - let _wp = wd::watch_millis("LegacyImporter::with_try_import_super_key", 500); + let _wp = wd::watch("LegacyImporter::with_try_import_super_key"); match key_accessor() { Ok(Some(result)) => return Ok(Some(result)), @@ -379,7 +379,7 @@ impl LegacyImporter { /// Deletes all keys belonging to the given namespace, importing them into the database /// for subsequent garbage collection if necessary. pub fn bulk_delete_uid(&self, domain: Domain, nspace: i64) -> Result<()> { - let _wp = wd::watch_millis("LegacyImporter::bulk_delete_uid", 500); + let _wp = wd::watch("LegacyImporter::bulk_delete_uid"); let uid = match (domain, nspace) { (Domain::APP, nspace) => nspace as u32, @@ -402,7 +402,7 @@ impl LegacyImporter { user_id: u32, keep_non_super_encrypted_keys: bool, ) -> Result<()> { - let _wp = wd::watch_millis("LegacyImporter::bulk_delete_user", 500); + let _wp = wd::watch("LegacyImporter::bulk_delete_user"); let result = self.do_serialized(move |importer_state| { importer_state @@ -923,11 +923,11 @@ fn get_key_characteristics_without_app_data( blob, &[], |blob| { - let _wd = wd::watch_millis("Calling GetKeyCharacteristics.", 500); + let _wd = wd::watch("get_key_characteristics_without_app_data: calling IKeyMintDevice::getKeyCharacteristics"); map_km_error(km_dev.getKeyCharacteristics(blob, &[], &[])) }, |_| Ok(()), ) - .context(ks_err!())?; + .context(ks_err!("getKeyCharacteristics failed: possibly invalid keyblob for uuid {uuid:?}"))?; Ok((key_characteristics_to_internal(characteristics), upgraded_blob)) } diff --git a/keystore2/src/maintenance.rs b/keystore2/src/maintenance.rs index 3e34cff3..61277f10 100644 --- a/keystore2/src/maintenance.rs +++ b/keystore2/src/maintenance.rs @@ -14,21 +14,21 @@ //! This module implements IKeystoreMaintenance AIDL interface. -use crate::database::{BootTime, KeyEntryLoadBits, KeyType}; +use crate::database::{KeyEntryLoadBits, KeyType}; +use crate::error::into_logged_binder; use crate::error::map_km_error; -use crate::error::map_or_log_err; use crate::error::Error; use crate::globals::get_keymint_device; use crate::globals::{DB, LEGACY_IMPORTER, SUPER_KEY}; use crate::ks_err; use crate::permission::{KeyPerm, KeystorePerm}; -use crate::super_key::{SuperKeyManager, UserState}; +use crate::super_key::SuperKeyManager; use crate::utils::{ 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::{ - IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel, + ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel, }; use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::{ BnKeystoreMaintenance, IKeystoreMaintenance, @@ -69,40 +69,6 @@ impl Maintenance { )) } - fn on_user_password_changed(user_id: i32, password: Option<Password>) -> Result<()> { - // Check permission. Function should return if this failed. Therefore having '?' at the end - // is very important. - check_keystore_permission(KeystorePerm::ChangePassword).context(ks_err!())?; - - let mut skm = SUPER_KEY.write().unwrap(); - - if let Some(pw) = password.as_ref() { - DB.with(|db| { - skm.unlock_unlocked_device_required_keys(&mut db.borrow_mut(), user_id as u32, pw) - }) - .context(ks_err!("unlock_unlocked_device_required_keys failed"))?; - } - - if let UserState::BeforeFirstUnlock = DB - .with(|db| skm.get_user_state(&mut db.borrow_mut(), &LEGACY_IMPORTER, user_id as u32)) - .context(ks_err!("Could not get user state while changing password!"))? - { - // Error - password can not be changed when the device is locked - return Err(Error::Rc(ResponseCode::LOCKED)).context(ks_err!("Device is locked.")); - } - - DB.with(|db| match password { - Some(pass) => { - skm.init_user(&mut db.borrow_mut(), &LEGACY_IMPORTER, user_id as u32, &pass) - } - None => { - // User transitioned to swipe. - skm.reset_user(&mut db.borrow_mut(), &LEGACY_IMPORTER, user_id as u32) - } - }) - .context(ks_err!("Failed to change user password!")) - } - fn add_or_remove_user(&self, user_id: i32) -> Result<()> { // Check permission. Function should return if this failed. Therefore having '?' at the end // is very important. @@ -177,9 +143,7 @@ impl Maintenance { let (km_dev, _, _) = get_keymint_device(&sec_level).context(ks_err!("getting keymint device"))?; - let _wp = wd::watch_millis_with("In call_with_watchdog", 500, move || { - format!("Seclevel: {:?} Op: {}", sec_level, name) - }); + let _wp = wd::watch_millis_with("Maintenance::call_with_watchdog", 500, (sec_level, name)); map_km_error(op(km_dev)).with_context(|| ks_err!("calling {}", name))?; Ok(()) } @@ -200,12 +164,21 @@ impl Maintenance { name, &sec_level_string ), - Err(ref e) => log::error!( - "Call to {} failed for security level {}: {}.", - name, - &sec_level_string, - e - ), + Err(ref e) => { + if *sec_level == SecurityLevel::STRONGBOX + && e.downcast_ref::<Error>() + == Some(&Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)) + { + log::info!("Call to {} failed for StrongBox as it is not available", name,) + } else { + log::error!( + "Call to {} failed for security level {}: {}.", + name, + &sec_level_string, + e + ) + } + } } curr_result }) @@ -224,14 +197,6 @@ impl Maintenance { Maintenance::call_on_all_security_levels("earlyBootEnded", |dev| dev.earlyBootEnded()) } - fn on_device_off_body() -> Result<()> { - // Security critical permission check. This statement must return on fail. - check_keystore_permission(KeystorePerm::ReportOffBody).context(ks_err!())?; - - DB.with(|db| db.borrow_mut().update_last_off_body(BootTime::now())); - Ok(()) - } - fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> { let calling_uid = ThreadState::get_calling_uid(); @@ -304,20 +269,10 @@ impl Maintenance { impl Interface for Maintenance {} impl IKeystoreMaintenance for Maintenance { - fn onUserPasswordChanged(&self, user_id: i32, password: Option<&[u8]>) -> BinderResult<()> { - log::info!( - "onUserPasswordChanged(user={}, password.is_some()={})", - user_id, - password.is_some() - ); - let _wp = wd::watch_millis("IKeystoreMaintenance::onUserPasswordChanged", 500); - map_or_log_err(Self::on_user_password_changed(user_id, password.map(|pw| pw.into())), Ok) - } - fn onUserAdded(&self, user_id: i32) -> BinderResult<()> { log::info!("onUserAdded(user={user_id})"); - let _wp = wd::watch_millis("IKeystoreMaintenance::onUserAdded", 500); - map_or_log_err(self.add_or_remove_user(user_id), Ok) + let _wp = wd::watch("IKeystoreMaintenance::onUserAdded"); + self.add_or_remove_user(user_id).map_err(into_logged_binder) } fn initUserSuperKeys( @@ -327,38 +282,33 @@ impl IKeystoreMaintenance for Maintenance { allow_existing: bool, ) -> BinderResult<()> { log::info!("initUserSuperKeys(user={user_id}, allow_existing={allow_existing})"); - let _wp = wd::watch_millis("IKeystoreMaintenance::initUserSuperKeys", 500); - map_or_log_err(self.init_user_super_keys(user_id, password.into(), allow_existing), Ok) + let _wp = wd::watch("IKeystoreMaintenance::initUserSuperKeys"); + self.init_user_super_keys(user_id, password.into(), allow_existing) + .map_err(into_logged_binder) } fn onUserRemoved(&self, user_id: i32) -> BinderResult<()> { log::info!("onUserRemoved(user={user_id})"); - let _wp = wd::watch_millis("IKeystoreMaintenance::onUserRemoved", 500); - map_or_log_err(self.add_or_remove_user(user_id), Ok) + let _wp = wd::watch("IKeystoreMaintenance::onUserRemoved"); + self.add_or_remove_user(user_id).map_err(into_logged_binder) } fn onUserLskfRemoved(&self, user_id: i32) -> BinderResult<()> { log::info!("onUserLskfRemoved(user={user_id})"); - let _wp = wd::watch_millis("IKeystoreMaintenance::onUserLskfRemoved", 500); - map_or_log_err(Self::on_user_lskf_removed(user_id), Ok) + let _wp = wd::watch("IKeystoreMaintenance::onUserLskfRemoved"); + Self::on_user_lskf_removed(user_id).map_err(into_logged_binder) } fn clearNamespace(&self, domain: Domain, nspace: i64) -> BinderResult<()> { log::info!("clearNamespace({domain:?}, nspace={nspace})"); - let _wp = wd::watch_millis("IKeystoreMaintenance::clearNamespace", 500); - map_or_log_err(self.clear_namespace(domain, nspace), Ok) + let _wp = wd::watch("IKeystoreMaintenance::clearNamespace"); + self.clear_namespace(domain, nspace).map_err(into_logged_binder) } fn earlyBootEnded(&self) -> BinderResult<()> { log::info!("earlyBootEnded()"); - let _wp = wd::watch_millis("IKeystoreMaintenance::earlyBootEnded", 500); - map_or_log_err(Self::early_boot_ended(), Ok) - } - - fn onDeviceOffBody(&self) -> BinderResult<()> { - log::info!("onDeviceOffBody()"); - let _wp = wd::watch_millis("IKeystoreMaintenance::onDeviceOffBody", 500); - map_or_log_err(Self::on_device_off_body(), Ok) + let _wp = wd::watch("IKeystoreMaintenance::earlyBootEnded"); + Self::early_boot_ended().map_err(into_logged_binder) } fn migrateKeyNamespace( @@ -367,14 +317,14 @@ impl IKeystoreMaintenance for Maintenance { destination: &KeyDescriptor, ) -> BinderResult<()> { log::info!("migrateKeyNamespace(src={source:?}, dest={destination:?})"); - let _wp = wd::watch_millis("IKeystoreMaintenance::migrateKeyNamespace", 500); - map_or_log_err(Self::migrate_key_namespace(source, destination), Ok) + let _wp = wd::watch("IKeystoreMaintenance::migrateKeyNamespace"); + Self::migrate_key_namespace(source, destination).map_err(into_logged_binder) } fn deleteAllKeys(&self) -> BinderResult<()> { - log::warn!("deleteAllKeys()"); - let _wp = wd::watch_millis("IKeystoreMaintenance::deleteAllKeys", 500); - map_or_log_err(Self::delete_all_keys(), Ok) + log::warn!("deleteAllKeys() invoked, indicating initial setup or post-factory reset"); + let _wp = wd::watch("IKeystoreMaintenance::deleteAllKeys"); + Self::delete_all_keys().map_err(into_logged_binder) } fn getAppUidsAffectedBySid( @@ -383,7 +333,7 @@ impl IKeystoreMaintenance for Maintenance { secure_user_id: i64, ) -> BinderResult<std::vec::Vec<i64>> { log::info!("getAppUidsAffectedBySid(secure_user_id={secure_user_id:?})"); - let _wp = wd::watch_millis("IKeystoreMaintenance::getAppUidsAffectedBySid", 500); - map_or_log_err(Self::get_app_uids_affected_by_sid(user_id, secure_user_id), Ok) + let _wp = wd::watch("IKeystoreMaintenance::getAppUidsAffectedBySid"); + Self::get_app_uids_affected_by_sid(user_id, secure_user_id).map_err(into_logged_binder) } } diff --git a/keystore2/src/metrics.rs b/keystore2/src/metrics.rs index cd1cd75d..47577393 100644 --- a/keystore2/src/metrics.rs +++ b/keystore2/src/metrics.rs @@ -14,7 +14,7 @@ //! This module implements the IKeystoreMetrics AIDL interface, which exposes the API method for the //! proxy in the system server to pull the aggregated metrics in keystore. -use crate::error::map_or_log_err; +use crate::error::into_logged_binder; use crate::ks_err; use crate::metrics_store::METRICS_STORE; use crate::permission::KeystorePerm; @@ -51,7 +51,7 @@ impl Interface for Metrics {} impl IKeystoreMetrics for Metrics { fn pullMetrics(&self, atom_id: AtomID) -> BinderResult<Vec<KeystoreAtom>> { - let _wp = wd::watch_millis("IKeystoreMetrics::pullMetrics", 500); - map_or_log_err(self.pull_metrics(atom_id), Ok) + let _wp = wd::watch("IKeystoreMetrics::pullMetrics"); + self.pull_metrics(atom_id).map_err(into_logged_binder) } } diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs index eabc1abb..27f8ef69 100644 --- a/keystore2/src/operation.rs +++ b/keystore2/src/operation.rs @@ -127,7 +127,7 @@ use crate::enforcements::AuthInfo; use crate::error::{ - error_to_serialized_error, map_err_with, map_km_error, map_or_log_err, Error, ErrorCode, + error_to_serialized_error, into_binder, into_logged_binder, map_km_error, Error, ErrorCode, ResponseCode, SerializedError, }; use crate::ks_err; @@ -286,11 +286,11 @@ impl Operation { } *locked_outcome = Outcome::Pruned; - let _wp = wd::watch_millis("In Operation::prune: calling abort()", 500); + let _wp = wd::watch("Operation::prune: calling IKeyMintOperation::abort()"); // We abort the operation. If there was an error we log it but ignore it. if let Err(e) = map_km_error(self.km_op.abort()) { - log::error!("In prune: KeyMint::abort failed with {:?}.", e); + log::warn!("In prune: KeyMint::abort failed with {:?}.", e); } Ok(()) @@ -362,7 +362,7 @@ impl Operation { .context(ks_err!("Trying to get auth tokens."))?; self.update_outcome(&mut outcome, { - let _wp = wd::watch_millis("Operation::update_aad: calling updateAad", 500); + let _wp = wd::watch("Operation::update_aad: calling IKeyMintOperation::updateAad"); map_km_error(self.km_op.updateAad(aad_input, hat.as_ref(), tst.as_ref())) }) .context(ks_err!("Update failed."))?; @@ -386,7 +386,7 @@ impl Operation { let output = self .update_outcome(&mut outcome, { - let _wp = wd::watch_millis("Operation::update: calling update", 500); + let _wp = wd::watch("Operation::update: calling IKeyMintOperation::update"); map_km_error(self.km_op.update(input, hat.as_ref(), tst.as_ref())) }) .context(ks_err!("Update failed."))?; @@ -416,7 +416,7 @@ impl Operation { let output = self .update_outcome(&mut outcome, { - let _wp = wd::watch_millis("Operation::finish: calling finish", 500); + let _wp = wd::watch("Operation::finish: calling IKeyMintOperation::finish"); map_km_error(self.km_op.finish( input, signature, @@ -447,7 +447,7 @@ impl Operation { *locked_outcome = outcome; { - let _wp = wd::watch_millis("Operation::abort: calling abort", 500); + let _wp = wd::watch("Operation::abort: calling IKeyMintOperation::abort"); map_km_error(self.km_op.abort()).context(ks_err!("KeyMint::abort failed.")) } } @@ -821,59 +821,50 @@ impl binder::Interface for KeystoreOperation {} impl IKeystoreOperation for KeystoreOperation { fn updateAad(&self, aad_input: &[u8]) -> binder::Result<()> { - let _wp = wd::watch_millis("IKeystoreOperation::updateAad", 500); - map_or_log_err( - self.with_locked_operation( - |op| op.update_aad(aad_input).context(ks_err!("KeystoreOperation::updateAad")), - false, - ), - Ok, + let _wp = wd::watch("IKeystoreOperation::updateAad"); + self.with_locked_operation( + |op| op.update_aad(aad_input).context(ks_err!("KeystoreOperation::updateAad")), + false, ) + .map_err(into_logged_binder) } fn update(&self, input: &[u8]) -> binder::Result<Option<Vec<u8>>> { - let _wp = wd::watch_millis("IKeystoreOperation::update", 500); - map_or_log_err( - self.with_locked_operation( - |op| op.update(input).context(ks_err!("KeystoreOperation::update")), - false, - ), - Ok, + let _wp = wd::watch("IKeystoreOperation::update"); + self.with_locked_operation( + |op| op.update(input).context(ks_err!("KeystoreOperation::update")), + false, ) + .map_err(into_logged_binder) } fn finish( &self, input: Option<&[u8]>, signature: Option<&[u8]>, ) -> binder::Result<Option<Vec<u8>>> { - let _wp = wd::watch_millis("IKeystoreOperation::finish", 500); - map_or_log_err( - self.with_locked_operation( - |op| op.finish(input, signature).context(ks_err!("KeystoreOperation::finish")), - true, - ), - Ok, + let _wp = wd::watch("IKeystoreOperation::finish"); + self.with_locked_operation( + |op| op.finish(input, signature).context(ks_err!("KeystoreOperation::finish")), + true, ) + .map_err(into_logged_binder) } fn abort(&self) -> binder::Result<()> { - let _wp = wd::watch_millis("IKeystoreOperation::abort", 500); - map_err_with( - self.with_locked_operation( - |op| op.abort(Outcome::Abort).context(ks_err!("KeystoreOperation::abort")), - true, - ), - |e| { - match e.root_cause().downcast_ref::<Error>() { - // Calling abort on expired operations is something very common. - // There is no reason to clutter the log with it. It is never the cause - // for a true problem. - Some(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {} - _ => log::error!("{:?}", e), - }; - e - }, - Ok, - ) + let _wp = wd::watch("IKeystoreOperation::abort"); + let result = self.with_locked_operation( + |op| op.abort(Outcome::Abort).context(ks_err!("KeystoreOperation::abort")), + true, + ); + result.map_err(|e| { + match e.root_cause().downcast_ref::<Error>() { + // Calling abort on expired operations is something very common. + // There is no reason to clutter the log with it. It is never the cause + // for a true problem. + Some(Error::Km(ErrorCode::INVALID_OPERATION_HANDLE)) => {} + _ => log::error!("{:?}", e), + }; + into_binder(e) + }) } } diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs index bc73744f..d79445b1 100644 --- a/keystore2/src/permission.rs +++ b/keystore2/src/permission.rs @@ -38,6 +38,9 @@ use selinux::getcon; #[cfg(test)] use tests::test_getcon as getcon; +#[cfg(test)] +mod tests; + lazy_static! { // Panicking here is allowed because keystore cannot function without this backend // and it would happen early and indicate a gross misconfiguration of the device. @@ -137,10 +140,7 @@ implement_class!( /// Checked when earlyBootEnded() is called. #[selinux(name = early_boot_ended)] EarlyBootEnded, - /// Checked when IKeystoreMaintenance::onDeviceOffBody is called. - #[selinux(name = report_off_body)] - ReportOffBody, - /// Checked when IkeystoreMetrics::pullMetrics is called. + /// Checked when IKeystoreMetrics::pullMetrics is called. #[selinux(name = pull_metrics)] PullMetrics, /// Checked when IKeystoreMaintenance::deleteAllKeys is called. @@ -400,433 +400,3 @@ pub fn check_key_permission( selinux::check_permission(caller_ctx, &target_context, perm) } - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::anyhow; - use anyhow::Result; - use keystore2_selinux::*; - - const ALL_PERMS: KeyPermSet = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::UseDevId, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - KeyPerm::Grant, - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - KeyPerm::ConvertStorageKeyToEphemeral, - ]; - - const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![ - KeyPerm::Delete, - KeyPerm::UseDevId, - // No KeyPerm::Grant - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - ]; - - const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::UseDevId, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - // No KeyPerm::Grant - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - KeyPerm::ConvertStorageKeyToEphemeral, - ]; - - const UNPRIV_PERMS: KeyPermSet = key_perm_set![ - KeyPerm::Delete, - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - ]; - - /// The su_key namespace as defined in su.te and keystore_key_contexts of the - /// SePolicy (system/sepolicy). - const SU_KEY_NAMESPACE: i32 = 0; - /// The shell_key namespace as defined in shell.te and keystore_key_contexts of the - /// SePolicy (system/sepolicy). - const SHELL_KEY_NAMESPACE: i32 = 1; - - pub fn test_getcon() -> Result<Context> { - Context::new("u:object_r:keystore:s0") - } - - // This macro evaluates the given expression and checks that - // a) evaluated to Result::Err() and that - // b) the wrapped error is selinux::Error::perm() (permission denied). - // We use a macro here because a function would mask which invocation caused the failure. - // - // TODO b/164121720 Replace this macro with a function when `track_caller` is available. - macro_rules! assert_perm_failed { - ($test_function:expr) => { - let result = $test_function; - assert!(result.is_err(), "Permission check should have failed."); - assert_eq!( - Some(&selinux::Error::perm()), - result.err().unwrap().root_cause().downcast_ref::<selinux::Error>() - ); - }; - } - - fn check_context() -> Result<(selinux::Context, i32, bool)> { - // Calling the non mocked selinux::getcon here intended. - let context = selinux::getcon()?; - match context.to_str().unwrap() { - "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)), - "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)), - c => Err(anyhow!(format!( - "This test must be run as \"su\" or \"shell\". Current context: \"{}\"", - c - ))), - } - } - - #[test] - fn check_keystore_permission_test() -> Result<()> { - let system_server_ctx = Context::new("u:r:system_server:s0")?; - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Lock).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Reset).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Unlock).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangeUser).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok()); - assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok()); - let shell_ctx = Context::new("u:r:shell:s0")?; - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::List)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Lock)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Reset)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Unlock)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangeUser)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword)); - assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID)); - Ok(()) - } - - #[test] - fn check_grant_permission_app() -> Result<()> { - let system_server_ctx = Context::new("u:r:system_server:s0")?; - let shell_ctx = Context::new("u:r:shell:s0")?; - let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None }; - check_grant_permission(&system_server_ctx, SYSTEM_SERVER_PERMISSIONS_NO_GRANT, &key) - .expect("Grant permission check failed."); - - // attempts to grant the grant permission must always fail even when privileged. - assert_perm_failed!(check_grant_permission( - &system_server_ctx, - KeyPerm::Grant.into(), - &key - )); - // unprivileged grant attempts always fail. shell does not have the grant permission. - assert_perm_failed!(check_grant_permission(&shell_ctx, UNPRIV_PERMS, &key)); - Ok(()) - } - - #[test] - fn check_grant_permission_selinux() -> Result<()> { - let (sctx, namespace, is_su) = check_context()?; - let key = KeyDescriptor { - domain: Domain::SELINUX, - nspace: namespace as i64, - alias: None, - blob: None, - }; - if is_su { - assert!(check_grant_permission(&sctx, NOT_GRANT_PERMS, &key).is_ok()); - // attempts to grant the grant permission must always fail even when privileged. - assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::Grant.into(), &key)); - } else { - // unprivileged grant attempts always fail. shell does not have the grant permission. - assert_perm_failed!(check_grant_permission(&sctx, UNPRIV_PERMS, &key)); - } - Ok(()) - } - - #[test] - fn check_key_permission_domain_grant() -> Result<()> { - let key = KeyDescriptor { domain: Domain::GRANT, nspace: 0, alias: None, blob: None }; - - assert_perm_failed!(check_key_permission( - 0, - &selinux::Context::new("ignored").unwrap(), - KeyPerm::Grant, - &key, - &Some(UNPRIV_PERMS) - )); - - check_key_permission( - 0, - &selinux::Context::new("ignored").unwrap(), - KeyPerm::Use, - &key, - &Some(ALL_PERMS), - ) - } - - #[test] - fn check_key_permission_domain_app() -> Result<()> { - let system_server_ctx = Context::new("u:r:system_server:s0")?; - let shell_ctx = Context::new("u:r:shell:s0")?; - let gmscore_app = Context::new("u:r:gmscore_app:s0")?; - - let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None }; - - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Use, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Delete, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::GetInfo, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Rebind, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Update, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Grant, &key, &None).is_ok()); - assert!(check_key_permission(0, &system_server_ctx, KeyPerm::UseDevId, &key, &None).is_ok()); - assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &key, &None).is_ok()); - - assert!(check_key_permission(0, &shell_ctx, KeyPerm::Use, &key, &None).is_ok()); - assert!(check_key_permission(0, &shell_ctx, KeyPerm::Delete, &key, &None).is_ok()); - assert!(check_key_permission(0, &shell_ctx, KeyPerm::GetInfo, &key, &None).is_ok()); - assert!(check_key_permission(0, &shell_ctx, KeyPerm::Rebind, &key, &None).is_ok()); - assert!(check_key_permission(0, &shell_ctx, KeyPerm::Update, &key, &None).is_ok()); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::Grant, &key, &None)); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ReqForcedOp, &key, &None)); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None)); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None)); - assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &key, &None)); - - // Also make sure that the permission fails if the caller is not the owner. - assert_perm_failed!(check_key_permission( - 1, // the owner is 0 - &system_server_ctx, - KeyPerm::Use, - &key, - &None - )); - // Unless there was a grant. - assert!(check_key_permission( - 1, - &system_server_ctx, - KeyPerm::Use, - &key, - &Some(key_perm_set![KeyPerm::Use]) - ) - .is_ok()); - // But fail if the grant did not cover the requested permission. - assert_perm_failed!(check_key_permission( - 1, - &system_server_ctx, - KeyPerm::Use, - &key, - &Some(key_perm_set![KeyPerm::GetInfo]) - )); - - Ok(()) - } - - #[test] - fn check_key_permission_domain_selinux() -> Result<()> { - let (sctx, namespace, is_su) = check_context()?; - let key = KeyDescriptor { - domain: Domain::SELINUX, - nspace: namespace as i64, - alias: None, - blob: None, - }; - - assert!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::Delete, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::GetInfo, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::Rebind, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::Update, &key, &None).is_ok()); - - if is_su { - assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok()); - assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok()); - } else { - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None)); - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None)); - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None)); - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None)); - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None)); - } - Ok(()) - } - - #[test] - fn check_key_permission_domain_blob() -> Result<()> { - let (sctx, namespace, is_su) = check_context()?; - let key = KeyDescriptor { - domain: Domain::BLOB, - nspace: namespace as i64, - alias: None, - blob: None, - }; - - if is_su { - check_key_permission(0, &sctx, KeyPerm::Use, &key, &None) - } else { - assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None)); - Ok(()) - } - } - - #[test] - fn check_key_permission_domain_key_id() -> Result<()> { - let key = KeyDescriptor { domain: Domain::KEY_ID, nspace: 0, alias: None, blob: None }; - - assert_eq!( - Some(&KsError::sys()), - check_key_permission( - 0, - &selinux::Context::new("ignored").unwrap(), - KeyPerm::Use, - &key, - &None - ) - .err() - .unwrap() - .root_cause() - .downcast_ref::<KsError>() - ); - Ok(()) - } - - #[test] - fn key_perm_set_all_test() { - let v = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::UseDevId, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - KeyPerm::Grant, - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use // Test if the macro accepts missing comma at the end of the list. - ]; - let mut i = v.into_iter(); - assert_eq!(i.next().unwrap().name(), "delete"); - assert_eq!(i.next().unwrap().name(), "gen_unique_id"); - assert_eq!(i.next().unwrap().name(), "get_info"); - assert_eq!(i.next().unwrap().name(), "grant"); - assert_eq!(i.next().unwrap().name(), "manage_blob"); - assert_eq!(i.next().unwrap().name(), "rebind"); - assert_eq!(i.next().unwrap().name(), "req_forced_op"); - assert_eq!(i.next().unwrap().name(), "update"); - assert_eq!(i.next().unwrap().name(), "use"); - assert_eq!(i.next().unwrap().name(), "use_dev_id"); - assert_eq!(None, i.next()); - } - #[test] - fn key_perm_set_sparse_test() { - let v = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - KeyPerm::Update, - KeyPerm::Use, // Test if macro accepts the comma at the end of the list. - ]; - let mut i = v.into_iter(); - assert_eq!(i.next().unwrap().name(), "gen_unique_id"); - assert_eq!(i.next().unwrap().name(), "manage_blob"); - assert_eq!(i.next().unwrap().name(), "req_forced_op"); - assert_eq!(i.next().unwrap().name(), "update"); - assert_eq!(i.next().unwrap().name(), "use"); - assert_eq!(None, i.next()); - } - #[test] - fn key_perm_set_empty_test() { - let v = key_perm_set![]; - let mut i = v.into_iter(); - assert_eq!(None, i.next()); - } - #[test] - fn key_perm_set_include_subset_test() { - let v1 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::UseDevId, - KeyPerm::ReqForcedOp, - KeyPerm::GenUniqueId, - KeyPerm::Grant, - KeyPerm::GetInfo, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - ]; - let v2 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - ]; - assert!(v1.includes(v2)); - assert!(!v2.includes(v1)); - } - #[test] - fn key_perm_set_include_equal_test() { - let v1 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - ]; - let v2 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - ]; - assert!(v1.includes(v2)); - assert!(v2.includes(v1)); - } - #[test] - fn key_perm_set_include_overlap_test() { - let v1 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::Grant, // only in v1 - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - ]; - let v2 = key_perm_set![ - KeyPerm::ManageBlob, - KeyPerm::Delete, - KeyPerm::ReqForcedOp, // only in v2 - KeyPerm::Rebind, - KeyPerm::Update, - KeyPerm::Use, - ]; - assert!(!v1.includes(v2)); - assert!(!v2.includes(v1)); - } - #[test] - fn key_perm_set_include_no_overlap_test() { - let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,]; - let v2 = - key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,]; - assert!(!v1.includes(v2)); - assert!(!v2.includes(v1)); - } -} diff --git a/keystore2/src/permission/tests.rs b/keystore2/src/permission/tests.rs new file mode 100644 index 00000000..f555c12c --- /dev/null +++ b/keystore2/src/permission/tests.rs @@ -0,0 +1,434 @@ +// Copyright 2020, 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. + +//! Access control tests. + +use super::*; +use crate::key_perm_set; +use anyhow::anyhow; +use anyhow::Result; +use keystore2_selinux::*; + +const ALL_PERMS: KeyPermSet = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::UseDevId, + KeyPerm::ReqForcedOp, + KeyPerm::GenUniqueId, + KeyPerm::Grant, + KeyPerm::GetInfo, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, + KeyPerm::ConvertStorageKeyToEphemeral, +]; + +const SYSTEM_SERVER_PERMISSIONS_NO_GRANT: KeyPermSet = key_perm_set![ + KeyPerm::Delete, + KeyPerm::UseDevId, + // No KeyPerm::Grant + KeyPerm::GetInfo, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, +]; + +const NOT_GRANT_PERMS: KeyPermSet = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::UseDevId, + KeyPerm::ReqForcedOp, + KeyPerm::GenUniqueId, + // No KeyPerm::Grant + KeyPerm::GetInfo, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, + KeyPerm::ConvertStorageKeyToEphemeral, +]; + +const UNPRIV_PERMS: KeyPermSet = key_perm_set![ + KeyPerm::Delete, + KeyPerm::GetInfo, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, +]; + +/// The su_key namespace as defined in su.te and keystore_key_contexts of the +/// SePolicy (system/sepolicy). +const SU_KEY_NAMESPACE: i32 = 0; +/// The shell_key namespace as defined in shell.te and keystore_key_contexts of the +/// SePolicy (system/sepolicy). +const SHELL_KEY_NAMESPACE: i32 = 1; + +pub fn test_getcon() -> Result<Context> { + Context::new("u:object_r:keystore:s0") +} + +// This macro evaluates the given expression and checks that +// a) evaluated to Result::Err() and that +// b) the wrapped error is selinux::Error::perm() (permission denied). +// We use a macro here because a function would mask which invocation caused the failure. +// +// TODO b/164121720 Replace this macro with a function when `track_caller` is available. +macro_rules! assert_perm_failed { + ($test_function:expr) => { + let result = $test_function; + assert!(result.is_err(), "Permission check should have failed."); + assert_eq!( + Some(&selinux::Error::perm()), + result.err().unwrap().root_cause().downcast_ref::<selinux::Error>() + ); + }; +} + +fn check_context() -> Result<(selinux::Context, i32, bool)> { + // Calling the non mocked selinux::getcon here intended. + let context = selinux::getcon()?; + match context.to_str().unwrap() { + "u:r:su:s0" => Ok((context, SU_KEY_NAMESPACE, true)), + "u:r:shell:s0" => Ok((context, SHELL_KEY_NAMESPACE, false)), + c => Err(anyhow!(format!( + "This test must be run as \"su\" or \"shell\". Current context: \"{}\"", + c + ))), + } +} + +#[test] +fn check_keystore_permission_test() -> Result<()> { + let system_server_ctx = Context::new("u:r:system_server:s0")?; + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::AddAuth).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearNs).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Lock).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Reset).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::Unlock).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangeUser).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ChangePassword).is_ok()); + assert!(check_keystore_permission(&system_server_ctx, KeystorePerm::ClearUID).is_ok()); + let shell_ctx = Context::new("u:r:shell:s0")?; + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::AddAuth)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearNs)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::List)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Lock)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Reset)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::Unlock)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangeUser)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ChangePassword)); + assert_perm_failed!(check_keystore_permission(&shell_ctx, KeystorePerm::ClearUID)); + Ok(()) +} + +#[test] +fn check_grant_permission_app() -> Result<()> { + let system_server_ctx = Context::new("u:r:system_server:s0")?; + let shell_ctx = Context::new("u:r:shell:s0")?; + let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None }; + check_grant_permission(&system_server_ctx, SYSTEM_SERVER_PERMISSIONS_NO_GRANT, &key) + .expect("Grant permission check failed."); + + // attempts to grant the grant permission must always fail even when privileged. + assert_perm_failed!(check_grant_permission(&system_server_ctx, KeyPerm::Grant.into(), &key)); + // unprivileged grant attempts always fail. shell does not have the grant permission. + assert_perm_failed!(check_grant_permission(&shell_ctx, UNPRIV_PERMS, &key)); + Ok(()) +} + +#[test] +fn check_grant_permission_selinux() -> Result<()> { + let (sctx, namespace, is_su) = check_context()?; + let key = KeyDescriptor { + domain: Domain::SELINUX, + nspace: namespace as i64, + alias: None, + blob: None, + }; + if is_su { + assert!(check_grant_permission(&sctx, NOT_GRANT_PERMS, &key).is_ok()); + // attempts to grant the grant permission must always fail even when privileged. + assert_perm_failed!(check_grant_permission(&sctx, KeyPerm::Grant.into(), &key)); + } else { + // unprivileged grant attempts always fail. shell does not have the grant permission. + assert_perm_failed!(check_grant_permission(&sctx, UNPRIV_PERMS, &key)); + } + Ok(()) +} + +#[test] +fn check_key_permission_domain_grant() -> Result<()> { + let key = KeyDescriptor { domain: Domain::GRANT, nspace: 0, alias: None, blob: None }; + + assert_perm_failed!(check_key_permission( + 0, + &selinux::Context::new("ignored").unwrap(), + KeyPerm::Grant, + &key, + &Some(UNPRIV_PERMS) + )); + + check_key_permission( + 0, + &selinux::Context::new("ignored").unwrap(), + KeyPerm::Use, + &key, + &Some(ALL_PERMS), + ) +} + +#[test] +fn check_key_permission_domain_app() -> Result<()> { + let system_server_ctx = Context::new("u:r:system_server:s0")?; + let shell_ctx = Context::new("u:r:shell:s0")?; + let gmscore_app = Context::new("u:r:gmscore_app:s0")?; + + let key = KeyDescriptor { domain: Domain::APP, nspace: 0, alias: None, blob: None }; + + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Use, &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Delete, &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::GetInfo, &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Rebind, &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Update, &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::Grant, &key, &None).is_ok()); + assert!(check_key_permission(0, &system_server_ctx, KeyPerm::UseDevId, &key, &None).is_ok()); + assert!(check_key_permission(0, &gmscore_app, KeyPerm::GenUniqueId, &key, &None).is_ok()); + + assert!(check_key_permission(0, &shell_ctx, KeyPerm::Use, &key, &None).is_ok()); + assert!(check_key_permission(0, &shell_ctx, KeyPerm::Delete, &key, &None).is_ok()); + assert!(check_key_permission(0, &shell_ctx, KeyPerm::GetInfo, &key, &None).is_ok()); + assert!(check_key_permission(0, &shell_ctx, KeyPerm::Rebind, &key, &None).is_ok()); + assert!(check_key_permission(0, &shell_ctx, KeyPerm::Update, &key, &None).is_ok()); + assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::Grant, &key, &None)); + assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ReqForcedOp, &key, &None)); + assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::ManageBlob, &key, &None)); + assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::UseDevId, &key, &None)); + assert_perm_failed!(check_key_permission(0, &shell_ctx, KeyPerm::GenUniqueId, &key, &None)); + + // Also make sure that the permission fails if the caller is not the owner. + assert_perm_failed!(check_key_permission( + 1, // the owner is 0 + &system_server_ctx, + KeyPerm::Use, + &key, + &None + )); + // Unless there was a grant. + assert!(check_key_permission( + 1, + &system_server_ctx, + KeyPerm::Use, + &key, + &Some(key_perm_set![KeyPerm::Use]) + ) + .is_ok()); + // But fail if the grant did not cover the requested permission. + assert_perm_failed!(check_key_permission( + 1, + &system_server_ctx, + KeyPerm::Use, + &key, + &Some(key_perm_set![KeyPerm::GetInfo]) + )); + + Ok(()) +} + +#[test] +fn check_key_permission_domain_selinux() -> Result<()> { + let (sctx, namespace, is_su) = check_context()?; + let key = KeyDescriptor { + domain: Domain::SELINUX, + nspace: namespace as i64, + alias: None, + blob: None, + }; + + assert!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::Delete, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::GetInfo, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::Rebind, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::Update, &key, &None).is_ok()); + + if is_su { + assert!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None).is_ok()); + assert!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None).is_ok()); + } else { + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Grant, &key, &None)); + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ReqForcedOp, &key, &None)); + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::ManageBlob, &key, &None)); + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::UseDevId, &key, &None)); + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::GenUniqueId, &key, &None)); + } + Ok(()) +} + +#[test] +fn check_key_permission_domain_blob() -> Result<()> { + let (sctx, namespace, is_su) = check_context()?; + let key = + KeyDescriptor { domain: Domain::BLOB, nspace: namespace as i64, alias: None, blob: None }; + + if is_su { + check_key_permission(0, &sctx, KeyPerm::Use, &key, &None) + } else { + assert_perm_failed!(check_key_permission(0, &sctx, KeyPerm::Use, &key, &None)); + Ok(()) + } +} + +#[test] +fn check_key_permission_domain_key_id() -> Result<()> { + let key = KeyDescriptor { domain: Domain::KEY_ID, nspace: 0, alias: None, blob: None }; + + assert_eq!( + Some(&KsError::sys()), + check_key_permission( + 0, + &selinux::Context::new("ignored").unwrap(), + KeyPerm::Use, + &key, + &None + ) + .err() + .unwrap() + .root_cause() + .downcast_ref::<KsError>() + ); + Ok(()) +} + +#[test] +fn key_perm_set_all_test() { + let v = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::UseDevId, + KeyPerm::ReqForcedOp, + KeyPerm::GenUniqueId, + KeyPerm::Grant, + KeyPerm::GetInfo, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use // Test if the macro accepts missing comma at the end of the list. + ]; + let mut i = v.into_iter(); + assert_eq!(i.next().unwrap().name(), "delete"); + assert_eq!(i.next().unwrap().name(), "gen_unique_id"); + assert_eq!(i.next().unwrap().name(), "get_info"); + assert_eq!(i.next().unwrap().name(), "grant"); + assert_eq!(i.next().unwrap().name(), "manage_blob"); + assert_eq!(i.next().unwrap().name(), "rebind"); + assert_eq!(i.next().unwrap().name(), "req_forced_op"); + assert_eq!(i.next().unwrap().name(), "update"); + assert_eq!(i.next().unwrap().name(), "use"); + assert_eq!(i.next().unwrap().name(), "use_dev_id"); + assert_eq!(None, i.next()); +} +#[test] +fn key_perm_set_sparse_test() { + let v = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::ReqForcedOp, + KeyPerm::GenUniqueId, + KeyPerm::Update, + KeyPerm::Use, // Test if macro accepts the comma at the end of the list. + ]; + let mut i = v.into_iter(); + assert_eq!(i.next().unwrap().name(), "gen_unique_id"); + assert_eq!(i.next().unwrap().name(), "manage_blob"); + assert_eq!(i.next().unwrap().name(), "req_forced_op"); + assert_eq!(i.next().unwrap().name(), "update"); + assert_eq!(i.next().unwrap().name(), "use"); + assert_eq!(None, i.next()); +} +#[test] +fn key_perm_set_empty_test() { + let v = key_perm_set![]; + let mut i = v.into_iter(); + assert_eq!(None, i.next()); +} +#[test] +fn key_perm_set_include_subset_test() { + let v1 = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::UseDevId, + KeyPerm::ReqForcedOp, + KeyPerm::GenUniqueId, + KeyPerm::Grant, + KeyPerm::GetInfo, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, + ]; + let v2 = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, + ]; + assert!(v1.includes(v2)); + assert!(!v2.includes(v1)); +} +#[test] +fn key_perm_set_include_equal_test() { + let v1 = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, + ]; + let v2 = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, + ]; + assert!(v1.includes(v2)); + assert!(v2.includes(v1)); +} +#[test] +fn key_perm_set_include_overlap_test() { + let v1 = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::Grant, // only in v1 + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, + ]; + let v2 = key_perm_set![ + KeyPerm::ManageBlob, + KeyPerm::Delete, + KeyPerm::ReqForcedOp, // only in v2 + KeyPerm::Rebind, + KeyPerm::Update, + KeyPerm::Use, + ]; + assert!(!v1.includes(v2)); + assert!(!v2.includes(v1)); +} +#[test] +fn key_perm_set_include_no_overlap_test() { + let v1 = key_perm_set![KeyPerm::ManageBlob, KeyPerm::Delete, KeyPerm::Grant,]; + let v2 = key_perm_set![KeyPerm::ReqForcedOp, KeyPerm::Rebind, KeyPerm::Update, KeyPerm::Use,]; + assert!(!v1.includes(v2)); + assert!(!v2.includes(v1)); +} diff --git a/keystore2/src/raw_device.rs b/keystore2/src/raw_device.rs index 44d805c3..bf1149c1 100644 --- a/keystore2/src/raw_device.rs +++ b/keystore2/src/raw_device.rs @@ -211,13 +211,10 @@ impl KeyMintDevice { KeyBlob::NonSensitive(key_blob_vec), |key_blob| { map_km_error({ - let _wp = wd::watch_millis( - concat!( - "In KeyMintDevice::lookup_or_generate_key: ", - "calling getKeyCharacteristics." - ), - 500, - ); + let _wp = wd::watch(concat!( + "KeyMintDevice::lookup_or_generate_key: ", + "calling IKeyMintDevice::getKeyCharacteristics." + )); self.km_dev.getKeyCharacteristics(key_blob, &[], &[]) }) }, @@ -308,7 +305,9 @@ impl KeyMintDevice { let (begin_result, _) = self .upgrade_keyblob_if_required_with(db, key_id_guard, key_blob, |blob| { map_km_error({ - let _wp = wd::watch_millis("In use_key_in_one_step: calling: begin", 500); + let _wp = wd::watch( + "KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::begin", + ); self.km_dev.begin(purpose, blob, operation_parameters, auth_token) }) }) @@ -316,7 +315,8 @@ impl KeyMintDevice { let operation: Strong<dyn IKeyMintOperation> = begin_result.operation.ok_or_else(Error::sys).context(ks_err!("Operation missing"))?; map_km_error({ - let _wp = wd::watch_millis("In use_key_in_one_step: calling: finish", 500); + let _wp = + wd::watch("KeyMintDevice::use_key_in_one_step: calling IKeyMintDevice::finish"); operation.finish(Some(input), None, None, None, None) }) .context(ks_err!("Failed to finish operation.")) diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs index 0ef8c953..cda93b3f 100644 --- a/keystore2/src/remote_provisioning.rs +++ b/keystore2/src/remote_provisioning.rs @@ -31,7 +31,6 @@ use android_system_keystore2::aidl::android::system::keystore2::{ use anyhow::{Context, Result}; use keystore2_crypto::parse_subject_from_certificate; -use crate::database::Uuid; use crate::error::wrapped_rkpd_error_to_ks_error; use crate::globals::get_remotely_provisioned_component_name; use crate::ks_err; @@ -44,18 +43,12 @@ use android_security_metrics::aidl::android::security::metrics::RkpError::RkpErr #[derive(Default)] pub struct RemProvState { security_level: SecurityLevel, - km_uuid: Uuid, } impl RemProvState { /// Creates a RemProvState struct. - pub fn new(security_level: SecurityLevel, km_uuid: Uuid) -> Self { - Self { security_level, km_uuid } - } - - /// Returns the uuid for the KM instance attached to this RemProvState struct. - pub fn get_uuid(&self) -> Uuid { - self.km_uuid + pub fn new(security_level: SecurityLevel) -> Self { + Self { security_level } } fn is_rkp_only(&self) -> bool { @@ -136,6 +129,6 @@ fn get_rkpd_attestation_key( // by the calling function and allow for natural fallback to the factory key. let rpc_name = get_remotely_provisioned_component_name(security_level) .context(ks_err!("Trying to get IRPC name."))?; - let _wd = wd::watch_millis("Calling get_rkpd_attestation_key()", 500); + let _wd = wd::watch("Calling get_rkpd_attestation_key()"); rkpd_client::get_rkpd_attestation_key(&rpc_name, caller_uid) } diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs index 6fb0eb20..bd20afb7 100644 --- a/keystore2/src/security_level.rs +++ b/keystore2/src/security_level.rs @@ -20,7 +20,7 @@ use crate::audit_log::{ }; use crate::database::{BlobInfo, CertificateInfo, KeyIdGuard}; use crate::error::{ - self, map_km_error, map_or_log_err, wrapped_rkpd_error_to_ks_error, Error, ErrorCode, + self, into_logged_binder, map_km_error, wrapped_rkpd_error_to_ks_error, Error, ErrorCode, }; use crate::globals::{ get_remotely_provisioned_component_name, DB, ENFORCEMENTS, LEGACY_IMPORTER, SUPER_KEY, @@ -34,7 +34,8 @@ use crate::super_key::{KeyBlob, SuperKeyManager}; use crate::utils::{ check_device_attestation_permissions, check_key_permission, check_unique_id_attestation_permissions, is_device_id_attestation_tag, - key_characteristics_to_internal, uid_to_android_user, watchdog as wd, UNDEFINED_NOT_AFTER, + key_characteristics_to_internal, log_security_safe_params, uid_to_android_user, watchdog as wd, + UNDEFINED_NOT_AFTER, }; use crate::{ database::{ @@ -99,7 +100,7 @@ impl KeystoreSecurityLevel { hw_info, km_uuid, operation_db: OperationDb::new(), - rem_prov_state: RemProvState::new(security_level, km_uuid), + rem_prov_state: RemProvState::new(security_level), id_rotation_state, }, BinderFeatures { set_requesting_sid: true, ..BinderFeatures::default() }, @@ -109,7 +110,12 @@ impl KeystoreSecurityLevel { fn watch_millis(&self, id: &'static str, millis: u64) -> Option<wd::WatchPoint> { let sec_level = self.security_level; - wd::watch_millis_with(id, millis, move || format!("SecurityLevel {:?}", sec_level)) + wd::watch_millis_with(id, millis, sec_level) + } + + fn watch(&self, id: &'static str) -> Option<wd::WatchPoint> { + let sec_level = self.security_level; + wd::watch_millis_with(id, wd::DEFAULT_TIMEOUT_MS, sec_level) } fn store_new_key( @@ -323,9 +329,8 @@ impl KeystoreSecurityLevel { operation_parameters, |blob| loop { match map_km_error({ - let _wp = self.watch_millis( - "In KeystoreSecurityLevel::create_operation: calling begin", - 500, + let _wp = self.watch( + "KeystoreSecurityLevel::create_operation: calling IKeyMintDevice::begin", ); self.keymint.begin( purpose, @@ -439,19 +444,23 @@ impl KeystoreSecurityLevel { // If there is an attestation challenge we need to get an application id. if params.iter().any(|kp| kp.tag == Tag::ATTESTATION_CHALLENGE) { - let aaid = { - let _wp = self.watch_millis( - "In KeystoreSecurityLevel::add_required_parameters calling: get_aaid", - 500, - ); - keystore2_aaid::get_aaid(uid) - .map_err(|e| anyhow!(ks_err!("get_aaid returned status {}.", e))) - }?; - - result.push(KeyParameter { - tag: Tag::ATTESTATION_APPLICATION_ID, - value: KeyParameterValue::Blob(aaid), - }); + let _wp = + self.watch(" KeystoreSecurityLevel::add_required_parameters: calling get_aaid"); + match keystore2_aaid::get_aaid(uid) { + Ok(aaid_ok) => { + result.push(KeyParameter { + tag: Tag::ATTESTATION_APPLICATION_ID, + value: KeyParameterValue::Blob(aaid_ok), + }); + } + Err(e) if e == ResponseCode::GET_ATTESTATION_APPLICATION_ID_FAILED.0 as u32 => { + return Err(Error::Rc(ResponseCode::GET_ATTESTATION_APPLICATION_ID_FAILED)) + .context(ks_err!("Attestation ID retrieval failed.")); + } + Err(e) => { + return Err(anyhow!(e)).context(ks_err!("Attestation ID retrieval error.")) + } + } } if params.iter().any(|kp| kp.tag == Tag::INCLUDE_UNIQUE_ID) { @@ -573,8 +582,8 @@ impl KeystoreSecurityLevel { map_km_error({ let _wp = self.watch_millis( concat!( - "In KeystoreSecurityLevel::generate_key (UserGenerated): ", - "calling generate_key." + "KeystoreSecurityLevel::generate_key (UserGenerated): ", + "calling IKeyMintDevice::generate_key" ), 5000, // Generate can take a little longer. ); @@ -582,15 +591,19 @@ impl KeystoreSecurityLevel { }) }, ) - .context(ks_err!("Using user generated attestation key.")) + .context(ks_err!( + "While generating with a user-generated \ + attestation key, params: {:?}.", + log_security_safe_params(¶ms) + )) .map(|(result, _)| result), Some(AttestationKeyInfo::RkpdProvisioned { attestation_key, attestation_certs }) => { self.upgrade_rkpd_keyblob_if_required_with(&attestation_key.keyBlob, &[], |blob| { map_km_error({ let _wp = self.watch_millis( concat!( - "In KeystoreSecurityLevel::generate_key (RkpdProvisioned): ", - "calling generate_key.", + "KeystoreSecurityLevel::generate_key (RkpdProvisioned): ", + "calling IKeyMintDevice::generate_key", ), 5000, // Generate can take a little longer. ); @@ -602,7 +615,12 @@ impl KeystoreSecurityLevel { self.keymint.generateKey(¶ms, dynamic_attest_key.as_ref()) }) }) - .context(ks_err!("While generating Key with remote provisioned attestation key.")) + .context(ks_err!( + "While generating Key {:?} with remote \ + provisioned attestation key and params: {:?}.", + key.alias, + log_security_safe_params(¶ms) + )) .map(|(mut result, _)| { result.certificateChain.push(attestation_certs); result @@ -611,14 +629,18 @@ impl KeystoreSecurityLevel { None => map_km_error({ let _wp = self.watch_millis( concat!( - "In KeystoreSecurityLevel::generate_key (No attestation): ", - "calling generate_key.", + "KeystoreSecurityLevel::generate_key (No attestation key): ", + "calling IKeyMintDevice::generate_key", ), 5000, // Generate can take a little longer. ); self.keymint.generateKey(¶ms, None) }) - .context(ks_err!("While generating Key without explicit attestation key.")), + .context(ks_err!( + "While generating without a provided \ + attestation key and params: {:?}.", + log_security_safe_params(¶ms) + )), } .context(ks_err!())?; @@ -676,7 +698,7 @@ impl KeystoreSecurityLevel { let km_dev = &self.keymint; let creation_result = map_km_error({ let _wp = - self.watch_millis("In KeystoreSecurityLevel::import_key: calling importKey.", 500); + self.watch("KeystoreSecurityLevel::import_key: calling IKeyMintDevice::importKey."); km_dev.importKey(¶ms, format, key_data, None /* attestKey */) }) .context(ks_err!("Trying to call importKey"))?; @@ -789,9 +811,8 @@ impl KeystoreSecurityLevel { wrapping_blob_metadata.km_uuid().copied(), &[], |wrapping_blob| { - let _wp = self.watch_millis( - "In KeystoreSecurityLevel::import_wrapped_key: calling importWrappedKey.", - 500, + let _wp = self.watch( + "KeystoreSecurityLevel::import_wrapped_key: calling IKeyMintDevice::importWrappedKey.", ); let creation_result = map_km_error(self.keymint.importWrappedKey( wrapped_data, @@ -865,7 +886,7 @@ impl KeystoreSecurityLevel { } }, ) - .context(ks_err!())?; + .context(ks_err!("upgrade_keyblob_if_required_with(key_id={:?})", key_id_guard))?; // If no upgrade was needed, use the opportunity to reencrypt the blob if required // and if the a key_id_guard is held. Note: key_id_guard can only be Some if no @@ -897,7 +918,7 @@ impl KeystoreSecurityLevel { params, f, |upgraded_blob| { - let _wp = wd::watch_millis("Calling store_rkpd_attestation_key()", 500); + let _wp = wd::watch("Calling store_rkpd_attestation_key()"); if let Err(e) = store_rkpd_attestation_key(&rpc_name, key_blob, upgraded_blob) { Err(wrapped_rkpd_error_to_ks_error(&e)).context(format!("{e:?}")) } else { @@ -905,7 +926,10 @@ impl KeystoreSecurityLevel { } }, ) - .context(ks_err!()) + .context(ks_err!( + "upgrade_rkpd_keyblob_if_required_with(params={:?})", + log_security_safe_params(params) + )) } fn convert_storage_key_to_ephemeral( @@ -927,38 +951,31 @@ impl KeystoreSecurityLevel { .context(ks_err!("Check permission"))?; let km_dev = &self.keymint; - match { - let _wp = self.watch_millis( - concat!( - "In IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ", - "calling convertStorageKeyToEphemeral (1)" - ), - 500, - ); + let res = { + let _wp = self.watch(concat!( + "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ", + "calling IKeyMintDevice::convertStorageKeyToEphemeral (1)" + )); map_km_error(km_dev.convertStorageKeyToEphemeral(key_blob)) - } { + }; + match res { Ok(result) => { Ok(EphemeralStorageKeyResponse { ephemeralKey: result, upgradedBlob: None }) } Err(error::Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => { let upgraded_blob = { - let _wp = self.watch_millis( - "In convert_storage_key_to_ephemeral: calling upgradeKey", - 500, - ); + let _wp = self.watch("IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: calling IKeyMintDevice::upgradeKey"); map_km_error(km_dev.upgradeKey(key_blob, &[])) } .context(ks_err!("Failed to upgrade key blob."))?; let ephemeral_key = { - let _wp = self.watch_millis( - "In convert_storage_key_to_ephemeral: calling convertStorageKeyToEphemeral (2)", - 500, - ); + let _wp = self.watch(concat!( + "IKeystoreSecurityLevel::convert_storage_key_to_ephemeral: ", + "calling IKeyMintDevice::convertStorageKeyToEphemeral (2)" + )); map_km_error(km_dev.convertStorageKeyToEphemeral(&upgraded_blob)) } - .context(ks_err!( - "Failed to retrieve ephemeral key (after upgrade)." - ))?; + .context(ks_err!("Failed to retrieve ephemeral key (after upgrade)."))?; Ok(EphemeralStorageKeyResponse { ephemeralKey: ephemeral_key, upgradedBlob: Some(upgraded_blob), @@ -986,7 +1003,7 @@ impl KeystoreSecurityLevel { let km_dev = &self.keymint; { let _wp = - self.watch_millis("In KeystoreSecuritylevel::delete_key: calling deleteKey", 500); + self.watch("KeystoreSecuritylevel::delete_key: calling IKeyMintDevice::deleteKey"); map_km_error(km_dev.deleteKey(key_blob)).context(ks_err!("keymint device deleteKey")) } } @@ -1001,8 +1018,8 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { operation_parameters: &[KeyParameter], forced: bool, ) -> binder::Result<CreateOperationResponse> { - let _wp = self.watch_millis("IKeystoreSecurityLevel::createOperation", 500); - map_or_log_err(self.create_operation(key, operation_parameters, forced), Ok) + let _wp = self.watch("IKeystoreSecurityLevel::createOperation"); + self.create_operation(key, operation_parameters, forced).map_err(into_logged_binder) } fn generateKey( &self, @@ -1018,7 +1035,7 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { let result = self.generate_key(key, attestation_key, params, flags, entropy); log_key_creation_event_stats(self.security_level, params, &result); log_key_generated(key, ThreadState::get_calling_uid(), result.is_ok()); - map_or_log_err(result, Ok) + result.map_err(into_logged_binder) } fn importKey( &self, @@ -1028,11 +1045,11 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { flags: i32, key_data: &[u8], ) -> binder::Result<KeyMetadata> { - let _wp = self.watch_millis("IKeystoreSecurityLevel::importKey", 500); + let _wp = self.watch("IKeystoreSecurityLevel::importKey"); let result = self.import_key(key, attestation_key, params, flags, key_data); log_key_creation_event_stats(self.security_level, params, &result); log_key_imported(key, ThreadState::get_calling_uid(), result.is_ok()); - map_or_log_err(result, Ok) + result.map_err(into_logged_binder) } fn importWrappedKey( &self, @@ -1042,25 +1059,25 @@ impl IKeystoreSecurityLevel for KeystoreSecurityLevel { params: &[KeyParameter], authenticators: &[AuthenticatorSpec], ) -> binder::Result<KeyMetadata> { - let _wp = self.watch_millis("IKeystoreSecurityLevel::importWrappedKey", 500); + let _wp = self.watch("IKeystoreSecurityLevel::importWrappedKey"); let result = self.import_wrapped_key(key, wrapping_key, masking_key, params, authenticators); log_key_creation_event_stats(self.security_level, params, &result); log_key_imported(key, ThreadState::get_calling_uid(), result.is_ok()); - map_or_log_err(result, Ok) + result.map_err(into_logged_binder) } fn convertStorageKeyToEphemeral( &self, storage_key: &KeyDescriptor, ) -> binder::Result<EphemeralStorageKeyResponse> { - let _wp = self.watch_millis("IKeystoreSecurityLevel::convertStorageKeyToEphemeral", 500); - map_or_log_err(self.convert_storage_key_to_ephemeral(storage_key), Ok) + let _wp = self.watch("IKeystoreSecurityLevel::convertStorageKeyToEphemeral"); + self.convert_storage_key_to_ephemeral(storage_key).map_err(into_logged_binder) } fn deleteKey(&self, key: &KeyDescriptor) -> binder::Result<()> { - let _wp = self.watch_millis("IKeystoreSecurityLevel::deleteKey", 500); + let _wp = self.watch("IKeystoreSecurityLevel::deleteKey"); let result = self.delete_key(key); log_key_deleted(key, ThreadState::get_calling_uid(), result.is_ok()); - map_or_log_err(result, Ok) + result.map_err(into_logged_binder) } } @@ -1129,7 +1146,7 @@ mod tests { |new_blob| { // This handler is only executed if a key upgrade was performed. key_upgraded = true; - let _wp = wd::watch_millis("Calling store_rkpd_attestation_key()", 500); + let _wp = wd::watch("Calling store_rkpd_attestation_key()"); store_rkpd_attestation_key(&rpc_name, &key.keyBlob, new_blob).unwrap(); Ok(()) }, diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs index 1459254e..95e17445 100644 --- a/keystore2/src/service.rs +++ b/keystore2/src/service.rs @@ -35,7 +35,7 @@ use crate::{ error::ResponseCode, }; use crate::{ - error::{self, map_or_log_err, ErrorCode}, + error::{self, into_logged_binder, ErrorCode}, id_rotation::IdRotationState, }; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel; @@ -62,11 +62,18 @@ impl KeystoreService { id_rotation_state: IdRotationState, ) -> Result<Strong<dyn IKeystoreService>> { let mut result: Self = Default::default(); - let (dev, uuid) = KeystoreSecurityLevel::new_native_binder( + let (dev, uuid) = match KeystoreSecurityLevel::new_native_binder( SecurityLevel::TRUSTED_ENVIRONMENT, id_rotation_state.clone(), - ) - .context(ks_err!("Trying to construct mandatory security level TEE."))?; + ) { + Ok(v) => v, + Err(e) => { + log::error!("Failed to construct mandatory security level TEE: {e:?}"); + log::error!("Does the device have a /default Keymaster or KeyMint instance?"); + return Err(e.context(ks_err!("Trying to construct mandatory security level TEE"))); + } + }; + result.i_sec_level_by_uuid.insert(uuid, dev); result.uuid_by_sec_level.insert(SecurityLevel::TRUSTED_ENVIRONMENT, uuid); @@ -381,14 +388,12 @@ impl IKeystoreService for KeystoreService { &self, security_level: SecurityLevel, ) -> binder::Result<Strong<dyn IKeystoreSecurityLevel>> { - let _wp = wd::watch_millis_with("IKeystoreService::getSecurityLevel", 500, move || { - format!("security_level: {}", security_level.0) - }); - map_or_log_err(self.get_security_level(security_level), Ok) + let _wp = wd::watch_millis_with("IKeystoreService::getSecurityLevel", 500, security_level); + self.get_security_level(security_level).map_err(into_logged_binder) } fn getKeyEntry(&self, key: &KeyDescriptor) -> binder::Result<KeyEntryResponse> { - let _wp = wd::watch_millis("IKeystoreService::get_key_entry", 500); - map_or_log_err(self.get_key_entry(key), Ok) + let _wp = wd::watch("IKeystoreService::get_key_entry"); + self.get_key_entry(key).map_err(into_logged_binder) } fn updateSubcomponent( &self, @@ -396,18 +401,18 @@ impl IKeystoreService for KeystoreService { public_cert: Option<&[u8]>, certificate_chain: Option<&[u8]>, ) -> binder::Result<()> { - let _wp = wd::watch_millis("IKeystoreService::updateSubcomponent", 500); - map_or_log_err(self.update_subcomponent(key, public_cert, certificate_chain), Ok) + let _wp = wd::watch("IKeystoreService::updateSubcomponent"); + self.update_subcomponent(key, public_cert, certificate_chain).map_err(into_logged_binder) } fn listEntries(&self, domain: Domain, namespace: i64) -> binder::Result<Vec<KeyDescriptor>> { - let _wp = wd::watch_millis("IKeystoreService::listEntries", 500); - map_or_log_err(self.list_entries(domain, namespace), Ok) + let _wp = wd::watch("IKeystoreService::listEntries"); + self.list_entries(domain, namespace).map_err(into_logged_binder) } fn deleteKey(&self, key: &KeyDescriptor) -> binder::Result<()> { - let _wp = wd::watch_millis("IKeystoreService::deleteKey", 500); + let _wp = wd::watch("IKeystoreService::deleteKey"); let result = self.delete_key(key); log_key_deleted(key, ThreadState::get_calling_uid(), result.is_ok()); - map_or_log_err(result, Ok) + result.map_err(into_logged_binder) } fn grant( &self, @@ -415,12 +420,12 @@ impl IKeystoreService for KeystoreService { grantee_uid: i32, access_vector: i32, ) -> binder::Result<KeyDescriptor> { - let _wp = wd::watch_millis("IKeystoreService::grant", 500); - map_or_log_err(self.grant(key, grantee_uid, access_vector.into()), Ok) + let _wp = wd::watch("IKeystoreService::grant"); + self.grant(key, grantee_uid, access_vector.into()).map_err(into_logged_binder) } fn ungrant(&self, key: &KeyDescriptor, grantee_uid: i32) -> binder::Result<()> { - let _wp = wd::watch_millis("IKeystoreService::ungrant", 500); - map_or_log_err(self.ungrant(key, grantee_uid), Ok) + let _wp = wd::watch("IKeystoreService::ungrant"); + self.ungrant(key, grantee_uid).map_err(into_logged_binder) } fn listEntriesBatched( &self, @@ -428,12 +433,12 @@ impl IKeystoreService for KeystoreService { namespace: i64, start_past_alias: Option<&str>, ) -> binder::Result<Vec<KeyDescriptor>> { - let _wp = wd::watch_millis("IKeystoreService::listEntriesBatched", 500); - map_or_log_err(self.list_entries_batched(domain, namespace, start_past_alias), Ok) + let _wp = wd::watch("IKeystoreService::listEntriesBatched"); + self.list_entries_batched(domain, namespace, start_past_alias).map_err(into_logged_binder) } fn getNumberOfEntries(&self, domain: Domain, namespace: i64) -> binder::Result<i32> { - let _wp = wd::watch_millis("IKeystoreService::getNumberOfEntries", 500); - map_or_log_err(self.count_num_entries(domain, namespace), Ok) + let _wp = wd::watch("IKeystoreService::getNumberOfEntries"); + self.count_num_entries(domain, namespace).map_err(into_logged_binder) } } diff --git a/keystore2/src/super_key.rs b/keystore2/src/super_key.rs index 44ce9abf..42fd7645 100644 --- a/keystore2/src/super_key.rs +++ b/keystore2/src/super_key.rs @@ -52,6 +52,9 @@ use std::{ }; use std::{convert::TryFrom, ops::Deref}; +#[cfg(test)] +mod tests; + const MAX_MAX_BOOT_LEVEL: usize = 1_000_000_000; /// Allow up to 15 seconds between the user unlocking using a biometric, and the auth /// token being used to unlock in [`SuperKeyManager::try_unlock_user_with_biometric`]. @@ -576,13 +579,9 @@ impl SuperKeyManager { pw: &Password, ) -> Result<(Vec<u8>, BlobMetaData)> { let salt = generate_salt().context("In encrypt_with_password: Failed to generate salt.")?; - let derived_key = if android_security_flags::fix_unlocked_device_required_keys_v2() { - pw.derive_key_hkdf(&salt, AES_256_KEY_LENGTH) - .context(ks_err!("Failed to derive key from password."))? - } else { - pw.derive_key_pbkdf2(&salt, AES_256_KEY_LENGTH) - .context(ks_err!("Failed to derive password."))? - }; + let derived_key = pw + .derive_key_hkdf(&salt, AES_256_KEY_LENGTH) + .context(ks_err!("Failed to derive key from password."))?; let mut metadata = BlobMetaData::new(); metadata.add(BlobMetaEntry::EncryptedBy(EncryptedBy::Password)); metadata.add(BlobMetaEntry::Salt(salt)); @@ -879,9 +878,7 @@ impl SuperKeyManager { ) { let entry = self.data.user_keys.entry(user_id).or_default(); if unlocking_sids.is_empty() { - if android_security_flags::fix_unlocked_device_required_keys_v2() { - entry.biometric_unlock = None; - } + entry.biometric_unlock = None; } else if let (Some(aes), Some(ecdh)) = ( entry.unlocked_device_required_symmetric.as_ref().cloned(), entry.unlocked_device_required_private.as_ref().cloned(), @@ -919,10 +916,8 @@ impl SuperKeyManager { &key_desc, KeyType::Client, /* TODO Should be Super b/189470584 */ |dev| { - let _wp = wd::watch_millis( - "In lock_unlocked_device_required_keys: calling importKey.", - 500, - ); + let _wp = + wd::watch("SKM::lock_unlocked_device_required_keys: calling IKeyMintDevice::importKey."); dev.importKey(key_params.as_slice(), KeyFormat::RAW, &encrypting_key, None) }, )?; @@ -986,8 +981,7 @@ impl SuperKeyManager { user_id: UserId, ) -> Result<()> { let entry = self.data.user_keys.entry(user_id).or_default(); - if android_security_flags::fix_unlocked_device_required_keys_v2() - && entry.unlocked_device_required_symmetric.is_some() + if entry.unlocked_device_required_symmetric.is_some() && entry.unlocked_device_required_private.is_some() { // If the keys are already cached in plaintext, then there is no need to decrypt the @@ -1011,7 +1005,7 @@ impl SuperKeyManager { let mut errs = vec![]; for sid in &biometric.sids { let sid = *sid; - if let Some((auth_token_entry, _)) = db.find_auth_token_entry(|entry| { + if let Some(auth_token_entry) = db.find_auth_token_entry(|entry| { entry.auth_token().userId == sid || entry.auth_token().authenticatorId == sid }) { let res: Result<(Arc<SuperKey>, Arc<SuperKey>)> = (|| { @@ -1098,92 +1092,13 @@ impl SuperKeyManager { legacy_importer .bulk_delete_user(user_id, false) .context(ks_err!("Trying to delete legacy keys."))?; - db.unbind_keys_for_user(user_id, false).context(ks_err!("Error in unbinding keys."))?; + db.unbind_keys_for_user(user_id).context(ks_err!("Error in unbinding keys."))?; // Delete super key in cache, if exists. self.forget_all_keys_for_user(user_id); Ok(()) } - /// Deletes all authentication bound keys and super keys for the given user. The user must be - /// unlocked before this function is called. This function is used to transition a user to - /// swipe. - pub fn reset_user( - &mut self, - db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, - user_id: UserId, - ) -> Result<()> { - log::info!("reset_user(user={user_id})"); - match self.get_user_state(db, legacy_importer, user_id)? { - UserState::Uninitialized => { - Err(Error::sys()).context(ks_err!("Tried to reset an uninitialized user!")) - } - UserState::BeforeFirstUnlock => { - Err(Error::sys()).context(ks_err!("Tried to reset a locked user's password!")) - } - UserState::AfterFirstUnlock(_) => { - // Mark keys created on behalf of the user as unreferenced. - legacy_importer - .bulk_delete_user(user_id, true) - .context(ks_err!("Trying to delete legacy keys."))?; - db.unbind_keys_for_user(user_id, true) - .context(ks_err!("Error in unbinding keys."))?; - - // Delete super key in cache, if exists. - self.forget_all_keys_for_user(user_id); - Ok(()) - } - } - } - - /// If the user hasn't been initialized yet, then this function generates the user's - /// AfterFirstUnlock super key and sets the user's state to AfterFirstUnlock. Otherwise this - /// function returns an error. - pub fn init_user( - &mut self, - db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, - user_id: UserId, - password: &Password, - ) -> Result<()> { - log::info!("init_user(user={user_id})"); - match self.get_user_state(db, legacy_importer, user_id)? { - UserState::AfterFirstUnlock(_) | UserState::BeforeFirstUnlock => { - Err(Error::sys()).context(ks_err!("Tried to re-init an initialized user!")) - } - UserState::Uninitialized => { - // Generate a new super key. - let super_key = - generate_aes256_key().context(ks_err!("Failed to generate AES 256 key."))?; - // Derive an AES256 key from the password and re-encrypt the super key - // before we insert it in the database. - let (encrypted_super_key, blob_metadata) = - Self::encrypt_with_password(&super_key, password) - .context(ks_err!("Failed to encrypt super key with password!"))?; - - let key_entry = db - .store_super_key( - user_id, - &USER_AFTER_FIRST_UNLOCK_SUPER_KEY, - &encrypted_super_key, - &blob_metadata, - &KeyMetaData::new(), - ) - .context(ks_err!("Failed to store super key."))?; - - self.populate_cache_from_super_key_blob( - user_id, - USER_AFTER_FIRST_UNLOCK_SUPER_KEY.algorithm, - key_entry, - password, - ) - .context(ks_err!("Failed to initialize user!"))?; - Ok(()) - } - } - } - /// Initializes the given user by creating their super keys, both AfterFirstUnlock and /// UnlockedDeviceRequired. If allow_existing is true, then the user already being initialized /// is not considered an error. @@ -1325,390 +1240,3 @@ impl<'a> Deref for KeyBlob<'a> { } } } - -#[cfg(test)] -mod tests { - use super::*; - use crate::database::tests::make_bootlevel_key_entry; - use crate::database::tests::make_test_key_entry; - use crate::database::tests::new_test_db; - use rand::prelude::*; - const USER_ID: u32 = 0; - const TEST_KEY_ALIAS: &str = "TEST_KEY"; - const TEST_BOOT_KEY_ALIAS: &str = "TEST_BOOT_KEY"; - - pub fn generate_password_blob() -> Password<'static> { - let mut rng = rand::thread_rng(); - let mut password = vec![0u8; 64]; - rng.fill_bytes(&mut password); - - let mut zvec = ZVec::new(64).expect("Failed to create ZVec"); - zvec[..].copy_from_slice(&password[..]); - - Password::Owned(zvec) - } - - fn setup_test(pw: &Password) -> (Arc<RwLock<SuperKeyManager>>, KeystoreDB, LegacyImporter) { - let mut keystore_db = new_test_db().unwrap(); - let mut legacy_importer = LegacyImporter::new(Arc::new(Default::default())); - legacy_importer.set_empty(); - let skm: Arc<RwLock<SuperKeyManager>> = Default::default(); - assert!(skm - .write() - .unwrap() - .init_user(&mut keystore_db, &legacy_importer, USER_ID, pw) - .is_ok()); - (skm, keystore_db, legacy_importer) - } - - fn assert_unlocked( - skm: &Arc<RwLock<SuperKeyManager>>, - keystore_db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, - user_id: u32, - err_msg: &str, - ) { - let user_state = - skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); - match user_state { - UserState::AfterFirstUnlock(_) => {} - _ => panic!("{}", err_msg), - } - } - - fn assert_locked( - skm: &Arc<RwLock<SuperKeyManager>>, - keystore_db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, - user_id: u32, - err_msg: &str, - ) { - let user_state = - skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); - match user_state { - UserState::BeforeFirstUnlock => {} - _ => panic!("{}", err_msg), - } - } - - fn assert_uninitialized( - skm: &Arc<RwLock<SuperKeyManager>>, - keystore_db: &mut KeystoreDB, - legacy_importer: &LegacyImporter, - user_id: u32, - err_msg: &str, - ) { - let user_state = - skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); - match user_state { - UserState::Uninitialized => {} - _ => panic!("{}", err_msg), - } - } - - #[test] - fn test_init_user() { - let pw: Password = generate_password_blob(); - let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); - assert_unlocked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was not unlocked after initialization!", - ); - } - - #[test] - fn test_unlock_user() { - let pw: Password = generate_password_blob(); - let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); - assert_unlocked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was not unlocked after initialization!", - ); - - skm.write().unwrap().data.user_keys.clear(); - assert_locked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "Clearing the cache did not lock the user!", - ); - - assert!(skm - .write() - .unwrap() - .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw) - .is_ok()); - assert_unlocked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user did not unlock!", - ); - } - - #[test] - fn test_unlock_wrong_password() { - let pw: Password = generate_password_blob(); - let wrong_pw: Password = generate_password_blob(); - let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); - assert_unlocked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was not unlocked after initialization!", - ); - - skm.write().unwrap().data.user_keys.clear(); - assert_locked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "Clearing the cache did not lock the user!", - ); - - assert!(skm - .write() - .unwrap() - .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &wrong_pw) - .is_err()); - assert_locked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was unlocked with an incorrect password!", - ); - } - - #[test] - fn test_unlock_user_idempotent() { - let pw: Password = generate_password_blob(); - let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); - assert_unlocked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was not unlocked after initialization!", - ); - - skm.write().unwrap().data.user_keys.clear(); - assert_locked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "Clearing the cache did not lock the user!", - ); - - for _ in 0..5 { - assert!(skm - .write() - .unwrap() - .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw) - .is_ok()); - assert_unlocked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user did not unlock!", - ); - } - } - - fn test_user_removal(locked: bool) { - let pw: Password = generate_password_blob(); - let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); - assert_unlocked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was not unlocked after initialization!", - ); - - assert!(make_test_key_entry( - &mut keystore_db, - Domain::APP, - USER_ID.into(), - TEST_KEY_ALIAS, - None - ) - .is_ok()); - assert!(make_bootlevel_key_entry( - &mut keystore_db, - Domain::APP, - USER_ID.into(), - TEST_BOOT_KEY_ALIAS, - false - ) - .is_ok()); - - assert!(keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) - .unwrap()); - assert!(keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) - .unwrap()); - - if locked { - skm.write().unwrap().data.user_keys.clear(); - assert_locked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "Clearing the cache did not lock the user!", - ); - } - - assert!(skm - .write() - .unwrap() - .remove_user(&mut keystore_db, &legacy_importer, USER_ID) - .is_ok()); - assert_uninitialized( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was not removed!", - ); - - assert!(!skm - .write() - .unwrap() - .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID) - .unwrap()); - - assert!(!keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) - .unwrap()); - assert!(!keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) - .unwrap()); - } - - fn test_user_reset(locked: bool) { - let pw: Password = generate_password_blob(); - let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); - assert_unlocked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was not unlocked after initialization!", - ); - - assert!(make_test_key_entry( - &mut keystore_db, - Domain::APP, - USER_ID.into(), - TEST_KEY_ALIAS, - None - ) - .is_ok()); - assert!(make_bootlevel_key_entry( - &mut keystore_db, - Domain::APP, - USER_ID.into(), - TEST_BOOT_KEY_ALIAS, - false - ) - .is_ok()); - assert!(keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) - .unwrap()); - assert!(keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) - .unwrap()); - - if locked { - skm.write().unwrap().data.user_keys.clear(); - assert_locked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "Clearing the cache did not lock the user!", - ); - assert!(skm - .write() - .unwrap() - .reset_user(&mut keystore_db, &legacy_importer, USER_ID) - .is_err()); - assert_locked( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "User state should not have changed!", - ); - - // Keys should still exist. - assert!(keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) - .unwrap()); - assert!(keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) - .unwrap()); - } else { - assert!(skm - .write() - .unwrap() - .reset_user(&mut keystore_db, &legacy_importer, USER_ID) - .is_ok()); - assert_uninitialized( - &skm, - &mut keystore_db, - &legacy_importer, - USER_ID, - "The user was not reset!", - ); - assert!(!skm - .write() - .unwrap() - .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID) - .unwrap()); - - // Auth bound key should no longer exist. - assert!(!keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) - .unwrap()); - assert!(keystore_db - .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) - .unwrap()); - } - } - - #[test] - fn test_remove_unlocked_user() { - test_user_removal(false); - } - - #[test] - fn test_remove_locked_user() { - test_user_removal(true); - } - - #[test] - fn test_reset_unlocked_user() { - test_user_reset(false); - } - - #[test] - fn test_reset_locked_user() { - test_user_reset(true); - } -} diff --git a/keystore2/src/super_key/tests.rs b/keystore2/src/super_key/tests.rs new file mode 100644 index 00000000..76a96a71 --- /dev/null +++ b/keystore2/src/super_key/tests.rs @@ -0,0 +1,287 @@ +// Copyright 2020, 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. + +//! Super-key tests. + +use super::*; +use crate::database::tests::make_bootlevel_key_entry; +use crate::database::tests::make_test_key_entry; +use crate::database::tests::new_test_db; +use rand::prelude::*; +const USER_ID: u32 = 0; +const TEST_KEY_ALIAS: &str = "TEST_KEY"; +const TEST_BOOT_KEY_ALIAS: &str = "TEST_BOOT_KEY"; + +pub fn generate_password_blob() -> Password<'static> { + let mut rng = rand::thread_rng(); + let mut password = vec![0u8; 64]; + rng.fill_bytes(&mut password); + + let mut zvec = ZVec::new(64).expect("Failed to create ZVec"); + zvec[..].copy_from_slice(&password[..]); + + Password::Owned(zvec) +} + +fn setup_test(pw: &Password) -> (Arc<RwLock<SuperKeyManager>>, KeystoreDB, LegacyImporter) { + let mut keystore_db = new_test_db().unwrap(); + let mut legacy_importer = LegacyImporter::new(Arc::new(Default::default())); + legacy_importer.set_empty(); + let skm: Arc<RwLock<SuperKeyManager>> = Default::default(); + assert!(skm + .write() + .unwrap() + .initialize_user(&mut keystore_db, &legacy_importer, USER_ID, pw, false) + .is_ok()); + (skm, keystore_db, legacy_importer) +} + +fn assert_unlocked( + skm: &Arc<RwLock<SuperKeyManager>>, + keystore_db: &mut KeystoreDB, + legacy_importer: &LegacyImporter, + user_id: u32, + err_msg: &str, +) { + let user_state = + skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); + match user_state { + UserState::AfterFirstUnlock(_) => {} + _ => panic!("{}", err_msg), + } +} + +fn assert_locked( + skm: &Arc<RwLock<SuperKeyManager>>, + keystore_db: &mut KeystoreDB, + legacy_importer: &LegacyImporter, + user_id: u32, + err_msg: &str, +) { + let user_state = + skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); + match user_state { + UserState::BeforeFirstUnlock => {} + _ => panic!("{}", err_msg), + } +} + +fn assert_uninitialized( + skm: &Arc<RwLock<SuperKeyManager>>, + keystore_db: &mut KeystoreDB, + legacy_importer: &LegacyImporter, + user_id: u32, + err_msg: &str, +) { + let user_state = + skm.write().unwrap().get_user_state(keystore_db, legacy_importer, user_id).unwrap(); + match user_state { + UserState::Uninitialized => {} + _ => panic!("{}", err_msg), + } +} + +#[test] +fn test_initialize_user() { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); +} + +#[test] +fn test_unlock_user() { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + + assert!(skm + .write() + .unwrap() + .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw) + .is_ok()); + assert_unlocked(&skm, &mut keystore_db, &legacy_importer, USER_ID, "The user did not unlock!"); +} + +#[test] +fn test_unlock_wrong_password() { + let pw: Password = generate_password_blob(); + let wrong_pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + + assert!(skm + .write() + .unwrap() + .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &wrong_pw) + .is_err()); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was unlocked with an incorrect password!", + ); +} + +#[test] +fn test_unlock_user_idempotent() { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + + for _ in 0..5 { + assert!(skm + .write() + .unwrap() + .unlock_user(&mut keystore_db, &legacy_importer, USER_ID, &pw) + .is_ok()); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user did not unlock!", + ); + } +} + +fn test_user_removal(locked: bool) { + let pw: Password = generate_password_blob(); + let (skm, mut keystore_db, legacy_importer) = setup_test(&pw); + assert_unlocked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not unlocked after initialization!", + ); + + assert!(make_test_key_entry( + &mut keystore_db, + Domain::APP, + USER_ID.into(), + TEST_KEY_ALIAS, + None + ) + .is_ok()); + assert!(make_bootlevel_key_entry( + &mut keystore_db, + Domain::APP, + USER_ID.into(), + TEST_BOOT_KEY_ALIAS, + false + ) + .is_ok()); + + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) + .unwrap()); + assert!(keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) + .unwrap()); + + if locked { + skm.write().unwrap().data.user_keys.clear(); + assert_locked( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "Clearing the cache did not lock the user!", + ); + } + + assert!(skm.write().unwrap().remove_user(&mut keystore_db, &legacy_importer, USER_ID).is_ok()); + assert_uninitialized( + &skm, + &mut keystore_db, + &legacy_importer, + USER_ID, + "The user was not removed!", + ); + + assert!(!skm + .write() + .unwrap() + .super_key_exists_in_db_for_user(&mut keystore_db, &legacy_importer, USER_ID) + .unwrap()); + + assert!(!keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_KEY_ALIAS, KeyType::Client) + .unwrap()); + assert!(!keystore_db + .key_exists(Domain::APP, USER_ID.into(), TEST_BOOT_KEY_ALIAS, KeyType::Client) + .unwrap()); +} + +#[test] +fn test_remove_unlocked_user() { + test_user_removal(false); +} + +#[test] +fn test_remove_locked_user() { + test_user_removal(true); +} diff --git a/keystore2/src/sw_keyblob.rs b/keystore2/src/sw_keyblob.rs index 47ab49fd..c0173b52 100644 --- a/keystore2/src/sw_keyblob.rs +++ b/keystore2/src/sw_keyblob.rs @@ -28,6 +28,9 @@ use anyhow::Result; use keystore2_crypto::hmac_sha256; use std::mem::size_of; +#[cfg(test)] +mod tests; + /// Root of trust value. const SOFTWARE_ROOT_OF_TRUST: &[u8] = b"SW"; @@ -556,481 +559,3 @@ fn serialize_params(params: &[KeyParameter]) -> Result<Vec<u8>> { .clone_from_slice(&serialized_size.to_ne_bytes()); Ok(result) } - -#[cfg(test)] -mod tests { - use super::*; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, - KeyOrigin::KeyOrigin, KeyParameter::KeyParameter, - KeyParameterValue::KeyParameterValue as KPV, KeyPurpose::KeyPurpose, - PaddingMode::PaddingMode, Tag::Tag, - }; - - macro_rules! expect_err { - ($result:expr, $err_msg:expr) => { - assert!( - $result.is_err(), - "Expected error containing '{}', got success {:?}", - $err_msg, - $result - ); - let err = $result.err(); - assert!( - format!("{:?}", err).contains($err_msg), - "Unexpected error {:?}, doesn't contain '{}'", - err, - $err_msg - ); - }; - } - - #[test] - fn test_consume_u8() { - let buffer = [1, 2]; - let mut data = &buffer[..]; - assert_eq!(1u8, consume_u8(&mut data).unwrap()); - assert_eq!(2u8, consume_u8(&mut data).unwrap()); - let result = consume_u8(&mut data); - expect_err!(result, "failed to find 1 byte"); - } - - #[test] - fn test_consume_u32() { - // All supported platforms are little-endian. - let buffer = [ - 0x01, 0x02, 0x03, 0x04, // little-endian u32 - 0x04, 0x03, 0x02, 0x01, // little-endian u32 - 0x11, 0x12, 0x13, - ]; - let mut data = &buffer[..]; - assert_eq!(0x04030201u32, consume_u32(&mut data).unwrap()); - assert_eq!(0x01020304u32, consume_u32(&mut data).unwrap()); - let result = consume_u32(&mut data); - expect_err!(result, "failed to find 4 bytes"); - } - - #[test] - fn test_consume_i64() { - // All supported platforms are little-endian. - let buffer = [ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // little-endian i64 - 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // little-endian i64 - 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - ]; - let mut data = &buffer[..]; - assert_eq!(0x0807060504030201i64, consume_i64(&mut data).unwrap()); - assert_eq!(0x0102030405060708i64, consume_i64(&mut data).unwrap()); - let result = consume_i64(&mut data); - expect_err!(result, "failed to find 8 bytes"); - } - - #[test] - fn test_consume_vec() { - let buffer = [ - 0x01, 0x00, 0x00, 0x00, 0xaa, // - 0x00, 0x00, 0x00, 0x00, // - 0x01, 0x00, 0x00, 0x00, 0xbb, // - 0x07, 0x00, 0x00, 0x00, 0xbb, // not enough data - ]; - let mut data = &buffer[..]; - assert_eq!(vec![0xaa], consume_vec(&mut data).unwrap()); - assert_eq!(Vec::<u8>::new(), consume_vec(&mut data).unwrap()); - assert_eq!(vec![0xbb], consume_vec(&mut data).unwrap()); - let result = consume_vec(&mut data); - expect_err!(result, "failed to find 7 bytes"); - - let buffer = [ - 0x01, 0x00, 0x00, // - ]; - let mut data = &buffer[..]; - let result = consume_vec(&mut data); - expect_err!(result, "failed to find 4 bytes"); - } - - #[test] - fn test_key_new_from_serialized() { - let hidden = hidden_params(&[], &[SOFTWARE_ROOT_OF_TRUST]); - // Test data originally generated by instrumenting Cuttlefish C++ KeyMint while running VTS - // tests. - let tests = [ - ( - concat!( - "0010000000d43c2f04f948521b81bdbf001310f5920000000000000000000000", - "00000000000c0000006400000002000010200000000300003080000000010000", - "2000000000010000200100000004000020020000000600002001000000be0200", - "1000000000c1020030b0ad0100c20200307b150300bd020060a8bb52407b0100", - "00ce02003011643401cf020030000000003b06b13ae6ae6671", - ), - KeyBlob { - key_material: hex::decode("d43c2f04f948521b81bdbf001310f592").unwrap(), - hw_enforced: vec![], - sw_enforced: vec![ - KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::AES) }, - KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(128) }, - KeyParameter { - tag: Tag::PURPOSE, - value: KPV::KeyPurpose(KeyPurpose::ENCRYPT), - }, - KeyParameter { - tag: Tag::PURPOSE, - value: KPV::KeyPurpose(KeyPurpose::DECRYPT), - }, - KeyParameter { - tag: Tag::BLOCK_MODE, - value: KPV::BlockMode(BlockMode::CBC), - }, - KeyParameter { - tag: Tag::PADDING, - value: KPV::PaddingMode(PaddingMode::NONE), - }, - KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, - KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, - KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, - KeyParameter { - tag: Tag::CREATION_DATETIME, - value: KPV::DateTime(1628871769000), - }, - KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, - KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, - ], - }, - Some(KeyFormat::RAW), - ), - ( - concat!( - "00df0000003081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6b", - "a14e46ab7ca532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654", - "e9e79413cd503eae3d9cf68ed24f47a00706052b81040023a181890381860004", - "006b840f0db0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d14", - "0743bdd028db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b7", - "45e30142e90685646661550344113aaf28bdee6cb02d19df1faab4398556a909", - "7d6f64b95209601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3", - "b877984d000000000000000000000000000000000c0000006400000002000010", - "030000000a000010030000000100002002000000010000200300000005000020", - "000000000300003009020000be02001000000000c1020030b0ad0100c2020030", - "7b150300bd02006018d352407b010000ce02003011643401cf02003000000000", - "2f69002e55e9b0a3" - ), - KeyBlob { - key_material: hex::decode(concat!( - "3081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6ba14e46ab7c", - "a532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654e9e79413cd", - "503eae3d9cf68ed24f47a00706052b81040023a181890381860004006b840f0d", - "b0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d140743bdd028", - "db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b745e30142e9", - "0685646661550344113aaf28bdee6cb02d19df1faab4398556a9097d6f64b952", - "09601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3b877984d", - )) - .unwrap(), - hw_enforced: vec![], - sw_enforced: vec![ - KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) }, - KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::P_521) }, - KeyParameter { - tag: Tag::PURPOSE, - value: KPV::KeyPurpose(KeyPurpose::SIGN), - }, - KeyParameter { - tag: Tag::PURPOSE, - value: KPV::KeyPurpose(KeyPurpose::VERIFY), - }, - KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) }, - KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(521) }, - KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, - KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, - KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, - KeyParameter { - tag: Tag::CREATION_DATETIME, - value: KPV::DateTime(1628871775000), - }, - KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, - KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, - ], - }, - Some(KeyFormat::PKCS8), - ), - ( - concat!( - "0037000000541d4c440223650d5f51753c1abd80c725034485551e874d62327c", - "65f6247a057f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc00000000", - "0000000000000000000000000c00000064000000020000108000000003000030", - "b801000001000020020000000100002003000000050000200400000008000030", - "00010000be02001000000000c1020030b0ad0100c20200307b150300bd020060", - "00d752407b010000ce02003011643401cf0200300000000036e6986ffc45fbb0", - ), - KeyBlob { - key_material: hex::decode(concat!( - "541d4c440223650d5f51753c1abd80c725034485551e874d62327c65f6247a05", - "7f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc" - )) - .unwrap(), - hw_enforced: vec![], - sw_enforced: vec![ - KeyParameter { - tag: Tag::ALGORITHM, - value: KPV::Algorithm(Algorithm::HMAC), - }, - KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(440) }, - KeyParameter { - tag: Tag::PURPOSE, - value: KPV::KeyPurpose(KeyPurpose::SIGN), - }, - KeyParameter { - tag: Tag::PURPOSE, - value: KPV::KeyPurpose(KeyPurpose::VERIFY), - }, - KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::SHA_2_256) }, - KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KPV::Integer(256) }, - KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, - KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, - KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, - KeyParameter { - tag: Tag::CREATION_DATETIME, - value: KPV::DateTime(1628871776000), - }, - KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, - KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, - ], - }, - Some(KeyFormat::RAW), - ), - ( - concat!( - "00a8040000308204a40201000282010100bc47b5c71116766669b91fa747df87", - "a1963df83956569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082", - "825c7c6e482a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31", - "c1ced84878edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c3973", - "6838151642eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d", - "5337c8bf9245d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0de", - "b0957d61dbba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2", - "540c84e45c4a99fb338b76bba7722856b5113341c349708937228f167d238ed8", - "efb9cc19547dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af", - "59fe87421af9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d3703", - "8bf9f588ae20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178", - "a848e06d558c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88e", - "e610260e406c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30a", - "e7a31f8262da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73a", - "e6f8b29a9144eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b", - "4217c8db50db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817", - "d9380b190bd382aaffa37785759f285194c11a188bccde0e2e2902818100fb23", - "3335770c9f3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a63", - "3f7790d1011bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f2", - "7a74852d6c7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd78009490", - "4c2856d2b944fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f45990281", - "8100bfecf2bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a7158458", - "18e01181ff06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb", - "0078b77fb5b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc84", - "20fcbf48d1eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b57", - "298902818100ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd", - "3765559994a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55", - "dad696d3821def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2", - "d8480ed07a7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f6332", - "38f521ba764902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6", - "603c6265f70018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d46", - "93fa48e3a9abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b", - "2a57e18a2e131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc", - "2af4ed75760858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1", - "c0a258d882984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4", - "340a9fae64a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c5526", - "1941b3654533b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a2", - "47f56a337e3b9845b4f2b61356000000000000000000000000000000000d0000", - "007000000002000010010000000300003000080000c800005001000100000000", - "0001000020020000000100002003000000050000200000000006000020010000", - "00be02001000000000c1020030b0ad0100c20200307b150300bd020060a8bb52", - "407b010000ce02003011643401cf02003000000000544862e9c961e857", - ), - KeyBlob { - key_material: hex::decode(concat!( - "308204a40201000282010100bc47b5c71116766669b91fa747df87a1963df839", - "56569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082825c7c6e48", - "2a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31c1ced84878", - "edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c39736838151642", - "eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d5337c8bf92", - "45d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0deb0957d61db", - "ba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2540c84e45c", - "4a99fb338b76bba7722856b5113341c349708937228f167d238ed8efb9cc1954", - "7dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af59fe87421a", - "f9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d37038bf9f588ae", - "20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178a848e06d55", - "8c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88ee610260e40", - "6c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30ae7a31f8262", - "da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73ae6f8b29a91", - "44eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b4217c8db50", - "db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817d9380b190b", - "d382aaffa37785759f285194c11a188bccde0e2e2902818100fb233335770c9f", - "3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a633f7790d101", - "1bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f27a74852d6c", - "7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd780094904c2856d2b9", - "44fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f459902818100bfecf2", - "bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a715845818e01181ff", - "06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb0078b77fb5", - "b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc8420fcbf48d1", - "eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b572989028181", - "00ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd3765559994", - "a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55dad696d382", - "1def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2d8480ed07a", - "7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f633238f521ba76", - "4902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6603c6265f7", - "0018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d4693fa48e3a9", - "abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b2a57e18a2e", - "131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc2af4ed7576", - "0858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1c0a258d882", - "984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4340a9fae64", - "a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c55261941b36545", - "33b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a247f56a337e", - "3b9845b4f2b61356", - )) - .unwrap(), - hw_enforced: vec![], - sw_enforced: vec![ - KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::RSA) }, - KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(2048) }, - KeyParameter { - tag: Tag::RSA_PUBLIC_EXPONENT, - value: KPV::LongInteger(65537), - }, - KeyParameter { - tag: Tag::PURPOSE, - value: KPV::KeyPurpose(KeyPurpose::SIGN), - }, - KeyParameter { - tag: Tag::PURPOSE, - value: KPV::KeyPurpose(KeyPurpose::VERIFY), - }, - KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) }, - KeyParameter { - tag: Tag::PADDING, - value: KPV::PaddingMode(PaddingMode::NONE), - }, - KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, - KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, - KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, - KeyParameter { - tag: Tag::CREATION_DATETIME, - value: KPV::DateTime(1628871769000), - }, - KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, - KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, - ], - }, - // No support for RSA keys in export_key(). - None, - ), - ]; - - for (input, want, want_format) in tests { - let input = hex::decode(input).unwrap(); - let got = KeyBlob::new_from_serialized(&input, &hidden).expect("invalid keyblob!"); - assert!(got == want); - - if let Some(want_format) = want_format { - let (got_format, _key_material, params) = - export_key(&input, &[]).expect("invalid keyblob!"); - assert_eq!(got_format, want_format); - // All the test cases are software-only keys. - assert_eq!(params, got.sw_enforced); - } - } - } - - #[test] - fn test_add_der_len() { - let tests = [ - (0, "00"), - (1, "01"), - (126, "7e"), - (127, "7f"), - (128, "8180"), - (129, "8181"), - (255, "81ff"), - (256, "820100"), - (257, "820101"), - (65535, "82ffff"), - ]; - for (input, want) in tests { - let mut got = Vec::new(); - add_der_len(&mut got, input).unwrap(); - assert_eq!(hex::encode(got), want, " for input length {input}"); - } - } - - #[test] - fn test_pkcs8_wrap_key_p256() { - // Key material taken from `ec_256_key` in - // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp - let input = hex::decode(concat!( - "3025", // SEQUENCE (ECPrivateKey) - "020101", // INTEGER length 1 value 1 (version) - "0420", // OCTET STRING (privateKey) - "737c2ecd7b8d1940bf2930aa9b4ed3ff", - "941eed09366bc03299986481f3a4d859", - )) - .unwrap(); - let want = hex::decode(concat!( - // RFC 5208 s5 - "3041", // SEQUENCE (PrivateKeyInfo) { - "020100", // INTEGER length 1 value 0 (version) - "3013", // SEQUENCE length 0x13 (AlgorithmIdentifier) { - "0607", // OBJECT IDENTIFIER length 7 (algorithm) - "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey) - "0608", // OBJECT IDENTIFIER length 8 (param) - "2a8648ce3d030107", // 1.2.840.10045.3.1.7 (secp256r1) - // } end SEQUENCE (AlgorithmIdentifier) - "0427", // OCTET STRING (privateKey) holding... - "3025", // SEQUENCE (ECPrivateKey) - "020101", // INTEGER length 1 value 1 (version) - "0420", // OCTET STRING length 0x20 (privateKey) - "737c2ecd7b8d1940bf2930aa9b4ed3ff", - "941eed09366bc03299986481f3a4d859", - // } end SEQUENCE (ECPrivateKey) - // } end SEQUENCE (PrivateKeyInfo) - )) - .unwrap(); - let got = pkcs8_wrap_nist_key(&input, EcCurve::P_256).unwrap(); - assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input)); - } - - #[test] - fn test_pkcs8_wrap_key_p521() { - // Key material taken from `ec_521_key` in - // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp - let input = hex::decode(concat!( - "3047", // SEQUENCE length 0xd3 (ECPrivateKey) - "020101", // INTEGER length 1 value 1 (version) - "0442", // OCTET STRING length 0x42 (privateKey) - "0011458c586db5daa92afab03f4fe46a", - "a9d9c3ce9a9b7a006a8384bec4c78e8e", - "9d18d7d08b5bcfa0e53c75b064ad51c4", - "49bae0258d54b94b1e885ded08ed4fb2", - "5ce9", - // } end SEQUENCE (ECPrivateKey) - )) - .unwrap(); - let want = hex::decode(concat!( - // RFC 5208 s5 - "3060", // SEQUENCE (PrivateKeyInfo) { - "020100", // INTEGER length 1 value 0 (version) - "3010", // SEQUENCE length 0x10 (AlgorithmIdentifier) { - "0607", // OBJECT IDENTIFIER length 7 (algorithm) - "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey) - "0605", // OBJECT IDENTIFIER length 5 (param) - "2b81040023", // 1.3.132.0.35 (secp521r1) - // } end SEQUENCE (AlgorithmIdentifier) - "0449", // OCTET STRING (privateKey) holding... - "3047", // SEQUENCE (ECPrivateKey) - "020101", // INTEGER length 1 value 1 (version) - "0442", // OCTET STRING length 0x42 (privateKey) - "0011458c586db5daa92afab03f4fe46a", - "a9d9c3ce9a9b7a006a8384bec4c78e8e", - "9d18d7d08b5bcfa0e53c75b064ad51c4", - "49bae0258d54b94b1e885ded08ed4fb2", - "5ce9", - // } end SEQUENCE (ECPrivateKey) - // } end SEQUENCE (PrivateKeyInfo) - )) - .unwrap(); - let got = pkcs8_wrap_nist_key(&input, EcCurve::P_521).unwrap(); - assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input)); - } -} diff --git a/keystore2/src/sw_keyblob/tests.rs b/keystore2/src/sw_keyblob/tests.rs new file mode 100644 index 00000000..fe01112c --- /dev/null +++ b/keystore2/src/sw_keyblob/tests.rs @@ -0,0 +1,449 @@ +// Copyright 2023, 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 software-backed keyblobs. +use super::*; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, + KeyOrigin::KeyOrigin, KeyParameter::KeyParameter, KeyParameterValue::KeyParameterValue as KPV, + KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, Tag::Tag, +}; + +macro_rules! expect_err { + ($result:expr, $err_msg:expr) => { + assert!( + $result.is_err(), + "Expected error containing '{}', got success {:?}", + $err_msg, + $result + ); + let err = $result.err(); + assert!( + format!("{:?}", err).contains($err_msg), + "Unexpected error {:?}, doesn't contain '{}'", + err, + $err_msg + ); + }; +} + +#[test] +fn test_consume_u8() { + let buffer = [1, 2]; + let mut data = &buffer[..]; + assert_eq!(1u8, consume_u8(&mut data).unwrap()); + assert_eq!(2u8, consume_u8(&mut data).unwrap()); + let result = consume_u8(&mut data); + expect_err!(result, "failed to find 1 byte"); +} + +#[test] +fn test_consume_u32() { + // All supported platforms are little-endian. + let buffer = [ + 0x01, 0x02, 0x03, 0x04, // little-endian u32 + 0x04, 0x03, 0x02, 0x01, // little-endian u32 + 0x11, 0x12, 0x13, + ]; + let mut data = &buffer[..]; + assert_eq!(0x04030201u32, consume_u32(&mut data).unwrap()); + assert_eq!(0x01020304u32, consume_u32(&mut data).unwrap()); + let result = consume_u32(&mut data); + expect_err!(result, "failed to find 4 bytes"); +} + +#[test] +fn test_consume_i64() { + // All supported platforms are little-endian. + let buffer = [ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // little-endian i64 + 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, // little-endian i64 + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + ]; + let mut data = &buffer[..]; + assert_eq!(0x0807060504030201i64, consume_i64(&mut data).unwrap()); + assert_eq!(0x0102030405060708i64, consume_i64(&mut data).unwrap()); + let result = consume_i64(&mut data); + expect_err!(result, "failed to find 8 bytes"); +} + +#[test] +fn test_consume_vec() { + let buffer = [ + 0x01, 0x00, 0x00, 0x00, 0xaa, // + 0x00, 0x00, 0x00, 0x00, // + 0x01, 0x00, 0x00, 0x00, 0xbb, // + 0x07, 0x00, 0x00, 0x00, 0xbb, // not enough data + ]; + let mut data = &buffer[..]; + assert_eq!(vec![0xaa], consume_vec(&mut data).unwrap()); + assert_eq!(Vec::<u8>::new(), consume_vec(&mut data).unwrap()); + assert_eq!(vec![0xbb], consume_vec(&mut data).unwrap()); + let result = consume_vec(&mut data); + expect_err!(result, "failed to find 7 bytes"); + + let buffer = [ + 0x01, 0x00, 0x00, // + ]; + let mut data = &buffer[..]; + let result = consume_vec(&mut data); + expect_err!(result, "failed to find 4 bytes"); +} + +#[test] +fn test_key_new_from_serialized() { + let hidden = hidden_params(&[], &[SOFTWARE_ROOT_OF_TRUST]); + // Test data originally generated by instrumenting Cuttlefish C++ KeyMint while running VTS + // tests. + let tests = [ + ( + concat!( + "0010000000d43c2f04f948521b81bdbf001310f5920000000000000000000000", + "00000000000c0000006400000002000010200000000300003080000000010000", + "2000000000010000200100000004000020020000000600002001000000be0200", + "1000000000c1020030b0ad0100c20200307b150300bd020060a8bb52407b0100", + "00ce02003011643401cf020030000000003b06b13ae6ae6671", + ), + KeyBlob { + key_material: hex::decode("d43c2f04f948521b81bdbf001310f592").unwrap(), + hw_enforced: vec![], + sw_enforced: vec![ + KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::AES) }, + KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(128) }, + KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::ENCRYPT) }, + KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::DECRYPT) }, + KeyParameter { tag: Tag::BLOCK_MODE, value: KPV::BlockMode(BlockMode::CBC) }, + KeyParameter { tag: Tag::PADDING, value: KPV::PaddingMode(PaddingMode::NONE) }, + KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, + KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, + KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, + KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KPV::DateTime(1628871769000), + }, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, + KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, + ], + }, + Some(KeyFormat::RAW), + ), + ( + concat!( + "00df0000003081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6b", + "a14e46ab7ca532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654", + "e9e79413cd503eae3d9cf68ed24f47a00706052b81040023a181890381860004", + "006b840f0db0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d14", + "0743bdd028db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b7", + "45e30142e90685646661550344113aaf28bdee6cb02d19df1faab4398556a909", + "7d6f64b95209601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3", + "b877984d000000000000000000000000000000000c0000006400000002000010", + "030000000a000010030000000100002002000000010000200300000005000020", + "000000000300003009020000be02001000000000c1020030b0ad0100c2020030", + "7b150300bd02006018d352407b010000ce02003011643401cf02003000000000", + "2f69002e55e9b0a3" + ), + KeyBlob { + key_material: hex::decode(concat!( + "3081dc020101044200b6ce876b947e263d61b8e3998d50dc0afb6ba14e46ab7c", + "a532fbe2a379b155d0a5bb99265402857b1601fb20be6c244bf654e9e79413cd", + "503eae3d9cf68ed24f47a00706052b81040023a181890381860004006b840f0d", + "b0b12f074ab916c7773cfa7d42967c9e5b4fae09cf999f7e116d140743bdd028", + "db0a3fcc670e721b9f00bc7fb70aa401c7d6de6582fc26962a29b745e30142e9", + "0685646661550344113aaf28bdee6cb02d19df1faab4398556a9097d6f64b952", + "09601a549389a311231c6cce78354f2cdbc3a904abf70686f5f0c3b877984d", + )) + .unwrap(), + hw_enforced: vec![], + sw_enforced: vec![ + KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::EC) }, + KeyParameter { tag: Tag::EC_CURVE, value: KPV::EcCurve(EcCurve::P_521) }, + KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) }, + KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) }, + KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) }, + KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(521) }, + KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, + KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, + KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, + KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KPV::DateTime(1628871775000), + }, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, + KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, + ], + }, + Some(KeyFormat::PKCS8), + ), + ( + concat!( + "0037000000541d4c440223650d5f51753c1abd80c725034485551e874d62327c", + "65f6247a057f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc00000000", + "0000000000000000000000000c00000064000000020000108000000003000030", + "b801000001000020020000000100002003000000050000200400000008000030", + "00010000be02001000000000c1020030b0ad0100c20200307b150300bd020060", + "00d752407b010000ce02003011643401cf0200300000000036e6986ffc45fbb0", + ), + KeyBlob { + key_material: hex::decode(concat!( + "541d4c440223650d5f51753c1abd80c725034485551e874d62327c65f6247a05", + "7f1218bd6c8cd7d319103ddb823fc11fb6c2c7268b5acc" + )) + .unwrap(), + hw_enforced: vec![], + sw_enforced: vec![ + KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::HMAC) }, + KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(440) }, + KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) }, + KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) }, + KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::SHA_2_256) }, + KeyParameter { tag: Tag::MIN_MAC_LENGTH, value: KPV::Integer(256) }, + KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, + KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, + KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, + KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KPV::DateTime(1628871776000), + }, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, + KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, + ], + }, + Some(KeyFormat::RAW), + ), + ( + concat!( + "00a8040000308204a40201000282010100bc47b5c71116766669b91fa747df87", + "a1963df83956569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082", + "825c7c6e482a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31", + "c1ced84878edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c3973", + "6838151642eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d", + "5337c8bf9245d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0de", + "b0957d61dbba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2", + "540c84e45c4a99fb338b76bba7722856b5113341c349708937228f167d238ed8", + "efb9cc19547dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af", + "59fe87421af9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d3703", + "8bf9f588ae20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178", + "a848e06d558c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88e", + "e610260e406c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30a", + "e7a31f8262da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73a", + "e6f8b29a9144eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b", + "4217c8db50db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817", + "d9380b190bd382aaffa37785759f285194c11a188bccde0e2e2902818100fb23", + "3335770c9f3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a63", + "3f7790d1011bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f2", + "7a74852d6c7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd78009490", + "4c2856d2b944fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f45990281", + "8100bfecf2bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a7158458", + "18e01181ff06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb", + "0078b77fb5b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc84", + "20fcbf48d1eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b57", + "298902818100ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd", + "3765559994a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55", + "dad696d3821def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2", + "d8480ed07a7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f6332", + "38f521ba764902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6", + "603c6265f70018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d46", + "93fa48e3a9abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b", + "2a57e18a2e131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc", + "2af4ed75760858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1", + "c0a258d882984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4", + "340a9fae64a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c5526", + "1941b3654533b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a2", + "47f56a337e3b9845b4f2b61356000000000000000000000000000000000d0000", + "007000000002000010010000000300003000080000c800005001000100000000", + "0001000020020000000100002003000000050000200000000006000020010000", + "00be02001000000000c1020030b0ad0100c20200307b150300bd020060a8bb52", + "407b010000ce02003011643401cf02003000000000544862e9c961e857", + ), + KeyBlob { + key_material: hex::decode(concat!( + "308204a40201000282010100bc47b5c71116766669b91fa747df87a1963df839", + "56569d4ac232aeba8a246c0ec73bf606374a6d07f30c2162f97082825c7c6e48", + "2a2841dfeaec1429d84e52c54a6b2f760dec952c9c44a3c3a80f31c1ced84878", + "edd4858059071c4d20d9ab0aae978bd68c1eb448e174a9736c39736838151642", + "eda8215107375865a99a57f29467c74c40f37b0221b93ec3f4f22d5337c8bf92", + "45d56936196a92b1dea315ecce8785f9fa9b7d159ca207612cc0deb0957d61db", + "ba5d9bd38784f4fecbf233b04e686a340528665ecd03db8e8a09b2540c84e45c", + "4a99fb338b76bba7722856b5113341c349708937228f167d238ed8efb9cc1954", + "7dd620f6a90d95f07e50bfe102030100010282010002f91b69d9af59fe87421a", + "f9ba60f15c77f9c1c90effd6634332876f8ee5a116b126f55d37038bf9f588ae", + "20c8d951d842e35c9ef35a7822d3ebf72c0b7c3e229b289ae2e178a848e06d55", + "8c2e03d26871ee98a35f370d461ff1c4acc39d684de680a25ec88ee610260e40", + "6c400bdeb2893b2d0330cb483e662fa5abd24c2b82143e85dfe30ae7a31f8262", + "da2903d882b35a34a26b699ff2d812bad4b126a0065ec0e101d73ae6f8b29a91", + "44eb83f54940a371fc7416c2c0370df6a41cb5391f17ba33239e1b4217c8db50", + "db5c6bf77ccf621354ecc652a4f7196054c254566fd7b3bc0f3817d9380b190b", + "d382aaffa37785759f285194c11a188bccde0e2e2902818100fb233335770c9f", + "3cbd4b6ede5f12d03c449b1997bce06a8249bc3de99972fd0d0a633f7790d101", + "1bf5eedee16fa45a9107a910656ecaee364ce9edb4369843be71f27a74852d6c", + "7215a6cc60d9803bcac544922f806d8e5844e0ddd914bd780094904c2856d2b9", + "44fade3fb1d67d4a33fb7663a9ab660ab372c2e4868a0f459902818100bfecf2", + "bb4012e880fd065a0b088f2d757af2878d3f1305f21ce7a715845818e01181ff", + "06b2f406239fc50808ce3dbe7b68ec01174913c0f237feb3c8c7eb0078b77fb5", + "b8f214b72f6d3835b1a7ebe8b132feb6cb34ab09ce22b98160fc8420fcbf48d1", + "eee49f874e902f049b206a61a095f0405a4935e7c5e49757ab7b572989028181", + "00ec0049383e16f3716de5fc5b2677148efe5dceb02483b43399bd3765559994", + "a9f3900eed7a7e9e8f3b0eee0e660eca392e3cb736cae612f39e55dad696d382", + "1def10d1f8bbca52f5e6d8e7893ffbdcb491aafdc17bebf86f84d2d8480ed07a", + "7bf9209d20ef6e79429489d4cb7768281a2f7e32ec1830fd6f633238f521ba76", + "4902818100b2c3ce5751580b4e51df3fb175387f5c24b79040a4d6603c6265f7", + "0018b441ff3aef7d8e4cd2f480ec0906f1c4c0481304e8861f9d4693fa48e3a9", + "abc362859eeb343e1c5507ac94b5439ce7ac04154a2fb886a4819b2a57e18a2e", + "131b412ac4a09b004766959cdf357745f003e272aab3de02e2d5bc2af4ed7576", + "0858ab181902818061d19c2a8dcacde104b97f7c4fae11216157c1c0a258d882", + "984d12383a73dc56fe2ac93512bb321df9706ecdb2f70a44c949c4340a9fae64", + "a0646cf51f37c58c08bebde91667b3b2fa7c895f7983d4786c55261941b36545", + "33b0598383ebbcffcdf28b6cf13d376e3a70b49b14d8d06e8563a247f56a337e", + "3b9845b4f2b61356", + )) + .unwrap(), + hw_enforced: vec![], + sw_enforced: vec![ + KeyParameter { tag: Tag::ALGORITHM, value: KPV::Algorithm(Algorithm::RSA) }, + KeyParameter { tag: Tag::KEY_SIZE, value: KPV::Integer(2048) }, + KeyParameter { tag: Tag::RSA_PUBLIC_EXPONENT, value: KPV::LongInteger(65537) }, + KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::SIGN) }, + KeyParameter { tag: Tag::PURPOSE, value: KPV::KeyPurpose(KeyPurpose::VERIFY) }, + KeyParameter { tag: Tag::DIGEST, value: KPV::Digest(Digest::NONE) }, + KeyParameter { tag: Tag::PADDING, value: KPV::PaddingMode(PaddingMode::NONE) }, + KeyParameter { tag: Tag::ORIGIN, value: KPV::Origin(KeyOrigin::GENERATED) }, + KeyParameter { tag: Tag::OS_VERSION, value: KPV::Integer(110000) }, + KeyParameter { tag: Tag::OS_PATCHLEVEL, value: KPV::Integer(202107) }, + KeyParameter { + tag: Tag::CREATION_DATETIME, + value: KPV::DateTime(1628871769000), + }, + KeyParameter { tag: Tag::VENDOR_PATCHLEVEL, value: KPV::Integer(20210705) }, + KeyParameter { tag: Tag::BOOT_PATCHLEVEL, value: KPV::Integer(0) }, + ], + }, + // No support for RSA keys in export_key(). + None, + ), + ]; + + for (input, want, want_format) in tests { + let input = hex::decode(input).unwrap(); + let got = KeyBlob::new_from_serialized(&input, &hidden).expect("invalid keyblob!"); + assert!(got == want); + + if let Some(want_format) = want_format { + let (got_format, _key_material, params) = + export_key(&input, &[]).expect("invalid keyblob!"); + assert_eq!(got_format, want_format); + // All the test cases are software-only keys. + assert_eq!(params, got.sw_enforced); + } + } +} + +#[test] +fn test_add_der_len() { + let tests = [ + (0, "00"), + (1, "01"), + (126, "7e"), + (127, "7f"), + (128, "8180"), + (129, "8181"), + (255, "81ff"), + (256, "820100"), + (257, "820101"), + (65535, "82ffff"), + ]; + for (input, want) in tests { + let mut got = Vec::new(); + add_der_len(&mut got, input).unwrap(); + assert_eq!(hex::encode(got), want, " for input length {input}"); + } +} + +#[test] +fn test_pkcs8_wrap_key_p256() { + // Key material taken from `ec_256_key` in + // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp + let input = hex::decode(concat!( + "3025", // SEQUENCE (ECPrivateKey) + "020101", // INTEGER length 1 value 1 (version) + "0420", // OCTET STRING (privateKey) + "737c2ecd7b8d1940bf2930aa9b4ed3ff", + "941eed09366bc03299986481f3a4d859", + )) + .unwrap(); + let want = hex::decode(concat!( + // RFC 5208 s5 + "3041", // SEQUENCE (PrivateKeyInfo) { + "020100", // INTEGER length 1 value 0 (version) + "3013", // SEQUENCE length 0x13 (AlgorithmIdentifier) { + "0607", // OBJECT IDENTIFIER length 7 (algorithm) + "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey) + "0608", // OBJECT IDENTIFIER length 8 (param) + "2a8648ce3d030107", // 1.2.840.10045.3.1.7 (secp256r1) + // } end SEQUENCE (AlgorithmIdentifier) + "0427", // OCTET STRING (privateKey) holding... + "3025", // SEQUENCE (ECPrivateKey) + "020101", // INTEGER length 1 value 1 (version) + "0420", // OCTET STRING length 0x20 (privateKey) + "737c2ecd7b8d1940bf2930aa9b4ed3ff", + "941eed09366bc03299986481f3a4d859", + // } end SEQUENCE (ECPrivateKey) + // } end SEQUENCE (PrivateKeyInfo) + )) + .unwrap(); + let got = pkcs8_wrap_nist_key(&input, EcCurve::P_256).unwrap(); + assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input)); +} + +#[test] +fn test_pkcs8_wrap_key_p521() { + // Key material taken from `ec_521_key` in + // hardware/interfaces/security/keymint/aidl/vts/function/KeyMintTest.cpp + let input = hex::decode(concat!( + "3047", // SEQUENCE length 0xd3 (ECPrivateKey) + "020101", // INTEGER length 1 value 1 (version) + "0442", // OCTET STRING length 0x42 (privateKey) + "0011458c586db5daa92afab03f4fe46a", + "a9d9c3ce9a9b7a006a8384bec4c78e8e", + "9d18d7d08b5bcfa0e53c75b064ad51c4", + "49bae0258d54b94b1e885ded08ed4fb2", + "5ce9", + // } end SEQUENCE (ECPrivateKey) + )) + .unwrap(); + let want = hex::decode(concat!( + // RFC 5208 s5 + "3060", // SEQUENCE (PrivateKeyInfo) { + "020100", // INTEGER length 1 value 0 (version) + "3010", // SEQUENCE length 0x10 (AlgorithmIdentifier) { + "0607", // OBJECT IDENTIFIER length 7 (algorithm) + "2a8648ce3d0201", // 1.2.840.10045.2.1 (ecPublicKey) + "0605", // OBJECT IDENTIFIER length 5 (param) + "2b81040023", // 1.3.132.0.35 (secp521r1) + // } end SEQUENCE (AlgorithmIdentifier) + "0449", // OCTET STRING (privateKey) holding... + "3047", // SEQUENCE (ECPrivateKey) + "020101", // INTEGER length 1 value 1 (version) + "0442", // OCTET STRING length 0x42 (privateKey) + "0011458c586db5daa92afab03f4fe46a", + "a9d9c3ce9a9b7a006a8384bec4c78e8e", + "9d18d7d08b5bcfa0e53c75b064ad51c4", + "49bae0258d54b94b1e885ded08ed4fb2", + "5ce9", + // } end SEQUENCE (ECPrivateKey) + // } end SEQUENCE (PrivateKeyInfo) + )) + .unwrap(); + let got = pkcs8_wrap_nist_key(&input, EcCurve::P_521).unwrap(); + assert_eq!(hex::encode(got), hex::encode(want), " for input {}", hex::encode(input)); +} diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs index a3fd8824..0a545f76 100644 --- a/keystore2/src/utils.rs +++ b/keystore2/src/utils.rs @@ -49,6 +49,9 @@ use keystore2_apc_compat::{ use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt, ZVec}; use std::iter::IntoIterator; +#[cfg(test)] +mod tests; + /// Per RFC 5280 4.1.2.5, an undefined expiration (not-after) field should be set to GeneralizedTime /// 999912312359559, which is 253402300799000 ms from Jan 1, 1970. pub const UNDEFINED_NOT_AFTER: i64 = 253402300799000i64; @@ -143,10 +146,7 @@ fn check_android_permission(permission: &str) -> anyhow::Result<()> { binder::get_interface("permission")?; let binder_result = { - let _wp = watchdog::watch_millis( - "In check_device_attestation_permissions: calling checkPermission.", - 500, - ); + let _wp = watchdog::watch("check_android_permission: calling checkPermission"); permission_controller.checkPermission( permission, ThreadState::get_calling_pid(), @@ -263,9 +263,8 @@ where log::debug!("import parameters={import_params:?}"); let creation_result = { - let _wp = watchdog::watch_millis( - "In utils::import_keyblob_and_perform_op: calling importKey.", - 500, + let _wp = watchdog::watch( + "utils::import_keyblob_and_perform_op: calling IKeyMintDevice::importKey", ); map_km_error(km_dev.importKey(&import_params, format, &key_material, None)) } @@ -306,9 +305,8 @@ where NewBlobHandler: FnOnce(&[u8]) -> Result<()>, { let upgraded_blob = { - let _wp = watchdog::watch_millis( - "In utils::upgrade_keyblob_and_perform_op: calling upgradeKey.", - 500, + let _wp = watchdog::watch( + "utils::upgrade_keyblob_and_perform_op: calling IKeyMintDevice::upgradeKey.", ); map_km_error(km_dev.upgradeKey(key_blob, upgrade_params)) } @@ -522,6 +520,8 @@ fn merge_and_filter_key_entry_lists( } fn estimate_safe_amount_to_return( + domain: Domain, + namespace: i64, key_descriptors: &[KeyDescriptor], response_size_limit: usize, ) -> usize { @@ -546,11 +546,9 @@ fn estimate_safe_amount_to_return( // 350KB and return a partial list. if returned_bytes > response_size_limit { log::warn!( - "Key descriptors list ({} items) may exceed binder \ - size, returning {} items est {} bytes.", + "{domain:?}:{namespace}: Key descriptors list ({} items) may exceed binder \ + size, returning {items_to_return} items est {returned_bytes} bytes.", key_descriptors.len(), - items_to_return, - returned_bytes ); break; } @@ -584,7 +582,7 @@ pub fn list_key_entries( const RESPONSE_SIZE_LIMIT: usize = 358400; let safe_amount_to_return = - estimate_safe_amount_to_return(&merged_key_entries, RESPONSE_SIZE_LIMIT); + estimate_safe_amount_to_return(domain, namespace, &merged_key_entries, RESPONSE_SIZE_LIMIT); Ok(merged_key_entries[..safe_amount_to_return].to_vec()) } @@ -599,6 +597,15 @@ pub fn count_key_entries(db: &mut KeystoreDB, domain: Domain, namespace: i64) -> Ok((legacy_keys.len() + num_keys_in_db) as i32) } +/// For params remove sensitive data before returning a string for logging +pub fn log_security_safe_params(params: &[KmKeyParameter]) -> Vec<KmKeyParameter> { + params + .iter() + .filter(|kp| (kp.tag != Tag::APPLICATION_ID && kp.tag != Tag::APPLICATION_DATA)) + .cloned() + .collect::<Vec<KmKeyParameter>>() +} + /// Trait implemented by objects that can be used to decrypt cipher text using AES-GCM. pub trait AesGcm { /// Deciphers `data` using the initialization vector `iv` and AEAD tag `tag` @@ -627,101 +634,3 @@ impl<T: AesGcmKey> AesGcm for T { aes_gcm_encrypt(plaintext, self.key()).context(ks_err!("Encryption failed.")) } } - -#[cfg(test)] -mod tests { - use super::*; - use anyhow::Result; - - #[test] - fn check_device_attestation_permissions_test() -> Result<()> { - check_device_attestation_permissions().or_else(|error| { - match error.root_cause().downcast_ref::<Error>() { - // Expected: the context for this test might not be allowed to attest device IDs. - Some(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) => Ok(()), - // Other errors are unexpected - _ => Err(error), - } - }) - } - - fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> { - key_aliases - .iter() - .map(|key_alias| KeyDescriptor { - domain: Domain::APP, - nspace: 0, - alias: Some(key_alias.to_string()), - blob: None, - }) - .collect::<Vec<KeyDescriptor>>() - } - - fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> { - key_descriptors - .iter() - .map( - |kd| { - if let Some(alias) = &kd.alias { - String::from(alias) - } else { - String::from("") - } - }, - ) - .collect::<Vec<String>>() - } - - #[test] - fn test_safe_amount_to_return() -> Result<()> { - let key_aliases = vec!["key1", "key2", "key3"]; - let key_descriptors = create_key_descriptors_from_aliases(&key_aliases); - - assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 20), 1); - assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 50), 2); - assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 100), 3); - Ok(()) - } - - #[test] - fn test_merge_and_sort_lists_without_filtering() -> Result<()> { - let legacy_key_aliases = vec!["key_c", "key_a", "key_b"]; - let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); - let db_key_aliases = vec!["key_a", "key_d"]; - let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); - let result = - merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None); - assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]); - Ok(()) - } - - #[test] - fn test_merge_and_sort_lists_with_filtering() -> Result<()> { - let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"]; - let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); - let db_key_aliases = vec!["key_c", "key_g"]; - let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); - let result = merge_and_filter_key_entry_lists( - &legacy_key_descriptors, - &db_key_descriptors, - Some("key_b"), - ); - assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]); - Ok(()) - } - - #[test] - fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> { - let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"]; - let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); - let db_key_aliases = vec!["key_d", "key_e", "key_g"]; - let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); - let result = merge_and_filter_key_entry_lists( - &legacy_key_descriptors, - &db_key_descriptors, - Some("key_c"), - ); - assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]); - Ok(()) - } -} diff --git a/keystore2/src/utils/tests.rs b/keystore2/src/utils/tests.rs new file mode 100644 index 00000000..618ea472 --- /dev/null +++ b/keystore2/src/utils/tests.rs @@ -0,0 +1,125 @@ +// Copyright 2020, 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. + +//! Utility functions tests. + +use super::*; +use anyhow::Result; + +#[test] +fn check_device_attestation_permissions_test() -> Result<()> { + check_device_attestation_permissions().or_else(|error| { + match error.root_cause().downcast_ref::<Error>() { + // Expected: the context for this test might not be allowed to attest device IDs. + Some(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) => Ok(()), + // Other errors are unexpected + _ => Err(error), + } + }) +} + +fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> { + key_aliases + .iter() + .map(|key_alias| KeyDescriptor { + domain: Domain::APP, + nspace: 0, + alias: Some(key_alias.to_string()), + blob: None, + }) + .collect::<Vec<KeyDescriptor>>() +} + +fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> { + key_descriptors + .iter() + .map(|kd| if let Some(alias) = &kd.alias { String::from(alias) } else { String::from("") }) + .collect::<Vec<String>>() +} + +#[test] +fn test_safe_amount_to_return() -> Result<()> { + let key_aliases = vec!["key1", "key2", "key3"]; + let key_descriptors = create_key_descriptors_from_aliases(&key_aliases); + + assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 20), 1); + assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 50), 2); + assert_eq!(estimate_safe_amount_to_return(Domain::APP, 1017, &key_descriptors, 100), 3); + Ok(()) +} + +#[test] +fn test_merge_and_sort_lists_without_filtering() -> Result<()> { + let legacy_key_aliases = vec!["key_c", "key_a", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_a", "key_d"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = + merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]); + Ok(()) +} + +#[test] +fn test_merge_and_sort_lists_with_filtering() -> Result<()> { + let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_c", "key_g"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = merge_and_filter_key_entry_lists( + &legacy_key_descriptors, + &db_key_descriptors, + Some("key_b"), + ); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]); + Ok(()) +} + +#[test] +fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> { + let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"]; + let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases); + let db_key_aliases = vec!["key_d", "key_e", "key_g"]; + let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases); + let result = merge_and_filter_key_entry_lists( + &legacy_key_descriptors, + &db_key_descriptors, + Some("key_c"), + ); + assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]); + Ok(()) +} + +#[test] +fn test_list_key_parameters_with_filter_on_security_sensitive_info() -> Result<()> { + let params = vec![ + KmKeyParameter { tag: Tag::APPLICATION_ID, value: KeyParameterValue::Integer(0) }, + KmKeyParameter { tag: Tag::APPLICATION_DATA, value: KeyParameterValue::Integer(0) }, + KmKeyParameter { + tag: Tag::CERTIFICATE_NOT_AFTER, + value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER), + }, + KmKeyParameter { tag: Tag::CERTIFICATE_NOT_BEFORE, value: KeyParameterValue::DateTime(0) }, + ]; + let wanted = vec![ + KmKeyParameter { + tag: Tag::CERTIFICATE_NOT_AFTER, + value: KeyParameterValue::DateTime(UNDEFINED_NOT_AFTER), + }, + KmKeyParameter { tag: Tag::CERTIFICATE_NOT_BEFORE, value: KeyParameterValue::DateTime(0) }, + ]; + + assert_eq!(log_security_safe_params(¶ms), wanted); + Ok(()) +} diff --git a/keystore2/src/watchdog_helper.rs b/keystore2/src/watchdog_helper.rs index 92a0abc1..1072ac07 100644 --- a/keystore2/src/watchdog_helper.rs +++ b/keystore2/src/watchdog_helper.rs @@ -23,6 +23,11 @@ pub mod watchdog { pub use watchdog_rs::WatchPoint; use watchdog_rs::Watchdog; + /// Default timeout interval, in milliseconds. + pub const DEFAULT_TIMEOUT_MS: u64 = 500; + + const DEFAULT_TIMEOUT: Duration = Duration::from_millis(DEFAULT_TIMEOUT_MS); + lazy_static! { /// A Watchdog thread, that can be used to create watch points. static ref WD: Arc<Watchdog> = Watchdog::new(Duration::from_secs(10)); @@ -33,14 +38,19 @@ pub mod watchdog { Watchdog::watch(&WD, id, Duration::from_millis(millis)) } - /// Like `watch_millis` but with a callback that is called every time a report - /// is printed about this watch point. + /// Sets a watch point with `id` and a default timeout of [`DEFAULT_TIMEOUT_MS`] milliseconds. + pub fn watch(id: &'static str) -> Option<WatchPoint> { + Watchdog::watch(&WD, id, DEFAULT_TIMEOUT) + } + + /// Like `watch_millis` but with context that is included every time a report is printed about + /// this watch point. pub fn watch_millis_with( id: &'static str, millis: u64, - callback: impl Fn() -> String + Send + 'static, + context: impl std::fmt::Debug + Send + 'static, ) -> Option<WatchPoint> { - Watchdog::watch_with(&WD, id, Duration::from_millis(millis), callback) + Watchdog::watch_with(&WD, id, Duration::from_millis(millis), context) } } @@ -53,11 +63,15 @@ pub mod watchdog { fn watch_millis(_: &'static str, _: u64) -> Option<WatchPoint> { None } + /// Sets a Noop watch point. + fn watch(_: &'static str) -> Option<WatchPoint> { + None + } pub fn watch_millis_with( _: &'static str, _: u64, - _: impl Fn() -> String + Send + 'static, + _: impl std::fmt::Debug + Send + 'static, ) -> Option<WatchPoint> { None } diff --git a/keystore2/test_utils/authorizations.rs b/keystore2/test_utils/authorizations.rs index 2cb2aaf6..a96d9946 100644 --- a/keystore2/test_utils/authorizations.rs +++ b/keystore2/test_utils/authorizations.rs @@ -360,6 +360,15 @@ impl AuthSetBuilder { }); 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/test_utils/key_generations.rs b/keystore2/test_utils/key_generations.rs index a733be39..c845332d 100644 --- a/keystore2/test_utils/key_generations.rs +++ b/keystore2/test_utils/key_generations.rs @@ -14,14 +14,12 @@ //! This module implements test utils to generate various types of keys. -use anyhow::Result; -use core::ops::Range; -use nix::unistd::getuid; -use std::collections::HashSet; -use std::fmt::Write; - -use binder::ThreadState; - +use crate::authorizations::AuthSetBuilder; +use crate::ffi_test_utils::{ + get_os_patchlevel, get_os_version, get_value_from_attest_record, get_vendor_patchlevel, + validate_certchain_with_strict_issuer_check, +}; +use crate::SecLevel; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType, @@ -30,18 +28,16 @@ use android_hardware_security_keymint::aidl::android::hardware::security::keymin }; use android_system_keystore2::aidl::android::system::keystore2::{ AuthenticatorSpec::AuthenticatorSpec, Authorization::Authorization, - CreateOperationResponse::CreateOperationResponse, Domain::Domain, - IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, + CreateOperationResponse::CreateOperationResponse, Domain::Domain, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, ResponseCode::ResponseCode, }; - -use crate::authorizations::AuthSetBuilder; use android_system_keystore2::binder::{ExceptionCode, Result as BinderResult}; - -use crate::ffi_test_utils::{ - get_os_patchlevel, get_os_version, get_value_from_attest_record, get_vendor_patchlevel, - validate_certchain_with_strict_issuer_check, -}; +use anyhow::Result; +use binder::ThreadState; +use core::ops::Range; +use nix::unistd::getuid; +use std::collections::HashSet; +use std::fmt::Write; /// Shell namespace. pub const SELINUX_SHELL_NAMESPACE: i64 = 1; @@ -391,12 +387,6 @@ pub fn map_ks_error<T>(r: BinderResult<T>) -> Result<T, Error> { }) } -/// Indicate whether the default device is KeyMint (rather than Keymaster). -pub fn has_default_keymint() -> bool { - binder::is_declared("android.hardware.security.keymint.IKeyMintDevice/default") - .expect("Could not check for declared keymint interface") -} - /// Verify that given key param is listed in given authorizations list. pub fn check_key_param(authorizations: &[Authorization], key_param: &KeyParameter) -> bool { authorizations.iter().any(|auth| &auth.keyParameter == key_param) @@ -404,15 +394,15 @@ pub fn check_key_param(authorizations: &[Authorization], key_param: &KeyParamete /// Verify the given key authorizations with the expected authorizations. pub fn check_key_authorizations( + sl: &SecLevel, authorizations: &[Authorization], expected_params: &[KeyParameter], expected_key_origin: KeyOrigin, ) { // Make sure key authorizations contains only `ALLOWED_TAGS_IN_KEY_AUTHS` authorizations.iter().all(|auth| { - // Ignore `INVALID` tag if the backend is Keymaster and not KeyMint. - // Keymaster allows INVALID tag for unsupported key parameters. - if !has_default_keymint() && auth.keyParameter.tag == Tag::INVALID { + // Ignore `INVALID` tag + if auth.keyParameter.tag == Tag::INVALID { return true; } assert!( @@ -423,7 +413,7 @@ pub fn check_key_authorizations( true }); - //Check allowed-expected-key-parameters are present in given key authorizations list. + // Check allowed-expected-key-parameters are present in given key authorizations list. expected_params.iter().all(|key_param| { // `INCLUDE_UNIQUE_ID` is not strictly expected to be in key authorizations but has been // put there by some implementations so cope with that. @@ -433,13 +423,25 @@ pub fn check_key_authorizations( return true; } - // Ignore below parameters if the backend is Keymaster and not KeyMint. - // Keymaster does not support these parameters. These key parameters are introduced in - // KeyMint1.0. - if !has_default_keymint() { - if matches!(key_param.tag, Tag::RSA_OAEP_MGF_DIGEST | Tag::USAGE_COUNT_LIMIT) { + // `Tag::RSA_OAEP_MGF_DIGEST` was added in KeyMint 1.0, but the KeyMint VTS tests didn't + // originally check for its presence and so some implementations of early versions (< 3) of + // the KeyMint HAL don't include it (cf. b/297306437 and aosp/2758513). + // + // Given that Keymaster implementations will also omit this tag, skip the check for it + // altogether (and rely on the updated KeyMint VTS tests to ensure that up-level KeyMint + // implementations correctly populate this tag). + if matches!(key_param.tag, Tag::RSA_OAEP_MGF_DIGEST) { + return true; + } + + if sl.is_keymaster() { + // `Tag::USAGE_COUNT_LIMIT` was added in KeyMint 1.0, so don't check for it if the + // underlying device is a Keymaster implementation. + if matches!(key_param.tag, Tag::USAGE_COUNT_LIMIT) { return true; } + // `KeyPurpose::ATTEST_KEY` was added in KeyMint 1.0, so don't check for it if the + // underlying device is a Keymaster implementation. if key_param.tag == Tag::PURPOSE && key_param.value == KeyParameterValue::KeyPurpose(KeyPurpose::ATTEST_KEY) { @@ -457,11 +459,15 @@ pub fn check_key_authorizations( true }); - check_common_auths(authorizations, expected_key_origin); + check_common_auths(sl, authorizations, expected_key_origin); } /// Verify common key authorizations. -fn check_common_auths(authorizations: &[Authorization], expected_key_origin: KeyOrigin) { +fn check_common_auths( + sl: &SecLevel, + authorizations: &[Authorization], + expected_key_origin: KeyOrigin, +) { assert!(check_key_param( authorizations, &KeyParameter { @@ -505,7 +511,7 @@ fn check_common_auths(authorizations: &[Authorization], expected_key_origin: Key } )); - if has_default_keymint() { + if sl.is_keymint() { assert!(authorizations .iter() .map(|auth| &auth.keyParameter) @@ -532,7 +538,7 @@ pub fn get_key_auth(authorizations: &[Authorization], tag: Tag) -> Option<&Autho /// Digest: SHA_2_256 /// Curve: P_256 pub fn generate_ec_p256_signing_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, @@ -552,7 +558,7 @@ pub fn generate_ec_p256_signing_key( gen_params = gen_params.clone().attestation_challenge(challenge.to_vec()); } - match sec_level.generateKey( + match sl.binder.generateKey( &KeyDescriptor { domain, nspace, alias, blob: None }, None, &gen_params, @@ -569,6 +575,7 @@ pub fn generate_ec_p256_signing_key( } check_key_authorizations( + sl, &key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED, @@ -581,7 +588,7 @@ pub fn generate_ec_p256_signing_key( /// Generate EC signing key. pub fn generate_ec_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, @@ -596,7 +603,7 @@ pub fn generate_ec_key( .digest(digest) .ec_curve(ec_curve); - let key_metadata = sec_level.generateKey( + let key_metadata = sl.binder.generateKey( &KeyDescriptor { domain, nspace, alias, blob: None }, None, &gen_params, @@ -615,13 +622,13 @@ pub fn generate_ec_key( } else { assert!(key_metadata.key.blob.is_none()); } - check_key_authorizations(&key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); + check_key_authorizations(sl, &key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); Ok(key_metadata) } /// Generate a RSA key with the given key parameters, alias, domain and namespace. pub fn generate_rsa_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, @@ -653,7 +660,7 @@ pub fn generate_rsa_key( gen_params = gen_params.attestation_challenge(value.to_vec()) } - let key_metadata = sec_level.generateKey( + let key_metadata = sl.binder.generateKey( &KeyDescriptor { domain, nspace, alias, blob: None }, attest_key, &gen_params, @@ -677,7 +684,7 @@ pub fn generate_rsa_key( || key_metadata.key.blob.is_none() ); - check_key_authorizations(&key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); + check_key_authorizations(sl, &key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); // If `RSA_OAEP_MGF_DIGEST` tag is not mentioned explicitly while generating/importing a key, // then make sure `RSA_OAEP_MGF_DIGEST` tag with default value (SHA1) must not be included in // key authorization list. @@ -695,7 +702,7 @@ pub fn generate_rsa_key( /// Generate AES/3DES key. pub fn generate_sym_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, algorithm: Algorithm, size: i32, alias: &str, @@ -716,7 +723,7 @@ pub fn generate_sym_key( gen_params = gen_params.min_mac_length(val); } - let key_metadata = sec_level.generateKey( + let key_metadata = sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -734,13 +741,13 @@ pub fn generate_sym_key( // Should not have an attestation record. assert!(key_metadata.certificateChain.is_none()); - check_key_authorizations(&key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); + check_key_authorizations(sl, &key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); Ok(key_metadata) } /// Generate HMAC key. pub fn generate_hmac_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias: &str, key_size: i32, min_mac_len: i32, @@ -755,7 +762,7 @@ pub fn generate_hmac_key( .min_mac_length(min_mac_len) .digest(digest); - let key_metadata = sec_level.generateKey( + let key_metadata = sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -774,7 +781,7 @@ pub fn generate_hmac_key( // Should not have an attestation record. assert!(key_metadata.certificateChain.is_none()); - check_key_authorizations(&key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); + check_key_authorizations(sl, &key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED); Ok(key_metadata) } @@ -785,7 +792,7 @@ pub fn generate_hmac_key( /// RSA-Key-Size: 2048 /// EC-Curve: EcCurve::P_256 pub fn generate_attestation_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, algorithm: Algorithm, att_challenge: &[u8], ) -> binder::Result<KeyMetadata> { @@ -794,7 +801,7 @@ pub fn generate_attestation_key( if algorithm == Algorithm::RSA { let alias = "ks_rsa_attest_test_key"; let metadata = generate_rsa_key( - sec_level, + sl, Domain::APP, -1, Some(alias.to_string()), @@ -812,13 +819,9 @@ pub fn generate_attestation_key( .unwrap(); Ok(metadata) } else { - let metadata = generate_ec_attestation_key( - sec_level, - att_challenge, - Digest::SHA_2_256, - EcCurve::P_256, - ) - .unwrap(); + let metadata = + generate_ec_attestation_key(sl, att_challenge, Digest::SHA_2_256, EcCurve::P_256) + .unwrap(); Ok(metadata) } @@ -827,7 +830,7 @@ pub fn generate_attestation_key( /// Generate EC attestation key with the given /// curve, attestation-challenge and attestation-app-id. pub fn generate_ec_attestation_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, att_challenge: &[u8], digest: Digest, ec_curve: EcCurve, @@ -841,7 +844,7 @@ pub fn generate_ec_attestation_key( .digest(digest) .attestation_challenge(att_challenge.to_vec()); - let attestation_key_metadata = sec_level.generateKey( + let attestation_key_metadata = sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -860,6 +863,7 @@ pub fn generate_ec_attestation_key( assert!(attestation_key_metadata.certificateChain.is_some()); check_key_authorizations( + sl, &attestation_key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED, @@ -869,7 +873,7 @@ pub fn generate_ec_attestation_key( /// Generate EC-P-256 key and attest it with given attestation key. pub fn generate_ec_256_attested_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias: Option<String>, att_challenge: &[u8], attest_key: &KeyDescriptor, @@ -883,7 +887,8 @@ pub fn generate_ec_256_attested_key( .ec_curve(EcCurve::P_256) .attestation_challenge(att_challenge.to_vec()); - let ec_key_metadata = sec_level + let ec_key_metadata = sl + .binder .generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias, blob: None }, Some(attest_key), @@ -898,19 +903,25 @@ pub fn generate_ec_256_attested_key( // Shouldn't have an attestation record. assert!(ec_key_metadata.certificateChain.is_none()); - check_key_authorizations(&ec_key_metadata.authorizations, &ec_gen_params, KeyOrigin::GENERATED); + check_key_authorizations( + sl, + &ec_key_metadata.authorizations, + &ec_gen_params, + KeyOrigin::GENERATED, + ); Ok(ec_key_metadata) } /// Imports above defined RSA key - `RSA_2048_KEY` and validates imported key parameters. pub fn import_rsa_2048_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, import_params: AuthSetBuilder, ) -> binder::Result<KeyMetadata> { - let key_metadata = sec_level + let key_metadata = sl + .binder .importKey( &KeyDescriptor { domain, nspace, alias, blob: None }, None, @@ -923,7 +934,7 @@ pub fn import_rsa_2048_key( assert!(key_metadata.certificate.is_some()); assert!(key_metadata.certificateChain.is_none()); - check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + check_key_authorizations(sl, &key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( @@ -967,13 +978,14 @@ pub fn import_rsa_2048_key( /// Imports above defined EC key - `EC_P_256_KEY` and validates imported key parameters. pub fn import_ec_p_256_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, import_params: AuthSetBuilder, ) -> binder::Result<KeyMetadata> { - let key_metadata = sec_level + let key_metadata = sl + .binder .importKey( &KeyDescriptor { domain, nspace, alias, blob: None }, None, @@ -986,7 +998,7 @@ pub fn import_ec_p_256_key( assert!(key_metadata.certificate.is_some()); assert!(key_metadata.certificateChain.is_none()); - check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + check_key_authorizations(sl, &key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( @@ -1013,7 +1025,7 @@ pub fn import_ec_p_256_key( /// Import sample AES key and validate its key parameters. pub fn import_aes_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, @@ -1030,7 +1042,7 @@ pub fn import_aes_key( .purpose(KeyPurpose::DECRYPT) .padding_mode(PaddingMode::PKCS7); - let key_metadata = sec_level.importKey( + let key_metadata = sl.binder.importKey( &KeyDescriptor { domain, nspace, alias, blob: None }, None, &import_params, @@ -1038,7 +1050,7 @@ pub fn import_aes_key( AES_KEY, )?; - check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + check_key_authorizations(sl, &key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( @@ -1070,7 +1082,7 @@ pub fn import_aes_key( /// Import sample 3DES key and validate its key parameters. pub fn import_3des_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, @@ -1089,7 +1101,7 @@ pub fn import_3des_key( .purpose(KeyPurpose::DECRYPT) .padding_mode(PaddingMode::PKCS7); - let key_metadata = sec_level.importKey( + let key_metadata = sl.binder.importKey( &KeyDescriptor { domain, nspace, alias, blob: None }, None, &import_params, @@ -1097,7 +1109,7 @@ pub fn import_3des_key( TRIPLE_DES_KEY, )?; - check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + check_key_authorizations(sl, &key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( @@ -1132,7 +1144,7 @@ pub fn import_3des_key( /// Import sample HMAC key and validate its key parameters. pub fn import_hmac_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, @@ -1149,7 +1161,7 @@ pub fn import_hmac_key( .digest(Digest::SHA_2_256) .min_mac_length(256); - let key_metadata = sec_level.importKey( + let key_metadata = sl.binder.importKey( &KeyDescriptor { domain, nspace, alias, blob: None }, None, &import_params, @@ -1157,7 +1169,7 @@ pub fn import_hmac_key( HMAC_KEY, )?; - check_key_authorizations(&key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); + check_key_authorizations(sl, &key_metadata.authorizations, &import_params, KeyOrigin::IMPORTED); // Check below auths explicitly, they might not be addd in import parameters. assert!(check_key_param( @@ -1182,7 +1194,7 @@ pub fn import_hmac_key( /// Imports RSA encryption key with WRAP_KEY purpose. pub fn import_wrapping_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, wrapping_key_data: &[u8], wrapping_key_alias: Option<String>, ) -> binder::Result<KeyMetadata> { @@ -1199,7 +1211,7 @@ pub fn import_wrapping_key( .cert_not_before(0) .cert_not_after(253402300799000); - sec_level.importKey( + sl.binder.importKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: wrapping_key_alias, blob: None }, None, &wrapping_key_params, @@ -1210,7 +1222,7 @@ pub fn import_wrapping_key( /// Import wrapped key using given wrapping key. pub fn import_wrapped_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias: Option<String>, wrapping_key_metadata: &KeyMetadata, wrapped_key: Option<Vec<u8>>, @@ -1223,7 +1235,7 @@ pub fn import_wrapped_key( authenticatorId: 0, }]; - let key_metadata = sec_level.importWrappedKey( + let key_metadata = sl.binder.importWrappedKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias, blob: wrapped_key }, &wrapping_key_metadata.key, None, @@ -1236,14 +1248,14 @@ pub fn import_wrapped_key( /// Import wrapping key and then import wrapped key using wrapping key. pub fn import_wrapping_key_and_wrapped_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, wrapping_key_alias: Option<String>, wrapping_key_params: AuthSetBuilder, ) -> binder::Result<KeyMetadata> { - let wrapping_key_metadata = sec_level.importKey( + let wrapping_key_metadata = sl.binder.importKey( &KeyDescriptor { domain, nspace, alias: wrapping_key_alias, blob: None }, None, &wrapping_key_params, @@ -1251,12 +1263,12 @@ pub fn import_wrapping_key_and_wrapped_key( WRAPPING_KEY, )?; - import_wrapped_key(sec_level, alias, &wrapping_key_metadata, Some(WRAPPED_KEY.to_vec())) + import_wrapped_key(sl, alias, &wrapping_key_metadata, Some(WRAPPED_KEY.to_vec())) } /// Import given key material as AES-256-GCM-NONE transport key. pub fn import_transport_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, transport_key_alias: Option<String>, transport_key: &[u8], ) -> binder::Result<KeyMetadata> { @@ -1271,7 +1283,7 @@ pub fn import_transport_key( .purpose(KeyPurpose::ENCRYPT) .purpose(KeyPurpose::DECRYPT); - sec_level.importKey( + sl.binder.importKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: transport_key_alias, blob: None }, None, &transport_key_params, @@ -1282,7 +1294,7 @@ pub fn import_transport_key( /// Generate EC key with purpose AGREE_KEY. pub fn generate_ec_agree_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, ec_curve: EcCurve, digest: Digest, domain: Domain, @@ -1296,7 +1308,7 @@ pub fn generate_ec_agree_key( .digest(digest) .ec_curve(ec_curve); - match sec_level.generateKey( + match sl.binder.generateKey( &KeyDescriptor { domain, nspace, alias, blob: None }, None, &gen_params, @@ -1310,6 +1322,7 @@ pub fn generate_ec_agree_key( } check_key_authorizations( + sl, &key_metadata.authorizations, &gen_params, KeyOrigin::GENERATED, @@ -1322,7 +1335,7 @@ pub fn generate_ec_agree_key( /// Helper method to import AES keys `total_count` of times. pub fn import_aes_keys( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias_prefix: String, total_count: Range<i32>, ) -> binder::Result<HashSet<String>> { @@ -1334,7 +1347,7 @@ pub fn import_aes_keys( write!(alias, "{}_{}", alias_prefix, count).unwrap(); imported_key_aliases.insert(alias.clone()); - import_aes_key(sec_level, Domain::APP, -1, Some(alias))?; + import_aes_key(sl, Domain::APP, -1, Some(alias))?; } Ok(imported_key_aliases) @@ -1342,7 +1355,7 @@ pub fn import_aes_keys( /// Generate attested EC-P_256 key with device id attestation. pub fn generate_key_with_attest_id( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, algorithm: Algorithm, alias: Option<String>, att_challenge: &[u8], @@ -1405,7 +1418,7 @@ pub fn generate_key_with_attest_id( } } - sec_level.generateKey( + sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias, blob: None }, Some(attest_key), &ec_gen_params, @@ -1416,11 +1429,11 @@ pub fn generate_key_with_attest_id( /// Generate Key and validate key characteristics. pub fn generate_key( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, gen_params: &AuthSetBuilder, alias: &str, ) -> binder::Result<KeyMetadata> { - let key_metadata = sec_level.generateKey( + let key_metadata = sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -1474,19 +1487,19 @@ pub fn generate_key( assert!(!att_app_id.is_empty()); } } - check_key_authorizations(&key_metadata.authorizations, gen_params, KeyOrigin::GENERATED); + check_key_authorizations(sl, &key_metadata.authorizations, gen_params, KeyOrigin::GENERATED); Ok(key_metadata) } /// Generate a key using given authorizations and create an operation using the generated key. pub fn create_key_and_operation( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, gen_params: &AuthSetBuilder, op_params: &AuthSetBuilder, alias: &str, ) -> binder::Result<CreateOperationResponse> { - let key_metadata = generate_key(sec_level, gen_params, alias)?; + let key_metadata = generate_key(sl, gen_params, alias)?; - sec_level.createOperation(&key_metadata.key, op_params, false) + sl.binder.createOperation(&key_metadata.key, op_params, false) } diff --git a/keystore2/test_utils/lib.rs b/keystore2/test_utils/lib.rs index 8394ca1c..825657fd 100644 --- a/keystore2/test_utils/lib.rs +++ b/keystore2/test_utils/lib.rs @@ -19,7 +19,13 @@ use std::io::ErrorKind; use std::path::{Path, PathBuf}; use std::{env::temp_dir, ops::Deref}; -use android_system_keystore2::aidl::android::system::keystore2::IKeystoreService::IKeystoreService; +use android_system_keystore2::aidl::android::system::keystore2::{ + IKeystoreService::IKeystoreService, + IKeystoreSecurityLevel::IKeystoreSecurityLevel, +}; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + ErrorCode::ErrorCode, IKeyMintDevice::IKeyMintDevice, SecurityLevel::SecurityLevel, +}; use android_security_authorization::aidl::android::security::authorization::IKeystoreAuthorization::IKeystoreAuthorization; pub mod authorizations; @@ -123,3 +129,68 @@ pub fn get_keystore_service() -> binder::Strong<dyn IKeystoreService> { pub fn get_keystore_auth_service() -> binder::Strong<dyn IKeystoreAuthorization> { binder::get_interface(AUTH_SERVICE_NAME).unwrap() } + +/// Security level-specific data. +pub struct SecLevel { + /// Binder connection for the top-level service. + pub keystore2: binder::Strong<dyn IKeystoreService>, + /// Binder connection for the security level. + pub binder: binder::Strong<dyn IKeystoreSecurityLevel>, + /// Security level. + pub level: SecurityLevel, +} + +impl SecLevel { + /// Return security level data for TEE. + pub fn tee() -> Self { + let level = SecurityLevel::TRUSTED_ENVIRONMENT; + let keystore2 = get_keystore_service(); + let binder = + keystore2.getSecurityLevel(level).expect("TEE security level should always be present"); + Self { keystore2, binder, level } + } + /// Return security level data for StrongBox, if present. + pub fn strongbox() -> Option<Self> { + let level = SecurityLevel::STRONGBOX; + let keystore2 = get_keystore_service(); + match key_generations::map_ks_error(keystore2.getSecurityLevel(level)) { + Ok(binder) => Some(Self { keystore2, binder, level }), + Err(e) => { + assert_eq!(e, key_generations::Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE)); + None + } + } + } + /// Indicate whether this security level is a KeyMint implementation (not Keymaster). + pub fn is_keymint(&self) -> bool { + let instance = match self.level { + SecurityLevel::TRUSTED_ENVIRONMENT => "default", + SecurityLevel::STRONGBOX => "strongbox", + l => panic!("unexpected level {l:?}"), + }; + let name = format!("android.hardware.security.keymint.IKeyMintDevice/{instance}"); + binder::is_declared(&name).expect("Could not check for declared keymint interface") + } + + /// Indicate whether this security level is a Keymaster implementation (not KeyMint). + pub fn is_keymaster(&self) -> bool { + !self.is_keymint() + } + + /// Get KeyMint version. + /// Returns 0 if the underlying device is Keymaster not KeyMint. + pub fn get_keymint_version(&self) -> i32 { + let instance = match self.level { + SecurityLevel::TRUSTED_ENVIRONMENT => "default", + SecurityLevel::STRONGBOX => "strongbox", + l => panic!("unexpected level {l:?}"), + }; + let name = format!("android.hardware.security.keymint.IKeyMintDevice/{instance}"); + if binder::is_declared(&name).expect("Could not check for declared keymint interface") { + let km: binder::Strong<dyn IKeyMintDevice> = binder::get_interface(&name).unwrap(); + km.getInterfaceVersion().unwrap() + } else { + 0 + } + } +} diff --git a/keystore2/test_utils/run_as.rs b/keystore2/test_utils/run_as.rs index be643b6b..d39d0697 100644 --- a/keystore2/test_utils/run_as.rs +++ b/keystore2/test_utils/run_as.rs @@ -29,13 +29,14 @@ use keystore2_selinux as selinux; use nix::sys::wait::{waitpid, WaitStatus}; use nix::unistd::{ - close, fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write, - ForkResult, Gid, Pid, Uid, + fork, pipe as nix_pipe, read as nix_read, setgid, setuid, write as nix_write, ForkResult, Gid, + Pid, Uid, }; use serde::{de::DeserializeOwned, Serialize}; use std::io::{Read, Write}; use std::marker::PhantomData; -use std::os::unix::io::RawFd; +use std::os::fd::AsRawFd; +use std::os::fd::OwnedFd; fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) { setgid(gid).expect("Failed to set GID. This test might need more privileges."); @@ -48,35 +49,23 @@ fn transition(se_context: selinux::Context, uid: Uid, gid: Gid) { /// PipeReader is a simple wrapper around raw pipe file descriptors. /// It takes ownership of the file descriptor and closes it on drop. It provides `read_all`, which /// reads from the pipe into an expending vector, until no more data can be read. -struct PipeReader(RawFd); +struct PipeReader(OwnedFd); impl Read for PipeReader { fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { - let bytes = nix_read(self.0, buf)?; + let bytes = nix_read(self.0.as_raw_fd(), buf)?; Ok(bytes) } } -impl Drop for PipeReader { - fn drop(&mut self) { - close(self.0).expect("Failed to close reader pipe fd."); - } -} - /// PipeWriter is a simple wrapper around raw pipe file descriptors. /// It takes ownership of the file descriptor and closes it on drop. It provides `write`, which /// writes the given buffer into the pipe, returning the number of bytes written. -struct PipeWriter(RawFd); - -impl Drop for PipeWriter { - fn drop(&mut self) { - close(self.0).expect("Failed to close writer pipe fd."); - } -} +struct PipeWriter(OwnedFd); impl Write for PipeWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { - let written = nix_write(self.0, buf)?; + let written = nix_write(&self.0, buf)?; Ok(written) } diff --git a/keystore2/tests/Android.bp b/keystore2/tests/Android.bp index 01ea7465..ca9f5e36 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 @@ rust_test { 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/AndroidTest.xml b/keystore2/tests/AndroidTest.xml index 7db36f7e..dde18a9b 100644 --- a/keystore2/tests/AndroidTest.xml +++ b/keystore2/tests/AndroidTest.xml @@ -14,6 +14,7 @@ limitations under the License. --> <configuration description="Config to run keystore2_client_tests device tests."> + <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" /> <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"> </target_preparer> diff --git a/keystore2/tests/keystore2_client_3des_key_tests.rs b/keystore2/tests/keystore2_client_3des_key_tests.rs index eda24db0..29f16177 100644 --- a/keystore2/tests/keystore2_client_3des_key_tests.rs +++ b/keystore2/tests/keystore2_client_3des_key_tests.rs @@ -12,26 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::keystore2_client_test_utils::{ + perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, +}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, - PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, + PaddingMode::PaddingMode, }; - use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, -}; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, -}; - -use crate::keystore2_client_test_utils::{ - perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, + Domain::Domain, KeyDescriptor::KeyDescriptor, }; +use keystore2_test_utils::{authorizations, key_generations, key_generations::Error, SecLevel}; /// Generate a 3DES key. Create encryption and decryption operations using the generated key. fn create_3des_key_and_operation( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, padding_mode: PaddingMode, block_mode: BlockMode, nonce: &mut Option<Vec<u8>>, @@ -39,7 +34,7 @@ fn create_3des_key_and_operation( let alias = format!("ks_3des_test_key_{}{}", block_mode.0, padding_mode.0); let key_metadata = key_generations::generate_sym_key( - sec_level, + sl, Algorithm::TRIPLE_DES, 168, &alias, @@ -50,7 +45,7 @@ fn create_3des_key_and_operation( // Encrypts `SAMPLE_PLAIN_TEXT` whose length is multiple of DES block size. let cipher_text = perform_sample_sym_key_encrypt_op( - sec_level, + &sl.binder, padding_mode, block_mode, nonce, @@ -60,7 +55,7 @@ fn create_3des_key_and_operation( assert!(cipher_text.is_some()); let plain_text = perform_sample_sym_key_decrypt_op( - sec_level, + &sl.binder, &cipher_text.unwrap(), padding_mode, block_mode, @@ -80,16 +75,15 @@ fn create_3des_key_and_operation( /// Test should generate keys and perform operation successfully. #[test] fn keystore2_3des_ecb_cbc_generate_key_success() { - let keystore2 = get_keystore_service(); let block_modes = [BlockMode::ECB, BlockMode::CBC]; let padding_modes = [PaddingMode::PKCS7, PaddingMode::NONE]; - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for block_mode in block_modes { for padding_mode in padding_modes { assert_eq!( Ok(()), - create_3des_key_and_operation(&sec_level, padding_mode, block_mode, &mut None) + create_3des_key_and_operation(&sl, padding_mode, block_mode, &mut None) ); } } @@ -99,13 +93,12 @@ fn keystore2_3des_ecb_cbc_generate_key_success() { /// an error code `UNSUPPORTED_KEY_SIZE`. #[test] fn keystore2_3des_key_fails_unsupported_key_size() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "3des_key_test_invalid_1"; let invalid_key_size = 128; let result = key_generations::map_ks_error(key_generations::generate_sym_key( - &sec_level, + &sl, Algorithm::TRIPLE_DES, invalid_key_size, alias, @@ -122,8 +115,7 @@ fn keystore2_3des_key_fails_unsupported_key_size() { /// `UNSUPPORTED_PADDING_MODE`. #[test] fn keystore2_3des_key_fails_missing_padding() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "3des_key_test_missing_padding"; let gen_params = authorizations::AuthSetBuilder::new() @@ -134,7 +126,8 @@ fn keystore2_3des_key_fails_missing_padding() { .key_size(168) .block_mode(BlockMode::ECB); - let key_metadata = sec_level + let key_metadata = sl + .binder .generateKey( &KeyDescriptor { domain: Domain::APP, @@ -153,7 +146,7 @@ fn keystore2_3des_key_fails_missing_padding() { .purpose(KeyPurpose::ENCRYPT) .block_mode(BlockMode::ECB); - let result = key_generations::map_ks_error(sec_level.createOperation( + let result = key_generations::map_ks_error(sl.binder.createOperation( &key_metadata.key, &op_params, false, @@ -166,12 +159,11 @@ fn keystore2_3des_key_fails_missing_padding() { /// multiple of the DES block size. #[test] fn keystore2_3des_key_encrypt_fails_invalid_input_length() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "3des_key_test_invalid_input_len"; let key_metadata = key_generations::generate_sym_key( - &sec_level, + &sl, Algorithm::TRIPLE_DES, 168, alias, @@ -186,7 +178,8 @@ fn keystore2_3des_key_encrypt_fails_invalid_input_length() { .padding_mode(PaddingMode::NONE) .block_mode(BlockMode::ECB); - let op_response = sec_level + let op_response = sl + .binder .createOperation(&key_metadata.key, &op_params, false) .expect("Error in creation of operation using rebound key."); assert!(op_response.iOperation.is_some()); @@ -204,11 +197,10 @@ fn keystore2_3des_key_encrypt_fails_invalid_input_length() { /// error code `UNSUPPORTED_BLOCK_MODE`. #[test] fn keystore2_3des_key_fails_unsupported_block_mode() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let result = key_generations::map_ks_error(create_3des_key_and_operation( - &sec_level, + &sl, PaddingMode::NONE, BlockMode::CTR, &mut None, diff --git a/keystore2/tests/keystore2_client_aes_key_tests.rs b/keystore2/tests/keystore2_client_aes_key_tests.rs index 313f596f..9f85c383 100644 --- a/keystore2/tests/keystore2_client_aes_key_tests.rs +++ b/keystore2/tests/keystore2_client_aes_key_tests.rs @@ -12,26 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::keystore2_client_test_utils::{ + perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, +}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, - PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, + PaddingMode::PaddingMode, }; - use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, -}; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, -}; - -use crate::keystore2_client_test_utils::{ - perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, + Domain::Domain, KeyDescriptor::KeyDescriptor, }; +use keystore2_test_utils::{authorizations, key_generations, key_generations::Error, SecLevel}; /// Generate a AES key. Create encrypt and decrypt operations using the generated key. fn create_aes_key_and_operation( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, key_size: i32, padding_mode: PaddingMode, block_mode: BlockMode, @@ -42,7 +37,7 @@ fn create_aes_key_and_operation( let alias = format!("ks_aes_test_key_{}{}{}", key_size, block_mode.0, padding_mode.0); let key_metadata = key_generations::generate_sym_key( - sec_level, + sl, Algorithm::AES, key_size, &alias, @@ -52,7 +47,7 @@ fn create_aes_key_and_operation( )?; let cipher_text = perform_sample_sym_key_encrypt_op( - sec_level, + &sl.binder, padding_mode, block_mode, nonce, @@ -63,7 +58,7 @@ fn create_aes_key_and_operation( assert!(cipher_text.is_some()); let plain_text = perform_sample_sym_key_decrypt_op( - sec_level, + &sl.binder, &cipher_text.unwrap(), padding_mode, block_mode, @@ -83,19 +78,18 @@ fn create_aes_key_and_operation( /// Test should generate keys and perform operation successfully. #[test] fn keystore2_aes_ecb_cbc_generate_key() { - let keystore2 = get_keystore_service(); let key_sizes = [128, 256]; let block_modes = [BlockMode::ECB, BlockMode::CBC]; let padding_modes = [PaddingMode::PKCS7, PaddingMode::NONE]; - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for key_size in key_sizes { for block_mode in block_modes { for padding_mode in padding_modes { assert_eq!( Ok(()), create_aes_key_and_operation( - &sec_level, + &sl, key_size, padding_mode, block_mode, @@ -115,16 +109,14 @@ fn keystore2_aes_ecb_cbc_generate_key() { /// Test should generate keys and perform operation successfully. #[test] fn keystore2_aes_ctr_gcm_generate_key_success() { - let keystore2 = get_keystore_service(); let key_sizes = [128, 256]; let key_params = [(BlockMode::CTR, None, None), (BlockMode::GCM, Some(128), Some(128))]; - - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for key_size in key_sizes { for (block_mode, mac_len, min_mac_len) in key_params { let result = key_generations::map_ks_error(create_aes_key_and_operation( - &sec_level, + &sl, key_size, PaddingMode::NONE, block_mode, @@ -145,16 +137,14 @@ fn keystore2_aes_ctr_gcm_generate_key_success() { /// with an error code `INCOMPATIBLE_PADDING_MODE`. #[test] fn keystore2_aes_ctr_gcm_generate_key_fails_incompatible() { - let keystore2 = get_keystore_service(); let key_sizes = [128, 256]; let key_params = [(BlockMode::CTR, None, None), (BlockMode::GCM, Some(128), Some(128))]; - - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for key_size in key_sizes { for (block_mode, mac_len, min_mac_len) in key_params { let result = key_generations::map_ks_error(create_aes_key_and_operation( - &sec_level, + &sl, key_size, PaddingMode::PKCS7, block_mode, @@ -173,12 +163,11 @@ fn keystore2_aes_ctr_gcm_generate_key_fails_incompatible() { /// an error code `UNSUPPORTED_KEY_SIZE`. #[test] fn keystore2_aes_key_fails_unsupported_key_size() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "aes_key_test_invalid_1"; let result = key_generations::map_ks_error(key_generations::generate_sym_key( - &sec_level, + &sl, Algorithm::AES, 1024, alias, @@ -194,12 +183,11 @@ fn keystore2_aes_key_fails_unsupported_key_size() { /// Test should fail to generate a key with an error code `MISSING_MIN_MAC_LENGTH`. #[test] fn keystore2_aes_gcm_key_fails_missing_min_mac_len() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "aes_key_test_invalid_1"; let result = key_generations::map_ks_error(key_generations::generate_sym_key( - &sec_level, + &sl, Algorithm::AES, 128, alias, @@ -215,8 +203,7 @@ fn keystore2_aes_gcm_key_fails_missing_min_mac_len() { /// an operation with `UNSUPPORTED_BLOCK_MODE` error code. #[test] fn keystore2_aes_key_op_fails_multi_block_modes() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "aes_key_test_invalid_1"; let gen_params = authorizations::AuthSetBuilder::new() @@ -229,7 +216,8 @@ fn keystore2_aes_key_op_fails_multi_block_modes() { .block_mode(BlockMode::CBC) .padding_mode(PaddingMode::NONE); - let key_metadata = sec_level + let key_metadata = sl + .binder .generateKey( &KeyDescriptor { domain: Domain::APP, @@ -250,7 +238,7 @@ fn keystore2_aes_key_op_fails_multi_block_modes() { .block_mode(BlockMode::CBC) .padding_mode(PaddingMode::NONE); - let result = key_generations::map_ks_error(sec_level.createOperation( + let result = key_generations::map_ks_error(sl.binder.createOperation( &key_metadata.key, &op_params, false, @@ -263,8 +251,7 @@ fn keystore2_aes_key_op_fails_multi_block_modes() { /// an operation with `UNSUPPORTED_PADDING_MODE` error code. #[test] fn keystore2_aes_key_op_fails_multi_padding_modes() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "aes_key_test_invalid_1"; let gen_params = authorizations::AuthSetBuilder::new() @@ -277,7 +264,8 @@ fn keystore2_aes_key_op_fails_multi_padding_modes() { .padding_mode(PaddingMode::PKCS7) .padding_mode(PaddingMode::NONE); - let key_metadata = sec_level + let key_metadata = sl + .binder .generateKey( &KeyDescriptor { domain: Domain::APP, @@ -298,7 +286,7 @@ fn keystore2_aes_key_op_fails_multi_padding_modes() { .padding_mode(PaddingMode::PKCS7) .padding_mode(PaddingMode::NONE); - let result = key_generations::map_ks_error(sec_level.createOperation( + let result = key_generations::map_ks_error(sl.binder.createOperation( &key_metadata.key, &op_params, false, @@ -312,12 +300,11 @@ fn keystore2_aes_key_op_fails_multi_padding_modes() { /// `INCOMPATIBLE_PADDING_MODE` error code. #[test] fn keystore2_aes_key_op_fails_incompatible_padding() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "aes_key_test_invalid_1"; let key_metadata = key_generations::generate_sym_key( - &sec_level, + &sl, Algorithm::AES, 128, alias, @@ -328,7 +315,7 @@ fn keystore2_aes_key_op_fails_incompatible_padding() { .unwrap(); let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op( - &sec_level, + &sl.binder, PaddingMode::PKCS7, BlockMode::ECB, &mut None, @@ -344,12 +331,11 @@ fn keystore2_aes_key_op_fails_incompatible_padding() { /// `INCOMPATIBLE_BLOCK_MODE` error code. #[test] fn keystore2_aes_key_op_fails_incompatible_blockmode() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "aes_key_test_invalid_1"; let key_metadata = key_generations::generate_sym_key( - &sec_level, + &sl, Algorithm::AES, 128, alias, @@ -360,7 +346,7 @@ fn keystore2_aes_key_op_fails_incompatible_blockmode() { .unwrap(); let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op( - &sec_level, + &sl.binder, PaddingMode::NONE, BlockMode::CBC, &mut None, @@ -376,13 +362,12 @@ fn keystore2_aes_key_op_fails_incompatible_blockmode() { /// `MISSING_MAC_LENGTH` error code. #[test] fn keystore2_aes_gcm_op_fails_missing_mac_len() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let mac_len = None; let min_mac_len = Some(128); let result = key_generations::map_ks_error(create_aes_key_and_operation( - &sec_level, + &sl, 128, PaddingMode::NONE, BlockMode::GCM, @@ -404,13 +389,12 @@ fn keystore2_aes_gcm_op_fails_missing_mac_len() { /// an operation with `INVALID_MAC_LENGTH` error code. #[test] fn keystore2_aes_gcm_op_fails_invalid_mac_len() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let mac_len = Some(96); let min_mac_len = Some(104); let result = key_generations::map_ks_error(create_aes_key_and_operation( - &sec_level, + &sl, 128, PaddingMode::NONE, BlockMode::GCM, @@ -427,11 +411,10 @@ fn keystore2_aes_gcm_op_fails_invalid_mac_len() { /// `UNSUPPORTED_MAC_LENGTH` error code. #[test] fn keystore2_aes_gcm_op_fails_unsupported_mac_len() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let result = key_generations::map_ks_error(create_aes_key_and_operation( - &sec_level, + &sl, 128, PaddingMode::NONE, BlockMode::GCM, @@ -448,13 +431,12 @@ fn keystore2_aes_gcm_op_fails_unsupported_mac_len() { /// `CALLER_NONCE_PROHIBITED` error code. #[test] fn keystore2_aes_key_op_fails_nonce_prohibited() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "aes_key_test_nonce_1"; let mut nonce = Some(vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); let key_metadata = key_generations::generate_sym_key( - &sec_level, + &sl, Algorithm::AES, 128, alias, @@ -465,7 +447,7 @@ fn keystore2_aes_key_op_fails_nonce_prohibited() { .unwrap(); let result = key_generations::map_ks_error(perform_sample_sym_key_encrypt_op( - &sec_level, + &sl.binder, PaddingMode::NONE, BlockMode::CBC, &mut nonce, diff --git a/keystore2/tests/keystore2_client_attest_key_tests.rs b/keystore2/tests/keystore2_client_attest_key_tests.rs index 454248a3..a303ee89 100644 --- a/keystore2/tests/keystore2_client_attest_key_tests.rs +++ b/keystore2/tests/keystore2_client_attest_key_tests.rs @@ -12,8 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use nix::unistd::getuid; - +use crate::keystore2_client_test_utils::{ + app_attest_key_feature_exists, device_id_attestation_feature_exists, get_attest_id_value, + is_second_imei_id_attestation_required, skip_device_id_attest_tests, +}; +use crate::{ + skip_device_id_attestation_tests, skip_test_if_no_app_attest_key_feature, + skip_test_if_no_device_id_attestation_feature, +}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, @@ -23,22 +29,12 @@ use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, }; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, -}; - use keystore2_test_utils::ffi_test_utils::{get_value_from_attest_record, validate_certchain}; - -use crate::{ - skip_device_id_attestation_tests, skip_test_if_no_app_attest_key_feature, - skip_test_if_no_device_id_attestation_feature, -}; - -use crate::keystore2_client_test_utils::{ - app_attest_key_feature_exists, device_id_attestation_feature_exists, get_attest_id_value, - is_second_imei_id_attestation_required, skip_device_id_attest_tests, +use keystore2_test_utils::{ + authorizations, key_generations, key_generations::Error, run_as, SecLevel, }; +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; /// Generate RSA and EC attestation keys and use them for signing RSA-signing keys. /// Test should be able to generate attestation keys and use them successfully. @@ -46,14 +42,13 @@ use crate::keystore2_client_test_utils::{ fn keystore2_attest_rsa_signing_key_success() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; for algo in [Algorithm::RSA, Algorithm::EC] { // Create attestation key. let attestation_key_metadata = - key_generations::generate_attestation_key(&sec_level, algo, att_challenge).unwrap(); + key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap(); let mut cert_chain: Vec<u8> = Vec::new(); cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); @@ -63,7 +58,7 @@ fn keystore2_attest_rsa_signing_key_success() { // Create RSA signing key and use attestation key to sign it. let sign_key_alias = format!("ks_attest_rsa_signing_key_{}", getuid()); let sign_key_metadata = key_generations::generate_rsa_key( - &sec_level, + &sl, Domain::APP, -1, Some(sign_key_alias), @@ -94,14 +89,13 @@ fn keystore2_attest_rsa_signing_key_success() { fn keystore2_attest_rsa_encrypt_key_success() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; for algo in [Algorithm::RSA, Algorithm::EC] { // Create attestation key. let attestation_key_metadata = - key_generations::generate_attestation_key(&sec_level, algo, att_challenge).unwrap(); + key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap(); let mut cert_chain: Vec<u8> = Vec::new(); cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); @@ -111,7 +105,7 @@ fn keystore2_attest_rsa_encrypt_key_success() { // Create RSA encrypt/decrypt key and use attestation key to sign it. let decrypt_key_alias = format!("ks_attest_rsa_encrypt_key_{}", getuid()); let decrypt_key_metadata = key_generations::generate_rsa_key( - &sec_level, + &sl, Domain::APP, -1, Some(decrypt_key_alias), @@ -143,14 +137,13 @@ fn keystore2_attest_rsa_encrypt_key_success() { fn keystore2_attest_ec_key_success() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; for algo in [Algorithm::RSA, Algorithm::EC] { // Create attestation key. let attestation_key_metadata = - key_generations::generate_attestation_key(&sec_level, algo, att_challenge).unwrap(); + key_generations::generate_attestation_key(&sl, algo, att_challenge).unwrap(); let mut cert_chain: Vec<u8> = Vec::new(); cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); @@ -160,7 +153,7 @@ fn keystore2_attest_ec_key_success() { // Create EC key and use attestation key to sign it. let ec_key_alias = format!("ks_ec_attested_test_key_{}", getuid()); let ec_key_metadata = key_generations::generate_ec_256_attested_key( - &sec_level, + &sl, Some(ec_key_alias), att_challenge, &attestation_key_metadata.key, @@ -183,13 +176,19 @@ fn keystore2_attest_ec_key_success() { fn keystore2_attest_rsa_signing_key_with_ec_25519_key_success() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); + if sl.get_keymint_version() < 2 { + // Curve 25519 was included in version 2 of the KeyMint interface. + // For device with KeyMint-V1 or Keymaster in backend, emulated Ed25519 key can't attest + // to a "real" RSA key. + return; + } + let att_challenge: &[u8] = b"foo"; // Create EcCurve::CURVE_25519 attestation key. let attestation_key_metadata = key_generations::generate_ec_attestation_key( - &sec_level, + &sl, att_challenge, Digest::NONE, EcCurve::CURVE_25519, @@ -204,7 +203,7 @@ fn keystore2_attest_rsa_signing_key_with_ec_25519_key_success() { // Create RSA signing key and use attestation key to sign it. let sign_key_alias = format!("ksrsa_attested_sign_test_key_{}", getuid()); let sign_key_metadata = key_generations::generate_rsa_key( - &sec_level, + &sl, Domain::APP, -1, Some(sign_key_alias), @@ -233,9 +232,14 @@ fn keystore2_attest_rsa_signing_key_with_ec_25519_key_success() { #[test] fn keystore2_generate_rsa_attest_key_with_multi_purpose_fail() { skip_test_if_no_app_attest_key_feature!(); - - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); + if sl.get_keymint_version() < 2 { + // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined + // with other key purposes. However, this was not checked at the time + // so we can only be strict about checking this for implementations of KeyMint + // version 2 and above. + return; + } let digest = Digest::SHA_2_256; let padding = PaddingMode::RSA_PKCS1_1_5_SIGN; @@ -255,7 +259,7 @@ fn keystore2_generate_rsa_attest_key_with_multi_purpose_fail() { .rsa_public_exponent(65537) .padding_mode(padding); - let result = key_generations::map_ks_error(sec_level.generateKey( + let result = key_generations::map_ks_error(sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -276,9 +280,14 @@ fn keystore2_generate_rsa_attest_key_with_multi_purpose_fail() { #[test] fn keystore2_ec_attest_key_with_multi_purpose_fail() { skip_test_if_no_app_attest_key_feature!(); - - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); + if sl.get_keymint_version() < 2 { + // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined + // with other key purposes. However, this was not checked at the time + // so we can only be strict about checking this for implementations of KeyMint + // version 2 and above. + return; + } let attest_key_alias = format!("ks_ec_attest_multipurpose_key_{}", getuid()); @@ -291,7 +300,7 @@ fn keystore2_ec_attest_key_with_multi_purpose_fail() { .digest(Digest::SHA_2_256) .ec_curve(EcCurve::P_256); - let result = key_generations::map_ks_error(sec_level.generateKey( + let result = key_generations::map_ks_error(sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -314,14 +323,12 @@ fn keystore2_ec_attest_key_with_multi_purpose_fail() { fn keystore2_attest_key_fails_missing_challenge() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; // Create RSA attestation key. let attestation_key_metadata = - key_generations::generate_attestation_key(&sec_level, Algorithm::RSA, att_challenge) - .unwrap(); + key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap(); let mut cert_chain: Vec<u8> = Vec::new(); cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); @@ -331,7 +338,7 @@ fn keystore2_attest_key_fails_missing_challenge() { // Try to attest RSA signing key without providing attestation challenge. let sign_key_alias = format!("ksrsa_attested_test_key_missing_challenge{}", getuid()); let result = key_generations::map_ks_error(key_generations::generate_rsa_key( - &sec_level, + &sl, Domain::APP, -1, Some(sign_key_alias), @@ -357,24 +364,18 @@ fn keystore2_attest_key_fails_missing_challenge() { fn keystore2_attest_rsa_key_with_non_attest_key_fails_incompat_purpose_error() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; let alias = format!("non_attest_key_{}", getuid()); - let non_attest_key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, - Domain::APP, - -1, - Some(alias), - None, - ) - .unwrap(); + let non_attest_key_metadata = + key_generations::generate_ec_p256_signing_key(&sl, Domain::APP, -1, Some(alias), None) + .unwrap(); // Try to generate RSA signing key with non-attestation key to sign it. let sign_key_alias = format!("ksrsa_attested_sign_test_key_non_attest_{}", getuid()); let result = key_generations::map_ks_error(key_generations::generate_rsa_key( - &sec_level, + &sl, Domain::APP, -1, Some(sign_key_alias), @@ -399,13 +400,12 @@ fn keystore2_attest_rsa_key_with_non_attest_key_fails_incompat_purpose_error() { fn keystore2_attest_rsa_key_with_symmetric_key_fails_sys_error() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; let alias = "aes_attest_key"; let sym_key_metadata = key_generations::generate_sym_key( - &sec_level, + &sl, Algorithm::AES, 128, alias, @@ -418,7 +418,7 @@ fn keystore2_attest_rsa_key_with_symmetric_key_fails_sys_error() { // Try to generate RSA signing key with symmetric key as attestation key. let sign_key_alias = format!("ksrsa_attested_sign_test_key_sym_attest_{}", getuid()); let result = key_generations::map_ks_error(key_generations::generate_rsa_key( - &sec_level, + &sl, Domain::APP, -1, Some(sign_key_alias), @@ -444,14 +444,12 @@ fn keystore2_attest_rsa_key_with_symmetric_key_fails_sys_error() { fn keystore2_attest_symmetric_key_fail_sys_error() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; // Create attestation key. let attestation_key_metadata = - key_generations::generate_attestation_key(&sec_level, Algorithm::RSA, att_challenge) - .unwrap(); + key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap(); let mut cert_chain: Vec<u8> = Vec::new(); cert_chain.extend(attestation_key_metadata.certificate.as_ref().unwrap()); @@ -470,7 +468,8 @@ fn keystore2_attest_symmetric_key_fail_sys_error() { .attestation_challenge(att_challenge.to_vec()); let alias = format!("ks_test_sym_key_attest_{}", getuid()); - let aes_key_metadata = sec_level + let aes_key_metadata = sl + .binder .generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, Some(&attestation_key_metadata.key), @@ -525,22 +524,21 @@ fn generate_attested_key_with_device_attest_ids(algorithm: Algorithm) { skip_test_if_no_device_id_attestation_feature!(); skip_device_id_attestation_tests!(); skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; let attest_key_metadata = - key_generations::generate_attestation_key(&sec_level, algorithm, att_challenge).unwrap(); + key_generations::generate_attestation_key(&sl, algorithm, att_challenge).unwrap(); - let attest_id_params = get_attestation_ids(&keystore2); + let attest_id_params = get_attestation_ids(&sl.keystore2); for (attest_id, value) in attest_id_params { // Create RSA/EC key and use attestation key to sign it. let key_alias = format!("ks_attested_test_key_{}", getuid()); let key_metadata = key_generations::map_ks_error(key_generations::generate_key_with_attest_id( - &sec_level, + &sl, algorithm, Some(key_alias), att_challenge, @@ -585,20 +583,15 @@ fn keystore2_attest_rsa_attestation_id() { fn keystore2_attest_key_fails_with_invalid_attestation_id() { skip_test_if_no_device_id_attestation_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let digest = Digest::SHA_2_256; let att_challenge: &[u8] = b"foo"; // Create EC-Attestation key. - let attest_key_metadata = key_generations::generate_ec_attestation_key( - &sec_level, - att_challenge, - digest, - EcCurve::P_256, - ) - .unwrap(); + let attest_key_metadata = + key_generations::generate_ec_attestation_key(&sl, att_challenge, digest, EcCurve::P_256) + .unwrap(); let attest_id_params = vec![ (Tag::ATTESTATION_ID_BRAND, b"invalid-brand".to_vec()), @@ -614,7 +607,7 @@ fn keystore2_attest_key_fails_with_invalid_attestation_id() { // Create EC key and use attestation key to sign it. let ec_key_alias = format!("ks_ec_attested_test_key_fail_{}{}", getuid(), digest.0); let result = key_generations::map_ks_error(key_generations::generate_key_with_attest_id( - &sec_level, + &sl, Algorithm::EC, Some(ec_key_alias), att_challenge, @@ -638,20 +631,18 @@ fn keystore2_attest_key_without_attestation_id_support_fails_with_cannot_attest_ return; } - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let att_challenge: &[u8] = b"foo"; let attest_key_metadata = - key_generations::generate_attestation_key(&sec_level, Algorithm::RSA, att_challenge) - .unwrap(); + key_generations::generate_attestation_key(&sl, Algorithm::RSA, att_challenge).unwrap(); - let attest_id_params = get_attestation_ids(&keystore2); + let attest_id_params = get_attestation_ids(&sl.keystore2); for (attest_id, value) in attest_id_params { // Create RSA/EC key and use attestation key to sign it. let key_alias = format!("ks_attested_test_key_{}", getuid()); let result = key_generations::map_ks_error(key_generations::generate_key_with_attest_id( - &sec_level, + &sl, Algorithm::RSA, Some(key_alias), att_challenge, @@ -666,3 +657,49 @@ fn keystore2_attest_key_without_attestation_id_support_fails_with_cannot_attest_ assert_eq!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); } } + +/// Try to generate an attestation key from user context with UID other than AID_SYSTEM or AID_ROOT +/// and also there is no package name associated with it. In such case key generation should fail +/// while collecting Attestation Application ID (AAID) from AAID provider service and keystore +/// should return error response code - `GET_ATTESTATION_APPLICATION_ID_FAILED`. +#[test] +fn keystore2_generate_attested_key_fail_to_get_aaid() { + static APP_USER_CTX: &str = "u:r:untrusted_app:s0:c91,c256,c10,c20"; + const USER_ID: u32 = 99; + const APPLICATION_ID: u32 = 10001; + static APP_UID: u32 = USER_ID * AID_USER_OFFSET + APPLICATION_ID; + static APP_GID: u32 = APP_UID; + + // SAFETY: The test is run in a separate process with no other threads. + unsafe { + run_as::run_as(APP_USER_CTX, Uid::from_raw(APP_UID), Gid::from_raw(APP_GID), || { + skip_test_if_no_app_attest_key_feature!(); + let sl = SecLevel::tee(); + let att_challenge: &[u8] = b"foo"; + let alias = format!("ks_attest_rsa_encrypt_key_aaid_fail{}", getuid()); + + let result = key_generations::map_ks_error(key_generations::generate_rsa_key( + &sl, + Domain::APP, + -1, + Some(alias), + &key_generations::KeyParams { + key_size: 2048, + purpose: vec![KeyPurpose::ATTEST_KEY], + padding: Some(PaddingMode::RSA_PKCS1_1_5_SIGN), + digest: Some(Digest::SHA_2_256), + mgf_digest: None, + block_mode: None, + att_challenge: Some(att_challenge.to_vec()), + }, + None, + )); + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + Error::Rc(ResponseCode::GET_ATTESTATION_APPLICATION_ID_FAILED) + ); + }) + }; +} diff --git a/keystore2/tests/keystore2_client_authorizations_tests.rs b/keystore2/tests/keystore2_client_authorizations_tests.rs index 0fde7aff..09817830 100644 --- a/keystore2/tests/keystore2_client_authorizations_tests.rs +++ b/keystore2/tests/keystore2_client_authorizations_tests.rs @@ -12,49 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::time::SystemTime; - -use openssl::bn::{BigNum, MsbOption}; -use openssl::x509::X509NameBuilder; - +use crate::keystore2_client_test_utils::{ + app_attest_key_feature_exists, delete_app_key, perform_sample_asym_sign_verify_op, + perform_sample_hmac_sign_verify_op, perform_sample_sym_key_decrypt_op, + perform_sample_sym_key_encrypt_op, verify_certificate_serial_num, + verify_certificate_subject_name, SAMPLE_PLAIN_TEXT, +}; +use crate::{require_keymint, skip_test_if_no_app_attest_key_feature}; +use aconfig_android_hardware_biometrics_rust; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, Tag::Tag, }; - -use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, - KeyMetadata::KeyMetadata, ResponseCode::ResponseCode, -}; - -use aconfig_android_hardware_biometrics_rust; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - HardwareAuthToken::HardwareAuthToken, - HardwareAuthenticatorType::HardwareAuthenticatorType + HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType, }; -use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::Timestamp::Timestamp; - -use keystore2_test_utils::{ - authorizations, get_keystore_auth_service, get_keystore_service, key_generations, - key_generations::Error, +use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{ + Timestamp::Timestamp }; - -use crate::keystore2_client_test_utils::{ - app_attest_key_feature_exists, delete_app_key, perform_sample_asym_sign_verify_op, - perform_sample_hmac_sign_verify_op, perform_sample_sym_key_decrypt_op, - perform_sample_sym_key_encrypt_op, verify_certificate_serial_num, - verify_certificate_subject_name, SAMPLE_PLAIN_TEXT, +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, + ResponseCode::ResponseCode, }; - -use crate::{skip_test_if_no_app_attest_key_feature, skip_tests_if_keymaster_impl_present}; - use keystore2_test_utils::ffi_test_utils::get_value_from_attest_record; +use keystore2_test_utils::{ + authorizations, get_keystore_auth_service, key_generations, + key_generations::Error, SecLevel, +}; +use openssl::bn::{BigNum, MsbOption}; +use openssl::x509::X509NameBuilder; +use std::time::SystemTime; -fn gen_key_including_unique_id( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, - alias: &str, -) -> Vec<u8> { +fn gen_key_including_unique_id(sl: &SecLevel, alias: &str) -> Vec<u8> { let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() .algorithm(Algorithm::EC) @@ -65,7 +55,7 @@ fn gen_key_including_unique_id( .attestation_challenge(b"foo".to_vec()) .include_unique_id(); - let key_metadata = key_generations::generate_key(sec_level, &gen_params, alias).unwrap(); + let key_metadata = key_generations::generate_key(sl, &gen_params, alias).unwrap(); let unique_id = get_value_from_attest_record( key_metadata.certificate.as_ref().unwrap(), @@ -78,16 +68,21 @@ fn gen_key_including_unique_id( } fn generate_key_and_perform_sign_verify_op_max_times( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, gen_params: &authorizations::AuthSetBuilder, alias: &str, max_usage_count: i32, ) -> binder::Result<KeyMetadata> { - let key_metadata = key_generations::generate_key(sec_level, gen_params, alias)?; + let key_metadata = key_generations::generate_key(sl, gen_params, alias)?; // Use above generated key `max_usage_count` times. for _ in 0..max_usage_count { - perform_sample_asym_sign_verify_op(sec_level, &key_metadata, None, Some(Digest::SHA_2_256)); + perform_sample_asym_sign_verify_op( + &sl.binder, + &key_metadata, + None, + Some(Digest::SHA_2_256), + ); } Ok(key_metadata) @@ -98,24 +93,20 @@ fn generate_key_and_perform_sign_verify_op_max_times( /// times subsequent attempts to use the key in test should fail with response code `KEY_NOT_FOUND`. /// Test should also verify that the attest record includes `USAGE_COUNT_LIMIT` for attested keys. fn generate_key_and_perform_op_with_max_usage_limit( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, gen_params: &authorizations::AuthSetBuilder, alias: &str, max_usage_count: i32, check_attestation: bool, ) { // Generate a key and use the key for `max_usage_count` times. - let key_metadata = generate_key_and_perform_sign_verify_op_max_times( - sec_level, - gen_params, - alias, - max_usage_count, - ) - .unwrap(); + let key_metadata = + generate_key_and_perform_sign_verify_op_max_times(sl, gen_params, alias, max_usage_count) + .unwrap(); let auth = key_generations::get_key_auth(&key_metadata.authorizations, Tag::USAGE_COUNT_LIMIT) .unwrap(); - if check_attestation && key_generations::has_default_keymint() { + if check_attestation && sl.is_keymint() { // Check usage-count-limit is included in attest-record. // `USAGE_COUNT_LIMIT` is supported from KeyMint1.0 assert_ne!( @@ -142,7 +133,7 @@ fn generate_key_and_perform_op_with_max_usage_limit( } // Try to use the key one more time. - let result = key_generations::map_ks_error(sec_level.createOperation( + let result = key_generations::map_ks_error(sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), false, @@ -156,8 +147,7 @@ fn generate_key_and_perform_op_with_max_usage_limit( /// the generated key successfully. #[test] fn keystore2_gen_key_auth_active_datetime_test_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let active_datetime = duration_since_epoch.as_millis(); @@ -173,13 +163,13 @@ fn keystore2_gen_key_auth_active_datetime_test_success() { let alias = "ks_test_auth_tags_test"; let result = key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), alias, ); assert!(result.is_ok()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `ACTIVE_DATETIME` set to future date and time. Test should successfully @@ -188,8 +178,7 @@ fn keystore2_gen_key_auth_active_datetime_test_success() { /// `KEY_NOT_YET_VALID`. #[test] fn keystore2_gen_key_auth_future_active_datetime_test_op_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let future_active_datetime = duration_since_epoch.as_millis() + (24 * 60 * 60 * 1000); @@ -205,14 +194,14 @@ fn keystore2_gen_key_auth_future_active_datetime_test_op_fail() { let alias = "ks_test_auth_tags_test"; let result = key_generations::map_ks_error(key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), alias, )); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::KEY_NOT_YET_VALID), result.unwrap_err()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `ORIGINATION_EXPIRE_DATETIME` set to future date and time. Test should @@ -220,8 +209,7 @@ fn keystore2_gen_key_auth_future_active_datetime_test_op_fail() { /// sign operation using the generated key successfully. #[test] fn keystore2_gen_key_auth_future_origination_expire_datetime_test_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let origination_expire_datetime = duration_since_epoch.as_millis() + (24 * 60 * 60 * 1000); @@ -237,13 +225,13 @@ fn keystore2_gen_key_auth_future_origination_expire_datetime_test_success() { let alias = "ks_test_auth_tags_test"; let result = key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), alias, ); assert!(result.is_ok()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `ORIGINATION_EXPIRE_DATETIME` set to current date and time. Test should @@ -252,8 +240,7 @@ fn keystore2_gen_key_auth_future_origination_expire_datetime_test_success() { /// `KEY_EXPIRED`. #[test] fn keystore2_gen_key_auth_origination_expire_datetime_test_op_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let origination_expire_datetime = duration_since_epoch.as_millis(); @@ -269,14 +256,14 @@ fn keystore2_gen_key_auth_origination_expire_datetime_test_op_fail() { let alias = "ks_test_auth_tags_test"; let result = key_generations::map_ks_error(key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), alias, )); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::KEY_EXPIRED), result.unwrap_err()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a HMAC key with `USAGE_EXPIRE_DATETIME` set to future date and time. Test should @@ -284,8 +271,7 @@ fn keystore2_gen_key_auth_origination_expire_datetime_test_op_fail() { /// sign and verify operations using the generated key successfully. #[test] fn keystore2_gen_key_auth_future_usage_expire_datetime_hmac_verify_op_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let usage_expire_datetime = duration_since_epoch.as_millis() + (24 * 60 * 60 * 1000); @@ -300,10 +286,10 @@ fn keystore2_gen_key_auth_future_usage_expire_datetime_hmac_verify_op_success() .usage_expire_date_time(usage_expire_datetime.try_into().unwrap()); let alias = "ks_test_auth_tags_hmac_verify_success"; - let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap(); - perform_sample_hmac_sign_verify_op(&sec_level, &key_metadata.key); - delete_app_key(&keystore2, alias).unwrap(); + perform_sample_hmac_sign_verify_op(&sl.binder, &key_metadata.key); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `USAGE_EXPIRE_DATETIME` set to current date and time. Test should @@ -312,8 +298,7 @@ fn keystore2_gen_key_auth_future_usage_expire_datetime_hmac_verify_op_success() /// `KEY_EXPIRED`. #[test] fn keystore2_gen_key_auth_usage_expire_datetime_hmac_verify_op_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let usage_expire_datetime = duration_since_epoch.as_millis(); @@ -328,10 +313,10 @@ fn keystore2_gen_key_auth_usage_expire_datetime_hmac_verify_op_fail() { .usage_expire_date_time(usage_expire_datetime.try_into().unwrap()); let alias = "ks_test_auth_tags_hamc_verify_fail"; - let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap(); let result = key_generations::map_ks_error( - sec_level.createOperation( + sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new() .purpose(KeyPurpose::VERIFY) @@ -341,7 +326,7 @@ fn keystore2_gen_key_auth_usage_expire_datetime_hmac_verify_op_fail() { ); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::KEY_EXPIRED), result.unwrap_err()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate AES key with `USAGE_EXPIRE_DATETIME` set to future date and time. Test should @@ -349,8 +334,7 @@ fn keystore2_gen_key_auth_usage_expire_datetime_hmac_verify_op_fail() { /// Encrypt and Decrypt operations successfully. #[test] fn keystore2_gen_key_auth_usage_future_expire_datetime_decrypt_op_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let usage_expire_datetime = duration_since_epoch.as_millis() + (24 * 60 * 60 * 1000); @@ -365,9 +349,9 @@ fn keystore2_gen_key_auth_usage_future_expire_datetime_decrypt_op_success() { .usage_expire_date_time(usage_expire_datetime.try_into().unwrap()); let alias = "ks_test_auth_tags_test"; - let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap(); let cipher_text = perform_sample_sym_key_encrypt_op( - &sec_level, + &sl.binder, PaddingMode::PKCS7, BlockMode::ECB, &mut None, @@ -379,7 +363,7 @@ fn keystore2_gen_key_auth_usage_future_expire_datetime_decrypt_op_success() { assert!(cipher_text.is_some()); let plain_text = perform_sample_sym_key_decrypt_op( - &sec_level, + &sl.binder, &cipher_text.unwrap(), PaddingMode::PKCS7, BlockMode::ECB, @@ -390,7 +374,7 @@ fn keystore2_gen_key_auth_usage_future_expire_datetime_decrypt_op_success() { .unwrap(); assert!(plain_text.is_some()); assert_eq!(plain_text.unwrap(), SAMPLE_PLAIN_TEXT.to_vec()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate AES key with `USAGE_EXPIRE_DATETIME` set to current date and time. Test should @@ -399,8 +383,7 @@ fn keystore2_gen_key_auth_usage_future_expire_datetime_decrypt_op_success() { /// `KEY_EXPIRED`. #[test] fn keystore2_gen_key_auth_usage_expire_datetime_decrypt_op_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let usage_expire_datetime = duration_since_epoch.as_millis(); @@ -415,9 +398,9 @@ fn keystore2_gen_key_auth_usage_expire_datetime_decrypt_op_fail() { .usage_expire_date_time(usage_expire_datetime.try_into().unwrap()); let alias = "ks_test_auth_tags_test"; - let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap(); let cipher_text = perform_sample_sym_key_encrypt_op( - &sec_level, + &sl.binder, PaddingMode::PKCS7, BlockMode::ECB, &mut None, @@ -429,7 +412,7 @@ fn keystore2_gen_key_auth_usage_expire_datetime_decrypt_op_fail() { assert!(cipher_text.is_some()); let result = key_generations::map_ks_error(perform_sample_sym_key_decrypt_op( - &sec_level, + &sl.binder, &cipher_text.unwrap(), PaddingMode::PKCS7, BlockMode::ECB, @@ -439,37 +422,7 @@ fn keystore2_gen_key_auth_usage_expire_datetime_decrypt_op_fail() { )); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::KEY_EXPIRED), result.unwrap_err()); - delete_app_key(&keystore2, alias).unwrap(); -} - -/// Generate a key with `BOOTLOADER_ONLY`. Test should successfully generate -/// a key and verify the key characteristics. Test should fail with error code `INVALID_KEY_BLOB` -/// during creation of an operation using this key. -#[test] -fn keystore2_gen_key_auth_boot_loader_only_op_fail() { - skip_tests_if_keymaster_impl_present!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); - - let gen_params = authorizations::AuthSetBuilder::new() - .no_auth_required() - .algorithm(Algorithm::EC) - .purpose(KeyPurpose::SIGN) - .purpose(KeyPurpose::VERIFY) - .digest(Digest::SHA_2_256) - .ec_curve(EcCurve::P_256) - .attestation_challenge(b"foo".to_vec()) - .boot_loader_only(); - - let alias = "ks_test_auth_tags_test"; - let result = key_generations::map_ks_error(key_generations::create_key_and_operation( - &sec_level, - &gen_params, - &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), - alias, - )); - assert!(result.is_err()); - assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err()); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `EARLY_BOOT_ONLY`. Test should successfully generate @@ -477,9 +430,8 @@ fn keystore2_gen_key_auth_boot_loader_only_op_fail() { /// during creation of an operation using this key. #[test] fn keystore2_gen_key_auth_early_boot_only_op_fail() { - skip_tests_if_keymaster_impl_present!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); + require_keymint!(sl); let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() @@ -493,14 +445,14 @@ fn keystore2_gen_key_auth_early_boot_only_op_fail() { let alias = "ks_test_auth_tags_test"; let result = key_generations::map_ks_error(key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), alias, )); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::EARLY_BOOT_ENDED), result.unwrap_err()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `MAX_USES_PER_BOOT`. Test should successfully generate @@ -509,8 +461,7 @@ fn keystore2_gen_key_auth_early_boot_only_op_fail() { /// subsequent attempts to use the key in test should fail with error code MAX_OPS_EXCEEDED. #[test] fn keystore2_gen_key_auth_max_uses_per_boot() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); const MAX_USES_COUNT: i32 = 3; let gen_params = authorizations::AuthSetBuilder::new() @@ -525,23 +476,19 @@ fn keystore2_gen_key_auth_max_uses_per_boot() { let alias = "ks_test_auth_tags_test"; // Generate a key and use the key for `MAX_USES_COUNT` times. - let key_metadata = generate_key_and_perform_sign_verify_op_max_times( - &sec_level, - &gen_params, - alias, - MAX_USES_COUNT, - ) - .unwrap(); + let key_metadata = + generate_key_and_perform_sign_verify_op_max_times(&sl, &gen_params, alias, MAX_USES_COUNT) + .unwrap(); // Try to use the key one more time. - let result = key_generations::map_ks_error(sec_level.createOperation( + let result = key_generations::map_ks_error(sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), false, )); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::KEY_MAX_OPS_EXCEEDED), result.unwrap_err()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `USAGE_COUNT_LIMIT`. Test should successfully generate @@ -551,8 +498,7 @@ fn keystore2_gen_key_auth_max_uses_per_boot() { /// Test should also verify that the attest record includes `USAGE_COUNT_LIMIT`. #[test] fn keystore2_gen_key_auth_usage_count_limit() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); const MAX_USES_COUNT: i32 = 3; let gen_params = authorizations::AuthSetBuilder::new() @@ -566,13 +512,7 @@ fn keystore2_gen_key_auth_usage_count_limit() { .usage_count_limit(MAX_USES_COUNT); let alias = "ks_test_auth_tags_test"; - generate_key_and_perform_op_with_max_usage_limit( - &sec_level, - &gen_params, - alias, - MAX_USES_COUNT, - true, - ); + generate_key_and_perform_op_with_max_usage_limit(&sl, &gen_params, alias, MAX_USES_COUNT, true); } /// Generate a key with `USAGE_COUNT_LIMIT`. Test should successfully generate @@ -582,8 +522,7 @@ fn keystore2_gen_key_auth_usage_count_limit() { /// Test should also verify that the attest record includes `USAGE_COUNT_LIMIT`. #[test] fn keystore2_gen_key_auth_usage_count_limit_one() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); const MAX_USES_COUNT: i32 = 1; let gen_params = authorizations::AuthSetBuilder::new() @@ -597,13 +536,7 @@ fn keystore2_gen_key_auth_usage_count_limit_one() { .usage_count_limit(MAX_USES_COUNT); let alias = "ks_test_auth_tags_test"; - generate_key_and_perform_op_with_max_usage_limit( - &sec_level, - &gen_params, - alias, - MAX_USES_COUNT, - true, - ); + generate_key_and_perform_op_with_max_usage_limit(&sl, &gen_params, alias, MAX_USES_COUNT, true); } /// Generate a non-attested key with `USAGE_COUNT_LIMIT`. Test should successfully generate @@ -612,8 +545,7 @@ fn keystore2_gen_key_auth_usage_count_limit_one() { /// subsequent attempts to use the key in test should fail with response code `KEY_NOT_FOUND`. #[test] fn keystore2_gen_non_attested_key_auth_usage_count_limit() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); const MAX_USES_COUNT: i32 = 2; let gen_params = authorizations::AuthSetBuilder::new() @@ -627,7 +559,7 @@ fn keystore2_gen_non_attested_key_auth_usage_count_limit() { let alias = "ks_test_auth_tags_test"; generate_key_and_perform_op_with_max_usage_limit( - &sec_level, + &sl, &gen_params, alias, MAX_USES_COUNT, @@ -640,8 +572,7 @@ fn keystore2_gen_non_attested_key_auth_usage_count_limit() { /// specify `CREATION_DATETIME`. #[test] fn keystore2_gen_key_auth_creation_date_time_test_fail_with_invalid_arg_error() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let duration_since_epoch = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let creation_datetime = duration_since_epoch.as_millis(); @@ -656,7 +587,7 @@ fn keystore2_gen_key_auth_creation_date_time_test_fail_with_invalid_arg_error() .creation_date_time(creation_datetime.try_into().unwrap()); let alias = "ks_test_auth_tags_test"; - let result = key_generations::map_ks_error(sec_level.generateKey( + let result = key_generations::map_ks_error(sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -677,27 +608,25 @@ fn keystore2_gen_key_auth_creation_date_time_test_fail_with_invalid_arg_error() /// included in attest record and it remains the same for new keys generated. #[test] fn keystore2_gen_key_auth_include_unique_id_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias_first = "ks_test_auth_tags_test_1"; - let unique_id_first = gen_key_including_unique_id(&sec_level, alias_first); + let unique_id_first = gen_key_including_unique_id(&sl, alias_first); let alias_second = "ks_test_auth_tags_test_2"; - let unique_id_second = gen_key_including_unique_id(&sec_level, alias_second); + let unique_id_second = gen_key_including_unique_id(&sl, alias_second); assert_eq!(unique_id_first, unique_id_second); - delete_app_key(&keystore2, alias_first).unwrap(); - delete_app_key(&keystore2, alias_second).unwrap(); + delete_app_key(&sl.keystore2, alias_first).unwrap(); + delete_app_key(&sl.keystore2, alias_second).unwrap(); } /// Generate a key with `APPLICATION_DATA`. Test should create an operation using the /// same `APPLICATION_DATA` successfully. #[test] fn keystore2_gen_key_auth_app_data_test_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() @@ -710,7 +639,7 @@ fn keystore2_gen_key_auth_app_data_test_success() { let alias = "ks_test_auth_tags_test"; let result = key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new() .purpose(KeyPurpose::SIGN) @@ -719,7 +648,7 @@ fn keystore2_gen_key_auth_app_data_test_success() { alias, ); assert!(result.is_ok()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `APPLICATION_DATA`. Try to create an operation using the @@ -727,8 +656,7 @@ fn keystore2_gen_key_auth_app_data_test_success() { /// `INVALID_KEY_BLOB`. #[test] fn keystore2_gen_key_auth_app_data_test_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() @@ -741,7 +669,7 @@ fn keystore2_gen_key_auth_app_data_test_fail() { let alias = "ks_test_auth_tags_test"; let result = key_generations::map_ks_error(key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new() .purpose(KeyPurpose::SIGN) @@ -751,15 +679,14 @@ fn keystore2_gen_key_auth_app_data_test_fail() { )); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `APPLICATION_ID`. Test should create an operation using the /// same `APPLICATION_ID` successfully. #[test] fn keystore2_gen_key_auth_app_id_test_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() @@ -772,7 +699,7 @@ fn keystore2_gen_key_auth_app_id_test_success() { let alias = "ks_test_auth_tags_test"; let result = key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new() .purpose(KeyPurpose::SIGN) @@ -781,7 +708,7 @@ fn keystore2_gen_key_auth_app_id_test_success() { alias, ); assert!(result.is_ok()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate a key with `APPLICATION_ID`. Try to create an operation using the @@ -789,8 +716,7 @@ fn keystore2_gen_key_auth_app_id_test_success() { /// `INVALID_KEY_BLOB`. #[test] fn keystore2_gen_key_auth_app_id_test_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() @@ -803,7 +729,7 @@ fn keystore2_gen_key_auth_app_id_test_fail() { let alias = "ks_test_auth_tags_test"; let result = key_generations::map_ks_error(key_generations::create_key_and_operation( - &sec_level, + &sl, &gen_params, &authorizations::AuthSetBuilder::new() .purpose(KeyPurpose::SIGN) @@ -813,7 +739,7 @@ fn keystore2_gen_key_auth_app_id_test_fail() { )); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate an attestation-key without specifying `APPLICATION_ID` and `APPLICATION_DATA`. @@ -822,8 +748,7 @@ fn keystore2_gen_key_auth_app_id_test_fail() { #[test] fn keystore2_gen_attested_key_auth_app_id_app_data_test_success() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); // Generate attestation key. let attest_gen_params = authorizations::AuthSetBuilder::new() @@ -835,7 +760,7 @@ fn keystore2_gen_attested_key_auth_app_id_app_data_test_success() { .attestation_challenge(b"foo".to_vec()); let attest_alias = "ks_test_auth_tags_attest_key"; let attest_key_metadata = - key_generations::generate_key(&sec_level, &attest_gen_params, attest_alias).unwrap(); + key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap(); // Generate attested key. let alias = "ks_test_auth_tags_attested_key"; @@ -850,7 +775,7 @@ fn keystore2_gen_attested_key_auth_app_id_app_data_test_success() { .app_id(b"app-id".to_vec()) .app_data(b"app-data".to_vec()); - let result = sec_level.generateKey( + let result = sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -864,8 +789,8 @@ fn keystore2_gen_attested_key_auth_app_id_app_data_test_success() { ); assert!(result.is_ok()); - delete_app_key(&keystore2, alias).unwrap(); - delete_app_key(&keystore2, attest_alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, attest_alias).unwrap(); } /// Generate an attestation-key with specifying `APPLICATION_ID` and `APPLICATION_DATA`. @@ -877,8 +802,7 @@ fn keystore2_gen_attested_key_auth_app_id_app_data_test_success() { #[test] fn keystore2_gen_attestation_key_with_auth_app_id_app_data_test_fail() { skip_test_if_no_app_attest_key_feature!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); // Generate attestation key. let attest_gen_params = authorizations::AuthSetBuilder::new() @@ -892,7 +816,7 @@ fn keystore2_gen_attestation_key_with_auth_app_id_app_data_test_fail() { .app_data(b"app-data".to_vec()); let attest_alias = "ks_test_auth_tags_attest_key"; let attest_key_metadata = - key_generations::generate_key(&sec_level, &attest_gen_params, attest_alias).unwrap(); + key_generations::generate_key(&sl, &attest_gen_params, attest_alias).unwrap(); // Generate new key using above generated attestation key without providing app-id and app-data. let alias = "ks_test_auth_tags_attested_key"; @@ -905,7 +829,7 @@ fn keystore2_gen_attestation_key_with_auth_app_id_app_data_test_fail() { .ec_curve(EcCurve::P_256) .attestation_challenge(b"foo".to_vec()); - let result = key_generations::map_ks_error(sec_level.generateKey( + let result = key_generations::map_ks_error(sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -920,7 +844,7 @@ fn keystore2_gen_attestation_key_with_auth_app_id_app_data_test_fail() { assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::INVALID_KEY_BLOB), result.unwrap_err()); - delete_app_key(&keystore2, attest_alias).unwrap(); + delete_app_key(&sl.keystore2, attest_alias).unwrap(); } fn add_hardware_token(auth_type: HardwareAuthenticatorType) { @@ -981,9 +905,8 @@ fn keystore2_flagged_on_get_last_auth_fingerprint_success() { /// generate a key successfully and verify the specified key parameters. #[test] fn keystore2_gen_key_auth_serial_number_subject_test_success() { - skip_tests_if_keymaster_impl_present!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); + require_keymint!(sl); let cert_subject = "test cert subject"; let mut x509_name = X509NameBuilder::new().unwrap(); @@ -1005,11 +928,11 @@ fn keystore2_gen_key_auth_serial_number_subject_test_success() { .cert_serial(serial.to_vec()); let alias = "ks_test_auth_tags_test"; - let key_metadata = key_generations::generate_key(&sec_level, &gen_params, alias).unwrap(); + let key_metadata = key_generations::generate_key(&sl, &gen_params, alias).unwrap(); verify_certificate_subject_name( key_metadata.certificate.as_ref().unwrap(), cert_subject.as_bytes(), ); verify_certificate_serial_num(key_metadata.certificate.as_ref().unwrap(), &serial); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } diff --git a/keystore2/tests/keystore2_client_delete_key_tests.rs b/keystore2/tests/keystore2_client_delete_key_tests.rs index 2a06edbc..a0fb9c2a 100644 --- a/keystore2/tests/keystore2_client_delete_key_tests.rs +++ b/keystore2/tests/keystore2_client_delete_key_tests.rs @@ -12,27 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use nix::unistd::getuid; - -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - ErrorCode::ErrorCode, SecurityLevel::SecurityLevel, -}; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::ErrorCode::ErrorCode; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, }; - -use keystore2_test_utils::{get_keystore_service, key_generations, key_generations::Error}; +use keystore2_test_utils::{ + get_keystore_service, key_generations, key_generations::Error, SecLevel, +}; +use nix::unistd::getuid; /// Generate a key and delete it using keystore2 service `deleteKey` API. Test should successfully /// delete the generated key. #[test] fn keystore2_delete_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "delete_key_success_key"; let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -40,10 +37,10 @@ fn keystore2_delete_key_success() { ) .unwrap(); - keystore2.deleteKey(&key_metadata.key).expect("Failed to delete a key."); + sl.keystore2.deleteKey(&key_metadata.key).expect("Failed to delete a key."); // Check wehther deleted key is removed from keystore. - let result = key_generations::map_ks_error(keystore2.getKeyEntry(&key_metadata.key)); + let result = key_generations::map_ks_error(sl.keystore2.getKeyEntry(&key_metadata.key)); assert!(result.is_err()); assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); } @@ -70,12 +67,11 @@ fn keystore2_delete_key_fail() { /// `INVALID_ARGUMENT`. #[test] fn keystore2_delete_key_with_blob_domain_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "delete_key_blob_fail_key"; let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::BLOB, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias.to_string()), @@ -83,7 +79,7 @@ fn keystore2_delete_key_with_blob_domain_fail() { ) .unwrap(); - let result = key_generations::map_ks_error(keystore2.deleteKey(&key_metadata.key)); + let result = key_generations::map_ks_error(sl.keystore2.deleteKey(&key_metadata.key)); assert!(result.is_err()); assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); } @@ -92,12 +88,11 @@ fn keystore2_delete_key_with_blob_domain_fail() { /// security level `deleteKey` API. Test should delete the key successfully. #[test] fn keystore2_delete_key_blob_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "delete_key_blob_success_key"; let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::BLOB, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias.to_string()), @@ -105,7 +100,7 @@ fn keystore2_delete_key_blob_success() { ) .unwrap(); - let result = sec_level.deleteKey(&key_metadata.key); + let result = sl.binder.deleteKey(&key_metadata.key); assert!(result.is_ok()); } @@ -113,10 +108,9 @@ fn keystore2_delete_key_blob_success() { /// key with error code `INVALID_ARGUMENT`. #[test] fn keystore2_delete_key_fails_with_missing_key_blob() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); - let result = key_generations::map_ks_error(sec_level.deleteKey(&KeyDescriptor { + let result = key_generations::map_ks_error(sl.binder.deleteKey(&KeyDescriptor { domain: Domain::BLOB, nspace: key_generations::SELINUX_SHELL_NAMESPACE, alias: None, @@ -131,20 +125,14 @@ fn keystore2_delete_key_fails_with_missing_key_blob() { /// with error code `INVALID_ARGUMENT`. #[test] fn keystore2_delete_key_blob_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_delete_keyblob_test_key_{}", getuid()); - let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, - Domain::APP, - -1, - Some(alias), - None, - ) - .unwrap(); + let key_metadata = + key_generations::generate_ec_p256_signing_key(&sl, Domain::APP, -1, Some(alias), None) + .unwrap(); - let result = key_generations::map_ks_error(sec_level.deleteKey(&key_metadata.key)); + let result = key_generations::map_ks_error(sl.binder.deleteKey(&key_metadata.key)); assert!(result.is_err()); assert_eq!(Error::Km(ErrorCode::INVALID_ARGUMENT), result.unwrap_err()); } diff --git a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs index 4f881bcd..cc5d85cc 100644 --- a/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs +++ b/keystore2/tests/keystore2_client_device_unique_attestation_tests.rs @@ -11,23 +11,18 @@ // 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, - KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, Tag::Tag, -}; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, -}; - -use keystore2_test_utils::ffi_test_utils::get_value_from_attest_record; use crate::keystore2_client_test_utils::{ delete_app_key, get_attest_id_value, is_second_imei_id_attestation_required, - perform_sample_asym_sign_verify_op, + perform_sample_asym_sign_verify_op, skip_device_unique_attestation_tests, }; - -use crate::skip_tests_if_keymaster_impl_present; +use crate::require_keymint; +use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ + Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, + KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, Tag::Tag, +}; +use keystore2_test_utils::ffi_test_utils::get_value_from_attest_record; +use keystore2_test_utils::{authorizations, key_generations, key_generations::Error, SecLevel}; /// This macro is used for generating device unique attested EC key with device id attestation. macro_rules! test_ec_key_device_unique_attestation_id { @@ -50,6 +45,9 @@ macro_rules! test_rsa_key_device_unique_attestation_id { } fn generate_ec_key_device_unique_attested_with_id_attest(attest_id_tag: Tag, prop_name: &str) { + if skip_device_unique_attestation_tests() { + return; + } let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() .algorithm(Algorithm::EC) @@ -67,6 +65,9 @@ fn generate_ec_key_device_unique_attested_with_id_attest(attest_id_tag: Tag, pro } fn generate_rsa_key_device_unique_attested_with_id_attest(attest_id_tag: Tag, prop_name: &str) { + if skip_device_unique_attestation_tests() { + return; + } let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() .algorithm(Algorithm::RSA) @@ -113,17 +114,10 @@ fn generate_device_unique_attested_key_with_device_attest_ids( attest_id: Tag, prop_name: &str, ) { - let keystore2 = get_keystore_service(); - let result = - key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::STRONGBOX)); - if result.is_err() { - assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); - return; - } - let sec_level = result.unwrap(); + let Some(sl) = SecLevel::strongbox() else { return }; if attest_id == Tag::ATTESTATION_ID_SECOND_IMEI - && !is_second_imei_id_attestation_required(&keystore2) + && !is_second_imei_id_attestation_required(&sl.keystore2) { return; } @@ -134,11 +128,8 @@ fn generate_device_unique_attested_key_with_device_attest_ids( } let gen_params = add_attest_id_auth(gen_params, attest_id, value.clone()); let alias = "ks_test_device_unique_attest_id_test"; - match key_generations::map_ks_error(key_generations::generate_key( - &sec_level, - &gen_params, - alias, - )) { + match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)) + { Ok(key_metadata) => { let attest_id_value = get_value_from_attest_record( key_metadata.certificate.as_ref().unwrap(), @@ -147,7 +138,7 @@ fn generate_device_unique_attested_key_with_device_attest_ids( ) .expect("Attest id verification failed."); assert_eq!(attest_id_value, value); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } Err(e) => { assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); @@ -160,9 +151,8 @@ fn generate_device_unique_attested_key_with_device_attest_ids( /// Test should fail to generate a key with error code `INVALID_ARGUMENT` #[test] fn keystore2_gen_key_device_unique_attest_with_default_sec_level_unimplemented() { - skip_tests_if_keymaster_impl_present!(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); + require_keymint!(sl); let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() @@ -175,13 +165,13 @@ fn keystore2_gen_key_device_unique_attest_with_default_sec_level_unimplemented() .device_unique_attestation(); let alias = "ks_test_auth_tags_test"; - let result = key_generations::map_ks_error(key_generations::generate_key( - &sec_level, - &gen_params, - alias, - )); + let result = + key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)); assert!(result.is_err()); - assert_eq!(Error::Km(ErrorCode::INVALID_ARGUMENT), result.unwrap_err()); + assert!(matches!( + result.unwrap_err(), + Error::Km(ErrorCode::INVALID_ARGUMENT) | Error::Km(ErrorCode::UNSUPPORTED_TAG) + )); } /// Generate a EC key with `DEVICE_UNIQUE_ATTESTATION` using `STRONGBOX` security level. @@ -189,15 +179,11 @@ fn keystore2_gen_key_device_unique_attest_with_default_sec_level_unimplemented() /// use it for performing an operation. #[test] fn keystore2_gen_ec_key_device_unique_attest_with_strongbox_sec_level_test_success() { - let keystore2 = get_keystore_service(); - let result = - key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::STRONGBOX)); - if result.is_err() { - assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); + let Some(sl) = SecLevel::strongbox() else { return }; + if skip_device_unique_attestation_tests() { return; } - let sec_level = result.unwrap(); let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() .algorithm(Algorithm::EC) @@ -209,19 +195,15 @@ fn keystore2_gen_ec_key_device_unique_attest_with_strongbox_sec_level_test_succe .device_unique_attestation(); let alias = "ks_device_unique_ec_key_attest_test"; - match key_generations::map_ks_error(key_generations::generate_key( - &sec_level, - &gen_params, - alias, - )) { + match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)) { Ok(key_metadata) => { perform_sample_asym_sign_verify_op( - &sec_level, + &sl.binder, &key_metadata, None, Some(Digest::SHA_2_256), ); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } Err(e) => { assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); @@ -234,15 +216,11 @@ fn keystore2_gen_ec_key_device_unique_attest_with_strongbox_sec_level_test_succe /// use it for performing an operation. #[test] fn keystore2_gen_rsa_key_device_unique_attest_with_strongbox_sec_level_test_success() { - let keystore2 = get_keystore_service(); - let result = - key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::STRONGBOX)); - if result.is_err() { - assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); + let Some(sl) = SecLevel::strongbox() else { return }; + if skip_device_unique_attestation_tests() { return; } - let sec_level = result.unwrap(); let gen_params = authorizations::AuthSetBuilder::new() .no_auth_required() .algorithm(Algorithm::RSA) @@ -256,19 +234,15 @@ fn keystore2_gen_rsa_key_device_unique_attest_with_strongbox_sec_level_test_succ .device_unique_attestation(); let alias = "ks_device_unique_rsa_key_attest_test"; - match key_generations::map_ks_error(key_generations::generate_key( - &sec_level, - &gen_params, - alias, - )) { + match key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)) { Ok(key_metadata) => { perform_sample_asym_sign_verify_op( - &sec_level, + &sl.binder, &key_metadata, Some(PaddingMode::RSA_PKCS1_1_5_SIGN), Some(Digest::SHA_2_256), ); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } Err(e) => { assert_eq!(e, Error::Km(ErrorCode::CANNOT_ATTEST_IDS)); @@ -280,15 +254,11 @@ fn keystore2_gen_rsa_key_device_unique_attest_with_strongbox_sec_level_test_succ /// Test should fail with error response code `CANNOT_ATTEST_IDS`. #[test] fn keystore2_device_unique_attest_key_fails_with_invalid_attestation_id() { - let keystore2 = get_keystore_service(); - let result = - key_generations::map_ks_error(keystore2.getSecurityLevel(SecurityLevel::STRONGBOX)); - if result.is_err() { - assert_eq!(Error::Km(ErrorCode::HARDWARE_TYPE_UNAVAILABLE), result.unwrap_err()); + let Some(sl) = SecLevel::strongbox() else { return }; + if skip_device_unique_attestation_tests() { return; } - let sec_level = result.unwrap(); let attest_id_params = vec![ (Tag::ATTESTATION_ID_BRAND, b"invalid-brand".to_vec()), (Tag::ATTESTATION_ID_DEVICE, b"invalid-device-name".to_vec()), @@ -312,11 +282,8 @@ fn keystore2_device_unique_attest_key_fails_with_invalid_attestation_id() { let alias = "ks_ec_device_unique_attested_test_key_fail"; let gen_params = add_attest_id_auth(gen_params, attest_id, value.clone()); - let result = key_generations::map_ks_error(key_generations::generate_key( - &sec_level, - &gen_params, - alias, - )); + let result = + key_generations::map_ks_error(key_generations::generate_key(&sl, &gen_params, alias)); assert!(result.is_err()); assert!(matches!(result.unwrap_err(), Error::Km(ErrorCode::CANNOT_ATTEST_IDS))); } diff --git a/keystore2/tests/keystore2_client_ec_key_tests.rs b/keystore2/tests/keystore2_client_ec_key_tests.rs index 82671402..8aa9bc49 100644 --- a/keystore2/tests/keystore2_client_ec_key_tests.rs +++ b/keystore2/tests/keystore2_client_ec_key_tests.rs @@ -12,27 +12,23 @@ // 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 crate::keystore2_client_test_utils::{ + delete_app_key, execute_op_run_as_child, get_vsr_api_level, perform_sample_sign_operation, + BarrierReached, ForcedOp, TestOutcome, +}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, - KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, + KeyPurpose::KeyPurpose, }; use android_system_keystore2::aidl::android::system::keystore2::{ - CreateOperationResponse::CreateOperationResponse, Domain::Domain, - IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, + CreateOperationResponse::CreateOperationResponse, Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, }; - use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, -}; - -use crate::keystore2_client_test_utils::{ - delete_app_key, execute_op_run_as_child, perform_sample_sign_operation, BarrierReached, - ForcedOp, TestOutcome, + authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, SecLevel, }; +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; macro_rules! test_ec_sign_key_op_success { ( $test_name:ident, $digest:expr, $ec_curve:expr ) => { @@ -57,7 +53,7 @@ macro_rules! test_ec_sign_key_op_with_none_or_md5_digest { } fn create_ec_key_and_operation( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, @@ -65,9 +61,9 @@ fn create_ec_key_and_operation( ec_curve: EcCurve, ) -> binder::Result<CreateOperationResponse> { let key_metadata = - key_generations::generate_ec_key(sec_level, domain, nspace, alias, ec_curve, digest)?; + key_generations::generate_ec_key(sl, domain, nspace, alias, ec_curve, digest)?; - sec_level.createOperation( + sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), false, @@ -75,11 +71,10 @@ fn create_ec_key_and_operation( } fn perform_ec_sign_key_op_success(alias: &str, digest: Digest, ec_curve: EcCurve) { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let op_response = create_ec_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -96,15 +91,14 @@ fn perform_ec_sign_key_op_success(alias: &str, digest: Digest, ec_curve: EcCurve )) ); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } fn perform_ec_sign_key_op_with_none_or_md5_digest(alias: &str, digest: Digest, ec_curve: EcCurve) { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); match key_generations::map_ks_error(create_ec_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -126,7 +120,7 @@ fn perform_ec_sign_key_op_with_none_or_md5_digest(alias: &str, digest: Digest, e } } - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } // Below macros generate tests for generating EC keys with curves EcCurve::P_224, EcCurve::P_256, @@ -199,12 +193,11 @@ test_ec_sign_key_op_success!(sign_ec_key_op_sha512_ec_p521, Digest::SHA_2_512, E /// INVALID_ARGUMENT error is expected. #[test] fn keystore2_get_key_entry_blob_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); // Generate a key with domain as BLOB. let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::BLOB, key_generations::SELINUX_SHELL_NAMESPACE, None, @@ -213,23 +206,22 @@ fn keystore2_get_key_entry_blob_fail() { .unwrap(); // Try to load the key using above generated KeyDescriptor. - let result = key_generations::map_ks_error(keystore2.getKeyEntry(&key_metadata.key)); + let result = key_generations::map_ks_error(sl.keystore2.getKeyEntry(&key_metadata.key)); assert!(result.is_err()); assert_eq!(Error::Rc(ResponseCode::INVALID_ARGUMENT), result.unwrap_err()); // Delete the generated key blob. - sec_level.deleteKey(&key_metadata.key).unwrap(); + sl.binder.deleteKey(&key_metadata.key).unwrap(); } /// Try to generate a key with invalid Domain. `INVALID_ARGUMENT` error response is expected. #[test] fn keystore2_generate_key_invalid_domain() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_invalid_test_key_{}", getuid()); let result = key_generations::map_ks_error(key_generations::generate_ec_key( - &sec_level, + &sl, Domain(99), // Invalid domain. key_generations::SELINUX_SHELL_NAMESPACE, Some(alias), @@ -244,8 +236,7 @@ fn keystore2_generate_key_invalid_domain() { /// `UNSUPPORTED_EC_CURVE or UNSUPPORTED_KEY_SIZE` error response is expected. #[test] fn keystore2_generate_ec_key_missing_curve() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_ec_no_curve_test_key_{}", getuid()); // Don't provide EC curve. @@ -256,7 +247,7 @@ fn keystore2_generate_ec_key_missing_curve() { .purpose(KeyPurpose::VERIFY) .digest(Digest::SHA_2_256); - let result = key_generations::map_ks_error(sec_level.generateKey( + let result = key_generations::map_ks_error(sl.binder.generateKey( &KeyDescriptor { domain: Domain::SELINUX, nspace: key_generations::SELINUX_SHELL_NAMESPACE, @@ -280,8 +271,7 @@ fn keystore2_generate_ec_key_missing_curve() { /// `INCOMPATIBLE_PURPOSE` error response is expected. #[test] fn keystore2_generate_ec_key_25519_multi_purpose() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_ec_no_curve_test_key_{}", getuid()); // Specify `SIGN and AGREE_KEY` purposes. @@ -293,7 +283,7 @@ fn keystore2_generate_ec_key_25519_multi_purpose() { .purpose(KeyPurpose::AGREE_KEY) .digest(Digest::SHA_2_256); - let result = key_generations::map_ks_error(sec_level.generateKey( + let result = key_generations::map_ks_error(sl.binder.generateKey( &KeyDescriptor { domain: Domain::SELINUX, nspace: key_generations::SELINUX_SHELL_NAMESPACE, @@ -314,12 +304,11 @@ fn keystore2_generate_ec_key_25519_multi_purpose() { /// able to create an operation successfully. #[test] fn keystore2_ec_25519_generate_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_ec_25519_none_test_key_gen_{}", getuid()); let key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias), @@ -328,7 +317,8 @@ fn keystore2_ec_25519_generate_key_success() { ) .unwrap(); - let op_response = sec_level + let op_response = sl + .binder .createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::NONE), @@ -350,8 +340,7 @@ fn keystore2_ec_25519_generate_key_success() { /// `UNSUPPORTED_DIGEST`. #[test] fn keystore2_ec_25519_generate_key_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let digests = [ Digest::MD5, @@ -365,7 +354,7 @@ fn keystore2_ec_25519_generate_key_fail() { for digest in digests { let alias = format!("ks_ec_25519_test_key_gen_{}{}", getuid(), digest.0); let key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -374,13 +363,18 @@ fn keystore2_ec_25519_generate_key_fail() { ) .unwrap(); - let result = key_generations::map_ks_error(sec_level.createOperation( - &key_metadata.key, - &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), - false, - )); - assert!(result.is_err()); - assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); + // The KeyMint v2 API added `CURVE_25519` and specified that "Ed25519 keys only support + // Digest::NONE". However, this was not checked at the time so we can only be strict about + // checking this for more recent implementations. + if get_vsr_api_level() >= 35 { + let result = key_generations::map_ks_error(sl.binder.createOperation( + &key_metadata.key, + &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), + false, + )); + assert!(result.is_err(), "unexpected success for digest {digest:?}"); + assert_eq!(Error::Km(ErrorCode::UNSUPPORTED_DIGEST), result.unwrap_err()); + } } } @@ -389,12 +383,11 @@ fn keystore2_ec_25519_generate_key_fail() { /// `INCOMPATIBLE_DIGEST` error as there is a mismatch of digest mode in key authorizations. #[test] fn keystore2_create_op_with_incompatible_key_digest() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_ec_test_incomp_key_digest"; let key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -407,7 +400,7 @@ fn keystore2_create_op_with_incompatible_key_digest() { [Digest::NONE, Digest::SHA1, Digest::SHA_2_224, Digest::SHA_2_384, Digest::SHA_2_512]; for digest in digests { - let result = key_generations::map_ks_error(sec_level.createOperation( + let result = key_generations::map_ks_error(sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(digest), false, @@ -483,11 +476,10 @@ fn keystore2_key_owner_validation() { /// successfully. #[test] fn keystore2_generate_key_with_blob_domain() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::BLOB, key_generations::SELINUX_SHELL_NAMESPACE, None, @@ -502,7 +494,7 @@ fn keystore2_generate_key_with_blob_domain() { // Must have the key blob. assert!(key_metadata.key.blob.is_some()); - let op_response = key_generations::map_ks_error(sec_level.createOperation( + let op_response = key_generations::map_ks_error(sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), false, @@ -517,5 +509,5 @@ fn keystore2_generate_key_with_blob_domain() { ); // Delete the generated key blob. - sec_level.deleteKey(&key_metadata.key).unwrap(); + sl.binder.deleteKey(&key_metadata.key).unwrap(); } diff --git a/keystore2/tests/keystore2_client_grant_key_tests.rs b/keystore2/tests/keystore2_client_grant_key_tests.rs index 516869a1..50b87b9a 100644 --- a/keystore2/tests/keystore2_client_grant_key_tests.rs +++ b/keystore2/tests/keystore2_client_grant_key_tests.rs @@ -12,37 +12,32 @@ // 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 crate::keystore2_client_test_utils::{ + generate_ec_key_and_grant_to_users, perform_sample_sign_operation, +}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Digest::Digest, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, + Digest::Digest, KeyPurpose::KeyPurpose, }; use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, - IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, + Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, ResponseCode::ResponseCode, }; - use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, -}; - -use crate::keystore2_client_test_utils::{ - generate_ec_key_and_grant_to_users, perform_sample_sign_operation, + authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, SecLevel, }; +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; /// Generate an EC signing key and grant it to the user with given access vector. fn generate_ec_key_and_grant_to_user( grantee_uid: i32, access_vector: i32, ) -> binder::Result<KeyDescriptor> { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("{}{}", "ks_grant_test_key_1", getuid()); let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias), @@ -50,15 +45,14 @@ fn generate_ec_key_and_grant_to_user( ) .unwrap(); - keystore2.grant(&key_metadata.key, grantee_uid, access_vector) + sl.keystore2.grant(&key_metadata.key, grantee_uid, access_vector) } fn load_grant_key_and_perform_sign_operation( - keystore2: &binder::Strong<dyn IKeystoreService>, - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, grant_key_nspace: i64, ) -> Result<(), binder::Status> { - let key_entry_response = keystore2.getKeyEntry(&KeyDescriptor { + let key_entry_response = sl.keystore2.getKeyEntry(&KeyDescriptor { domain: Domain::GRANT, nspace: grant_key_nspace, alias: None, @@ -66,7 +60,7 @@ fn load_grant_key_and_perform_sign_operation( })?; // Perform sample crypto operation using granted key. - let op_response = sec_level.createOperation( + let op_response = sl.binder.createOperation( &key_entry_response.metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), false, @@ -195,12 +189,11 @@ fn keystore2_grant_get_info_use_key_perm() { Uid::from_raw(GRANTEE_UID), Gid::from_raw(GRANTEE_GID), move || { - let keystore2 = get_keystore_service(); - let sec_level = - keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); // Load the granted key. - let key_entry_response = keystore2 + let key_entry_response = sl + .keystore2 .getKeyEntry(&KeyDescriptor { domain: Domain::GRANT, nspace: grant_key_nspace, @@ -210,7 +203,8 @@ fn keystore2_grant_get_info_use_key_perm() { .unwrap(); // Perform sample crypto operation using granted key. - let op_response = sec_level + let op_response = sl + .binder .createOperation( &key_entry_response.metadata.key, &authorizations::AuthSetBuilder::new() @@ -228,12 +222,13 @@ fn keystore2_grant_get_info_use_key_perm() { ); // Try to delete the key, it is expected to be fail with permission denied error. - let result = key_generations::map_ks_error(keystore2.deleteKey(&KeyDescriptor { - domain: Domain::GRANT, - nspace: grant_key_nspace, - alias: None, - blob: None, - })); + let result = + key_generations::map_ks_error(sl.keystore2.deleteKey(&KeyDescriptor { + domain: Domain::GRANT, + nspace: grant_key_nspace, + alias: None, + blob: None, + })); assert!(result.is_err()); assert_eq!(Error::Rc(ResponseCode::PERMISSION_DENIED), result.unwrap_err()); }, @@ -258,12 +253,10 @@ fn keystore2_grant_delete_key_success() { // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let access_vector = KeyPermission::DELETE.0; let mut grant_keys = generate_ec_key_and_grant_to_users( - &keystore2, - &sec_level, + &sl, Some(ALIAS.to_string()), vec![GRANTEE_UID.try_into().unwrap()], access_vector, @@ -335,13 +328,11 @@ fn keystore2_grant_key_fails_with_permission_denied() { // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let access_vector = KeyPermission::GET_INFO.0; let alias = format!("ks_grant_perm_denied_key_{}", getuid()); let mut grant_keys = generate_ec_key_and_grant_to_users( - &keystore2, - &sec_level, + &sl, Some(alias), vec![GRANTEE_UID.try_into().unwrap()], access_vector, @@ -411,8 +402,7 @@ fn keystore2_grant_key_fails_with_permission_denied() { /// `GRANT` access. Test should fail to grant a key with `PERMISSION_DENIED` error response code. #[test] fn keystore2_grant_key_fails_with_grant_perm_expect_perm_denied() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let access_vector = KeyPermission::GRANT.0; let alias = format!("ks_grant_access_vec_key_{}", getuid()); let user_id = 98; @@ -420,8 +410,7 @@ fn keystore2_grant_key_fails_with_grant_perm_expect_perm_denied() { let grantee_uid = user_id * AID_USER_OFFSET + application_id; let result = key_generations::map_ks_error(generate_ec_key_and_grant_to_users( - &keystore2, - &sec_level, + &sl, Some(alias), vec![grantee_uid.try_into().unwrap()], access_vector, @@ -470,13 +459,11 @@ fn keystore2_ungrant_key_success() { // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_ungrant_test_key_1{}", getuid()); let access_vector = KeyPermission::GET_INFO.0; let mut grant_keys = generate_ec_key_and_grant_to_users( - &keystore2, - &sec_level, + &sl, Some(alias.to_string()), vec![GRANTEE_UID.try_into().unwrap()], access_vector, @@ -485,8 +472,8 @@ fn keystore2_ungrant_key_success() { let grant_key_nspace = grant_keys.remove(0); - //Ungrant above granted key. - keystore2 + // Ungrant above granted key. + sl.keystore2 .ungrant( &KeyDescriptor { domain: Domain::APP, @@ -542,12 +529,11 @@ fn keystore2_ungrant_fails_with_non_existing_key_expect_key_not_found_error() { // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("{}{}", "ks_grant_delete_ungrant_test_key_1", getuid()); let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias.to_string()), @@ -556,17 +542,18 @@ fn keystore2_ungrant_fails_with_non_existing_key_expect_key_not_found_error() { .unwrap(); let access_vector = KeyPermission::GET_INFO.0; - let grant_key = keystore2 + let grant_key = sl + .keystore2 .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector) .unwrap(); assert_eq!(grant_key.domain, Domain::GRANT); // Delete above granted key. - keystore2.deleteKey(&key_metadata.key).unwrap(); + sl.keystore2.deleteKey(&key_metadata.key).unwrap(); // Try to ungrant above granted key. let result = key_generations::map_ks_error( - keystore2.ungrant(&key_metadata.key, GRANTEE_UID.try_into().unwrap()), + sl.keystore2.ungrant(&key_metadata.key, GRANTEE_UID.try_into().unwrap()), ); assert!(result.is_err()); assert_eq!(Error::Rc(ResponseCode::KEY_NOT_FOUND), result.unwrap_err()); @@ -574,7 +561,7 @@ fn keystore2_ungrant_fails_with_non_existing_key_expect_key_not_found_error() { // Generate a new key with the same alias and try to access the earlier granted key // in grantee context. let result = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias), @@ -631,14 +618,12 @@ fn keystore2_grant_key_to_multi_users_success() { // SAFETY: The test is run in a separate process with no other threads. let mut grant_keys = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_grant_test_key_2{}", getuid()); let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0; generate_ec_key_and_grant_to_users( - &keystore2, - &sec_level, + &sl, Some(alias), vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()], access_vector, @@ -658,15 +643,12 @@ fn keystore2_grant_key_to_multi_users_success() { Uid::from_raw(*grantee_uid), Gid::from_raw(*grantee_gid), move || { - let keystore2 = get_keystore_service(); - let sec_level = - keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); assert_eq!( Ok(()), key_generations::map_ks_error(load_grant_key_and_perform_sign_operation( - &keystore2, - &sec_level, + &sl, grant_key_nspace )) ); @@ -697,15 +679,13 @@ fn keystore2_grant_key_to_multi_users_delete_fails_with_key_not_found_error() { // SAFETY: The test is run in a separate process with no other threads. let mut grant_keys = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_grant_test_key_2{}", getuid()); let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::USE.0 | KeyPermission::DELETE.0; generate_ec_key_and_grant_to_users( - &keystore2, - &sec_level, + &sl, Some(alias), vec![GRANTEE_1_UID.try_into().unwrap(), GRANTEE_2_UID.try_into().unwrap()], access_vector, @@ -723,21 +703,18 @@ fn keystore2_grant_key_to_multi_users_delete_fails_with_key_not_found_error() { Uid::from_raw(GRANTEE_1_UID), Gid::from_raw(GRANTEE_1_GID), move || { - let keystore2 = get_keystore_service(); - let sec_level = - keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); assert_eq!( Ok(()), key_generations::map_ks_error(load_grant_key_and_perform_sign_operation( - &keystore2, - &sec_level, + &sl, grant_key1_nspace )) ); // Delete the granted key. - keystore2 + sl.keystore2 .deleteKey(&KeyDescriptor { domain: Domain::GRANT, nspace: grant_key1_nspace, diff --git a/keystore2/tests/keystore2_client_hmac_key_tests.rs b/keystore2/tests/keystore2_client_hmac_key_tests.rs index 6bb80017..76780a0b 100644 --- a/keystore2/tests/keystore2_client_hmac_key_tests.rs +++ b/keystore2/tests/keystore2_client_hmac_key_tests.rs @@ -12,23 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::keystore2_client_test_utils::perform_sample_sign_operation; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, - SecurityLevel::SecurityLevel, }; use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, -}; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, + Domain::Domain, KeyDescriptor::KeyDescriptor, }; - -use crate::keystore2_client_test_utils::perform_sample_sign_operation; +use keystore2_test_utils::{authorizations, key_generations, key_generations::Error, SecLevel}; /// Generate HMAC key with given parameters and perform a sample operation using generated key. fn create_hmac_key_and_operation( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias: &str, key_size: i32, mac_len: i32, @@ -36,9 +31,9 @@ fn create_hmac_key_and_operation( digest: Digest, ) -> Result<(), binder::Status> { let key_metadata = - key_generations::generate_hmac_key(sec_level, alias, key_size, min_mac_len, digest)?; + key_generations::generate_hmac_key(sl, alias, key_size, min_mac_len, digest)?; - let op_response = sec_level.createOperation( + let op_response = sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new() .purpose(KeyPurpose::SIGN) @@ -69,22 +64,14 @@ fn keystore2_hmac_key_op_success() { let mac_len = 128; let key_size = 128; - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for digest in digests { let alias = format!("ks_hmac_test_key_{}", digest.0); assert_eq!( Ok(()), - create_hmac_key_and_operation( - &sec_level, - &alias, - key_size, - mac_len, - min_mac_len, - digest, - ) + create_hmac_key_and_operation(&sl, &alias, key_size, mac_len, min_mac_len, digest,) ); } } @@ -96,13 +83,12 @@ fn keystore2_hmac_gen_keys_fails_expect_unsupported_key_size() { let min_mac_len = 256; let digest = Digest::SHA_2_256; - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for key_size in 0..513 { let alias = format!("ks_hmac_test_key_{}", key_size); let result = key_generations::map_ks_error(key_generations::generate_hmac_key( - &sec_level, + &sl, &alias, key_size, min_mac_len, @@ -128,13 +114,12 @@ fn keystore2_hmac_gen_keys_fails_expect_unsupported_min_mac_length() { let digest = Digest::SHA_2_256; let key_size = 128; - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for min_mac_len in 0..257 { let alias = format!("ks_hmac_test_key_mml_{}", min_mac_len); match key_generations::map_ks_error(key_generations::generate_hmac_key( - &sec_level, + &sl, &alias, key_size, min_mac_len, @@ -159,8 +144,7 @@ fn keystore2_hmac_gen_keys_fails_expect_unsupported_min_mac_length() { /// Test fails to generate a key with multiple digests with an error code `UNSUPPORTED_DIGEST`. #[test] fn keystore2_hmac_gen_key_multi_digests_fails_expect_unsupported_digest() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_hmac_test_key_multi_dig"; let gen_params = authorizations::AuthSetBuilder::new() @@ -173,7 +157,7 @@ fn keystore2_hmac_gen_key_multi_digests_fails_expect_unsupported_digest() { .digest(Digest::SHA1) .digest(Digest::SHA_2_256); - let result = key_generations::map_ks_error(sec_level.generateKey( + let result = key_generations::map_ks_error(sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -193,8 +177,7 @@ fn keystore2_hmac_gen_key_multi_digests_fails_expect_unsupported_digest() { /// no digest should fail with an error code `UNSUPPORTED_DIGEST`. #[test] fn keystore2_hmac_gen_key_no_digests_fails_expect_unsupported_digest() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_hmac_test_key_no_dig"; let gen_params = authorizations::AuthSetBuilder::new() @@ -205,7 +188,7 @@ fn keystore2_hmac_gen_key_no_digests_fails_expect_unsupported_digest() { .key_size(128) .min_mac_length(128); - let result = key_generations::map_ks_error(sec_level.generateKey( + let result = key_generations::map_ks_error(sl.binder.generateKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -227,12 +210,11 @@ fn keystore2_hmac_gen_key_no_digests_fails_expect_unsupported_digest() { fn keystore2_hmac_gen_key_with_none_digest_fails_expect_unsupported_digest() { let min_mac_len = 128; let key_size = 128; - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_hmac_test_key_fail"; let result = key_generations::map_ks_error(key_generations::generate_hmac_key( - &sec_level, + &sl, alias, key_size, min_mac_len, @@ -253,14 +235,13 @@ fn keystore2_hmac_key_op_with_mac_len_greater_than_digest_len_fail() { let mac_len = 256; let key_size = 128; - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for digest in digests { let alias = format!("ks_hmac_test_key_{}", digest.0); let result = key_generations::map_ks_error(create_hmac_key_and_operation( - &sec_level, + &sl, &alias, key_size, mac_len, @@ -284,14 +265,13 @@ fn keystore2_hmac_key_op_with_mac_len_less_than_min_mac_len_fail() { let mac_len = 64; let key_size = 128; - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); for digest in digests { let alias = format!("ks_hmac_test_key_{}", digest.0); let result = key_generations::map_ks_error(create_hmac_key_and_operation( - &sec_level, + &sl, &alias, key_size, mac_len, diff --git a/keystore2/tests/keystore2_client_import_keys_tests.rs b/keystore2/tests/keystore2_client_import_keys_tests.rs index 31d57a2f..f3a267bb 100644 --- a/keystore2/tests/keystore2_client_import_keys_tests.rs +++ b/keystore2/tests/keystore2_client_import_keys_tests.rs @@ -12,49 +12,41 @@ // See the License for the specific language governing permissions and // limitations under the License. -use nix::unistd::getuid; - -use openssl::rand::rand_bytes; -use openssl::x509::X509; - +use crate::keystore2_client_test_utils::{ + encrypt_secure_key, encrypt_transport_key, get_vsr_api_level, + perform_sample_asym_sign_verify_op, perform_sample_hmac_sign_verify_op, + perform_sample_sym_key_decrypt_op, perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, +}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, BlockMode::BlockMode, Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, HardwareAuthenticatorType::HardwareAuthenticatorType, - KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, + KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, }; use android_system_keystore2::aidl::android::system::keystore2::{ AuthenticatorSpec::AuthenticatorSpec, Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, ResponseCode::ResponseCode, }; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, -}; - use keystore2_test_utils::ffi_test_utils::{ create_wrapped_key, create_wrapped_key_additional_auth_data, }; - -use crate::keystore2_client_test_utils::{ - encrypt_secure_key, encrypt_transport_key, perform_sample_asym_sign_verify_op, - perform_sample_hmac_sign_verify_op, perform_sample_sym_key_decrypt_op, - perform_sample_sym_key_encrypt_op, SAMPLE_PLAIN_TEXT, -}; +use keystore2_test_utils::{authorizations, key_generations, key_generations::Error, SecLevel}; +use nix::unistd::getuid; +use openssl::rand::rand_bytes; +use openssl::x509::X509; pub fn import_rsa_sign_key_and_perform_sample_operation( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, import_params: authorizations::AuthSetBuilder, ) { let key_metadata = - key_generations::import_rsa_2048_key(sec_level, domain, nspace, alias, import_params) - .unwrap(); + key_generations::import_rsa_2048_key(sl, domain, nspace, alias, import_params).unwrap(); perform_sample_asym_sign_verify_op( - sec_level, + &sl.binder, &key_metadata, Some(PaddingMode::RSA_PSS), Some(Digest::SHA_2_256), @@ -93,7 +85,7 @@ fn perform_sym_key_encrypt_decrypt_op( } fn build_secure_key_wrapper( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, secure_key: &[u8], transport_key: &[u8], nonce: &[u8], @@ -103,10 +95,10 @@ fn build_secure_key_wrapper( // Encrypt secure key with transport key. let transport_key_alias = format!("ks_transport_key_aes_256_key_test_{}", getuid()); let transport_key_metadata = - key_generations::import_transport_key(sec_level, Some(transport_key_alias), transport_key) + key_generations::import_transport_key(sl, Some(transport_key_alias), transport_key) .unwrap(); let encrypted_secure_key = encrypt_secure_key( - sec_level, + &sl.binder, secure_key, aad, nonce.to_vec(), @@ -135,8 +127,7 @@ fn build_secure_key_wrapper( /// imported key. Test should be able to create an operation successfully. #[test] fn keystore2_rsa_import_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_rsa_key_test_import_1_{}{}", getuid(), 2048); @@ -153,7 +144,7 @@ fn keystore2_rsa_import_key_success() { .cert_not_after(253402300799000); import_rsa_sign_key_and_perform_sample_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias), @@ -167,8 +158,7 @@ fn keystore2_rsa_import_key_success() { /// able to create an operation successfully. #[test] fn keystore2_rsa_import_key_determine_key_size_and_pub_exponent() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_rsa_key_test_import_2_{}{}", getuid(), 2048); @@ -184,7 +174,7 @@ fn keystore2_rsa_import_key_determine_key_size_and_pub_exponent() { .cert_not_after(253402300799000); import_rsa_sign_key_and_perform_sample_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias), @@ -196,8 +186,7 @@ fn keystore2_rsa_import_key_determine_key_size_and_pub_exponent() { /// a key with `IMPORT_PARAMETER_MISMATCH` error code. #[test] fn keystore2_rsa_import_key_fails_with_keysize_param_mismatch_error() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_rsa_key_test_import_3_{}{}", getuid(), 2048); @@ -213,7 +202,7 @@ fn keystore2_rsa_import_key_fails_with_keysize_param_mismatch_error() { .cert_not_before(0) .cert_not_after(253402300799000); - let result = key_generations::map_ks_error(sec_level.importKey( + let result = key_generations::map_ks_error(sl.binder.importKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, None, &import_params, @@ -229,8 +218,7 @@ fn keystore2_rsa_import_key_fails_with_keysize_param_mismatch_error() { /// Test should fail to import a key with `IMPORT_PARAMETER_MISMATCH` error code. #[test] fn keystore2_rsa_import_key_fails_with_public_exponent_param_mismatch_error() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_rsa_key_test_import_4_{}{}", getuid(), 2048); @@ -246,7 +234,7 @@ fn keystore2_rsa_import_key_fails_with_public_exponent_param_mismatch_error() { .cert_not_before(0) .cert_not_after(253402300799000); - let result = key_generations::map_ks_error(sec_level.importKey( + let result = key_generations::map_ks_error(sl.binder.importKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, None, &import_params, @@ -259,12 +247,11 @@ fn keystore2_rsa_import_key_fails_with_public_exponent_param_mismatch_error() { } /// Try to import a key with multiple purposes. Test should fail to import a key with -/// `INCOMPATIBLE_PURPOSE` error code. If the backend is `keymaster` then `importKey` shall be -/// successful. +/// `INCOMPATIBLE_PURPOSE` error code. If the backend is `keymaster` or KeyMint-version-1 then +/// `importKey` shall be successful. #[test] fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_rsa_key_test_import_5_{}{}", getuid(), 2048); @@ -280,7 +267,7 @@ fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() { .cert_not_before(0) .cert_not_after(253402300799000); - let result = key_generations::map_ks_error(sec_level.importKey( + let result = key_generations::map_ks_error(sl.binder.importKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, None, &import_params, @@ -288,9 +275,15 @@ fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() { key_generations::RSA_2048_KEY, )); - if key_generations::has_default_keymint() { - assert!(result.is_err()); - assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); + if sl.is_keymint() { + if sl.get_keymint_version() >= 2 { + // The KeyMint v1 spec required that KeyPurpose::ATTEST_KEY not be combined + // with other key purposes. However, this was not checked at the time + // so we can only be strict about checking this for implementations of KeyMint + // version 2 and above. + assert!(result.is_err()); + assert_eq!(Error::Km(ErrorCode::INCOMPATIBLE_PURPOSE), result.unwrap_err()); + } } else { assert!(result.is_ok()); } @@ -301,11 +294,17 @@ fn keystore2_rsa_import_key_with_multipurpose_fails_incompt_purpose_error() { /// able to create an operation successfully. #[test] fn keystore2_import_ec_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_ec_key_test_import_1_{}{}", getuid(), 256); + if get_vsr_api_level() < 35 { + // The KeyMint spec was previously not clear as to whether EC_CURVE was optional on import + // of EC keys. However, this was not checked at the time so we can only be strict about + // checking this for implementations at VSR-V or later. + println!("Skipping EC_CURVE on import only strict >= VSR-V"); + return; + } // Don't specify ec-curve. let import_params = authorizations::AuthSetBuilder::new() .no_auth_required() @@ -316,24 +315,18 @@ fn keystore2_import_ec_key_success() { .cert_not_before(0) .cert_not_after(253402300799000); - let key_metadata = key_generations::import_ec_p_256_key( - &sec_level, - Domain::APP, - -1, - Some(alias), - import_params, - ) - .expect("Failed to import EC key."); + let key_metadata = + key_generations::import_ec_p_256_key(&sl, Domain::APP, -1, Some(alias), import_params) + .expect("Failed to import EC key."); - perform_sample_asym_sign_verify_op(&sec_level, &key_metadata, None, Some(Digest::SHA_2_256)); + perform_sample_asym_sign_verify_op(&sl.binder, &key_metadata, None, Some(Digest::SHA_2_256)); } /// Try to import EC key with wrong ec-curve as import-key-parameter. Test should fail to import a /// key with `IMPORT_PARAMETER_MISMATCH` error code. #[test] fn keystore2_ec_import_key_fails_with_mismatch_curve_error() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_ec_key_test_import_1_{}{}", getuid(), 256); @@ -347,7 +340,7 @@ fn keystore2_ec_import_key_fails_with_mismatch_curve_error() { .cert_not_before(0) .cert_not_after(253402300799000); - let result = key_generations::map_ks_error(sec_level.importKey( + let result = key_generations::map_ks_error(sl.binder.importKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, alias: Some(alias), blob: None }, None, &import_params, @@ -362,47 +355,41 @@ fn keystore2_ec_import_key_fails_with_mismatch_curve_error() { /// Test should be able to create an operation successfully. #[test] fn keystore2_import_aes_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_aes_key_test_import_1_{}{}", getuid(), 256); - let key_metadata = key_generations::import_aes_key(&sec_level, Domain::APP, -1, Some(alias)) + let key_metadata = key_generations::import_aes_key(&sl, Domain::APP, -1, Some(alias)) .expect("Failed to import AES key."); - perform_sym_key_encrypt_decrypt_op(&sec_level, &key_metadata); + perform_sym_key_encrypt_decrypt_op(&sl.binder, &key_metadata); } /// Import 3DES key and verify key parameters. Try to create an operation using the imported key. /// Test should be able to create an operation successfully. #[test] fn keystore2_import_3des_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = key_generations::map_ks_error( - keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT), - ) - .unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_3des_key_test_import_1_{}{}", getuid(), 168); - let key_metadata = key_generations::import_3des_key(&sec_level, Domain::APP, -1, Some(alias)) + let key_metadata = key_generations::import_3des_key(&sl, Domain::APP, -1, Some(alias)) .expect("Failed to import 3DES key."); - perform_sym_key_encrypt_decrypt_op(&sec_level, &key_metadata); + perform_sym_key_encrypt_decrypt_op(&sl.binder, &key_metadata); } /// Import HMAC key and verify key parameters. Try to create an operation using the imported key. /// Test should be able to create an operation successfully. #[test] fn keystore2_import_hmac_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_hmac_key_test_import_1_{}", getuid()); - let key_metadata = key_generations::import_hmac_key(&sec_level, Domain::APP, -1, Some(alias)) + let key_metadata = key_generations::import_hmac_key(&sl, Domain::APP, -1, Some(alias)) .expect("Failed to import HMAC key."); - perform_sample_hmac_sign_verify_op(&sec_level, &key_metadata.key); + perform_sample_hmac_sign_verify_op(&sl.binder, &key_metadata.key); } /// This test creates a wrapped key data and imports it. Validates the imported wrapped key. @@ -412,8 +399,7 @@ fn keystore2_import_hmac_key_success() { /// Test should successfully import the wrapped key and perform crypto operations. #[test] fn keystore2_create_wrapped_key_and_import_wrapped_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let mut secure_key = [0; 32]; rand_bytes(&mut secure_key).unwrap(); @@ -427,7 +413,7 @@ fn keystore2_create_wrapped_key_and_import_wrapped_key_success() { // Import wrapping key. let wrapping_key_alias = format!("ks_wrapping_key_test_import_2_{}_2048", getuid()); let wrapping_key_metadata = key_generations::import_wrapping_key( - &sec_level, + &sl, key_generations::RSA_2048_KEY, Some(wrapping_key_alias), ) @@ -439,7 +425,7 @@ fn keystore2_create_wrapped_key_and_import_wrapped_key_success() { // Build ASN.1 DER-encoded wrapped key material as described in `SecureKeyWrapper` schema. let wrapped_key_data = build_secure_key_wrapper( - &sec_level, + &sl, &secure_key, &transport_key, &nonce, @@ -451,14 +437,14 @@ fn keystore2_create_wrapped_key_and_import_wrapped_key_success() { // Unwrap the key. Import wrapped key. let secured_key_alias = format!("ks_wrapped_aes_key_{}", getuid()); let secured_key_metadata = key_generations::import_wrapped_key( - &sec_level, + &sl, Some(secured_key_alias), &wrapping_key_metadata, Some(wrapped_key_data.to_vec()), ) .unwrap(); - perform_sym_key_encrypt_decrypt_op(&sec_level, &secured_key_metadata); + perform_sym_key_encrypt_decrypt_op(&sl.binder, &secured_key_metadata); } /// Create a wrapped key data with invalid Additional Authenticated Data (AAD) and @@ -469,8 +455,7 @@ fn keystore2_create_wrapped_key_and_import_wrapped_key_success() { /// Test should fail to import the wrapped key with error code `VERIFICATION_FAILED`. #[test] fn keystore2_create_wrapped_key_with_invalid_aad_and_import_wrapped_key_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let mut secure_key = [0; 32]; rand_bytes(&mut secure_key).unwrap(); @@ -484,7 +469,7 @@ fn keystore2_create_wrapped_key_with_invalid_aad_and_import_wrapped_key_fail() { // Import wrapping key. let wrapping_key_alias = format!("ks_wrapping_key_test_import_2_{}_2048", getuid()); let wrapping_key_metadata = key_generations::import_wrapping_key( - &sec_level, + &sl, key_generations::RSA_2048_KEY, Some(wrapping_key_alias), ) @@ -495,7 +480,7 @@ fn keystore2_create_wrapped_key_with_invalid_aad_and_import_wrapped_key_fail() { // Build ASN.1 DER-encoded wrapped key material as described in `SecureKeyWrapper` schema. let wrapped_key_data = build_secure_key_wrapper( - &sec_level, + &sl, &secure_key, &transport_key, &nonce, @@ -507,7 +492,7 @@ fn keystore2_create_wrapped_key_with_invalid_aad_and_import_wrapped_key_fail() { // Unwrap the key. Import wrapped key. let secured_key_alias = format!("ks_wrapped_aes_key_{}", getuid()); let result = key_generations::map_ks_error(key_generations::import_wrapped_key( - &sec_level, + &sl, Some(secured_key_alias), &wrapping_key_metadata, Some(wrapped_key_data.to_vec()), @@ -521,8 +506,7 @@ fn keystore2_create_wrapped_key_with_invalid_aad_and_import_wrapped_key_fail() { /// perform crypto operations successfully. #[test] fn keystore2_import_wrapped_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_wrapped_key_test_import_1_{}_256", getuid()); let wrapping_key_alias = format!("ks_wrapping_key_test_import_1_{}_2048", getuid()); @@ -541,7 +525,7 @@ fn keystore2_import_wrapped_key_success() { .cert_not_after(253402300799000); let key_metadata = key_generations::import_wrapping_key_and_wrapped_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias), @@ -551,7 +535,7 @@ fn keystore2_import_wrapped_key_success() { .expect("Failed to import wrapped key."); // Try to perform operations using wrapped key. - perform_sym_key_encrypt_decrypt_op(&sec_level, &key_metadata); + perform_sym_key_encrypt_decrypt_op(&sl.binder, &key_metadata); } /// Import wrapping-key without specifying KeyPurpose::WRAP_KEY in import key parameters. Try to @@ -560,8 +544,7 @@ fn keystore2_import_wrapped_key_success() { /// `WRAP_KEY` purpose. #[test] fn keystore2_import_wrapped_key_fails_with_wrong_purpose() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let wrapping_key_alias = format!("ks_wrapping_key_test_import_2_{}_2048", getuid()); let alias = format!("ks_wrapped_key_test_import_2_{}_256", getuid()); @@ -581,7 +564,7 @@ fn keystore2_import_wrapped_key_fails_with_wrong_purpose() { let result = key_generations::map_ks_error(key_generations::import_wrapping_key_and_wrapped_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias), @@ -597,8 +580,7 @@ fn keystore2_import_wrapped_key_fails_with_wrong_purpose() { /// Test should fail to import wrapped key with `ResponseCode::KEY_NOT_FOUND`. #[test] fn keystore2_import_wrapped_key_fails_with_missing_wrapping_key() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let unwrap_params = authorizations::AuthSetBuilder::new() .digest(Digest::SHA_2_256) @@ -614,7 +596,7 @@ fn keystore2_import_wrapped_key_fails_with_missing_wrapping_key() { // Wrapping key with this alias doesn't exist. let wrapping_key_alias = format!("ks_wrapping_key_not_exist_{}_2048", getuid()); - let result = key_generations::map_ks_error(sec_level.importWrappedKey( + let result = key_generations::map_ks_error(sl.binder.importWrappedKey( &KeyDescriptor { domain: Domain::APP, nspace: -1, diff --git a/keystore2/tests/keystore2_client_key_agreement_tests.rs b/keystore2/tests/keystore2_client_key_agreement_tests.rs index 6b2e3c2d..6744b60d 100644 --- a/keystore2/tests/keystore2_client_key_agreement_tests.rs +++ b/keystore2/tests/keystore2_client_key_agreement_tests.rs @@ -12,27 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -use nix::unistd::getuid; - -use openssl::ec::{EcGroup, EcKey}; -use openssl::error::ErrorStack; -use openssl::nid::Nid; -use openssl::pkey::{PKey, PKeyRef, Private, Public}; -use openssl::pkey_ctx::PkeyCtx; -use openssl::x509::X509; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Digest::Digest, EcCurve::EcCurve, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, - SecurityLevel::SecurityLevel, }; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, }; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, -}; +use keystore2_test_utils::{authorizations, key_generations, key_generations::Error, SecLevel}; +use nix::unistd::getuid; +use openssl::ec::{EcGroup, EcKey}; +use openssl::error::ErrorStack; +use openssl::nid::Nid; +use openssl::pkey::{PKey, PKeyRef, Private, Public}; +use openssl::pkey_ctx::PkeyCtx; +use openssl::x509::X509; /// This macro is used to verify that the key agreement works for the given curve. macro_rules! test_ec_key_agree { @@ -89,13 +83,12 @@ fn ec_curve_to_openrssl_curve_name(ec_curve: &EcCurve) -> Nid { /// Generate two EC keys with given curve from KeyMint and OpeanSSL. Perform local ECDH between /// them and verify that the derived secrets are the same. fn perform_ec_key_agreement(ec_curve: EcCurve) { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let openssl_ec_curve = ec_curve_to_openrssl_curve_name(&ec_curve); let alias = format!("ks_ec_test_key_agree_{}", getuid()); let keymint_key = key_generations::generate_ec_agree_key( - &sec_level, + &sl, ec_curve, Digest::SHA_2_256, Domain::APP, @@ -111,7 +104,7 @@ fn perform_ec_key_agreement(ec_curve: EcCurve) { let local_key = PKey::from_ec_key(ec_key).unwrap(); let local_pub_key = local_key.public_key_to_der().unwrap(); - check_agreement(&sec_level, &keymint_key.key, &keymint_pub_key, &local_key, &local_pub_key); + check_agreement(&sl.binder, &keymint_key.key, &keymint_pub_key, &local_key, &local_pub_key); } test_ec_key_agree!(test_ec_p224_key_agreement, EcCurve::P_224); @@ -119,16 +112,15 @@ test_ec_key_agree!(test_ec_p256_key_agreement, EcCurve::P_256); test_ec_key_agree!(test_ec_p384_key_agreement, EcCurve::P_384); test_ec_key_agree!(test_ec_p521_key_agreement, EcCurve::P_521); -/// Generate two EC keys with curve `CURVE_25519` from KeyMint and OpeanSSL. +/// Generate two EC keys with curve `CURVE_25519` from KeyMint and OpenSSL. /// Perform local ECDH between them and verify that the derived secrets are the same. #[test] fn keystore2_ec_25519_agree_key_success() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_ec_25519_test_key_agree_{}", getuid()); let keymint_key = key_generations::generate_ec_agree_key( - &sec_level, + &sl, EcCurve::CURVE_25519, Digest::NONE, Domain::APP, @@ -142,19 +134,18 @@ fn keystore2_ec_25519_agree_key_success() { let local_key = PKey::generate_x25519().unwrap(); let local_pub_key = local_key.public_key_to_der().unwrap(); - check_agreement(&sec_level, &keymint_key.key, &keymint_pub_key, &local_key, &local_pub_key); + check_agreement(&sl.binder, &keymint_key.key, &keymint_pub_key, &local_key, &local_pub_key); } /// Generate two EC keys with different curves and try to perform local ECDH. Since keys are using /// different curves operation should fail with `ErrorCode:INVALID_ARGUMENT`. #[test] fn keystore2_ec_agree_key_with_different_curves_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_test_key_agree_fail{}", getuid()); let keymint_key = key_generations::generate_ec_agree_key( - &sec_level, + &sl, EcCurve::P_256, Digest::SHA_2_256, Domain::APP, @@ -169,7 +160,7 @@ fn keystore2_ec_agree_key_with_different_curves_fail() { // If the keys are using different curves KeyMint should fail with // ErrorCode:INVALID_ARGUMENT. let authorizations = authorizations::AuthSetBuilder::new().purpose(KeyPurpose::AGREE_KEY); - let key_agree_op = sec_level.createOperation(&keymint_key.key, &authorizations, false).unwrap(); + let key_agree_op = sl.binder.createOperation(&keymint_key.key, &authorizations, false).unwrap(); assert!(key_agree_op.iOperation.is_some()); let op = key_agree_op.iOperation.unwrap(); diff --git a/keystore2/tests/keystore2_client_key_id_domain_tests.rs b/keystore2/tests/keystore2_client_key_id_domain_tests.rs index 09b13784..8f9191f3 100644 --- a/keystore2/tests/keystore2_client_key_id_domain_tests.rs +++ b/keystore2/tests/keystore2_client_key_id_domain_tests.rs @@ -12,20 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use nix::unistd::getuid; - +use crate::keystore2_client_test_utils::perform_sample_sign_operation; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, + Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose, }; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode, }; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, -}; - -use crate::keystore2_client_test_utils::perform_sample_sign_operation; +use keystore2_test_utils::{authorizations, key_generations, key_generations::Error, SecLevel}; +use nix::unistd::getuid; /// Try to generate a key with `Domain::KEY_ID`, test should fail with an error code /// `SYSTEM_ERROR`. `Domain::KEY_ID` is not allowed to use for generating a key. Key id is returned @@ -33,11 +28,10 @@ use crate::keystore2_client_test_utils::perform_sample_sign_operation; #[test] fn keystore2_generate_key_with_key_id_domain_expect_sys_error() { let alias = "ks_gen_key_id_test_key"; - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let result = key_generations::map_ks_error(key_generations::generate_ec_key( - &sec_level, + &sl, Domain::KEY_ID, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias.to_string()), @@ -53,12 +47,11 @@ fn keystore2_generate_key_with_key_id_domain_expect_sys_error() { /// successfully. #[test] fn keystore2_find_key_with_key_id_as_domain() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_key_id_test_key"; let key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -68,7 +61,8 @@ fn keystore2_find_key_with_key_id_as_domain() { .expect("Failed to generate a EC key."); // Try to load the above generated key with KEY_ID as domain. - let key_entry_response = keystore2 + let key_entry_response = sl + .keystore2 .getKeyEntry(&KeyDescriptor { domain: Domain::KEY_ID, nspace: key_metadata.key.nspace, @@ -85,7 +79,8 @@ fn keystore2_find_key_with_key_id_as_domain() { // Try to create an operation using above loaded key, operation should be created // successfully. - let op_response = sec_level + let op_response = sl + .binder .createOperation( &key_entry_response.metadata.key, &authorizations::AuthSetBuilder::new() @@ -110,12 +105,11 @@ fn keystore2_find_key_with_key_id_as_domain() { /// create an operation using the rebound key. #[test] fn keystore2_key_id_alias_rebind_verify_by_alias() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_key_id_test_alias_rebind_1_{}", getuid()); let key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -127,7 +121,7 @@ fn keystore2_key_id_alias_rebind_verify_by_alias() { // Generate a key with same alias as above generated key, so that alias will be rebound // to this key. let new_key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias), @@ -142,7 +136,7 @@ fn keystore2_key_id_alias_rebind_verify_by_alias() { // Try to create an operation using previously generated key_metadata. // It should fail as previously generated key material is no longer remains valid. - let result = key_generations::map_ks_error(sec_level.createOperation( + let result = key_generations::map_ks_error(sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), false, @@ -152,7 +146,8 @@ fn keystore2_key_id_alias_rebind_verify_by_alias() { // Try to create an operation using rebound key, operation should be created // successfully. - let op_response = sec_level + let op_response = sl + .binder .createOperation( &new_key_metadata.key, &authorizations::AuthSetBuilder::new() @@ -177,12 +172,11 @@ fn keystore2_key_id_alias_rebind_verify_by_alias() { /// Test should successfully create an operation using the rebound key. #[test] fn keystore2_key_id_alias_rebind_verify_by_key_id() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_key_id_test_alias_rebind_2_{}", getuid()); let key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -192,7 +186,8 @@ fn keystore2_key_id_alias_rebind_verify_by_key_id() { .expect("Failed to generate a EC key."); // Load the above generated key with KEY_ID as domain. - let key_entry_response = keystore2 + let key_entry_response = sl + .keystore2 .getKeyEntry(&KeyDescriptor { domain: Domain::KEY_ID, nspace: key_metadata.key.nspace, @@ -210,7 +205,7 @@ fn keystore2_key_id_alias_rebind_verify_by_key_id() { // Generate another key with same alias as above generated key, so that alias will be rebound // to this key. let new_key_metadata = key_generations::generate_ec_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias), @@ -227,7 +222,7 @@ fn keystore2_key_id_alias_rebind_verify_by_key_id() { // Try to create an operation using previously loaded key_entry_response. // It should fail as previously generated key material is no longer valid. - let result = key_generations::map_ks_error(sec_level.createOperation( + let result = key_generations::map_ks_error(sl.binder.createOperation( &key_entry_response.metadata.key, &authorizations::AuthSetBuilder::new().purpose(KeyPurpose::SIGN).digest(Digest::SHA_2_256), false, @@ -237,7 +232,8 @@ fn keystore2_key_id_alias_rebind_verify_by_key_id() { // Try to create an operation using rebound key, operation should be created // successfully. - let op_response = sec_level + let op_response = sl + .binder .createOperation( &new_key_metadata.key, &authorizations::AuthSetBuilder::new() diff --git a/keystore2/tests/keystore2_client_keystore_engine_tests.rs b/keystore2/tests/keystore2_client_keystore_engine_tests.rs index 4651931b..01f8917e 100644 --- a/keystore2/tests/keystore2_client_keystore_engine_tests.rs +++ b/keystore2/tests/keystore2_client_keystore_engine_tests.rs @@ -12,27 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use nix::unistd::{Gid, Uid}; -use rustutils::users::AID_USER_OFFSET; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Algorithm::Algorithm, Digest::Digest, EcCurve::EcCurve, KeyPurpose::KeyPurpose, - PaddingMode::PaddingMode, SecurityLevel::SecurityLevel, + PaddingMode::PaddingMode, }; use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel, - IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, + Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, + KeyPermission::KeyPermission, }; - -use keystore2_test_utils::{authorizations::AuthSetBuilder, get_keystore_service, run_as}; - use keystore2_test_utils::ffi_test_utils::perform_crypto_op_using_keystore_engine; - +use keystore2_test_utils::{ + authorizations::AuthSetBuilder, get_keystore_service, run_as, SecLevel, +}; +use nix::unistd::{Gid, Uid}; use openssl::x509::X509; +use rustutils::users::AID_USER_OFFSET; fn generate_rsa_key_and_grant_to_user( - keystore2: &binder::Strong<dyn IKeystoreService>, - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias: &str, grantee_uid: i32, access_vector: i32, @@ -47,7 +44,8 @@ fn generate_rsa_key_and_grant_to_user( .padding_mode(PaddingMode::NONE) .digest(Digest::NONE); - let key_metadata = sec_level + let key_metadata = sl + .binder .generateKey( &KeyDescriptor { domain: Domain::APP, @@ -64,12 +62,11 @@ fn generate_rsa_key_and_grant_to_user( assert!(key_metadata.certificate.is_some()); - keystore2.grant(&key_metadata.key, grantee_uid, access_vector) + sl.keystore2.grant(&key_metadata.key, grantee_uid, access_vector) } fn generate_ec_key_and_grant_to_user( - keystore2: &binder::Strong<dyn IKeystoreService>, - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias: &str, grantee_uid: i32, access_vector: i32, @@ -82,7 +79,8 @@ fn generate_ec_key_and_grant_to_user( .digest(Digest::NONE) .ec_curve(EcCurve::P_256); - let key_metadata = sec_level + let key_metadata = sl + .binder .generateKey( &KeyDescriptor { domain: Domain::APP, @@ -99,12 +97,11 @@ fn generate_ec_key_and_grant_to_user( assert!(key_metadata.certificate.is_some()); - keystore2.grant(&key_metadata.key, grantee_uid, access_vector) + sl.keystore2.grant(&key_metadata.key, grantee_uid, access_vector) } fn generate_key_and_grant_to_user( - keystore2: &binder::Strong<dyn IKeystoreService>, - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias: &str, grantee_uid: u32, algo: Algorithm, @@ -115,16 +112,14 @@ fn generate_key_and_grant_to_user( let grant_key = match algo { Algorithm::RSA => generate_rsa_key_and_grant_to_user( - keystore2, - sec_level, + sl, alias, grantee_uid.try_into().unwrap(), access_vector, ) .unwrap(), Algorithm::EC => generate_ec_key_and_grant_to_user( - keystore2, - sec_level, + sl, alias, grantee_uid.try_into().unwrap(), access_vector, @@ -170,17 +165,9 @@ fn keystore2_perofrm_crypto_op_using_keystore2_engine_rsa_key_success() { // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "keystore2_engine_rsa_key"; - generate_key_and_grant_to_user( - &keystore2, - &sec_level, - alias, - GRANTEE_UID, - Algorithm::RSA, - ) - .unwrap() + generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap() }) }; @@ -213,17 +200,9 @@ fn keystore2_perofrm_crypto_op_using_keystore2_engine_ec_key_success() { // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "keystore2_engine_ec_test_key"; - generate_key_and_grant_to_user( - &keystore2, - &sec_level, - alias, - GRANTEE_UID, - Algorithm::EC, - ) - .unwrap() + generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::EC).unwrap() }) }; @@ -257,20 +236,14 @@ fn keystore2_perofrm_crypto_op_using_keystore2_engine_pem_pub_key_success() { // SAFETY: The test is run in a separate process with no other threads. let grant_key_nspace = unsafe { run_as::run_as(TARGET_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "keystore2_engine_rsa_pem_pub_key"; - let grant_key_nspace = generate_key_and_grant_to_user( - &keystore2, - &sec_level, - alias, - GRANTEE_UID, - Algorithm::RSA, - ) - .unwrap(); + let grant_key_nspace = + generate_key_and_grant_to_user(&sl, alias, GRANTEE_UID, Algorithm::RSA).unwrap(); // Update certificate with encodeed PEM data. - let key_entry_response = keystore2 + let key_entry_response = sl + .keystore2 .getKeyEntry(&KeyDescriptor { domain: Domain::APP, nspace: -1, @@ -281,7 +254,7 @@ fn keystore2_perofrm_crypto_op_using_keystore2_engine_pem_pub_key_success() { let cert_bytes = key_entry_response.metadata.certificate.as_ref().unwrap(); let cert = X509::from_der(cert_bytes.as_ref()).unwrap(); let cert_pem = cert.to_pem().unwrap(); - keystore2 + sl.keystore2 .updateSubcomponent(&key_entry_response.metadata.key, Some(&cert_pem), None) .expect("updateSubcomponent failed."); diff --git a/keystore2/tests/keystore2_client_list_entries_tests.rs b/keystore2/tests/keystore2_client_list_entries_tests.rs index 8b3f7001..539dac2d 100644 --- a/keystore2/tests/keystore2_client_list_entries_tests.rs +++ b/keystore2/tests/keystore2_client_list_entries_tests.rs @@ -12,19 +12,18 @@ // 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 std::collections::HashSet; -use std::fmt::Write; - -use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel; +use crate::keystore2_client_test_utils::{delete_all_entries, delete_app_key, verify_aliases}; use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, ResponseCode::ResponseCode, }; - -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, SecLevel, +}; +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; +use std::collections::HashSet; +use std::fmt::Write; /// Try to find a key with given key parameters using `listEntries` API. fn key_alias_exists( @@ -63,20 +62,19 @@ fn keystore2_list_entries_success() { // SAFETY: The test is run in a separate process with no other threads. unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("list_entries_grant_key1_{}", getuid()); // Make sure there is no key exist with this `alias` in `SELINUX` domain and // `SELINUX_SHELL_NAMESPACE` namespace. if key_alias_exists( - &keystore2, + &sl.keystore2, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, alias.to_string(), ) { - keystore2 + sl.keystore2 .deleteKey(&KeyDescriptor { domain: Domain::SELINUX, nspace: key_generations::SELINUX_SHELL_NAMESPACE, @@ -88,7 +86,7 @@ fn keystore2_list_entries_success() { // Generate a key with above defined `alias`. let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias.to_string()), @@ -99,7 +97,7 @@ fn keystore2_list_entries_success() { // Verify that above generated key entry is listed with domain SELINUX and // namespace SELINUX_SHELL_NAMESPACE assert!(key_alias_exists( - &keystore2, + &sl.keystore2, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, alias, @@ -107,7 +105,7 @@ fn keystore2_list_entries_success() { // Grant a key with GET_INFO permission. let access_vector = KeyPermission::GET_INFO.0; - keystore2 + sl.keystore2 .grant(&key_metadata.key, GRANTEE_UID.try_into().unwrap(), access_vector) .unwrap(); }) @@ -121,13 +119,11 @@ fn keystore2_list_entries_success() { Uid::from_raw(GRANTEE_UID), Gid::from_raw(GRANTEE_GID), move || { - let keystore2 = get_keystore_service(); - let sec_level = - keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("list_entries_success_key{}", getuid()); let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -137,7 +133,7 @@ fn keystore2_list_entries_success() { // Make sure there is only one key entry exist and that should be the same key // generated in this user context. Granted key shouldn't be included in this list. - let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap(); assert_eq!(1, key_descriptors.len()); let key = key_descriptors.first().unwrap(); @@ -145,9 +141,9 @@ fn keystore2_list_entries_success() { assert_eq!(key.nspace, GRANTEE_UID.try_into().unwrap()); assert_eq!(key.domain, Domain::APP); - keystore2.deleteKey(&key_metadata.key).unwrap(); + sl.keystore2.deleteKey(&key_metadata.key).unwrap(); - let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap(); assert_eq!(0, key_descriptors.len()); }, ) @@ -204,14 +200,13 @@ fn keystore2_list_entries_with_long_aliases_success() { // SAFETY: The test is run in a separate process with no other threads. 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(); + let sl = SecLevel::tee(); // Make sure there are no keystore entries exist before adding new entries. - let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap(); if !key_descriptors.is_empty() { key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| { - delete_app_key(&keystore2, &alias).unwrap(); + delete_app_key(&sl.keystore2, &alias).unwrap(); }); } @@ -223,8 +218,7 @@ fn keystore2_list_entries_with_long_aliases_success() { write!(alias, "{}_{}", "X".repeat(6000), count).unwrap(); imported_key_aliases.insert(alias.clone()); - let result = - key_generations::import_aes_key(&sec_level, Domain::APP, -1, Some(alias)); + let result = key_generations::import_aes_key(&sl, Domain::APP, -1, Some(alias)); assert!(result.is_ok()); } @@ -237,7 +231,7 @@ fn keystore2_list_entries_with_long_aliases_success() { // list of key aliases // - continue above steps till it cleanup all the imported keystore entries. while !imported_key_aliases.is_empty() { - let key_descriptors = keystore2.listEntries(Domain::APP, -1).unwrap(); + let key_descriptors = sl.keystore2.listEntries(Domain::APP, -1).unwrap(); // Check retrieved key entries list is a subset of imported keys list. assert!(key_descriptors @@ -246,7 +240,7 @@ fn keystore2_list_entries_with_long_aliases_success() { // Delete the listed key entries from Keystore as well as from imported keys list. key_descriptors.into_iter().map(|key| key.alias.unwrap()).for_each(|alias| { - delete_app_key(&keystore2, &alias).unwrap(); + delete_app_key(&sl.keystore2, &alias).unwrap(); assert!(imported_key_aliases.remove(&alias)); }); } @@ -271,17 +265,16 @@ fn keystore2_list_entries_batched_with_long_aliases_success() { // SAFETY: The test is run in a separate process with no other threads. 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(); + let sl = SecLevel::tee(); // Make sure there are no keystore entries exist before adding new entries. - delete_all_entries(&keystore2); + delete_all_entries(&sl.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(); + key_generations::import_aes_keys(&sl, "X".repeat(6000), 1..101).unwrap(); assert_eq!( - keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), 100, "Error while importing keys" ); @@ -290,7 +283,7 @@ fn keystore2_list_entries_batched_with_long_aliases_success() { let mut alias; while !imported_key_aliases.is_empty() { let key_descriptors = - keystore2.listEntriesBatched(Domain::APP, -1, start_past_alias).unwrap(); + sl.keystore2.listEntriesBatched(Domain::APP, -1, start_past_alias).unwrap(); // Check retrieved key entries list is a subset of imported keys list. assert!(key_descriptors @@ -306,9 +299,9 @@ fn keystore2_list_entries_batched_with_long_aliases_success() { } assert!(imported_key_aliases.is_empty()); - delete_all_entries(&keystore2); + delete_all_entries(&sl.keystore2); assert_eq!( - keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), 0, "Error while doing cleanup" ); @@ -339,25 +332,23 @@ fn keystore2_list_entries_batched_with_multi_procs_success() { // SAFETY: The test is run in a separate process with no other threads. 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(); + let sl = SecLevel::tee(); // Make sure there are no keystore entries exist before adding new entries. - delete_all_entries(&keystore2); + delete_all_entries(&sl.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(); + key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..4).unwrap(); assert_eq!( - keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + sl.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(); + let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap(); assert_eq!(key_descriptors.len(), 3); // Makes sure all listed aliases are matching with imported keys aliases. @@ -370,20 +361,18 @@ fn keystore2_list_entries_batched_with_multi_procs_success() { // SAFETY: The test is run in a separate process with no other threads. 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(); + let sl = SecLevel::tee(); // 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(); + key_generations::import_aes_keys(&sl, 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(), + sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), 8, "Error while importing keys" ); @@ -391,7 +380,8 @@ fn keystore2_list_entries_batched_with_multi_procs_success() { // 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 + let key_descriptors = sl + .keystore2 .listEntriesBatched(Domain::APP, -1, Some("key_test_batch_list_3")) .unwrap(); assert_eq!(key_descriptors.len(), 5); @@ -403,7 +393,7 @@ fn keystore2_list_entries_batched_with_multi_procs_success() { // 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(); + let key_descriptors = sl.keystore2.listEntriesBatched(Domain::APP, -1, None).unwrap(); assert_eq!(key_descriptors.len(), 8); // Include previously imported keys aliases as well @@ -416,9 +406,9 @@ fn keystore2_list_entries_batched_with_multi_procs_success() { .iter() .all(|key| imported_key_aliases.contains(key.alias.as_ref().unwrap()))); - delete_all_entries(&keystore2); + delete_all_entries(&sl.keystore2); assert_eq!( - keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + sl.keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), 0, "Error while doing cleanup" ); @@ -459,23 +449,23 @@ fn keystore2_list_entries_batched_with_empty_keystore_success() { /// 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 sl = SecLevel::tee(); let alias = "test_selinux_key_list_alias_batched"; - let _result = keystore2.deleteKey(&KeyDescriptor { + let _result = sl.keystore2.deleteKey(&KeyDescriptor { domain: Domain::SELINUX, nspace: key_generations::SELINUX_SHELL_NAMESPACE, alias: Some(alias.to_string()), blob: None, }); - let initial_count = keystore2 + let initial_count = sl + .keystore2 .getNumberOfEntries(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE) .unwrap(); key_generations::import_aes_key( - &sec_level, + &sl, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias.to_string()), @@ -483,14 +473,15 @@ fn keystore2_list_entries_batched_with_selinux_domain_success() { .unwrap(); assert_eq!( - keystore2 + sl.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 + let key_descriptors = sl + .keystore2 .listEntriesBatched(Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, None) .unwrap(); assert_eq!(key_descriptors.len(), (initial_count + 1) as usize); @@ -499,7 +490,7 @@ fn keystore2_list_entries_batched_with_selinux_domain_success() { key_descriptors.into_iter().map(|key| key.alias.unwrap()).filter(|a| a == alias).count(); assert_eq!(count, 1); - keystore2 + sl.keystore2 .deleteKey(&KeyDescriptor { domain: Domain::SELINUX, nspace: key_generations::SELINUX_SHELL_NAMESPACE, @@ -522,11 +513,10 @@ fn keystore2_list_entries_batched_validate_count_and_order_success() { // SAFETY: The test is run in a separate process with no other threads. 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(); + let sl = SecLevel::tee(); // Make sure there are no keystore entries exist before adding new entries. - delete_all_entries(&keystore2); + delete_all_entries(&sl.keystore2); // Import keys with below mentioned aliases - // [ @@ -542,48 +532,49 @@ fn keystore2_list_entries_batched_validate_count_and_order_success() { // key_test_batch_list_22, // ] let _imported_key_aliases = - key_generations::import_aes_keys(&sec_level, ALIAS_PREFIX.to_string(), 1..6) - .unwrap(); + key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 1..6).unwrap(); assert_eq!( - keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + sl.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(); + key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 10..13).unwrap(); assert_eq!( - keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + sl.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(); + key_generations::import_aes_keys(&sl, ALIAS_PREFIX.to_string(), 21..23).unwrap(); assert_eq!( - keystore2.getNumberOfEntries(Domain::APP, -1).unwrap(), + sl.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( + &sl.keystore2, + Some(format!("{}{}", ALIAS_PREFIX, "_5").as_str()), + vec![], + ); verify_aliases( - &keystore2, + &sl.keystore2, Some(format!("{}{}", ALIAS_PREFIX, "_4").as_str()), vec![ALIAS_PREFIX.to_owned() + "_5"], ); verify_aliases( - &keystore2, + &sl.keystore2, Some(format!("{}{}", ALIAS_PREFIX, "_3").as_str()), vec![ALIAS_PREFIX.to_owned() + "_4", ALIAS_PREFIX.to_owned() + "_5"], ); verify_aliases( - &keystore2, + &sl.keystore2, Some(format!("{}{}", ALIAS_PREFIX, "_2").as_str()), vec![ ALIAS_PREFIX.to_owned() + "_21", @@ -595,7 +586,7 @@ fn keystore2_list_entries_batched_validate_count_and_order_success() { ); verify_aliases( - &keystore2, + &sl.keystore2, Some(format!("{}{}", ALIAS_PREFIX, "_1").as_str()), vec![ ALIAS_PREFIX.to_owned() + "_10", @@ -611,7 +602,7 @@ fn keystore2_list_entries_batched_validate_count_and_order_success() { ); verify_aliases( - &keystore2, + &sl.keystore2, Some(ALIAS_PREFIX), vec![ ALIAS_PREFIX.to_owned() + "_1", @@ -628,7 +619,7 @@ fn keystore2_list_entries_batched_validate_count_and_order_success() { ); verify_aliases( - &keystore2, + &sl.keystore2, None, vec![ ALIAS_PREFIX.to_owned() + "_1", diff --git a/keystore2/tests/keystore2_client_operation_tests.rs b/keystore2/tests/keystore2_client_operation_tests.rs index 89b5a319..95888a66 100644 --- a/keystore2/tests/keystore2_client_operation_tests.rs +++ b/keystore2/tests/keystore2_client_operation_tests.rs @@ -12,27 +12,24 @@ // 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 std::thread; -use std::thread::JoinHandle; - +use crate::keystore2_client_test_utils::{ + create_signing_operation, execute_op_run_as_child, perform_sample_sign_operation, + BarrierReached, ForcedOp, TestOutcome, +}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ - Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, + Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, }; use android_system_keystore2::aidl::android::system::keystore2::{ CreateOperationResponse::CreateOperationResponse, Domain::Domain, IKeystoreOperation::IKeystoreOperation, ResponseCode::ResponseCode, }; - use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, -}; - -use crate::keystore2_client_test_utils::{ - create_signing_operation, execute_op_run_as_child, perform_sample_sign_operation, - BarrierReached, ForcedOp, TestOutcome, + authorizations, key_generations, key_generations::Error, run_as, SecLevel, }; +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; +use std::thread; +use std::thread::JoinHandle; /// Create `max_ops` number child processes with the given context and perform an operation under each /// child process. @@ -312,11 +309,10 @@ fn keystore2_ops_prune_test() { child_handle.recv(); // Generate a key to use in below operations. - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_prune_op_test_key_{}", getuid()); let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias), @@ -327,7 +323,7 @@ fn keystore2_ops_prune_test() { // Create multiple operations in this process to trigger cannibalizing sibling operations. let mut ops: Vec<binder::Result<CreateOperationResponse>> = (0..MAX_OPS) .map(|_| { - sec_level.createOperation( + sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new() .purpose(KeyPurpose::SIGN) @@ -353,7 +349,7 @@ fn keystore2_ops_prune_test() { // Create a new operation, it should trigger to cannibalize one of their own sibling // operations. ops.push( - sec_level.createOperation( + sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new() .purpose(KeyPurpose::SIGN) diff --git a/keystore2/tests/keystore2_client_rsa_key_tests.rs b/keystore2/tests/keystore2_client_rsa_key_tests.rs index ad176a48..15590087 100644 --- a/keystore2/tests/keystore2_client_rsa_key_tests.rs +++ b/keystore2/tests/keystore2_client_rsa_key_tests.rs @@ -12,20 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::keystore2_client_test_utils::{delete_app_key, perform_sample_sign_operation, ForcedOp}; use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, - SecurityLevel::SecurityLevel, }; use android_system_keystore2::aidl::android::system::keystore2::{ CreateOperationResponse::CreateOperationResponse, Domain::Domain, - IKeystoreSecurityLevel::IKeystoreSecurityLevel, -}; - -use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, }; - -use crate::keystore2_client_test_utils::{delete_app_key, perform_sample_sign_operation, ForcedOp}; +use keystore2_test_utils::{authorizations, key_generations, key_generations::Error, SecLevel}; /// This macro is used for creating signing key operation tests using digests and paddings /// for various key sizes. @@ -77,7 +71,7 @@ macro_rules! test_rsa_encrypt_key_op { /// Generate a RSA key and create an operation using the generated key. fn create_rsa_key_and_operation( - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, domain: Domain, nspace: i64, alias: Option<String>, @@ -86,7 +80,7 @@ fn create_rsa_key_and_operation( forced_op: ForcedOp, ) -> binder::Result<CreateOperationResponse> { let key_metadata = - key_generations::generate_rsa_key(sec_level, domain, nspace, alias, key_params, None)?; + key_generations::generate_rsa_key(sl, domain, nspace, alias, key_params, None)?; let mut op_params = authorizations::AuthSetBuilder::new().purpose(op_purpose); @@ -103,7 +97,7 @@ fn create_rsa_key_and_operation( op_params = op_params.block_mode(value) } - sec_level.createOperation(&key_metadata.key, &op_params, forced_op.0) + sl.binder.createOperation(&key_metadata.key, &op_params, forced_op.0) } /// Generate RSA signing key with given parameters and perform signing operation. @@ -113,11 +107,10 @@ fn perform_rsa_sign_key_op_success( alias: &str, padding: PaddingMode, ) { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let op_response = create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -143,17 +136,16 @@ fn perform_rsa_sign_key_op_success( )) ); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate RSA signing key with given parameters and try to perform signing operation. /// Error `INCOMPATIBLE_DIGEST | UNKNOWN_ERROR` is expected while creating an opearation. fn perform_rsa_sign_key_op_failure(digest: Digest, alias: &str, padding: PaddingMode) { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -176,7 +168,7 @@ fn perform_rsa_sign_key_op_failure(digest: Digest, alias: &str, padding: Padding e == Error::Km(ErrorCode::UNKNOWN_ERROR) || e == Error::Km(ErrorCode::INCOMPATIBLE_DIGEST) ); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } /// Generate RSA encrypt/decrypt key with given parameters and perform decrypt operation. @@ -187,11 +179,10 @@ fn create_rsa_encrypt_decrypt_key_op_success( padding: PaddingMode, mgf_digest: Option<Digest>, ) { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let result = create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -210,7 +201,7 @@ fn create_rsa_encrypt_decrypt_key_op_success( assert!(result.is_ok()); - delete_app_key(&keystore2, alias).unwrap(); + delete_app_key(&sl.keystore2, alias).unwrap(); } // Below macros generate tests for generating RSA signing keys with - @@ -1533,12 +1524,11 @@ test_rsa_encrypt_key_op!( /// `INCOMPATIBLE_DIGEST` error code. #[test] fn keystore2_rsa_generate_signing_key_padding_pss_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_pss_none_key_op_test"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1565,12 +1555,11 @@ fn keystore2_rsa_generate_signing_key_padding_pss_fail() { /// with an error code `INCOMPATIBLE_DIGEST`. #[test] fn keystore2_rsa_generate_key_with_oaep_padding_fail() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_key_oaep_padding_fail_test"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1596,12 +1585,11 @@ fn keystore2_rsa_generate_key_with_oaep_padding_fail() { /// `UNSUPPORTED_PADDING_MODE`. #[test] fn keystore2_rsa_generate_keys() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_key_unsupport_padding_test"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1625,12 +1613,11 @@ fn keystore2_rsa_generate_keys() { /// `INCOMPATIBLE_PURPOSE` is expected as the generated key doesn't support sign operation. #[test] fn keystore2_rsa_encrypt_key_op_invalid_purpose() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_test_key_1"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1654,12 +1641,11 @@ fn keystore2_rsa_encrypt_key_op_invalid_purpose() { /// `INCOMPATIBLE_PURPOSE` is expected as the generated key doesn't support decrypt operation. #[test] fn keystore2_rsa_sign_key_op_invalid_purpose() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_test_key_2"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1683,12 +1669,11 @@ fn keystore2_rsa_sign_key_op_invalid_purpose() { /// generated key, an error `UNSUPPORTED_PURPOSE` is expected as RSA doesn't support AGREE_KEY. #[test] fn keystore2_rsa_key_unsupported_purpose() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_key_test_3"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1713,14 +1698,13 @@ fn keystore2_rsa_key_unsupported_purpose() { /// mode. #[test] fn keystore2_rsa_encrypt_key_unsupported_padding() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let paddings = [PaddingMode::RSA_PKCS1_1_5_SIGN, PaddingMode::RSA_PSS]; for padding in paddings { let alias = format!("ks_rsa_encrypt_key_unsupported_pad_test{}", padding.0); let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1746,14 +1730,13 @@ fn keystore2_rsa_encrypt_key_unsupported_padding() { /// unsupported padding mode. #[test] fn keystore2_rsa_signing_key_unsupported_padding() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let paddings = [PaddingMode::RSA_PKCS1_1_5_ENCRYPT, PaddingMode::RSA_OAEP]; for padding in paddings { let alias = format!("ks_rsa_sign_key_unsupported_pad_test_4_{}", padding.0); let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1779,12 +1762,11 @@ fn keystore2_rsa_signing_key_unsupported_padding() { /// with RSA key. #[test] fn keystore2_rsa_key_unsupported_op() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_key_test_5"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1810,12 +1792,11 @@ fn keystore2_rsa_key_unsupported_op() { /// generated with decrypt purpose. #[test] fn keystore2_rsa_key_missing_purpose() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_key_test_6"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1840,12 +1821,11 @@ fn keystore2_rsa_key_missing_purpose() { /// operation with generated key, unsupported digest error is expected. #[test] fn keystore2_rsa_gen_keys_with_oaep_paddings_without_digest() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_key_padding_fail"; let result = key_generations::map_ks_error(create_rsa_key_and_operation( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), @@ -1869,12 +1849,11 @@ fn keystore2_rsa_gen_keys_with_oaep_paddings_without_digest() { /// Generate RSA keys with unsupported key size, an error `UNSUPPORTED_KEY_SIZE` is expected. #[test] fn keystore2_rsa_gen_keys_unsupported_size() { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = "ks_rsa_key_padding_fail"; let result = key_generations::map_ks_error(key_generations::generate_rsa_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias.to_string()), diff --git a/keystore2/tests/keystore2_client_test_utils.rs b/keystore2/tests/keystore2_client_test_utils.rs index f270297c..d4ca2ae9 100644 --- a/keystore2/tests/keystore2_client_test_utils.rs +++ b/keystore2/tests/keystore2_client_test_utils.rs @@ -12,29 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use nix::unistd::{Gid, Uid}; -use serde::{Deserialize, Serialize}; - -use std::path::PathBuf; -use std::process::{Command, Output}; - -use openssl::bn::BigNum; -use openssl::encrypt::Encrypter; -use openssl::error::ErrorStack; -use openssl::hash::MessageDigest; -use openssl::nid::Nid; -use openssl::pkey::PKey; -use openssl::pkey::Public; -use openssl::rsa::Padding; -use openssl::sign::Verifier; -use openssl::x509::X509; - -use binder::wait_for_interface; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ BlockMode::BlockMode, Digest::Digest, ErrorCode::ErrorCode, KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode, - SecurityLevel::SecurityLevel, Tag::Tag, + Tag::Tag, }; use android_system_keystore2::aidl::android::system::keystore2::{ CreateOperationResponse::CreateOperationResponse, Domain::Domain, @@ -42,12 +23,25 @@ use android_system_keystore2::aidl::android::system::keystore2::{ IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor, KeyMetadata::KeyMetadata, KeyParameters::KeyParameters, ResponseCode::ResponseCode, }; - -use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative; - +use binder::wait_for_interface; use keystore2_test_utils::{ - authorizations, get_keystore_service, key_generations, key_generations::Error, run_as, + authorizations, key_generations, key_generations::Error, run_as, SecLevel, }; +use nix::unistd::{Gid, Uid}; +use openssl::bn::BigNum; +use openssl::encrypt::Encrypter; +use openssl::error::ErrorStack; +use openssl::hash::MessageDigest; +use openssl::nid::Nid; +use openssl::pkey::PKey; +use openssl::pkey::Public; +use openssl::rsa::Padding; +use openssl::sign::Verifier; +use openssl::x509::X509; +use packagemanager_aidl::aidl::android::content::pm::IPackageManagerNative::IPackageManagerNative; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use std::process::{Command, Output}; /// This enum is used to communicate between parent and child processes. #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] @@ -70,8 +64,9 @@ pub struct ForcedOp(pub bool); pub const SAMPLE_PLAIN_TEXT: &[u8] = b"my message 11111"; pub const PACKAGE_MANAGER_NATIVE_SERVICE: &str = "package_native"; -pub const APP_ATTEST_KEY_FEATURE: &str = "android.hardware.keystore.app_attest_key"; -pub const DEVICE_ID_ATTESTATION_FEATURE: &str = "android.software.device_id_attestation"; +const APP_ATTEST_KEY_FEATURE: &str = "android.hardware.keystore.app_attest_key"; +const DEVICE_ID_ATTESTATION_FEATURE: &str = "android.software.device_id_attestation"; +const STRONGBOX_KEYSTORE_FEATURE: &str = "android.hardware.strongbox_keystore"; /// Determines whether app_attest_key_feature is supported or not. pub fn app_attest_key_feature_exists() -> bool { @@ -89,20 +84,26 @@ pub fn device_id_attestation_feature_exists() -> bool { pm.hasSystemFeature(DEVICE_ID_ATTESTATION_FEATURE, 0).expect("hasSystemFeature failed.") } +/// Determines whether device-unique attestation might be supported by StrongBox. +pub fn skip_device_unique_attestation_tests() -> bool { + let pm = wait_for_interface::<dyn IPackageManagerNative>(PACKAGE_MANAGER_NATIVE_SERVICE) + .expect("Failed to get package manager native service."); + + // Device unique attestation was first included in Keymaster 4.1. + !pm.hasSystemFeature(STRONGBOX_KEYSTORE_FEATURE, 41).expect("hasSystemFeature failed.") +} + /// Determines whether to skip device id attestation tests on GSI build with API level < 34. pub fn skip_device_id_attest_tests() -> bool { // b/298586194, there are some devices launched with Android T, and they will be receiving // only system update and not vendor update, newly added attestation properties // (ro.product.*_for_attestation) reading logic would not be available for such devices // hence skipping this test for such scenario. - let api_level = std::str::from_utf8(&get_system_prop("ro.board.first_api_level")) - .unwrap() - .parse::<i32>() - .unwrap(); + // This file is only present on GSI builds. - let path_buf = PathBuf::from("/system/system_ext/etc/init/init.gsi.rc"); + let gsi_marker = PathBuf::from("/system/system_ext/etc/init/init.gsi.rc"); - api_level < 34 && path_buf.as_path().is_file() + get_vsr_api_level() < 34 && gsi_marker.as_path().is_file() } #[macro_export] @@ -133,9 +134,9 @@ macro_rules! skip_device_id_attestation_tests { } #[macro_export] -macro_rules! skip_tests_if_keymaster_impl_present { - () => { - if !key_generations::has_default_keymint() { +macro_rules! require_keymint { + ($sl:ident) => { + if !$sl.is_keymint() { return; } }; @@ -144,19 +145,18 @@ macro_rules! skip_tests_if_keymaster_impl_present { /// Generate EC key and grant it to the list of users with given access vector. /// Returns the list of granted keys `nspace` values in the order of given grantee uids. pub fn generate_ec_key_and_grant_to_users( - keystore2: &binder::Strong<dyn IKeystoreService>, - sec_level: &binder::Strong<dyn IKeystoreSecurityLevel>, + sl: &SecLevel, alias: Option<String>, grantee_uids: Vec<i32>, access_vector: i32, ) -> Result<Vec<i64>, binder::Status> { let key_metadata = - key_generations::generate_ec_p256_signing_key(sec_level, Domain::APP, -1, alias, None)?; + key_generations::generate_ec_p256_signing_key(sl, Domain::APP, -1, alias, None)?; let mut granted_keys = Vec::new(); for uid in grantee_uids { - let granted_key = keystore2.grant(&key_metadata.key, uid, access_vector)?; + let granted_key = sl.keystore2.grant(&key_metadata.key, uid, access_vector)?; assert_eq!(granted_key.domain, Domain::GRANT); granted_keys.push(granted_key.nspace); } @@ -174,14 +174,12 @@ pub fn create_signing_operation( nspace: i64, alias: Option<String>, ) -> binder::Result<CreateOperationResponse> { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let key_metadata = - key_generations::generate_ec_p256_signing_key(&sec_level, domain, nspace, alias, None) - .unwrap(); + key_generations::generate_ec_p256_signing_key(&sl, domain, nspace, alias, None).unwrap(); - sec_level.createOperation( + sl.binder.createOperation( &key_metadata.key, &authorizations::AuthSetBuilder::new().purpose(op_purpose).digest(op_digest), forced_op.0, @@ -514,15 +512,38 @@ pub fn get_system_prop(name: &str) -> Vec<u8> { } } +fn get_integer_system_prop(name: &str) -> Option<i32> { + let val = get_system_prop(name); + if val.is_empty() { + return None; + } + let val = std::str::from_utf8(&val).ok()?; + val.parse::<i32>().ok() +} + +pub fn get_vsr_api_level() -> i32 { + if let Some(api_level) = get_integer_system_prop("ro.vendor.api_level") { + return api_level; + } + + let vendor_api_level = get_integer_system_prop("ro.board.api_level") + .or_else(|| get_integer_system_prop("ro.board.first_api_level")); + let product_api_level = get_integer_system_prop("ro.product.first_api_level") + .or_else(|| get_integer_system_prop("ro.build.version.sdk")); + + match (vendor_api_level, product_api_level) { + (Some(v), Some(p)) => std::cmp::min(v, p), + (Some(v), None) => v, + (None, Some(p)) => p, + _ => panic!("Could not determine VSR API level"), + } +} + /// Determines whether the SECOND-IMEI can be used as device attest-id. pub fn is_second_imei_id_attestation_required( keystore2: &binder::Strong<dyn IKeystoreService>, ) -> bool { - let api_level = std::str::from_utf8(&get_system_prop("ro.vendor.api_level")) - .unwrap() - .parse::<i32>() - .unwrap(); - keystore2.getInterfaceVersion().unwrap() >= 3 && api_level > 33 + keystore2.getInterfaceVersion().unwrap() >= 3 && get_vsr_api_level() > 33 } /// Run a service command and collect the output. diff --git a/keystore2/tests/keystore2_client_tests.rs b/keystore2/tests/keystore2_client_tests.rs index a0c140a0..34ba81f7 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_operation_tests; 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/keystore2_client_update_subcomponent_tests.rs b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs index d9576a84..e25e52a2 100644 --- a/keystore2/tests/keystore2_client_update_subcomponent_tests.rs +++ b/keystore2/tests/keystore2_client_update_subcomponent_tests.rs @@ -12,9 +12,6 @@ // 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 android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ ErrorCode::ErrorCode, SecurityLevel::SecurityLevel, }; @@ -22,8 +19,11 @@ use android_system_keystore2::aidl::android::system::keystore2::{ Domain::Domain, KeyDescriptor::KeyDescriptor, KeyPermission::KeyPermission, ResponseCode::ResponseCode, }; - -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, SecLevel, +}; +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; /// Generate a key and update its public certificate and certificate chain. Test should be able to /// load the key and able to verify whether its certificate and cert-chain are updated successfully. @@ -31,11 +31,10 @@ use keystore2_test_utils::{get_keystore_service, key_generations, key_generation fn keystore2_update_subcomponent_success() { let alias = "update_subcomponent_success_key"; - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::SELINUX, key_generations::SELINUX_SHELL_NAMESPACE, Some(alias.to_string()), @@ -46,11 +45,11 @@ fn keystore2_update_subcomponent_success() { let other_cert: [u8; 32] = [123; 32]; let other_cert_chain: [u8; 32] = [12; 32]; - keystore2 + sl.keystore2 .updateSubcomponent(&key_metadata.key, Some(&other_cert), Some(&other_cert_chain)) .expect("updateSubcomponent should have succeeded."); - let key_entry_response = keystore2.getKeyEntry(&key_metadata.key).unwrap(); + let key_entry_response = sl.keystore2.getKeyEntry(&key_metadata.key).unwrap(); assert_eq!(Some(other_cert.to_vec()), key_entry_response.metadata.certificate); assert_eq!(Some(other_cert_chain.to_vec()), key_entry_response.metadata.certificateChain); } @@ -170,13 +169,12 @@ fn keystore2_update_subcomponent_fails_permission_denied() { // SAFETY: The test is run in a separate process with no other threads. let mut granted_keys = unsafe { run_as::run_as(GRANTOR_SU_CTX, Uid::from_raw(0), Gid::from_raw(0), || { - let keystore2 = get_keystore_service(); - let sec_level = keystore2.getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT).unwrap(); + let sl = SecLevel::tee(); let alias = format!("ks_update_subcompo_test_1_{}", getuid()); let mut granted_keys = Vec::new(); let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::APP, -1, Some(alias), @@ -186,7 +184,8 @@ fn keystore2_update_subcomponent_fails_permission_denied() { // Grant a key without update permission. let access_vector = KeyPermission::GET_INFO.0; - let granted_key = keystore2 + let granted_key = sl + .keystore2 .grant(&key_metadata.key, GRANTEE_1_UID.try_into().unwrap(), access_vector) .unwrap(); assert_eq!(granted_key.domain, Domain::GRANT); @@ -194,7 +193,8 @@ fn keystore2_update_subcomponent_fails_permission_denied() { // Grant a key with update permission. let access_vector = KeyPermission::GET_INFO.0 | KeyPermission::UPDATE.0; - let granted_key = keystore2 + let granted_key = sl + .keystore2 .grant(&key_metadata.key, GRANTEE_2_UID.try_into().unwrap(), access_vector) .unwrap(); assert_eq!(granted_key.domain, Domain::GRANT); diff --git a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs index 0335159a..11a4c0b1 100644 --- a/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs +++ b/keystore2/tests/legacy_blobs/keystore2_legacy_blob_tests.rs @@ -12,40 +12,35 @@ // 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 std::ops::Deref; -use std::path::PathBuf; - use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel; - -use android_system_keystore2::aidl::android::system::keystore2::{ - Domain::Domain, KeyDescriptor::KeyDescriptor, -}; - -use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::IKeystoreMaintenance; - use android_security_authorization::aidl::android::security::authorization::{ IKeystoreAuthorization::IKeystoreAuthorization, }; - +use android_security_maintenance::aidl::android::security::maintenance::IKeystoreMaintenance::IKeystoreMaintenance; +use android_system_keystore2::aidl::android::system::keystore2::{ + Domain::Domain, KeyDescriptor::KeyDescriptor, +}; use keystore2::key_parameter::KeyParameter as KsKeyparameter; use keystore2::legacy_blob::test_utils::legacy_blob_test_vectors::*; use keystore2::legacy_blob::test_utils::*; use keystore2::legacy_blob::LegacyKeyCharacteristics; use keystore2::utils::AesGcm; use keystore2_crypto::{Password, ZVec}; - -use keystore2_test_utils::get_keystore_service; -use keystore2_test_utils::key_generations; -use keystore2_test_utils::run_as; +use keystore2_test_utils::{get_keystore_service, key_generations, run_as, SecLevel}; +use nix::unistd::{getuid, Gid, Uid}; +use rustutils::users::AID_USER_OFFSET; +use serde::{Deserialize, Serialize}; +use std::ops::Deref; +use std::path::PathBuf; static USER_MANAGER_SERVICE_NAME: &str = "android.security.maintenance"; static AUTH_SERVICE_NAME: &str = "android.security.authorization"; const SELINUX_SHELL_NAMESPACE: i64 = 1; +fn rkp_only() -> bool { + matches!(rustutils::system_properties::read("remote_provisioning.tee.rkp_only"), Ok(Some(v)) if v == "1") +} + fn get_maintenance() -> binder::Strong<dyn IKeystoreMaintenance> { binder::get_interface(USER_MANAGER_SERVICE_NAME).unwrap() } @@ -156,19 +151,16 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { println!("onUserRemoved error: {:#?}", e); } } + let sl = SecLevel::tee(); - let keystore2 = get_keystore_service(); - let sec_level = keystore2 - .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT) - .unwrap(); // Generate Key BLOB and prepare legacy keystore blob files. - let att_challenge: &[u8] = b"foo"; + let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") }; let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::BLOB, SELINUX_SHELL_NAMESPACE, None, - Some(att_challenge), + att_challenge, ) .expect("Failed to generate key blob"); @@ -212,14 +204,12 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { .unwrap(); } - let mut path_buf = PathBuf::from("/data/misc/keystore/user_99"); - path_buf.push("9910001_CACERT_authbound"); - if !path_buf.as_path().is_file() { - make_cert_blob_file( - path_buf.as_path(), - key_metadata.certificateChain.as_ref().unwrap(), - ) - .unwrap(); + if let Some(chain) = key_metadata.certificateChain.as_ref() { + let mut path_buf = PathBuf::from("/data/misc/keystore/user_99"); + path_buf.push("9910001_CACERT_authbound"); + if !path_buf.as_path().is_file() { + make_cert_blob_file(path_buf.as_path(), chain).unwrap(); + } } // Keystore2 disables the legacy importer when it finds the legacy database empty. @@ -246,7 +236,7 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { KeygenResult { cert: key_metadata.certificate.unwrap(), - cert_chain: key_metadata.certificateChain.unwrap(), + cert_chain: key_metadata.certificateChain.unwrap_or_default(), key_parameters: key_params, } }) @@ -275,7 +265,7 @@ fn keystore2_encrypted_characteristics() -> anyhow::Result<()> { gen_key_result.cert ); assert_eq!( - key_entry_response.metadata.certificateChain.unwrap(), + key_entry_response.metadata.certificateChain.unwrap_or_default(), gen_key_result.cert_chain ); assert_eq!(key_entry_response.metadata.key.domain, Domain::KEY_ID); @@ -410,18 +400,15 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { } } - let keystore2 = get_keystore_service(); - let sec_level = keystore2 - .getSecurityLevel(SecurityLevel::SecurityLevel::TRUSTED_ENVIRONMENT) - .unwrap(); + let sl = SecLevel::tee(); // Generate Key BLOB and prepare legacy keystore blob files. - let att_challenge: &[u8] = b"foo"; + let att_challenge: Option<&[u8]> = if rkp_only() { None } else { Some(b"foo") }; let key_metadata = key_generations::generate_ec_p256_signing_key( - &sec_level, + &sl, Domain::BLOB, SELINUX_SHELL_NAMESPACE, None, - Some(att_challenge), + att_challenge, ) .expect("Failed to generate key blob"); @@ -468,15 +455,12 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { .unwrap(); } - let mut path_buf = PathBuf::from("/data/misc/keystore/user_98"); - path_buf.push("9810001_CACERT_authboundcertenc"); - if !path_buf.as_path().is_file() { - make_encrypted_ca_cert_file( - path_buf.as_path(), - &super_key, - key_metadata.certificateChain.as_ref().unwrap(), - ) - .unwrap(); + if let Some(chain) = key_metadata.certificateChain.as_ref() { + let mut path_buf = PathBuf::from("/data/misc/keystore/user_98"); + path_buf.push("9810001_CACERT_authboundcertenc"); + if !path_buf.as_path().is_file() { + make_encrypted_ca_cert_file(path_buf.as_path(), &super_key, chain).unwrap(); + } } // Keystore2 disables the legacy importer when it finds the legacy database empty. @@ -503,7 +487,7 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { KeygenResult { cert: key_metadata.certificate.unwrap(), - cert_chain: key_metadata.certificateChain.unwrap(), + cert_chain: key_metadata.certificateChain.unwrap_or_default(), key_parameters: key_params, } }) @@ -532,7 +516,7 @@ fn keystore2_encrypted_certificates() -> anyhow::Result<()> { gen_key_result.cert ); assert_eq!( - key_entry_response.metadata.certificateChain.unwrap(), + key_entry_response.metadata.certificateChain.unwrap_or_default(), gen_key_result.cert_chain ); diff --git a/keystore2/tests/user_auth.rs b/keystore2/tests/user_auth.rs new file mode 100644 index 00000000..263ad565 --- /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], + } +} diff --git a/keystore2/watchdog/src/lib.rs b/keystore2/watchdog/src/lib.rs index fa4620a8..ff05783a 100644 --- a/keystore2/watchdog/src/lib.rs +++ b/keystore2/watchdog/src/lib.rs @@ -29,6 +29,9 @@ use std::{ time::{Duration, Instant}, }; +#[cfg(test)] +mod tests; + /// Represents a Watchdog record. It can be created with `Watchdog::watch` or /// `Watchdog::watch_with`. It disarms the record when dropped. pub struct WatchPoint { @@ -58,59 +61,73 @@ struct Index { struct Record { started: Instant, deadline: Instant, - callback: Option<Box<dyn Fn() -> String + Send + 'static>>, + context: Option<Box<dyn std::fmt::Debug + Send + 'static>>, } struct WatchdogState { state: State, thread: Option<thread::JoinHandle<()>>, - timeout: Duration, + /// How long to wait before dropping the watchdog thread when idle. + idle_timeout: Duration, records: HashMap<Index, Record>, - last_report: Instant, - has_overdue: bool, + last_report: Option<Instant>, + noisy_timeout: Duration, } impl WatchdogState { - fn update_overdue_and_find_next_timeout(&mut self) -> (bool, Option<Duration>) { + /// If we have overdue records, we want to log them but slowly backoff + /// so that we do not clog the logs. We start with logs every + /// `MIN_REPORT_TIMEOUT` sec then increment the timeout by 5 up + /// to a maximum of `MAX_REPORT_TIMEOUT`. + const MIN_REPORT_TIMEOUT: Duration = Duration::from_secs(1); + const MAX_REPORT_TIMEOUT: Duration = Duration::from_secs(30); + + fn reset_noisy_timeout(&mut self) { + self.noisy_timeout = Self::MIN_REPORT_TIMEOUT; + } + + fn update_noisy_timeout(&mut self) { + let noisy_update = self.noisy_timeout + Duration::from_secs(5); + self.noisy_timeout = min(Self::MAX_REPORT_TIMEOUT, noisy_update); + } + + fn overdue_and_next_timeout(&self) -> (bool, Option<Duration>) { let now = Instant::now(); let mut next_timeout: Option<Duration> = None; let mut has_overdue = false; for (_, r) in self.records.iter() { let timeout = r.deadline.saturating_duration_since(now); if timeout == Duration::new(0, 0) { + // This timeout has passed. has_overdue = true; - continue; + } else { + // This timeout is still to come; see if it's the closest one to now. + next_timeout = match next_timeout { + Some(nt) if timeout < nt => Some(timeout), + Some(nt) => Some(nt), + None => Some(timeout), + }; } - next_timeout = match next_timeout { - Some(nt) => { - if timeout < nt { - Some(timeout) - } else { - Some(nt) - } - } - None => Some(timeout), - }; } (has_overdue, next_timeout) } - fn log_report(&mut self, has_overdue: bool) -> bool { - match (self.has_overdue, has_overdue) { - (true, true) => { - if self.last_report.elapsed() < Watchdog::NOISY_REPORT_TIMEOUT { - self.has_overdue = false; - return false; - } - } - (_, false) => { - self.has_overdue = false; - return false; + fn log_report(&mut self, has_overdue: bool) { + if !has_overdue { + // Nothing to report. + self.last_report = None; + return; + } + // Something to report... + if let Some(reported_at) = self.last_report { + if reported_at.elapsed() < self.noisy_timeout { + // .. but it's too soon since the last report. + self.last_report = None; + return; } - (false, true) => {} } - self.last_report = Instant::now(); - self.has_overdue = has_overdue; + self.update_noisy_timeout(); + self.last_report = Some(Instant::now()); log::warn!("### Keystore Watchdog report - BEGIN ###"); let now = Instant::now(); @@ -149,15 +166,15 @@ impl WatchdogState { for g in groups.iter() { for (i, r) in g.iter() { - match &r.callback { - Some(cb) => { + match &r.context { + Some(ctx) => { log::warn!( - "{:?} {} Pending: {:?} Overdue {:?}: {}", + "{:?} {} Pending: {:?} Overdue {:?} for {:?}", i.tid, i.id, r.started.elapsed(), r.deadline.elapsed(), - (cb)() + ctx ); } None => { @@ -173,7 +190,6 @@ impl WatchdogState { } } log::warn!("### Keystore Watchdog report - END ###"); - true } fn disarm(&mut self, index: Index) { @@ -195,71 +211,66 @@ pub struct Watchdog { } impl Watchdog { - /// If we have overdue records, we want to be noisy about it and log a report - /// at least every `NOISY_REPORT_TIMEOUT` interval. - const NOISY_REPORT_TIMEOUT: Duration = Duration::from_secs(1); - - /// Construct a [`Watchdog`]. When `timeout` has elapsed since the watchdog thread became + /// Construct a [`Watchdog`]. When `idle_timeout` has elapsed since the watchdog thread became /// idle, i.e., there are no more active or overdue watch points, the watchdog thread /// terminates. - pub fn new(timeout: Duration) -> Arc<Self> { + pub fn new(idle_timeout: Duration) -> Arc<Self> { Arc::new(Self { state: Arc::new(( Condvar::new(), Mutex::new(WatchdogState { state: State::NotRunning, thread: None, - timeout, + idle_timeout, records: HashMap::new(), - last_report: Instant::now(), - has_overdue: false, + last_report: None, + noisy_timeout: WatchdogState::MIN_REPORT_TIMEOUT, }), )), }) } fn watch_with_optional( - wd: &Arc<Self>, - callback: Option<Box<dyn Fn() -> String + Send + 'static>>, + wd: Arc<Self>, + context: Option<Box<dyn std::fmt::Debug + Send + 'static>>, id: &'static str, timeout: Duration, ) -> Option<WatchPoint> { - let deadline = Instant::now().checked_add(timeout); - if deadline.is_none() { + let Some(deadline) = Instant::now().checked_add(timeout) else { log::warn!("Deadline computation failed for WatchPoint \"{}\"", id); log::warn!("WatchPoint not armed."); return None; - } - wd.arm(callback, id, deadline.unwrap()); - Some(WatchPoint { id, wd: wd.clone(), not_send: Default::default() }) + }; + wd.arm(context, id, deadline); + Some(WatchPoint { id, wd, not_send: Default::default() }) } /// Create a new watch point. If the WatchPoint is not dropped before the timeout /// expires, a report is logged at least every second, which includes the id string - /// and whatever string the callback returns. + /// and any provided context. pub fn watch_with( wd: &Arc<Self>, id: &'static str, timeout: Duration, - callback: impl Fn() -> String + Send + 'static, + context: impl std::fmt::Debug + Send + 'static, ) -> Option<WatchPoint> { - Self::watch_with_optional(wd, Some(Box::new(callback)), id, timeout) + Self::watch_with_optional(wd.clone(), Some(Box::new(context)), id, timeout) } - /// Like `watch_with`, but without a callback. + /// Like `watch_with`, but without context. pub fn watch(wd: &Arc<Self>, id: &'static str, timeout: Duration) -> Option<WatchPoint> { - Self::watch_with_optional(wd, None, id, timeout) + Self::watch_with_optional(wd.clone(), None, id, timeout) } fn arm( &self, - callback: Option<Box<dyn Fn() -> String + Send + 'static>>, + context: Option<Box<dyn std::fmt::Debug + Send + 'static>>, id: &'static str, deadline: Instant, ) { let tid = thread::current().id(); let index = Index { tid, id }; - let record = Record { started: Instant::now(), deadline, callback }; + let record = Record { started: Instant::now(), deadline, context }; let (ref condvar, ref state) = *self.state; @@ -297,21 +308,24 @@ impl Watchdog { let mut state = state.lock().unwrap(); loop { - let (has_overdue, next_timeout) = state.update_overdue_and_find_next_timeout(); + let (has_overdue, next_timeout) = state.overdue_and_next_timeout(); state.log_report(has_overdue); + let (next_timeout, idle) = match (has_overdue, next_timeout) { - (true, Some(next_timeout)) => { - (min(next_timeout, Self::NOISY_REPORT_TIMEOUT), false) - } + (true, Some(next_timeout)) => (min(next_timeout, state.noisy_timeout), false), + (true, None) => (state.noisy_timeout, false), (false, Some(next_timeout)) => (next_timeout, false), - (true, None) => (Self::NOISY_REPORT_TIMEOUT, false), - (false, None) => (state.timeout, true), + (false, None) => (state.idle_timeout, true), }; + // Wait until the closest timeout pops, but use a condition variable so that if a + // new watchpoint is started in the meanwhile it will interrupt the wait so we can + // recalculate. let (s, timeout) = condvar.wait_timeout(state, next_timeout).unwrap(); state = s; if idle && timeout.timed_out() && state.records.is_empty() { + state.reset_noisy_timeout(); state.state = State::NotRunning; break; } @@ -321,40 +335,3 @@ impl Watchdog { state.state = State::Running; } } - -#[cfg(test)] -mod tests { - - use super::*; - use std::sync::atomic; - use std::thread; - use std::time::Duration; - - #[test] - fn test_watchdog() { - android_logger::init_once( - android_logger::Config::default() - .with_tag("keystore2_watchdog_tests") - .with_max_level(log::LevelFilter::Debug), - ); - - let wd = Watchdog::new(Watchdog::NOISY_REPORT_TIMEOUT.checked_mul(3).unwrap()); - let hit_count = Arc::new(atomic::AtomicU8::new(0)); - let hit_count_clone = hit_count.clone(); - let wp = - Watchdog::watch_with(&wd, "test_watchdog", Duration::from_millis(100), move || { - format!("hit_count: {}", hit_count_clone.fetch_add(1, atomic::Ordering::Relaxed)) - }); - assert_eq!(0, hit_count.load(atomic::Ordering::Relaxed)); - thread::sleep(Duration::from_millis(500)); - assert_eq!(1, hit_count.load(atomic::Ordering::Relaxed)); - thread::sleep(Watchdog::NOISY_REPORT_TIMEOUT); - assert_eq!(2, hit_count.load(atomic::Ordering::Relaxed)); - drop(wp); - thread::sleep(Watchdog::NOISY_REPORT_TIMEOUT.checked_mul(4).unwrap()); - assert_eq!(2, hit_count.load(atomic::Ordering::Relaxed)); - let (_, ref state) = *wd.state; - let state = state.lock().unwrap(); - assert_eq!(state.state, State::NotRunning); - } -} diff --git a/keystore2/watchdog/src/tests.rs b/keystore2/watchdog/src/tests.rs new file mode 100644 index 00000000..d35c0dde --- /dev/null +++ b/keystore2/watchdog/src/tests.rs @@ -0,0 +1,86 @@ +// Copyright 2021, 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. + +//! Watchdog tests. + +use super::*; +use std::sync::atomic; +use std::thread; +use std::time::Duration; + +/// Count the number of times `Debug::fmt` is invoked. +#[derive(Default, Clone)] +struct DebugCounter(Arc<atomic::AtomicU8>); +impl DebugCounter { + fn value(&self) -> u8 { + self.0.load(atomic::Ordering::Relaxed) + } +} +impl std::fmt::Debug for DebugCounter { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { + let count = self.0.fetch_add(1, atomic::Ordering::Relaxed); + write!(f, "hit_count: {count}") + } +} + +#[test] +fn test_watchdog() { + android_logger::init_once( + android_logger::Config::default() + .with_tag("keystore2_watchdog_tests") + .with_max_level(log::LevelFilter::Debug), + ); + + let wd = Watchdog::new(Duration::from_secs(3)); + let hit_counter = DebugCounter::default(); + let wp = + Watchdog::watch_with(&wd, "test_watchdog", Duration::from_millis(100), hit_counter.clone()); + assert_eq!(0, hit_counter.value()); + thread::sleep(Duration::from_millis(500)); + assert_eq!(1, hit_counter.value()); + thread::sleep(Duration::from_secs(1)); + assert_eq!(1, hit_counter.value()); + + drop(wp); + thread::sleep(Duration::from_secs(10)); + assert_eq!(1, hit_counter.value()); + let (_, ref state) = *wd.state; + let state = state.lock().unwrap(); + assert_eq!(state.state, State::NotRunning); +} + +#[test] +fn test_watchdog_backoff() { + android_logger::init_once( + android_logger::Config::default() + .with_tag("keystore2_watchdog_tests") + .with_max_level(log::LevelFilter::Debug), + ); + + let wd = Watchdog::new(Duration::from_secs(3)); + let hit_counter = DebugCounter::default(); + let wp = + Watchdog::watch_with(&wd, "test_watchdog", Duration::from_millis(100), hit_counter.clone()); + assert_eq!(0, hit_counter.value()); + thread::sleep(Duration::from_millis(500)); + assert_eq!(1, hit_counter.value()); + thread::sleep(Duration::from_secs(6)); + assert_eq!(2, hit_counter.value()); + thread::sleep(Duration::from_secs(11)); + assert_eq!(3, hit_counter.value()); + + drop(wp); + thread::sleep(Duration::from_secs(4)); + assert_eq!(3, hit_counter.value()); +} |