diff --git a/src/lib.rs b/src/lib.rs index 117a5b4..c6b9b34 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod derp; #[cfg(feature = "apdu-dispatch")] mod dispatch; pub mod piv_types; +mod reply; pub mod state; pub use piv_types::{AsymmetricAlgorithms, Pin, Puk}; @@ -39,6 +40,7 @@ use trussed::{client, syscall, try_syscall}; use constants::*; pub type Result = iso7816::Result<()>; +use reply::Reply; use state::{AdministrationAlgorithm, CommandCache, LoadedState, State, TouchPolicy}; use crate::piv_types::DynamicAuthenticationTemplate; @@ -90,7 +92,7 @@ where // The way apdu-dispatch currently works, this would deselect, resetting security indicators. pub fn deselect(&mut self) {} - pub fn select(&mut self, reply: &mut Data) -> Result { + pub fn select(&mut self, mut reply: Reply<'_, R>) -> Result { use piv_types::Algorithms::*; info!("selecting PIV maybe"); @@ -100,7 +102,7 @@ where .with_supported_cryptographic_algorithms(&[Tdes, Aes256, P256, Ed25519, X25519]); application_property_template - .encode_to_heapless_vec(reply) + .encode_to_heapless_vec(*reply) .unwrap(); info!("returning: {:02X?}", reply); Ok(()) @@ -114,6 +116,7 @@ where info!("PIV responding to {:?}", command); let parsed_command: Command = command.try_into()?; info!("parsed: {:?}", &parsed_command); + let reply = Reply(reply); match parsed_command { Command::Verify(verify) => self.load()?.verify(verify), @@ -140,7 +143,7 @@ where fn get_data( &mut self, container: container::Container, - reply: &mut Data, + mut reply: Reply<'_, R>, ) -> Result { // TODO: check security status, else return Status::SecurityStatusNotSatisfied @@ -162,7 +165,7 @@ where // '5FC1 07' (351B) Container::CardCapabilityContainer => { piv_types::CardCapabilityContainer::default() - .encode_to_heapless_vec(reply) + .encode_to_heapless_vec(*reply) .unwrap(); info!("returning CCC {:02X?}", reply); } @@ -172,7 +175,7 @@ where let guid = self.state.persistent(&mut self.trussed)?.guid(); piv_types::CardHolderUniqueIdentifier::default() .with_guid(guid) - .encode_to_heapless_vec(reply) + .encode_to_heapless_vec(*reply) .unwrap(); info!("returning CHUID {:02X?}", reply); } @@ -218,7 +221,7 @@ where &mut self, data: &[u8], instruction: YubicoPivExtension, - reply: &mut Data, + mut reply: Reply<'_, R>, ) -> Result { info!("yubico extension: {:?}", &instruction); match instruction { @@ -283,7 +286,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> &mut self, data: &[u8], _touch_policy: TouchPolicy, - _reply: &mut Data, + _reply: Reply<'_, R>, ) -> Result { // cmd := apdu{ // instruction: insSetMGMKey, @@ -475,7 +478,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> &mut self, auth: GeneralAuthenticate, data: &[u8], - reply: &mut Data, + reply: Reply<'_, R>, ) -> Result { // For "SSH", we need implement A.4.2 in SP-800-73-4 Part 2, ECDSA signatures // @@ -523,7 +526,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> &mut self, auth: GeneralAuthenticate, data: derp::Input<'_>, - _reply: &mut Data, + _reply: Reply<'_, R>, ) -> Result { info!("Request for response"); let alg = self.state.persistent.keys.administration.alg; @@ -565,7 +568,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> &mut self, _auth: GeneralAuthenticate, _data: derp::Input<'_>, - _reply: &mut Data, + _reply: Reply<'_, R>, ) -> Result { info!("Request for exponentiation"); todo!() @@ -575,7 +578,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> &mut self, auth: GeneralAuthenticate, data: derp::Input<'_>, - reply: &mut Data, + mut reply: Reply<'_, R>, ) -> Result { let alg = self.state.persistent.keys.administration.alg; if !data.is_empty() { @@ -592,7 +595,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> Bytes::from_slice(&challenge).unwrap(), )); let resp = DynamicAuthenticationTemplate::with_challenge(&challenge); - resp.encode_to_heapless_vec(reply) + resp.encode_to_heapless_vec(*reply) .map_err(|_err| { error!("Failed to encode challenge: {_err:?}"); Status::UnspecifiedNonpersistentExecutionError @@ -604,7 +607,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> &mut self, _auth: GeneralAuthenticate, _data: derp::Input<'_>, - _reply: &mut Data, + _reply: Reply<'_, R>, ) -> Result { info!("Request for witness"); todo!() @@ -614,7 +617,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> &mut self, reference: AsymmetricKeyReference, data: &[u8], - reply: &mut Data, + reply: Reply<'_, R>, ) -> Result { if !self .state diff --git a/src/reply.rs b/src/reply.rs new file mode 100644 index 0000000..4ecdcd0 --- /dev/null +++ b/src/reply.rs @@ -0,0 +1,134 @@ +// Copyright (C) 2022 Nitrokey GmbH +// SPDX-License-Identifier: LGPL-3.0-only + +use iso7816::Status; + +use core::ops::{Deref, DerefMut}; + +#[derive(Debug)] +pub struct Reply<'v, const R: usize>(pub &'v mut heapless::Vec); + +impl<'v, const R: usize> Deref for Reply<'v, R> { + type Target = &'v mut heapless::Vec; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl<'v, const R: usize> DerefMut for Reply<'v, R> { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl<'v, const R: usize> Reply<'v, R> { + /// Extend the reply and return an error otherwise + /// The MoreAvailable and GET RESPONSE mechanisms are handled by adpu_dispatch + /// + /// Named expand and not extend to avoid conflicts with Deref + pub fn expand(&mut self, data: &[u8]) -> Result<(), Status> { + self.0.extend_from_slice(data).map_err(|_| { + error!("Buffer full"); + Status::NotEnoughMemory + }) + } + + fn serialize_len(len: usize) -> Result, Status> { + let mut buf = heapless::Vec::new(); + if let Ok(len) = u8::try_from(len) { + if len <= 0x7f { + buf.extend_from_slice(&[len]).ok(); + } else { + buf.extend_from_slice(&[0x81, len]).ok(); + } + } else if let Ok(len) = u16::try_from(len) { + let arr = len.to_be_bytes(); + buf.extend_from_slice(&[0x82, arr[0], arr[1]]).ok(); + } else { + error!("Length too long to be encoded"); + return Err(Status::UnspecifiedNonpersistentExecutionError); + } + Ok(buf) + } + + /// Prepend the length to some data. + /// + /// Input: + /// AAAAAAAAAABBBBBBB + /// ↑ + /// offset + /// + /// Output: + /// + /// AAAAAAAAAA 7 BBBBBBB + /// (There are seven Bs, the length is encoded as specified in § 4.4.4) + pub fn prepend_len(&mut self, offset: usize) -> Result<(), Status> { + if self.len() < offset { + error!("`prepend_len` called with offset lower than buffer length"); + return Err(Status::UnspecifiedNonpersistentExecutionError); + } + let len = self.len() - offset; + let encoded = Self::serialize_len(len)?; + self.extend_from_slice(&encoded).map_err(|_| { + error!("Buffer full"); + Status::UnspecifiedNonpersistentExecutionError + })?; + self[offset..].rotate_right(encoded.len()); + Ok(()) + } + + pub fn append_len(&mut self, len: usize) -> Result<(), Status> { + let encoded = Self::serialize_len(len)?; + self.extend_from_slice(&encoded).map_err(|_| { + error!("Buffer full"); + Status::UnspecifiedNonpersistentExecutionError + }) + } + + pub fn lend(&mut self) -> Reply<'_, R> { + Reply(self.0) + } +} + +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used, clippy::expect_used)] + use super::*; + #[test] + fn prep_length() { + let mut tmp = heapless::Vec::::new(); + let mut buf = Reply(&mut tmp); + let offset = buf.len(); + buf.extend_from_slice(&[0; 0]).unwrap(); + buf.prepend_len(offset).unwrap(); + assert_eq!(&buf[offset..], [0]); + + let offset = buf.len(); + buf.extend_from_slice(&[0; 20]).unwrap(); + buf.prepend_len(offset).unwrap(); + let mut expected = vec![20]; + expected.extend_from_slice(&[0; 20]); + assert_eq!(&buf[offset..], expected,); + + let offset = buf.len(); + buf.extend_from_slice(&[1; 127]).unwrap(); + buf.prepend_len(offset).unwrap(); + let mut expected = vec![127]; + expected.extend_from_slice(&[1; 127]); + assert_eq!(&buf[offset..], expected); + + let offset = buf.len(); + buf.extend_from_slice(&[2; 128]).unwrap(); + buf.prepend_len(offset).unwrap(); + let mut expected = vec![0x81, 128]; + expected.extend_from_slice(&[2; 128]); + assert_eq!(&buf[offset..], expected); + + let offset = buf.len(); + buf.extend_from_slice(&[3; 256]).unwrap(); + buf.prepend_len(offset).unwrap(); + let mut expected = vec![0x82, 0x01, 0x00]; + expected.extend_from_slice(&[3; 256]); + assert_eq!(&buf[offset..], expected); + } +}