Move piv::Verify handling to parsed enum matching approach

This commit is contained in:
Nicolas Stalder
2021-06-05 18:22:12 +02:00
committed by Nicolas Stalder
parent 18c6966723
commit e8a905cc9b
4 changed files with 253 additions and 39 deletions
+89 -34
View File
@@ -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),
})
}
}
+22
View File
@@ -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
View File
@@ -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
View File
@@ -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,