blob: 37ef0ff90aa635b51e6f2e7fc0d47d7c79cca856 [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;
23use rusqlite::{params, Connection, NO_PARAMS};
24#[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 {
32 pub fn new() -> Result<KeystoreDB> {
Joel Galenson0891bc12020-07-20 10:37:03 -070033 let db = KeystoreDB {
Joel Galenson26f4d012020-07-17 14:57:21 -070034 conn: Connection::open_in_memory()
35 .context("Failed to initialize sqlite connection.")?,
Joel Galenson0891bc12020-07-20 10:37:03 -070036 };
37 db.init_tables().context("Failed to create KeystoreDB.")?;
38 Ok(db)
39 }
40
41 fn init_tables(&self) -> Result<()> {
42 self.conn
43 .execute(
44 "CREATE TABLE IF NOT EXISTS keyentry (
45 id INTEGER UNIQUE,
46 creation_date DATETIME,
47 domain INTEGER,
48 namespace INTEGER,
49 alias TEXT);",
50 NO_PARAMS,
51 )
52 .context("Failed to initialize \"keyentry\" table.")?;
53 Ok(())
54 }
55
56 pub fn create_key_entry(&self, domain: aidl::Domain, namespace: i64) -> Result<i64> {
57 match domain {
58 aidl::Domain::App | aidl::Domain::SELinux => {}
59 _ => {
60 return Err(KsError::sys())
61 .context(format!("Domain {:?} must be either App or SELinux.", domain));
62 }
63 }
64 // Loop until we get a unique id.
65 loop {
66 let newid: i64 = random();
67 let ret = self.conn.execute(
68 "INSERT into keyentry (id, creation_date, domain, namespace, alias)
69 VALUES(?, datetime('now'), ?, ?, NULL);",
70 params![newid, domain as i64, namespace],
71 );
72 match ret {
73 // If the id already existed, try again.
74 Err(rusqlite::Error::SqliteFailure(
75 libsqlite3_sys::Error {
76 code: libsqlite3_sys::ErrorCode::ConstraintViolation,
77 extended_code: libsqlite3_sys::SQLITE_CONSTRAINT_UNIQUE,
78 },
79 _,
80 )) => (),
81 _ => return Ok(newid),
82 }
83 }
Joel Galenson26f4d012020-07-17 14:57:21 -070084 }
85}
86
87#[cfg(test)]
88mod tests {
89
90 use super::*;
Joel Galenson0891bc12020-07-20 10:37:03 -070091 use std::cell::RefCell;
92
93 // Ensure that we're using the "injected" random function, not the real one.
94 #[test]
95 fn test_mocked_random() {
96 let rand1 = random();
97 let rand2 = random();
98 let rand3 = random();
99 if rand1 == rand2 {
100 assert_eq!(rand2 + 1, rand3);
101 } else {
102 assert_eq!(rand1 + 1, rand2);
103 assert_eq!(rand2, rand3);
104 }
105 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700106
107 // Ensure we can initialize the database.
108 #[test]
109 fn test_new() -> Result<()> {
110 KeystoreDB::new()?;
111 Ok(())
112 }
113
114 // Test that we have the correct tables.
115 #[test]
116 fn test_tables() -> Result<()> {
117 let db = KeystoreDB::new()?;
118 let tables = db
119 .conn
120 .prepare("SELECT name from sqlite_master WHERE type='table' ORDER BY name;")?
121 .query_map(params![], |row| row.get(0))?
122 .collect::<rusqlite::Result<Vec<String>>>()?;
Joel Galenson0891bc12020-07-20 10:37:03 -0700123 assert_eq!(tables.len(), 1);
124 assert_eq!(tables[0], "keyentry");
Joel Galenson26f4d012020-07-17 14:57:21 -0700125 Ok(())
126 }
Joel Galenson0891bc12020-07-20 10:37:03 -0700127
128 #[test]
129 fn test_create_key_entry() -> Result<()> {
130 use aidl::Domain;
131
132 fn extractor(ke: &KeyEntryRow) -> (Domain, i64, Option<&str>) {
133 (ke.domain.unwrap(), ke.namespace.unwrap(), ke.alias.as_deref())
134 }
135
136 let db = KeystoreDB::new()?;
137
138 db.create_key_entry(Domain::App, 100)?;
139 db.create_key_entry(Domain::SELinux, 101)?;
140
141 let entries = get_keyentry(&db)?;
142 assert_eq!(entries.len(), 2);
143 assert_eq!(extractor(&entries[0]), (Domain::App, 100, None));
144 assert_eq!(extractor(&entries[1]), (Domain::SELinux, 101, None));
145
146 // Test that we must pass in a valid Domain.
147 check_result_is_error_containing_string(
148 db.create_key_entry(Domain::Grant, 102),
149 "Domain Grant must be either App or SELinux.",
150 );
151 check_result_is_error_containing_string(
152 db.create_key_entry(Domain::Blob, 103),
153 "Domain Blob must be either App or SELinux.",
154 );
155 check_result_is_error_containing_string(
156 db.create_key_entry(Domain::KeyId, 104),
157 "Domain KeyId must be either App or SELinux.",
158 );
159
160 Ok(())
161 }
162
163 // Helpers
164
165 // Checks that the given result is an error containing the given string.
166 fn check_result_is_error_containing_string<T>(result: Result<T>, target: &str) {
167 let error_str = format!(
168 "{:#?}",
169 result.err().unwrap_or_else(|| panic!("Expected the error: {}", target))
170 );
171 assert!(
172 error_str.contains(target),
173 "The string \"{}\" should contain \"{}\"",
174 error_str,
175 target
176 );
177 }
178
179 #[allow(dead_code)]
180 struct KeyEntryRow {
181 id: u32,
182 creation_date: String,
183 domain: Option<aidl::Domain>,
184 namespace: Option<i64>,
185 alias: Option<String>,
186 }
187
188 fn get_keyentry(db: &KeystoreDB) -> Result<Vec<KeyEntryRow>> {
189 db.conn
190 .prepare("SELECT * FROM keyentry;")?
191 .query_map(NO_PARAMS, |row| {
192 let domain: Option<i32> = row.get(2)?;
193 Ok(KeyEntryRow {
194 id: row.get(0)?,
195 creation_date: row.get(1)?,
196 domain: domain.map(domain_from_integer),
197 namespace: row.get(3)?,
198 alias: row.get(4)?,
199 })
200 })?
201 .map(|r| r.context("Could not read keyentry row."))
202 .collect::<Result<Vec<_>>>()
203 }
204
205 // TODO: Replace this with num_derive.
206 fn domain_from_integer(value: i32) -> aidl::Domain {
207 use aidl::Domain;
208 match value {
209 x if Domain::App as i32 == x => Domain::App,
210 x if Domain::Grant as i32 == x => Domain::Grant,
211 x if Domain::SELinux as i32 == x => Domain::SELinux,
212 x if Domain::Blob as i32 == x => Domain::Blob,
213 x if Domain::KeyId as i32 == x => Domain::KeyId,
214 _ => panic!("Unexpected domain: {}", value),
215 }
216 }
217
218 // Use a custom random number generator that repeats each number once.
219 // This allows us to test repeated elements.
220
221 thread_local! {
222 static RANDOM_COUNTER: RefCell<i64> = RefCell::new(0);
223 }
224
225 pub fn random() -> i64 {
226 RANDOM_COUNTER.with(|counter| {
227 let result = *counter.borrow() / 2;
228 *counter.borrow_mut() += 1;
229 result
230 })
231 }
Joel Galenson26f4d012020-07-17 14:57:21 -0700232}