use core::{convert::TryInto, marker::PhantomData};
use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message};
use ctaphid_dispatch::command::VendorCommand;
use apdu_dispatch::{Command, command, response, app as apdu};
use apdu_dispatch::iso7816::Status;
use trussed::{
syscall,
Client as TrussedClient,
};
pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15;
const UPDATE: VendorCommand = VendorCommand::H51;
const REBOOT: VendorCommand = VendorCommand::H53;
const RNG: VendorCommand = VendorCommand::H60;
const VERSION: VendorCommand = VendorCommand::H61;
const UUID: VendorCommand = VendorCommand::H62;
const LOCKED: VendorCommand = VendorCommand::H63;
pub trait Reboot {
fn reboot() -> !;
fn reboot_to_firmware_update() -> !;
fn reboot_to_firmware_update_destructive() -> !;
fn locked() -> bool;
}
pub struct App<T, R>
where T: TrussedClient,
R: Reboot,
{
trussed: T,
uuid: [u8; 16],
version: u32,
boot_interface: PhantomData<R>,
}
impl<T, R> App<T, R>
where T: TrussedClient,
R: Reboot,
{
pub fn new(client: T, uuid: [u8; 16], version: u32) -> Self {
Self { trussed: client, uuid, version, boot_interface: PhantomData }
}
fn user_present(&mut self) -> bool {
let user_present = syscall!(self.trussed.confirm_user_present(USER_PRESENCE_TIMEOUT_SECS * 1000)).result;
user_present.is_ok()
}
}
impl<T, R> hid::App for App<T, R>
where T: TrussedClient,
R: Reboot
{
fn commands(&self) -> &'static [HidCommand] {
&[
HidCommand::Wink,
HidCommand::Vendor(UPDATE),
HidCommand::Vendor(REBOOT),
HidCommand::Vendor(RNG),
HidCommand::Vendor(VERSION),
HidCommand::Vendor(UUID),
HidCommand::Vendor(LOCKED),
]
}
fn call(&mut self, command: HidCommand, input_data: &Message, response: &mut Message) -> hid::AppResult {
match command {
HidCommand::Vendor(REBOOT) => R::reboot(),
HidCommand::Vendor(LOCKED) => {
response.extend_from_slice(
&[R::locked() as u8]
).ok();
}
HidCommand::Vendor(RNG) => {
response.extend_from_slice(
&syscall!(self.trussed.random_bytes(57)).bytes.as_slice()
).ok();
}
HidCommand::Vendor(UPDATE) => {
if self.user_present() {
if input_data.len() > 0 && input_data[0] == 0x01 {
R::reboot_to_firmware_update_destructive();
} else {
R::reboot_to_firmware_update();
}
} else {
return Err(hid::Error::InvalidLength);
}
}
HidCommand::Vendor(UUID) => {
response.extend_from_slice(&self.uuid).ok();
}
HidCommand::Vendor(VERSION) => {
response.extend_from_slice(&self.version.to_be_bytes()).ok();
}
HidCommand::Wink => {
debug_now!("winking");
syscall!(self.trussed.wink(core::time::Duration::from_secs(10)));
}
_ => {
return Err(hid::Error::InvalidCommand);
}
}
Ok(())
}
}
impl<T, R> iso7816::App for App<T, R>
where T: TrussedClient,
R: Reboot
{
fn aid(&self) -> iso7816::Aid {
iso7816::Aid::new(&[ 0xA0, 0x00, 0x00, 0x08, 0x47, 0x00, 0x00, 0x00, 0x01])
}
}
impl<T, R> apdu::App<{command::SIZE}, {response::SIZE}> for App<T, R>
where T: TrussedClient,
R: Reboot
{
fn select(&mut self, _apdu: &Command, _reply: &mut response::Data) -> apdu::Result {
Ok(())
}
fn deselect(&mut self) {}
fn call(&mut self, interface: apdu::Interface, apdu: &Command, reply: &mut response::Data) -> apdu::Result {
let instruction: u8 = apdu.instruction().into();
if instruction == 0x08 {
syscall!(self.trussed.wink(core::time::Duration::from_secs(10)));
return Ok(());
}
let command: VendorCommand = instruction.try_into().map_err(|_e| Status::InstructionNotSupportedOrInvalid)?;
match command {
REBOOT => R::reboot(),
LOCKED => {
reply.extend_from_slice(&[R::locked() as u8]).ok();
}
RNG => {
reply.extend_from_slice(&syscall!(self.trussed.random_bytes(57)).bytes.as_slice()).ok();
}
UPDATE => {
if interface == apdu::Interface::Contact && self.user_present()
{
if apdu.p1 == 0x01 {
R::reboot_to_firmware_update_destructive();
} else {
R::reboot_to_firmware_update();
}
}
return Err(Status::ConditionsOfUseNotSatisfied);
}
UUID => {
reply.extend_from_slice(&self.uuid).ok();
}
VERSION => {
reply.extend_from_slice(&self.version.to_be_bytes()[..]).ok();
}
_ => return Err(Status::InstructionNotSupportedOrInvalid),
}
Ok(())
}
}