diff --git a/Cargo.toml b/Cargo.toml index da9cf4d..f8215db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ littlefs2 = "0.2.1" rand_core = { version = "0.5.1", features = ["getrandom"] } [features] -default = ["applet"] +default = [] applet = ["apdu-dispatch"] strict-pin = [] diff --git a/src/commands.rs b/src/commands.rs index e8290b5..6c72c4a 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -6,8 +6,8 @@ use core::convert::{TryFrom, TryInto}; // use flexiber::Decodable; +use heapless::ArrayLength; use iso7816::{Instruction, Status}; -use apdu_dispatch::{Command as IsoCommand, command::Data}; pub use crate::{container as containers, piv_types, Pin, Puk}; @@ -43,7 +43,7 @@ impl<'l> Command<'l> { /// Core method, constructs a PIV command, if the iso7816::Command is valid. /// /// Inherent method re-exposing the `TryFrom` implementation. - pub fn try_from(command: &'l IsoCommand) -> Result { + pub fn try_from>(command: &'l iso7816::Command) -> Result { command.try_into() } } @@ -54,12 +54,12 @@ pub struct Select<'l> { pub aid: &'l [u8], } -impl<'l> TryFrom<&'l Data> for Select<'l> { +impl<'l> TryFrom<&'l [u8]> for Select<'l> { type Error = Status; /// We allow ourselves the option of answering to more than just the official PIV AID. /// For instance, to offer additional functionality, under our own RID. - fn try_from(data: &'l Data) -> Result { - Ok(match data.as_slice() { + fn try_from(data: &'l [u8]) -> Result { + Ok(match data { crate::constants::PIV_AID => Self { aid: data }, _ => return Err(Status::NotFound), }) @@ -70,9 +70,9 @@ impl<'l> TryFrom<&'l Data> for Select<'l> { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct GetData(containers::Container); -impl TryFrom<&Data> for GetData { +impl TryFrom<&[u8]> for GetData { type Error = Status; - fn try_from(data: &Data) -> Result { + fn try_from(data: &[u8]) -> Result { let mut decoder = flexiber::Decoder::new(data); let tagged_slice: flexiber::TaggedSlice = decoder.decode().map_err(|_| Status::IncorrectDataParameter)?; if tagged_slice.tag() != flexiber::Tag::application(0x1C) { @@ -135,7 +135,7 @@ impl TryFrom for VerifyLogout { pub struct VerifyArguments<'l> { pub key_reference: VerifyKeyReference, pub logout: VerifyLogout, - pub data: &'l Data + pub data: &'l [u8], } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -161,7 +161,7 @@ impl TryFrom> for Verify { } Ok(match (logout.0, data.len()) { (false, 0) => Verify::Status(key_reference), - (false, 8) => Verify::Login(VerifyLogin::PivPin(data.as_slice().try_into().map_err(|_| Status::IncorrectDataParameter)?)), + (false, 8) => Verify::Login(VerifyLogin::PivPin(data.try_into().map_err(|_| Status::IncorrectDataParameter)?)), (false, _) => return Err(Status::IncorrectDataParameter), (true, 0) => Verify::Logout(key_reference), (true, _) => return Err(Status::IncorrectDataParameter), @@ -192,7 +192,7 @@ impl TryFrom for ChangeReferenceKeyReference { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct ChangeReferenceArguments<'l> { pub key_reference: ChangeReferenceKeyReference, - pub data: &'l Data + pub data: &'l [u8], } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -232,9 +232,9 @@ pub struct ResetPinRetries { pub puk: [u8; 8], } -impl TryFrom<&Data> for ResetPinRetries { +impl TryFrom<&[u8]> for ResetPinRetries { type Error = Status; - fn try_from(data: &Data) -> Result { + fn try_from(data: &[u8]) -> Result { if data.len() != 16 { return Err(Status::IncorrectDataParameter); } @@ -317,7 +317,7 @@ pub struct AuthenticateArguments<'l> { /// this is passed through as-is. pub unparsed_algorithm: u8, pub key_reference: AuthenticateKeyReference, - pub data: &'l Data + pub data: &'l [u8], } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -326,7 +326,7 @@ pub enum Authenticate { impl TryFrom> for Authenticate { type Error = Status; - fn try_from(arguments: AuthenticateArguments<'_>) -> Result { + fn try_from(_arguments: AuthenticateArguments<'_>) -> Result { todo!(); } } @@ -335,9 +335,9 @@ impl TryFrom> for Authenticate { pub struct PutData { } -impl TryFrom<&Data> for PutData { +impl TryFrom<&[u8]> for PutData { type Error = Status; - fn try_from(data: &Data) -> Result { + fn try_from(_data: &[u8]) -> Result { todo!(); } } @@ -369,7 +369,7 @@ impl TryFrom for GenerateAsymmetricKeyReference { #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct GenerateAsymmetricArguments<'l> { pub key_reference: GenerateAsymmetricKeyReference, - pub data: &'l Data + pub data: &'l [u8], } #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -378,12 +378,12 @@ pub enum GenerateAsymmetric { impl TryFrom> for GenerateAsymmetric { type Error = Status; - fn try_from(arguments: GenerateAsymmetricArguments<'_>) -> Result { + fn try_from(_arguments: GenerateAsymmetricArguments<'_>) -> Result { todo!(); } } -impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { +impl<'l, C: ArrayLength> TryFrom<&'l iso7816::Command> for Command<'l> { type Error = Status; /// The first layer of unraveling the iso7816::Command onion. /// @@ -391,7 +391,7 @@ impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { /// in the "Command Syntax" boxes of NIST SP 800-73-4, and return early errors. /// /// The individual piv::Command TryFroms then further interpret these validated parameters. - fn try_from(command: &'l IsoCommand) -> Result { + fn try_from(command: &'l iso7816::Command) -> Result { let (class, instruction, p1, p2) = (command.class(), command.instruction(), command.p1, command.p2); let data = command.data(); @@ -408,11 +408,11 @@ impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { Ok(match (class.into_inner(), instruction, p1, p2) { (0x00, Instruction::Select, 0x04, 0x00) => { - Self::Select(Select::try_from(data)?) + Self::Select(Select::try_from(data.as_slice())?) } (0x00, Instruction::GetData, 0x3F, 0xFF) => { - Self::GetData(GetData::try_from(data)?.0) + Self::GetData(GetData::try_from(data.as_slice())?.0) } (0x00, Instruction::Verify, p1, p2) => { @@ -427,7 +427,7 @@ impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { } (0x00, Instruction::ResetRetryCounter, 0x00, 0x80) => { - Self::ResetPinRetries(ResetPinRetries::try_from(data)?) + Self::ResetPinRetries(ResetPinRetries::try_from(data.as_slice())?) } (0x00, Instruction::GeneralAuthenticate, p1, p2) => { @@ -437,7 +437,7 @@ impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { } (0x00, Instruction::PutData, 0x3F, 0xFF) => { - Self::PutData(PutData::try_from(data)?) + Self::PutData(PutData::try_from(data.as_slice())?) } (0x00, Instruction::GenerateAsymmetricKeyPair, 0x00, p2) => { diff --git a/src/lib.rs b/src/lib.rs index 9bb7780..6a24781 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,8 +19,8 @@ pub use piv_types::{Pin, Puk}; use core::convert::TryInto; use flexiber::EncodableHeapless; -use iso7816::Status; -use apdu_dispatch::{Command as IsoCommand, response}; +use heapless::ArrayLength; +use iso7816::{Data, Status}; use trussed::client; use trussed::{syscall, try_syscall}; @@ -28,15 +28,23 @@ use constants::*; pub type Result = iso7816::Result<()>; -pub struct Authenticator +/// 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 +where + C: ArrayLength, { - state: state::State, + state: state::State, trussed: T, // trussed: RefCell, } -impl Authenticator +impl Authenticator where + C: ArrayLength, T: client::Client + client::Ed255 + client::Tdes, { pub fn new( @@ -59,7 +67,11 @@ where pub fn deselect(&mut self) { } - pub fn select(&mut self, _apdu: &IsoCommand, reply: &mut response::Data) -> Result { + pub fn select(&mut self, _apdu: &iso7816::Command, reply: &mut Data) -> Result + where + R: ArrayLength, + + { use piv_types::Algorithms::*; info_now!("selecting PIV maybe"); @@ -79,8 +91,12 @@ where Ok(()) } - pub fn respond(&mut self, command: &IsoCommand, reply: &mut response::Data) -> Result { - info_now!("PIV responding to {:?}", command); + pub fn respond(&mut self, command: &iso7816::Command, reply: &mut Data) -> Result + where + R: ArrayLength, + { + // need to implement Debug on iso7816::Command + // info_now!("PIV responding to {:?}", command); let last_or_only = command.class().chain().last_or_only(); // TODO: avoid owned copy? @@ -100,7 +116,7 @@ where None => { if last_or_only { - // IsoCommand + // iso7816::Command command.clone() } else { self.state.runtime.chained_command = Some(command.clone()); @@ -216,7 +232,7 @@ where return Ok(Default::default()); } - // pub fn old_respond(&mut self, command: &IsoCommand, reply: &mut response::Data) -> Result { + // pub fn old_respond(&mut self, command: &iso7816::Command, reply: &mut Data) -> Result { // // TEMP // // blocking::dbg!(self.state.persistent(&mut self.trussed).timestamp(&mut self.trussed)); @@ -246,7 +262,7 @@ where // None => { // if last_or_only { - // // IsoCommand + // // iso7816::Command // command.clone() // } else { // self.state.runtime.chained_command = Some(command.clone()); @@ -325,7 +341,10 @@ where // - 9000, 61XX for success // - 6982 security status // - 6A80, 6A86 for data, P1/P2 issue - fn general_authenticate(&mut self, command: &IsoCommand, reply: &mut response::Data) -> Result { + fn general_authenticate(&mut self, command: &iso7816::Command, reply: &mut Data) -> Result + where + R: ArrayLength, + { // For "SSH", we need implement A.4.2 in SP-800-73-4 Part 2, ECDSA signatures // @@ -435,7 +454,10 @@ where Ok(()) } - fn request_for_challenge(&mut self, command: &IsoCommand, remaining_data: &[u8], reply: &mut response::Data) -> Result { + fn request_for_challenge(&mut self, command: &iso7816::Command, remaining_data: &[u8], reply: &mut Data) -> Result + where + R: ArrayLength, + { // - data is of the form // 00 87 03 9B 16 7C 14 80 08 99 6D 71 40 E7 05 DF 7F 81 08 6E EF 9C 02 00 69 73 E8 // - remaining data contains 81 08 @@ -486,7 +508,10 @@ where Ok(()) } - fn request_for_witness(&mut self, command: &IsoCommand, remaining_data: &[u8], reply: &mut response::Data) -> Result { + fn request_for_witness(&mut self, command: &iso7816::Command, remaining_data: &[u8], reply: &mut Data) -> Result + where + R: ArrayLength, + { // invariants: parsed data was '7C L1 80 00' + remaining_data if command.p1 != 0x03 || command.p2 != 0x9b { @@ -661,7 +686,10 @@ where // } //} - fn generate_asymmetric_keypair(&mut self, command: &IsoCommand, reply: &mut response::Data) -> Result { + fn generate_asymmetric_keypair(&mut self, command: &iso7816::Command, reply: &mut Data) -> Result + where + R: ArrayLength, + { if !self.state.runtime.app_security_status.management_verified { return Err(Status::SecurityStatusNotSatisfied); } @@ -784,7 +812,7 @@ where Ok(()) } - fn put_data(&mut self, command: &IsoCommand) -> Result { + fn put_data(&mut self, command: &iso7816::Command) -> Result { info_now!("PutData"); if command.p1 != 0x3f || command.p2 != 0xff { return Err(Status::IncorrectP1OrP2Parameter); @@ -878,7 +906,10 @@ where // } // todo!(); - fn get_data(&mut self, container: container::Container, reply: &mut response::Data) -> Result { + fn get_data(&mut self, container: container::Container, reply: &mut Data) -> Result + where + R: ArrayLength, + { // TODO: check security status, else return Status::SecurityStatusNotSatisfied @@ -888,8 +919,7 @@ where match container { Container::DiscoveryObject => { // Err(Status::InstructionNotSupportedOrInvalid) - let data = response::Data::try_from_slice(DISCOVERY_OBJECT).unwrap(); - reply.extend_from_slice(&data).ok(); + reply.extend_from_slice(DISCOVERY_OBJECT).ok(); // todo!("discovery object"), } @@ -934,7 +964,7 @@ where // // '5F FF01' (754B) // YubicoObjects::AttestationCertificate => { - // let data = response::Data::try_from_slice(YUBICO_ATTESTATION_CERTIFICATE).unwrap(); + // let data = Data::try_from_slice(YUBICO_ATTESTATION_CERTIFICATE).unwrap(); // reply.extend_from_slice(&data).ok(); // } @@ -943,21 +973,22 @@ where Ok(()) } - fn yubico_piv_extension(&mut self, command: &IsoCommand, instruction: YubicoPivExtension, reply: &mut response::Data) -> Result { + fn yubico_piv_extension(&mut self, command: &iso7816::Command, instruction: YubicoPivExtension, reply: &mut Data) -> Result + where + R: ArrayLength, + { info_now!("yubico extension: {:?}", &instruction); match instruction { YubicoPivExtension::GetSerial => { // make up a 4-byte serial - let data = response::Data::try_from_slice( - &[0x00, 0x52, 0xf7, 0x43]).unwrap(); - reply.extend_from_slice(&data).ok(); + reply.extend_from_slice( + &[0x00, 0x52, 0xf7, 0x43]).ok(); } YubicoPivExtension::GetVersion => { // make up a version, be >= 5.0.0 - let data = response::Data::try_from_slice( - &[0x06, 0x06, 0x06]).unwrap(); - reply.extend_from_slice(&data).ok(); + reply.extend_from_slice( + &[0x06, 0x06, 0x06]).ok(); } YubicoPivExtension::Attest => { @@ -968,8 +999,7 @@ where let slot = command.p1; if slot == 0x9a { - let data = response::Data::try_from_slice(YUBICO_ATTESTATION_CERTIFICATE_FOR_9A).unwrap(); - reply.extend_from_slice(&data).ok(); + reply.extend_from_slice(YUBICO_ATTESTATION_CERTIFICATE_FOR_9A).ok(); } else { return Err(Status::FunctionNotSupported) @@ -1040,7 +1070,7 @@ where #[cfg(feature = "apdu-dispatch")] -impl apdu_dispatch::app::Aid for Authenticator { +impl apdu_dispatch::app::Aid for Authenticator { fn aid(&self) -> &'static [u8] { &constants::PIV_AID @@ -1053,17 +1083,17 @@ impl apdu_dispatch::app::Aid for Authenticator { #[cfg(feature = "apdu-dispatch")] -impl apdu_dispatch::app::App for Authenticator +impl apdu_dispatch::app::App for Authenticator where T: client::Client + client::Ed255 + client::Tdes { - fn select(&mut self, apdu: &IsoCommand, reply: &mut response::Data) -> Result { + fn select(&mut self, apdu: &apdu_dispatch::Command, reply: &mut apdu_dispatch::response::Data) -> Result { self.select(apdu, reply) } fn deselect(&mut self) { self.deselect() } - fn call(&mut self, _: iso7816::Interface, apdu: &IsoCommand, reply: &mut response::Data) -> Result { + fn call(&mut self, _: iso7816::Interface, apdu: &apdu_dispatch::Command, reply: &mut apdu_dispatch::response::Data) -> Result { self.respond(apdu, reply) } } diff --git a/src/state.rs b/src/state.rs index b793dd9..5e4762b 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,4 +1,5 @@ use core::convert::{TryFrom, TryInto}; +use heapless::ArrayLength; use trussed::{ block, @@ -139,14 +140,14 @@ pub struct Keys { #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct State { - pub runtime: Runtime, +pub struct State> { + pub runtime: Runtime, // temporary "state", to be removed again // pub hack: Hack, // trussed: RefCell>, } -impl State { +impl> State { pub fn new() -> Self { Default::default() } @@ -234,7 +235,7 @@ impl AsRef for Persistent<'_, T> { } #[derive(Clone, Debug, Default, Eq, PartialEq)] -pub struct Runtime { +pub struct Runtime> { // aid: Option< // consecutive_pin_mismatches: u8, @@ -242,7 +243,7 @@ pub struct Runtime { pub currently_selected_application: SelectableAid, pub app_security_status: AppSecurityStatus, pub command_cache: Option, - pub chained_command: Option, + pub chained_command: Option>, } pub trait Aid { @@ -551,6 +552,3 @@ where } -impl Runtime { -} -