blob: d64a26e5edfef3685fc91d158dce3d1e66ff0864 [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};
Joel Galenson0891bc12020-07-20 10:37:03 -070024#[cfg(test)]
25use tests::random;
Joel Galenson26f4d012020-07-17 14:57:21 -070026
27pub struct KeystoreDB {
Joel Galenson26f4d012020-07-17 14:57:21 -070028 conn: Connection,
29}
30
31impl KeystoreDB {
Joel Galenson2aab4432020-07-22 15:27:57 -070032 // TODO(b/160882985): Figure out the location for this file.
33 #[cfg(not(test))]
Joel Galenson26f4d012020-07-17 14:57:21 -070034 pub fn new() -> Result<KeystoreDB> {
Joel Galenson2aab4432020-07-22 15:27:57 -070035 KeystoreDB::new_with_filename("persistent.sql")
36 }
37
38 #[cfg(test)]
39 pub fn new() -> Result<KeystoreDB> {
40 KeystoreDB::new_with_filename("")
41 }
42
43 fn new_with_filename(persistent_file: &str) -> Result<KeystoreDB> {
Joel Galenson0891bc12020-07-20 10:37:03 -070044 let db = KeystoreDB {
Joel Galenson26f4d012020-07-17 14:57:21 -070045 conn: Connection::open_in_memory()
46 .context("Failed to initialize sqlite connection.")?,
Joel Galenson0891bc12020-07-20 10:37:03 -070047 };
Joel Galenson2aab4432020-07-22 15:27:57 -070048 db.attach_databases(persistent_file).context("Failed to create KeystoreDB.")?;
Joel Galenson0891bc12020-07-20 10:37:03 -070049 db.init_tables().context("Failed to create KeystoreDB.")?;
50 Ok(db)
51 }
52
Joel Galenson2aab4432020-07-22 15:27:57 -070053 fn attach_databases(&self, persistent_file: &str) -> Result<()> {
54 self.conn
55 .execute("ATTACH DATABASE ? as 'persistent';", params![persistent_file])
56 .context("Failed to attach databases.")?;
57 Ok(())
58 }
59
Joel Galenson0891bc12020-07-20 10:37:03 -070060 fn init_tables(&self) -> Result<()> {
61 self.conn
62 .execute(
Joel Galenson2aab4432020-07-22 15:27:57 -070063 "CREATE TABLE IF NOT EXISTS persistent.keyentry (
Joel Galenson0891bc12020-07-20 10:37:03 -070064 id INTEGER UNIQUE,
65 creation_date DATETIME,
66 domain INTEGER,
67 namespace INTEGER,
68 alias TEXT);",
69 NO_PARAMS,
70 )
71 .context("Failed to initialize \"keyentry\" table.")?;
Hasini Gunasingheaf993662020-07-24 18:40:20 +000072 self.conn
73 .execute(
74 "CREATE TABLE IF NOT EXISTS persistent.keyparameter (
75 keyentryid INTEGER,
76 tag INTEGER,
77 data ANY,
78 security_level INTEGER);",
79 NO_PARAMS,
80 )
81 .context("Failed to initialize \"keyparameter\" table.")?;
Joel Galenson0891bc12020-07-20 10:37:03 -070082 Ok(())
83 }
84
85 pub fn create_key_entry(&self, domain: aidl::Domain, namespace: i64) -> Result<i64> {
86 match domain {
87 aidl::Domain::App | aidl::Domain::SELinux => {}
88 _ => {
89 return Err(KsError::sys())
90 .context(format!("Domain {:?} must be either App or SELinux.", domain));
91 }
92 }
93 // Loop until we get a unique id.
94 loop {
95 let newid: i64 = random();
96 let ret = self.conn.execute(
Joel Galenson2aab4432020-07-22 15:27:57 -070097 "INSERT into persistent.keyentry (id, creation_date, domain, namespace, alias)
Joel Galenson0891bc12020-07-20 10:37:03 -070098 VALUES(?, datetime('now'), ?, ?, NULL);",
99 params![newid, domain as i64, namespace],
100 );
101 match ret {
102 // If the id already existed, try again.
103 Err(rusqlite::Error::SqliteFailure(
104 libsqlite3_sys::Error {
105 code: libsqlite3_sys::ErrorCode::ConstraintViolation,
106 extended_code: libsqlite3_sys::SQLITE_CONSTRAINT_UNIQUE,
107 },
108 _,
109 )) => (),
110 _ => return Ok(newid),
111 }
112 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700113 }
Joel Galenson33c04ad2020-08-03 11:04:38 -0700114
115 pub fn rebind_alias(
116 &mut self,
117 newid: u32,
118 alias: &str,
119 domain: aidl::Domain,
120 namespace: i64,
121 ) -> Result<()> {
122 match domain {
123 aidl::Domain::App | aidl::Domain::SELinux => {}
124 _ => {
125 return Err(KsError::sys())
126 .context(format!("Domain {:?} must be either App or SELinux.", domain));
127 }
128 }
129 let tx = self
130 .conn
131 .transaction_with_behavior(TransactionBehavior::Immediate)
132 .context("Failed to initialize transaction.")?;
133 tx.execute(
134 "UPDATE persistent.keyentry
135 SET alias = NULL, domain = NULL, namespace = NULL
136 WHERE alias = ? AND domain = ? AND namespace = ?;",
137 params![alias, domain as i64, namespace],
138 )
139 .context("Failed to rebind existing entry.")?;
140 let result = tx
141 .execute(
142 "UPDATE persistent.keyentry
143 SET alias = ?
144 WHERE id = ? AND domain = ? AND namespace = ?;",
145 params![alias, newid, domain as i64, namespace],
146 )
147 .context("Failed to set alias.")?;
148 if result != 1 {
149 // Note that this explicit rollback is not required, as
150 // the transaction should rollback if we do not commit it.
151 // We leave it here for readability.
152 tx.rollback().context("Failed to rollback a failed transaction.")?;
153 return Err(KsError::sys()).context(format!(
154 "Expected to update a single entry but instead updated {}.",
155 result
156 ));
157 }
158 tx.commit().context("Failed to commit transaction.")
159 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700160}
161
162#[cfg(test)]
163mod tests {
164
165 use super::*;
Joel Galenson0891bc12020-07-20 10:37:03 -0700166 use std::cell::RefCell;
167
168 // Ensure that we're using the "injected" random function, not the real one.
169 #[test]
170 fn test_mocked_random() {
171 let rand1 = random();
172 let rand2 = random();
173 let rand3 = random();
174 if rand1 == rand2 {
175 assert_eq!(rand2 + 1, rand3);
176 } else {
177 assert_eq!(rand1 + 1, rand2);
178 assert_eq!(rand2, rand3);
179 }
180 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700181
182 // Ensure we can initialize the database.
183 #[test]
184 fn test_new() -> Result<()> {
185 KeystoreDB::new()?;
186 Ok(())
187 }
188
189 // Test that we have the correct tables.
190 #[test]
191 fn test_tables() -> Result<()> {
192 let db = KeystoreDB::new()?;
193 let tables = db
194 .conn
Joel Galenson2aab4432020-07-22 15:27:57 -0700195 .prepare("SELECT name from persistent.sqlite_master WHERE type='table' ORDER BY name;")?
Joel Galenson26f4d012020-07-17 14:57:21 -0700196 .query_map(params![], |row| row.get(0))?
197 .collect::<rusqlite::Result<Vec<String>>>()?;
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000198 assert_eq!(tables.len(), 2);
Joel Galenson0891bc12020-07-20 10:37:03 -0700199 assert_eq!(tables[0], "keyentry");
Hasini Gunasingheaf993662020-07-24 18:40:20 +0000200 assert_eq!(tables[1], "keyparameter");
Joel Galenson26f4d012020-07-17 14:57:21 -0700201 Ok(())
202 }
Joel Galenson0891bc12020-07-20 10:37:03 -0700203
204 #[test]
Joel Galenson2aab4432020-07-22 15:27:57 -0700205 fn test_no_persistence_for_tests() -> Result<()> {
206 let db = KeystoreDB::new()?;
207
208 db.create_key_entry(aidl::Domain::App, 100)?;
209 let entries = get_keyentry(&db)?;
210 assert_eq!(entries.len(), 1);
211 let db = KeystoreDB::new()?;
212
213 let entries = get_keyentry(&db)?;
214 assert_eq!(entries.len(), 0);
215 Ok(())
216 }
217
218 #[test]
219 fn test_persistence_for_files() -> Result<()> {
220 let persistent = TempFile { filename: "/data/local/tmp/persistent.sql" };
221 let db = KeystoreDB::new_with_filename(persistent.filename)?;
222
223 db.create_key_entry(aidl::Domain::App, 100)?;
224 let entries = get_keyentry(&db)?;
225 assert_eq!(entries.len(), 1);
226 let db = KeystoreDB::new_with_filename(persistent.filename)?;
227
228 let entries_new = get_keyentry(&db)?;
229 assert_eq!(entries, entries_new);
230 Ok(())
231 }
232
233 #[test]
Joel Galenson0891bc12020-07-20 10:37:03 -0700234 fn test_create_key_entry() -> Result<()> {
235 use aidl::Domain;
236
237 fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>) {
238 (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref())
239 }
240
241 let db = KeystoreDB::new()?;
242
243 db.create_key_entry(Domain::App, 100)?;
244 db.create_key_entry(Domain::SELinux, 101)?;
245
246 let entries = get_keyentry(&db)?;
247 assert_eq!(entries.len(), 2);
248 assert_eq!(extractor(&entries[0]), (Domain::App, 100, None));
249 assert_eq!(extractor(&entries[1]), (Domain::SELinux, 101, None));
250
251 // Test that we must pass in a valid Domain.
252 check_result_is_error_containing_string(
253 db.create_key_entry(Domain::Grant, 102),
254 "Domain Grant must be either App or SELinux.",
255 );
256 check_result_is_error_containing_string(
257 db.create_key_entry(Domain::Blob, 103),
258 "Domain Blob must be either App or SELinux.",
259 );
260 check_result_is_error_containing_string(
261 db.create_key_entry(Domain::KeyId, 104),
262 "Domain KeyId must be either App or SELinux.",
263 );
264
265 Ok(())
266 }
267
Joel Galenson33c04ad2020-08-03 11:04:38 -0700268 #[test]
269 fn test_rebind_alias() -> Result<()> {
270 use aidl::Domain;
271
272 fn extractor(ke: &KeyEntryRow) -> (Option<Domain>, Option<i64>, Option<&str>) {
273 (ke.domain, ke.namespace, ke.alias.as_deref())
274 }
275
276 let mut db = KeystoreDB::new()?;
277 db.create_key_entry(Domain::App, 42)?;
278 db.create_key_entry(Domain::App, 42)?;
279 let entries = get_keyentry(&db)?;
280 assert_eq!(entries.len(), 2);
281 assert_eq!(extractor(&entries[0]), (Some(Domain::App), Some(42), None));
282 assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), None));
283
284 // Test that the first call to rebind_alias sets the alias.
285 db.rebind_alias(entries[0].id, "foo", Domain::App, 42)?;
286 let entries = get_keyentry(&db)?;
287 assert_eq!(entries.len(), 2);
288 assert_eq!(extractor(&entries[0]), (Some(Domain::App), Some(42), Some("foo")));
289 assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), None));
290
291 // Test that the second call to rebind_alias also empties the old one.
292 db.rebind_alias(entries[1].id, "foo", Domain::App, 42)?;
293 let entries = get_keyentry(&db)?;
294 assert_eq!(entries.len(), 2);
295 assert_eq!(extractor(&entries[0]), (None, None, None));
296 assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), Some("foo")));
297
298 // Test that we must pass in a valid Domain.
299 check_result_is_error_containing_string(
300 db.rebind_alias(0, "foo", Domain::Grant, 42),
301 "Domain Grant must be either App or SELinux.",
302 );
303 check_result_is_error_containing_string(
304 db.rebind_alias(0, "foo", Domain::Blob, 42),
305 "Domain Blob must be either App or SELinux.",
306 );
307 check_result_is_error_containing_string(
308 db.rebind_alias(0, "foo", Domain::KeyId, 42),
309 "Domain KeyId must be either App or SELinux.",
310 );
311
312 // Test that we correctly handle setting an alias for something that does not exist.
313 check_result_is_error_containing_string(
314 db.rebind_alias(0, "foo", Domain::SELinux, 42),
315 "Expected to update a single entry but instead updated 0",
316 );
317 // Test that we correctly abort the transaction in this case.
318 let entries = get_keyentry(&db)?;
319 assert_eq!(entries.len(), 2);
320 assert_eq!(extractor(&entries[0]), (None, None, None));
321 assert_eq!(extractor(&entries[1]), (Some(Domain::App), Some(42), Some("foo")));
322
323 Ok(())
324 }
325
Joel Galenson0891bc12020-07-20 10:37:03 -0700326 // Helpers
327
328 // Checks that the given result is an error containing the given string.
329 fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) {
330 let error_str = format!(
331 "{:#?}",
332 result.err().unwrap_or_else(|| panic!("Expected the error: {}", target))
333 );
334 assert!(
335 error_str.contains(target),
336 "The string \"{}\" should contain \"{}\"",
337 error_str,
338 target
339 );
340 }
341
Joel Galenson2aab4432020-07-22 15:27:57 -0700342 #[derive(Debug, PartialEq)]
Joel Galenson0891bc12020-07-20 10:37:03 -0700343 #[allow(dead_code)]
344 struct KeyEntryRow {
345 id: u32,
346 creation_date: String,
347 domain: Option<aidl::Domain>,
348 namespace: Option<i64>,
349 alias: Option<String>,
350 }
351
352 fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> {
353 db.conn
Joel Galenson2aab4432020-07-22 15:27:57 -0700354 .prepare("SELECT * FROM persistent.keyentry;")?
Joel Galenson0891bc12020-07-20 10:37:03 -0700355 .query_map(NO_PARAMS, |row| {
356 let domain: Option<i32> = row.get(2)?;
357 Ok(KeyEntryRow {
358 id: row.get(0)?,
359 creation_date: row.get(1)?,
360 domain: domain.map(domain_from_integer),
361 namespace: row.get(3)?,
362 alias: row.get(4)?,
363 })
364 })?
365 .map(|r| r.context("Could not read keyentry row."))
366 .collect::<Result<Vec<_>>>()
367 }
368
369 // TODO: Replace this with num_derive.
370 fn domain_from_integer(value: i32) -> aidl::Domain {
371 use aidl::Domain;
372 match value {
373 x if Domain::App as i32 == x => Domain::App,
374 x if Domain::Grant as i32 == x => Domain::Grant,
375 x if Domain::SELinux as i32 == x => Domain::SELinux,
376 x if Domain::Blob as i32 == x => Domain::Blob,
377 x if Domain::KeyId as i32 == x => Domain::KeyId,
378 _ => panic!("Unexpected domain: {}", value),
379 }
380 }
381
Joel Galenson2aab4432020-07-22 15:27:57 -0700382 // A class that deletes a file when it is dropped.
383 // TODO: If we ever add a crate that does this, we can use it instead.
384 struct TempFile {
385 filename: &'static str,
386 }
387
388 impl Drop for TempFile {
389 fn drop(&mut self) {
390 std::fs::remove_file(self.filename).expect("Cannot delete temporary file");
391 }
392 }
393
Joel Galenson0891bc12020-07-20 10:37:03 -0700394 // Use a custom random number generator that repeats each number once.
395 // This allows us to test repeated elements.
396
397 thread_local! {
398 static RANDOM_COUNTER: RefCell<i64> = RefCell::new(0);
399 }
400
401 pub fn random() -> i64 {
402 RANDOM_COUNTER.with(|counter| {
403 let result = *counter.borrow() / 2;
404 *counter.borrow_mut() += 1;
405 result
406 })
407 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700408}