From cac98ad7d4b78486fa107880f2826b1340dbc5f6 Mon Sep 17 00:00:00 2001 From: matejcik Date: Fri, 1 Apr 2022 11:05:19 +0200 Subject: [PATCH] feat(core/rust): implement storage wrapper layer [no changelog] --- core/embed/rust/build.rs | 5 + core/embed/rust/src/trezorhal/mod.rs | 1 + core/embed/rust/src/trezorhal/storage.rs | 333 +++++++++++++++++++++++ core/embed/rust/trezorhal.h | 1 + 4 files changed, 340 insertions(+) create mode 100644 core/embed/rust/src/trezorhal/storage.rs diff --git a/core/embed/rust/build.rs b/core/embed/rust/build.rs index c8993cec49..d5a0104a9b 100644 --- a/core/embed/rust/build.rs +++ b/core/embed/rust/build.rs @@ -228,11 +228,15 @@ fn generate_trezorhal_bindings() { let bindings = prepare_bindings() .header("trezorhal.h") + // common + .allowlist_var("HW_ENTROPY_DATA") // secbool .allowlist_type("secbool") .must_use_type("secbool") .allowlist_var("sectrue") .allowlist_var("secfalse") + // flash + .allowlist_function("flash_init") // storage .allowlist_var("EXTERNAL_SALT_SIZE") .allowlist_var("FLAG_PUBLIC") @@ -247,6 +251,7 @@ fn generate_trezorhal_bindings() { .allowlist_function("storage_has_pin") .allowlist_function("storage_get_pin_rem") .allowlist_function("storage_change_pin") + .allowlist_function("storage_ensure_not_wipe_code") .allowlist_function("storage_has") .allowlist_function("storage_get") .allowlist_function("storage_set") diff --git a/core/embed/rust/src/trezorhal/mod.rs b/core/embed/rust/src/trezorhal/mod.rs index 560dfb7186..496a0f2917 100644 --- a/core/embed/rust/src/trezorhal/mod.rs +++ b/core/embed/rust/src/trezorhal/mod.rs @@ -12,6 +12,7 @@ pub mod random; #[cfg(feature = "model_tr")] pub mod rgb_led; pub mod slip39; +pub mod storage; pub mod uzlib; pub mod buffers; diff --git a/core/embed/rust/src/trezorhal/storage.rs b/core/embed/rust/src/trezorhal/storage.rs new file mode 100644 index 0000000000..0c728b006d --- /dev/null +++ b/core/embed/rust/src/trezorhal/storage.rs @@ -0,0 +1,333 @@ +use super::ffi; +use crate::error::Error; +use core::ptr; +use cstr_core::{cstr, CStr}; + +/// Result of PIN delay callback. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum PinCallbackResult { + /// Continue waiting for PIN unlock. + Continue, + /// Abort the unlock attempt before time is up. + Abort, +} + +/// PIN delay callback function type. +/// The storage layer will call this function while the PIN timeout is in +/// progress. This is useful for showing UI progress bar. +/// `wait` is the total number of seconds waiting. +/// `progress` is a value between 0 and 1000, where 1000 indicates 100%. +/// `message` is a message to show to the user. +pub type PinDelayCallback = fn(wait: u32, progress: u32, message: &str) -> PinCallbackResult; + +pub type ExternalSalt = [u8; ffi::EXTERNAL_SALT_SIZE as usize]; + +/// Static reference to the currently set PIN callback function. +static mut PIN_UI_CALLBACK: Option = None; + +/// C-compatible wrapper for the Rust callback. +unsafe extern "C" fn callback_wrapper( + wait: u32, + progress: u32, + message: *const cty::c_char, +) -> ffi::secbool { + let message = unsafe { CStr::from_ptr(message as _) }; + let result = unsafe { + PIN_UI_CALLBACK + .map(|c| c(wait, progress, message.to_str().unwrap_or(""))) + .unwrap_or(PinCallbackResult::Continue) + }; + if matches!(result, PinCallbackResult::Abort) { + ffi::sectrue + } else { + ffi::secfalse + } +} + +/// Storage error type. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum StorageError { + /// Data is too long or not appropriate for the operation. + InvalidData, + /// Write failed. + WriteFailed, + /// Read failed. + ReadFailed, + /// Delete failed. + DeleteFailed, + /// Failed to perform the get-and-increment operation on a counter. + CounterFailed, +} + +impl From for Error { + fn from(err: StorageError) -> Self { + match err { + StorageError::InvalidData => Error::ValueError(cstr!("Invalid data for storage")), + StorageError::WriteFailed => Error::ValueError(cstr!("Storage write failed")), + StorageError::ReadFailed => Error::ValueError(cstr!("Storage read failed")), + StorageError::DeleteFailed => Error::ValueError(cstr!("Storage delete failed")), + StorageError::CounterFailed => { + Error::ValueError(cstr!("Retrieving counter value failed")) + } + } + } +} + +pub type StorageResult = Result; + +/// Initialize the storage layer. +/// This function must be called before any other storage function. +pub fn init() { + unsafe { + ffi::storage_init( + Some(callback_wrapper), + ffi::HW_ENTROPY_DATA.as_ptr(), + ffi::HW_ENTROPY_DATA.len() as u16, + ); + } +} + +/// Set the PIN callback function. +/// See documentation for `PinDelayCallback` for more information. +pub fn set_pin_delay_callback(callback: PinDelayCallback) { + unsafe { + PIN_UI_CALLBACK = Some(callback); + } +} + +/// Wipe storage. +pub fn wipe() { + unsafe { ffi::storage_wipe() } +} + +/// Check if storage is unlocked. +pub fn is_unlocked() -> bool { + ffi::sectrue == unsafe { ffi::storage_is_unlocked() } +} + +/// Lock storage. +pub fn lock() { + unsafe { ffi::storage_lock() } +} + +/// Unlock storage with PIN and optional external salt. +/// Returns true if the PIN + salt combination is correct. +pub fn unlock(pin: &str, salt: Option<&ExternalSalt>) -> bool { + let salt = salt.map(|s| s.as_ptr()).unwrap_or(ptr::null()); + ffi::sectrue == unsafe { ffi::storage_unlock(pin.as_ptr() as *const _, pin.len(), salt) } +} + +/// Change PIN and/or external salt. +/// Returns true if the PIN + salt combination is correct and the change was +/// successful. +pub fn change_pin( + old_pin: &str, + new_pin: &str, + old_salt: Option<&ExternalSalt>, + new_salt: Option<&ExternalSalt>, +) -> bool { + ffi::sectrue + == unsafe { + ffi::storage_change_pin( + old_pin.as_ptr() as *const _, + old_pin.len(), + new_pin.as_ptr() as *const _, + new_pin.len(), + old_salt.map(|s| s.as_ptr()).unwrap_or(ptr::null()), + new_salt.map(|s| s.as_ptr()).unwrap_or(ptr::null()), + ) + } +} + +/// Check if storage has PIN set. +pub fn has_pin() -> bool { + ffi::sectrue == unsafe { ffi::storage_has_pin() } +} + +/// Get remaining PIN attempts. +pub fn get_pin_remaining() -> u32 { + unsafe { ffi::storage_get_pin_rem() } +} + +pub fn ensure_not_wipe_pin(pin: &str) { + unsafe { + ffi::storage_ensure_not_wipe_code(pin.as_ptr(), pin.len()); + } +} + +/// Check if value for `appkey` exists. +pub fn has(appkey: u16) -> bool { + ffi::sectrue == unsafe { ffi::storage_has(appkey) } +} + +/// Get length of value stored for `appkey`. +/// Returns an error if the value does not exist. +pub fn get_length(appkey: u16) -> StorageResult { + let mut len = 0; + if ffi::sectrue == unsafe { ffi::storage_get(appkey, ptr::null_mut() as _, 0, &mut len) } { + Ok(len as usize) + } else { + Err(StorageError::ReadFailed) + } +} + +/// Get value for `appkey` from storage. +/// The provided `data` buffer must be long enough to hold the value. If the +/// buffer is shorter than the stored value, an error is returned. Use +/// `get_length` to find the size of the value beforehand. On success, the data +/// is copied to `data`, and subslice is returned. Data previously +/// in the buffer beyond the copied data is not modified. +/// If `appkey` is not present in storage, or the value is private and storage +/// is locked, returns an error. +pub fn get(appkey: u16, data: &mut [u8]) -> StorageResult<&[u8]> { + let max_len = data.len().min(u16::MAX as usize) as u16; + let mut len = 0u16; + if ffi::sectrue + == unsafe { ffi::storage_get(appkey, data.as_mut_ptr() as _, max_len, &mut len) } + { + Ok(&data[0..len as usize]) + } else { + Err(StorageError::ReadFailed) + } +} + +/// Set value for `appkey` in storage. +/// The maximum length of the value is `u16::MAX`. Passing longer buffers will +/// result in an error. +/// If storage is locked and the value is not public-writable, returns an error. +pub fn set(appkey: u16, data: &[u8]) -> StorageResult<()> { + if data.len() > u16::MAX as usize { + Err(StorageError::InvalidData) + } else if ffi::sectrue + == unsafe { ffi::storage_set(appkey, data.as_ptr() as _, data.len() as u16) } + { + Ok(()) + } else { + Err(StorageError::WriteFailed) + } +} + +/// Set value for monotonic counter `appkey` in storage. +pub fn set_counter(appkey: u16, counter: u32) -> StorageResult<()> { + if ffi::sectrue == unsafe { ffi::storage_set_counter(appkey, counter) } { + Ok(()) + } else { + Err(StorageError::WriteFailed) + } +} + +/// Atomically get and increment the value for monotonic counter `appkey` in +/// storage. +pub fn get_next_counter(appkey: u16) -> StorageResult { + let mut counter = 0u32; + if ffi::sectrue == unsafe { ffi::storage_next_counter(appkey, &mut counter) } { + Ok(counter) + } else { + Err(StorageError::CounterFailed) + } +} + +/// Delete value for `appkey` from storage. +/// Returns Ok if the value was successfully deleted. +/// If the storage is locked, or the value does not exist in storage, returns +/// an error. +pub fn delete(appkey: u16) -> StorageResult<()> { + if ffi::sectrue == unsafe { ffi::storage_delete(appkey) } { + Ok(()) + } else { + Err(StorageError::DeleteFailed) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const APPKEY: u16 = 0x0101; + + fn init_storage(unlock: bool) { + unsafe { + ffi::flash_init(); + } + init(); + wipe(); + lock(); + if unlock { + super::unlock("", None); + } + } + + #[test] + fn test_init() { + init(); + } + + static mut PIN_CALLBACK_CALLED: bool = false; + + fn pin_callback(_wait: u32, _progress: u32, _message: &str) -> PinCallbackResult { + unsafe { + PIN_CALLBACK_CALLED = true; + } + PinCallbackResult::Continue + } + + #[test] + fn test_callback() { + set_pin_delay_callback(pin_callback); + init_storage(false); + unsafe { + PIN_CALLBACK_CALLED = false; + } + unlock("", None); + assert!(unsafe { PIN_CALLBACK_CALLED }); + } + + #[test] + fn test_get_set() { + init_storage(true); + let mut data = [0u8; 42]; + for i in 0..data.len() { + data[i] = i as u8; + } + assert!(set(APPKEY, &data).is_ok()); + let mut data2 = [0u8; 42]; + assert_eq!(get(APPKEY, &mut data2).unwrap().len(), data.len()); + assert_eq!(get(APPKEY, &mut data2).unwrap(), data); + assert_eq!(data, data2); + } + + #[test] + fn test_unlock() { + init_storage(false); + assert!(!is_unlocked()); + let buf = [0u8; 32]; + assert_eq!(set(APPKEY, &buf), Err(StorageError::WriteFailed)); + assert!(unlock("", None)); + assert!(is_unlocked()); + assert_eq!(set(APPKEY, &buf), Ok(())); + } + + #[test] + fn test_pin() { + init_storage(false); + assert!(!is_unlocked()); + assert!(!has_pin()); + assert!(unlock("", None)); + assert!(is_unlocked()); + assert!(!has_pin()); + + lock(); + assert!(!is_unlocked()); + assert!(!has_pin()); + + assert!(!unlock("1234", None)); + + //unlock("", None); + assert!(change_pin("", "1234", None, None)); + assert!(has_pin()); + + lock(); + assert!(!unlock("", None)); + assert!(unlock("1234", None)); + } +} diff --git a/core/embed/rust/trezorhal.h b/core/embed/rust/trezorhal.h index dd18552dee..0f474ae0a6 100644 --- a/core/embed/rust/trezorhal.h +++ b/core/embed/rust/trezorhal.h @@ -3,6 +3,7 @@ #include "display.h" #include "display_interface.h" #include "dma2d.h" +#include "flash.h" #include "fonts/fonts.h" #include "rgb_led.h" #include "secbool.h"