mirror of
https://github.com/LordGrimmauld/yubi-oath-rs.git
synced 2025-03-04 05:44:40 +01:00
improve error propagation and classification
This commit is contained in:
parent
1348183976
commit
0c877a859a
5 changed files with 157 additions and 42 deletions
35
Cargo.lock
generated
35
Cargo.lock
generated
|
@ -96,6 +96,12 @@ version = "0.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hmac"
|
name = "hmac"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -140,6 +146,8 @@ dependencies = [
|
||||||
"regex",
|
"regex",
|
||||||
"sha1",
|
"sha1",
|
||||||
"sha2",
|
"sha2",
|
||||||
|
"strum",
|
||||||
|
"strum_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -159,7 +167,7 @@ version = "0.18.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
|
checksum = "3c7028bdd3d43083f6d8d4d5187680d0d3560d54df4cc9d752005268b41e64d0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck 0.4.1",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"proc-macro2-diagnostics",
|
"proc-macro2-diagnostics",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -262,6 +270,12 @@ version = "0.8.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
@ -290,6 +304,25 @@ version = "1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ce1475c515a4f03a8a7129bb5228b81a781a86cb0b3fbbc19e1c556d491a401f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strum_macros"
|
||||||
|
version = "0.27.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9688894b43459159c82bfa5a5fa0435c19cbe3c9b427fa1dd7b1ce0c279b18a7"
|
||||||
|
dependencies = [
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"rustversion",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subtle"
|
name = "subtle"
|
||||||
version = "2.6.1"
|
version = "2.6.1"
|
||||||
|
|
|
@ -9,6 +9,8 @@ pcsc = "2.9.0"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
sha1 = "0.10.6"
|
sha1 = "0.10.6"
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
|
strum = { version = "0.27.0", features = [ ] }
|
||||||
|
strum_macros = "0.27.0"
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "oath-rs-experiments"
|
name = "oath-rs-experiments"
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
use iso7816_tlv::simple::Tlv;
|
use iso7816_tlv::simple::Tlv;
|
||||||
#[crate_type = "lib"]
|
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
use sha2::{Sha256, Sha512};
|
use sha2::{Sha256, Sha512};
|
||||||
|
use strum::IntoEnumIterator; // 0.17.1
|
||||||
|
use strum_macros::EnumIter; // 0.17.1
|
||||||
pub const INS_SELECT: u8 = 0xa4;
|
pub const INS_SELECT: u8 = 0xa4;
|
||||||
pub const OATH_AID: [u8; 7] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01];
|
pub const OATH_AID: [u8; 7] = [0xa0, 0x00, 0x00, 0x05, 0x27, 0x21, 0x01];
|
||||||
|
|
||||||
|
@ -9,6 +10,7 @@ pub const DEFAULT_PERIOD: u32 = 30;
|
||||||
pub const DEFAULT_DIGITS: OathDigits = OathDigits::Six;
|
pub const DEFAULT_DIGITS: OathDigits = OathDigits::Six;
|
||||||
pub const DEFAULT_IMF: u32 = 0;
|
pub const DEFAULT_IMF: u32 = 0;
|
||||||
|
|
||||||
|
#[derive(Debug, EnumIter, Clone, Copy, PartialEq, Eq)]
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
pub enum ErrorResponse {
|
pub enum ErrorResponse {
|
||||||
NoSpace = 0x6a84,
|
NoSpace = 0x6a84,
|
||||||
|
@ -20,12 +22,48 @@ pub enum ErrorResponse {
|
||||||
NoSuchObject = 0x6984,
|
NoSuchObject = 0x6984,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl ErrorResponse {
|
||||||
|
pub fn as_string(self) -> String {
|
||||||
|
match self {
|
||||||
|
ErrorResponse::NoSpace => "No Space left on device",
|
||||||
|
ErrorResponse::CommandAborted => "Command aborted",
|
||||||
|
ErrorResponse::InvalidInstruction => "Invalid instruction",
|
||||||
|
ErrorResponse::AuthRequired => "Authentication required",
|
||||||
|
ErrorResponse::WrongSyntax => "Wrong syntax",
|
||||||
|
ErrorResponse::GenericError => "Generic Error",
|
||||||
|
ErrorResponse::NoSuchObject => "No such Object",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn any_match(code: u16) -> Option<ErrorResponse> {
|
||||||
|
for resp in ErrorResponse::iter() {
|
||||||
|
if code == resp as u16 {
|
||||||
|
return Some(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, EnumIter, Clone, Copy)]
|
||||||
#[repr(u16)]
|
#[repr(u16)]
|
||||||
pub enum SuccessResponse {
|
pub enum SuccessResponse {
|
||||||
MoreData = 0x61,
|
MoreData = 0x61,
|
||||||
Okay = 0x9000,
|
Okay = 0x9000,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SuccessResponse {
|
||||||
|
pub fn any_match(code: u16) -> Option<SuccessResponse> {
|
||||||
|
for resp in SuccessResponse::iter() {
|
||||||
|
if code == resp as u16 {
|
||||||
|
return Some(resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
pub enum Instruction {
|
pub enum Instruction {
|
||||||
Put = 0x01,
|
Put = 0x01,
|
||||||
|
|
|
@ -181,9 +181,9 @@ impl<'a> OathSession<'a> {
|
||||||
&self,
|
&self,
|
||||||
cred: OathCredential,
|
cred: OathCredential,
|
||||||
timestamp_sys: Option<SystemTime>,
|
timestamp_sys: Option<SystemTime>,
|
||||||
) -> Result<OathCodeDisplay, String> {
|
) -> Result<OathCodeDisplay, FormattableErrorResponse> {
|
||||||
if self.name != cred.device_id {
|
if self.name != cred.device_id {
|
||||||
return Err("device id mismatch: Credential no on key".to_string());
|
return Err(FormattableErrorResponse::DeviceMismatchError);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timestamp = time_to_u64(timestamp_sys.unwrap_or_else(SystemTime::now));
|
let timestamp = time_to_u64(timestamp_sys.unwrap_or_else(SystemTime::now));
|
||||||
|
@ -204,15 +204,22 @@ impl<'a> OathSession<'a> {
|
||||||
Some(&data),
|
Some(&data),
|
||||||
);
|
);
|
||||||
|
|
||||||
let meta = TlvIter::from_vec(resp?)
|
let meta =
|
||||||
.next()
|
TlvIter::from_vec(resp?)
|
||||||
.ok_or("No credentials to unpack found in response".to_string())?;
|
.next()
|
||||||
|
.ok_or(FormattableErrorResponse::ParsingError(
|
||||||
|
"No credentials to unpack found in response".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
OathCodeDisplay::from_tlv(meta).ok_or("error parsing calculation response".to_string())
|
OathCodeDisplay::from_tlv(meta).ok_or(FormattableErrorResponse::ParsingError(
|
||||||
|
"error parsing calculation response".to_string(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read the OATH codes from the device
|
/// Read the OATH codes from the device
|
||||||
pub fn get_oath_codes(&self) -> Result<Vec<RefreshableOathCredential>, String> {
|
pub fn get_oath_codes(
|
||||||
|
&self,
|
||||||
|
) -> Result<Vec<RefreshableOathCredential>, FormattableErrorResponse> {
|
||||||
let timestamp = SystemTime::now();
|
let timestamp = SystemTime::now();
|
||||||
// Request OATH codes from device
|
// Request OATH codes from device
|
||||||
let response = self.transaction_context.apdu_read_all(
|
let response = self.transaction_context.apdu_read_all(
|
||||||
|
|
|
@ -5,6 +5,7 @@ extern crate pcsc;
|
||||||
use iso7816_tlv::simple::{Tag as TlvTag, Tlv};
|
use iso7816_tlv::simple::{Tag as TlvTag, Tlv};
|
||||||
use ouroboros::self_referencing;
|
use ouroboros::self_referencing;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::fmt::Display;
|
||||||
use std::str::{self};
|
use std::str::{self};
|
||||||
|
|
||||||
use apdu_core::{Command, Response};
|
use apdu_core::{Command, Response};
|
||||||
|
@ -13,6 +14,63 @@ use pcsc::{Card, Transaction};
|
||||||
|
|
||||||
use std::ffi::CString;
|
use std::ffi::CString;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub enum FormattableErrorResponse {
|
||||||
|
NoError,
|
||||||
|
Unknown(String),
|
||||||
|
Protocol(ErrorResponse),
|
||||||
|
PcscError(pcsc::Error),
|
||||||
|
ParsingError(String),
|
||||||
|
DeviceMismatchError,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormattableErrorResponse {
|
||||||
|
pub fn from_apdu_response(sw1: u8, sw2: u8) -> FormattableErrorResponse {
|
||||||
|
let code: u16 = (sw1 as u16 | sw2 as u16) << 8;
|
||||||
|
if let Some(e) = ErrorResponse::any_match(code) {
|
||||||
|
return FormattableErrorResponse::Protocol(e);
|
||||||
|
}
|
||||||
|
if SuccessResponse::any_match(code)
|
||||||
|
.or(SuccessResponse::any_match(sw1.into()))
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return FormattableErrorResponse::NoError;
|
||||||
|
}
|
||||||
|
FormattableErrorResponse::Unknown(String::from("Unknown error"))
|
||||||
|
}
|
||||||
|
pub fn is_ok(&self) -> bool {
|
||||||
|
*self == FormattableErrorResponse::NoError
|
||||||
|
}
|
||||||
|
pub fn as_opt(self) -> Option<FormattableErrorResponse> {
|
||||||
|
if self.is_ok() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_transmit(err: pcsc::Error) -> FormattableErrorResponse {
|
||||||
|
FormattableErrorResponse::PcscError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn as_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
FormattableErrorResponse::NoError => "ok".to_string(),
|
||||||
|
FormattableErrorResponse::Unknown(msg) => msg.to_owned(),
|
||||||
|
FormattableErrorResponse::Protocol(error_response) => error_response.as_string(),
|
||||||
|
FormattableErrorResponse::PcscError(error) => format!("{}", error),
|
||||||
|
FormattableErrorResponse::ParsingError(msg) => msg.to_owned(),
|
||||||
|
FormattableErrorResponse::DeviceMismatchError => "Devices do not match".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for FormattableErrorResponse {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(&self.as_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct ApduResponse {
|
pub struct ApduResponse {
|
||||||
pub buf: Vec<u8>,
|
pub buf: Vec<u8>,
|
||||||
pub sw1: u8,
|
pub sw1: u8,
|
||||||
|
@ -27,7 +85,7 @@ fn apdu(
|
||||||
parameter1: u8,
|
parameter1: u8,
|
||||||
parameter2: u8,
|
parameter2: u8,
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
) -> Result<ApduResponse, String> {
|
) -> Result<ApduResponse, FormattableErrorResponse> {
|
||||||
let command = if let Some(data) = data {
|
let command = if let Some(data) = data {
|
||||||
Command::new_with_payload(class, instruction, parameter1, parameter2, data)
|
Command::new_with_payload(class, instruction, parameter1, parameter2, data)
|
||||||
} else {
|
} else {
|
||||||
|
@ -42,14 +100,16 @@ fn apdu(
|
||||||
// Write the payload to the device and error if there is a problem
|
// Write the payload to the device and error if there is a problem
|
||||||
let rx_buf = match tx.transmit(&tx_buf, &mut rx_buf) {
|
let rx_buf = match tx.transmit(&tx_buf, &mut rx_buf) {
|
||||||
Ok(slice) => slice,
|
Ok(slice) => slice,
|
||||||
Err(err) => return Err(format!("{}", err)),
|
// Err(err) => return Err(format!("{}", err)),
|
||||||
|
Err(err) => return Err(FormattableErrorResponse::from_transmit(err)),
|
||||||
};
|
};
|
||||||
|
|
||||||
let resp = Response::from(rx_buf);
|
let resp = Response::from(rx_buf);
|
||||||
let error_context = to_error_response(resp.trailer.0, resp.trailer.1);
|
let error_context =
|
||||||
|
FormattableErrorResponse::from_apdu_response(resp.trailer.0, resp.trailer.1);
|
||||||
|
|
||||||
if let Some(err) = error_context {
|
if !error_context.is_ok() {
|
||||||
return Err(err);
|
return Err(error_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ApduResponse {
|
Ok(ApduResponse {
|
||||||
|
@ -66,7 +126,7 @@ fn apdu_read_all(
|
||||||
parameter1: u8,
|
parameter1: u8,
|
||||||
parameter2: u8,
|
parameter2: u8,
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
) -> Result<Vec<u8>, String> {
|
) -> Result<Vec<u8>, FormattableErrorResponse> {
|
||||||
let mut response_buf = Vec::new();
|
let mut response_buf = Vec::new();
|
||||||
let mut resp = apdu(tx, class, instruction, parameter1, parameter2, data)?;
|
let mut resp = apdu(tx, class, instruction, parameter1, parameter2, data)?;
|
||||||
response_buf.extend(resp.buf);
|
response_buf.extend(resp.buf);
|
||||||
|
@ -77,31 +137,6 @@ fn apdu_read_all(
|
||||||
Ok(response_buf)
|
Ok(response_buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_error_response(sw1: u8, sw2: u8) -> Option<String> {
|
|
||||||
let code: usize = (sw1 as usize | sw2 as usize) << 8;
|
|
||||||
|
|
||||||
match code {
|
|
||||||
code if code == ErrorResponse::GenericError as usize => Some(String::from("Generic error")),
|
|
||||||
code if code == ErrorResponse::NoSpace as usize => Some(String::from("No space on device")),
|
|
||||||
code if code == ErrorResponse::NoSuchObject as usize => {
|
|
||||||
Some(String::from("No such object"))
|
|
||||||
}
|
|
||||||
code if code == ErrorResponse::CommandAborted as usize => {
|
|
||||||
Some(String::from("Command was aborted"))
|
|
||||||
}
|
|
||||||
code if code == ErrorResponse::AuthRequired as usize => {
|
|
||||||
Some(String::from("Authentication required"))
|
|
||||||
}
|
|
||||||
code if code == ErrorResponse::WrongSyntax as usize => Some(String::from("Wrong syntax")),
|
|
||||||
code if code == ErrorResponse::InvalidInstruction as usize => {
|
|
||||||
Some(String::from("Invalid instruction"))
|
|
||||||
}
|
|
||||||
code if code == SuccessResponse::Okay as usize => None,
|
|
||||||
sw1 if sw1 == SuccessResponse::MoreData as usize => None,
|
|
||||||
_ => Some(String::from("Unknown error")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[self_referencing]
|
#[self_referencing]
|
||||||
pub struct TransactionContext {
|
pub struct TransactionContext {
|
||||||
card: Card,
|
card: Card,
|
||||||
|
@ -140,7 +175,7 @@ impl TransactionContext {
|
||||||
parameter1: u8,
|
parameter1: u8,
|
||||||
parameter2: u8,
|
parameter2: u8,
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
) -> Result<ApduResponse, String> {
|
) -> Result<ApduResponse, FormattableErrorResponse> {
|
||||||
apdu(
|
apdu(
|
||||||
self.borrow_transaction(),
|
self.borrow_transaction(),
|
||||||
class,
|
class,
|
||||||
|
@ -158,7 +193,7 @@ impl TransactionContext {
|
||||||
parameter1: u8,
|
parameter1: u8,
|
||||||
parameter2: u8,
|
parameter2: u8,
|
||||||
data: Option<&[u8]>,
|
data: Option<&[u8]>,
|
||||||
) -> Result<Vec<u8>, String> {
|
) -> Result<Vec<u8>, FormattableErrorResponse> {
|
||||||
apdu_read_all(
|
apdu_read_all(
|
||||||
self.borrow_transaction(),
|
self.borrow_transaction(),
|
||||||
class,
|
class,
|
||||||
|
|
Loading…
Add table
Reference in a new issue