Add getAuthTokensForCredStore method to Authorization aidl.
This is CL adds and implements getAuthTokensForCredStore.
Bug: 159341610
Test: CtsVerifier
Change-Id: I71eabc0932eae56a6396f867320461e12b257897
diff --git a/keystore2/aidl/android/security/authorization/AuthorizationTokens.aidl b/keystore2/aidl/android/security/authorization/AuthorizationTokens.aidl
new file mode 100644
index 0000000..9061998
--- /dev/null
+++ b/keystore2/aidl/android/security/authorization/AuthorizationTokens.aidl
@@ -0,0 +1,33 @@
+// 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.
+
+package android.security.authorization;
+
+import android.hardware.security.keymint.HardwareAuthToken;
+import android.hardware.security.secureclock.TimeStampToken;
+
+/**
+ * This parcelable is returned by `IKeystoreAuthorization::getAuthTokensForCredStore`.
+ * @hide
+ */
+parcelable AuthorizationTokens {
+ /**
+ * HardwareAuthToken provided by an authenticator.
+ */
+ HardwareAuthToken authToken;
+ /**
+ * TimeStampToken provided by a SecureClock.
+ */
+ TimeStampToken timestampToken;
+}
\ No newline at end of file
diff --git a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
index df64401..6dc172e 100644
--- a/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
+++ b/keystore2/aidl/android/security/authorization/IKeystoreAuthorization.aidl
@@ -16,6 +16,7 @@
import android.hardware.security.keymint.HardwareAuthToken;
import android.security.authorization.LockScreenEvent;
+import android.security.authorization.AuthorizationTokens;
// TODO: mark the interface with @SensitiveData when the annotation is ready (b/176110256).
@@ -45,6 +46,8 @@
* ## Error conditions:
* `ResponseCode::PERMISSION_DENIED` - if the callers do not have the 'Unlock' permission.
* `ResponseCode::SYSTEM_ERROR` - if failed to perform lock/unlock operations due to various
+ * `ResponseCode::VALUE_CORRUPTED` - if the super key can not be decrypted.
+ * `ResponseCode::KEY_NOT_FOUND` - if the super key is not found.
*
* @lockScreenEvent - Indicates what happened.
* * LockScreenEvent.UNLOCK if the screen was unlocked.
@@ -56,4 +59,37 @@
*/
void onLockScreenEvent(in LockScreenEvent lockScreenEvent, in int userId,
in @nullable byte[] password);
+
+ /**
+ * Allows Credstore to retrieve a HardwareAuthToken and a TimestampToken.
+ * Identity Credential Trusted App can run either in the TEE or in other secure Hardware.
+ * So, credstore always need to retrieve a TimestampToken along with a HardwareAuthToken.
+ *
+ * The passed in |challenge| parameter must always be non-zero.
+ *
+ * The returned TimestampToken will always have its |challenge| field set to
+ * the |challenge| parameter.
+ *
+ * This method looks through auth-tokens cached by keystore which match
+ * the passed-in |secureUserId|.
+ * The most recent matching auth token which has a |challenge| field which matches
+ * the passed-in |challenge| parameter is returned.
+ * In this case the |authTokenMaxAgeMillis| parameter is not used.
+ *
+ * Otherwise, the most recent matching auth token which is younger
+ * than |authTokenMaxAgeMillis| is returned.
+ *
+ * This method is called by credstore (and only credstore).
+ *
+ * The caller requires 'get_auth_token' permission.
+ *
+ * ## Error conditions:
+ * `ResponseCode::PERMISSION_DENIED` - if the caller does not have the 'get_auth_token'
+ * permission.
+ * `ResponseCode::SYSTEM_ERROR` - if failed to obtain an authtoken from the database.
+ * `ResponseCode::NO_AUTH_TOKEN_FOUND` - a matching auth token is not found.
+ * `ResponseCode::INVALID_ARGUMENT` - if the passed-in |challenge| parameter is zero.
+ */
+ AuthorizationTokens getAuthTokensForCredStore(in long challenge, in long secureUserId,
+ in long authTokenMaxAgeMillis);
}
diff --git a/keystore2/aidl/android/security/authorization/ResponseCode.aidl b/keystore2/aidl/android/security/authorization/ResponseCode.aidl
new file mode 100644
index 0000000..94f1120
--- /dev/null
+++ b/keystore2/aidl/android/security/authorization/ResponseCode.aidl
@@ -0,0 +1,56 @@
+// 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.
+
+package android.security.authorization;
+
+/**
+ * Used as exception codes by IKeystoreAuthorization.
+ */
+@Backing(type="int")
+enum ResponseCode {
+ /**
+ * A matching auth token is not found.
+ */
+ NO_AUTH_TOKEN_FOUND = 1,
+ /**
+ * The matching auth token is expired.
+ */
+ AUTH_TOKEN_EXPIRED = 2,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Any unexpected Error such as IO or communication errors.
+ */
+ SYSTEM_ERROR = 4,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Indicates that the caller does not have the permissions for the attempted request.
+ */
+ PERMISSION_DENIED = 6,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Indicates that the requested key does not exist.
+ */
+ KEY_NOT_FOUND = 7,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Indicates that a value being processed is corrupted.
+ */
+ VALUE_CORRUPTED = 8,
+ /**
+ * Same as in keystore2/ResponseCode.aidl.
+ * Indicates that an invalid argument was passed to an API call.
+ */
+ INVALID_ARGUMENT = 20,
+
+ }
\ No newline at end of file
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index 02b19c4..75c5add 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -15,7 +15,6 @@
//! This module implements IKeystoreAuthorization AIDL interface.
use crate::error::Error as KeystoreError;
-use crate::error::map_or_log_err;
use crate::globals::{ENFORCEMENTS, SUPER_KEY, DB, LEGACY_MIGRATOR};
use crate::permission::KeystorePerm;
use crate::super_key::UserState;
@@ -23,14 +22,87 @@
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken,
};
-use android_security_authorization::binder::{Interface, Result as BinderResult, Strong};
-use android_security_authorization::aidl::android::security::authorization::IKeystoreAuthorization::{
- BnKeystoreAuthorization, IKeystoreAuthorization,
+use android_security_authorization::binder::{ExceptionCode, Interface, Result as BinderResult,
+ Strong, Status as BinderStatus};
+use android_security_authorization::aidl::android::security::authorization::{
+ IKeystoreAuthorization::BnKeystoreAuthorization, IKeystoreAuthorization::IKeystoreAuthorization,
+ LockScreenEvent::LockScreenEvent, AuthorizationTokens::AuthorizationTokens,
+ ResponseCode::ResponseCode,
};
-use android_security_authorization:: aidl::android::security::authorization::LockScreenEvent::LockScreenEvent;
-use android_system_keystore2::aidl::android::system::keystore2::ResponseCode::ResponseCode;
+use android_system_keystore2::aidl::android::system::keystore2::{
+ ResponseCode::ResponseCode as KsResponseCode };
use anyhow::{Context, Result};
use binder::IBinder;
+use keystore2_selinux as selinux;
+
+/// This is the Authorization error type, it wraps binder exceptions and the
+/// Authorization ResponseCode
+#[derive(Debug, thiserror::Error, PartialEq)]
+pub enum Error {
+ /// Wraps an IKeystoreAuthorization response code as defined by
+ /// android.security.authorization AIDL interface specification.
+ #[error("Error::Rc({0:?})")]
+ Rc(ResponseCode),
+ /// Wraps a Binder exception code other than a service specific exception.
+ #[error("Binder exception code {0:?}, {1:?}")]
+ 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.
+///
+/// `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,
+/// which are then converted to the corresponding response codes of android.security.authorization
+/// AIDL interface specification.
+///
+/// `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, None));
+ }
+ 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, None))
+ },
+ handle_ok,
+ )
+}
/// This struct is defined to implement the aforementioned AIDL interface.
/// As of now, it is an empty struct.
@@ -99,11 +171,33 @@
}
_ => {
// Any other combination is not supported.
- Err(KeystoreError::Rc(ResponseCode::INVALID_ARGUMENT))
+ Err(Error::Rc(ResponseCode::INVALID_ARGUMENT))
.context("In on_lock_screen_event: Unknown event.")
}
}
}
+
+ fn get_auth_tokens_for_credstore(
+ &self,
+ challenge: i64,
+ secure_user_id: i64,
+ auth_token_max_age_millis: i64,
+ ) -> Result<AuthorizationTokens> {
+ // Check permission. Function should return if this failed. Therefore having '?' at the end
+ // is very important.
+ check_keystore_permission(KeystorePerm::get_auth_token())
+ .context("In get_auth_tokens_for_credstore.")?;
+
+ // if the challenge is zero, return error
+ if challenge == 0 {
+ return Err(Error::Rc(ResponseCode::INVALID_ARGUMENT))
+ .context("In get_auth_tokens_for_credstore. Challenge can not be zero.");
+ }
+ // Obtain the auth token and the timestamp token from the enforcement module.
+ let (auth_token, ts_token) =
+ ENFORCEMENTS.get_auth_tokens(challenge, secure_user_id, auth_token_max_age_millis)?;
+ Ok(AuthorizationTokens { authToken: auth_token, timestampToken: ts_token })
+ }
}
impl Interface for AuthorizationManager {}
@@ -121,4 +215,20 @@
) -> BinderResult<()> {
map_or_log_err(self.on_lock_screen_event(lock_screen_event, user_id, password), Ok)
}
+
+ fn getAuthTokensForCredStore(
+ &self,
+ challenge: i64,
+ secure_user_id: i64,
+ auth_token_max_age_millis: i64,
+ ) -> binder::public_api::Result<AuthorizationTokens> {
+ map_or_log_err(
+ self.get_auth_tokens_for_credstore(
+ challenge,
+ secure_user_id,
+ auth_token_max_age_millis,
+ ),
+ Ok,
+ )
+ }
}
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 57ca7aa..5f19cf0 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -738,6 +738,11 @@
self.0
}
+ /// Returns the value of MonotonicRawTime in milli seconds as i64
+ pub fn milli_seconds(&self) -> i64 {
+ self.0 * 1000
+ }
+
/// Like i64::checked_sub.
pub fn checked_sub(&self, other: &Self) -> Option<Self> {
self.0.checked_sub(other.0).map(Self)
@@ -790,6 +795,11 @@
pub fn time_received(&self) -> MonotonicRawTime {
self.time_received
}
+
+ /// Returns the challenge value of the auth token.
+ pub fn challenge(&self) -> i64 {
+ self.auth_token.challenge
+ }
}
/// Shared in-memory databases get destroyed as soon as the last connection to them gets closed.
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index aa852f4..06648f1 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -14,6 +14,7 @@
//! This is the Keystore 2.0 Enforcements module.
// TODO: more description to follow.
+use crate::authorization::Error as AuthzError;
use crate::database::{AuthTokenEntry, MonotonicRawTime};
use crate::error::{map_binder_status, Error, ErrorCode};
use crate::globals::{get_timestamp_service, ASYNC_TASK, DB, ENFORCEMENTS};
@@ -26,6 +27,7 @@
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
ISecureClock::ISecureClock, TimeStampToken::TimeStampToken,
};
+use android_security_authorization::aidl::android::security::authorization::ResponseCode::ResponseCode as AuthzResponseCode;
use android_system_keystore2::aidl::android::system::keystore2::{
IKeystoreSecurityLevel::KEY_FLAG_AUTH_BOUND_WITHOUT_CRYPTOGRAPHIC_LSKF_BINDING,
OperationChallenge::OperationChallenge,
@@ -218,7 +220,7 @@
if let Err(e) = sender.send(get_timestamp_token(challenge)) {
log::info!(
concat!(
- "In timestamp_token_request: Operation hung up ",
+ "In timestamp_token_request: Receiver hung up ",
"before timestamp token could be delivered. {:?}"
),
e
@@ -767,6 +769,66 @@
auth_bound && !skip_lskf_binding
}
+ /// Finds a matching auth token along with a timestamp token.
+ /// This method looks through auth-tokens cached by keystore which satisfy the given
+ /// authentication information (i.e. |secureUserId|).
+ /// The most recent matching auth token which has a |challenge| field which matches
+ /// the passed-in |challenge| parameter is returned.
+ /// In this case the |authTokenMaxAgeMillis| parameter is not used.
+ ///
+ /// Otherwise, the most recent matching auth token which is younger than |authTokenMaxAgeMillis|
+ /// is returned.
+ pub fn get_auth_tokens(
+ &self,
+ challenge: i64,
+ secure_user_id: i64,
+ auth_token_max_age_millis: i64,
+ ) -> Result<(HardwareAuthToken, TimeStampToken)> {
+ let auth_type = HardwareAuthenticatorType::ANY;
+ let sids: Vec<i64> = vec![secure_user_id];
+ // Filter the matching auth tokens by challenge
+ let result = Self::find_auth_token(|hat: &AuthTokenEntry| {
+ (challenge == hat.challenge()) && hat.satisfies(&sids, auth_type)
+ })
+ .context(
+ "In get_auth_tokens: Failed to get a matching auth token filtered by challenge.",
+ )?;
+
+ let auth_token = if let Some((auth_token_entry, _)) = result {
+ auth_token_entry.take_auth_token()
+ } else {
+ // Filter the matching auth tokens by age.
+ if auth_token_max_age_millis != 0 {
+ let now_in_millis = MonotonicRawTime::now().milli_seconds();
+ let result = Self::find_auth_token(|auth_token_entry: &AuthTokenEntry| {
+ let token_valid = now_in_millis
+ .checked_sub(auth_token_entry.time_received().milli_seconds())
+ .map_or(false, |token_age_in_millis| {
+ token_age_in_millis > auth_token_max_age_millis
+ });
+ token_valid && auth_token_entry.satisfies(&sids, auth_type)
+ })
+ .context(
+ "In get_auth_tokens: Failed to get a matching auth token filtered by age.",
+ )?;
+
+ if let Some((auth_token_entry, _)) = result {
+ auth_token_entry.take_auth_token()
+ } else {
+ return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND))
+ .context("In get_auth_tokens: No auth token found.");
+ }
+ } else {
+ return Err(AuthzError::Rc(AuthzResponseCode::NO_AUTH_TOKEN_FOUND))
+ .context("In get_auth_tokens: Passed-in auth token max age is zero.");
+ }
+ };
+ // Wait and obtain the timestamp token from secure clock service.
+ let tst = get_timestamp_token(challenge)
+ .context("In get_auth_tokens. Error in getting timestamp token.")?;
+ Ok((auth_token, tst))
+ }
+
/// Watch the `keystore.boot_level` system property, and keep self.boot_level up to date.
/// Blocks waiting for system property changes, so must be run in its own thread.
pub fn watch_boot_level(&self) -> Result<()> {