summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSami Tolvanen <samitolvanen@google.com>2024-04-26 22:14:45 +0000
committerSami Tolvanen <samitolvanen@google.com>2024-04-30 20:11:41 +0000
commita836831733d124787619ad712a1cb7e599bc6506 (patch)
treee8bf9a5abd6746651ce67e18c2c7303c21771572
parent72fb743cfe30f2de2ab06e0eccb030ebaf0bf8e7 (diff)
downloadkeymint-a836831733d124787619ad712a1cb7e599bc6506.tar.gz
Implement SecureDeletionSecretManager
Adapt Trusty's SecureDeletionSecretManager to OP-TEE. Test: make ta + unit tests Change-Id: Ia5d5a5cd1964990746bf29c3396bed971c5a01c5
-rw-r--r--src/main.rs4
-rw-r--r--src/storage.rs16
-rw-r--r--src/storage/sdsm.rs885
3 files changed, 904 insertions, 1 deletions
diff --git a/src/main.rs b/src/main.rs
index cd72eae..31198c1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -26,6 +26,7 @@ mod crypto;
mod error;
mod invoke;
mod software;
+mod storage;
mod stubs;
use alloc::boxed::Box;
@@ -87,7 +88,7 @@ fn keymint_ta() -> &'static Mutex<SingleThreadedTa> {
keys: Box::new(software::keys::Keys),
sign_info: Some(Box::new(software::attest::CertSignInfo::new())),
attest_ids: None,
- sdd_mgr: None,
+ sdd_mgr: Some(Box::new(storage::sdsm::SecureDeletionSecretManager::new())),
bootloader: Box::new(kmr_ta::device::BootloaderDone),
sk_wrapper: None,
tup: Box::new(kmr_ta::device::TrustedPresenceUnsupported),
@@ -171,6 +172,7 @@ fn invoke_command(
#[cfg(feature = "dev")]
command::RUN_TEST => {
crypto::tests::run();
+ storage::sdsm::tests::run();
Ok(())
}
#[cfg(feature = "dev")]
diff --git a/src/storage.rs b/src/storage.rs
new file mode 100644
index 0000000..e26fdee
--- /dev/null
+++ b/src/storage.rs
@@ -0,0 +1,16 @@
+//
+// Copyright (C) 2024 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.
+
+pub mod sdsm;
diff --git a/src/storage/sdsm.rs b/src/storage/sdsm.rs
new file mode 100644
index 0000000..c2d8a07
--- /dev/null
+++ b/src/storage/sdsm.rs
@@ -0,0 +1,885 @@
+//
+// Copyright (C) 2024 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.
+//! Based on the Trusty implementation (app/keymint/secure_deletion_secret_manager.rs).
+
+use crate::error::Error;
+use core::{cell::RefCell, cmp, ops::DerefMut};
+use kmr_common::{
+ crypto, keyblob,
+ keyblob::{SecureDeletionData, SecureDeletionSlot, SlotPurpose},
+ km_err,
+};
+use log::{debug, error, info, warn};
+use optee_utee::{DataFlag, ObjectStorageConstants, PersistentObject, Whence};
+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 MAX_TRIES: usize = 3;
+
+// Each secret is 16 bytes.
+const 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 FACTORY_RESET_SECRET_SIZE: usize = SECRET_SIZE * 2;
+const FACTORY_RESET_SECRET_POS: usize = 0;
+const FACTORY_FIRST_SECURE_DELETION_SECRET_POS: usize =
+ FACTORY_RESET_SECRET_POS + FACTORY_RESET_SECRET_SIZE;
+
+// We read secrets in blocks of 32, so 512 bytes.
+const BLOCK_SIZE: usize = SECRET_SIZE * 32;
+
+// Limit file size to 16 KiB (except for key upgrades, see
+// MAX_SECRET_FILE_SIZE_FOR_UPGRADES).
+const MAX_SECRET_FILE_SIZE: usize = BLOCK_SIZE * 32;
+
+// This is a higher file size limit, with the space above 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 MAX_SECRET_FILE_SIZE_FOR_UPGRADES: usize = MAX_SECRET_FILE_SIZE + 8 * 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 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 SECURE_DELETION_SECRET_FILENAME: &'static str = "SecureDeletionSecrets_1";
+
+// TODO: Add crate static_assertions to trusty to replace these with static_assert!
+const _: () = assert!(
+ BLOCK_SIZE >= FACTORY_RESET_SECRET_SIZE,
+ "BLOCK_SIZE should be bigger than FACTORY_RESET_SECRET_SIZE"
+);
+const _: () = assert!((BLOCK_SIZE % SECRET_SIZE) == 0, "Broke find_empty_slot assumption");
+const _: () =
+ assert!((FACTORY_RESET_SECRET_SIZE % SECRET_SIZE) == 0, "Broke find_empty_slot assumption");
+
+fn delete_secure_deletion_secret_file() -> Result<(), kmr_common::Error> {
+ match PersistentObject::open(
+ ObjectStorageConstants::Private,
+ SECURE_DELETION_SECRET_FILENAME.as_bytes(),
+ DataFlag::ACCESS_WRITE_META,
+ ) {
+ Ok(mut object) => object.close_and_delete().map_err(Error::kmerr),
+ Err(e) => match e.kind() {
+ optee_utee::ErrorKind::ItemNotFound => Ok(()),
+ _ => Err(Error::kmerr(e)),
+ },
+ }
+}
+
+enum RetrieveSecureDeletionSecretFileData {
+ EmptyFileFound(SecureDeletionSecretFile),
+ CachedDataFound(SecureDeletionData),
+ DataFoundOnFile(SecureDeletionData),
+}
+
+struct SecureDeletionSecretFile {
+ object: PersistentObject,
+}
+
+impl SecureDeletionSecretFile {
+ fn create() -> Result<SecureDeletionSecretFile, kmr_common::Error> {
+ let object = PersistentObject::create(
+ ObjectStorageConstants::Private,
+ SECURE_DELETION_SECRET_FILENAME.as_bytes(),
+ DataFlag::ACCESS_READ | DataFlag::ACCESS_WRITE,
+ None,
+ &[],
+ )
+ .map_err(Error::kmerr)?;
+
+ Ok(SecureDeletionSecretFile { object: object })
+ }
+
+ fn open_or_create() -> Result<SecureDeletionSecretFile, kmr_common::Error> {
+ match PersistentObject::open(
+ ObjectStorageConstants::Private,
+ SECURE_DELETION_SECRET_FILENAME.as_bytes(),
+ DataFlag::ACCESS_READ | DataFlag::ACCESS_WRITE,
+ ) {
+ Ok(object) => Ok(SecureDeletionSecretFile { object: object }),
+ Err(e) => match e.kind() {
+ optee_utee::ErrorKind::ItemNotFound => SecureDeletionSecretFile::create(),
+ _ => Err(Error::kmerr(e)),
+ },
+ }
+ }
+
+ fn read_block<'buf>(
+ &mut self,
+ start: usize,
+ buffer: &'buf mut [u8],
+ ) -> Result<&'buf [u8], kmr_common::Error> {
+ let req_len = buffer.len();
+
+ self.object.seek(start as i32, Whence::DataSeekSet).map_err(Error::kmerr)?;
+ let len = self.object.read(buffer).map_err(Error::kmerr)?;
+
+ if len as usize != req_len {
+ Err(km_err!(
+ UnknownError,
+ "couldn't read {} bytes of secure deletion secret file at offset {}. Read {} bytes",
+ req_len,
+ start,
+ len
+ ))
+ } else {
+ Ok(buffer)
+ }
+ }
+
+ // Find empty slot is used to find the first [SECRET_SIZE] position on the secure file that
+ // isn't currently in use. For this it will read the secure file in [BLOCK_SIZE] chunks and
+ // move in [SECRET_SIZE] increments; checking if the IN_USE_FLAG is set on that position.
+ fn find_empty_slot(&mut self, is_upgrade: bool) -> Result<Option<usize>, kmr_common::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 block_buffer = [0; BLOCK_SIZE];
+ for start_pos in (0..end).step_by(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);
+ }
+ };
+ // Code assumes that we always read a complete block. API called could potentially
+ // return less data.
+ if read_data.len() != 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 {
+ FACTORY_RESET_SECRET_POS => FACTORY_FIRST_SECURE_DELETION_SECRET_POS,
+ _ => 0,
+ };
+
+ for (chunk_num, secret) in read_data[block_start..].chunks(SECRET_SIZE).enumerate() {
+ if (secret[0] & IN_USE_FLAG) == 0 {
+ let key_slot =
+ (start_pos + block_start + (chunk_num * SECRET_SIZE)) / SECRET_SIZE;
+ return Ok(Some(key_slot));
+ }
+ }
+ }
+ Ok(None)
+ }
+
+ fn write_block(&mut self, start: usize, buffer: &[u8]) -> Result<(), kmr_common::Error> {
+ self.object.seek(start as i32, Whence::DataSeekSet).map_err(Error::kmerr)?;
+ self.object.write(buffer).map_err(Error::kmerr)
+ }
+
+ fn get_file_size(&mut self) -> Result<usize, kmr_common::Error> {
+ Ok(self.object.info().map_err(Error::kmerr)?.data_size())
+ }
+
+ fn get_max_file_size(is_upgrade: bool) -> usize {
+ match is_upgrade {
+ true => MAX_SECRET_FILE_SIZE_FOR_UPGRADES,
+ false => MAX_SECRET_FILE_SIZE,
+ }
+ }
+
+ fn resize(&mut self, new_size: usize) -> Result<(), kmr_common::Error> {
+ self.object.truncate(new_size as u32).map_err(Error::kmerr)
+ }
+
+ fn zero_entries(&mut self, begin: usize, end: usize) -> Result<(), kmr_common::Error> {
+ if (begin % SECRET_SIZE) != 0 {
+ return Err(km_err!(
+ InvalidArgument,
+ "zero_entries called with invalid offset {}",
+ begin
+ ));
+ }
+ let zero_buff = [0; SECRET_SIZE];
+ for start_pos in (begin..end).step_by(SECRET_SIZE) {
+ self.write_block(start_pos, &zero_buff)?;
+ }
+ Ok(())
+ }
+}
+
+#[derive(Clone, PartialEq, Eq, Zeroize, ZeroizeOnDrop)]
+struct FactoryResetSecret([u8; FACTORY_RESET_SECRET_SIZE]);
+
+#[derive(Clone, PartialEq, Eq)]
+pub struct SecureDeletionSecretManager {
+ factory_reset_secret: RefCell<Option<FactoryResetSecret>>,
+}
+
+impl SecureDeletionSecretManager {
+ pub fn new() -> Self {
+ SecureDeletionSecretManager { factory_reset_secret: RefCell::new(None) }
+ }
+
+ fn get_factory_reset_secret_impl(
+ &self,
+ ) -> Result<RetrieveSecureDeletionSecretFileData, kmr_common::Error> {
+ // Checking if we already have a cached secret we can return
+ if let Some(secret) = self.factory_reset_secret.borrow_mut().deref_mut() {
+ return Ok(RetrieveSecureDeletionSecretFileData::CachedDataFound(SecureDeletionData {
+ factory_reset_secret: secret.0.clone(),
+ secure_deletion_secret: [0; SECRET_SIZE],
+ }));
+ }
+
+ // Looking now if we can read the secret from file
+ let mut sdsf_file = SecureDeletionSecretFile::open_or_create()?;
+ let file_size = sdsf_file.get_file_size().map_err(|e| {
+ km_err!(
+ SecureHwCommunicationFailed,
+ "couldn't get secure deletion secret file size: {:?}",
+ e
+ )
+ })?;
+
+ // Found an empty file
+ if file_size <= 0 {
+ return Ok(RetrieveSecureDeletionSecretFileData::EmptyFileFound(sdsf_file));
+ }
+
+ // The file isn't empty, read the secret and cache it before returning the data
+ debug!("Opened non-empty secure secrets file");
+ let mut buffer = [0; FACTORY_RESET_SECRET_SIZE];
+ let block = sdsf_file.read_block(FACTORY_RESET_SECRET_POS, &mut buffer)?;
+ debug!("Read factory-reset secret, size {}", block.len());
+ if block.len() != FACTORY_RESET_SECRET_SIZE {
+ return Err(km_err!(
+ SecureHwCommunicationFailed,
+ "failed to read complete secret data from storage. Received {} bytes",
+ block.len()
+ ));
+ }
+ self.factory_reset_secret
+ .borrow_mut()
+ .deref_mut()
+ .replace(FactoryResetSecret(buffer.clone()));
+ Ok(RetrieveSecureDeletionSecretFileData::DataFoundOnFile(SecureDeletionData {
+ factory_reset_secret: buffer,
+ secure_deletion_secret: [0; SECRET_SIZE],
+ }))
+ }
+
+ // 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,
+ ) -> Result<SecureDeletionData, kmr_common::Error> {
+ let secret_file_data = self.get_factory_reset_secret_impl()?;
+ match secret_file_data {
+ RetrieveSecureDeletionSecretFileData::CachedDataFound(data) => Ok(data),
+ RetrieveSecureDeletionSecretFileData::DataFoundOnFile(data) => Ok(data),
+ RetrieveSecureDeletionSecretFileData::EmptyFileFound(mut sdsf_file) => {
+ sdsf_file.resize(BLOCK_SIZE)?;
+ debug!("Resized secure secrets file to size {}", BLOCK_SIZE);
+ let mut buffer = [0; FACTORY_RESET_SECRET_SIZE];
+ rng.fill_bytes(&mut buffer);
+ sdsf_file.write_block(FACTORY_RESET_SECRET_POS, &buffer)?;
+ debug!("Wrote new factory reset secret");
+ sdsf_file.zero_entries(FACTORY_FIRST_SECURE_DELETION_SECRET_POS, BLOCK_SIZE)?;
+ debug!("Zeroed secrets");
+ debug!("Committed new secrets file");
+ self.factory_reset_secret
+ .borrow_mut()
+ .deref_mut()
+ .replace(FactoryResetSecret(buffer.clone()));
+ Ok(SecureDeletionData {
+ factory_reset_secret: buffer,
+ secure_deletion_secret: [0; SECRET_SIZE],
+ })
+ }
+ }
+ }
+
+ fn read_slot_data(
+ &self,
+ slot: SecureDeletionSlot,
+ buffer: &mut [u8],
+ ) -> Result<(), kmr_common::Error> {
+ let buffer_size = buffer.len();
+ if buffer_size != SECRET_SIZE {
+ return Err(km_err!(
+ InsufficientBufferSpace,
+ "needed {} bytes to read slot, received {}",
+ SECRET_SIZE,
+ buffer.len()
+ ));
+ }
+ let requested_slot = slot.0 as usize;
+ let key_slot_pos = requested_slot * SECRET_SIZE;
+ let mut sdsf_file = match SecureDeletionSecretFile::open_or_create() {
+ Ok(sdsf_file) => sdsf_file,
+ Err(e) => {
+ error!("Failed to open file to get secure deletion data: {:?}", e);
+ 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: {:?}", e);
+ return Err(e);
+ }
+ };
+ if (key_slot_pos + 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, e);
+ Err(e)
+ }
+ }
+ }
+}
+
+impl Drop for SecureDeletionSecretManager {
+ fn drop(&mut self) {
+ self.factory_reset_secret.borrow_mut().deref_mut().zeroize();
+ }
+}
+
+impl ZeroizeOnDrop for SecureDeletionSecretManager {}
+
+impl keyblob::SecureDeletionSecretManager for SecureDeletionSecretManager {
+ fn get_or_create_factory_reset_secret(
+ &mut self,
+ rng: &mut dyn crypto::Rng,
+ ) -> Result<SecureDeletionData, kmr_common::Error> {
+ self.get_or_create_factory_reset_secret_impl(rng)
+ }
+
+ fn get_factory_reset_secret(&self) -> Result<SecureDeletionData, kmr_common::Error> {
+ let secret_file_data = self.get_factory_reset_secret_impl()?;
+ 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), kmr_common::Error> {
+ let is_upgrade = slot_purpose == SlotPurpose::KeyUpgrade;
+ let mut secure_deletion_data = match self.get_or_create_factory_reset_secret_impl(rng) {
+ Ok(data) => data,
+ Err(e) => {
+ info!("Unable to get factory reset secret: {:?}", e);
+ return Err(e);
+ }
+ };
+ rng.fill_bytes(&mut secure_deletion_data.secure_deletion_secret);
+ secure_deletion_data.secure_deletion_secret[0] |= 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 sdsf_file = SecureDeletionSecretFile::open_or_create()?;
+ let empty_slot = match sdsf_file.find_empty_slot(is_upgrade) {
+ Ok(slot) => slot,
+ Err(e) => {
+ error!("Error while searching for key slot: {:?}", e);
+ return Err(e);
+ }
+ };
+
+ let original_file_size = sdsf_file.get_file_size()?;
+ let empty_slot = match empty_slot {
+ Some(slot_number) => slot_number,
+ None => {
+ // 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 + 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: {:?}", e);
+ 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: {:?}", e);
+ return Err(e);
+ }
+ let slot_number = original_file_size / SECRET_SIZE;
+ slot_number
+ }
+ };
+
+ debug!("Writing new deletion secret to key slot {}", empty_slot);
+ if let Err(e) = sdsf_file
+ .write_block(empty_slot * SECRET_SIZE, &secure_deletion_data.secure_deletion_secret)
+ {
+ error!("Failed to write new deletion secret to key slot {}: {:?}", empty_slot, e);
+ return Err(e);
+ }
+ debug!("Committed new secret");
+ Ok((SecureDeletionSlot(empty_slot as u32), secure_deletion_data))
+ }
+
+ fn get_secret(
+ &self,
+ slot: SecureDeletionSlot,
+ ) -> Result<SecureDeletionData, kmr_common::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 >= MAX_TRIES) {
+ break data?;
+ }
+ current_try += 1;
+ };
+ let requested_slot = slot.0 as usize;
+ if requested_slot == 0 || requested_slot == 1 {
+ warn!("Requested slot {} which corresponds to factory reset secret", requested_slot);
+ return Err(km_err!(
+ InvalidArgument,
+ "requested slot {} which does not contain a per-key secret",
+ requested_slot
+ ));
+ }
+
+ 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 >= MAX_TRIES {
+ break Err(e);
+ }
+ }
+ }
+ current_try += 1;
+ }
+ }
+
+ fn delete_secret(&mut self, slot: SecureDeletionSlot) -> Result<(), kmr_common::Error> {
+ let requested_slot = slot.0 as usize;
+ if requested_slot == 0 || requested_slot == 1 {
+ debug!("key_slot == {}, nothing to delete", requested_slot);
+ return Err(km_err!(
+ InvalidArgument,
+ "requested slot {} which does not contain a per-key secret",
+ requested_slot
+ ));
+ }
+ let key_slot_start = requested_slot * SECRET_SIZE;
+ let key_slot_end = key_slot_start + SECRET_SIZE;
+ if key_slot_start < FACTORY_FIRST_SECURE_DELETION_SECRET_POS {
+ // This should be unhittable (the test for slot 0 or 1 above will trigger) but keep
+ // in just in case of future arithmetic errors.
+ 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 sdsf_file = match SecureDeletionSecretFile::open_or_create() {
+ Ok(sdsf_file) => sdsf_file,
+ Err(e) => {
+ error!("Failed to open file to retrieve secure deletion data: {:?}", e);
+ 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
+ );
+ 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 {
+ match delete_secure_deletion_secret_file() {
+ Ok(_) => break,
+ Err(e) => error!("Couldn't delete file. Received error: {:?}", e),
+ }
+ }
+ }
+}
+
+#[cfg(feature = "dev")]
+pub mod tests {
+ use super::*;
+ use crate::crypto::rng::Rng;
+ use kmr_common::keyblob::SecureDeletionSecretManager;
+ use kmr_common::keyblob::SlotPurpose;
+
+ pub fn run() {
+ info!("sdsm::tests::run: Starting");
+
+ secret_data_is_cached();
+ new_secret_data_file_is_clean();
+ new_secret_data_file_expands();
+ new_secret_data_dont_affect_neighbors();
+
+ info!("sdsm::tests::run: Done");
+ }
+
+ fn secret_manager_file_exists() -> bool {
+ PersistentObject::open(
+ ObjectStorageConstants::Private,
+ SECURE_DELETION_SECRET_FILENAME.as_bytes(),
+ DataFlag::ACCESS_READ,
+ )
+ .is_ok()
+ }
+
+ fn secret_data_is_cached() {
+ let mut sdsf = super::SecureDeletionSecretManager::new();
+ sdsf.delete_all();
+ assert!(!secret_manager_file_exists(), "Couldn't delete secret manager file");
+ let mut rng = Rng;
+ let secret1 =
+ sdsf.get_or_create_factory_reset_secret(&mut rng).expect("Couldn't create secret");
+ let secret2 = match sdsf.get_factory_reset_secret_impl() {
+ 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");
+ assert_eq!(
+ secret1.factory_reset_secret, secret2.factory_reset_secret,
+ "Should have retrieved the same secret"
+ );
+ assert_eq!(
+ secret1.secure_deletion_secret, secret2.secure_deletion_secret,
+ "Should have retrieved the same secret"
+ );
+ assert_eq!(secret1.secure_deletion_secret, [0; SECRET_SIZE], "Deletion secret should be 0");
+ assert_ne!(
+ secret1.factory_reset_secret, [0; FACTORY_RESET_SECRET_SIZE],
+ "Factory reset secret should not be 0"
+ );
+ assert_eq!(
+ secret1.factory_reset_secret, secret3.factory_reset_secret,
+ "Should have retrieved the same secret"
+ );
+ sdsf.factory_reset_secret.replace(None);
+ let secret3 = match sdsf.get_factory_reset_secret_impl() {
+ Ok(RetrieveSecureDeletionSecretFileData::DataFoundOnFile(secret)) => secret,
+ _ => panic!("Data couldn't be read from file."),
+ };
+ assert_eq!(
+ secret1.factory_reset_secret, secret3.factory_reset_secret,
+ "Should have retrieved the same secret"
+ );
+ sdsf.delete_all();
+ assert!(!secret_manager_file_exists(), "Couldn't delete secret manager file");
+
+ info!("sdsm::tests::secret_data_is_cached: Success");
+ }
+
+ fn new_secret_data_file_is_clean() {
+ let mut sdsf = super::SecureDeletionSecretManager::new();
+ sdsf.delete_all();
+ assert!(!secret_manager_file_exists(), "Couldn't delete secret manager file");
+ let mut rng = Rng;
+ let secret1 =
+ sdsf.get_or_create_factory_reset_secret(&mut rng).expect("Couldn't create secret");
+ let num_initial_slots = BLOCK_SIZE / 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");
+ assert_eq!(
+ secret.secure_deletion_secret, [0; SECRET_SIZE],
+ "Deletion secret should be 0"
+ );
+ assert_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));
+ assert!(secret.is_err(), "Read outside of initial range should fail");
+ sdsf.delete_all();
+ assert!(!secret_manager_file_exists(), "Couldn't delete secret manager file");
+
+ info!("sdsm::tests::new_secret_data_file_is_clean: Success");
+ }
+
+ // Not running next test because it takes too long when run on build server, which causes unit
+ // tests to timeout sometimes. Also not using #[ignore] because it doesn't seem to be supported
+ // yet.
+
+ #[allow(dead_code)]
+ fn new_secret_data_file_expands() {
+ let mut sdsf = super::SecureDeletionSecretManager::new();
+ sdsf.delete_all();
+ assert!(!secret_manager_file_exists(), "Couldn't delete secret manager file");
+ let mut rng = Rng;
+ let _secret1 =
+ sdsf.get_or_create_factory_reset_secret(&mut rng).expect("Couldn't create secret");
+ let max_num_slots = MAX_SECRET_FILE_SIZE / 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.
+ assert_eq!(deletion_slot.0 as usize, slot_num, "Wrong slot used for new secret");
+ assert_ne!(
+ deletion_data.secure_deletion_secret, [0; SECRET_SIZE],
+ "Deletion secret should not be 0"
+ );
+ assert_ne!(
+ deletion_data.secure_deletion_secret[0] & 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");
+ assert_eq!(
+ deletion_data.secure_deletion_secret, slot_data.secure_deletion_secret,
+ "Secret data should match"
+ );
+ assert_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);
+ assert!(size_failure.is_err(), "Shouldn't be able to increase secret file size any larger");
+ // Testing upgrade flow
+ let max_num_upgrade_slots = (MAX_SECRET_FILE_SIZE_FOR_UPGRADES) / 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");
+ assert_eq!(deletion_slot.0 as usize, slot_num, "Wrong slot used for new secret");
+ assert_ne!(
+ deletion_data.secure_deletion_secret, [0; SECRET_SIZE],
+ "Deletion secret should not be 0"
+ );
+ assert_ne!(
+ deletion_data.secure_deletion_secret[0] & 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");
+ assert_eq!(
+ deletion_data.secure_deletion_secret, slot_data.secure_deletion_secret,
+ "Secret data should match"
+ );
+ assert_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);
+ assert!(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");
+ assert_eq!(
+ slot_data.secure_deletion_secret, [0; SECRET_SIZE],
+ "Deletion secret should be 0"
+ );
+ let (deletion_slot, deletion_data) =
+ sdsf.new_secret(&mut rng, SlotPurpose::KeyUpgrade).expect("Couldn't create secret");
+ assert_eq!(deletion_slot.0 as usize, slot_num, "Wrong slot used for new secret");
+ assert_ne!(
+ deletion_data.secure_deletion_secret, [0; SECRET_SIZE],
+ "Deletion secret should not be 0"
+ );
+ assert_ne!(
+ deletion_data.secure_deletion_secret[0] & IN_USE_FLAG,
+ 0,
+ "Slot should be marked as in use"
+ );
+ }
+ sdsf.delete_all();
+ assert!(!secret_manager_file_exists(), "Couldn't delete secret manager file");
+
+ info!("sdsm::tests::new_secret_data_file_expands: Success");
+ }
+
+ fn new_secret_data_dont_affect_neighbors() {
+ let mut sdsf = super::SecureDeletionSecretManager::new();
+ sdsf.delete_all();
+ assert!(!secret_manager_file_exists(), "Couldn't delete secret manager file");
+ let mut rng = Rng;
+ 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(None);
+ let reset_secret_1 =
+ sdsf.get_factory_reset_secret().expect("Couldn't get factory reset secret");
+ assert_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");
+ assert_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");
+ assert_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");
+ assert_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");
+ assert_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");
+ assert_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");
+ assert_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");
+ assert_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");
+ assert_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");
+ assert_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");
+ assert_eq!(
+ slot_data.secure_deletion_secret, deletion_data_5.secure_deletion_secret,
+ "Secret data should match"
+ );
+ // Delete cached data
+ sdsf.factory_reset_secret.replace(None);
+ let reset_secret_1 =
+ sdsf.get_factory_reset_secret().expect("Couldn't get factory reset secret");
+ assert_eq!(
+ reset_secret.factory_reset_secret, reset_secret_1.factory_reset_secret,
+ "Factory reset secret should match"
+ );
+ sdsf.delete_all();
+ assert!(!secret_manager_file_exists(), "Couldn't delete secret manager file");
+
+ info!("sdsm::tests::new_secret_data_dont_affect_neighbors: Success");
+ }
+}