1use core::{convert::TryInto, marker::PhantomData};
2use ctaphid_dispatch::app::{self as hid, Command as HidCommand, Message};
3use ctaphid_dispatch::command::VendorCommand;
4use apdu_dispatch::{Command, command, response, app as apdu};
5use apdu_dispatch::iso7816::Status;
6use trussed::{
7 syscall,
8 Client as TrussedClient,
9};
10
11pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15;
12
13const UPDATE: VendorCommand = VendorCommand::H51;
14const REBOOT: VendorCommand = VendorCommand::H53;
15const RNG: VendorCommand = VendorCommand::H60;
16const VERSION: VendorCommand = VendorCommand::H61;
17const UUID: VendorCommand = VendorCommand::H62;
18const LOCKED: VendorCommand = VendorCommand::H63;
19
20pub trait Reboot {
21 fn reboot() -> !;
23
24 fn reboot_to_firmware_update() -> !;
30
31 fn reboot_to_firmware_update_destructive() -> !;
37
38 fn locked() -> bool;
41}
42
43pub struct App<T, R>
44where T: TrussedClient,
45 R: Reboot,
46{
47 trussed: T,
48 uuid: [u8; 16],
49 version: u32,
50 boot_interface: PhantomData<R>,
51}
52
53impl<T, R> App<T, R>
54where T: TrussedClient,
55 R: Reboot,
56{
57 pub fn new(client: T, uuid: [u8; 16], version: u32) -> Self {
58 Self { trussed: client, uuid, version, boot_interface: PhantomData }
59 }
60
61 fn user_present(&mut self) -> bool {
62 let user_present = syscall!(self.trussed.confirm_user_present(USER_PRESENCE_TIMEOUT_SECS * 1000)).result;
63 user_present.is_ok()
64 }
65
66
67}
68
69impl<T, R> hid::App for App<T, R>
70where T: TrussedClient,
71 R: Reboot
72{
73 fn commands(&self) -> &'static [HidCommand] {
74 &[
75 HidCommand::Wink,
76 HidCommand::Vendor(UPDATE),
77 HidCommand::Vendor(REBOOT),
78 HidCommand::Vendor(RNG),
79 HidCommand::Vendor(VERSION),
80 HidCommand::Vendor(UUID),
81 HidCommand::Vendor(LOCKED),
82 ]
83 }
84
85 fn call(&mut self, command: HidCommand, input_data: &Message, response: &mut Message) -> hid::AppResult {
86 match command {
87 HidCommand::Vendor(REBOOT) => R::reboot(),
88 HidCommand::Vendor(LOCKED) => {
89 response.extend_from_slice(
90 &[R::locked() as u8]
91 ).ok();
92 }
93 HidCommand::Vendor(RNG) => {
94 response.extend_from_slice(
96 &syscall!(self.trussed.random_bytes(57)).bytes.as_slice()
97 ).ok();
98 }
99 HidCommand::Vendor(UPDATE) => {
100 if self.user_present() {
101 if input_data.len() > 0 && input_data[0] == 0x01 {
102 R::reboot_to_firmware_update_destructive();
103 } else {
104 R::reboot_to_firmware_update();
105 }
106 } else {
107 return Err(hid::Error::InvalidLength);
108 }
109 }
110 HidCommand::Vendor(UUID) => {
111 response.extend_from_slice(&self.uuid).ok();
113 }
114 HidCommand::Vendor(VERSION) => {
115 response.extend_from_slice(&self.version.to_be_bytes()).ok();
117 }
118 HidCommand::Wink => {
119 debug_now!("winking");
120 syscall!(self.trussed.wink(core::time::Duration::from_secs(10)));
121 }
122 _ => {
123 return Err(hid::Error::InvalidCommand);
124 }
125 }
126 Ok(())
127 }
128}
129
130impl<T, R> iso7816::App for App<T, R>
131where T: TrussedClient,
132 R: Reboot
133{
134 fn aid(&self) -> iso7816::Aid {
136 iso7816::Aid::new(&[ 0xA0, 0x00, 0x00, 0x08, 0x47, 0x00, 0x00, 0x00, 0x01])
137 }
138}
139
140impl<T, R> apdu::App<{command::SIZE}, {response::SIZE}> for App<T, R>
141where T: TrussedClient,
142 R: Reboot
143{
144
145 fn select(&mut self, _apdu: &Command, _reply: &mut response::Data) -> apdu::Result {
146 Ok(())
147 }
148
149 fn deselect(&mut self) {}
150
151 fn call(&mut self, interface: apdu::Interface, apdu: &Command, reply: &mut response::Data) -> apdu::Result {
152 let instruction: u8 = apdu.instruction().into();
153
154 if instruction == 0x08 {
155 syscall!(self.trussed.wink(core::time::Duration::from_secs(10)));
156 return Ok(());
157 }
158
159 let command: VendorCommand = instruction.try_into().map_err(|_e| Status::InstructionNotSupportedOrInvalid)?;
160
161 match command {
162 REBOOT => R::reboot(),
163 LOCKED => {
164 reply.extend_from_slice(&[R::locked() as u8]).ok();
166 }
167 RNG => {
168 reply.extend_from_slice(&syscall!(self.trussed.random_bytes(57)).bytes.as_slice()).ok();
170 }
171 UPDATE => {
172 if interface == apdu::Interface::Contact && self.user_present()
174 {
175 if apdu.p1 == 0x01 {
176 R::reboot_to_firmware_update_destructive();
177 } else {
178 R::reboot_to_firmware_update();
179 }
180 }
181 return Err(Status::ConditionsOfUseNotSatisfied);
182 }
183 UUID => {
184 reply.extend_from_slice(&self.uuid).ok();
186 }
187 VERSION => {
188 reply.extend_from_slice(&self.version.to_be_bytes()[..]).ok();
190 }
191
192 _ => return Err(Status::InstructionNotSupportedOrInvalid),
193
194 }
195 Ok(())
196
197 }
198}
199