diff --git a/Cargo.toml b/Cargo.toml index 026c16d..2190f9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ log = "0.4" heapless-bytes = "0.3.0" subtle = { version = "2", default-features = false } trussed-rsa-alloc = { version = "0.1.0", features = ["raw"] } +trussed-staging = { version = "0.1.0", features = ["chunked", "encrypted-chunked"]} [dev-dependencies] littlefs2 = "0.3.2" @@ -58,7 +59,7 @@ usbd-ccid = { version = "0.2.0", features = ["highspeed-usb"]} rand = "0.8.5" [features] -default = [] +default = ["virt"] strict-pin = [] std = [] vpicc = ["std", "dep:vpicc", "virt"] @@ -74,10 +75,10 @@ log-warn = [] log-error = [] [patch.crates-io] -trussed = { git = "https://github.com/Nitrokey/trussed", tag = "v0.1.0-nitrokey.9" } -trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth", tag = "v0.2.1"} +trussed = { git = "https://github.com/trussed-dev/trussed" , rev = "55ea391367fce4bf5093ff2d3c79041d7aef0485" } +trussed-auth = { git = "https://github.com/trussed-dev/trussed-auth.git", rev = "6beabd125ea57e87bf642e18278fcc2ba224c50c"} trussed-rsa-alloc = { git = "https://github.com/Nitrokey/trussed-rsa-backend.git", tag = "v0.1.0"} -littlefs2 = { git = "https://github.com/Nitrokey/littlefs2", tag = "v0.3.2-nitrokey-2" } +trussed-staging = { git = "https://github.com/Nitrokey/trussed-staging", rev = "a3df414fd569564c745d1a597a89869e57ece1cf" } [profile.dev.package.rsa] opt-level = 2 diff --git a/src/dispatch.rs b/src/dispatch.rs index ad33cb7..fb7a11d 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -4,13 +4,11 @@ use crate::{reply::Reply, Authenticator, /*constants::PIV_AID,*/ Result}; use apdu_dispatch::{app::App, command, response, Command}; -use trussed::client; -use trussed_auth::AuthClient; #[cfg(feature = "apdu-dispatch")] impl App<{ command::SIZE }, { response::SIZE }> for Authenticator where - T: client::Client + AuthClient + client::Ed255 + client::Tdes, + T: crate::Client, { fn select(&mut self, _apdu: &Command, reply: &mut response::Data) -> Result { self.select(Reply(reply)) diff --git a/src/lib.rs b/src/lib.rs index f7f0aac..4fbb09d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,7 @@ pub mod state; mod tlv; pub use piv_types::{AsymmetricAlgorithms, Pin, Puk}; +use trussed_staging::streaming::ChunkedClient; #[cfg(feature = "virt")] pub mod virt; @@ -100,7 +101,7 @@ impl iso7816::App for Authenticator { impl Authenticator where - T: client::Client + AuthClient + client::Ed255 + client::Tdes, + T: Client, { pub fn new(trussed: T, options: Options) -> Self { // seems like RefCell is not the right thing, we want something like `Rc` instead, @@ -219,7 +220,7 @@ where } } -impl<'a, T: trussed::Client + AuthClient + trussed::client::Ed255> LoadedAuthenticator<'a, T> { +impl<'a, T: Client> LoadedAuthenticator<'a, T> { pub fn yubico_set_administration_key( &mut self, data: &[u8], @@ -991,3 +992,13 @@ impl<'a, T: trussed::Client + AuthClient + trussed::client::Ed255> LoadedAuthent Ok(()) } } + +/// Super trait with all trussed extensions required by opcard +pub trait Client: + trussed::Client + AuthClient + ChunkedClient + trussed::client::Ed255 + client::Tdes +{ +} +impl Client + for C +{ +} diff --git a/src/state.rs b/src/state.rs index 0e00b84..2b729ce 100644 --- a/src/state.rs +++ b/src/state.rs @@ -8,15 +8,13 @@ use flexiber::EncodableHeapless; use heapless::Vec; use heapless_bytes::Bytes; use iso7816::Status; -use trussed::types::OpenSeekFrom; use trussed::{ api::reply::Metadata, config::MAX_MESSAGE_LENGTH, syscall, try_syscall, types::{KeyId, KeySerialization, Location, Mechanism, PathBuf, StorageAttributes}, - utils, }; -use trussed_auth::AuthClient; +use trussed_staging::streaming::utils; use crate::piv_types::CardHolderUniqueIdentifier; use crate::reply::Reply; @@ -192,7 +190,7 @@ pub struct State { } impl State { - pub fn load( + pub fn load( &mut self, client: &mut T, storage: Location, @@ -206,7 +204,7 @@ impl State { }) } - pub fn persistent( + pub fn persistent( &mut self, client: &mut T, storage: Location, @@ -334,41 +332,33 @@ impl Persistent { const DEFAULT_PIN: Pin = Pin(*b"123456\xff\xff"); const DEFAULT_PUK: Puk = Puk(*b"12345678"); - pub fn remaining_pin_retries(&self, client: &mut T) -> u8 { + pub fn remaining_pin_retries(&self, client: &mut T) -> u8 { try_syscall!(client.pin_retries(PinType::UserPin)) .map(|r| r.retries.unwrap_or_default()) .unwrap_or(0) } - pub fn remaining_puk_retries(&self, client: &mut T) -> u8 { + pub fn remaining_puk_retries(&self, client: &mut T) -> u8 { try_syscall!(client.pin_retries(PinType::Puk)) .map(|r| r.retries.unwrap_or_default()) .unwrap_or(0) } - pub fn verify_pin( - &mut self, - value: &Pin, - client: &mut T, - ) -> bool { + pub fn verify_pin(&mut self, value: &Pin, client: &mut T) -> bool { let pin = Bytes::from_slice(&value.0).expect("Convertion of static array"); try_syscall!(client.check_pin(PinType::UserPin, pin)) .map(|r| r.success) .unwrap_or(false) } - pub fn verify_puk( - &mut self, - value: &Puk, - client: &mut T, - ) -> bool { + pub fn verify_puk(&mut self, value: &Puk, client: &mut T) -> bool { let puk = Bytes::from_slice(&value.0).expect("Convertion of static array"); try_syscall!(client.check_pin(PinType::Puk, puk)) .map(|r| r.success) .unwrap_or(false) } - pub fn change_pin( + pub fn change_pin( &mut self, old_value: &Pin, new_value: &Pin, @@ -381,7 +371,7 @@ impl Persistent { .unwrap_or(false) } - pub fn change_puk( + pub fn change_puk( &mut self, old_value: &Puk, new_value: &Puk, @@ -394,7 +384,7 @@ impl Persistent { .unwrap_or(false) } - pub fn set_pin( + pub fn set_pin( &mut self, new_pin: Pin, client: &mut T, @@ -413,7 +403,7 @@ impl Persistent { .map(drop) } - pub fn set_puk( + pub fn set_puk( &mut self, new_puk: Puk, client: &mut T, @@ -426,14 +416,14 @@ impl Persistent { }) .map(drop) } - pub fn reset_pin( + pub fn reset_pin( &mut self, new_pin: Pin, client: &mut T, ) -> Result<(), Status> { self.set_pin(new_pin, client) } - pub fn reset_puk( + pub fn reset_puk( &mut self, new_puk: Puk, client: &mut T, @@ -441,7 +431,7 @@ impl Persistent { self.set_puk(new_puk, client) } - pub fn reset_administration_key(&mut self, client: &mut impl trussed::Client) { + pub fn reset_administration_key(&mut self, client: &mut impl crate::Client) { self.set_administration_key( YUBICO_DEFAULT_MANAGEMENT_KEY, YUBICO_DEFAULT_MANAGEMENT_KEY_ALG, @@ -453,7 +443,7 @@ impl Persistent { &mut self, management_key: &[u8], alg: AdministrationAlgorithm, - client: &mut impl trussed::Client, + client: &mut impl crate::Client, ) { // let new_management_key = syscall!(self.trussed.unsafe_inject_tdes_key( let id = syscall!(client.unsafe_inject_key( @@ -483,7 +473,7 @@ impl Persistent { &mut self, key: AsymmetricKeyReference, alg: AsymmetricAlgorithms, - client: &mut impl trussed::Client, + client: &mut impl crate::Client, ) -> KeyId { let id = syscall!(client.generate_key( alg.key_mechanism(), @@ -498,10 +488,7 @@ impl Persistent { id } - pub fn initialize( - client: &mut T, - storage: Location, - ) -> Result { + pub fn initialize(client: &mut T, storage: Location) -> Result { info!("initializing PIV state"); let administration = KeyWithAlg { id: syscall!(client.unsafe_inject_key( @@ -566,7 +553,7 @@ impl Persistent { Ok(state) } - pub fn load_or_initialize( + pub fn load_or_initialize( client: &mut T, storage: Location, ) -> Result { @@ -584,13 +571,13 @@ impl Persistent { Ok(parsed) } - pub fn save(&mut self, client: &mut impl trussed::Client) { + pub fn save(&mut self, client: &mut impl crate::Client) { let data: trussed::types::Message = trussed::cbor_serialize_bytes(&self).unwrap(); syscall!(client.write_file(self.storage, PathBuf::from(Self::FILENAME), data, None,)); } - pub fn timestamp(&mut self, client: &mut impl trussed::Client) -> u32 { + pub fn timestamp(&mut self, client: &mut impl crate::Client) -> u32 { self.timestamp += 1; self.save(client); self.timestamp @@ -598,7 +585,7 @@ impl Persistent { } fn load_if_exists( - client: &mut impl trussed::Client, + client: &mut impl crate::Client, location: Location, path: &PathBuf, ) -> Result>, Status> { @@ -622,26 +609,30 @@ fn load_if_exists( /// Returns false if the file does not exist fn load_if_exists_streaming( - client: &mut impl trussed::Client, + client: &mut impl crate::Client, location: Location, path: &PathBuf, mut buffer: Reply<'_, R>, ) -> Result { let mut read_len = 0; let file_len; - match try_syscall!(client.read_file_chunk(location, path.clone(), OpenSeekFrom::Start(0))) { + match try_syscall!(client.start_chunked_read(location, path.clone())) { Ok(r) => { read_len += r.data.len(); file_len = r.len; buffer.append_len(file_len)?; buffer.expand(&r.data)?; + if !r.data.is_full() { + debug_assert_eq!(read_len, file_len); + return Ok(true); + } } - Err(_) => match try_syscall!(client.entry_metadata(location, path.clone())) { + Err(_err) => match try_syscall!(client.entry_metadata(location, path.clone())) { Ok(Metadata { metadata: None }) => return Ok(false), Ok(Metadata { metadata: Some(_metadata), }) => { - error!("File {path} exists but couldn't be read: {_metadata:?}"); + error!("File {path} exists but couldn't be read: {_metadata:?}, {_err:?}"); return Err(Status::UnspecifiedPersistentExecutionError); } Err(_err) => { @@ -651,15 +642,16 @@ fn load_if_exists_streaming( }, } - while read_len < file_len { - match try_syscall!(client.read_file_chunk( - location, - path.clone(), - OpenSeekFrom::Start(read_len as u32) - )) { + loop { + match try_syscall!(client.read_file_chunk()) { Ok(r) => { + debug_assert_eq!(r.len, file_len); read_len += r.data.len(); buffer.expand(&r.data)?; + if !r.data.is_full() { + debug_assert_eq!(read_len, file_len); + break; + } } Err(_err) => { error!("Failed to read chunk: {:?}", _err); @@ -732,7 +724,7 @@ impl ContainerStorage { pub fn exists( self, - client: &mut impl trussed::Client, + client: &mut impl crate::Client, storage: Location, ) -> Result { match try_syscall!(client.entry_metadata(storage, self.path())) { @@ -759,7 +751,7 @@ impl ContainerStorage { // Write the length of the file and write pub fn load( self, - client: &mut impl trussed::Client, + client: &mut impl crate::Client, storage: Location, mut reply: Reply<'_, R>, ) -> Result { @@ -778,11 +770,11 @@ impl ContainerStorage { pub fn save( self, - client: &mut impl trussed::Client, + client: &mut impl crate::Client, bytes: &[u8], storage: Location, ) -> Result<(), Status> { - utils::write_all(client, storage, self.path(), bytes, None).map_err(|_err| { + utils::write_all(client, storage, self.path(), bytes, None, None).map_err(|_err| { error!("Failed to write data object: {:?}", _err); Status::UnspecifiedNonpersistentExecutionError }) diff --git a/src/virt.rs b/src/virt.rs index b5d21c7..9aeb61e 100644 --- a/src/virt.rs +++ b/src/virt.rs @@ -10,16 +10,17 @@ pub mod dispatch { backend::{Backend as _, BackendId}, error::Error, platform::Platform, - serde_extensions::{ExtensionDispatch, ExtensionId, ExtensionImpl as _}, + serde_extensions::{ExtensionDispatch, ExtensionId, ExtensionImpl}, service::ServiceResources, types::{Bytes, Context, Location}, }; use trussed_auth::{AuthBackend, AuthContext, AuthExtension, MAX_HW_KEY_LEN}; - use trussed_rsa_alloc::SoftwareRsa; + use trussed_staging::{streaming::ChunkedExtension, StagingBackend, StagingContext}; /// Backends used by opcard pub const BACKENDS: &[BackendId] = &[ + BackendId::Custom(Backend::Staging), BackendId::Custom(Backend::Auth), BackendId::Custom(Backend::Rsa), BackendId::Core, @@ -29,17 +30,20 @@ pub mod dispatch { pub enum Backend { Auth, Rsa, + Staging, } #[derive(Debug, Clone, Copy)] pub enum Extension { Auth, + Chunked, } impl From for u8 { fn from(extension: Extension) -> Self { match extension { Extension::Auth => 0, + Extension::Chunked => 1, } } } @@ -50,6 +54,7 @@ pub mod dispatch { fn try_from(id: u8) -> Result { match id { 0 => Ok(Extension::Auth), + 1 => Ok(Extension::Chunked), _ => Err(Error::InternalError), } } @@ -59,24 +64,28 @@ pub mod dispatch { #[derive(Debug)] pub struct Dispatch { auth: AuthBackend, + staging: StagingBackend, } /// Dispatch context for the backends required by opcard - #[derive(Default, Debug)] + #[derive(Default)] pub struct DispatchContext { auth: AuthContext, + staging: StagingContext, } impl Dispatch { pub fn new() -> Self { Self { auth: AuthBackend::new(Location::Internal), + staging: StagingBackend::new(), } } pub fn with_hw_key(hw_key: Bytes) -> Self { Self { auth: AuthBackend::with_hw_key(Location::Internal, hw_key), + staging: StagingBackend::new(), } } } @@ -104,6 +113,12 @@ pub mod dispatch { self.auth .request(&mut ctx.core, &mut ctx.backends.auth, request, resources) } + Backend::Staging => self.staging.request( + &mut ctx.core, + &mut ctx.backends.staging, + request, + resources, + ), Backend::Rsa => SoftwareRsa.request(&mut ctx.core, &mut (), request, resources), } } @@ -124,7 +139,20 @@ pub mod dispatch { request, resources, ), - }, + Extension::Chunked => Err(Error::RequestNotAvailable), + } + Backend::Staging =>match extension { + Extension::Chunked => { + >::extension_request_serialized( + &mut self.staging, + &mut ctx.core, + &mut ctx.backends.staging, + request, + resources + ) + } + Extension::Auth => Err(Error::RequestNotAvailable), + } Backend::Rsa => Err(Error::RequestNotAvailable), } } @@ -135,6 +163,11 @@ pub mod dispatch { const ID: Self::Id = Self::Id::Auth; } + impl ExtensionId for Dispatch { + type Id = Extension; + + const ID: Self::Id = Self::Id::Chunked; + } } use std::path::PathBuf;