admin_app/
admin.rs

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    /// Reboots the device.
22    fn reboot() -> !;
23
24    /// Reboots the device.
25    ///
26    /// Presuming the device has a separate mode of operation that
27    /// allows updating its firmware (for instance, a bootloader),
28    /// reboots the device into this mode.
29    fn reboot_to_firmware_update() -> !;
30
31    /// Reboots the device.
32    ///
33    /// Presuming the device has a separate destructive but more
34    /// reliable way of rebooting into the firmware mode of operation,
35    /// does so.
36    fn reboot_to_firmware_update_destructive() -> !;
37
38    /// Is device bootloader locked down?
39    /// E.g., is secure boot enabled?
40    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                // Fill the HID packet (57 bytes)
95                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                // Get UUID
112                response.extend_from_slice(&self.uuid).ok();
113            }
114            HidCommand::Vendor(VERSION) => {
115                // GET VERSION
116                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    // Solo management app
135    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                // Random bytes
165                reply.extend_from_slice(&[R::locked() as u8]).ok();
166            }
167            RNG => {
168                // Random bytes
169                reply.extend_from_slice(&syscall!(self.trussed.random_bytes(57)).bytes.as_slice()).ok();
170            }
171            UPDATE => {
172                // Boot to mcuboot (only when contact interface)
173                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                // Get UUID
185                reply.extend_from_slice(&self.uuid).ok();
186            }
187            VERSION => {
188                // Get version
189                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