fido/
api.rs

1// SPDX-FileCopyrightText: 2025 Foundation Devices, Inc. <hello@foundation.xyz>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4use server::{CheckedConn, CheckedPermissions, MessageAllowed};
5
6#[cfg(feature = "test-app")]
7use crate::messages::ResetState;
8use crate::messages::{
9    CreateSecurityKey, CtapProcessCbor, EditSecurityKey, GetSelectedSecurityKey, ListSecurityKeys,
10    SelectSecurityKey, SetArchived, Transport, U2fApduCommand, U2fProcessApdu,
11};
12use crate::SecurityKeyView;
13
14#[macro_export]
15macro_rules! use_api {
16    () => {
17        mod fido_permissions {
18            use fido::messages::*;
19            #[derive(Debug, Clone, Default, server::Permissions)]
20            #[server_name = "os/fido"]
21            pub struct FidoPermissions;
22        }
23        type FidoApi = fido::api::FidoApi<fido_permissions::FidoPermissions>;
24    };
25}
26
27#[derive(Debug, Default)]
28pub struct FidoApi<P: CheckedPermissions>(CheckedConn<P>);
29
30/// Stands in for `FidoApi` in unit tests, which run without a fido server to
31/// connect to. Only the ctap-hid/nfc entry points are stubbed.
32#[derive(Debug, Default)]
33pub struct FidoApiStub;
34
35impl FidoApiStub {
36    pub fn u2f_process_apdu(&self, _command: U2fApduCommand, _transport: Transport) -> Vec<u8> { Vec::new() }
37
38    pub fn ctap_process_cbor(&self, _cmd: u8, _raw: Vec<u8>) -> Vec<u8> { Vec::new() }
39}
40
41impl<P: CheckedPermissions> FidoApi<P> {
42    /* API for gui-app-security-keys application */
43    // Note: To subscribe to key changes, use:
44    //   slint_keyos_platform::subscribe_archive::<FidoPermissions, _>(SubscribeKeyChanges)
45    // This returns an async stream of KeysChangedEvent.
46
47    /// Create a new Security Key with metadata. Returns the new key index, or an error if
48    /// creation failed.
49    pub fn create_security_key(
50        &self,
51        label: String,
52        color: u8,
53        icon: String,
54    ) -> Result<usize, crate::error::FidoError>
55    where
56        P: MessageAllowed<CreateSecurityKey>,
57    {
58        self.0.send_blocking_archive(CreateSecurityKey { label, color, icon })
59    }
60
61    /// Edit a Security Key's metadata. Blocks until the server returns the validation
62    /// outcome (`EmptyLabel` / `DuplicateLabel` are surfaced here so callers don't need a
63    /// separate `validate_label` round-trip). Pass `date: 0` to leave the date unchanged.
64    pub fn edit_security_key(
65        &self,
66        index: usize,
67        label: String,
68        color: u8,
69        icon: String,
70        date: u64,
71    ) -> Result<(), crate::error::FidoError>
72    where
73        P: MessageAllowed<EditSecurityKey>,
74    {
75        self.0.send_blocking_archive(EditSecurityKey { index, label, color, icon, date })
76    }
77
78    /// Set the archived state of a Security Key. Blocks until the server confirms; returns
79    /// `FidoError::InvalidIndex` if the slot doesn't exist. Archived keys are automatically
80    /// set to live=false.
81    pub fn set_archived(&self, index: usize, archived: bool) -> Result<(), crate::error::FidoError>
82    where
83        P: MessageAllowed<SetArchived>,
84    {
85        self.0.try_send_blocking_scalar(SetArchived { index, archived })?
86    }
87
88    /// Synchronous snapshot of all security keys. Use at startup to populate local state
89    /// before the async `SubscribeKeyChanges` stream has delivered its initial event.
90    pub fn list_security_keys(&self) -> Vec<SecurityKeyView>
91    where
92        P: MessageAllowed<ListSecurityKeys>,
93    {
94        self.0.send_blocking_archive(ListSecurityKeys)
95    }
96
97    /// Get the index of the selected Security Key if any.
98    pub fn selected_security_key_index(&self) -> Result<Option<usize>, crate::error::FidoError>
99    where
100        P: MessageAllowed<GetSelectedSecurityKey>,
101    {
102        Ok(self.0.try_send_blocking_scalar(GetSelectedSecurityKey)?)
103    }
104
105    /// Select/Deselect a Security Key for Registration (fire-and-forget).
106    pub fn select_security_key(&self, index: Option<usize>)
107    where
108        P: MessageAllowed<SelectSecurityKey>,
109    {
110        self.0.try_send_scalar(SelectSecurityKey(index)).ok();
111    }
112
113    /* API for ctap-hid/nfc server */
114
115    pub fn u2f_process_apdu(&self, command: U2fApduCommand, transport: Transport) -> Vec<u8>
116    where
117        P: MessageAllowed<U2fProcessApdu>,
118    {
119        self.0.send_blocking_archive(U2fProcessApdu { command, transport })
120    }
121
122    pub fn ctap_process_cbor(&self, cmd: u8, raw: Vec<u8>) -> Vec<u8>
123    where
124        P: MessageAllowed<CtapProcessCbor>,
125    {
126        self.0.send_blocking_archive(CtapProcessCbor { cmd, raw })
127    }
128
129    /* API for Test Apps only */
130
131    #[cfg(feature = "test-app")]
132    pub fn reset_state(&mut self) -> Result<(), crate::error::FidoError>
133    where
134        P: MessageAllowed<ResetState>,
135    {
136        self.0.try_send_blocking_scalar(ResetState)?
137    }
138}