diff --git a/src/dispatch.rs b/src/dispatch.rs index d1d8142..ad33cb7 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -5,11 +5,12 @@ 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 + client::Ed255 + client::Tdes, + T: client::Client + AuthClient + client::Ed255 + client::Tdes, { 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 7f6bec4..8bf1d52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,7 @@ use heapless_bytes::Bytes; use iso7816::{Data, Status}; use trussed::types::{KeySerialization, Location, PathBuf, StorageAttributes}; use trussed::{client, syscall, try_syscall}; +use trussed_auth::AuthClient; use constants::*; @@ -62,7 +63,7 @@ impl Default for Options { impl Options { pub fn storage(self, storage: Location) -> Self { - Self { storage, ..self } + Self { storage } } } @@ -91,7 +92,7 @@ impl iso7816::App for Authenticator { impl Authenticator where - T: client::Client + client::Ed255 + client::Tdes, + T: client::Client + AuthClient + client::Ed255 + client::Tdes, { pub fn new(trussed: T, options: Options) -> Self { // seems like RefCell is not the right thing, we want something like `Rc` instead, @@ -216,7 +217,7 @@ where } } -impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> { +impl<'a, T: trussed::Client + AuthClient + trussed::client::Ed255> LoadedAuthenticator<'a, T> { pub fn yubico_set_administration_key( &mut self, data: &[u8], @@ -280,24 +281,13 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> // maybe reserve this for the case VerifyLogin::PivPin? pub fn login(&mut self, login: commands::VerifyLogin) -> Result { if let commands::VerifyLogin::PivPin(pin) = login { - // the actual PIN verification - if self.state.persistent.remaining_pin_retries() == 0 { - return Err(Status::OperationBlocked); - } - if self.state.persistent.verify_pin(&pin, self.trussed) { - self.state - .persistent - .reset_consecutive_pin_mismatches(self.trussed); self.state.volatile.app_security_status.pin_verified = true; Ok(()) } else { - let remaining = self - .state - .persistent - .increment_consecutive_pin_mismatches(self.trussed); // should we logout here? self.state.volatile.app_security_status.pin_verified = false; + let remaining = self.state.persistent.remaining_pin_retries(self.trussed); Err(Status::RemainingRetries(remaining)) } } else { @@ -322,7 +312,7 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> if self.state.volatile.app_security_status.pin_verified { Ok(()) } else { - let retries = self.state.persistent.remaining_pin_retries(); + let retries = self.state.persistent.remaining_pin_retries(self.trussed); Err(Status::RemainingRetries(retries)) } } @@ -338,45 +328,25 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> } pub fn change_pin(&mut self, old_pin: commands::Pin, new_pin: commands::Pin) -> Result { - if self.state.persistent.remaining_pin_retries() == 0 { - return Err(Status::OperationBlocked); - } - - if !self.state.persistent.verify_pin(&old_pin, self.trussed) { - let remaining = self - .state - .persistent - .increment_consecutive_pin_mismatches(self.trussed); - self.state.volatile.app_security_status.pin_verified = false; - return Err(Status::RemainingRetries(remaining)); - } - - self.state + if !self + .state .persistent - .reset_consecutive_pin_mismatches(self.trussed); - self.state.persistent.set_pin(new_pin, self.trussed); + .change_pin(&old_pin, &new_pin, self.trussed) + { + return Err(Status::VerificationFailed); + } self.state.volatile.app_security_status.pin_verified = true; Ok(()) } pub fn change_puk(&mut self, old_puk: commands::Puk, new_puk: commands::Puk) -> Result { - if self.state.persistent.remaining_puk_retries() == 0 { - return Err(Status::OperationBlocked); - } - - if !self.state.persistent.verify_puk(&old_puk, self.trussed) { - let remaining = self - .state - .persistent - .increment_consecutive_puk_mismatches(self.trussed); - self.state.volatile.app_security_status.puk_verified = false; - return Err(Status::RemainingRetries(remaining)); - } - - self.state + if !self + .state .persistent - .reset_consecutive_puk_mismatches(self.trussed); - self.state.persistent.set_puk(new_puk, self.trussed); + .change_puk(&old_puk, &new_puk, self.trussed) + { + return Err(Status::VerificationFailed); + } self.state.volatile.app_security_status.puk_verified = true; Ok(()) } @@ -979,7 +949,9 @@ impl<'a, T: trussed::Client + trussed::client::Ed255> LoadedAuthenticator<'a, T> { return Err(Status::VerificationFailed); } - self.state.persistent.set_pin(Pin(data.pin), self.trussed); + self.state + .persistent + .reset_pin(Pin(data.pin), self.trussed)?; Ok(()) } diff --git a/src/state.rs b/src/state.rs index 507829e..93c145e 100644 --- a/src/state.rs +++ b/src/state.rs @@ -14,6 +14,7 @@ use trussed::{ syscall, try_syscall, types::{KeyId, KeySerialization, Location, Mechanism, PathBuf, StorageAttributes}, }; +use trussed_auth::AuthClient; use crate::piv_types::CardHolderUniqueIdentifier; use crate::{constants::*, piv_types::AsymmetricAlgorithms}; @@ -188,9 +189,9 @@ pub struct State { } impl State { - pub fn load( + pub fn load( &mut self, - client: &mut impl trussed::Client, + client: &mut T, storage: Location, ) -> Result, Status> { if self.persistent.is_none() { @@ -202,9 +203,9 @@ impl State { }) } - pub fn persistent( + pub fn persistent( &mut self, - client: &mut impl trussed::Client, + client: &mut T, storage: Location, ) -> Result<&mut Persistent, Status> { Ok(self.load(client, storage)?.persistent) @@ -226,18 +227,23 @@ fn volatile() -> Location { Location::Volatile } +enum PinType { + Puk, + UserPin, +} + +impl From for trussed_auth::PinId { + fn from(value: PinType) -> Self { + match value { + PinType::UserPin => 0.into(), + PinType::Puk => 1.into(), + } + } +} + #[derive(Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] pub struct Persistent { pub keys: Keys, - consecutive_pin_mismatches: u8, - consecutive_puk_mismatches: u8, - // the PIN can be 6-8 digits, padded with 0xFF if <8 - // we just store all of them for now. - pin: Pin, - // the PUK should be 8 digits, but it seems Yubico allows 6-8 - // like for PIN - puk: Puk, - // pin_hash: Option<[u8; 16]>, // Ideally, we'd dogfood a "Monotonic Counter" from `trussed`. timestamp: u32, #[serde(skip, default = "volatile")] @@ -322,119 +328,114 @@ impl Persistent { // hmm...! pub const PUK_RETRIES_DEFAULT: u8 = 5; const FILENAME: &'static [u8] = b"persistent-state.cbor"; - const DEFAULT_PIN: &'static [u8] = b"123456\xff\xff"; - const DEFAULT_PUK: &'static [u8] = b"12345678"; + const DEFAULT_PIN: Pin = Pin(*b"123456\xff\xff"); + const DEFAULT_PUK: Puk = Puk(*b"12345678"); - pub fn remaining_pin_retries(&self) -> u8 { - if self.consecutive_pin_mismatches >= Self::PIN_RETRIES_DEFAULT { - 0 - } else { - Self::PIN_RETRIES_DEFAULT - self.consecutive_pin_mismatches - } + 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) -> u8 { - if self.consecutive_puk_mismatches >= Self::PUK_RETRIES_DEFAULT { - 0 - } else { - Self::PUK_RETRIES_DEFAULT - self.consecutive_puk_mismatches - } + 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) } - // FIXME: revisit with trussed pin management - pub fn verify_pin(&mut self, other_pin: &Pin, client: &mut impl trussed::Client) -> bool { - if self.remaining_pin_retries() == 0 { - return false; - } - self.consecutive_pin_mismatches += 1; - self.save(client); - if self.pin == *other_pin { - self.consecutive_pin_mismatches = 0; - true - } else { - false - } - } - - // FIXME: revisit with trussed pin management - pub fn verify_puk(&mut self, other_puk: &Puk, client: &mut impl trussed::Client) -> bool { - if self.remaining_puk_retries() == 0 { - return false; - } - self.consecutive_puk_mismatches += 1; - self.save(client); - if self.puk == *other_puk { - self.consecutive_puk_mismatches = 0; - true - } else { - false - } - } - - pub fn set_pin(&mut self, new_pin: Pin, client: &mut impl trussed::Client) { - self.pin = new_pin; - self.consecutive_pin_mismatches = 0; - self.save(client); - } - - pub fn set_puk(&mut self, new_puk: Puk, client: &mut impl trussed::Client) { - self.puk = new_puk; - self.consecutive_puk_mismatches = 0; - self.save(client); - } - - pub fn reset_pin(&mut self, client: &mut impl trussed::Client) { - self.set_pin(Pin::try_from(Self::DEFAULT_PIN).unwrap(), client); - self.reset_consecutive_pin_mismatches(client); - } - - pub fn reset_puk(&mut self, client: &mut impl trussed::Client) { - self.set_puk(Puk::try_from(Self::DEFAULT_PUK).unwrap(), client); - self.reset_consecutive_puk_mismatches(client); - } - - pub fn increment_consecutive_pin_mismatches( + pub fn verify_pin( &mut self, - client: &mut impl trussed::Client, - ) -> u8 { - if self.consecutive_pin_mismatches >= Self::PIN_RETRIES_DEFAULT { - return 0; - } - - self.consecutive_pin_mismatches += 1; - self.save(client); - Self::PIN_RETRIES_DEFAULT - self.consecutive_pin_mismatches + 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 increment_consecutive_puk_mismatches( + pub fn verify_puk( &mut self, - client: &mut impl trussed::Client, - ) -> u8 { - if self.consecutive_puk_mismatches >= Self::PUK_RETRIES_DEFAULT { - return 0; - } - - self.consecutive_puk_mismatches += 1; - self.save(client); - Self::PUK_RETRIES_DEFAULT - self.consecutive_puk_mismatches + 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 reset_consecutive_pin_mismatches(&mut self, client: &mut impl trussed::Client) -> u8 { - if self.consecutive_pin_mismatches != 0 { - self.consecutive_pin_mismatches = 0; - self.save(client); - } - - Self::PIN_RETRIES_DEFAULT + pub fn change_pin( + &mut self, + old_value: &Pin, + new_value: &Pin, + client: &mut T, + ) -> bool { + let old_pin = Bytes::from_slice(&old_value.0).expect("Convertion of static array"); + let new_pin = Bytes::from_slice(&new_value.0).expect("Convertion of static array"); + try_syscall!(client.change_pin(PinType::UserPin, old_pin, new_pin)) + .map(|r| r.success) + .unwrap_or(false) } - pub fn reset_consecutive_puk_mismatches(&mut self, client: &mut impl trussed::Client) -> u8 { - if self.consecutive_puk_mismatches != 0 { - self.consecutive_puk_mismatches = 0; - self.save(client); - } + pub fn change_puk( + &mut self, + old_value: &Puk, + new_value: &Puk, + client: &mut T, + ) -> bool { + let old_puk = Bytes::from_slice(&old_value.0).expect("Convertion of static array"); + let new_puk = Bytes::from_slice(&new_value.0).expect("Convertion of static array"); + try_syscall!(client.change_pin(PinType::UserPin, old_puk, new_puk)) + .map(|r| r.success) + .unwrap_or(false) + } - Self::PUK_RETRIES_DEFAULT + pub fn set_pin( + &mut self, + new_pin: Pin, + client: &mut T, + ) -> Result<(), Status> { + let new_pin = Bytes::from_slice(&new_pin.0).expect("Convertion of static array"); + try_syscall!(client.set_pin( + PinType::UserPin, + new_pin, + Some(Self::PIN_RETRIES_DEFAULT), + true + )) + .map_err(|_err| { + error!("Failed to set pin"); + Status::UnspecifiedPersistentExecutionError + }) + .map(drop) + } + + pub fn set_puk( + &mut self, + new_puk: Puk, + client: &mut T, + ) -> Result<(), Status> { + let new_puk = Bytes::from_slice(&new_puk.0).expect("Convertion of static array"); + try_syscall!(client.set_pin(PinType::Puk, new_puk, Some(Self::PUK_RETRIES_DEFAULT), true)) + .map_err(|_err| { + error!("Failed to set puk"); + Status::UnspecifiedPersistentExecutionError + }) + .map(drop) + } + pub fn reset_pin( + &mut self, + new_pin: Pin, + client: &mut T, + ) -> Result<(), Status> { + self.set_pin(new_pin, client) + } + pub fn reset_puk( + &mut self, + new_puk: Puk, + client: &mut T, + ) -> Result<(), Status> { + self.set_puk(new_puk, client) } pub fn reset_administration_key(&mut self, client: &mut impl trussed::Client) { @@ -494,7 +495,10 @@ impl Persistent { id } - pub fn initialize(client: &mut impl trussed::Client, storage: Location) -> Self { + pub fn initialize( + client: &mut T, + storage: Location, + ) -> Result { info!("initializing PIV state"); let administration = KeyWithAlg { id: syscall!(client.unsafe_inject_key( @@ -549,26 +553,24 @@ impl Persistent { let mut state = Self { keys, - consecutive_pin_mismatches: 0, - consecutive_puk_mismatches: 0, - 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 + state.reset_pin(Self::DEFAULT_PIN, client)?; + state.reset_puk(Self::DEFAULT_PUK, client)?; + Ok(state) } - pub fn load_or_initialize( - client: &mut impl trussed::Client, + pub fn load_or_initialize( + client: &mut T, storage: Location, ) -> Result { // todo: can't seem to combine load + initialize without code repetition let data = load_if_exists(client, storage, &PathBuf::from(Self::FILENAME))?; let Some(bytes) = data else { - return Ok( Self::initialize(client, storage)); + return Self::initialize(client, storage); }; let mut parsed: Self = trussed::cbor_deserialize(&bytes).map_err(|_err| {