Keystore 2.0: Move per-boot database out of SQLite
Being in SQLite incurs a variety of overheads. Originally, the per-boot database was in SQLite with the intention of living in a temporary file to allow keystore2 to restart without losing auth token state. Since keystore2 is not allowed to crash, it was moved to an in-memory SQLite database. Since it is no longer vfs backed, we do not need to pay the memory, speed, and complexity costs of SQLite for it any longer. Bug: 186436093 Test: atest keystore2_test Test: atest CtsKeystoreTestCases Change-Id: I5c219d294af1876a18a7fdef40307f3b92ae4b8b
This commit is contained in:
parent
4fb1911b8a
commit
d7815caead
8 changed files with 222 additions and 227 deletions
|
@ -121,7 +121,7 @@ impl AuthorizationManager {
|
|||
// Check keystore permission.
|
||||
check_keystore_permission(KeystorePerm::add_auth()).context("In add_auth_token.")?;
|
||||
|
||||
ENFORCEMENTS.add_auth_token(auth_token.clone())?;
|
||||
ENFORCEMENTS.add_auth_token(auth_token.clone());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@
|
|||
//! from the database module these functions take permission check
|
||||
//! callbacks.
|
||||
|
||||
mod perboot;
|
||||
|
||||
use crate::impl_metadata; // This is in db_utils.rs
|
||||
use crate::key_parameter::{KeyParameter, Tag};
|
||||
use crate::permission::KeyPermSet;
|
||||
|
@ -61,9 +63,6 @@ use android_hardware_security_keymint::aidl::android::hardware::security::keymin
|
|||
HardwareAuthToken::HardwareAuthToken,
|
||||
HardwareAuthenticatorType::HardwareAuthenticatorType, SecurityLevel::SecurityLevel,
|
||||
};
|
||||
use android_hardware_security_secureclock::aidl::android::hardware::security::secureclock::{
|
||||
Timestamp::Timestamp,
|
||||
};
|
||||
use android_system_keystore2::aidl::android::system::keystore2::{
|
||||
Domain::Domain, KeyDescriptor::KeyDescriptor,
|
||||
};
|
||||
|
@ -734,6 +733,7 @@ impl<T> DoGc<T> for Result<T> {
|
|||
pub struct KeystoreDB {
|
||||
conn: Connection,
|
||||
gc: Option<Arc<Gc>>,
|
||||
perboot: Arc<perboot::PerbootDB>,
|
||||
}
|
||||
|
||||
/// Database representation of the monotonic time retrieved from the system call clock_gettime with
|
||||
|
@ -782,6 +782,7 @@ impl FromSql for MonotonicRawTime {
|
|||
|
||||
/// This struct encapsulates the information to be stored in the database about the auth tokens
|
||||
/// received by keystore.
|
||||
#[derive(Clone)]
|
||||
pub struct AuthTokenEntry {
|
||||
auth_token: HardwareAuthToken,
|
||||
time_received: MonotonicRawTime,
|
||||
|
@ -828,21 +829,10 @@ pub struct PerBootDbKeepAlive(Connection);
|
|||
|
||||
impl KeystoreDB {
|
||||
const UNASSIGNED_KEY_ID: i64 = -1i64;
|
||||
const PERBOOT_DB_FILE_NAME: &'static str = &"file:perboot.sqlite?mode=memory&cache=shared";
|
||||
|
||||
/// Name of the file that holds the cross-boot persistent database.
|
||||
pub const PERSISTENT_DB_FILENAME: &'static str = &"persistent.sqlite";
|
||||
|
||||
/// This creates a PerBootDbKeepAlive object to keep the per boot database alive.
|
||||
pub fn keep_perboot_db_alive() -> Result<PerBootDbKeepAlive> {
|
||||
let conn = Connection::open_in_memory()
|
||||
.context("In keep_perboot_db_alive: Failed to initialize SQLite connection.")?;
|
||||
|
||||
conn.execute("ATTACH DATABASE ? as perboot;", params![Self::PERBOOT_DB_FILE_NAME])
|
||||
.context("In keep_perboot_db_alive: Failed to attach database perboot.")?;
|
||||
Ok(PerBootDbKeepAlive(conn))
|
||||
}
|
||||
|
||||
/// This will create a new database connection connecting the two
|
||||
/// files persistent.sqlite and perboot.sqlite in the given directory.
|
||||
/// It also attempts to initialize all of the tables.
|
||||
|
@ -859,12 +849,12 @@ impl KeystoreDB {
|
|||
let mut persistent_path_str = "file:".to_owned();
|
||||
persistent_path_str.push_str(&persistent_path.to_string_lossy());
|
||||
|
||||
let conn = Self::make_connection(&persistent_path_str, &Self::PERBOOT_DB_FILE_NAME)?;
|
||||
let conn = Self::make_connection(&persistent_path_str)?;
|
||||
|
||||
// On busy fail Immediately. It is unlikely to succeed given a bug in sqlite.
|
||||
conn.busy_handler(None).context("In KeystoreDB::new: Failed to set busy handler.")?;
|
||||
|
||||
let mut db = Self { conn, gc };
|
||||
let mut db = Self { conn, gc, perboot: perboot::PERBOOT_DB.clone() };
|
||||
db.with_transaction(TransactionBehavior::Immediate, |tx| {
|
||||
Self::init_tables(tx).context("Trying to initialize tables.").no_gc()
|
||||
})?;
|
||||
|
@ -978,41 +968,10 @@ impl KeystoreDB {
|
|||
)
|
||||
.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).
|
||||
// tx.execute("DROP TABLE IF EXISTS perboot.authtoken;", NO_PARAMS)
|
||||
// .context("Failed to drop perboot.authtoken table")?;
|
||||
tx.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.")?;
|
||||
|
||||
// tx.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.
|
||||
tx.execute(
|
||||
"CREATE TABLE IF NOT EXISTS perboot.metadata (
|
||||
key TEXT,
|
||||
value BLOB,
|
||||
UNIQUE(key));",
|
||||
NO_PARAMS,
|
||||
)
|
||||
.context("Failed to initialize \"metadata\" table.")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn make_connection(persistent_file: &str, perboot_file: &str) -> Result<Connection> {
|
||||
fn make_connection(persistent_file: &str) -> Result<Connection> {
|
||||
let conn =
|
||||
Connection::open_in_memory().context("Failed to initialize SQLite connection.")?;
|
||||
|
||||
|
@ -1030,26 +989,10 @@ impl KeystoreDB {
|
|||
}
|
||||
break;
|
||||
}
|
||||
loop {
|
||||
if let Err(e) = conn
|
||||
.execute("ATTACH DATABASE ? as perboot;", params![perboot_file])
|
||||
.context("Failed to attach database perboot.")
|
||||
{
|
||||
if Self::is_locked_error(&e) {
|
||||
std::thread::sleep(std::time::Duration::from_micros(500));
|
||||
continue;
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Drop the cache size from default (2M) to 0.5M
|
||||
conn.execute("PRAGMA persistent.cache_size = -500;", params![])
|
||||
.context("Failed to decrease cache size for persistent db")?;
|
||||
conn.execute("PRAGMA perboot.cache_size = -500;", params![])
|
||||
.context("Failed to decrease cache size for perboot db")?;
|
||||
|
||||
Ok(conn)
|
||||
}
|
||||
|
@ -1135,7 +1078,15 @@ impl KeystoreDB {
|
|||
}
|
||||
StatsdStorageType::Grant => self.get_table_size(storage_type, "persistent", "grant"),
|
||||
StatsdStorageType::AuthToken => {
|
||||
self.get_table_size(storage_type, "perboot", "authtoken")
|
||||
// Since the table is actually a BTreeMap now, unused_size is not meaningfully
|
||||
// reportable
|
||||
// Size provided is only an approximation
|
||||
Ok(Keystore2StorageStats {
|
||||
storage_type,
|
||||
size: (self.perboot.auth_tokens_len() * std::mem::size_of::<AuthTokenEntry>())
|
||||
as i64,
|
||||
unused_size: 0,
|
||||
})
|
||||
}
|
||||
StatsdStorageType::BlobMetadata => {
|
||||
self.get_table_size(storage_type, "persistent", "blobmetadata")
|
||||
|
@ -3177,110 +3128,35 @@ impl KeystoreDB {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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<()> {
|
||||
let _wp = wd::watch_millis("KeystoreDB::insert_auth_token", 500);
|
||||
|
||||
self.with_transaction(TransactionBehavior::Immediate, |tx| {
|
||||
tx.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(()).no_gc()
|
||||
})
|
||||
/// Insert or replace the auth token based on (user_id, auth_id, auth_type)
|
||||
pub fn insert_auth_token(&mut self, auth_token: &HardwareAuthToken) {
|
||||
self.perboot.insert_auth_token_entry(AuthTokenEntry::new(
|
||||
auth_token.clone(),
|
||||
MonotonicRawTime::now(),
|
||||
))
|
||||
}
|
||||
|
||||
/// Find the newest auth token matching the given predicate.
|
||||
pub fn find_auth_token_entry<F>(
|
||||
&mut self,
|
||||
p: F,
|
||||
) -> Result<Option<(AuthTokenEntry, MonotonicRawTime)>>
|
||||
pub fn find_auth_token_entry<F>(&self, p: F) -> Option<(AuthTokenEntry, MonotonicRawTime)>
|
||||
where
|
||||
F: Fn(&AuthTokenEntry) -> bool,
|
||||
{
|
||||
let _wp = wd::watch_millis("KeystoreDB::find_auth_token_entry", 500);
|
||||
|
||||
self.with_transaction(TransactionBehavior::Deferred, |tx| {
|
||||
let mut stmt = tx
|
||||
.prepare("SELECT * from perboot.authtoken ORDER BY time_received DESC;")
|
||||
.context("Prepare statement failed.")?;
|
||||
|
||||
let mut rows = stmt.query(NO_PARAMS).context("Failed to query.")?;
|
||||
|
||||
while let Some(row) = rows.next().context("Failed to get next row.")? {
|
||||
let entry = 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)?,
|
||||
);
|
||||
if p(&entry) {
|
||||
return Ok(Some((
|
||||
entry,
|
||||
Self::get_last_off_body(tx)
|
||||
.context("In find_auth_token_entry: Trying to get last off body")?,
|
||||
)))
|
||||
.no_gc();
|
||||
}
|
||||
}
|
||||
Ok(None).no_gc()
|
||||
})
|
||||
.context("In find_auth_token_entry.")
|
||||
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(&mut self, last_off_body: MonotonicRawTime) -> Result<()> {
|
||||
let _wp = wd::watch_millis("KeystoreDB::insert_last_off_body", 500);
|
||||
|
||||
self.with_transaction(TransactionBehavior::Immediate, |tx| {
|
||||
tx.execute(
|
||||
"INSERT OR REPLACE INTO perboot.metadata (key, value) VALUES (?, ?);",
|
||||
params!["last_off_body", last_off_body],
|
||||
)
|
||||
.context("In insert_last_off_body: failed to insert.")?;
|
||||
Ok(()).no_gc()
|
||||
})
|
||||
pub fn insert_last_off_body(&self, last_off_body: MonotonicRawTime) {
|
||||
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(&mut self, last_off_body: MonotonicRawTime) -> Result<()> {
|
||||
let _wp = wd::watch_millis("KeystoreDB::update_last_off_body", 500);
|
||||
|
||||
self.with_transaction(TransactionBehavior::Immediate, |tx| {
|
||||
tx.execute(
|
||||
"UPDATE perboot.metadata SET value = ? WHERE key = ?;",
|
||||
params![last_off_body, "last_off_body"],
|
||||
)
|
||||
.context("In update_last_off_body: failed to update.")?;
|
||||
Ok(()).no_gc()
|
||||
})
|
||||
pub fn update_last_off_body(&self, last_off_body: MonotonicRawTime) {
|
||||
self.perboot.set_last_off_body(last_off_body)
|
||||
}
|
||||
|
||||
/// Get last_off_body time when finding auth tokens
|
||||
fn get_last_off_body(tx: &Transaction) -> Result<MonotonicRawTime> {
|
||||
let _wp = wd::watch_millis("KeystoreDB::get_last_off_body", 500);
|
||||
|
||||
tx.query_row(
|
||||
"SELECT value from perboot.metadata WHERE key = ?;",
|
||||
params!["last_off_body"],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.context("In get_last_off_body: query_row failed.")
|
||||
fn get_last_off_body(&self) -> MonotonicRawTime {
|
||||
self.perboot.get_last_off_body()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3304,7 +3180,7 @@ mod tests {
|
|||
Timestamp::Timestamp,
|
||||
};
|
||||
use rusqlite::NO_PARAMS;
|
||||
use rusqlite::{Error, TransactionBehavior};
|
||||
use rusqlite::TransactionBehavior;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt::Write;
|
||||
|
@ -3316,9 +3192,9 @@ mod tests {
|
|||
use std::time::Instant;
|
||||
|
||||
fn new_test_db() -> Result<KeystoreDB> {
|
||||
let conn = KeystoreDB::make_connection("file::memory:", "file::memory:")?;
|
||||
let conn = KeystoreDB::make_connection("file::memory:")?;
|
||||
|
||||
let mut db = KeystoreDB { conn, gc: None };
|
||||
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()
|
||||
})?;
|
||||
|
@ -3404,15 +3280,6 @@ mod tests {
|
|||
assert_eq!(tables[3], "keyentry");
|
||||
assert_eq!(tables[4], "keymetadata");
|
||||
assert_eq!(tables[5], "keyparameter");
|
||||
let tables = db
|
||||
.conn
|
||||
.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(), 2);
|
||||
assert_eq!(tables[0], "authtoken");
|
||||
assert_eq!(tables[1], "metadata");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -3427,8 +3294,8 @@ mod tests {
|
|||
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)?;
|
||||
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
|
||||
|
@ -3442,8 +3309,8 @@ mod tests {
|
|||
mac: String::from("mac").into_bytes(),
|
||||
};
|
||||
|
||||
db.insert_auth_token(&auth_token2)?;
|
||||
let mut auth_tokens_returned = get_auth_tokens(&mut db)?;
|
||||
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() {
|
||||
|
@ -3461,33 +3328,16 @@ mod tests {
|
|||
mac: String::from("mac").into_bytes(),
|
||||
};
|
||||
|
||||
db.insert_auth_token(&auth_token3)?;
|
||||
let auth_tokens_returned = get_auth_tokens(&mut db)?;
|
||||
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: &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)
|
||||
fn get_auth_tokens(db: &KeystoreDB) -> Vec<AuthTokenEntry> {
|
||||
db.perboot.get_all_auth_token_entries()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -5341,16 +5191,16 @@ mod tests {
|
|||
#[test]
|
||||
fn test_last_off_body() -> Result<()> {
|
||||
let mut db = new_test_db()?;
|
||||
db.insert_last_off_body(MonotonicRawTime::now())?;
|
||||
db.insert_last_off_body(MonotonicRawTime::now());
|
||||
let tx = db.conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
let last_off_body_1 = KeystoreDB::get_last_off_body(&tx)?;
|
||||
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(MonotonicRawTime::now())?;
|
||||
db.update_last_off_body(MonotonicRawTime::now());
|
||||
let tx2 = db.conn.transaction_with_behavior(TransactionBehavior::Immediate)?;
|
||||
let last_off_body_2 = KeystoreDB::get_last_off_body(&tx2)?;
|
||||
tx2.commit()?;
|
||||
let last_off_body_2 = db.get_last_off_body();
|
||||
assert!(last_off_body_1.seconds() < last_off_body_2.seconds());
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5437,7 +5287,12 @@ mod tests {
|
|||
|
||||
for t in get_valid_statsd_storage_types() {
|
||||
let stat = db.get_storage_stat(t)?;
|
||||
assert!(stat.size >= PAGE_SIZE);
|
||||
// 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 StatsdStorageType::AuthToken = t {
|
||||
} else {
|
||||
assert!(stat.size >= PAGE_SIZE);
|
||||
}
|
||||
assert!(stat.size >= stat.unused_size);
|
||||
}
|
||||
|
||||
|
@ -5567,7 +5422,7 @@ mod tests {
|
|||
authenticatorType: kmhw_authenticator_type::ANY,
|
||||
timestamp: Timestamp { milliSeconds: 10 },
|
||||
mac: b"mac".to_vec(),
|
||||
})?;
|
||||
});
|
||||
assert_storage_increased(&mut db, vec![StatsdStorageType::AuthToken], &mut working_stats);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5596,4 +5451,40 @@ mod tests {
|
|||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
|
122
keystore2/src/database/perboot.rs
Normal file
122
keystore2/src/database/perboot.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
// 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.
|
||||
|
||||
//! 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.
|
||||
|
||||
use super::{AuthTokenEntry, MonotonicRawTime};
|
||||
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;
|
||||
|
||||
#[derive(PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||
struct AuthTokenId {
|
||||
user_id: i64,
|
||||
auth_id: i64,
|
||||
authenticator_type: HardwareAuthenticatorType,
|
||||
}
|
||||
|
||||
impl AuthTokenId {
|
||||
fn from_auth_token(tok: &HardwareAuthToken) -> Self {
|
||||
AuthTokenId {
|
||||
user_id: tok.userId,
|
||||
auth_id: tok.authenticatorId,
|
||||
authenticator_type: tok.authenticatorType,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Implements Eq/Hash to only operate on the AuthTokenId portion
|
||||
//of the AuthTokenEntry. This allows a HashSet to DTRT.
|
||||
#[derive(Clone)]
|
||||
struct AuthTokenEntryWrap(AuthTokenEntry);
|
||||
|
||||
impl std::hash::Hash for AuthTokenEntryWrap {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
AuthTokenId::from_auth_token(&self.0.auth_token).hash(state)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<AuthTokenEntryWrap> for AuthTokenEntryWrap {
|
||||
fn eq(&self, other: &AuthTokenEntryWrap) -> bool {
|
||||
AuthTokenId::from_auth_token(&self.0.auth_token)
|
||||
== AuthTokenId::from_auth_token(&other.0.auth_token)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for AuthTokenEntryWrap {}
|
||||
|
||||
/// Per-boot state structure. Currently only used to track auth tokens and
|
||||
/// last-off-body.
|
||||
#[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! {
|
||||
/// The global instance of the perboot DB. Located here rather than in globals
|
||||
/// in order to restrict access to the database module.
|
||||
pub static ref PERBOOT_DB: Arc<PerbootDB> = Arc::new(PerbootDB::new());
|
||||
}
|
||||
|
||||
impl PerbootDB {
|
||||
/// Construct a new perboot database. Currently just uses default values.
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
/// Add a new auth token + timestamp to the database, replacing any which
|
||||
/// match all of user_id, auth_id, and auth_type.
|
||||
pub fn insert_auth_token_entry(&self, entry: AuthTokenEntry) {
|
||||
self.auth_tokens.write().unwrap().replace(AuthTokenEntryWrap(entry));
|
||||
}
|
||||
/// Locate an auth token entry which matches the predicate with the most
|
||||
/// recent update time.
|
||||
pub fn find_auth_token_entry<P: Fn(&AuthTokenEntry) -> bool>(
|
||||
&self,
|
||||
p: P,
|
||||
) -> Option<AuthTokenEntry> {
|
||||
let reader = self.auth_tokens.read().unwrap();
|
||||
let mut matches: Vec<_> = reader.iter().filter(|x| p(&x.0)).collect();
|
||||
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) -> MonotonicRawTime {
|
||||
MonotonicRawTime(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: MonotonicRawTime) {
|
||||
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()
|
||||
}
|
||||
#[cfg(test)]
|
||||
/// For testing, return all auth tokens currently tracked.
|
||||
pub fn get_all_auth_token_entries(&self) -> Vec<AuthTokenEntry> {
|
||||
self.auth_tokens.read().unwrap().iter().cloned().map(|x| x.0).collect()
|
||||
}
|
||||
}
|
|
@ -638,8 +638,7 @@ impl Enforcements {
|
|||
} else {
|
||||
unlocked_device_required
|
||||
}
|
||||
})
|
||||
.context("In authorize_create: Trying to get required auth token.")?;
|
||||
});
|
||||
Some(
|
||||
hat_and_last_off_body
|
||||
.ok_or(Error::Km(Ec::KEY_USER_NOT_AUTHENTICATED))
|
||||
|
@ -700,15 +699,11 @@ impl Enforcements {
|
|||
})
|
||||
}
|
||||
|
||||
fn find_auth_token<F>(p: F) -> Result<Option<(AuthTokenEntry, MonotonicRawTime)>>
|
||||
fn find_auth_token<F>(p: F) -> Option<(AuthTokenEntry, MonotonicRawTime)>
|
||||
where
|
||||
F: Fn(&AuthTokenEntry) -> bool,
|
||||
{
|
||||
DB.with(|db| {
|
||||
let mut db = db.borrow_mut();
|
||||
db.find_auth_token_entry(p).context("Trying to find auth token.")
|
||||
})
|
||||
.context("In find_auth_token.")
|
||||
DB.with(|db| db.borrow().find_auth_token_entry(p))
|
||||
}
|
||||
|
||||
/// Checks if the time now since epoch is greater than (or equal, if is_given_time_inclusive is
|
||||
|
@ -752,11 +747,9 @@ impl Enforcements {
|
|||
/// Add this auth token to the database.
|
||||
/// Then present the auth token to the op auth map. If an operation is waiting for this
|
||||
/// auth token this fulfills the request and removes the receiver from the map.
|
||||
pub fn add_auth_token(&self, hat: HardwareAuthToken) -> Result<()> {
|
||||
DB.with(|db| db.borrow_mut().insert_auth_token(&hat)).context("In add_auth_token.")?;
|
||||
|
||||
pub fn add_auth_token(&self, hat: HardwareAuthToken) {
|
||||
DB.with(|db| db.borrow_mut().insert_auth_token(&hat));
|
||||
self.op_auth_map.add_auth_token(hat);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// This allows adding an entry to the op_auth_map, indexed by the operation challenge.
|
||||
|
@ -824,10 +817,7 @@ impl Enforcements {
|
|||
// 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()
|
||||
|
@ -842,10 +832,7 @@ impl Enforcements {
|
|||
auth_token_max_age_millis > token_age_in_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()
|
||||
|
|
|
@ -61,8 +61,7 @@ pub fn create_thread_local_db() -> KeystoreDB {
|
|||
.expect("Failed to open database.");
|
||||
DB_INIT.call_once(|| {
|
||||
log::info!("Touching Keystore 2.0 database for this first time since boot.");
|
||||
db.insert_last_off_body(MonotonicRawTime::now())
|
||||
.expect("Could not initialize database with last off body.");
|
||||
db.insert_last_off_body(MonotonicRawTime::now());
|
||||
log::info!("Calling cleanup leftovers.");
|
||||
let n = db.cleanup_leftovers().expect("Failed to cleanup database on startup.");
|
||||
if n != 0 {
|
||||
|
|
|
@ -47,10 +47,6 @@ fn main() {
|
|||
// Saying hi.
|
||||
info!("Keystore2 is starting.");
|
||||
|
||||
// Initialize the per boot database.
|
||||
let _keep_me_alive = keystore2::database::KeystoreDB::keep_perboot_db_alive()
|
||||
.expect("Failed to initialize the perboot database.");
|
||||
|
||||
let mut args = std::env::args();
|
||||
args.next().expect("That's odd. How is there not even a first argument?");
|
||||
|
||||
|
|
|
@ -179,8 +179,8 @@ impl Maintenance {
|
|||
check_keystore_permission(KeystorePerm::report_off_body())
|
||||
.context("In on_device_off_body.")?;
|
||||
|
||||
DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now()))
|
||||
.context("In on_device_off_body: Trying to update last off body time.")
|
||||
DB.with(|db| db.borrow_mut().update_last_off_body(MonotonicRawTime::now()));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn migrate_key_namespace(source: &KeyDescriptor, destination: &KeyDescriptor) -> Result<()> {
|
||||
|
|
|
@ -992,7 +992,7 @@ impl SuperKeyManager {
|
|||
for sid in &biometric.sids {
|
||||
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>)> = (|| {
|
||||
let slb = biometric.screen_lock_bound.decrypt(
|
||||
db,
|
||||
|
|
Loading…
Reference in a new issue