1use super::Client as TrussedClient;
2use apdu_app::{CommandView, Interface};
3use cbor_smol::{cbor_deserialize, cbor_serialize_to};
4use core::{convert::TryInto, time::Duration};
5use ctaphid_app::{self as hid, Command as HidCommand, VendorCommand};
6use heapless::VecView;
7use heapless_bytes::BytesView;
8use iso7816::Status;
9#[cfg(feature = "factory-reset")]
10use littlefs2_core::PathBuf;
11use serde::Deserialize;
12use trussed::store::{Filestore, Store};
13use trussed_core::{syscall, try_syscall, InterruptFlag};
14
15use crate::config::{self, Config, ConfigError};
16use crate::migrations::Migrator;
17
18pub const USER_PRESENCE_TIMEOUT_SECS: u32 = 15;
19
20const ADMIN: VendorCommand = VendorCommand::H72;
23const STATUS: u8 = 0x80;
24const TEST_SE050: u8 = 0x81;
25const GET_CONFIG: u8 = 0x82;
26const SET_CONFIG: u8 = 0x83;
27#[cfg(feature = "factory-reset")]
28const FACTORY_RESET: u8 = 0x84;
29#[cfg(feature = "factory-reset")]
30const FACTORY_RESET_APP: u8 = 0x85;
31const LIST_AVAILABLE_FIELDS: u8 = 0x86;
32
33const UPDATE: VendorCommand = VendorCommand::H51;
35const REBOOT: VendorCommand = VendorCommand::H53;
36const RNG: VendorCommand = VendorCommand::H60;
37const VERSION: VendorCommand = VendorCommand::H61;
38const UUID: VendorCommand = VendorCommand::H62;
39const LOCKED: VendorCommand = VendorCommand::H63;
40
41const WINK: HidCommand = HidCommand::Wink; const RNG_DATA_LEN: usize = 57;
45
46const CONFIG_OK: u8 = 0x00;
47#[cfg(feature = "factory-reset")]
48const FACTORY_RESET_OK: u8 = 0x00;
49#[cfg(feature = "factory-reset")]
50const FACTORY_RESET_NOT_CONFIRMED: u8 = 0x01;
51#[cfg(feature = "factory-reset")]
52const FACTORY_RESET_APP_NOT_ALLOWED: u8 = 0x02;
53#[cfg(feature = "factory-reset")]
54const FACTORY_RESET_APP_FAILED_PARSE: u8 = 0x03;
55
56#[derive(PartialEq, Debug)]
57enum Command {
58 Update,
59 Reboot,
60 Rng,
61 Version,
62 Uuid,
63 Locked,
64 Wink,
65 Status,
66 TestSe05X,
67 GetConfig,
68 SetConfig,
69 #[cfg(feature = "factory-reset")]
70 FactoryReset,
71 #[cfg(feature = "factory-reset")]
72 FactoryResetApp,
73 ListAvailableFields,
74}
75
76impl TryFrom<u8> for Command {
77 type Error = Error;
78
79 fn try_from(command: u8) -> Result<Self, Self::Error> {
80 if let Ok(command) = HidCommand::try_from(command) {
82 if let Ok(command) = command.try_into() {
83 return Ok(command);
84 }
85 }
86
87 match command {
89 STATUS => Ok(Command::Status),
90 TEST_SE050 => Ok(Command::TestSe05X),
91 GET_CONFIG => Ok(Command::GetConfig),
92 SET_CONFIG => Ok(Command::SetConfig),
93 #[cfg(feature = "factory-reset")]
94 FACTORY_RESET => Ok(Command::FactoryReset),
95 #[cfg(feature = "factory-reset")]
96 FACTORY_RESET_APP => Ok(Command::FactoryResetApp),
97 LIST_AVAILABLE_FIELDS => Ok(Command::ListAvailableFields),
98 _ => Err(Error::UnsupportedCommand),
99 }
100 }
101}
102
103impl TryFrom<HidCommand> for Command {
104 type Error = Error;
105
106 fn try_from(command: HidCommand) -> Result<Self, Self::Error> {
107 match command {
108 WINK => Ok(Command::Wink),
109 HidCommand::Vendor(command) => command.try_into(),
110 _ => Err(Error::UnsupportedCommand),
111 }
112 }
113}
114
115impl TryFrom<VendorCommand> for Command {
116 type Error = Error;
117
118 fn try_from(command: VendorCommand) -> Result<Self, Self::Error> {
119 match command {
120 UPDATE => Ok(Command::Update),
121 REBOOT => Ok(Command::Reboot),
122 RNG => Ok(Command::Rng),
123 VERSION => Ok(Command::Version),
124 UUID => Ok(Command::Uuid),
125 LOCKED => Ok(Command::Locked),
126 _ => Err(Error::UnsupportedCommand),
127 }
128 }
129}
130
131enum Error {
132 InvalidLength,
133 NotAvailable,
134 UnsupportedCommand,
135}
136
137impl From<Error> for hid::Error {
138 fn from(error: Error) -> Self {
139 match error {
140 Error::InvalidLength => Self::InvalidLength,
141 Error::NotAvailable => Self::InvalidLength,
143 Error::UnsupportedCommand => Self::InvalidCommand,
144 }
145 }
146}
147
148impl From<Error> for Status {
149 fn from(error: Error) -> Self {
150 match error {
151 Error::InvalidLength => Self::WrongLength,
152 Error::NotAvailable => Self::ConditionsOfUseNotSatisfied,
153 Error::UnsupportedCommand => Self::InstructionNotSupportedOrInvalid,
154 }
155 }
156}
157
158#[derive(Debug, Deserialize)]
159struct SetConfigRequest<'a> {
160 key: &'a str,
161 value: &'a str,
162}
163
164pub trait StatusBytes {
166 type Serialized: AsRef<[u8]>;
167 fn set_random_error(&mut self, value: bool);
169 fn get_random_error(&self) -> bool;
171 fn serialize(&self) -> Self::Serialized;
173}
174
175#[derive(Clone, Copy)]
176pub struct Data {
177 pub uuid: [u8; 16],
178 pub version: u32,
179 pub full_version: &'static str,
180 pub migrations: &'static [Migrator],
181 pub reboot: fn() -> !,
183 pub reboot_to_firmware_update: fn(),
189 pub reboot_to_firmware_update_destructive: Option<fn() -> !>,
195 pub locked: fn() -> bool,
198}
199
200pub struct App<T, S, C = ()> {
201 trussed: T,
202 data: Data,
203 status: S,
204 config: C,
205}
206
207impl<T, S, C> App<T, S, C>
208where
209 T: TrussedClient,
210 S: StatusBytes,
211 C: Config,
212{
213 pub fn load_config<F: Filestore>(
215 client: T,
216 filestore: &mut F,
217 data: Data,
218 status: S,
219 ) -> Result<Self, (T, ConfigError)> {
220 match config::load(filestore) {
221 Ok(config) => Ok(Self::new(client, data, status, config)),
222 Err(err) => {
223 error!("failed to load configuration: {:?}", err);
224 Err((client, err))
225 }
226 }
227 }
228
229 pub fn migrate<F: Filestore>(
230 &mut self,
231 to_version: u32,
232 store: impl Store,
233 filestore: &mut F,
234 ) -> Result<(), ConfigError> {
235 let Some(current_version) = self.config.migration_version() else {
236 return Err(ConfigError::InvalidValue);
238 };
239
240 if current_version == to_version {
241 return Ok(());
242 }
243
244 if to_version < current_version {
245 return Err(ConfigError::InvalidValue);
246 }
247
248 let internal = store.ifs();
249 let external = store.efs();
250
251 for migration in self.data.migrations {
252 if migration.version > current_version && migration.version <= to_version {
253 (migration.migrate)(internal, external).map_err(|_err| {
254 error_now!("Migration failed: {_err:?}");
255 ConfigError::WriteFailed
256 })?;
257 }
258 }
259
260 if !self.config.set_migration_version(to_version) {
261 return Err(ConfigError::InvalidValue);
262 }
263 config::save_filestore(filestore, &self.config)
264 }
265
266 pub fn with_default_config(client: T, data: Data, status: S) -> Self {
272 Self::new(client, data, status, Default::default())
273 }
274
275 fn new(client: T, data: Data, status: S, config: C) -> Self {
276 Self {
277 trussed: client,
278 data,
279 status,
280 config,
281 }
282 }
283
284 pub fn config(&self) -> &C {
285 &self.config
286 }
287
288 pub fn config_mut(&mut self) -> &mut C {
289 &mut self.config
290 }
291
292 pub fn save_config_filestore<F: Filestore>(
293 &mut self,
294 filestore: &mut F,
295 ) -> Result<(), ConfigError> {
296 config::save_filestore(filestore, &self.config)
297 }
298
299 fn user_present(&mut self) -> bool {
300 let user_present = syscall!(self
301 .trussed
302 .confirm_user_present(USER_PRESENCE_TIMEOUT_SECS * 1000))
303 .result;
304 user_present.is_ok()
305 }
306
307 fn exec(
308 &mut self,
309 command: Command,
310 input: &[u8],
311 response: &mut VecView<u8>,
312 ) -> Result<(), Error> {
313 debug_now!("Executing command: {command:?}");
314 match command {
315 Command::Reboot => (self.data.reboot)(),
316 Command::Locked => {
317 response.push((self.data.locked)().into()).ok();
318 }
319 Command::Rng => {
320 response
322 .extend_from_slice(&syscall!(self.trussed.random_bytes(RNG_DATA_LEN)).bytes)
323 .ok();
324 }
325 Command::Update => {
326 if self.user_present() {
327 if input.first().copied() == Some(0x01) {
328 if let Some(f) = self.data.reboot_to_firmware_update_destructive {
329 f();
330 } else {
331 return Err(Error::UnsupportedCommand);
332 }
333 } else {
334 (self.data.reboot_to_firmware_update)();
335 }
336 } else {
337 return Err(Error::NotAvailable);
338 }
339 }
340 Command::Uuid => {
341 response.extend_from_slice(&self.data.uuid).ok();
343 }
344 Command::Version => {
345 if input.first().copied() == Some(0x01) {
347 response
348 .extend_from_slice(self.data.full_version.as_bytes())
349 .ok();
350 } else {
351 response
352 .extend_from_slice(&self.data.version.to_be_bytes())
353 .ok();
354 }
355 }
356 Command::Wink => {
357 debug_now!("winking");
358 syscall!(self.trussed.wink(Duration::from_secs(10)));
359 }
360 Command::Status => {
361 if !self.status.get_random_error() {
362 let is_random_working = try_syscall!(self.trussed.random_bytes(1)).is_ok();
363 self.status.set_random_error(!is_random_working);
364 }
365 response
366 .extend_from_slice(self.status.serialize().as_ref())
367 .ok();
368 }
369 Command::TestSe05X => {
370 #[cfg(feature = "se050")]
371 {
372 let rep = syscall!(self.trussed.test_se050());
373 response.extend_from_slice(&rep.reply).ok();
374 return Ok(());
375 }
376 #[cfg(not(feature = "se050"))]
377 {
378 return Err(Error::UnsupportedCommand);
379 }
380 }
381 Command::GetConfig => {
382 response.push(CONFIG_OK).ok();
384 if let Err(error) = self.get_config(input, response) {
385 response.clear();
386 response.push(error.into()).ok();
387 }
388 }
389 Command::SetConfig => {
390 let status = match self.set_config(input) {
392 Ok(()) => CONFIG_OK,
393 Err(error) => error.into(),
394 };
395 response.push(status).ok();
396 }
397 Command::ListAvailableFields => {
398 cbor_serialize_to::<_, &mut VecView<u8>>(
399 &self.config.list_available_fields(),
400 response,
401 )
402 .ok();
403 return Ok(());
404 }
405 #[cfg(feature = "factory-reset")]
406 Command::FactoryReset => {
407 debug_now!("Factory resetting the device");
408 if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result {
409 debug_now!("Failed to verify user presence: {_err:?}");
410 response.push(FACTORY_RESET_NOT_CONFIRMED).ok();
411 return Ok(());
412 }
413 syscall!(self.trussed.factory_reset_device());
414 (self.data.reboot)();
415 }
416 #[cfg(feature = "factory-reset")]
417 Command::FactoryResetApp => {
418 let Ok(client) = core::str::from_utf8(input) else {
419 response.push(FACTORY_RESET_APP_FAILED_PARSE).ok();
420 return Ok(());
421 };
422 let Ok(path) = PathBuf::try_from(client) else {
423 response.push(FACTORY_RESET_APP_FAILED_PARSE).ok();
424 return Ok(());
425 };
426
427 let Some((_, flag)) = self.config().reset_client_id(client) else {
428 response.push(FACTORY_RESET_APP_NOT_ALLOWED).ok();
429 return Ok(());
430 };
431
432 if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result {
433 debug_now!("Failed to verify user presence: {_err:?}");
434 response.push(FACTORY_RESET_NOT_CONFIRMED).ok();
435 return Ok(());
436 }
437
438 match self.config.reset_client_config(client) {
439 crate::config::ResetConfigResult::Changed => {
440 flag.set_config_changed();
441 config::save(&mut self.trussed, &self.config).map_err(|_err| {
442 error_now!("Failed to save config: {_err:?}");
443 Error::InvalidLength
444 })?;
445 syscall!(self.trussed.factory_reset_client(&path));
446 }
447 crate::config::ResetConfigResult::Unchanged => {
448 if flag.set_factory_reset() {
450 syscall!(self.trussed.factory_reset_client(&path));
451 }
452 }
453 crate::config::ResetConfigResult::WrongKey => {
454 response.push(FACTORY_RESET_APP_NOT_ALLOWED).ok();
455 return Ok(());
456 }
457 }
458
459 response.push(FACTORY_RESET_OK).ok();
460 }
461 }
462 Ok(())
463 }
464
465 fn get_config(&mut self, input: &[u8], response: &mut VecView<u8>) -> Result<(), ConfigError> {
466 let key = core::str::from_utf8(input).map_err(|_| ConfigError::InvalidKey)?;
467 config::get(&mut self.config, key, response)
468 }
469
470 fn set_config(&mut self, input: &[u8]) -> Result<(), ConfigError> {
471 let request: SetConfigRequest<'_> =
472 cbor_deserialize(input).map_err(|_| ConfigError::DeserializationFailed)?;
473 let reset_client_id = self.config.reset_client_id(request.key);
474
475 if reset_client_id.is_some() {
476 if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result {
477 debug_now!("Failed to verify user presence: {_err:?}");
478 return Err(ConfigError::NotConfirmed);
479 }
480 }
481
482 config::set(&mut self.config, request.key, request.value)?;
483 if let Some((client, signal)) = reset_client_id {
484 signal.set_config_changed();
485 syscall!(self.trussed.factory_reset_client(client));
486 }
487
488 config::save(&mut self.trussed, &self.config)
489 }
490
491 pub fn status(&self) -> &S {
492 &self.status
493 }
494
495 pub fn status_mut(&mut self) -> &mut S {
496 &mut self.status
497 }
498}
499
500impl<T, S, C> hid::App<'static> for App<T, S, C>
501where
502 T: TrussedClient,
503 S: StatusBytes,
504 C: Config,
505{
506 fn commands(&self) -> &'static [HidCommand] {
507 &[
508 HidCommand::Wink,
509 HidCommand::Vendor(ADMIN),
510 HidCommand::Vendor(UPDATE),
511 HidCommand::Vendor(REBOOT),
512 HidCommand::Vendor(RNG),
513 HidCommand::Vendor(VERSION),
514 HidCommand::Vendor(UUID),
515 HidCommand::Vendor(LOCKED),
516 ]
517 }
518
519 fn call(
520 &mut self,
521 command: HidCommand,
522 input_data: &[u8],
523 response: &mut BytesView,
524 ) -> Result<(), hid::Error> {
525 let (command, input) = if command == HidCommand::Vendor(ADMIN) {
526 let (command, input) = input_data.split_first().ok_or(Error::InvalidLength)?;
528 let command = Command::try_from(*command)?;
529 (command, input)
530 } else {
531 (Command::try_from(command)?, input_data)
533 };
534 self.exec(command, input, response.as_mut())
535 .map_err(From::from)
536 }
537
538 fn interrupt(&self) -> Option<&'static InterruptFlag> {
539 self.trussed.interrupt()
540 }
541}
542
543impl<T, S, C> iso7816::App for App<T, S, C>
544where
545 T: TrussedClient,
546 S: StatusBytes,
547{
548 fn aid(&self) -> iso7816::Aid {
550 iso7816::Aid::new(&[0xA0, 0x00, 0x00, 0x08, 0x47, 0x00, 0x00, 0x00, 0x01])
551 }
552}
553
554impl<T, S, C> apdu_app::App for App<T, S, C>
555where
556 T: TrussedClient,
557 S: StatusBytes,
558 C: Config,
559{
560 fn select(
561 &mut self,
562 _interface: Interface,
563 _apdu: CommandView<'_>,
564 _reply: &mut heapless::VecView<u8>,
565 ) -> apdu_app::Result {
566 Ok(())
567 }
568
569 fn deselect(&mut self) {}
570
571 fn call(
572 &mut self,
573 interface: Interface,
574 apdu: CommandView<'_>,
575 reply: &mut heapless::VecView<u8>,
576 ) -> apdu_app::Result {
577 let instruction: u8 = apdu.instruction().into();
578 let command = Command::try_from(instruction)?;
579
580 if command == Command::Reboot && interface != Interface::Contact {
582 return Err(Status::ConditionsOfUseNotSatisfied);
583 }
584
585 if command == Command::Update || command == Command::Version {
589 self.exec(command, &[apdu.p1], reply)
590 } else {
591 self.exec(command, apdu.data(), reply)
592 }
593 .map_err(From::from)
594 }
595}