blob: 2ff5cf00c7e916c2e2b86e50ec16e5ca06f28020 [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};
Joel Galenson0891bc12020-07-20 10:37:03 -070020use keystore_aidl_generated as aidl;
21#[cfg(not(test))]
22use rand::prelude::random;
Joel Galenson33c04ad2020-08-03 11:04:38 -070023use rusqlite::{params, Connection, TransactionBehavior, NO_PARAMS};
Janis Danisevskis4df44f42020-08-26 14:40:03 -070024use std::sync::Once;
Joel Galenson0891bc12020-07-20 10:37:03 -070025#[cfg(test)]
26use tests::random;
Joel Galenson26f4d012020-07-17 14:57:21 -070027
Janis Danisevskis4df44f42020-08-26 14:40:03 -070028static INIT_TABLES: Once = Once::new();
29
Joel Galenson26f4d012020-07-17 14:57:21 -070030pub struct KeystoreDB {
Joel Galenson26f4d012020-07-17 14:57:21 -070031 conn: Connection,
32}
33
34impl KeystoreDB {
Janis Danisevskis4df44f42020-08-26 14:40:03 -070035 pub fn new() -> Result<Self> {
36 let conn = Self::make_connection("file:persistent.sqlite", "file:perboot.sqlite")?;
37
38 INIT_TABLES.call_once(|| Self::init_tables(&conn).expect("Failed to initialize tables."));
39 Ok(Self { conn })
Joel Galenson2aab4432020-07-22 15:27:57 -070040 }
41
Janis Danisevskis4df44f42020-08-26 14:40:03 -070042 fn init_tables(conn: &Connection) -> Result<()> {
43 conn.execute(
44 "CREATE TABLE IF NOT EXISTS persistent.keyentry (
Joel Galenson0891bc12020-07-20 10:37:03 -070045 id INTEGER UNIQUE,
46 creation_date DATETIME,
47 domain INTEGER,
48 namespace INTEGER,
49 alias TEXT);",
Janis Danisevskis4df44f42020-08-26 14:40:03 -070050 NO_PARAMS,
51 )
52 .context("Failed to initialize \"keyentry\" table.")?;
53
54 conn.execute(
55 "CREATE TABLE IF NOT EXISTS persistent.keyparameter (
Hasini Gunasingheaf993662020-07-24 18:40:20 +000056 keyentryid INTEGER,
57 tag INTEGER,
58 data ANY,
59 security_level INTEGER);",
Janis Danisevskis4df44f42020-08-26 14:40:03 -070060 NO_PARAMS,
61 )
62 .context("Failed to initialize \"keyparameter\" table.")?;
63
Joel Galenson0891bc12020-07-20 10:37:03 -070064 Ok(())
65 }
66
Janis Danisevskis4df44f42020-08-26 14:40:03 -070067 fn make_connection(persistent_file: &str, perboot_file: &str) -> Result<Connection> {
68 let conn =
69 Connection::open_in_memory().context("Failed to initialize SQLite connection.")?;
70
71 conn.execute("ATTACH DATABASE ? as persistent;", params![persistent_file])
72 .context("Failed to attach database persistent.")?;
73 conn.execute("ATTACH DATABASE ? as perboot;", params![perboot_file])
74 .context("Failed to attach database perboot.")?;
75
76 Ok(conn)
77 }
78
Joel Galenson0891bc12020-07-20 10:37:03 -070079 pub fn create_key_entry(&self, domain: aidl::Domain, namespace: i64) -> Result<i64> {
80 match domain {
81 aidl::Domain::App | aidl::Domain::SELinux => {}
82 _ => {
83 return Err(KsError::sys())
84 .context(format!("Domain {:?} must be either App or SELinux.", domain));
85 }
86 }
87 // Loop until we get a unique id.
88 loop {
89 let newid: i64 = random();
90 let ret = self.conn.execute(
Joel Galenson2aab4432020-07-22 15:27:57 -070091 "INSERT into persistent.keyentry (id, creation_date, domain, namespace, alias)
Joel Galenson0891bc12020-07-20 10:37:03 -070092 VALUES(?, datetime('now'), ?, ?, NULL);",
93 params![newid, domain as i64, namespace],
94 );
95 match ret {
96 // If the id already existed, try again.
97 Err(rusqlite::Error::SqliteFailure(
98 libsqlite3_sys::Error {
99 code: libsqlite3_sys::ErrorCode::ConstraintViolation,
100 extended_code: libsqlite3_sys::SQLITE_CONSTRAINT_UNIQUE,
101 },
102 _,
103 )) => (),
104 _ => return Ok(newid),
105 }
106 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700107 }
Joel Galenson33c04ad2020-08-03 11:04:38 -0700108
109 pub fn rebind_alias(
110 &mut self,
111 newid: u32,
112 alias: &str,
113 domain: aidl::Domain,
114 namespace: i64,
115 ) -> Result<()> {
116 match domain {
117 aidl::Domain::App | aidl::Domain::SELinux => {}
118 _ => {
119 return Err(KsError::sys())
120 .context(format!("Domain {:?} must be either App or SELinux.", domain));
121 }
122 }
123 let tx = self
124 .conn
125 .transaction_with_behavior(TransactionBehavior::Immediate)
126 .context("Failed to initialize transaction.")?;
127 tx.execute(
128 "UPDATE persistent.keyentry
129 SET alias = NULL, domain = NULL, namespace = NULL
130 WHERE alias = ? AND domain = ? AND namespace = ?;",
131 params![alias, domain as i64, namespace],
132 )
133 .context("Failed to rebind existing entry.")?;
134 let result = tx
135 .execute(
136 "UPDATE persistent.keyentry
137 SET alias = ?
138 WHERE id = ? AND domain = ? AND namespace = ?;",
139 params![alias, newid, domain as i64, namespace],
140 )
141 .context("Failed to set alias.")?;
142 if result != 1 {
143 // Note that this explicit rollback is not required, as
144 // the transaction should rollback if we do not commit it.
145 // We leave it here for readability.
146 tx.rollback().context("Failed to rollback a failed transaction.")?;
147 return Err(KsError::sys()).context(format!(
148 "Expected to update a single entry but instead updated {}.",
149 result
150 ));
151 }
152 tx.commit().context("Failed to commit transaction.")
153 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700154}
155
156#[cfg(test)]
157mod tests {
158
159 use super::*;
Joel Galenson0891bc12020-07-20 10:37:03 -0700160 use std::cell::RefCell;
161
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700162 static PERSISTENT_TEST_SQL: &str = "/data/local/tmp/persistent.sqlite";
163 static PERBOOT_TEST_SQL: &str = "/data/local/tmp/perboot.sqlite";
164
165 fn new_test_db() -> Result<KeystoreDB> {
166 let conn = KeystoreDB::make_connection("file::memory:", "file::memory:")?;
167
168 KeystoreDB::init_tables(&conn).context("Failed to initialize tables.")?;
169 Ok(KeystoreDB { conn })
170 }
171
172 fn new_test_db_with_persistent_file() -> Result<KeystoreDB> {
173 let conn = KeystoreDB::make_connection(PERSISTENT_TEST_SQL, PERBOOT_TEST_SQL)?;
174
175 KeystoreDB::init_tables(&conn).context("Failed to initialize tables.")?;
176 Ok(KeystoreDB { conn })
177 }
178
Joel Galenson0891bc12020-07-20 10:37:03 -0700179 // Ensure that we're using the "injected" random function, not the real one.
180 #[test]
181 fn test_mocked_random() {
182 let rand1 = random();
183 let rand2 = random();
184 let rand3 = random();
185 if rand1 == rand2 {
186 assert_eq!(rand2 + 1, rand3);
187 } else {
188 assert_eq!(rand1 + 1, rand2);
189 assert_eq!(rand2, rand3);
190 }
191 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700192
Joel Galenson26f4d012020-07-17 14:57:21 -0700193 // Test that we have the correct tables.
194 #[test]
195 fn test_tables() -> Result<()> {
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700196 let db = new_test_db()?;
Joel Galenson26f4d012020-07-17 14:57:21 -0700197 let tables = db
198 .conn
Joel Galenson2aab4432020-07-22 15:27:57 -0700199 .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
Joel Galenson26f4d012020-07-17 14:57:21 -0700200 .query_map(params![], |row| row.get(0))?
201 .collect::<rusqlite::Result<Vec<String>>>()?;
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000202 assert_eq!(tables.len(), 2);
Joel Galenson0891bc12020-07-20 10:37:03 -0700203 assert_eq!(tables[0], "keyentry");
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000204 assert_eq!(tables[1], "keyparameter");
Joel Galenson26f4d012020-07-17 14:57:21 -0700205 Ok(())
206 }
Joel Galenson0891bc12020-07-20 10:37:03 -0700207
208 #[test]
Joel Galenson2aab4432020-07-22 15:27:57 -0700209 fn test_no_persistence_for_tests() -> Result<()> {
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700210 let db = new_test_db()?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700211
212 db.create_key_entry(aidl::Domain::App, 100)?;
213 let entries = get_keyentry(&db)?;
214 assert_eq!(entries.len(), 1);
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700215 let db = new_test_db()?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700216
217 let entries = get_keyentry(&db)?;
218 assert_eq!(entries.len(), 0);
219 Ok(())
220 }
221
222 #[test]
223 fn test_persistence_for_files() -> Result<()> {
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700224 let _file_guard_persistent = TempFile { filename: PERSISTENT_TEST_SQL };
225 let _file_guard_perboot = TempFile { filename: PERBOOT_TEST_SQL };
226 let db = new_test_db_with_persistent_file()?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700227
228 db.create_key_entry(aidl::Domain::App, 100)?;
229 let entries = get_keyentry(&db)?;
230 assert_eq!(entries.len(), 1);
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700231 let db = new_test_db_with_persistent_file()?;
Joel Galenson2aab4432020-07-22 15:27:57 -0700232
233 let entries_new = get_keyentry(&db)?;
234 assert_eq!(entries, entries_new);
235 Ok(())
236 }
237
238 #[test]
Joel Galenson0891bc12020-07-20 10:37:03 -0700239 fn test_create_key_entry() -> Result<()> {
240 use aidl::Domain;
241
242 fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>) {
243 (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref())
244 }
245
Janis Danisevskis4df44f42020-08-26 14:40:03 -0700246 let db = new_test_db()?;
Joel Galenson0891bc12020-07-20 10:37:03 -0700247
248 db.create_key_entry(Domain::App, 100)?;
249 db.create_key_entry(Domain::SELinux, 101)?;
250
251 let entries = get_keyentry(&db)?;
252 assert_eq!(entries.len(), 2);
253 assert_eq!(extractor(&entries[0]), (Domain::App, 100, None));
254 assert_eq!(extractor(&entries[1]), (Domain::SELinux, 101, None));
255
256 // Test that we must pass in a valid Domain.
257 check_result_is_error_containing_string(
258 db.create_key_entry(Domain::Grant, 102),
259 "Domain Grant must be either App or SELinux.",
260 );
261 check_result_is_error_containing_string(
262 db.create_key_entry(Domain::Blob, 103),
263 "Domain Blob must be either App or SELinux.",
264 );
265 check_result_is_error_containing_string(
266 db.create_key_entry(Domain::KeyId, 104),
267 "Domain KeyId must be either App or SELinux.",
268 );
269
270 Ok(())
271 }
272
Joel Galenson33c04ad2020-08-03 11:04:38 -0700273 #[test]
274 fn test_rebind_alias() -> Result<()> {
275 use aidl::Domain;
276
277 fn extractor(ke: &KeyEntryRow) -> (Option<Domain>, Option<i64>, Option<&str>) {
278 (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),
306 "Domain Grant must be either App or SELinux.",
307 );
308 check_result_is_error_containing_string(
309 db.rebind_alias(0, "foo", Domain::Blob, 42),
310 "Domain Blob must be either App or SELinux.",
311 );
312 check_result_is_error_containing_string(
313 db.rebind_alias(0, "foo", Domain::KeyId, 42),
314 "Domain KeyId must be either App or SELinux.",
315 );
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,
352 domain: Option<aidl::Domain>,
353 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| {
361 let domain: Option<i32> = row.get(2)?;
362 Ok(KeyEntryRow {
363 id: row.get(0)?,
364 creation_date: row.get(1)?,
365 domain: domain.map(domain_from_integer),
366 namespace: row.get(3)?,
367 alias: row.get(4)?,
368 })
369 })?
370 .map(|r| r.context("Could not read keyentry row."))
371 .collect::<Result<Vec<_>>>()
372 }
373
374 // TODO: Replace this with num_derive.
375 fn domain_from_integer(value: i32) -> aidl::Domain {
376 use aidl::Domain;
377 match value {
378 x if Domain::App as i32 == x => Domain::App,
379 x if Domain::Grant as i32 == x => Domain::Grant,
380 x if Domain::SELinux as i32 == x => Domain::SELinux,
381 x if Domain::Blob as i32 == x => Domain::Blob,
382 x if Domain::KeyId as i32 == x => Domain::KeyId,
383 _ => panic!("Unexpected domain: {}", value),
384 }
385 }
386
Joel Galenson2aab4432020-07-22 15:27:57 -0700387 // A class that deletes a file when it is dropped.
388 // TODO: If we ever add a crate that does this, we can use it instead.
389 struct TempFile {
390 filename: &'static str,
391 }
392
393 impl Drop for TempFile {
394 fn drop(&mut self) {
395 std::fs::remove_file(self.filename).expect("Cannot delete temporary file");
396 }
397 }
398
Joel Galenson0891bc12020-07-20 10:37:03 -0700399 // Use a custom random number generator that repeats each number once.
400 // This allows us to test repeated elements.
401
402 thread_local! {
403 static RANDOM_COUNTER: RefCell<i64> = RefCell::new(0);
404 }
405
406 pub fn random() -> i64 {
407 RANDOM_COUNTER.with(|counter| {
408 let result = *counter.borrow() / 2;
409 *counter.borrow_mut() += 1;
410 result
411 })
412 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700413}