Implement authorize_update_or_finish.
This CL implements authorize_update_or_finish method and helper
structs and methods for that.
Bug: 159461976
Test: Unit tests
Change-Id: I4fae6a464ca66fc632fc2f6a9662a425ad7046bf
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 9086faf..4dd60bc 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -12,6 +12,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+//TODO: remove this in the future CLs in the stack.
+#![allow(dead_code)]
+
//! This is the Keystore 2.0 database module.
//! The database module provides a connection to the backing SQLite store.
//! We have two databases one for persistent key blob storage and one for
@@ -47,7 +50,10 @@
use crate::permission::KeyPermSet;
use anyhow::{anyhow, Context, Result};
-use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
+ SecurityLevel::SecurityLevel,
+};
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, KeyDescriptor::KeyDescriptor,
};
@@ -244,6 +250,42 @@
conn: Connection,
}
+/// This struct encapsulates the information to be stored in the database about the auth tokens
+/// received by keystore.
+pub struct AuthTokenEntry {
+ auth_token: HardwareAuthToken,
+ time_received: i32,
+}
+
+impl AuthTokenEntry {
+ fn new(auth_token: HardwareAuthToken, time_received: i32) -> Self {
+ AuthTokenEntry { auth_token, time_received }
+ }
+
+ /// Checks if this auth token satisfies the given authentication information.
+ pub fn satisfies_auth(
+ auth_token: &HardwareAuthToken,
+ user_secure_ids: &[i64],
+ auth_type: HardwareAuthenticatorType,
+ ) -> bool {
+ user_secure_ids.iter().any(|&sid| {
+ (sid == auth_token.userId || sid == auth_token.authenticatorId)
+ && (((auth_type.0 as i32) & (auth_token.authenticatorType.0 as i32)) != 0)
+ })
+ }
+
+ fn is_newer_than(&self, other: &AuthTokenEntry) -> bool {
+ // NOTE: Although in legacy keystore both timestamp and time_received are involved in this
+ // check, we decided to only consider time_received in keystore2 code.
+ self.time_received > other.time_received
+ }
+
+ /// Returns the auth token wrapped by the AuthTokenEntry
+ pub fn get_auth_token(self) -> HardwareAuthToken {
+ self.auth_token
+ }
+}
+
impl KeystoreDB {
/// This will create a new database connection connecting the two
/// files persistent.sqlite and perboot.sqlite in the given directory.
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 7f9569f..dfd89b5 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -17,7 +17,16 @@
//! This is the Keystore 2.0 Enforcements module.
// TODO: more description to follow.
-use android_hardware_security_keymint::aidl::android::hardware::security::keymint::HardwareAuthToken::HardwareAuthToken;
+use crate::auth_token_handler::AuthTokenHandler;
+use crate::database::AuthTokenEntry;
+use crate::error::Error as KeystoreError;
+use crate::key_parameter::{KeyParameter, KeyParameterValue};
+use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ ErrorCode::ErrorCode as Ec, HardwareAuthToken::HardwareAuthToken,
+ HardwareAuthenticatorType::HardwareAuthenticatorType,
+};
+use android_system_keystore2::aidl::android::system::keystore2::OperationChallenge::OperationChallenge;
+use anyhow::{Context, Result};
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
@@ -39,6 +48,87 @@
op_auth_map: Mutex::new(HashMap::new()),
}
}
+
+ /// Checks if update or finish calls are authorized. If the operation is based on per-op key,
+ /// try to receive the auth token from the op_auth_map. We assume that by the time update/finish
+ /// is called, the auth token has been delivered to keystore. Therefore, we do not wait for it
+ /// and if the auth token is not found in the map, an error is returned.
+ pub fn authorize_update_or_finish(
+ &self,
+ key_params: &[KeyParameter],
+ op_challenge: Option<OperationChallenge>,
+ ) -> Result<AuthTokenHandler> {
+ let mut user_auth_type: Option<HardwareAuthenticatorType> = None;
+ let mut user_secure_ids = Vec::<i64>::new();
+ let mut is_timeout_key = false;
+
+ for key_param in key_params.iter() {
+ match key_param.key_parameter_value() {
+ KeyParameterValue::NoAuthRequired => {
+ // unlike in authorize_create, we do not check if both NoAuthRequired and user
+ // secure id are present, because that is already checked in authorize_create.
+ return Ok(AuthTokenHandler::NoAuthRequired);
+ }
+ KeyParameterValue::AuthTimeout(_) => {
+ is_timeout_key = true;
+ }
+ KeyParameterValue::HardwareAuthenticatorType(a) => {
+ user_auth_type = Some(*a);
+ }
+ KeyParameterValue::UserSecureID(u) => {
+ user_secure_ids.push(*u);
+ }
+ _ => {}
+ }
+ }
+
+ // If either of auth_type or secure_id is present and the other is not present,
+ // authorize_create would have already returned error.
+ // At this point, if UserSecureID is present and AuthTimeout is not present in
+ // key parameters, per-op auth is required.
+ // Obtain and validate the auth token.
+ if !is_timeout_key && !user_secure_ids.is_empty() {
+ let challenge =
+ op_challenge.ok_or(KeystoreError::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(
+ "In authorize_update_or_finish: Auth required, but operation challenge is not
+ present.",
+ )?;
+ let auth_type =
+ user_auth_type.ok_or(KeystoreError::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(
+ "In authorize_update_or_finish: Auth required, but authenticator type is not
+ present.",
+ )?;
+ // It is ok to unwrap here, because there is no way this lock can get poisoned and
+ // and there is no way to recover if it is poisoned.
+ let mut op_auth_map_guard = self.op_auth_map.lock().unwrap();
+ let auth_entry = op_auth_map_guard.remove(&(challenge.challenge));
+
+ match auth_entry {
+ Some(Some(auth_token)) => {
+ if AuthTokenEntry::satisfies_auth(&auth_token, &user_secure_ids, auth_type) {
+ return Ok(AuthTokenHandler::Token(auth_token, None));
+ } else {
+ return Err(KeystoreError::Km(Ec::KEY_USER_NOT_AUTHENTICATED))
+ .context("In authorize_update_or_finish: Auth token does not match.");
+ }
+ }
+ _ => {
+ // there was no auth token
+ return Err(KeystoreError::Km(Ec::KEY_USER_NOT_AUTHENTICATED)).context(
+ "In authorize_update_or_finish: Auth required, but an auth token
+ is not found for the given operation challenge, in the op_auth_map.",
+ );
+ }
+ }
+ }
+
+ // If we don't find HardwareAuthenticatorType and UserSecureID, we assume that
+ // authentication is not required, because in legacy keys, authentication related
+ // key parameters may not present.
+ // TODO: METRICS: count how many times (if any) this code path is executed, in order
+ // to identify if any such keys are in use
+ Ok(AuthTokenHandler::NoAuthRequired)
+ }
}
impl Default for Enforcements {
@@ -47,4 +137,4 @@
}
}
-//TODO: Add tests to enforcement module (b/175578618).
+// TODO: Add tests to enforcement module (b/175578618).