blob: b1cde6e3fd4fc1f2d4c442b3ad8920ddd71dcfe4 [file] [log] [blame]
Joel Galenson26f4d012020-07-17 14:57:21 -07001// Copyright 2020, The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15// TODO: Once this is stable, remove this and document everything public.
16#![allow(missing_docs)]
17
Joel Galenson0891bc12020-07-20 10:37:03 -070018use crate::error::Error as KsError;
Joel Galenson26f4d012020-07-17 14:57:21 -070019use anyhow::{Context, Result};
Janis Danisevskis60400fe2020-08-26 15:24:42 -070020
21use android_security_keystore2::aidl::android::security::keystore2::{
22 Domain, Domain::Domain as DomainType,
23};
24
Joel Galenson0891bc12020-07-20 10:37:03 -070025#[cfg(not(test))]
26use rand::prelude::random;
Joel Galenson33c04ad2020-08-03 11:04:38 -070027use rusqlite::{params, Connection, TransactionBehavior, NO_PARAMS};
Janis Danisevskis4df44f42020-08-26 14:40:03 -070028use std::sync::Once;
Joel Galenson0891bc12020-07-20 10:37:03 -070029#[cfg(test)]
30use tests::random;
Joel Galenson26f4d012020-07-17 14:57:21 -070031
Janis Danisevskis4df44f42020-08-26 14:40:03 -070032static INIT_TABLES: Once = Once::new();
33
Joel Galenson26f4d012020-07-17 14:57:21 -070034pub struct KeystoreDB {
Joel Galenson26f4d012020-07-17 14:57:21 -070035 conn: Connection,
36}
37
38impl KeystoreDB {
Janis Danisevskis4df44f42020-08-26 14:40:03 -070039 pub fn new() -> Result<Self> {
40 let conn = Self::make_connection("file:persistent.sqlite", "file:perboot.sqlite")?;
41
42 INIT_TABLES.call_once(|| Self::init_tables(&conn).expect("Failed to initialize tables."));
43 Ok(Self { conn })
Joel Galenson2aab4432020-07-22 15:27:57 -070044 }
45
Janis Danisevskis4df44f42020-08-26 14:40:03 -070046 fn init_tables(conn: &Connection) -> Result<()> {
47 conn.execute(
48 "CREATE TABLE IF NOT EXISTS persistent.keyentry (
Joel Galenson0891bc12020-07-20 10:37:03 -070049 id INTEGER UNIQUE,
50 creation_date DATETIME,
51 domain INTEGER,
52 namespace INTEGER,
53 alias TEXT);",
Janis Danisevskis4df44f42020-08-26 14:40:03 -070054 NO_PARAMS,
55 )
56 .context("Failed to initialize \"keyentry\" table.")?;
57
58 conn.execute(
59 "CREATE TABLE IF NOT EXISTS persistent.keyparameter (
Hasini Gunasingheaf993662020-07-24 18:40:20 +000060 keyentryid INTEGER,
61 tag INTEGER,
62 data ANY,
63 security_level INTEGER);",
Janis Danisevskis4df44f42020-08-26 14:40:03 -070064 NO_PARAMS,
65 )
66 .context("Failed to initialize \"keyparameter\" table.")?;
67
Joel Galenson0891bc12020-07-20 10:37:03 -070068 Ok(())
69 }
70
Janis Danisevskis4df44f42020-08-26 14:40:03 -070071 fn make_connection(persistent_file: &str, perboot_file: &str) -> Result<Connection> {
72 let conn =
73 Connection::open_in_memory().context("Failed to initialize SQLite connection.")?;
74
75 conn.execute("ATTACH DATABASE ? as persistent;", params![persistent_file])
76 .context("Failed to attach database persistent.")?;
77 conn.execute("ATTACH DATABASE ? as perboot;", params![perboot_file])
78 .context("Failed to attach database perboot.")?;
79
80 Ok(conn)
81 }
82
Janis Danisevskis60400fe2020-08-26 15:24:42 -070083 pub fn create_key_entry(&self, domain: DomainType, namespace: i64) -> Result<i64> {
Joel Galenson0891bc12020-07-20 10:37:03 -070084 match domain {
Janis Danisevskis60400fe2020-08-26 15:24:42 -070085 Domain::App | Domain::SELinux => {}
Joel Galenson0891bc12020-07-20 10:37:03 -070086 _ => {
87 return Err(KsError::sys())
88 .context(format!("Domain {:?} must be either App or SELinux.", domain));
89 }
90 }
91 // Loop until we get a unique id.
92 loop {
93 let newid: i64 = random();
94 let ret = self.conn.execute(
Joel Galenson2aab4432020-07-22 15:27:57 -070095 "INSERT into persistent.keyentry (id, creation_date, domain, namespace, alias)
Joel Galenson0891bc12020-07-20 10:37:03 -070096 VALUES(?, datetime('now'), ?, ?, NULL);",
97 params![newid, domain as i64, namespace],
98 );
99 match ret {
100 // If the id already existed, try again.
101 Err(rusqlite::Error::SqliteFailure(
102 libsqlite3_sys::Error {
103 code: libsqlite3_sys::ErrorCode::ConstraintViolation,
104 extended_code: libsqlite3_sys::SQLITE_CONSTRAINT_UNIQUE,
105 },
106 _,
107 )) => (),
108 _ => return Ok(newid),
109 }
110 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700111 }
Joel Galenson33c04ad2020-08-03 11:04:38 -0700112
113 pub fn rebind_alias(
114 &mut self,
115 newid: u32,
116 alias: &str,
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700117 domain: DomainType,
Joel Galenson33c04ad2020-08-03 11:04:38 -0700118 namespace: i64,
119 ) -> Result<()> {
120 match domain {
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700121 Domain::App | Domain::SELinux => {}
Joel Galenson33c04ad2020-08-03 11:04:38 -0700122 _ => {
123 return Err(KsError::sys())
124 .context(format!("Domain {:?} must be either App or SELinux.", domain));
125 }
126 }
127 let tx = self
128 .conn
129 .transaction_with_behavior(TransactionBehavior::Immediate)
130 .context("Failed to initialize transaction.")?;
131 tx.execute(
132 "UPDATE persistent.keyentry
133 SET alias = NULL, domain = NULL, namespace = NULL
134 WHERE alias = ? AND domain = ? AND namespace = ?;",
135 params![alias, domain as i64, namespace],
136 )
137 .context("Failed to rebind existing entry.")?;
138 let result = tx
139 .execute(
140 "UPDATE persistent.keyentry
141 SET alias = ?
142 WHERE id = ? AND domain = ? AND namespace = ?;",
143 params![alias, newid, domain as i64, namespace],
144 )
145 .context("Failed to set alias.")?;
146 if result != 1 {
147 // Note that this explicit rollback is not required, as
148 // the transaction should rollback if we do not commit it.
149 // We leave it here for readability.
150 tx.rollback().context("Failed to rollback a failed transaction.")?;
151 return Err(KsError::sys()).context(format!(
152 "Expected to update a single entry but instead updated {}.",
153 result
154 ));
155 }
156 tx.commit().context("Failed to commit transaction.")
157 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700158}
159
160#[cfg(test)]
161mod tests {
162
163 use super::*;
Joel Galenson0891bc12020-07-20 10:37:03 -0700164 use std::cell::RefCell;
165
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700166 static PERSISTENT_TEST_SQL: &str = "/data/local/tmp/persistent.sqlite";
167 static PERBOOT_TEST_SQL: &str = "/data/local/tmp/perboot.sqlite";
168
169 fn new_test_db() -> Result<KeystoreDB> {
170 let conn = KeystoreDB::make_connection("file::memory:", "file::memory:")?;
171
172 KeystoreDB::init_tables(&conn).context("Failed to initialize tables.")?;
173 Ok(KeystoreDB { conn })
174 }
175
176 fn new_test_db_with_persistent_file() -> Result<KeystoreDB> {
177 let conn = KeystoreDB::make_connection(PERSISTENT_TEST_SQL, PERBOOT_TEST_SQL)?;
178
179 KeystoreDB::init_tables(&conn).context("Failed to initialize tables.")?;
180 Ok(KeystoreDB { conn })
181 }
182
Joel Galenson0891bc12020-07-20 10:37:03 -0700183 // Ensure that we're using the "injected" random function, not the real one.
184 #[test]
185 fn test_mocked_random() {
186 let rand1 = random();
187 let rand2 = random();
188 let rand3 = random();
189 if rand1 == rand2 {
190 assert_eq!(rand2 + 1, rand3);
191 } else {
192 assert_eq!(rand1 + 1, rand2);
193 assert_eq!(rand2, rand3);
194 }
195 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700196
Joel Galenson26f4d012020-07-17 14:57:21 -0700197 // Test that we have the correct tables.
198 #[test]
199 fn test_tables() -> Result<()> {
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700200 let db = new_test_db()?;
Joel Galenson26f4d012020-07-17 14:57:21 -0700201 let tables = db
202 .conn
Joel Galenson2aab4432020-07-22 15:27:57 -0700203 .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
Joel Galenson26f4d012020-07-17 14:57:21 -0700204 .query_map(params![], |row| row.get(0))?
205 .collect::<rusqlite::Result<Vec<String>>>()?;
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000206 assert_eq!(tables.len(), 2);
Joel Galenson0891bc12020-07-20 10:37:03 -0700207 assert_eq!(tables[0], "keyentry");
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000208 assert_eq!(tables[1], "keyparameter");
Joel Galenson26f4d012020-07-17 14:57:21 -0700209 Ok(())
210 }
Joel Galenson0891bc12020-07-20 10:37:03 -0700211
212 #[test]
Joel Galenson2aab4432020-07-22 15:27:57 -0700213 fn test_no_persistence_for_tests() -> Result<()> {
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700214 let db = new_test_db()?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700215
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700216 db.create_key_entry(Domain::App, 100)?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700217 let entries = get_keyentry(&db)?;
218 assert_eq!(entries.len(), 1);
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700219 let db = new_test_db()?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700220
221 let entries = get_keyentry(&db)?;
222 assert_eq!(entries.len(), 0);
223 Ok(())
224 }
225
226 #[test]
227 fn test_persistence_for_files() -> Result<()> {
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700228 let _file_guard_persistent = TempFile { filename: PERSISTENT_TEST_SQL };
229 let _file_guard_perboot = TempFile { filename: PERBOOT_TEST_SQL };
230 let db = new_test_db_with_persistent_file()?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700231
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700232 db.create_key_entry(Domain::App, 100)?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700233 let entries = get_keyentry(&db)?;
234 assert_eq!(entries.len(), 1);
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700235 let db = new_test_db_with_persistent_file()?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700236
237 let entries_new = get_keyentry(&db)?;
238 assert_eq!(entries, entries_new);
239 Ok(())
240 }
241
242 #[test]
Joel Galenson0891bc12020-07-20 10:37:03 -0700243 fn test_create_key_entry() -> Result<()> {
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700244 fn extractor(ke: &KeyEntryRow) -> (DomainType, i64, Option<&str>) {
Joel Galenson0891bc12020-07-20 10:37:03 -0700245 (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref())
246 }
247
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700248 let db = new_test_db()?;
Joel Galenson0891bc12020-07-20 10:37:03 -0700249
250 db.create_key_entry(Domain::App, 100)?;
251 db.create_key_entry(Domain::SELinux, 101)?;
252
253 let entries = get_keyentry(&db)?;
254 assert_eq!(entries.len(), 2);
255 assert_eq!(extractor(&entries[0]), (Domain::App, 100, None));
256 assert_eq!(extractor(&entries[1]), (Domain::SELinux, 101, None));
257
258 // Test that we must pass in a valid Domain.
259 check_result_is_error_containing_string(
260 db.create_key_entry(Domain::Grant, 102),
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700261 "Domain 1 must be either App or SELinux.",
Joel Galenson0891bc12020-07-20 10:37:03 -0700262 );
263 check_result_is_error_containing_string(
264 db.create_key_entry(Domain::Blob, 103),
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700265 "Domain 3 must be either App or SELinux.",
Joel Galenson0891bc12020-07-20 10:37:03 -0700266 );
267 check_result_is_error_containing_string(
268 db.create_key_entry(Domain::KeyId, 104),
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700269 "Domain 4 must be either App or SELinux.",
Joel Galenson0891bc12020-07-20 10:37:03 -0700270 );
271
272 Ok(())
273 }
274
Joel Galenson33c04ad2020-08-03 11:04:38 -0700275 #[test]
276 fn test_rebind_alias() -> Result<()> {
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700277 fn extractor(ke: &KeyEntryRow) -> (Option<DomainType>, Option<i64>, Option<&str>) {
Joel Galenson33c04ad2020-08-03 11:04:38 -0700278 (ke.domain, ke.namespace, ke.alias.as_deref())
279 }
280
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700281 let mut db = new_test_db()?;
Joel Galenson33c04ad2020-08-03 11:04:38 -0700282 db.create_key_entry(Domain::App, 42)?;
283 db.create_key_entry(Domain::App, 42)?;
284 let entries = get_keyentry(&db)?;
285 assert_eq!(entries.len(), 2);
286 assert_eq!(extractor(&entries[0]), (Some(Domain::App), Some(42), None));
287 assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), None));
288
289 // Test that the first call to rebind_alias sets the alias.
290 db.rebind_alias(entries[0].id, "foo", Domain::App, 42)?;
291 let entries = get_keyentry(&db)?;
292 assert_eq!(entries.len(), 2);
293 assert_eq!(extractor(&entries[0]), (Some(Domain::App), Some(42), Some("foo")));
294 assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), None));
295
296 // Test that the second call to rebind_alias also empties the old one.
297 db.rebind_alias(entries[1].id, "foo", Domain::App, 42)?;
298 let entries = get_keyentry(&db)?;
299 assert_eq!(entries.len(), 2);
300 assert_eq!(extractor(&entries[0]), (None, None, None));
301 assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), Some("foo")));
302
303 // Test that we must pass in a valid Domain.
304 check_result_is_error_containing_string(
305 db.rebind_alias(0, "foo", Domain::Grant, 42),
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700306 "Domain 1 must be either App or SELinux.",
Joel Galenson33c04ad2020-08-03 11:04:38 -0700307 );
308 check_result_is_error_containing_string(
309 db.rebind_alias(0, "foo", Domain::Blob, 42),
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700310 "Domain 3 must be either App or SELinux.",
Joel Galenson33c04ad2020-08-03 11:04:38 -0700311 );
312 check_result_is_error_containing_string(
313 db.rebind_alias(0, "foo", Domain::KeyId, 42),
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700314 "Domain 4 must be either App or SELinux.",
Joel Galenson33c04ad2020-08-03 11:04:38 -0700315 );
316
317 // Test that we correctly handle setting an alias for something that does not exist.
318 check_result_is_error_containing_string(
319 db.rebind_alias(0, "foo", Domain::SELinux, 42),
320 "Expected to update a single entry but instead updated 0",
321 );
322 // Test that we correctly abort the transaction in this case.
323 let entries = get_keyentry(&db)?;
324 assert_eq!(entries.len(), 2);
325 assert_eq!(extractor(&entries[0]), (None, None, None));
326 assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), Some("foo")));
327
328 Ok(())
329 }
330
Joel Galenson0891bc12020-07-20 10:37:03 -0700331 // Helpers
332
333 // Checks that the given result is an error containing the given string.
334 fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) {
335 let error_str = format!(
336 "{:#?}",
337 result.err().unwrap_or_else(|| panic!("Expected the error: {}", target))
338 );
339 assert!(
340 error_str.contains(target),
341 "The string \"{}\" should contain \"{}\"",
342 error_str,
343 target
344 );
345 }
346
Joel Galenson2aab4432020-07-22 15:27:57 -0700347 #[derive(Debug, PartialEq)]
Joel Galenson0891bc12020-07-20 10:37:03 -0700348 #[allow(dead_code)]
349 struct KeyEntryRow {
350 id: u32,
351 creation_date: String,
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700352 domain: Option<DomainType>,
Joel Galenson0891bc12020-07-20 10:37:03 -0700353 namespace: Option<i64>,
354 alias: Option<String>,
355 }
356
357 fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> {
358 db.conn
Joel Galenson2aab4432020-07-22 15:27:57 -0700359 .prepare("SELECT * FROM persistent.keyentry;")?
Joel Galenson0891bc12020-07-20 10:37:03 -0700360 .query_map(NO_PARAMS, |row| {
Joel Galenson0891bc12020-07-20 10:37:03 -0700361 Ok(KeyEntryRow {
362 id: row.get(0)?,
363 creation_date: row.get(1)?,
Janis Danisevskis60400fe2020-08-26 15:24:42 -0700364 domain: row.get(2)?,
Joel Galenson0891bc12020-07-20 10:37:03 -0700365 namespace: row.get(3)?,
366 alias: row.get(4)?,
367 })
368 })?
369 .map(|r| r.context("Could not read keyentry row."))
370 .collect::<Result<Vec<_>>>()
371 }
372
Joel Galenson2aab4432020-07-22 15:27:57 -0700373 // A class that deletes a file when it is dropped.
374 // TODO: If we ever add a crate that does this, we can use it instead.
375 struct TempFile {
376 filename: &'static str,
377 }
378
379 impl Drop for TempFile {
380 fn drop(&mut self) {
381 std::fs::remove_file(self.filename).expect("Cannot delete temporary file");
382 }
383 }
384
Joel Galenson0891bc12020-07-20 10:37:03 -0700385 // Use a custom random number generator that repeats each number once.
386 // This allows us to test repeated elements.
387
388 thread_local! {
389 static RANDOM_COUNTER: RefCell<i64> = RefCell::new(0);
390 }
391
392 pub fn random() -> i64 {
393 RANDOM_COUNTER.with(|counter| {
394 let result = *counter.borrow() / 2;
395 *counter.borrow_mut() += 1;
396 result
397 })
398 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700399}