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())?;