Tock V2 port - rebased and updated (#620)

* Changes from #580

* fixes USB cancel panic

* style fixes

* Update src/env/tock/storage.rs

Co-authored-by: Zach Halvorsen <zhalvorsen@google.com>

---------

Co-authored-by: Zach Halvorsen <zhalvorsen@google.com>
This commit is contained in:
kaczmarczyck
2023-05-05 09:55:16 +02:00
committed by GitHub
parent 645c1ba3a7
commit f25cdd6acc
78 changed files with 4079 additions and 4699 deletions

View File

@@ -14,20 +14,35 @@
use super::storage_helper::ModRange;
use alloc::boxed::Box;
use core::marker::PhantomData;
use libtock_platform as platform;
use libtock_platform::Syscalls;
use persistent_store::{StorageError, StorageResult};
use platform::DefaultConfig;
const PARTITION_LENGTH: usize = 0x41000;
const METADATA_LENGTH: usize = 0x1000;
pub struct BufferUpgradeStorage {
pub struct BufferUpgradeStorage<
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config = DefaultConfig,
> {
/// Content of the partition storage.
partition: Box<[u8]>,
s: PhantomData<S>,
c: PhantomData<C>,
}
impl BufferUpgradeStorage {
pub fn new() -> StorageResult<BufferUpgradeStorage> {
impl<S, C> BufferUpgradeStorage<S, C>
where
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
{
pub fn new() -> StorageResult<Self> {
Ok(BufferUpgradeStorage {
partition: vec![0xff; PARTITION_LENGTH].into_boxed_slice(),
s: PhantomData,
c: PhantomData,
})
}
@@ -72,10 +87,11 @@ impl BufferUpgradeStorage {
#[cfg(test)]
mod tests {
use super::*;
use libtock_unittest::fake::Syscalls;
#[test]
fn read_write_bundle() {
let mut storage = BufferUpgradeStorage::new().unwrap();
let mut storage = BufferUpgradeStorage::<Syscalls>::new().unwrap();
assert_eq!(storage.read_partition(0, 2).unwrap(), &[0xFF, 0xFF]);
assert!(storage.write_bundle(1, vec![0x88, 0x88]).is_ok());
assert_eq!(storage.read_partition(0, 2).unwrap(), &[0xFF, 0x88]);
@@ -108,7 +124,7 @@ mod tests {
#[test]
fn partition_slice() {
let storage = BufferUpgradeStorage::new().unwrap();
let storage = BufferUpgradeStorage::<Syscalls>::new().unwrap();
assert_eq!(storage.bundle_identifier(), 0x60000);
}
}

32
src/env/tock/clock.rs vendored
View File

@@ -12,19 +12,21 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use core::marker::PhantomData;
use libtock_drivers::timer::{get_clock_frequency, get_ticks};
use libtock_platform::Syscalls;
use opensk::api::clock::Clock;
/// 56-bits timestamp (valid for 70k+ years)
#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
struct Timestamp {
epoch: usize, // 32-bits
tick: usize, // 24-bits (32kHz)
epoch: u32,
tick: u32, // 24-bits (32kHz)
}
impl Timestamp {
/// Adds (potentially more than 24 bit of) ticks to this timestamp.
pub fn add_ticks(&mut self, ticks: usize) {
pub fn add_ticks(&mut self, ticks: u32) {
// Saturating should never happen, but it fails gracefully.
let sum = self.tick.saturating_add(ticks);
self.epoch += sum >> 24;
@@ -42,17 +44,26 @@ pub struct TockTimer {
/// To guarantee correctness, you have to call any of its functions at least once per full tick
/// counter wrap. In our case, 24 bit ticks with a 32 kHz frequency wrap after 512 seconds. If you
/// can't guarantee to regularly create or check timers, call tickle at least every 8 minutes.
#[derive(Default)]
pub struct TockClock {
pub struct TockClock<S: Syscalls> {
now: Timestamp,
s: PhantomData<S>,
}
impl TockClock {
impl<S: Syscalls> Default for TockClock<S> {
fn default() -> Self {
TockClock {
now: Timestamp::default(),
s: PhantomData,
}
}
}
impl<S: Syscalls> TockClock<S> {
/// Elapses timers before the clock wraps.
///
/// Call this regularly to timeout reliably despite wrapping clock ticks.
pub fn tickle(&mut self) {
let cur_tick = get_ticks().ok().unwrap();
let cur_tick = get_ticks::<S>().ok().unwrap();
if cur_tick < self.now.tick {
self.now.epoch += 1;
}
@@ -60,12 +71,13 @@ impl TockClock {
}
}
impl Clock for TockClock {
impl<S: Syscalls> Clock for TockClock<S> {
type Timer = TockTimer;
fn make_timer(&mut self, milliseconds: usize) -> Self::Timer {
let milliseconds = milliseconds as u32;
self.tickle();
let clock_frequency = get_clock_frequency().ok().unwrap();
let clock_frequency = get_clock_frequency::<S>().ok().unwrap();
let delta_tick = match milliseconds.checked_mul(clock_frequency) {
Some(x) => x / 1000,
// All CTAP timeouts are multiples of 100 so far. Worst case we timeout too early.
@@ -83,7 +95,7 @@ impl Clock for TockClock {
#[cfg(feature = "debug_ctap")]
fn timestamp_us(&mut self) -> usize {
let clock_frequency = get_clock_frequency().ok().unwrap();
let clock_frequency = get_clock_frequency::<S>().ok().unwrap();
let total_ticks = 0x100_0000u64 * self.now.epoch as u64 + self.now.tick as u64;
(total_ticks.wrapping_mul(1_000_000u64) / clock_frequency as u64) as usize
}

View File

@@ -17,6 +17,7 @@ use alloc::vec;
use alloc::vec::Vec;
use arrayref::array_ref;
use core::convert::TryFrom;
use libtock_platform::Syscalls;
use opensk::api::attestation_store::{self, Attestation, AttestationStore};
use opensk::api::crypto::sha256::Sha256;
use opensk::api::crypto::EC_FIELD_SIZE;
@@ -31,15 +32,18 @@ use opensk::ctap::secret::Secret;
use opensk::ctap::status_code::Ctap2StatusCode;
use opensk::ctap::{cbor_read, cbor_write, Channel};
use opensk::env::{Env, Sha};
use sk_cbor as cbor;
use sk_cbor::{cbor_map_options, destructure_cbor_map};
use {libtock_platform as platform, sk_cbor as cbor};
const VENDOR_COMMAND_CONFIGURE: u8 = 0x40;
const VENDOR_COMMAND_UPGRADE: u8 = 0x42;
const VENDOR_COMMAND_UPGRADE_INFO: u8 = 0x43;
pub fn process_vendor_command(
env: &mut TockEnv,
pub fn process_vendor_command<
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
>(
env: &mut TockEnv<S, C>,
bytes: &[u8],
channel: Channel,
) -> Option<Vec<u8>> {
@@ -50,8 +54,8 @@ pub fn process_vendor_command(
process_cbor(env, bytes, channel).unwrap_or_else(|e| Some(vec![e as u8]))
}
fn process_cbor(
env: &mut TockEnv,
fn process_cbor<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config>(
env: &mut TockEnv<S, C>,
bytes: &[u8],
channel: Channel,
) -> Result<Option<Vec<u8>>, Ctap2StatusCode> {
@@ -85,8 +89,11 @@ fn encode_cbor(value: cbor::Value) -> Vec<u8> {
}
}
fn process_vendor_configure(
env: &mut TockEnv,
fn process_vendor_configure<
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
>(
env: &mut TockEnv<S, C>,
params: VendorConfigureParameters,
// Unused in std only
_channel: Channel,
@@ -141,12 +148,15 @@ fn process_vendor_configure(
Ok(response)
}
fn process_vendor_upgrade(
env: &mut TockEnv,
fn process_vendor_upgrade<
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
>(
env: &mut TockEnv<S, C>,
params: VendorUpgradeParameters,
) -> Result<(), Ctap2StatusCode> {
let VendorUpgradeParameters { offset, data, hash } = params;
let calculated_hash = Sha::<TockEnv>::digest(&data);
let calculated_hash = Sha::<TockEnv<S>>::digest(&data);
if hash != calculated_hash {
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
}
@@ -156,8 +166,11 @@ fn process_vendor_upgrade(
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
}
fn process_vendor_upgrade_info(
env: &mut TockEnv,
fn process_vendor_upgrade_info<
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
>(
env: &mut TockEnv<S, C>,
) -> Result<VendorUpgradeInfoResponse, Ctap2StatusCode> {
let upgrade_locations = env
.upgrade_storage()
@@ -288,6 +301,7 @@ impl From<VendorUpgradeInfoResponse> for cbor::Value {
mod test {
use super::*;
use cbor::cbor_map;
use libtock_unittest::fake::Syscalls;
const DUMMY_CHANNEL: Channel = Channel::MainHid([0x12, 0x34, 0x56, 0x78]);
#[cfg(feature = "vendor_hid")]
@@ -295,14 +309,14 @@ mod test {
#[test]
fn test_process_cbor_unrelated_input() {
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
let cbor_bytes = vec![0x01];
assert_eq!(process_cbor(&mut env, &cbor_bytes, DUMMY_CHANNEL), Ok(None));
}
#[test]
fn test_process_cbor_invalid_input() {
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
let cbor_bytes = vec![VENDOR_COMMAND_CONFIGURE];
assert_eq!(
process_cbor(&mut env, &cbor_bytes, DUMMY_CHANNEL),
@@ -312,7 +326,7 @@ mod test {
#[test]
fn test_process_cbor_valid_input() {
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
let cbor_bytes = vec![VENDOR_COMMAND_UPGRADE_INFO];
assert!(process_cbor(&mut env, &cbor_bytes, DUMMY_CHANNEL)
.unwrap()
@@ -322,7 +336,7 @@ mod test {
#[test]
#[cfg(feature = "vendor_hid")]
fn test_process_command_valid_vendor_hid() {
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
let cbor_bytes = vec![VENDOR_COMMAND_UPGRADE_INFO];
assert!(process_cbor(&mut env, &cbor_bytes, VENDOR_CHANNEL)
.unwrap()
@@ -453,7 +467,7 @@ mod test {
#[test]
fn test_deserialize_vendor_upgrade_info() {
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
let cbor_bytes = [VENDOR_COMMAND_UPGRADE_INFO];
assert!(process_cbor(&mut env, &cbor_bytes, DUMMY_CHANNEL)
.unwrap()
@@ -462,7 +476,7 @@ mod test {
#[test]
fn test_vendor_configure() {
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
// Nothing should be configured at the beginning
let response = process_vendor_configure(
@@ -538,7 +552,7 @@ mod test {
}))
);
// Now try to lock the device
// Now try to lock the device, but that is currently not supported.
let response = process_vendor_configure(
&mut env,
VendorConfigureParameters {
@@ -549,10 +563,7 @@ mod test {
);
assert_eq!(
response,
Ok(VendorConfigureResponse {
cert_programmed: true,
pkey_programmed: true,
})
Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
);
}
@@ -561,13 +572,13 @@ mod test {
// The test partition storage has size 0x40000.
// The test metadata storage has size 0x1000.
// The test identifier matches partition B.
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
const METADATA_LEN: usize = 0x1000;
let metadata = vec![0xFF; METADATA_LEN];
let metadata_hash = Sha::<TockEnv>::digest(&metadata);
let metadata_hash = Sha::<TockEnv<Syscalls>>::digest(&metadata);
let data = vec![0xFF; 0x1000];
let hash = Sha::<TockEnv>::digest(&data);
let hash = Sha::<TockEnv<Syscalls>>::digest(&data);
// Write to partition.
let response = process_vendor_upgrade(
@@ -638,11 +649,11 @@ mod test {
#[test]
fn test_vendor_upgrade_no_second_partition() {
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
env.disable_upgrade_storage();
let data = vec![0xFF; 0x1000];
let hash = Sha::<TockEnv>::digest(&data);
let hash = Sha::<TockEnv<Syscalls>>::digest(&data);
let response = process_vendor_upgrade(
&mut env,
VendorUpgradeParameters {
@@ -656,7 +667,7 @@ mod test {
#[test]
fn test_vendor_upgrade_info() {
let mut env = TockEnv::default();
let mut env = TockEnv::<Syscalls>::default();
let bundle_identifier = env.upgrade_storage().unwrap().bundle_identifier();
let upgrade_info_reponse = process_vendor_upgrade_info(&mut env);

323
src/env/tock/mod.rs vendored
View File

@@ -16,16 +16,18 @@ use alloc::vec::Vec;
use clock::TockClock;
use core::cell::Cell;
use core::convert::TryFrom;
#[cfg(not(feature = "std"))]
use core::marker::PhantomData;
#[cfg(all(target_has_atomic = "8", not(feature = "std")))]
use core::sync::atomic::{AtomicBool, Ordering};
use libtock_core::result::{CommandError, EALREADY};
use libtock_drivers::buttons::{self, ButtonState};
use libtock_drivers::console::Console;
#[cfg(not(feature = "std"))]
use libtock_drivers::crp;
use libtock_buttons::{ButtonListener, ButtonState, Buttons};
use libtock_console::{Console, ConsoleWriter};
use libtock_drivers::result::{FlexUnwrap, TockError};
use libtock_drivers::timer::Duration;
use libtock_drivers::{led, rng, timer, usb_ctap_hid};
use libtock_drivers::usb_ctap_hid::UsbCtapHid;
use libtock_drivers::{rng, timer, usb_ctap_hid};
use libtock_leds::Leds;
use libtock_platform as platform;
use libtock_platform::{ErrorCode, Syscalls};
use opensk::api::attestation_store::AttestationStore;
use opensk::api::connection::{
HidConnection, SendOrRecvError, SendOrRecvResult, SendOrRecvStatus, UsbEndpoint,
@@ -38,28 +40,31 @@ use opensk::api::{attestation_store, key_store};
use opensk::ctap::Channel;
use opensk::env::Env;
#[cfg(feature = "std")]
use persistent_store::{BufferOptions, BufferStorage};
use persistent_store::BufferOptions;
use persistent_store::{StorageResult, Store};
use platform::{share, DefaultConfig, Subscribe};
use rand_core::{impls, CryptoRng, Error, RngCore};
#[cfg(feature = "std")]
mod buffer_upgrade_storage;
mod clock;
mod commands;
#[cfg(feature = "std")]
mod phantom_buffer_storage;
#[cfg(not(feature = "std"))]
mod storage;
mod storage_helper;
mod upgrade_helper;
#[cfg(not(feature = "std"))]
pub type Storage = storage::TockStorage;
pub type Storage<S, C> = storage::TockStorage<S, C>;
#[cfg(feature = "std")]
pub type Storage = BufferStorage;
pub type Storage<S, C> = phantom_buffer_storage::PhantomBufferStorage<S, C>;
#[cfg(not(feature = "std"))]
type UpgradeStorage = storage::TockUpgradeStorage;
type UpgradeStorage<S, C> = storage::TockUpgradeStorage<S, C>;
#[cfg(feature = "std")]
type UpgradeStorage = buffer_upgrade_storage::BufferUpgradeStorage;
type UpgradeStorage<S, C> = buffer_upgrade_storage::BufferUpgradeStorage<S, C>;
pub const AAGUID: &[u8; AAGUID_LENGTH] =
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
@@ -70,11 +75,21 @@ const TOCK_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
};
/// RNG backed by the TockOS rng driver.
pub struct TockRng {}
pub struct TockRng<S: Syscalls> {
_syscalls: PhantomData<S>,
}
impl CryptoRng for TockRng {}
impl<S: Syscalls> Default for TockRng<S> {
fn default() -> Self {
Self {
_syscalls: PhantomData,
}
}
}
impl RngCore for TockRng {
impl<S: Syscalls> CryptoRng for TockRng<S> {}
impl<S: Syscalls> RngCore for TockRng<S> {
fn next_u32(&mut self) -> u32 {
impls::next_u32_via_fill(self)
}
@@ -84,7 +99,7 @@ impl RngCore for TockRng {
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
rng::fill_buffer(dest);
rng::Rng::<S>::fill_buffer(dest);
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
@@ -93,74 +108,89 @@ impl RngCore for TockRng {
}
}
impl Rng for TockRng {}
impl<S: Syscalls> Rng for TockRng<S> {}
pub struct TockHidConnection {
pub struct TockHidConnection<S: Syscalls> {
endpoint: UsbEndpoint,
s: PhantomData<S>,
}
impl HidConnection for TockHidConnection {
impl<S: Syscalls> HidConnection for TockHidConnection<S> {
fn send_and_maybe_recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult {
match usb_ctap_hid::send_or_recv_with_timeout(
match UsbCtapHid::<S>::send_or_recv_with_timeout(
buf,
Duration::from_ms(timeout_ms as isize),
self.endpoint as usize,
self.endpoint as u32,
) {
Ok(usb_ctap_hid::SendOrRecvStatus::Timeout) => Ok(SendOrRecvStatus::Timeout),
Ok(usb_ctap_hid::SendOrRecvStatus::Sent) => Ok(SendOrRecvStatus::Sent),
Ok(usb_ctap_hid::SendOrRecvStatus::Received(recv_endpoint)) => {
UsbEndpoint::try_from(recv_endpoint).map(SendOrRecvStatus::Received)
UsbEndpoint::try_from(recv_endpoint as usize).map(SendOrRecvStatus::Received)
}
_ => Err(SendOrRecvError),
}
}
}
pub struct TockEnv {
rng: TockRng,
store: Store<Storage>,
upgrade_storage: Option<UpgradeStorage>,
main_connection: TockHidConnection,
pub struct TockEnv<
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config = DefaultConfig,
> {
rng: TockRng<S>,
store: Store<Storage<S, C>>,
upgrade_storage: Option<UpgradeStorage<S, C>>,
main_connection: TockHidConnection<S>,
#[cfg(feature = "vendor_hid")]
vendor_connection: TockHidConnection,
vendor_connection: TockHidConnection<S>,
blink_pattern: usize,
clock: TockClock,
clock: TockClock<S>,
c: PhantomData<C>,
}
impl Default for TockEnv {
impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> Default
for TockEnv<S, C>
{
/// Returns the unique instance of the Tock environment.
///
/// # Panics
///
/// - If called a second time.
fn default() -> Self {
let rng = TockRng::default();
// We rely on `take_storage` to ensure that this function is called only once.
let storage = take_storage().unwrap();
let storage = take_storage::<S, C>().unwrap();
let store = Store::new(storage).ok().unwrap();
let upgrade_storage = UpgradeStorage::new().ok();
TockEnv {
rng: TockRng {},
rng,
store,
upgrade_storage,
main_connection: TockHidConnection {
endpoint: UsbEndpoint::MainHid,
s: PhantomData,
},
#[cfg(feature = "vendor_hid")]
vendor_connection: TockHidConnection {
endpoint: UsbEndpoint::VendorHid,
s: PhantomData,
},
blink_pattern: 0,
clock: TockClock::default(),
c: PhantomData,
}
}
}
impl TockEnv {
impl<S, C> TockEnv<S, C>
where
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
{
/// Returns the upgrade storage instance.
///
/// Upgrade storage is optional, so implementations may return `None`. However, implementations
/// should either always return `None` or always return `Some`.
pub fn upgrade_storage(&mut self) -> Option<&mut UpgradeStorage> {
pub fn upgrade_storage(&mut self) -> Option<&mut UpgradeStorage<S, C>> {
self.upgrade_storage.as_mut()
}
@@ -169,39 +199,13 @@ impl TockEnv {
}
pub fn lock_firmware_protection(&mut self) -> bool {
#[cfg(not(feature = "std"))]
{
matches!(
crp::set_protection(crp::ProtectionLevel::FullyLocked),
Ok(())
| Err(TockError::Command(CommandError {
return_code: EALREADY,
..
}))
)
}
#[cfg(feature = "std")]
{
true
}
false
}
}
/// Returns the unique storage instance.
///
/// # Panics
///
/// - If called a second time.
#[cfg(not(feature = "std"))]
pub fn take_storage() -> StorageResult<Storage> {
// Make sure the storage was not already taken.
static TAKEN: AtomicBool = AtomicBool::new(false);
assert!(!TAKEN.fetch_or(true, Ordering::SeqCst));
Storage::new()
}
#[cfg(feature = "std")]
pub fn take_storage() -> StorageResult<Storage> {
pub fn take_storage<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config>(
) -> StorageResult<Storage<S, C>> {
// Use the Nordic configuration.
const PAGE_SIZE: usize = 0x1000;
const NUM_PAGES: usize = 20;
@@ -213,10 +217,54 @@ pub fn take_storage() -> StorageResult<Storage> {
max_page_erases: 10000,
strict_mode: true,
};
Ok(BufferStorage::new(store, options))
Ok(phantom_buffer_storage::PhantomBufferStorage::new(
store, options,
))
}
impl UserPresence for TockEnv {
/// Returns the unique storage instance.
///
/// # Panics
///
/// - If called a second time.
#[cfg(not(feature = "std"))]
pub fn take_storage<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config>(
) -> StorageResult<Storage<S, C>> {
// Make sure the storage was not already taken.
#[cfg(target_has_atomic = "8")]
{
static TAKEN: AtomicBool = AtomicBool::new(false);
assert!(!TAKEN.fetch_or(true, Ordering::SeqCst));
}
#[cfg(not(target_has_atomic = "8"))]
{
static mut TAKEN: bool = false;
// Safety
//
// We can not use an AtomicBool on platforms that do not support atomics,
// such as the whole `riscv32i[mc]` family like OpenTitan.
// Thus, we need to use a mutable static variable which are unsafe
// cause they could cause a data race when two threads access it
// at the same time.
//
// However, as we are running an application on TockOS and because
// of its [architecture](https://www.tockos.org/documentation/design)
// we are running in a single-threaded event loop which means the
// aforementioned data race is impossible. Thus, in this case, the
// usage of a static mut is safe.
unsafe {
assert!(!TAKEN);
TAKEN = true;
}
}
Storage::new()
}
impl<S, C> UserPresence for TockEnv<S, C>
where
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
{
fn check_init(&mut self) {
self.blink_pattern = 0;
}
@@ -225,52 +273,77 @@ impl UserPresence for TockEnv {
if timeout_ms == 0 {
return Err(UserPresenceError::Timeout);
}
blink_leds(self.blink_pattern);
blink_leds::<S>(self.blink_pattern);
self.blink_pattern += 1;
// enable interrupts for all buttons
let num_buttons = Buttons::<S>::count().map_err(|_| UserPresenceError::Fail)?;
(0..num_buttons)
.try_for_each(|n| Buttons::<S>::enable_interrupts(n))
.map_err(|_| UserPresenceError::Fail)?;
let button_touched = Cell::new(false);
let mut buttons_callback = buttons::with_callback(|_button_num, state| {
let button_listener = ButtonListener(|_button_num, state| {
match state {
ButtonState::Pressed => button_touched.set(true),
ButtonState::Released => (),
};
});
let mut buttons = buttons_callback.init().flex_unwrap();
for mut button in &mut buttons {
button.enable().flex_unwrap();
}
// Setup a keep-alive callback.
// Setup a keep-alive callback but don't enable it yet
let keepalive_expired = Cell::new(false);
let mut keepalive_callback = timer::with_callback(|_, _| {
keepalive_expired.set(true);
});
let mut keepalive = keepalive_callback.init().flex_unwrap();
let keepalive_alarm = keepalive
.set_alarm(Duration::from_ms(timeout_ms as isize))
.flex_unwrap();
let mut keepalive_callback =
timer::with_callback::<S, C, _>(|_| keepalive_expired.set(true));
share::scope::<
(
Subscribe<_, { libtock_buttons::DRIVER_NUM }, 0>,
Subscribe<
S,
{ libtock_drivers::timer::DRIVER_NUM },
{ libtock_drivers::timer::subscribe::CALLBACK },
>,
),
_,
_,
>(|handle| {
let (sub_button, sub_timer) = handle.split();
Buttons::<S>::register_listener(&button_listener, sub_button)
.map_err(|_| UserPresenceError::Fail)?;
// Wait for a button touch or an alarm.
libtock_drivers::util::yieldk_for(|| button_touched.get() || keepalive_expired.get());
let mut keepalive = keepalive_callback.init().flex_unwrap();
keepalive_callback
.enable(sub_timer)
.map_err(|_| UserPresenceError::Fail)?;
keepalive
.set_alarm(timer::Duration::from_ms(timeout_ms as isize))
.flex_unwrap();
// Cleanup alarm callback.
match keepalive.stop_alarm(keepalive_alarm) {
Ok(()) => (),
Err(TockError::Command(CommandError {
return_code: EALREADY,
..
})) => assert!(keepalive_expired.get()),
Err(_e) => {
#[cfg(feature = "debug_ctap")]
panic!("Unexpected error when stopping alarm: {:?}", _e);
#[cfg(not(feature = "debug_ctap"))]
panic!("Unexpected error when stopping alarm: <error is only visible with the debug_ctap feature>");
// Wait for a button touch or an alarm.
libtock_drivers::util::Util::<S>::yieldk_for(|| {
button_touched.get() || keepalive_expired.get()
});
Buttons::<S>::unregister_listener();
// disable event interrupts for all buttons
(0..num_buttons)
.try_for_each(|n| Buttons::<S>::disable_interrupts(n))
.map_err(|_| UserPresenceError::Fail)?;
// Cleanup alarm callback.
match keepalive.stop_alarm() {
Ok(()) => (),
Err(TockError::Command(ErrorCode::Already)) => assert!(keepalive_expired.get()),
Err(_e) => {
#[cfg(feature = "debug_ctap")]
panic!("Unexpected error when stopping alarm: {:?}", _e);
#[cfg(not(feature = "debug_ctap"))]
panic!("Unexpected error when stopping alarm: <error is only visible with the debug_ctap feature>");
}
}
}
for mut button in &mut buttons {
button.disable().flex_unwrap();
}
Ok::<(), UserPresenceError>(())
})?;
if button_touched.get() {
Ok(())
@@ -282,13 +355,22 @@ impl UserPresence for TockEnv {
}
fn check_complete(&mut self) {
switch_off_leds();
switch_off_leds::<S>();
}
}
impl key_store::Helper for TockEnv {}
impl<S, C> key_store::Helper for TockEnv<S, C>
where
S: Syscalls,
C: platform::allow_ro::Config + platform::subscribe::Config,
{
}
impl AttestationStore for TockEnv {
impl<S, C> AttestationStore for TockEnv<S, C>
where
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
{
fn get(
&mut self,
id: &attestation_store::Id,
@@ -311,16 +393,18 @@ impl AttestationStore for TockEnv {
}
}
impl Env for TockEnv {
type Rng = TockRng;
impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> Env
for TockEnv<S, C>
{
type Rng = TockRng<S>;
type UserPresence = Self;
type Storage = Storage;
type Storage = Storage<S, C>;
type KeyStore = Self;
type AttestationStore = Self;
type Clock = TockClock;
type Write = Console;
type Clock = TockClock<S>;
type Write = ConsoleWriter<S>;
type Customization = CustomizationImpl;
type HidConnection = TockHidConnection;
type HidConnection = TockHidConnection<S>;
type Crypto = SoftwareCrypto;
fn rng(&mut self) -> &mut Self::Rng {
@@ -348,7 +432,7 @@ impl Env for TockEnv {
}
fn write(&mut self) -> Self::Write {
Console::new()
Console::<S>::writer()
}
fn customization(&self) -> &Self::Customization {
@@ -375,17 +459,17 @@ impl Env for TockEnv {
}
}
pub fn blink_leds(pattern_seed: usize) {
for l in 0..led::count().flex_unwrap() {
if (pattern_seed ^ l).count_ones() & 1 != 0 {
led::get(l).flex_unwrap().on().flex_unwrap();
pub fn blink_leds<S: Syscalls>(pattern_seed: usize) {
for l in 0..Leds::<S>::count().unwrap() {
if (pattern_seed ^ l as usize).count_ones() & 1 != 0 {
Leds::<S>::on(l).unwrap();
} else {
led::get(l).flex_unwrap().off().flex_unwrap();
Leds::<S>::off(l).unwrap();
}
}
}
pub fn wink_leds(pattern_seed: usize) {
pub fn wink_leds<S: Syscalls>(pattern_seed: usize) {
// This generates a "snake" pattern circling through the LEDs.
// Fox example with 4 LEDs the sequence of lit LEDs will be the following.
// 0 1 2 3
@@ -398,7 +482,7 @@ pub fn wink_leds(pattern_seed: usize) {
// * *
// * * *
// * *
let count = led::count().flex_unwrap();
let count = Leds::<S>::count().unwrap() as usize;
let a = (pattern_seed / 2) % count;
let b = ((pattern_seed + 1) / 2) % count;
let c = ((pattern_seed + 3) / 2) % count;
@@ -411,16 +495,17 @@ pub fn wink_leds(pattern_seed: usize) {
_ => l,
};
if k == a || k == b || k == c {
led::get(l).flex_unwrap().on().flex_unwrap();
Leds::<S>::on(l as u32).unwrap();
} else {
led::get(l).flex_unwrap().off().flex_unwrap();
Leds::<S>::off(l as u32).unwrap();
}
}
}
pub fn switch_off_leds() {
for l in 0..led::count().flex_unwrap() {
led::get(l).flex_unwrap().off().flex_unwrap();
pub fn switch_off_leds<S: Syscalls>() {
let count = Leds::<S>::count().unwrap();
for l in 0..count {
Leds::<S>::off(l).unwrap();
}
}

111
src/env/tock/phantom_buffer_storage.rs vendored Normal file
View File

@@ -0,0 +1,111 @@
// Copyright 2023 Google LLC
//
// 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.
use alloc::borrow::Cow;
use core::marker::PhantomData;
use libtock_platform as platform;
use libtock_platform::Syscalls;
use persistent_store::{
BufferCorruptFunction, BufferOptions, BufferStorage, Storage, StorageIndex, StorageResult,
};
/// Wrapper with phantom data for the test storage implementation.
pub struct PhantomBufferStorage<
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
> {
storage: BufferStorage,
s: PhantomData<S>,
c: PhantomData<C>,
}
impl<S, C> PhantomBufferStorage<S, C>
where
S: Syscalls,
C: platform::allow_ro::Config + platform::subscribe::Config,
{
pub fn new(storage: Box<[u8]>, options: BufferOptions) -> Self {
Self {
storage: BufferStorage::new(storage, options),
s: PhantomData,
c: PhantomData,
}
}
pub fn arm_interruption(&mut self, delay: usize) {
self.storage.arm_interruption(delay);
}
pub fn disarm_interruption(&mut self) -> usize {
self.storage.disarm_interruption()
}
pub fn reset_interruption(&mut self) {
self.storage.reset_interruption();
}
pub fn corrupt_operation(&mut self, corrupt: BufferCorruptFunction) {
self.storage.corrupt_operation(corrupt);
}
pub fn get_word_writes(&self, word: usize) -> usize {
self.storage.get_word_writes(word)
}
pub fn get_page_erases(&self, page: usize) -> usize {
self.storage.get_page_erases(page)
}
pub fn set_page_erases(&mut self, page: usize, cycle: usize) {
self.storage.set_page_erases(page, cycle);
}
}
impl<S, C> Storage for PhantomBufferStorage<S, C>
where
S: Syscalls,
C: platform::allow_ro::Config + platform::subscribe::Config,
{
fn word_size(&self) -> usize {
self.storage.word_size()
}
fn page_size(&self) -> usize {
self.storage.page_size()
}
fn num_pages(&self) -> usize {
self.storage.num_pages()
}
fn max_word_writes(&self) -> usize {
self.storage.max_word_writes()
}
fn max_page_erases(&self) -> usize {
self.storage.max_page_erases()
}
fn read_slice(&self, index: StorageIndex, length: usize) -> StorageResult<Cow<[u8]>> {
self.storage.read_slice(index, length)
}
fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> StorageResult<()> {
self.storage.write_slice(index, value)
}
fn erase_page(&mut self, page: usize) -> StorageResult<()> {
self.storage.erase_page(page)
}
}

View File

@@ -20,34 +20,37 @@ use super::TockEnv;
use alloc::borrow::Cow;
use alloc::vec::Vec;
use core::cell::Cell;
use libtock_core::{callback, syscalls};
use core::marker::PhantomData;
use libtock_platform as platform;
use libtock_platform::{syscall_class, ErrorCode, RawSyscalls, Syscalls};
use opensk::api::crypto::sha256::Sha256;
use opensk::env::Sha;
use persistent_store::{Storage, StorageError, StorageIndex, StorageResult};
use platform::share;
const DRIVER_NUMBER: usize = 0x50003;
const DRIVER_NUMBER: u32 = 0x50003;
const UPGRADE_PUBLIC_KEY: &[u8; 65] =
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey.bin"));
mod subscribe_nr {
pub const DONE: usize = 0;
pub const DONE: u32 = 0;
}
mod command_nr {
pub const GET_INFO: usize = 1;
pub const GET_INFO: u32 = 1;
pub mod get_info_nr {
pub const WORD_SIZE: usize = 0;
pub const PAGE_SIZE: usize = 1;
pub const MAX_WORD_WRITES: usize = 2;
pub const MAX_PAGE_ERASES: usize = 3;
pub const WORD_SIZE: u32 = 0;
pub const PAGE_SIZE: u32 = 1;
pub const MAX_WORD_WRITES: u32 = 2;
pub const MAX_PAGE_ERASES: u32 = 3;
}
pub const WRITE_SLICE: usize = 2;
pub const ERASE_PAGE: usize = 3;
pub const WRITE_SLICE: u32 = 2;
pub const ERASE_PAGE: u32 = 3;
}
mod allow_nr {
pub const WRITE_SLICE: usize = 0;
mod ro_allow_nr {
pub const WRITE_SLICE: u32 = 0;
}
mod memop_nr {
@@ -58,85 +61,98 @@ mod memop_nr {
}
mod storage_type {
pub const STORE: usize = 1;
pub const PARTITION: usize = 2;
pub const STORE: u32 = 1;
pub const PARTITION: u32 = 2;
}
fn get_info(nr: usize, arg: usize) -> StorageResult<usize> {
let code = syscalls::command(DRIVER_NUMBER, command_nr::GET_INFO, nr, arg);
code.map_err(|_| StorageError::CustomError)
fn get_info<S: Syscalls>(nr: u32, arg: u32) -> StorageResult<u32> {
let info = S::command(DRIVER_NUMBER, command_nr::GET_INFO, nr, arg)
.to_result::<u32, ErrorCode>()
.map_err(|_| StorageError::CustomError)?;
Ok(info)
}
fn memop(nr: u32, arg: usize) -> StorageResult<usize> {
let code = unsafe { syscalls::raw::memop(nr, arg) };
if code < 0 {
Err(StorageError::CustomError)
} else {
Ok(code as usize)
fn memop<S: RawSyscalls>(nr: u32, arg: u32) -> StorageResult<u32> {
let registers = unsafe { S::syscall2::<{ syscall_class::MEMOP }>([nr.into(), arg.into()]) };
let r0 = registers[0].as_u32();
let r1 = registers[1].as_u32();
// make sure r0 is the `success with u32` (129) return variant and then return the value in r1 as u32
// see: https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#32-return-values
match (r0, r1) {
(129, r1) => Ok(r1),
(_, _) => Err(StorageError::CustomError),
}
}
fn block_command(driver: usize, cmd: usize, arg1: usize, arg2: usize) -> StorageResult<()> {
let done = Cell::new(None);
let mut alarm = |status| done.set(Some(status));
let subscription = syscalls::subscribe::<callback::Identity1Consumer, _>(
DRIVER_NUMBER,
subscribe_nr::DONE,
&mut alarm,
);
if subscription.is_err() {
return Err(StorageError::CustomError);
}
fn block_command<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config>(
driver: u32,
cmd: u32,
arg1: u32,
arg2: u32,
) -> StorageResult<()> {
let called: Cell<Option<(u32,)>> = Cell::new(None);
let code = syscalls::command(driver, cmd, arg1, arg2);
if code.is_err() {
return Err(StorageError::CustomError);
}
libtock_drivers::util::yieldk_for(|| done.get().is_some());
if done.get().unwrap() == 0 {
Ok(())
} else {
Err(StorageError::CustomError)
}
share::scope(|subscribe| {
S::subscribe::<_, _, C, DRIVER_NUMBER, { subscribe_nr::DONE }>(subscribe, &called)
.map_err(|_| StorageError::CustomError)?;
S::command(driver, cmd, arg1, arg2)
.to_result::<(), ErrorCode>()
.map_err(|_| StorageError::CustomError)?;
libtock_drivers::util::Util::<S>::yieldk_for(|| called.get().is_some());
if called.get().unwrap().0 == 0 {
Ok(())
} else {
Err(StorageError::CustomError)
}
})
}
unsafe fn read_slice(address: usize, length: usize) -> &'static [u8] {
core::slice::from_raw_parts(address as *const u8, length)
}
fn write_slice(ptr: usize, value: &[u8]) -> StorageResult<()> {
let code = unsafe {
syscalls::raw::allow(
fn write_slice<S: Syscalls, C: platform::allow_ro::Config + platform::subscribe::Config>(
ptr: usize,
value: &[u8],
) -> StorageResult<()> {
share::scope(|allow_ro| {
S::allow_ro::<C, DRIVER_NUMBER, { ro_allow_nr::WRITE_SLICE }>(allow_ro, value)
.map_err(|_| StorageError::CustomError)?;
block_command::<S, C>(
DRIVER_NUMBER,
allow_nr::WRITE_SLICE,
// We rely on the driver not writing to the slice. This should use read-only allow
// when available. See https://github.com/tock/tock/issues/1274.
value.as_ptr() as *mut u8,
value.len(),
command_nr::WRITE_SLICE,
ptr as u32,
value.len() as u32,
)
};
if code < 0 {
return Err(StorageError::CustomError);
}
block_command(DRIVER_NUMBER, command_nr::WRITE_SLICE, ptr, value.len())
})
}
fn erase_page(ptr: usize, page_length: usize) -> StorageResult<()> {
block_command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, page_length)
fn erase_page<S: Syscalls, C: platform::allow_ro::Config + platform::subscribe::Config>(
ptr: usize,
page_length: usize,
) -> StorageResult<()> {
block_command::<S, C>(
DRIVER_NUMBER,
command_nr::ERASE_PAGE,
ptr as u32,
page_length as u32,
)
}
pub struct TockStorage {
pub struct TockStorage<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> {
word_size: usize,
page_size: usize,
num_pages: usize,
max_word_writes: usize,
max_page_erases: usize,
storage_locations: Vec<&'static [u8]>,
s: PhantomData<S>,
c: PhantomData<C>,
}
impl TockStorage {
impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> TockStorage<S, C> {
/// Provides access to the embedded flash if available.
///
/// # Errors
@@ -146,14 +162,16 @@ impl TockStorage {
/// - The page size is a power of two.
/// - The page size is a multiple of the word size.
/// - The storage is page-aligned.
pub fn new() -> StorageResult<TockStorage> {
pub fn new() -> StorageResult<TockStorage<S, C>> {
let mut syscall = TockStorage {
word_size: get_info(command_nr::get_info_nr::WORD_SIZE, 0)?,
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
word_size: get_info::<S>(command_nr::get_info_nr::WORD_SIZE, 0)? as usize,
page_size: get_info::<S>(command_nr::get_info_nr::PAGE_SIZE, 0)? as usize,
num_pages: 0,
max_word_writes: get_info(command_nr::get_info_nr::MAX_WORD_WRITES, 0)?,
max_page_erases: get_info(command_nr::get_info_nr::MAX_PAGE_ERASES, 0)?,
max_word_writes: get_info::<S>(command_nr::get_info_nr::MAX_WORD_WRITES, 0)? as usize,
max_page_erases: get_info::<S>(command_nr::get_info_nr::MAX_PAGE_ERASES, 0)? as usize,
storage_locations: Vec::new(),
s: PhantomData,
c: PhantomData,
};
if !syscall.word_size.is_power_of_two()
|| !syscall.page_size.is_power_of_two()
@@ -161,12 +179,13 @@ impl TockStorage {
{
return Err(StorageError::CustomError);
}
for i in 0..memop(memop_nr::STORAGE_CNT, 0)? {
if memop(memop_nr::STORAGE_TYPE, i)? != storage_type::STORE {
let num_storage_locations = memop::<S>(memop_nr::STORAGE_CNT, 0)?;
for i in 0..num_storage_locations {
if memop::<S>(memop_nr::STORAGE_TYPE, i)? != storage_type::STORE {
continue;
}
let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?;
let storage_len = memop(memop_nr::STORAGE_LEN, i)?;
let storage_ptr = memop::<S>(memop_nr::STORAGE_PTR, i)? as usize;
let storage_len = memop::<S>(memop_nr::STORAGE_LEN, i)? as usize;
if !syscall.is_page_aligned(storage_ptr) || !syscall.is_page_aligned(storage_len) {
return Err(StorageError::CustomError);
}
@@ -187,7 +206,9 @@ impl TockStorage {
}
}
impl Storage for TockStorage {
impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> Storage
for TockStorage<S, C>
{
fn word_size(&self) -> usize {
self.word_size
}
@@ -218,26 +239,35 @@ impl Storage for TockStorage {
return Err(StorageError::NotAligned);
}
let ptr = self.read_slice(index, value.len())?.as_ptr() as usize;
write_slice(ptr, value)
write_slice::<S, C>(ptr, value)
}
fn erase_page(&mut self, page: usize) -> StorageResult<()> {
let index = StorageIndex { page, byte: 0 };
let length = self.page_size();
let ptr = self.read_slice(index, length)?.as_ptr() as usize;
erase_page(ptr, length)
erase_page::<S, C>(ptr, length)
}
}
pub struct TockUpgradeStorage {
pub struct TockUpgradeStorage<
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
> {
page_size: usize,
partition: Partition,
metadata: ModRange,
running_metadata: ModRange,
identifier: u32,
s: PhantomData<S>,
c: PhantomData<C>,
}
impl TockUpgradeStorage {
impl<S, C> TockUpgradeStorage<S, C>
where
S: Syscalls,
C: platform::allow_ro::Config + platform::subscribe::Config,
{
// Ideally, the kernel should tell us metadata and partitions directly.
// This code only works for one layout, refactor this into the storage driver to support more.
const METADATA_ADDRESS: usize = 0x4000;
@@ -258,25 +288,27 @@ impl TockUpgradeStorage {
/// Returns a `NotAligned` error if partitions or metadata ranges are
/// - not exclusive or,
/// - not consecutive.
pub fn new() -> StorageResult<TockUpgradeStorage> {
pub fn new() -> StorageResult<TockUpgradeStorage<S, C>> {
let mut locations = TockUpgradeStorage {
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
page_size: get_info::<S>(command_nr::get_info_nr::PAGE_SIZE, 0)? as usize,
partition: Partition::default(),
metadata: ModRange::new_empty(),
running_metadata: ModRange::new_empty(),
identifier: Self::PARTITION_ADDRESS_A as u32,
s: PhantomData,
c: PhantomData,
};
if !locations.page_size.is_power_of_two() {
return Err(StorageError::CustomError);
}
let mut firmware_range = ModRange::new_empty();
for i in 0..memop(memop_nr::STORAGE_CNT, 0)? {
let storage_type = memop(memop_nr::STORAGE_TYPE, i)?;
for i in 0..memop::<S>(memop_nr::STORAGE_CNT, 0)? {
let storage_type = memop::<S>(memop_nr::STORAGE_TYPE, i)?;
if !matches!(storage_type, storage_type::PARTITION) {
continue;
};
let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?;
let storage_len = memop(memop_nr::STORAGE_LEN, i)?;
let storage_ptr = memop::<S>(memop_nr::STORAGE_PTR, i)? as usize;
let storage_len = memop::<S>(memop_nr::STORAGE_LEN, i)? as usize;
if !locations.is_page_aligned(storage_ptr) || !locations.is_page_aligned(storage_len) {
return Err(StorageError::CustomError);
}
@@ -337,7 +369,7 @@ impl TockUpgradeStorage {
/// Checks if the metadata's hash matches the partition's content.
fn check_partition_hash(&self, metadata: &[u8]) -> StorageResult<()> {
let start_address = self.metadata.start() + METADATA_SIGN_OFFSET;
let mut hasher = Sha::<TockEnv>::new();
let mut hasher = Sha::<TockEnv<S>>::new();
for range in self.partition.ranges_from(start_address) {
let partition_slice = unsafe { read_slice(range.start(), range.length()) };
// The hash implementation handles this in chunks, so no memory issues.
@@ -362,15 +394,15 @@ impl TockUpgradeStorage {
let write_range = ModRange::new(address, data.len());
if self.contains_metadata(&write_range)? {
let new_metadata = &data[self.metadata.start() - address..][..self.metadata.length()];
check_metadata::<TockEnv>(self, UPGRADE_PUBLIC_KEY, new_metadata)?;
check_metadata::<TockEnv<S, C>, S, C>(self, UPGRADE_PUBLIC_KEY, new_metadata)?;
}
// Erases all pages that have their first byte in the write range.
// Since we expect calls in order, we don't want to erase half-written pages.
for address in write_range.aligned_iter(self.page_size) {
erase_page(address, self.page_size)?;
erase_page::<S, C>(address, self.page_size)?;
}
write_slice(address, &data)?;
write_slice::<S, C>(address, &data)?;
let written_slice = unsafe { read_slice(address, data.len()) };
if written_slice != data {
return Err(StorageError::CustomError);
@@ -378,7 +410,7 @@ impl TockUpgradeStorage {
// Case: Last slice is written.
if data.len() == self.partition.length() - offset {
let metadata = unsafe { read_slice(self.metadata.start(), self.metadata.length()) };
self.check_partition_hash(&metadata)?;
self.check_partition_hash(metadata)?;
}
Ok(())
}

View File

@@ -21,6 +21,8 @@ use crate::env::tock::buffer_upgrade_storage::BufferUpgradeStorage;
use crate::env::tock::storage::TockUpgradeStorage;
use arrayref::array_ref;
use byteorder::{ByteOrder, LittleEndian};
use libtock_platform as platform;
use libtock_platform::Syscalls;
use opensk::api::crypto::ecdsa::{PublicKey as _, Signature as _};
use opensk::env::{EcdsaPk, EcdsaSignature, Env};
use persistent_store::{StorageError, StorageResult};
@@ -39,9 +41,13 @@ pub const METADATA_SIGN_OFFSET: usize = 0x800;
///
/// Checks signature correctness against the hash, and whether the partition offset matches.
/// Whether the hash matches the partition content is not tested here!
pub fn check_metadata<E: Env>(
#[cfg(not(feature = "std"))] upgrade_locations: &TockUpgradeStorage,
#[cfg(feature = "std")] upgrade_locations: &BufferUpgradeStorage,
pub fn check_metadata<
E: Env,
S: Syscalls,
C: platform::subscribe::Config + platform::allow_ro::Config,
>(
#[cfg(not(feature = "std"))] upgrade_locations: &TockUpgradeStorage<S, C>,
#[cfg(feature = "std")] upgrade_locations: &BufferUpgradeStorage<S, C>,
public_key_bytes: &[u8],
metadata: &[u8],
) -> StorageResult<()> {
@@ -104,11 +110,13 @@ fn verify_signature<E: Env>(
mod test {
use super::*;
use arrayref::mut_array_refs;
use libtock_unittest::fake::Syscalls;
use opensk::api::crypto::ecdsa::SecretKey as _;
use opensk::api::crypto::sha256::Sha256;
use opensk::api::crypto::{EC_FIELD_SIZE, EC_SIGNATURE_SIZE};
use opensk::env::test::TestEnv;
use opensk::env::{EcdsaSk, Sha};
use platform::DefaultConfig;
fn to_uncompressed(public_key: &EcdsaPk<TestEnv>) -> [u8; 1 + 2 * EC_FIELD_SIZE] {
// Formatting according to:
@@ -147,20 +155,28 @@ mod test {
let public_key_bytes = to_uncompressed(&public_key);
assert_eq!(
check_metadata::<TestEnv>(&upgrade_locations, &public_key_bytes, &metadata),
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
&upgrade_locations,
&public_key_bytes,
&metadata
),
Ok(())
);
// Manipulating the partition address fails.
metadata[METADATA_SIGN_OFFSET + 8] = 0x88;
assert_eq!(
check_metadata::<TestEnv>(&upgrade_locations, &public_key_bytes, &metadata),
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
&upgrade_locations,
&public_key_bytes,
&metadata
),
Err(StorageError::CustomError)
);
metadata[METADATA_SIGN_OFFSET + 8] = 0x00;
// Wrong metadata length fails.
assert_eq!(
check_metadata::<TestEnv>(
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
&upgrade_locations,
&public_key_bytes,
&metadata[..METADATA_LEN - 1]
@@ -170,14 +186,22 @@ mod test {
// Manipulating the hash fails.
metadata[0] ^= 0x01;
assert_eq!(
check_metadata::<TestEnv>(&upgrade_locations, &public_key_bytes, &metadata),
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
&upgrade_locations,
&public_key_bytes,
&metadata
),
Err(StorageError::CustomError)
);
metadata[0] ^= 0x01;
// Manipulating the signature fails.
metadata[32] ^= 0x01;
assert_eq!(
check_metadata::<TestEnv>(&upgrade_locations, &public_key_bytes, &metadata),
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
&upgrade_locations,
&public_key_bytes,
&metadata
),
Err(StorageError::CustomError)
);
}

View File

@@ -13,6 +13,7 @@
// limitations under the License.
#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(not(feature = "std"), no_main)]
extern crate alloc;
extern crate arrayref;
@@ -21,8 +22,6 @@ extern crate byteorder;
extern crate core;
extern crate lang_items;
#[cfg(feature = "with_ctap1")]
use core::cell::Cell;
use core::convert::TryFrom;
#[cfg(feature = "debug_ctap")]
use core::fmt::Write;
@@ -30,22 +29,31 @@ use core::fmt::Write;
use ctap2::env::tock::blink_leds;
use ctap2::env::tock::{switch_off_leds, wink_leds, TockEnv};
#[cfg(feature = "with_ctap1")]
use libtock_drivers::buttons::{self, ButtonState};
use libtock_buttons::Buttons;
#[cfg(feature = "debug_ctap")]
use libtock_drivers::console::Console;
use libtock_console::Console;
#[cfg(feature = "debug_ctap")]
use libtock_console::ConsoleWriter;
use libtock_drivers::result::FlexUnwrap;
use libtock_drivers::timer::Duration;
use libtock_drivers::usb_ctap_hid;
#[cfg(not(feature = "std"))]
use libtock_runtime::{set_main, stack_size, TockSyscalls};
#[cfg(feature = "std")]
use libtock_unittest::fake;
use opensk::api::clock::Clock;
use opensk::api::connection::{HidConnection, SendOrRecvStatus, UsbEndpoint};
use opensk::api::connection::UsbEndpoint;
use opensk::ctap::hid::HidPacketIterator;
use opensk::ctap::KEEPALIVE_DELAY_MS;
use opensk::env::Env;
use opensk::Transport;
libtock_core::stack_size! {0x4000}
#[cfg(not(feature = "std"))]
stack_size! {0x4000}
#[cfg(not(feature = "std"))]
set_main! {main}
const SEND_TIMEOUT_MS: usize = 1000;
const SEND_TIMEOUT_MS: Duration<isize> = Duration::from_ms(1000);
const KEEPALIVE_DELAY_MS_TOCK: Duration<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS as isize);
#[cfg(not(feature = "vendor_hid"))]
@@ -56,19 +64,18 @@ const NUM_ENDPOINTS: usize = 2;
// The reply/replies that are queued for each endpoint.
struct EndpointReply {
endpoint: UsbEndpoint,
transport: Transport,
reply: HidPacketIterator,
}
#[cfg(feature = "std")]
type SyscallImplementation = fake::Syscalls;
#[cfg(not(feature = "std"))]
type SyscallImplementation = TockSyscalls;
impl EndpointReply {
pub fn new(endpoint: UsbEndpoint) -> Self {
EndpointReply {
endpoint,
transport: match endpoint {
UsbEndpoint::MainHid => Transport::MainHid,
#[cfg(feature = "vendor_hid")]
UsbEndpoint::VendorHid => Transport::VendorHid,
},
reply: HidPacketIterator::none(),
}
}
@@ -76,7 +83,7 @@ impl EndpointReply {
// A single packet to send.
struct SendPacket {
transport: Transport,
endpoint: UsbEndpoint,
packet: [u8; 64],
}
@@ -99,97 +106,103 @@ impl EndpointReplies {
for ep in self.replies.iter_mut() {
if let Some(packet) = ep.reply.next() {
return Some(SendPacket {
transport: ep.transport,
endpoint: ep.endpoint,
packet,
});
}
}
None
}
pub fn clear(&mut self, endpoint: UsbEndpoint) {
for ep in self.replies.iter_mut() {
if ep.endpoint == endpoint {
*ep = EndpointReply::new(endpoint);
break;
}
}
}
}
fn main() {
#[cfg(feature = "debug_ctap")]
let mut writer = Console::<SyscallImplementation>::writer();
#[cfg(feature = "debug_ctap")]
{
writeln!(writer, "Hello world from OpenSK!").ok().unwrap();
}
// Setup USB driver.
if !usb_ctap_hid::setup() {
if !usb_ctap_hid::UsbCtapHid::<SyscallImplementation>::setup() {
panic!("Cannot setup USB driver");
}
let env = TockEnv::default();
let env = TockEnv::<SyscallImplementation>::default();
let mut ctap = opensk::Ctap::new(env);
let mut led_counter = 0;
let mut led_blink_timer = <<TockEnv as Env>::Clock as Clock>::Timer::default();
let mut led_blink_timer =
<<TockEnv<SyscallImplementation> as Env>::Clock as Clock>::Timer::default();
let mut replies = EndpointReplies::new();
// Main loop. If CTAP1 is used, we register button presses for U2F while receiving and waiting.
// The way TockOS and apps currently interact, callbacks need a yield syscall to execute,
// making consistent blinking patterns and sending keepalives harder.
#[cfg(feature = "debug_ctap")]
writeln!(writer, "Entering main ctap loop").unwrap();
loop {
// Create the button callback, used for CTAP1.
#[cfg(feature = "with_ctap1")]
let button_touched = Cell::new(false);
#[cfg(feature = "with_ctap1")]
let mut buttons_callback = buttons::with_callback(|_button_num, state| {
match state {
ButtonState::Pressed => button_touched.set(true),
ButtonState::Released => (),
};
});
#[cfg(feature = "with_ctap1")]
let mut buttons = buttons_callback.init().flex_unwrap();
// At the moment, all buttons are accepted. You can customize your setup here.
#[cfg(feature = "with_ctap1")]
for mut button in &mut buttons {
button.enable().flex_unwrap();
}
let num_buttons = Buttons::<SyscallImplementation>::count().ok().unwrap();
// Variable for use in both the send_and_maybe_recv and recv cases.
let mut usb_endpoint: Option<UsbEndpoint> = None;
let mut pkt_request = [0; 64];
if let Some(mut packet) = replies.next_packet() {
// send and receive.
let hid_connection = packet.transport.hid_connection(ctap.env());
match hid_connection.send_and_maybe_recv(&mut packet.packet, SEND_TIMEOUT_MS) {
Ok(SendOrRecvStatus::Timeout) => {
if let Some(packet) = replies.next_packet() {
match usb_ctap_hid::UsbCtapHid::<SyscallImplementation>::send(
&packet.packet,
SEND_TIMEOUT_MS,
packet.endpoint as u32,
)
.flex_unwrap()
{
usb_ctap_hid::SendOrRecvStatus::Sent => {
#[cfg(feature = "debug_ctap")]
print_packet_notice(
"Sending packet timed out",
print_packet_notice::<SyscallImplementation>(
"Sent packet",
ctap.env().clock().timestamp_us(),
&mut writer,
);
// TODO: reset the ctap_hid state.
// Since sending the packet timed out, we cancel this reply.
break;
}
Ok(SendOrRecvStatus::Sent) => {
usb_ctap_hid::SendOrRecvStatus::Timeout => {
#[cfg(feature = "debug_ctap")]
print_packet_notice("Sent packet", ctap.env().clock().timestamp_us());
}
Ok(SendOrRecvStatus::Received(ep)) => {
#[cfg(feature = "debug_ctap")]
print_packet_notice(
"Received another packet",
print_packet_notice::<SyscallImplementation>(
"Timeout while sending packet",
ctap.env().clock().timestamp_us(),
&mut writer,
);
usb_endpoint = Some(ep);
// Copy to incoming packet to local buffer to be consistent
// with the receive flow.
pkt_request = packet.packet;
// The client is unresponsive, so we discard all pending packets.
replies.clear(packet.endpoint);
}
Err(_) => panic!("Error sending packet"),
}
_ => panic!("Unexpected status on USB transmission"),
};
} else {
// receive
usb_endpoint =
match usb_ctap_hid::recv_with_timeout(&mut pkt_request, KEEPALIVE_DELAY_MS_TOCK)
.flex_unwrap()
match usb_ctap_hid::UsbCtapHid::<SyscallImplementation>::recv_with_timeout(
&mut pkt_request,
KEEPALIVE_DELAY_MS_TOCK,
)
.flex_unwrap()
{
usb_ctap_hid::SendOrRecvStatus::Received(endpoint) => {
#[cfg(feature = "debug_ctap")]
print_packet_notice("Received packet", ctap.env().clock().timestamp_us());
UsbEndpoint::try_from(endpoint).ok()
print_packet_notice::<SyscallImplementation>(
"Received packet",
ctap.env().clock().timestamp_us(),
&mut writer,
);
UsbEndpoint::try_from(endpoint as usize).ok()
}
usb_ctap_hid::SendOrRecvStatus::Sent => {
panic!("Returned transmit status on receive")
@@ -200,17 +213,10 @@ fn main() {
#[cfg(feature = "with_ctap1")]
{
if button_touched.get() {
let button_touched = (0..num_buttons).any(Buttons::<SyscallImplementation>::is_pressed);
if button_touched {
ctap.u2f_grant_user_presence();
}
// Cleanup button callbacks. We miss button presses while processing though.
// Heavy computation mostly follows a registered touch luckily. Unregistering
// callbacks is important to not clash with those from check_user_presence.
for mut button in &mut buttons {
button.disable().flex_unwrap();
}
drop(buttons);
drop(buttons_callback);
}
// This call is making sure that even for long inactivity, wrapping clock values
@@ -231,7 +237,7 @@ fn main() {
if ep.reply.has_data() {
#[cfg(feature = "debug_ctap")]
writeln!(
Console::new(),
Console::<SyscallImplementation>::writer(),
"Warning overwriting existing reply for endpoint {}",
endpoint as usize
)
@@ -252,26 +258,30 @@ fn main() {
}
if ctap.should_wink() {
wink_leds(led_counter);
wink_leds::<SyscallImplementation>(led_counter);
} else {
#[cfg(not(feature = "with_ctap1"))]
switch_off_leds();
switch_off_leds::<SyscallImplementation>();
#[cfg(feature = "with_ctap1")]
if ctap.u2f_needs_user_presence() {
// Flash the LEDs with an almost regular pattern. The inaccuracy comes from
// delay caused by processing and sending of packets.
blink_leds(led_counter);
blink_leds::<SyscallImplementation>(led_counter);
} else {
switch_off_leds();
switch_off_leds::<SyscallImplementation>();
}
}
}
}
#[cfg(feature = "debug_ctap")]
fn print_packet_notice(notice_text: &str, now_us: usize) {
fn print_packet_notice<S: libtock_platform::Syscalls>(
notice_text: &str,
now_us: usize,
writer: &mut ConsoleWriter<S>,
) {
writeln!(
Console::new(),
writer,
"{} at {}.{:06} s",
notice_text,
now_us / 1_000_000,