mirror of
https://github.com/trussed-dev/piv-authenticator.git
synced 2026-06-20 04:16:15 -07:00
Move piv::Verify handling to parsed enum matching approach
This commit is contained in:
committed by
Nicolas Stalder
parent
18c6966723
commit
e8a905cc9b
+89
-34
@@ -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<Self, Status> {
|
||||
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<u8> for VerifyKeyReference {
|
||||
type Error = Status;
|
||||
fn try_from(p2: u8) -> Result<Self, Self::Error> {
|
||||
// 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<u8> for VerifyParameter1 {
|
||||
impl TryFrom<u8> for VerifyLogout {
|
||||
type Error = Status;
|
||||
fn try_from(p1: u8) -> Result<Self, Self::Error> {
|
||||
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<u8> 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<VerifyArguments<'_>> for Verify {
|
||||
type Error = Status;
|
||||
fn try_from(arguments: VerifyArguments<'_>) -> Result<Self, Self::Error> {
|
||||
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<u8> 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<ChangeReferenceArguments<'_>> 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<Self, Self::Error> {
|
||||
if data.len() != 16 {
|
||||
@@ -229,9 +282,11 @@ impl TryFrom<u8> 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<u8> 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Success, Error>;
|
||||
|
||||
// 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()]
|
||||
// }
|
||||
|
||||
+126
-3
@@ -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);
|
||||
|
||||
+16
-2
@@ -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<heapless::consts::U8>,
|
||||
pin: heapless_bytes::Bytes<heapless::consts::U8>,
|
||||
}
|
||||
|
||||
// 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<CommandCache>,
|
||||
pub chained_command: Option<apdu_dispatch::Command>,
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user