diff --git a/Cargo.toml b/Cargo.toml index ba9609b..237cfb3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,9 +72,8 @@ log-warn = [] log-error = [] [patch.crates-io] -# trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey-4"} -trussed = { git = "https://github.com/trussed-dev/trussed", rev = "d9276a689d68ffeb4c5d9ac635b18232be172f45"} -# littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-1" } + trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey.8"} +littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-2" } [profile.dev.package.rsa] opt-level = 2 diff --git a/examples/usbip.rs b/examples/usbip.rs index 7fe20f9..fa62ce9 100644 --- a/examples/usbip.rs +++ b/examples/usbip.rs @@ -22,7 +22,7 @@ impl trussed_usbip::Apps>>, ()> _data: (), ) -> Self { PivApp { - piv: piv::Authenticator::new(make_client("piv")), + piv: piv::Authenticator::new(make_client("piv"), piv::Options::default()), } } diff --git a/examples/virtual.rs b/examples/virtual.rs index 4f20b27..8404d1c 100644 --- a/examples/virtual.rs +++ b/examples/virtual.rs @@ -11,11 +11,13 @@ // TODO: add CLI +use piv_authenticator::{Authenticator, Options}; + fn main() { env_logger::init(); trussed_rsa_alloc::virt::with_ram_client("piv-authenticator", |client| { - let card = piv_authenticator::Authenticator::new(client); + let card = Authenticator::new(client, Options::default()); let mut virtual_card = piv_authenticator::vpicc::VirtualCard::new(card); let vpicc = vpicc::connect().expect("failed to connect to vpicc"); vpicc diff --git a/src/lib.rs b/src/lib.rs index 96330e7..5dde67b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,7 +36,7 @@ use core::convert::TryInto; use flexiber::EncodableHeapless; use heapless_bytes::Bytes; use iso7816::{Data, Status}; -use trussed::types::{KeySerialization, Location, StorageAttributes}; +use trussed::types::{KeySerialization, Location, PathBuf, StorageAttributes}; use trussed::{client, syscall, try_syscall}; use constants::*; @@ -45,17 +45,38 @@ pub type Result = iso7816::Result; use reply::Reply; use state::{AdministrationAlgorithm, CommandCache, KeyWithAlg, LoadedState, State, TouchPolicy}; +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Options { + storage: Location, +} + +impl Default for Options { + fn default() -> Self { + Self { + storage: Location::External, + } + } +} + +impl Options { + pub fn storage(self, storage: Location) -> Self { + Self { storage, ..self } + } +} + /// PIV authenticator Trussed app. /// /// The `C` parameter is necessary, as PIV includes command sequences, /// where we need to store the previous command, so we need to know how /// much space to allocate. pub struct Authenticator { + options: Options, state: State, trussed: T, } struct LoadedAuthenticator<'a, T> { + options: &'a mut Options, state: LoadedState<'a>, trussed: &'a mut T, } @@ -70,21 +91,23 @@ impl Authenticator where T: client::Client + client::Ed255 + client::Tdes, { - pub fn new(trussed: T) -> Self { + pub fn new(trussed: T, options: Options) -> Self { // seems like RefCell is not the right thing, we want something like `Rc` instead, // which can be cloned and injected into other parts of the App that use Trussed. // let trussed = RefCell::new(trussed); Self { // state: state::State::new(trussed.clone()), state: Default::default(), + options, trussed, } } fn load(&mut self) -> core::result::Result, Status> { Ok(LoadedAuthenticator { - state: self.state.load(&mut self.trussed)?, + state: self.state.load(&mut self.trussed, self.options.storage)?, trussed: &mut self.trussed, + options: &mut self.options, }) } @@ -170,30 +193,13 @@ where } YubicoPivExtension::Reset => { - let persistent_state = self.state.persistent(&mut self.trussed)?; + let this = self.load()?; // TODO: find out what all needs resetting :) - persistent_state.reset_pin(&mut self.trussed); - persistent_state.reset_puk(&mut self.trussed); - persistent_state.reset_administration_key(&mut self.trussed); - self.state.volatile.app_security_status.pin_verified = false; - self.state.volatile.app_security_status.puk_verified = false; - self.state - .volatile - .app_security_status - .administrator_verified = false; - - try_syscall!(self.trussed.remove_file( - trussed::types::Location::Internal, - trussed::types::PathBuf::from(b"printed-information"), - )) - .ok(); - - try_syscall!(self.trussed.remove_file( - trussed::types::Location::Internal, - trussed::types::PathBuf::from(b"authentication-key.x5c"), - )) - .ok(); + for location in [Location::Volatile, Location::External, Location::Internal] { + try_syscall!(this.trussed.delete_all(location)).ok(); + try_syscall!(this.trussed.remove_dir_all(location, PathBuf::new())).ok(); + } } YubicoPivExtension::SetManagementKey(touch_policy) => { @@ -916,7 +922,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> let offset = reply.len(); match container { Container::KeyHistoryObject => self.get_key_history_object(reply.lend())?, - _ => match ContainerStorage(container).load(self.trussed)? { + _ => match ContainerStorage(container).load(self.trussed, self.options.storage)? { Some(data) => reply.expand(&data)?, None => return Err(Status::NotFound), }, @@ -946,7 +952,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> }; use state::ContainerStorage; - ContainerStorage(container).save(self.trussed, data) + ContainerStorage(container).save(self.trussed, data, self.options.storage) } fn reset_retry_counter(&mut self, data: ResetRetryCounter) -> Result { @@ -976,7 +982,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> use state::ContainerStorage; for c in RETIRED_CERTS { - if ContainerStorage(c).exists(self.trussed)? { + if ContainerStorage(c).exists(self.trussed, self.options.storage)? { num_certs += 1; } } diff --git a/src/state.rs b/src/state.rs index 5b8d64d..507829e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -188,9 +188,13 @@ pub struct State { } impl State { - pub fn load(&mut self, client: &mut impl trussed::Client) -> Result, Status> { + pub fn load( + &mut self, + client: &mut impl trussed::Client, + storage: Location, + ) -> Result, Status> { if self.persistent.is_none() { - self.persistent = Some(Persistent::load_or_initialize(client)?); + self.persistent = Some(Persistent::load_or_initialize(client, storage)?); } Ok(LoadedState { volatile: &mut self.volatile, @@ -201,8 +205,9 @@ impl State { pub fn persistent( &mut self, client: &mut impl trussed::Client, + storage: Location, ) -> Result<&mut Persistent, Status> { - Ok(self.load(client)?.persistent) + Ok(self.load(client, storage)?.persistent) } pub fn new() -> Self { @@ -216,6 +221,11 @@ pub struct LoadedState<'t> { pub persistent: &'t mut Persistent, } +/// exists only to please serde, which doesn't accept enum variants in `#[serde(default=…)]` +fn volatile() -> Location { + Location::Volatile +} + #[derive(Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct Persistent { pub keys: Keys, @@ -230,6 +240,8 @@ pub struct Persistent { // pin_hash: Option<[u8; 16]>, // Ideally, we'd dogfood a "Monotonic Counter" from `trussed`. timestamp: u32, + #[serde(skip, default = "volatile")] + storage: Location, } #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -443,7 +455,7 @@ impl Persistent { let id = syscall!(client.unsafe_inject_key( alg.mechanism(), management_key, - trussed::types::Location::Internal, + self.storage, KeySerialization::Raw )) .key; @@ -471,7 +483,7 @@ impl Persistent { ) -> KeyId { let id = syscall!(client.generate_key( alg.key_mechanism(), - StorageAttributes::default().set_persistence(Location::Internal) + StorageAttributes::default().set_persistence(self.storage) )) .key; let old = self.set_asymmetric_key(key, id, alg); @@ -482,13 +494,13 @@ impl Persistent { id } - pub fn initialize(client: &mut impl trussed::Client) -> Self { + pub fn initialize(client: &mut impl trussed::Client, storage: Location) -> Self { info!("initializing PIV state"); let administration = KeyWithAlg { id: syscall!(client.unsafe_inject_key( YUBICO_DEFAULT_MANAGEMENT_KEY_ALG.mechanism(), YUBICO_DEFAULT_MANAGEMENT_KEY, - trussed::types::Location::Internal, + storage, KeySerialization::Raw )) .key, @@ -498,7 +510,7 @@ impl Persistent { let authentication = KeyWithAlg { id: syscall!(client.generate_key( Mechanism::P256, - StorageAttributes::new().set_persistence(Location::Internal) + StorageAttributes::new().set_persistence(storage) )) .key, alg: AsymmetricAlgorithms::P256, @@ -521,6 +533,7 @@ impl Persistent { .save( client, &guid_file[2..], // Remove the unnecessary 53 tag + storage, ) .ok(); @@ -541,34 +554,35 @@ impl Persistent { pin: Pin::try_from(Self::DEFAULT_PIN).unwrap(), puk: Puk::try_from(Self::DEFAULT_PUK).unwrap(), timestamp: 0, + // In case of forgotten to rebind, ensure the bug is found + storage: Location::Volatile, }; state.save(client); state } - pub fn load_or_initialize(client: &mut impl trussed::Client) -> Result { + pub fn load_or_initialize( + client: &mut impl trussed::Client, + storage: Location, + ) -> Result { // todo: can't seem to combine load + initialize without code repetition - let data = load_if_exists(client, Location::Internal, &PathBuf::from(Self::FILENAME))?; + let data = load_if_exists(client, storage, &PathBuf::from(Self::FILENAME))?; let Some(bytes) = data else { - return Ok( Self::initialize(client)); + return Ok( Self::initialize(client, storage)); }; - let parsed = trussed::cbor_deserialize(&bytes).map_err(|_err| { + let mut parsed: Self = trussed::cbor_deserialize(&bytes).map_err(|_err| { error!("{_err:?}"); Status::UnspecifiedPersistentExecutionError })?; + parsed.storage = storage; Ok(parsed) } pub fn save(&mut self, client: &mut impl trussed::Client) { let data: trussed::types::Message = trussed::cbor_serialize_bytes(&self).unwrap(); - syscall!(client.write_file( - Location::Internal, - PathBuf::from(Self::FILENAME), - data, - None, - )); + syscall!(client.write_file(self.storage, PathBuf::from(Self::FILENAME), data, None,)); } pub fn timestamp(&mut self, client: &mut impl trussed::Client) -> u32 { @@ -661,8 +675,12 @@ impl ContainerStorage { } } - pub fn exists(self, client: &mut impl trussed::Client) -> Result { - match try_syscall!(client.entry_metadata(Location::Internal, self.path())) { + pub fn exists( + self, + client: &mut impl trussed::Client, + storage: Location, + ) -> Result { + match try_syscall!(client.entry_metadata(storage, self.path())) { Ok(Metadata { metadata: None }) => Ok(false), Ok(Metadata { metadata: Some(metadata), @@ -686,22 +704,26 @@ impl ContainerStorage { pub fn load( self, client: &mut impl trussed::Client, + storage: Location, ) -> Result>, Status> { - load_if_exists(client, Location::Internal, &self.path()) + load_if_exists(client, storage, &self.path()) .map(|data| data.or_else(|| self.default().map(Bytes::from))) } - pub fn save(self, client: &mut impl trussed::Client, bytes: &[u8]) -> Result<(), Status> { + pub fn save( + self, + client: &mut impl trussed::Client, + bytes: &[u8], + storage: Location, + ) -> Result<(), Status> { let msg = Bytes::from(heapless::Vec::try_from(bytes).map_err(|_| { error!("Buffer full"); Status::IncorrectDataParameter })?); - try_syscall!(client.write_file(Location::Internal, self.path(), msg, None)).map_err( - |_err| { - error!("Failed to store data: {_err:?}"); - Status::UnspecifiedNonpersistentExecutionError - }, - )?; + try_syscall!(client.write_file(storage, self.path(), msg, None)).map_err(|_err| { + error!("Failed to store data: {_err:?}"); + Status::UnspecifiedNonpersistentExecutionError + })?; Ok(()) } } diff --git a/tests/card/mod.rs b/tests/card/mod.rs index de845ae..bdfd344 100644 --- a/tests/card/mod.rs +++ b/tests/card/mod.rs @@ -1,7 +1,7 @@ // Copyright (C) 2022 Nitrokey GmbH // SPDX-License-Identifier: LGPL-3.0-only -use piv_authenticator::{vpicc::VirtualCard, Authenticator}; +use piv_authenticator::{vpicc::VirtualCard, Authenticator, Options}; use std::{sync::mpsc, thread::sleep, time::Duration}; use stoppable_thread::spawn; @@ -18,7 +18,7 @@ pub fn with_vsc R, R>(f: F) -> R { let (tx, rx) = mpsc::channel(); let handle = spawn(move |stopped| { trussed_rsa_alloc::virt::with_ram_client("opcard", |client| { - let card = Authenticator::new(client); + let card = Authenticator::new(client, Options::default()); let mut virtual_card = VirtualCard::new(card); let mut result = Ok(()); while !stopped.get() && result.is_ok() { diff --git a/tests/setup/mod.rs b/tests/setup/mod.rs index d559ced..a355692 100644 --- a/tests/setup/mod.rs +++ b/tests/setup/mod.rs @@ -11,6 +11,7 @@ macro_rules! cmd { }; } +use piv_authenticator::{Authenticator, Options}; use trussed::virt::Ram; use trussed_rsa_alloc::virt::Client; @@ -18,7 +19,7 @@ pub type Piv = piv_authenticator::Authenticator>; pub fn piv(test: impl FnOnce(&mut Piv) -> R) -> R { trussed_rsa_alloc::virt::with_ram_client("test", |client| { - let mut piv_app = piv_authenticator::Authenticator::new(client); + let mut piv_app = Authenticator::new(client, Options::default()); test(&mut piv_app) }) }