1use super::Client as TrussedClient;
2use apdu_app::{CommandView, Interface};
3use cbor_smol::{cbor_deserialize, cbor_serialize_to};
4use core::{convert::TryInto, marker::PhantomData, 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 Reboot {
165 fn reboot() -> !;
167
168 fn reboot_to_firmware_update() -> !;
174
175 fn reboot_to_firmware_update_destructive() -> !;
181
182 fn locked() -> bool;
185}
186
187pub trait StatusBytes {
189 type Serialized: AsRef<[u8]>;
190 fn set_random_error(&mut self, value: bool);
192 fn get_random_error(&self) -> bool;
194 fn serialize(&self) -> Self::Serialized;
196}
197
198pub struct App<T, R, S, C = ()> {
199 trussed: T,
200 uuid: [u8; 16],
201 version: u32,
202 full_version: &'static str,
203 status: S,
204 boot_interface: PhantomData<R>,
205 config: C,
206 migrations: &'static [Migrator],
207}
208
209impl<T, R, S, C> App<T, R, S, C>
210where
211 T: TrussedClient,
212 R: Reboot,
213 S: StatusBytes,
214 C: Config,
215{
216 pub fn load_config<F: Filestore>(
218 client: T,
219 filestore: &mut F,
220 uuid: [u8; 16],
221 version: u32,
222 full_version: &'static str,
223 status: S,
224 migrations: &'static [Migrator],
225 ) -> Result<Self, (T, ConfigError)> {
226 match config::load(filestore) {
227 Ok(config) => Ok(Self::new(
228 client,
229 uuid,
230 version,
231 full_version,
232 status,
233 config,
234 migrations,
235 )),
236 Err(err) => {
237 error!("failed to load configuration: {:?}", err);
238 Err((client, err))
239 }
240 }
241 }
242
243 pub fn migrate<F: Filestore>(
244 &mut self,
245 to_version: u32,
246 store: impl Store,
247 filestore: &mut F,
248 ) -> Result<(), ConfigError> {
249 let Some(current_version) = self.config.migration_version() else {
250 return Err(ConfigError::InvalidValue);
252 };
253
254 if current_version == to_version {
255 return Ok(());
256 }
257
258 if to_version < current_version {
259 return Err(ConfigError::InvalidValue);
260 }
261
262 let internal = store.ifs();
263 let external = store.efs();
264
265 for migration in self.migrations {
266 if migration.version > current_version && migration.version <= to_version {
267 (migration.migrate)(internal, external).map_err(|_err| {
268 error_now!("Migration failed: {_err:?}");
269 ConfigError::WriteFailed
270 })?;
271 }
272 }
273
274 if !self.config.set_migration_version(to_version) {
275 return Err(ConfigError::InvalidValue);
276 }
277 config::save_filestore(filestore, &self.config)
278 }
279
280 pub fn with_default_config(
286 client: T,
287 uuid: [u8; 16],
288 version: u32,
289 full_version: &'static str,
290 status: S,
291 migrations: &'static [Migrator],
292 ) -> Self {
293 Self::new(
294 client,
295 uuid,
296 version,
297 full_version,
298 status,
299 Default::default(),
300 migrations,
301 )
302 }
303
304 fn new(
305 client: T,
306 uuid: [u8; 16],
307 version: u32,
308 full_version: &'static str,
309 status: S,
310 config: C,
311 migrations: &'static [Migrator],
312 ) -> Self {
313 Self {
314 trussed: client,
315 uuid,
316 version,
317 full_version,
318 status,
319 boot_interface: PhantomData,
320 config,
321 migrations,
322 }
323 }
324
325 pub fn config(&self) -> &C {
326 &self.config
327 }
328
329 pub fn config_mut(&mut self) -> &mut C {
330 &mut self.config
331 }
332
333 pub fn save_config_filestore<F: Filestore>(
334 &mut self,
335 filestore: &mut F,
336 ) -> Result<(), ConfigError> {
337 config::save_filestore(filestore, &self.config)
338 }
339
340 fn user_present(&mut self) -> bool {
341 let user_present = syscall!(self
342 .trussed
343 .confirm_user_present(USER_PRESENCE_TIMEOUT_SECS * 1000))
344 .result;
345 user_present.is_ok()
346 }
347
348 fn exec(
349 &mut self,
350 command: Command,
351 input: &[u8],
352 response: &mut VecView<u8>,
353 ) -> Result<(), Error> {
354 debug_now!("Executing command: {command:?}");
355 match command {
356 Command::Reboot => R::reboot(),
357 Command::Locked => {
358 response.push(R::locked().into()).ok();
359 }
360 Command::Rng => {
361 response
363 .extend_from_slice(&syscall!(self.trussed.random_bytes(RNG_DATA_LEN)).bytes)
364 .ok();
365 }
366 Command::Update => {
367 if self.user_present() {
368 if input.first().copied() == Some(0x01) {
369 R::reboot_to_firmware_update_destructive();
370 } else {
371 R::reboot_to_firmware_update();
372 }
373 } else {
374 return Err(Error::NotAvailable);
375 }
376 }
377 Command::Uuid => {
378 response.extend_from_slice(&self.uuid).ok();
380 }
381 Command::Version => {
382 if input.first().copied() == Some(0x01) {
384 response
385 .extend_from_slice(self.full_version.as_bytes())
386 .ok();
387 } else {
388 response.extend_from_slice(&self.version.to_be_bytes()).ok();
389 }
390 }
391 Command::Wink => {
392 debug_now!("winking");
393 syscall!(self.trussed.wink(Duration::from_secs(10)));
394 }
395 Command::Status => {
396 if !self.status.get_random_error() {
397 let is_random_working = try_syscall!(self.trussed.random_bytes(1)).is_ok();
398 self.status.set_random_error(!is_random_working);
399 }
400 response
401 .extend_from_slice(self.status.serialize().as_ref())
402 .ok();
403 }
404 Command::TestSe05X => {
405 #[cfg(feature = "se050")]
406 {
407 let rep = syscall!(self.trussed.test_se050());
408 response.extend_from_slice(&rep.reply).ok();
409 return Ok(());
410 }
411 #[cfg(not(feature = "se050"))]
412 {
413 return Err(Error::UnsupportedCommand);
414 }
415 }
416 Command::GetConfig => {
417 response.push(CONFIG_OK).ok();
419 if let Err(error) = self.get_config(input, response) {
420 response.clear();
421 response.push(error.into()).ok();
422 }
423 }
424 Command::SetConfig => {
425 let status = match self.set_config(input) {
427 Ok(()) => CONFIG_OK,
428 Err(error) => error.into(),
429 };
430 response.push(status).ok();
431 }
432 Command::ListAvailableFields => {
433 cbor_serialize_to::<_, &mut VecView<u8>>(
434 &self.config.list_available_fields(),
435 response,
436 )
437 .ok();
438 return Ok(());
439 }
440 #[cfg(feature = "factory-reset")]
441 Command::FactoryReset => {
442 debug_now!("Factory resetting the device");
443 if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result {
444 debug_now!("Failed to verify user presence: {_err:?}");
445 response.push(FACTORY_RESET_NOT_CONFIRMED).ok();
446 return Ok(());
447 }
448 syscall!(self.trussed.factory_reset_device());
449 R::reboot();
450 }
451 #[cfg(feature = "factory-reset")]
452 Command::FactoryResetApp => {
453 let Ok(client) = core::str::from_utf8(input) else {
454 response.push(FACTORY_RESET_APP_FAILED_PARSE).ok();
455 return Ok(());
456 };
457 let Ok(path) = PathBuf::try_from(client) else {
458 response.push(FACTORY_RESET_APP_FAILED_PARSE).ok();
459 return Ok(());
460 };
461
462 let Some((_, flag)) = self.config().reset_client_id(client) else {
463 response.push(FACTORY_RESET_APP_NOT_ALLOWED).ok();
464 return Ok(());
465 };
466
467 if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result {
468 debug_now!("Failed to verify user presence: {_err:?}");
469 response.push(FACTORY_RESET_NOT_CONFIRMED).ok();
470 return Ok(());
471 }
472
473 match self.config.reset_client_config(client) {
474 crate::config::ResetConfigResult::Changed => {
475 flag.set_config_changed();
476 config::save(&mut self.trussed, &self.config).map_err(|_err| {
477 error_now!("Failed to save config: {_err:?}");
478 Error::InvalidLength
479 })?;
480 syscall!(self.trussed.factory_reset_client(&path));
481 }
482 crate::config::ResetConfigResult::Unchanged => {
483 if flag.set_factory_reset() {
485 syscall!(self.trussed.factory_reset_client(&path));
486 }
487 }
488 crate::config::ResetConfigResult::WrongKey => {
489 response.push(FACTORY_RESET_APP_NOT_ALLOWED).ok();
490 return Ok(());
491 }
492 }
493
494 response.push(FACTORY_RESET_OK).ok();
495 }
496 }
497 Ok(())
498 }
499
500 fn get_config(&mut self, input: &[u8], response: &mut VecView<u8>) -> Result<(), ConfigError> {
501 let key = core::str::from_utf8(input).map_err(|_| ConfigError::InvalidKey)?;
502 config::get(&mut self.config, key, response)
503 }
504
505 fn set_config(&mut self, input: &[u8]) -> Result<(), ConfigError> {
506 let request: SetConfigRequest<'_> =
507 cbor_deserialize(input).map_err(|_| ConfigError::DeserializationFailed)?;
508 let reset_client_id = self.config.reset_client_id(request.key);
509
510 if reset_client_id.is_some() {
511 if let Err(_err) = syscall!(self.trussed.confirm_user_present(15 * 1000)).result {
512 debug_now!("Failed to verify user presence: {_err:?}");
513 return Err(ConfigError::NotConfirmed);
514 }
515 }
516
517 config::set(&mut self.config, request.key, request.value)?;
518 if let Some((client, signal)) = reset_client_id {
519 signal.set_config_changed();
520 syscall!(self.trussed.factory_reset_client(client));
521 }
522
523 config::save(&mut self.trussed, &self.config)
524 }
525
526 pub fn status(&self) -> &S {
527 &self.status
528 }
529
530 pub fn status_mut(&mut self) -> &mut S {
531 &mut self.status
532 }
533}
534
535impl<T, R, S, C> hid::App<'static> for App<T, R, S, C>
536where
537 T: TrussedClient,
538 R: Reboot,
539 S: StatusBytes,
540 C: Config,
541{
542 fn commands(&self) -> &'static [HidCommand] {
543 &[
544 HidCommand::Wink,
545 HidCommand::Vendor(ADMIN),
546 HidCommand::Vendor(UPDATE),
547 HidCommand::Vendor(REBOOT),
548 HidCommand::Vendor(RNG),
549 HidCommand::Vendor(VERSION),
550 HidCommand::Vendor(UUID),
551 HidCommand::Vendor(LOCKED),
552 ]
553 }
554
555 fn call(
556 &mut self,
557 command: HidCommand,
558 input_data: &[u8],
559 response: &mut BytesView,
560 ) -> Result<(), hid::Error> {
561 let (command, input) = if command == HidCommand::Vendor(ADMIN) {
562 let (command, input) = input_data.split_first().ok_or(Error::InvalidLength)?;
564 let command = Command::try_from(*command)?;
565 (command, input)
566 } else {
567 (Command::try_from(command)?, input_data)
569 };
570 self.exec(command, input, response.as_mut())
571 .map_err(From::from)
572 }
573
574 fn interrupt(&self) -> Option<&'static InterruptFlag> {
575 self.trussed.interrupt()
576 }
577}
578
579impl<T, R, S, C> iso7816::App for App<T, R, S, C>
580where
581 T: TrussedClient,
582 R: Reboot,
583 S: StatusBytes,
584{
585 fn aid(&self) -> iso7816::Aid {
587 iso7816::Aid::new(&[0xA0, 0x00, 0x00, 0x08, 0x47, 0x00, 0x00, 0x00, 0x01])
588 }
589}
590
591impl<T, R, S, C> apdu_app::App for App<T, R, S, C>
592where
593 T: TrussedClient,
594 R: Reboot,
595 S: StatusBytes,
596 C: Config,
597{
598 fn select(
599 &mut self,
600 _interface: Interface,
601 _apdu: CommandView<'_>,
602 _reply: &mut heapless::VecView<u8>,
603 ) -> apdu_app::Result {
604 Ok(())
605 }
606
607 fn deselect(&mut self) {}
608
609 fn call(
610 &mut self,
611 interface: Interface,
612 apdu: CommandView<'_>,
613 reply: &mut heapless::VecView<u8>,
614 ) -> apdu_app::Result {
615 let instruction: u8 = apdu.instruction().into();
616 let command = Command::try_from(instruction)?;
617
618 if command == Command::Reboot && interface != Interface::Contact {
620 return Err(Status::ConditionsOfUseNotSatisfied);
621 }
622
623 if command == Command::Update || command == Command::Version {
627 self.exec(command, &[apdu.p1], reply)
628 } else {
629 self.exec(command, apdu.data(), reply)
630 }
631 .map_err(From::from)
632 }
633}