| /* |
| * Copyright (C) 2022 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. |
| */ |
| |
| //! Module that implements the [`SecureDeletionSecretManager`] trait. |
| use alloc::rc::Rc; |
| use core::{ |
| cell::RefCell, |
| cmp, |
| ops::{Deref, DerefMut}, |
| }; |
| use kmr_common::{ |
| crypto, |
| keyblob::{SecureDeletionData, SecureDeletionSecretManager, SecureDeletionSlot, SlotPurpose}, |
| km_err, Error, |
| }; |
| use log::{debug, error, info}; |
| use storage::{self as storage_session, OpenMode, Port, SecureFile, Session, Transaction}; |
| use trusty_sys; |
| use zeroize::{Zeroize, ZeroizeOnDrop}; |
| |
| // Maximum number of attempts to perform a secure storage transaction to read or |
| // delete a secure deletion secret. Because the storageproxy may be restarted |
| // while this code is running, it may be necessary to retry. But because it's |
| // unclear exactly what error codes may be returned when the proxy is shut down, |
| // we conservatively retry all unexpected errors. To avoid an infinite loop, we |
| // set a limit on the number of retries (though hitting the limit and returning |
| // an error will likely break the boot anyway). Ideally, we should never need |
| // more than one retry. We allow three. |
| const KM_MAX_TRIES: usize = 3; |
| |
| // Each secret is 16 bytes. |
| const KM_SECRET_SIZE: usize = 16; |
| // The factory reset secret is composed of two secrets, so 32 bytes, and it is |
| // stored at offset 0. After this position (offset 32), all the secrets are |
| // stored one after the other. On each secret a single bit on the first byte is |
| // set to indicate that the secret is valid (mask 0x80). Any unused secret/newly |
| // allocated space on the file is set to 0 |
| const KM_FACTORY_RESET_SECRET_SIZE: usize = KM_SECRET_SIZE * 2; |
| const KM_FACTORY_RESET_SECRET_POS: usize = 0; |
| const KM_FACTORY_FIRST_SECURE_DELETION_SECRET_POS: usize = |
| KM_FACTORY_RESET_SECRET_POS + KM_FACTORY_RESET_SECRET_SIZE; |
| |
| // We read secrets in blocks of 32, so 512 bytes. |
| const KM_BLOCK_SIZE: usize = KM_SECRET_SIZE * 32; |
| |
| // Limit file size to 16 KiB (except for key upgrades, see |
| // KM_MAX_SECRET_FILE_SIZE_FOR_UPGRADES). |
| const KM_MAX_SECRET_FILE_SIZE: usize = KM_BLOCK_SIZE * 32; |
| |
| // This is a higher file size limit, with the space above KM_MAX_SECRET_FILE_SIZE |
| // usable only for key IDs that need to be written as part of a key upgrade. |
| // This is to reduce the probability that keys are degraded as a result of |
| // upgrading. |
| const KM_MAX_SECRET_FILE_SIZE_FOR_UPGRADES: usize = KM_MAX_SECRET_FILE_SIZE + 8 * KM_BLOCK_SIZE; |
| |
| // We set a bit in the first byte of each slot to indicate that the slot is in |
| // use. This reduces the maximum entropy of each slot to 127 bits. |
| const KM_IN_USE_FLAG: u8 = 0x80; |
| |
| // Name of the file to store secrets. The "_1" suffix is to allow for new file |
| // formats/versions in the future. |
| const KM_SECURE_DELETION_SECRET_FILENAME: &'static str = "SecureDeletionSecrets_1"; |
| |
| const _: () = assert!( |
| KM_BLOCK_SIZE >= KM_FACTORY_RESET_SECRET_SIZE, |
| "KM_BLOCK_SIZE should be bigger than KM_FACTORY_RESET_SECRET_SIZE" |
| ); |
| const _: () = assert!((KM_BLOCK_SIZE % KM_SECRET_SIZE) == 0, "Broke find_empty_slot assumption"); |
| const _: () = assert!( |
| (KM_FACTORY_RESET_SECRET_SIZE % KM_SECRET_SIZE) == 0, |
| "Broke find_empty_slot assumption" |
| ); |
| |
| struct SecureDeletionSecretFileSession { |
| session: Session, |
| } |
| |
| struct SecureDeletionSecretFile<'a> { |
| file: SecureFile, |
| transaction: Transaction<'a>, |
| } |
| |
| enum RetrieveSecureDeletionSecretFileData<'a> { |
| EmptyFileFound(SecureDeletionSecretFile<'a>), |
| CachedDataFound(SecureDeletionData), |
| DataFoundOnFile(SecureDeletionData), |
| } |
| |
| impl SecureDeletionSecretFileSession { |
| fn new(wait_on_port: bool) -> Result<Self, Error> { |
| let mut session = Session::new(Port::TamperProof, wait_on_port).map_err(|_| { |
| km_err!( |
| SecureHwCommunicationFailed, |
| "Failed to connect to secure storage port for opening secure deletion secret file" |
| ) |
| })?; |
| Ok(SecureDeletionSecretFileSession { session }) |
| } |
| |
| fn delete_file(&mut self) -> Result<(), Error> { |
| // We do not consider the file not existing an error when trying to delete it because the |
| // end result is the same. |
| match self.session.remove(KM_SECURE_DELETION_SECRET_FILENAME) { |
| Ok(_) => Ok(()), |
| Err(storage_session::Error::Code(trusty_sys::Error::NotFound)) => Ok(()), |
| Err(_) => { |
| Err(km_err!(SecureHwCommunicationFailed, "Couldn't delete secure secrets file")) |
| } |
| } |
| } |
| |
| fn close(self) { |
| self.session.close() |
| } |
| } |
| |
| impl<'a> SecureDeletionSecretFile<'a> { |
| fn open_or_create( |
| session: &'a mut SecureDeletionSecretFileSession, |
| ) -> Result<SecureDeletionSecretFile<'a>, Error> { |
| let mut transaction = session.session.begin_transaction(); |
| let mut file = |
| transaction.open_file(KM_SECURE_DELETION_SECRET_FILENAME, OpenMode::Create).map_err( |
| |_| km_err!(SecureHwCommunicationFailed, "Failed open secure deletion secret file"), |
| )?; |
| Ok(SecureDeletionSecretFile { transaction, file }) |
| } |
| |
| fn read_block<'buf>( |
| &mut self, |
| start: usize, |
| buffer: &'buf mut [u8], |
| ) -> Result<&'buf [u8], Error> { |
| let req_len = buffer.len(); |
| let data = self.transaction.read_at(&self.file, start, buffer).map_err(|_| { |
| km_err!( |
| SecureHwCommunicationFailed, |
| "Failed to read secure deletion secret file at offset {} with len {}", |
| start, |
| req_len |
| ) |
| })?; |
| if data.len() != req_len { |
| Err(km_err!( |
| UnknownError, |
| "Couldn't read {} bytes of secure deletion secret file at offset {}. Read {} bytes", |
| req_len, |
| start, |
| data.len() |
| )) |
| } else { |
| Ok(data) |
| } |
| } |
| |
| // Find empty slot is used to find the first [KM_SECRET_SIZE] position on the secure file that |
| // isn't currently in use. For this it will read the secure file in [KM_BLOCK_SIZE] chunks and |
| // move in [KM_SECRET_SIZE] increments; checking if the KM_IN_USE_FLAG if set on that position. |
| fn find_empty_slot(&mut self, is_upgrade: bool) -> Result<usize, Error> { |
| let end = SecureDeletionSecretFile::get_max_file_size(is_upgrade); |
| let file_size = self.get_file_size()?; |
| let end = cmp::min(end, file_size); |
| let mut key_slot = 0; |
| let mut block_buffer = [0; KM_BLOCK_SIZE]; |
| 'search_loop: for start_pos in (0..end).step_by(KM_BLOCK_SIZE) { |
| let read_data = match self.read_block(start_pos, &mut block_buffer) { |
| Ok(read_data) => read_data, |
| Err(e) => { |
| error!("Failed to read block of secrets"); |
| return Err(e); |
| } |
| }; |
| if read_data.len() != KM_BLOCK_SIZE { |
| return Err(km_err!( |
| SecureHwCommunicationFailed, |
| "Failed to read complete block from storage. Received {} bytes", |
| read_data.len() |
| )); |
| } |
| let block_start = match start_pos { |
| KM_FACTORY_RESET_SECRET_POS => KM_FACTORY_FIRST_SECURE_DELETION_SECRET_POS, |
| _ => 0, |
| }; |
| for (chunk_num, secret) in read_data[block_start..].chunks(KM_SECRET_SIZE).enumerate() { |
| if (secret[0] & KM_IN_USE_FLAG) == 0 { |
| key_slot = |
| (start_pos + block_start + (chunk_num * KM_SECRET_SIZE)) / KM_SECRET_SIZE; |
| break 'search_loop; |
| } |
| } |
| } |
| Ok(key_slot) |
| } |
| |
| fn write_block(&mut self, start: usize, buffer: &[u8]) -> Result<(), Error> { |
| self.transaction.write_at(&mut self.file, start, buffer).map_err(|_| { |
| km_err!( |
| SecureHwCommunicationFailed, |
| "Failed to write to deletion secret file at pos {}", |
| start |
| ) |
| }) |
| } |
| |
| fn get_file_size(&mut self) -> Result<usize, Error> { |
| self.transaction.get_size(&self.file).map_err(|_| { |
| km_err!(SecureHwCommunicationFailed, "Couldn't get secure deletion secret file size") |
| }) |
| } |
| |
| fn get_max_file_size(is_upgrade: bool) -> usize { |
| match is_upgrade { |
| true => KM_MAX_SECRET_FILE_SIZE_FOR_UPGRADES, |
| false => KM_MAX_SECRET_FILE_SIZE, |
| } |
| } |
| |
| fn resize(&mut self, new_size: usize) -> Result<(), Error> { |
| self.transaction.set_size(&mut self.file, new_size).map_err(|_| { |
| km_err!( |
| SecureHwCommunicationFailed, |
| "Failed to resize secure deletion secret file to {}", |
| new_size |
| ) |
| })?; |
| Ok(()) |
| } |
| |
| fn zero_entries(&mut self, begin: usize, end: usize) -> Result<(), Error> { |
| if (begin % KM_SECRET_SIZE) != 0 { |
| return Err(km_err!( |
| InvalidArgument, |
| "zero_entries called with invalid offset {}", |
| begin |
| )); |
| } |
| let zero_buff = [0; KM_SECRET_SIZE]; |
| for start_pos in (begin..end).step_by(KM_SECRET_SIZE) { |
| self.write_block(start_pos, &zero_buff)?; |
| } |
| Ok(()) |
| } |
| |
| fn finish_transaction(mut self) -> Result<(), Error> { |
| self.transaction.commit().map_err(|_| { |
| km_err!( |
| SecureHwCommunicationFailed, |
| "Failed to commit transaction on secure deletion secret file" |
| ) |
| }) |
| } |
| } |
| |
| #[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)] |
| struct FactoryResetSecret(Option<[u8; KM_FACTORY_RESET_SECRET_SIZE]>); |
| |
| #[derive(Clone, PartialEq, Eq)] |
| pub struct TrustySecureDeletionSecretManager { |
| factory_reset_secret: RefCell<FactoryResetSecret>, |
| } |
| |
| impl TrustySecureDeletionSecretManager { |
| pub fn new() -> Self { |
| TrustySecureDeletionSecretManager { |
| factory_reset_secret: RefCell::new(FactoryResetSecret(None)), |
| } |
| } |
| |
| // get_factory_reset_secret_impl will just try to get the factory reset secret either from cache |
| // or from the file if it exists. If the secret is read and not cached, it will cache it. |
| // If the file doesn't exist, or if it is empty, it will return a File object/session; so the |
| // caller can initialize it. In case the caller doesn't initialize it, we will end up with an |
| // empty file on the file system, but this should be fine because we treat an empty file in the |
| // same way we treat a non-existing file. |
| fn get_factory_reset_secret_impl<'a>( |
| &'a self, |
| session: Option<&'a mut SecureDeletionSecretFileSession>, |
| ) -> Result<RetrieveSecureDeletionSecretFileData, Error> { |
| match self.factory_reset_secret.borrow_mut().deref_mut().0 { |
| Some(secret) => { |
| Ok(RetrieveSecureDeletionSecretFileData::CachedDataFound(SecureDeletionData { |
| factory_reset_secret: secret.clone(), |
| secure_deletion_secret: [0; KM_SECRET_SIZE], |
| })) |
| } |
| mut factory_reset_secret @ None => { |
| debug!("Trying to open a session to read factory reset secret"); |
| let session = session.ok_or(km_err!( |
| SecureHwCommunicationFailed, |
| "Couldn't get a session to open the secure deletion secret file" |
| ))?; |
| let mut sdsf_file = SecureDeletionSecretFile::open_or_create(session)?; |
| let file_size = sdsf_file.get_file_size().map_err(|_| { |
| km_err!( |
| SecureHwCommunicationFailed, |
| "Couldn't get secure deletion secret file size" |
| ) |
| })?; |
| if file_size > 0 { |
| // We found a secret on the file; read it |
| debug!("Opened non-empty secure secrets file"); |
| let mut buffer = [0; KM_FACTORY_RESET_SECRET_SIZE]; |
| let block = sdsf_file.read_block(KM_FACTORY_RESET_SECRET_POS, &mut buffer)?; |
| debug!("Read factory-reset secret, size {}", block.len()); |
| if block.len() != KM_FACTORY_RESET_SECRET_SIZE { |
| return Err(km_err!( |
| SecureHwCommunicationFailed, |
| "Failed to read complete secret data from storage. Received {} bytes", |
| block.len() |
| )); |
| } |
| factory_reset_secret.replace(buffer.clone()); |
| Ok(RetrieveSecureDeletionSecretFileData::DataFoundOnFile(SecureDeletionData { |
| factory_reset_secret: buffer, |
| secure_deletion_secret: [0; KM_SECRET_SIZE], |
| })) |
| } else { |
| Ok(RetrieveSecureDeletionSecretFileData::EmptyFileFound(sdsf_file)) |
| } |
| } |
| } |
| } |
| |
| // get_or_create_factory_reset_secret_impl will use get_factory_reset_secret_impl to try to get |
| // the factory reset secret. If the secure deletion secret file doesn't exist on secure storage, |
| // it will create it and will also initialize it. |
| fn get_or_create_factory_reset_secret_impl( |
| &mut self, |
| rng: &mut dyn crypto::Rng, |
| wait_for_port: bool, |
| ) -> Result<SecureDeletionData, Error> { |
| let mut session = SecureDeletionSecretFileSession::new(wait_for_port).ok(); |
| let mut secret_file_data = self.get_factory_reset_secret_impl(session.as_mut())?; |
| match secret_file_data { |
| RetrieveSecureDeletionSecretFileData::CachedDataFound(data) => Ok(data), |
| RetrieveSecureDeletionSecretFileData::DataFoundOnFile(data) => Ok(data), |
| RetrieveSecureDeletionSecretFileData::EmptyFileFound(mut sdsf_file) => { |
| sdsf_file.resize(KM_BLOCK_SIZE)?; |
| debug!("Resized secure secrets file to size {}", KM_BLOCK_SIZE); |
| let mut buffer = [0; KM_FACTORY_RESET_SECRET_SIZE]; |
| rng.fill_bytes(&mut buffer); |
| sdsf_file.write_block(KM_FACTORY_RESET_SECRET_POS, &buffer)?; |
| debug!("Wrote new factory reset secret"); |
| sdsf_file |
| .zero_entries(KM_FACTORY_FIRST_SECURE_DELETION_SECRET_POS, KM_BLOCK_SIZE)?; |
| debug!("Zeroed secrets"); |
| sdsf_file.finish_transaction()?; |
| debug!("Committed new secrets file"); |
| self.factory_reset_secret.borrow_mut().deref_mut().0.replace(buffer.clone()); |
| Ok(SecureDeletionData { |
| factory_reset_secret: buffer, |
| secure_deletion_secret: [0; KM_SECRET_SIZE], |
| }) |
| } |
| } |
| } |
| |
| fn read_slot_data(&self, slot: SecureDeletionSlot, buffer: &mut [u8]) -> Result<(), Error> { |
| let buffer_size = buffer.len(); |
| if buffer_size != KM_SECRET_SIZE { |
| return Err(km_err!( |
| InsufficientBufferSpace, |
| "Needed {} bytes to read slot. Received {}", |
| KM_SECRET_SIZE, |
| buffer.len() |
| )); |
| } |
| let requested_slot = slot.0 as usize; |
| let key_slot_pos = requested_slot * KM_SECRET_SIZE; |
| let mut session = match SecureDeletionSecretFileSession::new(true) { |
| Ok(session) => session, |
| Err(e) => { |
| error!("Failed to open session to get secure deletion data"); |
| return Err(e); |
| } |
| }; |
| let mut sdsf_file = match SecureDeletionSecretFile::open_or_create(&mut session) { |
| Ok(sdsf_file) => sdsf_file, |
| Err(e) => { |
| error!("Failed to open file to get secure deletion data"); |
| return Err(e); |
| } |
| }; |
| let file_size = match sdsf_file.get_file_size() { |
| Ok(file_size) => file_size, |
| Err(e) => { |
| error!("Failed to read secure deletion data file size"); |
| return Err(e); |
| } |
| }; |
| if (key_slot_pos + KM_SECRET_SIZE) > file_size { |
| return Err(km_err!( |
| InvalidArgument, |
| "Invalid key slot {} would read past end of file of size {}", |
| requested_slot, |
| file_size |
| )); |
| } |
| match sdsf_file.read_block(key_slot_pos, buffer) { |
| Ok(read_data) => { |
| if buffer_size == read_data.len() { |
| Ok(()) |
| } else { |
| Err(km_err!( |
| SecureHwCommunicationFailed, |
| "Failed to read complete slot data from storage. Received {} bytes", |
| read_data.len() |
| )) |
| } |
| } |
| Err(e) => { |
| error!("Failed to read secret from slot {}", requested_slot); |
| Err(e) |
| } |
| } |
| } |
| } |
| |
| impl Drop for TrustySecureDeletionSecretManager { |
| fn drop(&mut self) { |
| self.factory_reset_secret.borrow_mut().deref_mut().zeroize(); |
| } |
| } |
| |
| impl ZeroizeOnDrop for TrustySecureDeletionSecretManager {} |
| |
| impl SecureDeletionSecretManager for TrustySecureDeletionSecretManager { |
| fn get_or_create_factory_reset_secret( |
| &mut self, |
| rng: &mut dyn crypto::Rng, |
| ) -> Result<SecureDeletionData, Error> { |
| self.get_or_create_factory_reset_secret_impl(rng, true) |
| } |
| |
| fn get_factory_reset_secret(&self) -> Result<SecureDeletionData, Error> { |
| let mut session = SecureDeletionSecretFileSession::new(true).ok(); |
| let mut secret_file_data = self.get_factory_reset_secret_impl(session.as_mut())?; |
| match secret_file_data { |
| RetrieveSecureDeletionSecretFileData::CachedDataFound(data) => Ok(data), |
| RetrieveSecureDeletionSecretFileData::DataFoundOnFile(data) => Ok(data), |
| RetrieveSecureDeletionSecretFileData::EmptyFileFound(_) => { |
| Err(km_err!(UnknownError, "Factory reset secret not found")) |
| } |
| } |
| } |
| |
| fn new_secret( |
| &mut self, |
| rng: &mut dyn crypto::Rng, |
| slot_purpose: SlotPurpose, |
| ) -> Result<(SecureDeletionSlot, SecureDeletionData), Error> { |
| let is_upgrade = match slot_purpose { |
| SlotPurpose::KeyUpgrade => true, |
| _ => false, |
| }; |
| // We are not waiting on the connection if the TA port is not available. This follows the |
| // behavior of the original code. |
| let mut secure_deletion_data = |
| match self.get_or_create_factory_reset_secret_impl(rng, false) { |
| Ok(data) => data, |
| Err(e) => { |
| info!("Unable to get factory reset secret"); |
| return Err(e); |
| } |
| }; |
| rng.fill_bytes(&mut secure_deletion_data.secure_deletion_secret); |
| secure_deletion_data.secure_deletion_secret[0] |= KM_IN_USE_FLAG; |
| // Next call will block on the port. It should be fine, because if we reach this point, the |
| // TA should have been available before. Also, the original code follows a similar flow on |
| // which they use a blocking call if this point is reached. |
| let mut session = SecureDeletionSecretFileSession::new(true)?; |
| let mut sdsf_file = SecureDeletionSecretFile::open_or_create(&mut session)?; |
| let mut empty_slot = match sdsf_file.find_empty_slot(is_upgrade) { |
| Ok(slot) => slot, |
| Err(e) => { |
| error!("Error while searching for key slot"); |
| return Err(e); |
| } |
| }; |
| |
| let original_file_size = sdsf_file.get_file_size()?; |
| if empty_slot == 0 { |
| //No empty slot found, try to increase file size |
| let max_file_size = SecureDeletionSecretFile::get_max_file_size(is_upgrade); |
| if (original_file_size >= max_file_size) { |
| error!( |
| "Didn't find a slot and can't grow the file larger than {}", |
| original_file_size |
| ); |
| return Err(km_err!( |
| UnknownError, |
| "Didn't find a slot and can't grow the file larger than {}", |
| original_file_size |
| )); |
| } |
| let new_size = original_file_size + KM_BLOCK_SIZE; |
| debug!("Attempting to resize file from {} to {}", original_file_size, new_size); |
| if let Err(e) = sdsf_file.resize(new_size) { |
| error!("Failed to grow file to make room for a key slot"); |
| return Err(e); |
| } |
| debug!("Resized file to {}", new_size); |
| if let Err(e) = sdsf_file.zero_entries(original_file_size, new_size) { |
| error!("Error zeroing space in extended file"); |
| return Err(e); |
| } |
| empty_slot = original_file_size / KM_SECRET_SIZE; |
| } |
| debug!("Writing new deletion secret to key slot {}", empty_slot); |
| if let Err(e) = sdsf_file |
| .write_block(empty_slot * KM_SECRET_SIZE, &secure_deletion_data.secure_deletion_secret) |
| { |
| error!("Failed to write new deletion secret to key slot {}", empty_slot); |
| return Err(e); |
| } |
| if let Err(e) = sdsf_file.finish_transaction() { |
| error!( |
| "Failed to commit transaction writing new deletion secret to slot {}", |
| empty_slot |
| ); |
| return Err(e); |
| } |
| debug!("Committed new secret"); |
| Ok((SecureDeletionSlot(empty_slot as u32), secure_deletion_data)) |
| } |
| |
| fn get_secret(&self, slot: SecureDeletionSlot) -> Result<SecureDeletionData, Error> { |
| let mut current_try = 0; |
| let mut secure_deletion_data = loop { |
| let data = self.get_factory_reset_secret(); |
| if (data.is_ok()) || (current_try >= KM_MAX_TRIES) { |
| break data?; |
| } |
| current_try += 1; |
| }; |
| let requested_slot = slot.0 as usize; |
| // TODO: Should we also limit access to slot 1? slot 1 should be part of the factory reset |
| // secret, but c++ code only checked for slot 0. |
| if requested_slot == 0 { |
| // Original debug message from c++ code was "Secure deletion not requested" |
| debug!("Requested deletion of slot 0 which corresponds to factory reset secret."); |
| return Err(km_err!( |
| InvalidArgument, |
| "Requested slot 0 which does not contain a secret" |
| )); |
| } |
| |
| current_try = 0; |
| loop { |
| match self.read_slot_data(slot, &mut secure_deletion_data.secure_deletion_secret) { |
| Ok(_) => { |
| debug!( |
| "Read secure deletion secret, size: {}", |
| secure_deletion_data.secure_deletion_secret.len() |
| ); |
| break Ok(secure_deletion_data); |
| } |
| Err(e) => { |
| if (current_try >= KM_MAX_TRIES) { |
| break Err(e); |
| } |
| } |
| } |
| current_try += 1; |
| } |
| } |
| |
| fn delete_secret(&mut self, slot: SecureDeletionSlot) -> Result<(), Error> { |
| let requested_slot = slot.0 as usize; |
| // TODO: Should we also limit access to slot 1? slot 1 should be part of the factory reset |
| // secret, but c++ code only checked for slot 0. |
| if requested_slot == 0 { |
| debug!("key_slot == 0, nothing to delete"); |
| return Err(km_err!( |
| InvalidArgument, |
| "Requested slot 0 which does not contain a secret" |
| )); |
| } |
| let key_slot_start = requested_slot * KM_SECRET_SIZE; |
| let key_slot_end = key_slot_start + KM_SECRET_SIZE; |
| if (key_slot_start < KM_FACTORY_FIRST_SECURE_DELETION_SECRET_POS) { |
| return Err(km_err!( |
| InvalidArgument, |
| "Attempted to delete invalid key slot {}", |
| requested_slot |
| )); |
| } |
| // TODO: Check if we should also stop trying to delete the key after some number of retries. |
| // C++ code doesn't stop retrying, which is the current behavior here. |
| loop { |
| let mut session = match SecureDeletionSecretFileSession::new(true) { |
| Ok(mut session) => session, |
| Err(_) => { |
| error!("Failed to open session to retrieve secure deletion data"); |
| continue; |
| } |
| }; |
| let mut sdsf_file = match SecureDeletionSecretFile::open_or_create(&mut session) { |
| Ok(mut sdsf_file) => sdsf_file, |
| Err(_) => { |
| error!("Failed to open file to retrieve secure deletion data"); |
| continue; |
| } |
| }; |
| let file_size = match sdsf_file.get_file_size() { |
| Ok(size) => size, |
| Err(_) => continue, |
| }; |
| if key_slot_end > file_size { |
| return Err(km_err!( |
| InvalidArgument, |
| "Attempted to delete invalid key slot {}", |
| requested_slot |
| )); |
| } |
| if let Err(_) = sdsf_file.zero_entries(key_slot_start, key_slot_end) { |
| continue; |
| } |
| debug!( |
| "Deleted secure key slot {}, zeroing {} to {}", |
| requested_slot, key_slot_start, key_slot_end |
| ); |
| if let Err(_) = sdsf_file.finish_transaction() { |
| error!("Failed to commit transaction deleting key at slot {}", requested_slot); |
| continue; |
| } |
| debug!("Committed deletion"); |
| break; |
| } |
| Ok(()) |
| } |
| |
| fn delete_all(&mut self) { |
| // TODO: Check if we should also stop trying to delete all keys after some number of |
| // retries. C++ code doesn't stop retrying, which is the current behavior here. |
| loop { |
| let mut session = match SecureDeletionSecretFileSession::new(true) { |
| Ok(mut session) => session, |
| Err(_) => { |
| error!("Failed to open session to delete secrets file"); |
| continue; |
| } |
| }; |
| if session.delete_file().is_ok() { |
| break; |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::*; |
| use kmr_crypto_boring::rng::BoringRng; |
| use test::{expect, expect_eq, expect_ne}; |
| |
| fn check_secret_manager_file_exists() -> bool { |
| let mut session = |
| Session::new(Port::TamperProof, true).expect("Couldn't connect to storage"); |
| session.open_file(KM_SECURE_DELETION_SECRET_FILENAME, OpenMode::Open).is_ok() |
| } |
| |
| #[test] |
| fn secret_data_is_cached() { |
| let mut sdsf = TrustySecureDeletionSecretManager::new(); |
| sdsf.delete_all(); |
| expect!(check_secret_manager_file_exists() == false, "Couldn't delete secret manager file"); |
| let mut rng = BoringRng::default(); |
| let secret1 = |
| sdsf.get_or_create_factory_reset_secret(&mut rng).expect("Couldn't create secret"); |
| let mut session = SecureDeletionSecretFileSession::new(true).ok(); |
| let secret2 = match sdsf.get_factory_reset_secret_impl(session.as_mut()) { |
| Ok(RetrieveSecureDeletionSecretFileData::CachedDataFound(secret)) => secret, |
| _ => panic!("Data should have been cached"), |
| }; |
| let secret3 = |
| sdsf.get_or_create_factory_reset_secret(&mut rng).expect("Couldn't get secret"); |
| expect_eq!( |
| secret1.factory_reset_secret, |
| secret2.factory_reset_secret, |
| "Should have retrieved the same secret" |
| ); |
| expect_eq!( |
| secret1.secure_deletion_secret, |
| secret2.secure_deletion_secret, |
| "Should have retrieved the same secret" |
| ); |
| expect_eq!( |
| secret1.secure_deletion_secret, |
| [0; KM_SECRET_SIZE], |
| "Deletion secret should be 0" |
| ); |
| expect_ne!( |
| secret1.factory_reset_secret, |
| [0; KM_FACTORY_RESET_SECRET_SIZE], |
| "Factory reset secret should not be 0" |
| ); |
| expect_eq!( |
| secret1.factory_reset_secret, |
| secret3.factory_reset_secret, |
| "Should have retrieved the same secret" |
| ); |
| sdsf.factory_reset_secret.replace(FactoryResetSecret(None)); |
| let secret3 = match sdsf.get_factory_reset_secret_impl(session.as_mut()) { |
| Ok(RetrieveSecureDeletionSecretFileData::DataFoundOnFile(secret)) => secret, |
| _ => panic!("Data couldn't be read from file."), |
| }; |
| expect_eq!( |
| secret1.factory_reset_secret, |
| secret3.factory_reset_secret, |
| "Should have retrieved the same secret" |
| ); |
| sdsf.delete_all(); |
| expect!(check_secret_manager_file_exists() == false, "Couldn't delete secret manager file"); |
| } |
| |
| #[test] |
| fn new_secret_data_file_is_clean() { |
| let mut sdsf = TrustySecureDeletionSecretManager::new(); |
| sdsf.delete_all(); |
| expect!(check_secret_manager_file_exists() == false, "Couldn't delete secret manager file"); |
| let mut rng = BoringRng::default(); |
| let secret1 = |
| sdsf.get_or_create_factory_reset_secret(&mut rng).expect("Couldn't create secret"); |
| let num_initial_slots = KM_BLOCK_SIZE / KM_SECRET_SIZE; |
| for slot_num in (2..num_initial_slots) { |
| let secret = |
| sdsf.get_secret(SecureDeletionSlot(slot_num as u32)).expect("Couldn't read slot"); |
| expect_eq!( |
| secret.secure_deletion_secret, |
| [0; KM_SECRET_SIZE], |
| "Deletion secret should be 0" |
| ); |
| expect_eq!( |
| secret.factory_reset_secret, |
| secret1.factory_reset_secret, |
| "Factory reset secret should match" |
| ); |
| } |
| let secret = sdsf.get_secret(SecureDeletionSlot(num_initial_slots as u32)); |
| expect!(secret.is_err(), "Read outside of initial range should fail"); |
| sdsf.delete_all(); |
| expect!(check_secret_manager_file_exists() == false, "Couldn't delete secret manager file"); |
| } |
| |
| #[test] |
| fn new_secret_data_file_expands() { |
| let mut sdsf = TrustySecureDeletionSecretManager::new(); |
| sdsf.delete_all(); |
| expect!(check_secret_manager_file_exists() == false, "Couldn't delete secret manager file"); |
| let mut rng = BoringRng::default(); |
| let secret1 = |
| sdsf.get_or_create_factory_reset_secret(&mut rng).expect("Couldn't create secret"); |
| let num_slots_per_block = KM_BLOCK_SIZE / KM_SECRET_SIZE; |
| let max_num_slots = KM_MAX_SECRET_FILE_SIZE / KM_SECRET_SIZE; |
| for slot_num in (2..max_num_slots) { |
| let (deletion_slot, deletion_data) = sdsf |
| .new_secret(&mut rng, SlotPurpose::KeyGeneration) |
| .expect("Couldn't create secret"); |
| // This test assumes order of secret creation on an empty file; next line can be changed |
| // to something like a map (to check that an empty slot if chosen every time) if order |
| // is not sequential anymore. |
| expect_eq!(deletion_slot.0 as usize, slot_num, "Wrong slot used for new secret"); |
| expect_ne!( |
| deletion_data.secure_deletion_secret, |
| [0; KM_SECRET_SIZE], |
| "Deletion secret should not be 0" |
| ); |
| expect_ne!( |
| deletion_data.secure_deletion_secret[0] & KM_IN_USE_FLAG, |
| 0, |
| "Slot should be marked as in use" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot).expect("Couldn't read back secret"); |
| expect_eq!( |
| deletion_data.secure_deletion_secret, |
| slot_data.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| expect_eq!( |
| deletion_data.factory_reset_secret, |
| slot_data.factory_reset_secret, |
| "Factory reset secret should match" |
| ); |
| } |
| let size_failure = sdsf.new_secret(&mut rng, SlotPurpose::KeyGeneration); |
| expect!(size_failure.is_err(), "Shouldn't be able to increase secret file size any larger"); |
| //Testing upgrade flow |
| let max_num_upgrade_slots = (KM_MAX_SECRET_FILE_SIZE_FOR_UPGRADES) / KM_SECRET_SIZE; |
| for slot_num in (max_num_slots..max_num_upgrade_slots) { |
| let (deletion_slot, deletion_data) = sdsf |
| .new_secret(&mut rng, SlotPurpose::KeyUpgrade) |
| .expect("Couldn't create secret for upgrade flow"); |
| expect_eq!(deletion_slot.0 as usize, slot_num, "Wrong slot used for new secret"); |
| expect_ne!( |
| deletion_data.secure_deletion_secret, |
| [0; KM_SECRET_SIZE], |
| "Deletion secret should not be 0" |
| ); |
| expect_ne!( |
| deletion_data.secure_deletion_secret[0] & KM_IN_USE_FLAG, |
| 0, |
| "Slot should be marked as in use" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot).expect("Couldn't read back secret"); |
| expect_eq!( |
| deletion_data.secure_deletion_secret, |
| slot_data.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| expect_eq!( |
| deletion_data.factory_reset_secret, |
| slot_data.factory_reset_secret, |
| "Factory reset secret should match" |
| ); |
| } |
| let size_failure = sdsf.new_secret(&mut rng, SlotPurpose::KeyUpgrade); |
| expect!(size_failure.is_err(), "Shouldn't be able to increase secret file size any larger"); |
| //Testing deletion |
| for slot_num in (2..max_num_upgrade_slots).rev() { |
| let slot = SecureDeletionSlot(slot_num as u32); |
| sdsf.delete_secret(slot).expect("Couldn't delete secret"); |
| let slot_data = sdsf.get_secret(slot).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| [0; KM_SECRET_SIZE], |
| "Deletion secret should be 0" |
| ); |
| let (deletion_slot, deletion_data) = |
| sdsf.new_secret(&mut rng, SlotPurpose::KeyUpgrade).expect("Couldn't create secret"); |
| expect_eq!(deletion_slot.0 as usize, slot_num, "Wrong slot used for new secret"); |
| expect_ne!( |
| deletion_data.secure_deletion_secret, |
| [0; KM_SECRET_SIZE], |
| "Deletion secret should not be 0" |
| ); |
| expect_ne!( |
| deletion_data.secure_deletion_secret[0] & KM_IN_USE_FLAG, |
| 0, |
| "Slot should be marked as in use" |
| ); |
| } |
| sdsf.delete_all(); |
| expect!(check_secret_manager_file_exists() == false, "Couldn't delete secret manager file"); |
| } |
| |
| #[test] |
| fn new_secret_data_dont_affect_neighbors() { |
| let mut sdsf = TrustySecureDeletionSecretManager::new(); |
| sdsf.delete_all(); |
| expect!(check_secret_manager_file_exists() == false, "Couldn't delete secret manager file"); |
| let mut rng = BoringRng::default(); |
| let reset_secret = sdsf |
| .get_or_create_factory_reset_secret(&mut rng) |
| .expect("Couldn't create factory reset secret"); |
| let (deletion_slot_1, deletion_data_1) = |
| sdsf.new_secret(&mut rng, SlotPurpose::KeyGeneration).expect("Couldn't create secret"); |
| sdsf.delete_secret(deletion_slot_1).expect("Couldn't delete secret"); |
| // Delete cached data |
| sdsf.factory_reset_secret.replace(FactoryResetSecret(None)); |
| let reset_secret_1 = |
| sdsf.get_factory_reset_secret().expect("Couldn't get factory reset secret"); |
| expect_eq!( |
| reset_secret.factory_reset_secret, |
| reset_secret_1.factory_reset_secret, |
| "Factory reset secret should match" |
| ); |
| let (deletion_slot_1, deletion_data_1) = |
| sdsf.new_secret(&mut rng, SlotPurpose::KeyGeneration).expect("Couldn't create secret"); |
| let (deletion_slot_2, deletion_data_2) = |
| sdsf.new_secret(&mut rng, SlotPurpose::KeyGeneration).expect("Couldn't create secret"); |
| let (deletion_slot_3, deletion_data_3) = |
| sdsf.new_secret(&mut rng, SlotPurpose::KeyGeneration).expect("Couldn't create secret"); |
| let (deletion_slot_4, deletion_data_4) = |
| sdsf.new_secret(&mut rng, SlotPurpose::KeyGeneration).expect("Couldn't create secret"); |
| let (deletion_slot_5, deletion_data_5) = |
| sdsf.new_secret(&mut rng, SlotPurpose::KeyGeneration).expect("Couldn't create secret"); |
| let slot_data = sdsf.get_secret(deletion_slot_1).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_1.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot_2).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_2.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot_3).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_3.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot_4).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_4.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot_5).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_5.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| sdsf.delete_secret(deletion_slot_3).expect("Couldn't delete secret"); |
| let slot_data = sdsf.get_secret(deletion_slot_1).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_1.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot_2).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_2.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot_3).expect("Couldn't read back secret"); |
| expect_ne!( |
| slot_data.secure_deletion_secret, |
| deletion_data_3.secure_deletion_secret, |
| "Secret data should not match anymore" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot_4).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_4.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| let slot_data = sdsf.get_secret(deletion_slot_5).expect("Couldn't read back secret"); |
| expect_eq!( |
| slot_data.secure_deletion_secret, |
| deletion_data_5.secure_deletion_secret, |
| "Secret data should match" |
| ); |
| // Delete cached data |
| sdsf.factory_reset_secret.replace(FactoryResetSecret(None)); |
| let reset_secret_1 = |
| sdsf.get_factory_reset_secret().expect("Couldn't get factory reset secret"); |
| expect_eq!( |
| reset_secret.factory_reset_secret, |
| reset_secret_1.factory_reset_secret, |
| "Factory reset secret should match" |
| ); |
| sdsf.delete_all(); |
| expect!(check_secret_manager_file_exists() == false, "Couldn't delete secret manager file"); |
| } |
| } |
| |
| /// Wrapper to allow a single instance of [`SecureDeletionSecretManager`] to be shared. |
| #[derive(Clone)] |
| pub struct SharedSddManager<T> { |
| inner: Rc<RefCell<T>>, |
| } |
| |
| impl<T> SharedSddManager<T> { |
| /// Move a [`SecureDeletionSecretManager`] into a shareable wrapper. |
| pub fn new(inner: T) -> Self { |
| Self { inner: Rc::new(RefCell::new(inner)) } |
| } |
| } |
| |
| impl<T: SecureDeletionSecretManager> SecureDeletionSecretManager for SharedSddManager<T> { |
| fn get_or_create_factory_reset_secret( |
| &mut self, |
| rng: &mut dyn crypto::Rng, |
| ) -> Result<SecureDeletionData, Error> { |
| self.inner.borrow_mut().get_or_create_factory_reset_secret(rng) |
| } |
| |
| fn get_factory_reset_secret(&self) -> Result<SecureDeletionData, Error> { |
| self.inner.borrow_mut().get_factory_reset_secret() |
| } |
| |
| fn new_secret( |
| &mut self, |
| rng: &mut dyn crypto::Rng, |
| purpose: kmr_common::keyblob::SlotPurpose, |
| ) -> Result<(SecureDeletionSlot, SecureDeletionData), Error> { |
| self.inner.borrow_mut().new_secret(rng, purpose) |
| } |
| |
| fn get_secret(&self, slot: SecureDeletionSlot) -> Result<SecureDeletionData, Error> { |
| self.inner.borrow().get_secret(slot) |
| } |
| |
| fn delete_secret(&mut self, slot: SecureDeletionSlot) -> Result<(), Error> { |
| self.inner.borrow_mut().delete_secret(slot) |
| } |
| |
| fn delete_all(&mut self) { |
| self.inner.borrow_mut().delete_all() |
| } |
| } |