Merge "Add IKeystoreAuthorization AIDL interface with addAuthToken method."
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index e63a469..7bd3c77 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -49,6 +49,7 @@
"liblazy_static",
"liblibsqlite3_sys",
"liblog_rust",
+ "librand",
"librusqlite",
"libthiserror",
],
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 45d561a..9086faf 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -41,6 +41,7 @@
//! from the database module these functions take permission check
//! callbacks.
+use crate::db_utils;
use crate::error::{Error as KsError, ResponseCode};
use crate::key_parameter::{KeyParameter, SqlField, Tag};
use crate::permission::KeyPermSet;
@@ -56,11 +57,12 @@
use rand::prelude::random;
use rusqlite::{
params, types::FromSql, types::FromSqlResult, types::ToSqlOutput, types::ValueRef, Connection,
- OptionalExtension, Row, Rows, ToSql, Transaction, TransactionBehavior, NO_PARAMS,
+ OptionalExtension, ToSql, Transaction, TransactionBehavior, NO_PARAMS,
};
use std::{
collections::HashSet,
- sync::{Condvar, Mutex, Once},
+ path::Path,
+ sync::{Condvar, Mutex},
};
#[cfg(test)]
use tests::random;
@@ -236,8 +238,6 @@
}
}
-static INIT_TABLES: Once = Once::new();
-
/// KeystoreDB wraps a connection to an SQLite database and tracks its
/// ownership. It also implements all of Keystore 2.0's database functionality.
pub struct KeystoreDB {
@@ -246,15 +246,26 @@
impl KeystoreDB {
/// This will create a new database connection connecting the two
- /// files persistent.sqlite and perboot.sqlite in the current working
- /// directory, which is usually `/data/misc/keystore/`.
- /// It also attempts to initialize all of the tables on the first instantiation
- /// per service startup. KeystoreDB cannot be used by multiple threads.
+ /// files persistent.sqlite and perboot.sqlite in the given directory.
+ /// It also attempts to initialize all of the tables.
+ /// KeystoreDB cannot be used by multiple threads.
/// Each thread should open their own connection using `thread_local!`.
- pub fn new() -> Result<Self> {
- let conn = Self::make_connection("file:persistent.sqlite", "file:perboot.sqlite")?;
+ pub fn new(db_root: &Path) -> Result<Self> {
+ // Build the path to the sqlite files.
+ let mut persistent_path = db_root.to_path_buf();
+ persistent_path.push("persistent.sqlite");
+ let mut perboot_path = db_root.to_path_buf();
+ perboot_path.push("perboot.sqlite");
- INIT_TABLES.call_once(|| Self::init_tables(&conn).expect("Failed to initialize tables."));
+ // Now convert them to strings prefixed with "file:"
+ let mut persistent_path_str = "file:".to_owned();
+ persistent_path_str.push_str(&persistent_path.to_string_lossy());
+ let mut perboot_path_str = "file:".to_owned();
+ perboot_path_str.push_str(&perboot_path.to_string_lossy());
+
+ let conn = Self::make_connection(&persistent_path_str, &perboot_path_str)?;
+
+ Self::init_tables(&conn)?;
Ok(Self { conn })
}
@@ -298,14 +309,8 @@
)
.context("Failed to initialize \"keyparameter\" table.")?;
- // TODO only drop the perboot table if we start up for the first time per boot.
- // Right now this is done once per startup which will lose some information
- // upon a crash.
- // Note: This is no regression with respect to the legacy Keystore.
- conn.execute("DROP TABLE IF EXISTS perboot.grant;", NO_PARAMS)
- .context("Failed to drop perboot.grant table")?;
conn.execute(
- "CREATE TABLE perboot.grant (
+ "CREATE TABLE IF NOT EXISTS persistent.grant (
id INTEGER UNIQUE,
grantee INTEGER,
keyentryid INTEGER,
@@ -385,24 +390,30 @@
key_id: &KeyIdGuard,
params: impl IntoIterator<Item = &'a KeyParameter>,
) -> Result<()> {
- let mut stmt = self
+ let tx = self
.conn
- .prepare(
- "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
+ .transaction_with_behavior(TransactionBehavior::Immediate)
+ .context("In insert_keyparameter: Failed to start transaction.")?;
+ {
+ let mut stmt = tx
+ .prepare(
+ "INSERT into persistent.keyparameter (keyentryid, tag, data, security_level)
VALUES (?, ?, ?, ?);",
- )
- .context("In insert_keyparameter: Failed to prepare statement.")?;
+ )
+ .context("In insert_keyparameter: Failed to prepare statement.")?;
- let iter = params.into_iter();
- for p in iter {
- stmt.insert(params![
- key_id.0,
- p.get_tag().0,
- p.key_parameter_value(),
- p.security_level().0
- ])
- .with_context(|| format!("In insert_keyparameter: Failed to insert {:?}", p))?;
+ let iter = params.into_iter();
+ for p in iter {
+ stmt.insert(params![
+ key_id.0,
+ p.get_tag().0,
+ p.key_parameter_value(),
+ p.security_level().0
+ ])
+ .with_context(|| format!("In insert_keyparameter: Failed to insert {:?}", p))?;
+ }
}
+ tx.commit().context("In insert_keyparameter: Failed to commit transaction.")?;
Ok(())
}
@@ -478,7 +489,7 @@
let mut rows = stmt
.query(params![key.domain.0 as u32, key.nspace, alias])
.context("In load_key_entry_id: Failed to read from keyentry table.")?;
- Self::with_rows_extract_one(&mut rows, |row| {
+ db_utils::with_rows_extract_one(&mut rows, |row| {
row.map_or_else(|| Err(KsError::Rc(ResponseCode::KEY_NOT_FOUND)), Ok)?
.get(0)
.context("Failed to unpack id.")
@@ -527,7 +538,7 @@
Domain::GRANT => {
let mut stmt = tx
.prepare(
- "SELECT keyentryid, access_vector FROM perboot.grant
+ "SELECT keyentryid, access_vector FROM persistent.grant
WHERE grantee = ? AND id = ?;",
)
.context("Domain::GRANT prepare statement failed")?;
@@ -535,7 +546,7 @@
.query(params![caller_uid as i64, key.nspace])
.context("Domain:Grant: query failed.")?;
let (key_id, access_vector): (i64, i32) =
- Self::with_rows_extract_one(&mut rows, |row| {
+ db_utils::with_rows_extract_one(&mut rows, |row| {
let r =
row.map_or_else(|| Err(KsError::Rc(ResponseCode::KEY_NOT_FOUND)), Ok)?;
Ok((
@@ -560,7 +571,7 @@
let mut rows =
stmt.query(params![key.nspace]).context("Domain::KEY_ID: query failed.")?;
let (domain, namespace): (Domain, i64) =
- Self::with_rows_extract_one(&mut rows, |row| {
+ db_utils::with_rows_extract_one(&mut rows, |row| {
let r =
row.map_or_else(|| Err(KsError::Rc(ResponseCode::KEY_NOT_FOUND)), Ok)?;
Ok((
@@ -599,7 +610,7 @@
let mut km_blob: Option<Vec<u8>> = None;
let mut cert_blob: Option<Vec<u8>> = None;
let mut cert_chain_blob: Option<Vec<u8>> = None;
- Self::with_rows_extract_all(&mut rows, |row| {
+ db_utils::with_rows_extract_all(&mut rows, |row| {
let sub_type: SubComponentType =
row.get(2).context("Failed to extract subcomponent_type.")?;
match (sub_type, load_bits.load_public()) {
@@ -640,7 +651,7 @@
let mut rows =
stmt.query(params![key_id]).context("In load_key_parameters: query failed.")?;
- Self::with_rows_extract_all(&mut rows, |row| {
+ db_utils::with_rows_extract_all(&mut rows, |row| {
let tag = Tag(row.get(0).context("Failed to read tag.")?);
let sec_level = SecurityLevel(row.get(2).context("Failed to read sec_level.")?);
parameters.push(
@@ -754,6 +765,35 @@
))
}
+ /// Returns a list of KeyDescriptors in the selected domain/namespace.
+ /// The key descriptors will have the domain, nspace, and alias field set.
+ /// Domain must be APP or SELINUX, the caller must make sure of that.
+ pub fn list(&mut self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
+ let mut stmt = self
+ .conn
+ .prepare(
+ "SELECT alias FROM persistent.keyentry
+ WHERE domain = ? AND namespace = ? AND alias IS NOT NULL;",
+ )
+ .context("In list: Failed to prepare.")?;
+
+ let mut rows =
+ stmt.query(params![domain.0 as u32, namespace]).context("In list: Failed to query.")?;
+
+ let mut descriptors: Vec<KeyDescriptor> = Vec::new();
+ db_utils::with_rows_extract_all(&mut rows, |row| {
+ descriptors.push(KeyDescriptor {
+ domain,
+ nspace: namespace,
+ alias: Some(row.get(0).context("Trying to extract alias.")?),
+ blob: None,
+ });
+ Ok(())
+ })
+ .context("In list.")?;
+ Ok(descriptors)
+ }
+
/// Adds a grant to the grant table.
/// Like `load_key_entry` this function loads the access tuple before
/// it uses the callback for a permission check. Upon success,
@@ -795,7 +835,7 @@
let grant_id = if let Some(grant_id) = tx
.query_row(
- "SELECT id FROM perboot.grant
+ "SELECT id FROM persistent.grant
WHERE keyentryid = ? AND grantee = ?;",
params![key_id, grantee_uid],
|row| row.get(0),
@@ -804,7 +844,7 @@
.context("In grant: Failed get optional existing grant id.")?
{
tx.execute(
- "UPDATE perboot.grant
+ "UPDATE persistent.grant
SET access_vector = ?
WHERE id = ?;",
params![i32::from(access_vector), grant_id],
@@ -814,7 +854,7 @@
} else {
Self::insert_with_retry(|id| {
tx.execute(
- "INSERT INTO perboot.grant (id, grantee, keyentryid, access_vector)
+ "INSERT INTO persistent.grant (id, grantee, keyentryid, access_vector)
VALUES (?, ?, ?, ?);",
params![id, grantee_uid, key_id, i32::from(access_vector)],
)
@@ -850,7 +890,7 @@
check_permission(&access_key_descriptor).context("In grant: check_permission failed.")?;
tx.execute(
- "DELETE FROM perboot.grant
+ "DELETE FROM persistent.grant
WHERE keyentryid = ? AND grantee = ?;",
params![key_id, grantee_uid],
)
@@ -883,41 +923,6 @@
}
}
}
-
- // Takes Rows as returned by a query call on prepared statement.
- // Extracts exactly one row with the `row_extractor` and fails if more
- // rows are available.
- // If no row was found, `None` is passed to the `row_extractor`.
- // This allows the row extractor to decide on an error condition or
- // a different default behavior.
- fn with_rows_extract_one<'a, T, F>(rows: &mut Rows<'a>, row_extractor: F) -> Result<T>
- where
- F: FnOnce(Option<&Row<'a>>) -> Result<T>,
- {
- let result =
- row_extractor(rows.next().context("with_rows_extract_one: Failed to unpack row.")?);
-
- rows.next()
- .context("In with_rows_extract_one: Failed to unpack unexpected row.")?
- .map_or_else(|| Ok(()), |_| Err(KsError::sys()))
- .context("In with_rows_extract_one: Unexpected row.")?;
-
- result
- }
-
- fn with_rows_extract_all<'a, F>(rows: &mut Rows<'a>, mut row_extractor: F) -> Result<()>
- where
- F: FnMut(&Row<'a>) -> Result<()>,
- {
- loop {
- match rows.next().context("In with_rows_extract_all: Failed to unpack row")? {
- Some(row) => {
- row_extractor(&row).context("In with_rows_extract_all.")?;
- }
- None => break Ok(()),
- }
- }
- }
}
#[cfg(test)]
@@ -930,15 +935,13 @@
};
use crate::key_perm_set;
use crate::permission::{KeyPerm, KeyPermSet};
+ use crate::test::utils::TempDir;
use rusqlite::NO_PARAMS;
use std::cell::RefCell;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::Arc;
use std::thread;
- static PERSISTENT_TEST_SQL: &str = "/data/local/tmp/persistent.sqlite";
- static PERBOOT_TEST_SQL: &str = "/data/local/tmp/perboot.sqlite";
-
fn new_test_db() -> Result<KeystoreDB> {
let conn = KeystoreDB::make_connection("file::memory:", "file::memory:")?;
@@ -946,13 +949,6 @@
Ok(KeystoreDB { conn })
}
- fn new_test_db_with_persistent_file() -> Result<KeystoreDB> {
- let conn = KeystoreDB::make_connection(PERSISTENT_TEST_SQL, PERBOOT_TEST_SQL)?;
-
- KeystoreDB::init_tables(&conn).context("Failed to initialize tables.")?;
- Ok(KeystoreDB { conn })
- }
-
// Ensure that we're using the "injected" random function, not the real one.
#[test]
fn test_mocked_random() {
@@ -976,44 +972,30 @@
.prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
.query_map(params![], |row| row.get(0))?
.collect::<rusqlite::Result<Vec<String>>>()?;
- assert_eq!(tables.len(), 3);
+ assert_eq!(tables.len(), 4);
assert_eq!(tables[0], "blobentry");
- assert_eq!(tables[1], "keyentry");
- assert_eq!(tables[2], "keyparameter");
+ assert_eq!(tables[1], "grant");
+ assert_eq!(tables[2], "keyentry");
+ assert_eq!(tables[3], "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(), 1);
- assert_eq!(tables[0], "grant");
- Ok(())
- }
-
- #[test]
- fn test_no_persistence_for_tests() -> Result<()> {
- let db = new_test_db()?;
-
- db.create_key_entry(Domain::APP, 100)?;
- let entries = get_keyentry(&db)?;
- assert_eq!(entries.len(), 1);
- let db = new_test_db()?;
-
- let entries = get_keyentry(&db)?;
- assert_eq!(entries.len(), 0);
+ assert_eq!(tables.len(), 0);
Ok(())
}
#[test]
fn test_persistence_for_files() -> Result<()> {
- let _file_guard_persistent = TempFile { filename: PERSISTENT_TEST_SQL };
- let _file_guard_perboot = TempFile { filename: PERBOOT_TEST_SQL };
- let db = new_test_db_with_persistent_file()?;
+ let temp_dir = TempDir::new("persistent_db_test")?;
+ let db = KeystoreDB::new(temp_dir.path())?;
db.create_key_entry(Domain::APP, 100)?;
let entries = get_keyentry(&db)?;
assert_eq!(entries.len(), 1);
- let db = new_test_db_with_persistent_file()?;
+
+ let db = KeystoreDB::new(temp_dir.path())?;
let entries_new = get_keyentry(&db)?;
assert_eq!(entries, entries_new);
@@ -1230,7 +1212,7 @@
// Limiting scope of stmt, because it borrows db.
let mut stmt = db
.conn
- .prepare("SELECT id, grantee, keyentryid, access_vector FROM perboot.grant;")?;
+ .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
let mut rows =
stmt.query_map::<(i64, u32, i64, KeyPermSet), _, _>(NO_PARAMS, |row| {
Ok((
@@ -1438,22 +1420,12 @@
static KEY_LOCK_TEST_ALIAS: &str = "my super duper locked key";
- static KEY_LOCK_TEST_SQL: &str = "/data/local/tmp/persistent_key_lock.sqlite";
- static KEY_LOCK_PERBOOT_TEST_SQL: &str = "/data/local/tmp/perboot_key_lock.sqlite";
-
- fn new_test_db_with_persistent_file_key_lock() -> Result<KeystoreDB> {
- let conn = KeystoreDB::make_connection(KEY_LOCK_TEST_SQL, KEY_LOCK_PERBOOT_TEST_SQL)?;
-
- KeystoreDB::init_tables(&conn).context("Failed to initialize tables.")?;
- Ok(KeystoreDB { conn })
- }
-
#[test]
fn test_insert_and_load_full_keyentry_domain_app_concurrently() -> Result<()> {
let handle = {
- let _file_guard_persistent = Arc::new(TempFile { filename: KEY_LOCK_TEST_SQL });
- let _file_guard_perboot = Arc::new(TempFile { filename: KEY_LOCK_PERBOOT_TEST_SQL });
- let mut db = new_test_db_with_persistent_file_key_lock()?;
+ let temp_dir = Arc::new(TempDir::new("id_lock_test")?);
+ let temp_dir_clone = temp_dir.clone();
+ let mut db = KeystoreDB::new(temp_dir.path())?;
let key_id = make_test_key_entry(&mut db, Domain::APP, 33, KEY_LOCK_TEST_ALIAS)
.context("test_insert_and_load_full_keyentry_domain_app")?
.0;
@@ -1490,9 +1462,8 @@
// of `state` from 1 to 2, despite having a whole second to overtake
// the primary thread.
let handle = thread::spawn(move || {
- let _file_a = _file_guard_persistent;
- let _file_b = _file_guard_perboot;
- let mut db = new_test_db_with_persistent_file_key_lock().unwrap();
+ let temp_dir = temp_dir_clone;
+ let mut db = KeystoreDB::new(temp_dir.path()).unwrap();
assert!(db
.load_key_entry(
KeyDescriptor {
@@ -1529,6 +1500,90 @@
Ok(())
}
+ #[test]
+ fn list() -> Result<()> {
+ let temp_dir = TempDir::new("list_test")?;
+ let mut db = KeystoreDB::new(temp_dir.path())?;
+ static LIST_O_ENTRIES: &[(Domain, i64, &str)] = &[
+ (Domain::APP, 1, "test1"),
+ (Domain::APP, 1, "test2"),
+ (Domain::APP, 1, "test3"),
+ (Domain::APP, 1, "test4"),
+ (Domain::APP, 1, "test5"),
+ (Domain::APP, 1, "test6"),
+ (Domain::APP, 1, "test7"),
+ (Domain::APP, 2, "test1"),
+ (Domain::APP, 2, "test2"),
+ (Domain::APP, 2, "test3"),
+ (Domain::APP, 2, "test4"),
+ (Domain::APP, 2, "test5"),
+ (Domain::APP, 2, "test6"),
+ (Domain::APP, 2, "test8"),
+ (Domain::SELINUX, 100, "test1"),
+ (Domain::SELINUX, 100, "test2"),
+ (Domain::SELINUX, 100, "test3"),
+ (Domain::SELINUX, 100, "test4"),
+ (Domain::SELINUX, 100, "test5"),
+ (Domain::SELINUX, 100, "test6"),
+ (Domain::SELINUX, 100, "test9"),
+ ];
+
+ let list_o_keys: Vec<(i64, i64)> = LIST_O_ENTRIES
+ .iter()
+ .map(|(domain, ns, alias)| {
+ let entry =
+ make_test_key_entry(&mut db, *domain, *ns, *alias).unwrap_or_else(|e| {
+ panic!("Failed to insert {:?} {} {}. Error {:?}", domain, ns, alias, e)
+ });
+ (entry.id(), *ns)
+ })
+ .collect();
+
+ for (domain, namespace) in
+ &[(Domain::APP, 1i64), (Domain::APP, 2i64), (Domain::SELINUX, 100i64)]
+ {
+ let mut list_o_descriptors: Vec<KeyDescriptor> = LIST_O_ENTRIES
+ .iter()
+ .filter_map(|(domain, ns, alias)| match ns {
+ ns if *ns == *namespace => Some(KeyDescriptor {
+ domain: *domain,
+ nspace: *ns,
+ alias: Some(alias.to_string()),
+ blob: None,
+ }),
+ _ => None,
+ })
+ .collect();
+ list_o_descriptors.sort();
+ let mut list_result = db.list(*domain, *namespace)?;
+ list_result.sort();
+ assert_eq!(list_o_descriptors, list_result);
+
+ let mut list_o_ids: Vec<i64> = list_o_descriptors
+ .into_iter()
+ .map(|d| {
+ let (_, entry) = db
+ .load_key_entry(d, KeyEntryLoadBits::NONE, *namespace as u32, |_, _| Ok(()))
+ .unwrap();
+ entry.id()
+ })
+ .collect();
+ list_o_ids.sort_unstable();
+ let mut loaded_entries: Vec<i64> = list_o_keys
+ .iter()
+ .filter_map(|(id, ns)| match ns {
+ ns if *ns == *namespace => Some(*id),
+ _ => None,
+ })
+ .collect();
+ loaded_entries.sort_unstable();
+ assert_eq!(list_o_ids, loaded_entries);
+ }
+ assert_eq!(Vec::<KeyDescriptor>::new(), db.list(Domain::SELINUX, 101)?);
+
+ Ok(())
+ }
+
// Helpers
// Checks that the given result is an error containing the given string.
@@ -1846,8 +1901,9 @@
}
fn debug_dump_grant_table(db: &mut KeystoreDB) -> Result<()> {
- let mut stmt =
- db.conn.prepare("SELECT id, grantee, keyentryid, access_vector FROM perboot.grant;")?;
+ let mut stmt = db
+ .conn
+ .prepare("SELECT id, grantee, keyentryid, access_vector FROM persistent.grant;")?;
let rows = stmt.query_map::<(i64, i64, i64, i64), _, _>(NO_PARAMS, |row| {
Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?))
})?;
@@ -1860,18 +1916,6 @@
Ok(())
}
- // A class that deletes a file when it is dropped.
- // TODO: If we ever add a crate that does this, we can use it instead.
- struct TempFile {
- filename: &'static str,
- }
-
- impl Drop for TempFile {
- fn drop(&mut self) {
- std::fs::remove_file(self.filename).expect("Cannot delete temporary file");
- }
- }
-
// Use a custom random number generator that repeats each number once.
// This allows us to test repeated elements.
diff --git a/keystore2/src/db_utils.rs b/keystore2/src/db_utils.rs
new file mode 100644
index 0000000..615005f
--- /dev/null
+++ b/keystore2/src/db_utils.rs
@@ -0,0 +1,52 @@
+// Copyright 2020, 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.
+
+use crate::error::Error as KsError;
+use anyhow::{Context, Result};
+use rusqlite::{Row, Rows};
+
+// Takes Rows as returned by a query call on prepared statement.
+// Extracts exactly one row with the `row_extractor` and fails if more
+// rows are available.
+// If no row was found, `None` is passed to the `row_extractor`.
+// This allows the row extractor to decide on an error condition or
+// a different default behavior.
+pub fn with_rows_extract_one<'a, T, F>(rows: &mut Rows<'a>, row_extractor: F) -> Result<T>
+where
+ F: FnOnce(Option<&Row<'a>>) -> Result<T>,
+{
+ let result =
+ row_extractor(rows.next().context("with_rows_extract_one: Failed to unpack row.")?);
+
+ rows.next()
+ .context("In with_rows_extract_one: Failed to unpack unexpected row.")?
+ .map_or_else(|| Ok(()), |_| Err(KsError::sys()))
+ .context("In with_rows_extract_one: Unexpected row.")?;
+
+ result
+}
+
+pub fn with_rows_extract_all<'a, F>(rows: &mut Rows<'a>, mut row_extractor: F) -> Result<()>
+where
+ F: FnMut(&Row<'a>) -> Result<()>,
+{
+ loop {
+ match rows.next().context("In with_rows_extract_all: Failed to unpack row")? {
+ Some(row) => {
+ row_extractor(&row).context("In with_rows_extract_all.")?;
+ }
+ None => break Ok(()),
+ }
+ }
+}
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index 0654b29..3ef75c8 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -25,5 +25,12 @@
/// used by only one thread. So we store one database connection per
/// thread in this thread local key.
pub static DB: RefCell<KeystoreDB> =
- RefCell::new(KeystoreDB::new().expect("Failed to open database."));
+ RefCell::new(
+ KeystoreDB::new(
+ // Keystore changes to the database directory on startup
+ // (see keystor2_main.rs).
+ &std::env::current_dir()
+ .expect("Could not get the current working directory.")
+ )
+ .expect("Failed to open database."));
}
diff --git a/keystore2/src/keystore2_main.rs b/keystore2/src/keystore2_main.rs
index ab00794..2916549 100644
--- a/keystore2/src/keystore2_main.rs
+++ b/keystore2/src/keystore2_main.rs
@@ -37,6 +37,9 @@
let mut args = std::env::args();
args.next().expect("That's odd. How is there not even a first argument?");
+
+ // Keystore changes to the database directory on startup (typically /data/misc/keystore).
+ // For the ground truth check the service startup rule for init (typically in keystore2.rc).
if let Some(dir) = args.next() {
if std::env::set_current_dir(dir.clone()).is_err() {
panic!("Failed to set working directory {}.", dir)
diff --git a/keystore2/src/lib.rs b/keystore2/src/lib.rs
index 067399e..f75ec77 100644
--- a/keystore2/src/lib.rs
+++ b/keystore2/src/lib.rs
@@ -24,3 +24,10 @@
pub mod security_level;
pub mod service;
pub mod utils;
+
+mod db_utils;
+
+#[cfg(test)]
+mod test {
+ pub mod utils;
+}
diff --git a/keystore2/src/security_level.rs b/keystore2/src/security_level.rs
index e7d07e3..9a8c7d9 100644
--- a/keystore2/src/security_level.rs
+++ b/keystore2/src/security_level.rs
@@ -52,7 +52,7 @@
operation_db: OperationDb,
}
-static KEYMINT_SERVICE_NAME: &str = "android.hardware.keymint.IKeyMintDevice";
+static KEYMINT_SERVICE_NAME: &str = "android.hardware.security.keymint.IKeyMintDevice";
// Blob of 32 zeroes used as empty masking key.
static ZERO_BLOB_32: &[u8] = &[0; 32];
diff --git a/keystore2/src/service.rs b/keystore2/src/service.rs
index eb0d01b..cb04031 100644
--- a/keystore2/src/service.rs
+++ b/keystore2/src/service.rs
@@ -22,10 +22,11 @@
use crate::error::{self, map_or_log_err, ErrorCode};
use crate::globals::DB;
use crate::permission;
-use crate::permission::KeyPerm;
+use crate::permission::{KeyPerm, KeystorePerm};
use crate::security_level::KeystoreSecurityLevel;
use crate::utils::{
- check_grant_permission, check_key_permission, key_parameters_to_authorizations, Asp,
+ check_grant_permission, check_key_permission, check_keystore_permission,
+ key_parameters_to_authorizations, Asp,
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::SecurityLevel::SecurityLevel;
use android_system_keystore2::aidl::android::system::keystore2::{
@@ -35,6 +36,8 @@
};
use anyhow::{anyhow, Context, Result};
use binder::{IBinder, Interface, ThreadState};
+use error::Error;
+use keystore2_selinux as selinux;
/// Implementation of the IKeystoreService.
pub struct KeystoreService {
@@ -146,8 +149,44 @@
}
fn list_entries(&self, domain: Domain, namespace: i64) -> Result<Vec<KeyDescriptor>> {
- // TODO implement.
- Err(anyhow!(error::Error::sys()))
+ let mut k = match domain {
+ Domain::APP => KeyDescriptor {
+ domain,
+ nspace: ThreadState::get_calling_uid() as u64 as i64,
+ ..Default::default()
+ },
+ Domain::SELINUX => KeyDescriptor{domain, nspace: namespace, ..Default::default()},
+ _ => return Err(Error::perm()).context(
+ "In list_entries: List entries is only supported for Domain::APP and Domain::SELINUX."
+ ),
+ };
+
+ // First we check if the caller has the info permission for the selected domain/namespace.
+ // By default we use the calling uid as namespace if domain is Domain::APP.
+ // If the first check fails we check if the caller has the list permission allowing to list
+ // any namespace. In that case we also adjust the queried namespace if a specific uid was
+ // selected.
+ match check_key_permission(KeyPerm::get_info(), &k, &None) {
+ Err(e) => {
+ if let Some(selinux::Error::PermissionDenied) =
+ e.root_cause().downcast_ref::<selinux::Error>()
+ {
+ check_keystore_permission(KeystorePerm::list())
+ .context("In list_entries: While checking keystore permission.")?;
+ if namespace != -1 {
+ k.nspace = namespace;
+ }
+ } else {
+ return Err(e).context("In list_entries: While checking key permission.")?;
+ }
+ }
+ Ok(()) => {}
+ };
+
+ DB.with(|db| {
+ let mut db = db.borrow_mut();
+ db.list(k.domain, k.nspace)
+ })
}
fn delete_key(&self, key: &KeyDescriptor) -> Result<()> {
diff --git a/keystore2/src/test/utils.rs b/keystore2/src/test/utils.rs
new file mode 100644
index 0000000..e016ec0
--- /dev/null
+++ b/keystore2/src/test/utils.rs
@@ -0,0 +1,63 @@
+// Copyright 2020, 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.
+
+use std::env::temp_dir;
+use std::fs::{create_dir, remove_dir_all};
+use std::io::ErrorKind;
+use std::path::Path;
+
+#[derive(Debug)]
+pub struct TempDir {
+ path: std::path::PathBuf,
+ do_drop: bool,
+}
+
+impl TempDir {
+ pub fn new(prefix: &str) -> std::io::Result<Self> {
+ let tmp = loop {
+ let mut tmp = temp_dir();
+ let number: u16 = rand::random();
+ tmp.push(format!("{}_{:05}", prefix, number));
+ match create_dir(&tmp) {
+ Err(e) => match e.kind() {
+ ErrorKind::AlreadyExists => continue,
+ _ => return Err(e),
+ },
+ Ok(()) => break tmp,
+ }
+ };
+ Ok(Self { path: tmp, do_drop: true })
+ }
+
+ pub fn path(&self) -> &Path {
+ &self.path
+ }
+
+ /// When a test is failing you can set this to false in order to inspect
+ /// the directory structure after the test failed.
+ #[allow(dead_code)]
+ pub fn do_not_drop(&mut self) {
+ println!("Disabled automatic cleanup for: {:?}", self.path);
+ log::info!("Disabled automatic cleanup for: {:?}", self.path);
+ self.do_drop = false;
+ }
+}
+
+impl Drop for TempDir {
+ fn drop(&mut self) {
+ if self.do_drop {
+ remove_dir_all(&self.path).expect("Cannot delete temporary dir.");
+ }
+ }
+}