Implement add auth token functionality.
This CL implements the functionality related to adding an auth token
to auth token table in keystore.
Bug: 171503352
Test: TBD
Change-Id: Ide4150ffcb4bc51b062afa44c82cb0a89b0ee689
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index f9b320f..03523ff 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -48,7 +48,7 @@
use crate::error::{Error as KsError, ResponseCode};
use crate::key_parameter::{KeyParameter, Tag};
use crate::permission::KeyPermSet;
-use anyhow::{anyhow, Context, Result};
+use crate::utils::get_current_time_in_seconds;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
HardwareAuthToken::HardwareAuthToken, HardwareAuthenticatorType::HardwareAuthenticatorType,
@@ -57,13 +57,15 @@
use android_system_keystore2::aidl::android::system::keystore2::{
Domain::Domain, KeyDescriptor::KeyDescriptor,
};
+use anyhow::{anyhow, Context, Result};
use lazy_static::lazy_static;
#[cfg(not(test))]
use rand::prelude::random;
use rusqlite::{
- params, types::FromSql, types::FromSqlResult, types::ToSqlOutput, types::ValueRef, Connection,
- OptionalExtension, ToSql, Transaction, TransactionBehavior, NO_PARAMS,
+ params, types::FromSql, types::FromSqlResult, types::ToSqlOutput, types::Value,
+ types::ValueRef, Connection, OptionalExtension, ToSql, Transaction, TransactionBehavior,
+ NO_PARAMS,
};
use std::{
collections::HashSet,
@@ -250,15 +252,44 @@
conn: Connection,
}
+/// Database representation of the monotonic time retrieved from the system call clock_gettime with
+/// CLOCK_MONOTONIC_RAW. Stores monotonic time as i64 in seconds.
+#[derive(Debug, Copy, Clone, Default, Eq, PartialEq, Ord, PartialOrd)]
+pub struct MonotonicRawTime(i64);
+
+impl MonotonicRawTime {
+ /// Constructs a new MonotonicRawTime
+ pub fn now() -> Self {
+ Self(get_current_time_in_seconds())
+ }
+
+ /// Returns the integer value of MonotonicRawTime as i64
+ pub fn seconds(&self) -> i64 {
+ self.0
+ }
+}
+
+impl ToSql for MonotonicRawTime {
+ fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
+ Ok(ToSqlOutput::Owned(Value::Integer(self.0)))
+ }
+}
+
+impl FromSql for MonotonicRawTime {
+ fn column_result(value: ValueRef) -> FromSqlResult<Self> {
+ Ok(Self(i64::column_result(value)?))
+ }
+}
+
/// 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,
+ time_received: MonotonicRawTime,
}
impl AuthTokenEntry {
- fn new(auth_token: HardwareAuthToken, time_received: i32) -> Self {
+ fn new(auth_token: HardwareAuthToken, time_received: MonotonicRawTime) -> Self {
AuthTokenEntry { auth_token, time_received }
}
@@ -277,7 +308,7 @@
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
+ self.time_received.seconds() > other.time_received.seconds()
}
/// Returns the auth token wrapped by the AuthTokenEntry
@@ -361,6 +392,38 @@
)
.context("Failed to initialize \"grant\" table.")?;
+ //TODO: only drop the following two perboot tables if this is the first start up
+ //during the boot (b/175716626).
+ // conn.execute("DROP TABLE IF EXISTS perboot.authtoken;", NO_PARAMS)
+ // .context("Failed to drop perboot.authtoken table")?;
+ conn.execute(
+ "CREATE TABLE IF NOT EXISTS perboot.authtoken (
+ id INTEGER PRIMARY KEY,
+ challenge INTEGER,
+ user_id INTEGER,
+ auth_id INTEGER,
+ authenticator_type INTEGER,
+ timestamp INTEGER,
+ mac BLOB,
+ time_received INTEGER,
+ UNIQUE(user_id, auth_id, authenticator_type));",
+ NO_PARAMS,
+ )
+ .context("Failed to initialize \"authtoken\" table.")?;
+
+ // conn.execute("DROP TABLE IF EXISTS perboot.metadata;", NO_PARAMS)
+ // .context("Failed to drop perboot.metadata table")?;
+ // metadata table stores certain miscellaneous information required for keystore functioning
+ // during a boot cycle, as key-value pairs.
+ conn.execute(
+ "CREATE TABLE IF NOT EXISTS perboot.metadata (
+ key TEXT,
+ value BLOB,
+ UNIQUE(key));",
+ NO_PARAMS,
+ )
+ .context("Failed to initialize \"metadata\" table.")?;
+
Ok(())
}
@@ -965,6 +1028,26 @@
}
}
}
+
+ /// Insert or replace the auth token based on the UNIQUE constraint of the auth token table
+ pub fn insert_auth_token(&mut self, auth_token: &HardwareAuthToken) -> Result<()> {
+ self.conn
+ .execute(
+ "INSERT OR REPLACE INTO perboot.authtoken (challenge, user_id, auth_id,
+ authenticator_type, timestamp, mac, time_received) VALUES(?, ?, ?, ?, ?, ?, ?);",
+ params![
+ auth_token.challenge,
+ auth_token.userId,
+ auth_token.authenticatorId,
+ auth_token.authenticatorType.0 as i32,
+ auth_token.timestamp.milliSeconds as i64,
+ auth_token.mac,
+ MonotonicRawTime::now(),
+ ],
+ )
+ .context("In insert_auth_token: failed to insert auth token into the database")?;
+ Ok(())
+ }
}
#[cfg(test)]
@@ -978,6 +1061,12 @@
use crate::key_perm_set;
use crate::permission::{KeyPerm, KeyPermSet};
use crate::test::utils::TempDir;
+ use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ HardwareAuthToken::HardwareAuthToken,
+ HardwareAuthenticatorType::HardwareAuthenticatorType as kmhw_authenticator_type,
+ Timestamp::Timestamp,
+ };
+ use rusqlite::Error;
use rusqlite::NO_PARAMS;
use std::cell::RefCell;
use std::sync::atomic::{AtomicU8, Ordering};
@@ -1024,11 +1113,88 @@
.prepare("SELECT name from perboot.sqlite_master WHERE type='table' ORDER BY name;")?
.query_map(params![], |row| row.get(0))?
.collect::<rusqlite::Result<Vec<String>>>()?;
- assert_eq!(tables.len(), 0);
+
+ assert_eq!(tables.len(), 2);
+ assert_eq!(tables[0], "authtoken");
+ assert_eq!(tables[1], "metadata");
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(&mut 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(&mut 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(&mut db)?;
+ assert_eq!(auth_tokens_returned.len(), 2);
+
+ Ok(())
+ }
+
+ // utility function for test_auth_token_table_invariant()
+ fn get_auth_tokens(db: &mut KeystoreDB) -> Result<Vec<AuthTokenEntry>> {
+ let mut stmt = db.conn.prepare("SELECT * from perboot.authtoken;")?;
+
+ let auth_token_entries: Vec<AuthTokenEntry> = stmt
+ .query_map(NO_PARAMS, |row| {
+ Ok(AuthTokenEntry::new(
+ HardwareAuthToken {
+ challenge: row.get(1)?,
+ userId: row.get(2)?,
+ authenticatorId: row.get(3)?,
+ authenticatorType: HardwareAuthenticatorType(row.get(4)?),
+ timestamp: Timestamp { milliSeconds: row.get(5)? },
+ mac: row.get(6)?,
+ },
+ row.get(7)?,
+ ))
+ })?
+ .collect::<Result<Vec<AuthTokenEntry>, Error>>()?;
+ Ok(auth_token_entries)
+ }
+
+ #[test]
fn test_persistence_for_files() -> Result<()> {
let temp_dir = TempDir::new("persistent_db_test")?;
let db = KeystoreDB::new(temp_dir.path())?;
diff --git a/keystore2/src/enforcements.rs b/keystore2/src/enforcements.rs
index 1c64833..473686c 100644
--- a/keystore2/src/enforcements.rs
+++ b/keystore2/src/enforcements.rs
@@ -20,11 +20,12 @@
use crate::auth_token_handler::AuthTokenHandler;
use crate::database::AuthTokenEntry;
use crate::error::Error as KeystoreError;
+use crate::globals::DB;
use crate::key_parameter::{KeyParameter, KeyParameterValue};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, ErrorCode::ErrorCode as Ec, HardwareAuthToken::HardwareAuthToken,
HardwareAuthenticatorType::HardwareAuthenticatorType, KeyPurpose::KeyPurpose,
- SecurityLevel::SecurityLevel, Tag::Tag,
+ SecurityLevel::SecurityLevel, Tag::Tag, Timestamp::Timestamp,
};
use android_system_keystore2::aidl::android::system::keystore2::OperationChallenge::OperationChallenge;
use anyhow::{Context, Result};
@@ -337,6 +338,45 @@
let set = self.device_unlocked_set.lock().unwrap();
!set.contains(&user_id)
}
+
+ /// Sets the device locked status for the user. This method is called externally.
+ pub fn set_device_locked(&self, user_id: i32, device_locked_status: bool) {
+ // unwrap here because there's no way this mutex guard can be poisoned and
+ // because there's no way to recover, even if it is poisoned.
+ let mut set = self.device_unlocked_set.lock().unwrap();
+ if device_locked_status {
+ set.remove(&user_id);
+ } else {
+ set.insert(user_id);
+ }
+ }
+
+ /// Add this auth token to the database.
+ /// Then check if there is an entry in the op_auth_map, indexed by the challenge of this
+ /// auth token (which could have been inserted during create_operation of an operation on a
+ /// per-op-auth key). If so, add a copy of this auth token to the map indexed by the
+ /// challenge.
+ pub fn add_auth_token(&self, auth_token: HardwareAuthToken) -> Result<()> {
+ //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();
+
+ if op_auth_map_guard.contains_key(&auth_token.challenge) {
+ let auth_token_copy = HardwareAuthToken {
+ challenge: auth_token.challenge,
+ userId: auth_token.userId,
+ authenticatorId: auth_token.authenticatorId,
+ authenticatorType: HardwareAuthenticatorType(auth_token.authenticatorType.0),
+ timestamp: Timestamp { milliSeconds: auth_token.timestamp.milliSeconds },
+ mac: auth_token.mac.clone(),
+ };
+ op_auth_map_guard.insert(auth_token.challenge, Some(auth_token_copy));
+ }
+
+ DB.with(|db| db.borrow_mut().insert_auth_token(&auth_token))
+ .context("In add_auth_token.")?;
+ Ok(())
+ }
}
impl Default for Enforcements {
diff --git a/keystore2/src/utils.rs b/keystore2/src/utils.rs
index 66f3db6..c168486 100644
--- a/keystore2/src/utils.rs
+++ b/keystore2/src/utils.rs
@@ -12,6 +12,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+// This suppresses the compiler's complaint about converting tv_sec to i64 in method
+// get_current_time_in_seconds.
+#![allow(clippy::useless_conversion)]
+
//! This module implements utility functions used by the Keystore 2.0 service
//! implementation.
@@ -26,6 +30,7 @@
};
use anyhow::{anyhow, Context};
use binder::{FromIBinder, SpIBinder, ThreadState};
+use std::convert::TryFrom;
use std::sync::Mutex;
/// This function uses its namesake in the permission module and in
@@ -136,3 +141,15 @@
) -> Vec<Authorization> {
parameters.into_iter().map(|p| p.into_authorization()).collect()
}
+
+/// This returns the current time (in seconds) as an instance of a monotonic clock, by invoking the
+/// system call since Rust does not support getting monotonic time instance as an integer.
+pub fn get_current_time_in_seconds() -> i64 {
+ let mut current_time = libc::timespec { tv_sec: 0, tv_nsec: 0 };
+ // Following unsafe block includes one system call to get monotonic time.
+ // Therefore, it is not considered harmful.
+ unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_RAW, &mut current_time) };
+ // It is safe to unwrap here because try_from() returns std::convert::Infallible, which is
+ // defined to be an error that can never happen (i.e. the result is always ok).
+ i64::try_from(current_time.tv_sec).unwrap()
+}