use core::convert::{TryFrom, TryInto};
use iso7816::{Data, Status};
use crate::oath;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Command<'l> {
Select(Select<'l>),
Calculate(Calculate<'l>),
CalculateAll(CalculateAll<'l>),
ClearPassword,
Delete(Delete<'l>),
ListCredentials,
Register(Register<'l>),
Reset,
SetPassword(SetPassword<'l>),
Validate(Validate<'l>),
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Select<'l> {
pub aid: &'l [u8],
}
impl core::fmt::Debug for Select<'_> {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
fmt.debug_struct("Select")
.field("aid", &hex_str!(&self.aid, 5))
.finish()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct SetPassword<'l> {
pub kind: oath::Kind,
pub algorithm: oath::Algorithm,
pub key: &'l [u8],
pub challenge: &'l [u8],
pub response: &'l [u8],
}
impl<'l, const C: usize> TryFrom<&'l Data<C>> for SetPassword<'l> {
type Error = Status;
fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
use flexiber::TaggedSlice;
let mut decoder = flexiber::Decoder::new(data);
let slice: TaggedSlice = decoder.decode().unwrap();
assert!(slice.tag() == (oath::Tag::Key as u8).try_into().unwrap());
let (key_header, key) = slice.as_bytes().split_at(1);
let kind: oath::Kind = key_header[0].try_into()?;
let algorithm: oath::Algorithm = key_header[0].try_into()?;
let slice: TaggedSlice = decoder.decode().unwrap();
assert!(slice.tag() == (oath::Tag::Challenge as u8).try_into().unwrap());
let challenge = slice.as_bytes();
let slice: TaggedSlice = decoder.decode().unwrap();
assert!(slice.tag() == (oath::Tag::Response as u8).try_into().unwrap());
let response = slice.as_bytes();
Ok(SetPassword {
kind,
algorithm,
key,
challenge,
response,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Validate<'l> {
pub response: &'l [u8],
pub challenge: &'l [u8],
}
impl<'l, const C: usize> TryFrom<&'l Data<C>> for Validate<'l> {
type Error = Status;
fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
use flexiber::TaggedSlice;
let mut decoder = flexiber::Decoder::new(data);
let slice: TaggedSlice = decoder.decode().unwrap();
assert!(slice.tag() == (oath::Tag::Response as u8).try_into().unwrap());
let response = slice.as_bytes();
let slice: TaggedSlice = decoder.decode().unwrap();
assert!(slice.tag() == (oath::Tag::Challenge as u8).try_into().unwrap());
let challenge = slice.as_bytes();
Ok(Validate { challenge, response })
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Calculate<'l> {
pub label: &'l [u8],
pub challenge: &'l [u8],
}
impl<'l, const C: usize> TryFrom<&'l Data<C>> for Calculate<'l> {
type Error = Status;
fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
use flexiber::TaggedSlice;
let mut decoder = flexiber::Decoder::new(data);
let first: TaggedSlice = decoder.decode().unwrap();
assert!(first.tag() == (oath::Tag::Name as u8).try_into().unwrap());
let label = first.as_bytes();
let second: TaggedSlice = decoder.decode().unwrap();
assert!(second.tag() == (oath::Tag::Challenge as u8).try_into().unwrap());
let challenge = second.as_bytes();
Ok(Calculate { label, challenge })
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct CalculateAll<'l> {
pub challenge: &'l [u8],
}
impl<'l, const C: usize> TryFrom<&'l Data<C>> for CalculateAll<'l> {
type Error = Status;
fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
use flexiber::TaggedSlice;
let mut decoder = flexiber::Decoder::new(data);
let first: TaggedSlice = decoder.decode().unwrap();
assert!(first.tag() == (oath::Tag::Challenge as u8).try_into().unwrap());
let challenge = first.as_bytes();
Ok(CalculateAll { challenge })
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Delete<'l> {
pub label: &'l [u8],
}
impl core::fmt::Debug for Delete<'_> {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
fmt.debug_struct("Credential")
.field("label", &core::str::from_utf8(self.label).unwrap_or(&"invalid UTF8 label"))
.finish()
}
}
impl<'l, const C: usize> TryFrom<&'l Data<C>> for Delete<'l> {
type Error = iso7816::Status;
fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
use flexiber::TaggedSlice;
let mut decoder = flexiber::Decoder::new(data);
let first: TaggedSlice = decoder.decode().unwrap();
assert!(first.tag() == (oath::Tag::Name as u8).try_into().unwrap());
let label = first.as_bytes();
Ok(Delete { label })
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Register<'l> {
pub credential: Credential<'l>,
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Credential<'l> {
pub label: &'l [u8],
pub kind: oath::Kind,
pub algorithm: oath::Algorithm,
pub digits: u8,
pub secret: &'l [u8],
pub touch_required: bool,
pub counter: Option<u32>,
}
impl core::fmt::Debug for Credential<'_> {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> core::result::Result<(), core::fmt::Error> {
fmt.debug_struct("Credential")
.field("label", &core::str::from_utf8(self.label).unwrap_or(&"invalid UTF8 label")) .field("kind", &self.kind)
.field("alg", &self.algorithm)
.field("digits", &self.digits)
.field("secret", &hex_str!(&self.secret, 4))
.field("touch", &self.touch_required)
.field("counter", &self.counter)
.finish()
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct Properties(u8);
impl Properties {
fn touch_required(&self) -> bool {
self.0 & (oath::Properties::RequireTouch as u8) != 0
}
}
impl<'a> flexiber::Decodable<'a> for Properties {
fn decode(decoder: &mut flexiber::Decoder<'a>) -> flexiber::Result<Properties> {
let two_bytes: [u8; 2] = decoder.decode()?;
let [tag, properties] = two_bytes;
use flexiber::Tagged;
assert_eq!(flexiber::Tag::try_from(tag).unwrap(), Self::tag());
Ok(Properties(properties))
}
}
impl flexiber::Tagged for Properties {
fn tag() -> flexiber::Tag {
let ret = flexiber::Tag::try_from(oath::Tag::Property as u8).unwrap();
ret
}
}
impl<'l, const C: usize> TryFrom<&'l Data<C>> for Register<'l> {
type Error = iso7816::Status;
fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
use flexiber::{Decodable, TagLike};
type TaggedSlice<'a> = flexiber::TaggedSlice<'a, flexiber::SimpleTag>;
let mut decoder = flexiber::Decoder::new(data);
let first: TaggedSlice = decoder.decode().unwrap();
assert!(first.tag() == (oath::Tag::Name as u8).try_into().unwrap());
let label = first.as_bytes();
let second: TaggedSlice = decoder.decode().unwrap();
second.tag().assert_eq((oath::Tag::Key as u8).try_into().unwrap()).unwrap();
let (secret_header, secret) = second.as_bytes().split_at(2);
let kind: oath::Kind = secret_header[0].try_into()?;
let algorithm: oath::Algorithm = secret_header[0].try_into()?;
let digits = secret_header[1];
let maybe_properties: Option<Properties> = decoder.decode().unwrap();
let touch_required = maybe_properties
.map(|properties| {
info_now!("unraveling {:?}", &properties);
properties.touch_required()
})
.unwrap_or(false);
let mut counter = None;
if kind == oath::Kind::Hotp {
counter = Some(0);
if let Ok(last) = TaggedSlice::decode(&mut decoder) {
if last.tag() == (oath::Tag::InitialMovingFactor as u8).try_into().unwrap() {
let bytes = last.as_bytes();
if bytes.len() == 4 {
counter = Some(u32::from_be_bytes(bytes.try_into().unwrap()));
}
}
}
debug_now!("counter set to {:?}", &counter);
}
let credential = Credential {
label,
kind,
algorithm,
digits,
secret,
touch_required,
counter,
};
Ok(Register { credential })
}
}
impl<'l, const C: usize> TryFrom<&'l iso7816::Command<C>> for Command<'l> {
type Error = Status;
fn try_from(command: &'l iso7816::Command<C>) -> Result<Self, Self::Error> {
let (class, instruction, p1, p2) = (command.class(), command.instruction(), command.p1, command.p2);
let data = command.data();
if !class.secure_messaging().none() {
return Err(Status::SecureMessagingNotSupported);
}
if class.channel() != Some(0) {
return Err(Status::LogicalChannelNotSupported);
}
if (0x00, iso7816::Instruction::Select, 0x04, 0x00) == (class.into_inner(), instruction, p1, p2) {
Ok(Self::Select(Select::try_from(data)?))
} else {
let instruction_byte: u8 = instruction.into();
let instruction: oath::Instruction = instruction_byte.try_into()?;
Ok(match (class.into_inner(), instruction, p1, p2) {
(0x00, oath::Instruction::Calculate, 0x00, 0x01) => Self::Calculate(Calculate::try_from(data)?),
(0x00, oath::Instruction::CalculateAll, 0x00, 0x01) => Self::CalculateAll(CalculateAll::try_from(data)?),
(0x00, oath::Instruction::Delete, 0x00, 0x00) => Self::Delete(Delete::try_from(data)?),
(0x00, oath::Instruction::List, 0x00, 0x00) => Self::ListCredentials,
(0x00, oath::Instruction::Put, 0x00, 0x00) => Self::Register(Register::try_from(data)?),
(0x00, oath::Instruction::Reset, 0xde, 0xad) => Self::Reset,
(0x00, oath::Instruction::SetCode, 0x00, 0x00) => {
if data.len() == 2 {
Self::ClearPassword
} else {
Self::SetPassword(SetPassword::try_from(data)?)
}
}
(0x00, oath::Instruction::Validate, 0x00, 0x00) => Self::Validate(Validate::try_from(data)?),
_ => return Err(Status::InstructionNotSupportedOrInvalid),
})
}
}
}
impl<'l, const C: usize> TryFrom<&'l Data<C>> for Select<'l> {
type Error = Status;
fn try_from(data: &'l Data<C>) -> Result<Self, Self::Error> {
Ok(match data.as_slice() {
crate::YUBICO_OATH_AID => Self { aid: data },
_ => return Err(Status::NotFound),
})
}
}