diff options
author | Treehugger Robot <treehugger-gerrit@google.com> | 2020-09-09 20:40:38 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-09-09 20:40:38 +0000 |
commit | dc6e5d9ecc082f147905c23bb74a1db772369c94 (patch) | |
tree | c2ddad1dc129392aa0bb3c2d96f6f0a4feb74c7f | |
parent | e18c228d111b1f152c774cfd2075ff8c4e5cc0fc (diff) | |
parent | 501a9749123201ad36068e9b9415a27a321e478a (diff) | |
download | security-temp_sam_168057903.tar.gz |
Merge "Add functionality to Keystore 2.0 database module." am: 501a974912temp_sam_168057903
Original change: https://android-review.googlesource.com/c/platform/system/security/+/1419343
Change-Id: I7047e259671193e73f02217afdc57d549e8ca992
-rw-r--r-- | keystore2/src/database.rs | 953 |
1 files changed, 933 insertions, 20 deletions
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs index b1cde6e3..75f20271 100644 --- a/keystore2/src/database.rs +++ b/keystore2/src/database.rs @@ -12,30 +12,170 @@ // See the License for the specific language governing permissions and // limitations under the License. -// TODO: Once this is stable, remove this and document everything public. -#![allow(missing_docs)] +//! 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 +//! items that have a per boot life cycle. +//! +//! ## Persistent database +//! The persistent database has tables for key blobs. They are organized +//! as follows: +//! The `keyentry` table is the primary table for key entries. It is +//! accompanied by two tables for blobs and parameters. +//! Each key entry occupies exactly one row in the `keyentry` table and +//! zero or more rows in the tables `blobentry` and `keyparameter`. +//! +//! ## Per boot database +//! The per boot database stores items with a per boot lifecycle. +//! Currently, there is only the `grant` table in this database. +//! Grants are references to a key that can be used to access a key by +//! clients that don't own that key. Grants can only be created by the +//! owner of a key. And only certain components can create grants. +//! This is governed by SEPolicy. +//! +//! ## Access control +//! Some database functions that load keys or create grants perform +//! access control. This is because in some cases access control +//! can only be performed after some information about the designated +//! key was loaded from the database. To decouple the permission checks +//! from the database module these functions take permission check +//! callbacks. use crate::error::Error as KsError; -use anyhow::{Context, Result}; +use crate::{error, permission::KeyPermSet}; +use anyhow::{anyhow, Context, Result}; +use android_hardware_keymint::aidl::android::hardware::keymint::SecurityLevel::SecurityLevel; use android_security_keystore2::aidl::android::security::keystore2::{ - Domain, Domain::Domain as DomainType, + Domain, Domain::Domain as DomainType, KeyDescriptor::KeyDescriptor, }; #[cfg(not(test))] use rand::prelude::random; -use rusqlite::{params, Connection, TransactionBehavior, NO_PARAMS}; +use rusqlite::{ + params, types::FromSql, types::FromSqlResult, types::ToSqlOutput, types::ValueRef, Connection, + OptionalExtension, Row, Rows, ToSql, Transaction, TransactionBehavior, NO_PARAMS, +}; use std::sync::Once; #[cfg(test)] use tests::random; +/// Keys have a KeyMint blob component and optional public certificate and +/// certificate chain components. +/// KeyEntryLoadBits is a bitmap that indicates to `KeystoreDB::load_key_entry` +/// which components shall be loaded from the database if present. +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct KeyEntryLoadBits(u32); + +impl KeyEntryLoadBits { + /// Indicate to `KeystoreDB::load_key_entry` that no component shall be loaded. + pub const NONE: KeyEntryLoadBits = Self(0); + /// Indicate to `KeystoreDB::load_key_entry` that the KeyMint component shall be loaded. + pub const KM: KeyEntryLoadBits = Self(1); + /// Indicate to `KeystoreDB::load_key_entry` that the Public components shall be loaded. + pub const PUBLIC: KeyEntryLoadBits = Self(2); + /// Indicate to `KeystoreDB::load_key_entry` that both components shall be loaded. + pub const BOTH: KeyEntryLoadBits = Self(3); + + /// Returns true if this object indicates that the public components shall be loaded. + pub const fn load_public(&self) -> bool { + self.0 & Self::PUBLIC.0 != 0 + } + + /// Returns true if the object indicates that the KeyMint component shall be loaded. + pub const fn load_km(&self) -> bool { + self.0 & Self::KM.0 != 0 + } +} + +/// This type represents a Keystore 2.0 key entry. +/// An entry has a unique `id` by which it can be found in the database. +/// It has a security level field, key parameters, and three optional fields +/// for the KeyMint blob, public certificate and a public certificate chain. +#[derive(Debug, Default, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub struct KeyEntry { + id: i64, + km_blob: Option<Vec<u8>>, + cert: Option<Vec<u8>>, + cert_chain: Option<Vec<u8>>, + sec_level: SecurityLevel, + // parameters: Vec<KeyParameters>, +} + +impl KeyEntry { + /// Returns the unique id of the Key entry. + pub fn id(&self) -> i64 { + self.id + } + /// Exposes the optional KeyMint blob. + pub fn km_blob(&self) -> &Option<Vec<u8>> { + &self.km_blob + } + /// Extracts the Optional KeyMint blob. + pub fn take_km_blob(&mut self) -> Option<Vec<u8>> { + self.km_blob.take() + } + /// Exposes the optional public certificate. + pub fn cert(&self) -> &Option<Vec<u8>> { + &self.cert + } + /// Extracts the optional public certificate. + pub fn take_cert(&mut self) -> Option<Vec<u8>> { + self.cert.take() + } + /// Exposes the optional public certificate chain. + pub fn cert_chain(&self) -> &Option<Vec<u8>> { + &self.cert_chain + } + /// Extracts the optional public certificate_chain. + pub fn take_cert_chain(&mut self) -> Option<Vec<u8>> { + self.cert_chain.take() + } + /// Returns the security level of the key entry. + pub fn sec_level(&self) -> SecurityLevel { + self.sec_level + } +} + +/// Indicates the sub component of a key entry for persistent storage. +#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)] +pub struct SubComponentType(u32); +impl SubComponentType { + /// Persistent identifier for a KeyMint blob. + pub const KM_BLOB: SubComponentType = Self(0); + /// Persistent identifier for a certificate blob. + pub const CERT: SubComponentType = Self(1); + /// Persistent identifier for a certificate chain blob. + pub const CERT_CHAIN: SubComponentType = Self(2); +} + +impl ToSql for SubComponentType { + fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> { + self.0.to_sql() + } +} + +impl FromSql for SubComponentType { + fn column_result(value: ValueRef) -> FromSqlResult<Self> { + Ok(Self(u32::column_result(value)?)) + } +} + 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 { conn: Connection, } 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. + /// 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")?; @@ -56,6 +196,24 @@ impl KeystoreDB { .context("Failed to initialize \"keyentry\" table.")?; conn.execute( + "CREATE VIEW IF NOT EXISTS persistent.orphaned AS + SELECT id FROM persistent.keyentry WHERE domain IS NULL;", + NO_PARAMS, + ) + .context("Failed to initialize \"orphaned\" view")?; + + conn.execute( + "CREATE TABLE IF NOT EXISTS persistent.blobentry ( + id INTEGER PRIMARY KEY, + subcomponent_type INTEGER, + keyentryid INTEGER, + blob BLOB, + sec_level INTEGER);", + NO_PARAMS, + ) + .context("Failed to initialize \"blobentry\" table.")?; + + conn.execute( "CREATE TABLE IF NOT EXISTS persistent.keyparameter ( keyentryid INTEGER, tag INTEGER, @@ -65,6 +223,22 @@ impl KeystoreDB { ) .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 ( + id INTEGER UNIQUE, + grantee INTEGER, + keyentryid INTEGER, + access_vector INTEGER);", + NO_PARAMS, + ) + .context("Failed to initialize \"grant\" table.")?; + Ok(()) } @@ -80,6 +254,12 @@ impl KeystoreDB { Ok(conn) } + /// Creates a new key entry and allocates a new randomized id for the new key. + /// The key id gets associated with a domain and namespace but not with an alias. + /// To complete key generation `rebind_alias` should be called after all of the + /// key artifacts, i.e., blobs and parameters have been associated with the new + /// key id. Finalizing with `rebind_alias` makes the creation of a new key entry + /// atomic even if key generation is not. pub fn create_key_entry(&self, domain: DomainType, namespace: i64) -> Result<i64> { match domain { Domain::App | Domain::SELinux => {} @@ -105,14 +285,41 @@ impl KeystoreDB { }, _, )) => (), + Err(e) => return Err(e).context("Failed to create key entry."), _ => return Ok(newid), } } } + /// Inserts a new blob and associates it with the given key id. Each blob + /// has a sub component type and a security level. + /// Each key can have one of each sub component type associated. If more + /// are added only the most recent can be retrieved, and superseded blobs + /// will get garbage collected. The security level field of components + /// other than `SubComponentType::KM_BLOB` are ignored. + pub fn insert_blob( + &mut self, + key_id: i64, + sc_type: SubComponentType, + blob: &[u8], + sec_level: SecurityLevel, + ) -> Result<()> { + self.conn + .execute( + "INSERT into persistent.blobentry (subcomponent_type, keyentryid, blob, sec_level) + VALUES (?, ?, ?, ?);", + params![sc_type, key_id, blob, sec_level], + ) + .context("Failed to insert blob.")?; + Ok(()) + } + + /// Updates the alias column of the given key id `newid` with the given alias, + /// and atomically, removes the alias, domain, and namespace from another row + /// with the same alias-domain-namespace tuple if such row exits. pub fn rebind_alias( &mut self, - newid: u32, + newid: i64, alias: &str, domain: DomainType, namespace: i64, @@ -120,40 +327,384 @@ impl KeystoreDB { match domain { Domain::App | Domain::SELinux => {} _ => { - return Err(KsError::sys()) - .context(format!("Domain {:?} must be either App or SELinux.", domain)); + return Err(KsError::sys()).context(format!( + "In rebind_alias: Domain {:?} must be either App or SELinux.", + domain + )); } } let tx = self .conn .transaction_with_behavior(TransactionBehavior::Immediate) - .context("Failed to initialize transaction.")?; + .context("In rebind_alias: Failed to initialize transaction.")?; tx.execute( "UPDATE persistent.keyentry SET alias = NULL, domain = NULL, namespace = NULL WHERE alias = ? AND domain = ? AND namespace = ?;", params![alias, domain as i64, namespace], ) - .context("Failed to rebind existing entry.")?; + .context("In rebind_alias: Failed to rebind existing entry.")?; let result = tx .execute( "UPDATE persistent.keyentry - SET alias = ? - WHERE id = ? AND domain = ? AND namespace = ?;", + SET alias = ? + WHERE id = ? AND domain = ? AND namespace = ?;", params![alias, newid, domain as i64, namespace], ) - .context("Failed to set alias.")?; + .context("In rebind_alias: Failed to set alias.")?; if result != 1 { // Note that this explicit rollback is not required, as // the transaction should rollback if we do not commit it. // We leave it here for readability. - tx.rollback().context("Failed to rollback a failed transaction.")?; + tx.rollback().context("In rebind_alias: Failed to rollback a failed transaction.")?; return Err(KsError::sys()).context(format!( - "Expected to update a single entry but instead updated {}.", + "In rebind_alias: Expected to update a single entry but instead updated {}.", result )); } - tx.commit().context("Failed to commit transaction.") + tx.commit().context("In rebind_alias: Failed to commit transaction.") + } + + // Helper function loading the key_id given the key descriptor + // tuple comprising domain, namespace, and alias. + // Requires a valid transaction. + fn load_key_entry_id(key: &KeyDescriptor, tx: &Transaction) -> Result<i64> { + let alias = key + .alias + .as_ref() + .map_or_else(|| Err(KsError::sys()), Ok) + .context("In load_key_entry_id: Alias must be specified.")?; + let mut stmt = tx + .prepare( + "SELECT id FROM persistent.keyentry + WHERE + domain = ? + AND namespace = ? + AND alias = ?;", + ) + .context("In load_key_entry_id: Failed to select from keyentry table.")?; + let mut rows = stmt + .query(params![key.domain, key.namespace_, alias]) + .context("In load_key_entry_id: Failed to read from keyentry table.")?; + Self::with_rows_extract_one(&mut rows, |row| { + row.map_or_else(|| Err(KsError::Rc(error::Rc::KeyNotFound)), Ok)? + .get(0) + .context("Failed to unpack id.") + }) + .context("In load_key_entry_id.") + } + + /// This helper function completes the access tuple of a key, which is required + /// to perform access control. The strategy depends on the `domain` field in the + /// key descriptor. + /// * Domain::SELinux: The access tuple is complete and this function only loads + /// the key_id for further processing. + /// * Domain::App: Like Domain::SELinux, but the tuple is completed by `caller_uid` + /// which serves as the namespace. + /// * Domain::Grant: The grant table is queried for the `key_id` and the + /// `access_vector`. + /// * Domain::KeyId: The keyentry table is queried for the owning `domain` and + /// `namespace`. + /// In each case the information returned is sufficient to perform the access + /// check and the key id can be used to load further key artifacts. + fn load_access_tuple( + tx: &Transaction, + key: KeyDescriptor, + caller_uid: u32, + ) -> Result<(i64, KeyDescriptor, Option<KeyPermSet>)> { + match key.domain { + // Domain App or SELinux. In this case we load the key_id from + // the keyentry database for further loading of key components. + // We already have the full access tuple to perform access control. + // The only distinction is that we use the caller_uid instead + // of the caller supplied namespace if the domain field is + // Domain::App. + Domain::App | Domain::SELinux => { + let mut access_key = key; + if access_key.domain == Domain::App { + access_key.namespace_ = caller_uid as i64; + } + let key_id = Self::load_key_entry_id(&access_key, &tx) + .with_context(|| format!("With key.domain = {}.", access_key.domain))?; + + Ok((key_id, access_key, None)) + } + + // Domain::Grant. In this case we load the key_id and the access_vector + // from the grant table. + Domain::Grant => { + let mut stmt = tx + .prepare( + "SELECT keyentryid, access_vector FROM perboot.grant + WHERE grantee = ? AND id = ?;", + ) + .context("Domain::Grant prepare statement failed")?; + let mut rows = stmt + .query(params![caller_uid as i64, key.namespace_]) + .context("Domain:Grant: query failed.")?; + let (key_id, access_vector): (i64, i32) = + Self::with_rows_extract_one(&mut rows, |row| { + let r = row.map_or_else(|| Err(KsError::Rc(error::Rc::KeyNotFound)), Ok)?; + Ok(( + r.get(0).context("Failed to unpack key_id.")?, + r.get(1).context("Failed to unpack access_vector.")?, + )) + }) + .context("Domain::Grant.")?; + Ok((key_id, key, Some(access_vector.into()))) + } + + // Domain::KeyId. In this case we load the domain and namespace from the + // keyentry database because we need them for access control. + Domain::KeyId => { + let mut stmt = tx + .prepare( + "SELECT domain, namespace FROM persistent.keyentry + WHERE + id = ?;", + ) + .context("Domain::KeyId: prepare statement failed")?; + let mut rows = + stmt.query(params![key.namespace_]).context("Domain::KeyId: query failed.")?; + let (domain, namespace): (DomainType, i64) = + Self::with_rows_extract_one(&mut rows, |row| { + let r = row.map_or_else(|| Err(KsError::Rc(error::Rc::KeyNotFound)), Ok)?; + Ok(( + r.get(0).context("Failed to unpack domain.")?, + r.get(1).context("Failed to unpack namespace.")?, + )) + }) + .context("Domain::KeyId.")?; + let key_id = key.namespace_; + let mut access_key = key; + access_key.domain = domain; + access_key.namespace_ = namespace; + + Ok((key_id, access_key, None)) + } + _ => Err(anyhow!(KsError::sys())), + } + } + + /// Load a key entry by the given key descriptor. + /// It uses the `check_permission` callback to verify if the access is allowed + /// given the key access tuple read from the database using `load_access_tuple`. + /// With `load_bits` the caller may specify which blobs shall be loaded from + /// the blob database. + pub fn load_key_entry( + &mut self, + key: KeyDescriptor, + load_bits: KeyEntryLoadBits, + caller_uid: u32, + check_permission: impl FnOnce(&KeyDescriptor, Option<KeyPermSet>) -> Result<()>, + ) -> Result<KeyEntry> { + let tx = self + .conn + .transaction_with_behavior(TransactionBehavior::Deferred) + .context("In load_key_entry: Failed to initialize transaction.")?; + + // Load the key_id and complete the access control tuple. + let (key_id, access_key_descriptor, access_vector) = + Self::load_access_tuple(&tx, key, caller_uid).context("In load_key_entry:")?; + + // Perform access control. It is vital that we return here if the permission is denied. + // So do not touch that '?' at the end. + check_permission(&access_key_descriptor, access_vector).context("In load_key_entry")?; + + let mut result = + KeyEntry { id: key_id, km_blob: None, cert: None, cert_chain: None, sec_level: 0 }; + + let mut stmt = tx + .prepare( + "SELECT MAX(id), sec_level, subcomponent_type, blob FROM persistent.blobentry + WHERE keyentryid = ? GROUP BY subcomponent_type;", + ) + .context("In load_key_entry: blobentry: prepare statement failed.")?; + + let mut rows = + stmt.query(params![key_id]).context("In load_key_entry: blobentry: query failed.")?; + Self::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()) { + (SubComponentType::KM_BLOB, _) => { + result.sec_level = row.get(1).context("Failed to extract security level.")?; + if load_bits.load_km() { + result.km_blob = Some(row.get(3).context("Failed to extract KM blob.")?); + } + } + (SubComponentType::CERT, true) => { + result.cert = + Some(row.get(3).context("Failed to extract public certificate blob.")?); + } + (SubComponentType::CERT_CHAIN, true) => { + result.cert_chain = + Some(row.get(3).context("Failed to extract certificate chain blob.")?); + } + (SubComponentType::CERT, _) | (SubComponentType::CERT_CHAIN, _) => {} + _ => Err(KsError::sys()).context("Unknown subcomponent type.")?, + } + Ok(()) + }) + .context("In load_key_entry")?; + + // TODO load key parameters. + + Ok(result) + } + + /// 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, + /// it inserts the `grantee_uid`, `key_id`, and `access_vector` into the + /// grant table. The new row will have a randomized id, which is used as + /// grant id in the namespace field of the resulting KeyDescriptor. + pub fn grant( + &mut self, + key: KeyDescriptor, + caller_uid: u32, + grantee_uid: u32, + access_vector: KeyPermSet, + check_permission: impl FnOnce(&KeyDescriptor, &KeyPermSet) -> Result<()>, + ) -> Result<KeyDescriptor> { + let tx = self + .conn + .transaction_with_behavior(TransactionBehavior::Immediate) + .context("In grant: Failed to initialize transaction.")?; + + // Load the key_id and complete the access control tuple. + // We ignore the access vector here because grants cannot be granted. + // The access vector returned here expresses the permissions the + // grantee has if key.domain == Domain::Grant. But this vector + // cannot include the grant permission by design, so there is no way the + // subsequent permission check can pass. + // We could check key.domain == Domain::Grant and fail early. + // But even if we load the access tuple by grant here, the permission + // check denies the attempt to create a grant by grant descriptor. + let (key_id, access_key_descriptor, _) = + Self::load_access_tuple(&tx, key, caller_uid).context("In grant")?; + + // Perform access control. It is vital that we return here if the permission + // was denied. So do not touch that '?' at the end of the line. + // This permission check checks if the caller has the grant permission + // for the given key and in addition to all of the permissions + // expressed in `access_vector`. + check_permission(&access_key_descriptor, &access_vector) + .context("In grant: check_permission failed.")?; + + let grant_id = if let Some(grant_id) = tx + .query_row( + "SELECT id FROM perboot.grant + WHERE keyentryid = ? AND grantee = ?;", + params![key_id, grantee_uid], + |row| row.get(0), + ) + .optional() + .context("In grant: Failed get optional existing grant id.")? + { + tx.execute( + "UPDATE perboot.grant + SET access_vector = ? + WHERE id = ?;", + params![i32::from(access_vector), grant_id], + ) + .context("In grant: Failed to update existing grant.")?; + grant_id + } else { + loop { + let newid: i64 = random(); + let ret = tx.execute( + "INSERT INTO perboot.grant (id, grantee, keyentryid, access_vector) + VALUES (?, ?, ?, ?);", + params![newid, grantee_uid, key_id, i32::from(access_vector)], + ); + match ret { + // If the id already existed, try again. + Err(rusqlite::Error::SqliteFailure( + libsqlite3_sys::Error { + code: libsqlite3_sys::ErrorCode::ConstraintViolation, + extended_code: libsqlite3_sys::SQLITE_CONSTRAINT_UNIQUE, + }, + _, + )) => (), + Err(e) => return Err(e).context("Failed to insert grant."), + Ok(_) => break newid, + } + } + }; + tx.commit().context("In grant: failed to commit transaction.")?; + + Ok(KeyDescriptor { domain: Domain::Grant, namespace_: grant_id, alias: None, blob: None }) + } + + /// This function checks permissions like `grant` and `load_key_entry` + /// before removing a grant from the grant table. + pub fn ungrant( + &mut self, + key: KeyDescriptor, + caller_uid: u32, + grantee_uid: u32, + check_permission: impl FnOnce(&KeyDescriptor) -> Result<()>, + ) -> Result<()> { + let tx = self + .conn + .transaction_with_behavior(TransactionBehavior::Immediate) + .context("In ungrant: Failed to initialize transaction.")?; + + // Load the key_id and complete the access control tuple. + // We ignore the access vector here because grants cannot be granted. + let (key_id, access_key_descriptor, _) = + Self::load_access_tuple(&tx, key, caller_uid).context("In ungrant.")?; + + // Perform access control. We must return here if the permission + // was denied. So do not touch the '?' at the end of this line. + check_permission(&access_key_descriptor).context("In grant: check_permission failed.")?; + + tx.execute( + "DELETE FROM perboot.grant + WHERE keyentryid = ? AND grantee = ?;", + params![key_id, grantee_uid], + ) + .context("Failed to delete grant.")?; + + tx.commit().context("In ungrant: failed to commit transaction.")?; + + Ok(()) + } + + // 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(()), + } + } } } @@ -161,6 +712,9 @@ impl KeystoreDB { mod tests { use super::*; + use crate::key_perm_set; + use crate::permission::{KeyPerm, KeyPermSet}; + use rusqlite::NO_PARAMS; use std::cell::RefCell; static PERSISTENT_TEST_SQL: &str = "/data/local/tmp/persistent.sqlite"; @@ -203,9 +757,17 @@ mod tests { .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(), 2); - assert_eq!(tables[0], "keyentry"); - assert_eq!(tables[1], "keyparameter"); + assert_eq!(tables.len(), 3); + assert_eq!(tables[0], "blobentry"); + assert_eq!(tables[1], "keyentry"); + assert_eq!(tables[2], "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(()) } @@ -328,6 +890,303 @@ mod tests { Ok(()) } + #[test] + fn test_grant_ungrant() -> Result<()> { + const CALLER_UID: u32 = 15; + const GRANTEE_UID: u32 = 12; + const SELINUX_NAMESPACE: i64 = 7; + + let mut db = new_test_db()?; + db.conn.execute( + "INSERT INTO persistent.keyentry (id, creation_date, domain, namespace, alias) + VALUES (1, '1980', 0, 15, 'key'), (2, '1980', 2, 7, 'yek');", + NO_PARAMS, + )?; + let app_key = KeyDescriptor { + domain: super::Domain::App, + namespace_: 0, + alias: Some("key".to_string()), + blob: None, + }; + const PVEC1: KeyPermSet = key_perm_set![KeyPerm::use_(), KeyPerm::get_info()]; + const PVEC2: KeyPermSet = key_perm_set![KeyPerm::use_()]; + + // Reset totally predictable random number generator in case we + // are not the first test running on this thread. + reset_random(); + let next_random = 0i64; + + let app_granted_key = + db.grant(app_key.clone(), CALLER_UID, GRANTEE_UID, PVEC1, |k, a| { + assert_eq!(*a, PVEC1); + assert_eq!( + *k, + KeyDescriptor { + domain: super::Domain::App, + // namespace must be set to the caller_uid. + namespace_: CALLER_UID as i64, + alias: Some("key".to_string()), + blob: None, + } + ); + Ok(()) + })?; + + assert_eq!( + app_granted_key, + KeyDescriptor { + domain: super::Domain::Grant, + // The grantid is next_random due to the mock random number generator. + namespace_: next_random, + alias: None, + blob: None, + } + ); + + let selinux_key = KeyDescriptor { + domain: super::Domain::SELinux, + namespace_: SELINUX_NAMESPACE, + alias: Some("yek".to_string()), + blob: None, + }; + + let selinux_granted_key = + db.grant(selinux_key.clone(), CALLER_UID, 12, PVEC1, |k, a| { + assert_eq!(*a, PVEC1); + assert_eq!( + *k, + KeyDescriptor { + domain: super::Domain::SELinux, + // namespace must be the supplied SELinux + // namespace. + namespace_: SELINUX_NAMESPACE, + alias: Some("yek".to_string()), + blob: None, + } + ); + Ok(()) + })?; + + assert_eq!( + selinux_granted_key, + KeyDescriptor { + domain: super::Domain::Grant, + // The grantid is next_random + 1 due to the mock random number generator. + namespace_: next_random + 1, + alias: None, + blob: None, + } + ); + + // This should update the existing grant with PVEC2. + let selinux_granted_key = + db.grant(selinux_key.clone(), CALLER_UID, 12, PVEC2, |k, a| { + assert_eq!(*a, PVEC2); + assert_eq!( + *k, + KeyDescriptor { + domain: super::Domain::SELinux, + // namespace must be the supplied SELinux + // namespace. + namespace_: SELINUX_NAMESPACE, + alias: Some("yek".to_string()), + blob: None, + } + ); + Ok(()) + })?; + + assert_eq!( + selinux_granted_key, + KeyDescriptor { + domain: super::Domain::Grant, + // Same grant id as before. The entry was only updated. + namespace_: next_random + 1, + alias: None, + blob: None, + } + ); + + { + // Limiting scope of stmt, because it borrows db. + let mut stmt = db + .conn + .prepare("SELECT id, grantee, keyentryid, access_vector FROM perboot.grant;")?; + let mut rows = stmt.query_map::<(i64, u32, i64, i32), _, _>(NO_PARAMS, |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) + })?; + + let r = rows.next().unwrap().unwrap(); + assert_eq!(r, (next_random, GRANTEE_UID, 1, 516)); + let r = rows.next().unwrap().unwrap(); + assert_eq!(r, (next_random + 1, GRANTEE_UID, 2, 512)); + assert!(rows.next().is_none()); + } + + debug_dump_keyentry_table(&mut db)?; + println!("app_key {:?}", app_key); + println!("selinux_key {:?}", selinux_key); + + db.ungrant(app_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?; + db.ungrant(selinux_key, CALLER_UID, GRANTEE_UID, |_| Ok(()))?; + + Ok(()) + } + + static TEST_KM_BLOB: &[u8] = b"my test blob"; + static TEST_CERT_BLOB: &[u8] = b"my test cert"; + static TEST_CERT_CHAIN_BLOB: &[u8] = b"my test cert_chain"; + + #[test] + fn test_insert_blob() -> Result<()> { + let mut db = new_test_db()?; + db.insert_blob(1, SubComponentType::KM_BLOB, TEST_KM_BLOB, 1)?; + db.insert_blob(1, SubComponentType::CERT, TEST_CERT_BLOB, 2)?; + db.insert_blob(1, SubComponentType::CERT_CHAIN, TEST_CERT_CHAIN_BLOB, 3)?; + + let mut stmt = db.conn.prepare( + "SELECT subcomponent_type, keyentryid, blob, sec_level FROM persistent.blobentry + ORDER BY sec_level ASC;", + )?; + let mut rows = stmt + .query_map::<(SubComponentType, i64, Vec<u8>, i64), _, _>(NO_PARAMS, |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) + })?; + let r = rows.next().unwrap().unwrap(); + assert_eq!(r, (SubComponentType::KM_BLOB, 1, TEST_KM_BLOB.to_vec(), 1)); + let r = rows.next().unwrap().unwrap(); + assert_eq!(r, (SubComponentType::CERT, 1, TEST_CERT_BLOB.to_vec(), 2)); + let r = rows.next().unwrap().unwrap(); + assert_eq!(r, (SubComponentType::CERT_CHAIN, 1, TEST_CERT_CHAIN_BLOB.to_vec(), 3)); + + Ok(()) + } + + static TEST_ALIAS: &str = "my super duper key"; + + #[test] + fn test_insert_and_load_full_keyentry_domain_app() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::App, 1, TEST_ALIAS) + .context("test_insert_and_load_full_keyentry_domain_app")?; + let key_entry = db.load_key_entry( + KeyDescriptor { + domain: Domain::App, + namespace_: 0, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyEntryLoadBits::BOTH, + 1, + |_k, _av| Ok(()), + )?; + assert_eq!( + key_entry, + KeyEntry { + id: key_id, + km_blob: Some(TEST_KM_BLOB.to_vec()), + cert: Some(TEST_CERT_BLOB.to_vec()), + cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()), + sec_level: 1, + } + ); + Ok(()) + } + + #[test] + fn test_insert_and_load_full_keyentry_domain_selinux() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::SELinux, 1, TEST_ALIAS) + .context("test_insert_and_load_full_keyentry_domain_selinux")?; + let key_entry = db.load_key_entry( + KeyDescriptor { + domain: Domain::SELinux, + namespace_: 1, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + KeyEntryLoadBits::BOTH, + 1, + |_k, _av| Ok(()), + )?; + assert_eq!( + key_entry, + KeyEntry { + id: key_id, + km_blob: Some(TEST_KM_BLOB.to_vec()), + cert: Some(TEST_CERT_BLOB.to_vec()), + cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()), + sec_level: 1, + } + ); + Ok(()) + } + + #[test] + fn test_insert_and_load_full_keyentry_domain_key_id() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::SELinux, 1, TEST_ALIAS) + .context("test_insert_and_load_full_keyentry_domain_key_id")?; + let key_entry = db.load_key_entry( + KeyDescriptor { domain: Domain::KeyId, namespace_: key_id, alias: None, blob: None }, + KeyEntryLoadBits::BOTH, + 1, + |_k, _av| Ok(()), + )?; + assert_eq!( + key_entry, + KeyEntry { + id: key_id, + km_blob: Some(TEST_KM_BLOB.to_vec()), + cert: Some(TEST_CERT_BLOB.to_vec()), + cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()), + sec_level: 1, + } + ); + + Ok(()) + } + + #[test] + fn test_insert_and_load_full_keyentry_from_grant() -> Result<()> { + let mut db = new_test_db()?; + let key_id = make_test_key_entry(&mut db, Domain::App, 1, TEST_ALIAS) + .context("test_insert_and_load_full_keyentry_from_grant")?; + + let granted_key = db.grant( + KeyDescriptor { + domain: Domain::App, + namespace_: 0, + alias: Some(TEST_ALIAS.to_string()), + blob: None, + }, + 1, + 2, + key_perm_set![KeyPerm::use_()], + |_k, _av| Ok(()), + )?; + + debug_dump_grant_table(&mut db)?; + + let key_entry = db.load_key_entry(granted_key, KeyEntryLoadBits::BOTH, 2, |k, av| { + assert_eq!(Domain::Grant, k.domain); + assert!(av.unwrap().includes(KeyPerm::use_())); + Ok(()) + })?; + + assert_eq!( + key_entry, + KeyEntry { + id: key_id, + km_blob: Some(TEST_KM_BLOB.to_vec()), + cert: Some(TEST_CERT_BLOB.to_vec()), + cert_chain: Some(TEST_CERT_CHAIN_BLOB.to_vec()), + sec_level: 1, + } + ); + Ok(()) + } + // Helpers // Checks that the given result is an error containing the given string. @@ -347,7 +1206,7 @@ mod tests { #[derive(Debug, PartialEq)] #[allow(dead_code)] struct KeyEntryRow { - id: u32, + id: i64, creation_date: String, domain: Option<DomainType>, namespace: Option<i64>, @@ -370,6 +1229,54 @@ mod tests { .collect::<Result<Vec<_>>>() } + fn make_test_key_entry( + db: &mut KeystoreDB, + domain: DomainType, + namespace: i64, + alias: &str, + ) -> Result<i64> { + let key_id = db.create_key_entry(domain, namespace)?; + db.insert_blob(key_id, SubComponentType::KM_BLOB, TEST_KM_BLOB, 1)?; + db.insert_blob(key_id, SubComponentType::CERT, TEST_CERT_BLOB, 1)?; + db.insert_blob(key_id, SubComponentType::CERT_CHAIN, TEST_CERT_CHAIN_BLOB, 1)?; + db.rebind_alias(key_id, alias, domain, namespace)?; + Ok(key_id) + } + + fn debug_dump_keyentry_table(db: &mut KeystoreDB) -> Result<()> { + let mut stmt = db.conn.prepare( + "SELECT id, creation_date, domain, namespace, alias FROM persistent.keyentry;", + )?; + let rows = stmt.query_map::<(i64, i64, i32, i64, String), _, _>(NO_PARAMS, |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?, row.get(4)?)) + })?; + + println!("Key entry table rows:"); + for r in rows { + let (id, cdate, domain, namespace, alias) = r.unwrap(); + println!( + " id: {} Creation date: {} Domain: {} Namespace: {} Alias: {}", + id, cdate, domain, namespace, alias + ); + } + Ok(()) + } + + 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 rows = stmt.query_map::<(i64, i64, i64, i64), _, _>(NO_PARAMS, |row| { + Ok((row.get(0)?, row.get(1)?, row.get(2)?, row.get(3)?)) + })?; + + println!("Grant table rows:"); + for r in rows { + let (id, gt, ki, av) = r.unwrap(); + println!(" id: {} grantee: {} key_id: {} access_vector: {}", id, gt, ki, av); + } + 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 { @@ -389,6 +1296,12 @@ mod tests { static RANDOM_COUNTER: RefCell<i64> = RefCell::new(0); } + fn reset_random() { + RANDOM_COUNTER.with(|counter| { + *counter.borrow_mut() = 0; + }) + } + pub fn random() -> i64 { RANDOM_COUNTER.with(|counter| { let result = *counter.borrow() / 2; |