/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! Implementation of the `KeyValueStore` trait using Trusty secure storage. //! //! Store each secret in a file of its own (mapping the secret ID to the filename) as there are not //! expected to be many extant secrets. However, use a prefix for the filename to allow easier //! migration to a different scheme in future if this assumption changes. use alloc::string::String; use secretkeeper_comm::data_types::error::Error; use secretkeeper_core::store::KeyValueStore; use storage::{OpenMode, Port, Session}; /// Store each secret in a file named "v1_", using the hex representation of the key/secret ID. /// Note that IDs are not confidential, so can appear in logs. const PREFIX_V1: &str = "v1_"; /// Generate the filename corresponding to a key. fn filename(key: &[u8]) -> String { let mut result = String::with_capacity(PREFIX_V1.len() + 2 * key.len()); result += PREFIX_V1; for byte in key { result += &format!("{:02x}", byte); } result } /// Helper macro for emitting an error. macro_rules! ss_err { { $e:expr, $($arg:tt)+ } => { { log::error!("{}: {:?}", format_args!($($arg)+), $e); Error::UnexpectedError } }; } /// Create a storage session. fn create_session() -> Result { // Use TD storage, which means that: // - storage is only available after Android has booted // - storage is wiped on factory reset // - size of stored data isn't problematic Session::new(Port::TamperDetect, /* wait_for_port= */ true) .map_err(|e| ss_err!(e, "Couldn't create storage session")) } /// An implementation of `KeyValueStore` backed by secure storage. #[derive(Default)] pub struct SecureStore; impl KeyValueStore for SecureStore { fn store(&mut self, key: &[u8], val: &[u8]) -> Result<(), Error> { let filename = filename(key); let mut session = create_session()?; // This will overwrite the value if key is already present. let mut file = session .open_file(&filename, OpenMode::Create) .map_err(|e| ss_err!(e, "Couldn't create file '{filename}'"))?; session.write_all(&mut file, val).map_err(|e| ss_err!(e, "Failed to write data"))?; Ok(()) } fn get(&self, key: &[u8]) -> Result>, Error> { let filename = filename(key); let mut session = create_session()?; let file = match session.open_file(&filename, OpenMode::Open) { Ok(f) => f, Err(storage::Error::Code(trusty_sys::Error::NotFound)) => return Ok(None), Err(e) => return Err(ss_err!(e, "Failed to open file '{filename}'")), }; let size = session .get_size(&file) .map_err(|e| ss_err!(e, "Failed to get size for '{filename}'"))?; let mut buffer = vec![0; size]; let content = session .read_all(&file, buffer.as_mut_slice()) .map_err(|e| ss_err!(e, "Failed to read '{filename}'"))?; let total_size = content.len(); buffer.resize(total_size, 0); Ok(Some(buffer)) } fn delete(&mut self, key: &[u8]) -> Result<(), Error> { let filename = filename(key); let mut session = create_session()?; match session.remove(&filename) { Ok(_) => Ok(()), Err(storage::Error::Code(trusty_sys::Error::NotFound)) => Ok(()), Err(e) => Err(ss_err!(e, "Failed to delete file '{filename}'")), } } fn delete_all(&mut self) -> Result<(), Error> { let mut failed = false; let mut session = create_session()?; for entry in session.list_files().map_err(|e| ss_err!(e, "Failed to list files"))? { match entry { Ok((filename, _state)) if filename.starts_with(PREFIX_V1) => { let result = session .remove(&filename) .map_err(|e| ss_err!(e, "Failed to delete '{filename}'")); if result.is_err() { failed = true; } } Ok((filename, _state)) => log::info!("Skipping unrelated file {filename}"), Err(e) => log::error!("Failed to delete an entry: {e:?}"), }; } if failed { Err(Error::UnexpectedError) } else { Ok(()) } } } #[cfg(test)] mod tests { use super::*; use test::expect_eq; const KEY1: &[u8] = b"bogus key 1"; const KEY2: &[u8] = b"bogus key 2"; const SECRET1: &[u8] = b"bogus secret 1"; const SECRET2: &[u8] = b"bogus secret 2"; #[test] fn test_secretkeeper_store() { let mut store = SecureStore::default(); // Ensure consistent state before. store.delete(KEY1).unwrap(); store.delete(KEY2).unwrap(); store.store(KEY1, SECRET1).unwrap(); expect_eq!(store.get(KEY1), Ok(Some(SECRET1.to_vec()))); expect_eq!(store.get(KEY2), Ok(None)); store.store(KEY1, SECRET2).unwrap(); expect_eq!(store.get(KEY1), Ok(Some(SECRET2.to_vec()))); expect_eq!(store.get(KEY2), Ok(None)); store.store(KEY1, SECRET1).unwrap(); expect_eq!(store.get(KEY1), Ok(Some(SECRET1.to_vec()))); expect_eq!(store.get(KEY2), Ok(None)); store.store(KEY2, SECRET1).unwrap(); expect_eq!(store.get(KEY1), Ok(Some(SECRET1.to_vec()))); expect_eq!(store.get(KEY2), Ok(Some(SECRET1.to_vec()))); store.delete(KEY1).unwrap(); expect_eq!(store.get(KEY1), Ok(None)); expect_eq!(store.get(KEY2), Ok(Some(SECRET1.to_vec()))); store.delete(KEY2).unwrap(); expect_eq!(store.get(KEY1), Ok(None)); expect_eq!(store.get(KEY2), Ok(None)); // Ensure consistent state after. store.delete(KEY1).unwrap(); store.delete(KEY2).unwrap(); } }