diff --git a/src/commands.rs b/src/commands.rs index 7c8e5c7..0295e77 100644 --- a/src/commands.rs +++ b/src/commands.rs @@ -1,24 +1,55 @@ +//! Parsed PIV commands. +//! +//! The types here should enforce all restrictions in the spec (such as padded_piv_pin.len() == 8), +//! but no implementation-specific ones (such as "GlobalPin not supported"). + use core::convert::{TryFrom, TryInto}; -use flexiber::Decodable; -use iso7816::{Command as IsoCommand, command::Data, Instruction, Status}; +// use flexiber::Decodable; +use iso7816::{Instruction, Status}; +use apdu_dispatch::{Command as IsoCommand, command::Data}; #[derive(Clone, Copy, Eq, PartialEq)] pub enum Command<'l> { + /// Select the application + /// + /// Resets security indicators if we are implicitly deselected. Select(Select<'l>), + /// Get a data object / container. GetData(GetData), + /// Check PIN + /// + /// This verifies that the sent PIN (global or PIV) is correct. + /// + /// In principle, other key references (biometric, pairing code) could + /// be verified, but this is not implemented. Verify(Verify), + /// Change PIN or PUK ChangeReference(ChangeReference), - ChangePin(ChangePin), + /// If the PIN is blocked, reset it using the PUK + ResetPinRetries(ResetPinRetries), + /// The most general purpose method, performing actual cryptographic operations + /// + /// In particular, this can also decrypt or similar. Authenticate(Authenticate), + /// Store a data object / container. PutData(PutData), GenerateAsymmetric(GenerateAsymmetric), } +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 { + command.try_into() + } +} + /// TODO: change into enum #[derive(Clone, Copy, Eq, PartialEq)] pub struct Select<'l> { - aid: &'l [u8], + pub aid: &'l [u8], } impl<'l> TryFrom<&'l Data> for Select<'l> { @@ -58,30 +89,32 @@ pub enum VerifyKeyReference { impl TryFrom for VerifyKeyReference { type Error = Status; fn try_from(p2: u8) -> Result { + // If the PIV Card Application does not contain the Discovery Object as described in Part 1, + // then no other key reference shall be able to be verified by the PIV Card Application VERIFY command. match p2 { 0x00 => Ok(Self::GlobalPin), + // 0x00 => Err(Status::FunctionNotSupported), 0x80 => Ok(Self::PivPin), - 0x96 => Err(Status::FunctionNotSupported), - 0x97 => Err(Status::FunctionNotSupported), - 0x98 => Err(Status::FunctionNotSupported), + 0x96 => Ok(Self::PrimaryFingerOcc), + 0x97 => Ok(Self::SecondaryFingerOcc), + 0x98 => Ok(Self::PairingCode), + // 0x96 => Err(Status::FunctionNotSupported), + // 0x97 => Err(Status::FunctionNotSupported), + // 0x98 => Err(Status::FunctionNotSupported), _ => Err(Status::KeyReferenceNotFound), } } } #[derive(Clone, Copy, Eq, PartialEq)] -#[repr(u8)] -pub enum VerifyParameter1 { - CheckOrVerify = 0x00, - Reset = 0xFF, -} +pub struct VerifyLogout(bool); -impl TryFrom for VerifyParameter1 { +impl TryFrom for VerifyLogout { type Error = Status; fn try_from(p1: u8) -> Result { match p1 { - 0x00 => Ok(VerifyParameter1::CheckOrVerify), - 0xFF => Ok(VerifyParameter1::Reset), + 0x00 => Ok(Self(false)), + 0xFF => Ok(Self(true)), _ => Err(Status::IncorrectP1OrP2Parameter), } } @@ -89,19 +122,39 @@ impl TryFrom for VerifyParameter1 { #[derive(Clone, Copy, Eq, PartialEq)] pub struct VerifyArguments<'l> { - key_reference: VerifyKeyReference, - parameter1: VerifyParameter1, - data: &'l Data + pub key_reference: VerifyKeyReference, + pub logout: VerifyLogout, + pub data: &'l Data +} + +#[derive(Clone, Copy, Eq, PartialEq)] +#[non_exhaustive] +pub enum VerifyLogin { + PivPin([u8; 8]), + GlobalPin([u8; 8]), } #[derive(Clone, Copy, Eq, PartialEq)] pub enum Verify { + Login(VerifyLogin), + Logout(VerifyKeyReference), + Status(VerifyKeyReference), } impl TryFrom> for Verify { type Error = Status; fn try_from(arguments: VerifyArguments<'_>) -> Result { - todo!(); + let VerifyArguments { key_reference, logout, data } = arguments; + if key_reference != VerifyKeyReference::PivPin { + return Err(Status::FunctionNotSupported); + } + 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, _) => return Err(Status::IncorrectDataParameter), + (true, 0) => Verify::Logout(key_reference), + (true, _) => return Err(Status::IncorrectDataParameter), + }) } } @@ -127,8 +180,8 @@ impl TryFrom for ChangeReferenceKeyReference { #[derive(Clone, Copy, Eq, PartialEq)] pub struct ChangeReferenceArguments<'l> { - key_reference: ChangeReferenceKeyReference, - data: &'l Data + pub key_reference: ChangeReferenceKeyReference, + pub data: &'l Data } #[derive(Clone, Copy, Eq, PartialEq)] @@ -143,12 +196,12 @@ impl TryFrom> for ChangeReference { } #[derive(Clone, Copy, Eq, PartialEq)] -pub struct ChangePin { - padded_pin: [u8; 8], - puk: [u8; 8], +pub struct ResetPinRetries { + pub padded_pin: [u8; 8], + pub puk: [u8; 8], } -impl TryFrom<&Data> for ChangePin { +impl TryFrom<&Data> for ResetPinRetries { type Error = Status; fn try_from(data: &Data) -> Result { if data.len() != 16 { @@ -229,9 +282,11 @@ impl TryFrom for AuthenticateKeyReference { #[derive(Clone, Copy, Eq, PartialEq)] pub struct AuthenticateArguments<'l> { - unparsed_algorithm: u8, - key_reference: AuthenticateKeyReference, - data: &'l Data + /// To allow the authenticator to have additional algorithms beyond NIST SP 800-78-4, + /// this is passed through as-is. + pub unparsed_algorithm: u8, + pub key_reference: AuthenticateKeyReference, + pub data: &'l Data } #[derive(Clone, Copy, Eq, PartialEq)] @@ -282,8 +337,8 @@ impl TryFrom for GenerateAsymmetricKeyReference { #[derive(Clone, Copy, Eq, PartialEq)] pub struct GenerateAsymmetricArguments<'l> { - key_reference: GenerateAsymmetricKeyReference, - data: &'l Data + pub key_reference: GenerateAsymmetricKeyReference, + pub data: &'l Data } #[derive(Clone, Copy, Eq, PartialEq)] @@ -330,9 +385,9 @@ impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { } (0x00, Instruction::Verify, p1, p2) => { - let parameter1 = VerifyParameter1::try_from(p1)?; + let logout = VerifyLogout::try_from(p1)?; let key_reference = VerifyKeyReference::try_from(p2)?; - Self::Verify(Verify::try_from(VerifyArguments { key_reference, parameter1, data })?) + Self::Verify(Verify::try_from(VerifyArguments { key_reference, logout, data })?) } (0x00, Instruction::ChangeReferenceData, 0x00, p2) => { @@ -341,7 +396,7 @@ impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { } (0x00, Instruction::ResetRetryCounter, 0x00, 0x80) => { - Self::ChangePin(ChangePin::try_from(data)?) + Self::ResetPinRetries(ResetPinRetries::try_from(data)?) } (0x00, Instruction::GeneralAuthenticate, p1, p2) => { @@ -359,7 +414,7 @@ impl<'l> TryFrom<&'l IsoCommand> for Command<'l> { Self::GenerateAsymmetric(GenerateAsymmetric::try_from(GenerateAsymmetricArguments { key_reference, data })?) } - _ => todo!(), + _ => return Err(Status::FunctionNotSupported), }) } } diff --git a/src/error.rs b/src/error.rs index 13f5a4b..4131ea0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,24 @@ +pub enum Error { + VerificationFailed { remaining: u8 }, // 63 00 or 63 CX + SecureMessagingNotSupported, // 68 82 + SecurityStatusNotSatisfied, // 69 82 + AuthenticationMethodBlocked, // 69 83 + // ExpectedSecureMessagingDataObjectsMissing, // 69 87 + // SecureMessagingDataObjectsIncorrect, // 69 88 + IncorrectParameterInCommandDataField, // 6A 80 + FunctionNotSupported, // 6A 81 + DataObjectOrApplicationNotFound, // 6A 82 + NotEnoughMemory, // 6A 84 + IncorrecParameterInP1OrP2, // 6A 86 + ReferencedDataOrReferenceDataNotFound, // 6A 88 +} + +pub enum Success { + Success, // 61 xx + SuccessResponseDataStillAvailable(u8), // 90 00 +} + +pub type Result = core::result::Result; // macro_rules! status_word { // ($($Name:ident: [$sw1:expr, $sw2:tt],)*) => { @@ -69,6 +90,7 @@ // pub trait StatusWordTrait { // fn sw1(&self) -> u8; // fn sw2(&self) -> u8; +// // fn sw(&self) -> [u8; 2] { // [self.sw1(), self.sw2()] // } diff --git a/src/lib.rs b/src/lib.rs index d90928f..cefe662 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ generate_macros!(); extern crate hex_literal; pub mod commands; +// pub use commands::Command; pub mod constants; pub mod state; pub mod derp; @@ -19,7 +20,7 @@ use flexiber::EncodableHeapless; use iso7816::{ Instruction, Status, }; -use apdu_dispatch::{Command, response}; +use apdu_dispatch::{Command as Command, response}; use trussed::client; use trussed::{syscall, try_syscall}; @@ -76,6 +77,98 @@ where Ok(()) } + // pub fn old_respond(&mut self, command: &Command, reply: &mut response::Data) -> Result { + // let last_or_only = command.class().chain().last_or_only(); + + // // TODO: avoid owned copy? + // let entire_command = match self.state.runtime.chained_command.as_mut() { + // Some(command_so_far) => { + // // TODO: make sure the header matches, e.g. '00 DB 3F FF' + // command_so_far.data_mut().extend_from_slice(command.data()).unwrap(); + + // if last_or_only { + // let entire_command = command_so_far.clone(); + // self.state.runtime.chained_command = None; + // entire_command + // } else { + // return Ok(Default::default()); + // } + // } + + // None => { + // if last_or_only { + // // IsoCommand + // command.clone() + // } else { + // self.state.runtime.chained_command = Some(command.clone()); + // return Ok(Default::default()); + // } + // } + // }; + + // // parse Iso7816Command as PivCommand + // let command: Command = (&entire_command).try_into()?; + + // match command { + // Command::Verify(verify) => self.verify(verify), + // _ => todo!(), + // } + // } + + // maybe reserve this for the case VerifyLogin::PivPin? + pub fn login(&mut self, login: commands::VerifyLogin) -> Result { + if let commands::VerifyLogin::PivPin(padded_pin) = login { + // TODO: improve this type + let sent_pin = state::Pin::try_new(&padded_pin) + .map_err(|_| Status::IncorrectDataParameter)?; + + // the actual PIN verification + let persistent_state = self.state.persistent(&mut self.trussed); + + if persistent_state.remaining_pin_retries() == 0 { + return Err(Status::OperationBlocked); + } + + if persistent_state.verify_pin(&sent_pin) { + persistent_state.reset_consecutive_pin_mismatches(&mut self.trussed); + self.state.runtime.app_security_status.pin_verified = true; + Ok(()) + + } else { + let remaining = persistent_state.increment_consecutive_pin_mismatches(&mut self.trussed); + // should we logout here? + self.state.runtime.app_security_status.pin_verified = false; + Err(Status::RemainingRetries(remaining)) + } + } else { + Err(Status::FunctionNotSupported) + } + } + + pub fn verify(&mut self, command: commands::Verify) -> Result { + use commands::Verify; + match command { + Verify::Login(login) => self.login(login), + + Verify::Logout(_) => { + self.state.runtime.app_security_status.pin_verified = false; + Ok(()) + } + + Verify::Status(key_reference) => { + if key_reference != commands::VerifyKeyReference::PivPin { + return Err(Status::FunctionNotSupported); + } + if self.state.runtime.app_security_status.pin_verified { + return Ok(()) + } else { + let retries = self.state.persistent(&mut self.trussed).remaining_pin_retries(); + return Err(Status::RemainingRetries(retries)); + } + } + } + } + pub fn respond(&mut self, command: &Command, reply: &mut response::Data) -> Result { // TEMP @@ -87,6 +180,36 @@ where // - only channel zero supported // - ensure INS known to us + let last_or_only = command.class().chain().last_or_only(); + + // TODO: avoid owned copy? + let owned_command = match self.state.runtime.chained_command.as_mut() { + Some(command_so_far) => { + // TODO: make sure the prefix matches, e.g. '00 DB 3F FF' + command_so_far.data_mut().extend_from_slice(command.data()).unwrap(); + + if last_or_only { + let total_command = command_so_far.clone(); + self.state.runtime.chained_command = None; + total_command + } else { + return Ok(Default::default()); + } + } + + None => { + if last_or_only { + // IsoCommand + command.clone() + } else { + self.state.runtime.chained_command = Some(command.clone()); + return Ok(Default::default()); + } + } + }; + + let command = &owned_command; + let class = command.class(); if !class.secure_messaging().none() { @@ -108,7 +231,7 @@ where match command.instruction() { Instruction::GetData => self.get_data(command, reply), Instruction::PutData => self.put_data(command), - Instruction::Verify => self.verify(command), + Instruction::Verify => self.old_verify(command), Instruction::ChangeReferenceData => self.change_reference_data(command), Instruction::GeneralAuthenticate => self.general_authenticate(command, reply), Instruction::GenerateAsymmetricKeyPair => self.generate_asymmetric_keypair(command, reply), @@ -430,7 +553,7 @@ where Err(Status::KeyReferenceNotFound) } - fn verify(&mut self, command: &Command) -> Result { + fn old_verify(&mut self, command: &Command) -> Result { // we only implement our own PIN, not global Pin, not OCC data, not pairing code if command.p2 != 0x80 { return Err(Status::KeyReferenceNotFound); diff --git a/src/state.rs b/src/state.rs index ea4f7af..278c18e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -179,7 +179,7 @@ impl State { #[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct Pin { // padded_pin: [u8; 8] - padded_pin: heapless_bytes::Bytes, + pin: heapless_bytes::Bytes, } // impl Default for Pin { @@ -208,7 +208,7 @@ impl Pin { if valid_bytes { Ok(Self { // padded_pin: padded_pin.try_into().unwrap(), - padded_pin: Bytes::try_from_slice(padded_pin).unwrap(),//padded_pin.try_into().unwrap(), + pin: Bytes::try_from_slice(padded_pin).unwrap(),//padded_pin.try_into().unwrap(), }) } else { Err(()) @@ -241,6 +241,7 @@ pub struct Runtime { pub currently_selected_application: SelectableAid, pub app_security_status: AppSecurityStatus, pub command_cache: Option, + pub chained_command: Option, } pub trait Aid { @@ -300,6 +301,19 @@ impl Aid for YubicoOtpAid { pub struct GlobalSecurityStatus { } +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum SecurityStatus { + JustVerified, + Verified, + NotVerified, +} + +impl Default for SecurityStatus { + fn default() -> Self { + Self::NotVerified + } +} + #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AppSecurityStatus { pub pin_verified: bool,