1#![cfg_attr(not(test), no_std)]
15#[macro_use]
18extern crate delog;
19generate_macros!();
20
21pub use state::migrate;
22
23use core::time::Duration;
24
25use trussed_core::{
26 mechanisms, syscall,
27 types::{
28 KeyId, KeySerialization, Location, Mechanism, SerializedKey, Signature,
29 SignatureSerialization, StorageAttributes,
30 },
31 CertificateClient, CryptoClient, FilesystemClient, ManagementClient, UiClient,
32};
33use trussed_fs_info::{FsInfoClient, FsInfoReply};
34use trussed_hkdf::HkdfClient;
35
36pub use ctap_types::Error;
38
39mod ctap1;
40mod ctap2;
41
42#[cfg(feature = "dispatch")]
43mod dispatch;
44
45pub mod constants;
46pub mod credential;
47pub mod state;
48
49pub use ctap2::large_blobs::Config as LargeBlobsConfig;
50
51pub type Result<T> = core::result::Result<T, Error>;
53
54pub trait TrussedRequirements:
63 CertificateClient
64 + CryptoClient
65 + FilesystemClient
66 + ManagementClient
67 + UiClient
68 + mechanisms::P256
69 + mechanisms::Chacha8Poly1305
70 + mechanisms::Aes256Cbc
71 + mechanisms::Sha256
72 + mechanisms::HmacSha256
73 + mechanisms::Ed255
74 + FsInfoClient
75 + HkdfClient
76 + ExtensionRequirements
77{
78}
79
80impl<T> TrussedRequirements for T where
81 T: CertificateClient
82 + CryptoClient
83 + FilesystemClient
84 + ManagementClient
85 + UiClient
86 + mechanisms::P256
87 + mechanisms::Chacha8Poly1305
88 + mechanisms::Aes256Cbc
89 + mechanisms::Sha256
90 + mechanisms::HmacSha256
91 + mechanisms::Ed255
92 + FsInfoClient
93 + HkdfClient
94 + ExtensionRequirements
95{
96}
97
98#[cfg(not(feature = "chunked"))]
99pub trait ExtensionRequirements {}
100
101#[cfg(not(feature = "chunked"))]
102impl<T> ExtensionRequirements for T {}
103
104#[cfg(feature = "chunked")]
105pub trait ExtensionRequirements: trussed_chunked::ChunkedClient {}
106
107#[cfg(feature = "chunked")]
108impl<T> ExtensionRequirements for T where T: trussed_chunked::ChunkedClient {}
109
110#[derive(Copy, Clone, Debug, Eq, PartialEq)]
111pub struct Config {
113 pub max_msg_size: usize,
116 pub skip_up_timeout: Option<Duration>,
121 pub max_resident_credential_count: Option<u32>,
123 pub large_blobs: Option<ctap2::large_blobs::Config>,
127 pub nfc_transport: bool,
129}
130
131impl Config {
132 pub fn supports_large_blobs(&self) -> bool {
133 self.large_blobs.is_some()
134 }
135}
136
137pub struct Authenticator<UP, T>
158where
161 UP: UserPresence,
162{
163 trussed: T,
164 state: state::State,
165 up: UP,
166 config: Config,
167}
168
169fn format_hex<'a>(data: &[u8], buffer: &'a mut [u8]) -> &'a str {
171 const HEX_CHARS: &[u8] = b"0123456789abcdef";
172 assert!(data.len() * 2 >= buffer.len());
173 for (idx, byte) in data.iter().enumerate() {
174 buffer[idx * 2] = HEX_CHARS[(byte >> 4) as usize];
175 buffer[idx * 2 + 1] = HEX_CHARS[(byte & 0xf) as usize];
176 }
177
178 unsafe { core::str::from_utf8_unchecked(&buffer[0..data.len() * 2]) }
180}
181
182#[inline]
195#[allow(dead_code)]
196pub(crate) fn msp() -> u32 {
197 0x2000_0000
198}
199
200#[derive(Copy, Clone, Debug, Eq, PartialEq)]
202#[repr(i32)]
203#[non_exhaustive]
204pub enum SigningAlgorithm {
205 Ed25519 = -8,
207 P256 = -7,
209}
210
211impl SigningAlgorithm {
212 pub fn mechanism(&self) -> Mechanism {
213 match self {
214 Self::Ed25519 => Mechanism::Ed255,
215 Self::P256 => Mechanism::P256,
216 }
217 }
218
219 pub fn signature_serialization(&self) -> SignatureSerialization {
220 match self {
221 Self::Ed25519 => SignatureSerialization::Raw,
222 Self::P256 => SignatureSerialization::Asn1Der,
223 }
224 }
225
226 pub fn generate_private_key<C: CryptoClient>(
227 &self,
228 trussed: &mut C,
229 location: Location,
230 ) -> KeyId {
231 syscall!(trussed.generate_key(
232 self.mechanism(),
233 StorageAttributes::new().set_persistence(location)
234 ))
235 .key
236 }
237
238 pub fn derive_public_key<C: CryptoClient>(
239 &self,
240 trussed: &mut C,
241 private_key: KeyId,
242 ) -> SerializedKey {
243 let mechanism = self.mechanism();
244 let public_key = syscall!(trussed.derive_key(
245 mechanism,
246 private_key,
247 None,
248 StorageAttributes::new().set_persistence(Location::Volatile)
249 ))
250 .key;
251 let cose_public_key =
252 syscall!(trussed.serialize_key(mechanism, public_key, KeySerialization::Cose))
253 .serialized_key;
254 if !syscall!(trussed.delete(public_key)).success {
255 error!("failed to delete credential public key");
256 }
257 cose_public_key
258 }
259
260 pub fn sign<C: CryptoClient>(&self, trussed: &mut C, key: KeyId, data: &[u8]) -> Signature {
261 syscall!(trussed.sign(self.mechanism(), key, data, self.signature_serialization()))
262 .signature
263 }
264}
265
266impl From<SigningAlgorithm> for i32 {
267 fn from(alg: SigningAlgorithm) -> Self {
268 match alg {
269 SigningAlgorithm::P256 => -7,
270 SigningAlgorithm::Ed25519 => -8,
271 }
272 }
273}
274
275impl TryFrom<i32> for SigningAlgorithm {
276 type Error = Error;
277
278 fn try_from(alg: i32) -> Result<Self> {
279 Ok(match alg {
280 -7 => SigningAlgorithm::P256,
281 -8 => SigningAlgorithm::Ed25519,
282 _ => return Err(Error::UnsupportedAlgorithm),
283 })
284 }
285}
286
287pub trait UserPresence: Copy {
289 fn user_present<T: TrussedRequirements>(
290 self,
291 trussed: &mut T,
292 timeout_milliseconds: u32,
293 ) -> Result<()>;
294}
295
296#[deprecated(note = "use `Silent` directly`")]
297#[doc(hidden)]
298pub type SilentAuthenticator = Silent;
299
300#[derive(Copy, Clone)]
302pub struct Silent {}
303
304impl UserPresence for Silent {
305 fn user_present<T: TrussedRequirements>(self, _: &mut T, _: u32) -> Result<()> {
306 Ok(())
307 }
308}
309
310#[deprecated(note = "use `Conforming` directly")]
311#[doc(hidden)]
312pub type NonSilentAuthenticator = Conforming;
313
314#[derive(Copy, Clone)]
316pub struct Conforming {}
317
318impl UserPresence for Conforming {
319 fn user_present<T: TrussedRequirements>(
320 self,
321 trussed: &mut T,
322 timeout_milliseconds: u32,
323 ) -> Result<()> {
324 let result = syscall!(trussed.confirm_user_present(timeout_milliseconds)).result;
325 result.map_err(|err| match err {
326 trussed_core::types::consent::Error::TimedOut => Error::UserActionTimeout,
327 trussed_core::types::consent::Error::Interrupted => Error::KeepaliveCancel,
328 _ => Error::OperationDenied,
329 })
330 }
331}
332
333impl<UP, T> Authenticator<UP, T>
334where
335 UP: UserPresence,
336 T: TrussedRequirements,
337{
338 pub fn new(trussed: T, up: UP, config: Config) -> Self {
339 let state = state::State::new();
340 Self {
341 trussed,
342 state,
343 up,
344 config,
345 }
346 }
347
348 fn estimate_remaining_inner(info: &FsInfoReply) -> Option<u32> {
349 let block_size = info.block_info.as_ref()?.size;
350 let size_taken = 2 * block_size + 400;
352 Some((info.available_space.saturating_sub(5 * block_size) / size_taken) as u32)
354 }
355
356 fn estimate_remaining(&mut self) -> Option<u32> {
357 let info = syscall!(self.trussed.fs_info(Location::Internal));
358 debug!("Got filesystem info: {info:?}");
359 Self::estimate_remaining_inner(&info)
360 }
361
362 fn can_fit_inner(info: &FsInfoReply, size: usize) -> Option<bool> {
363 let block_size = info.block_info.as_ref()?.size;
364 let size_taken = 6 * block_size + size + 50;
366 Some(size_taken < info.available_space)
367 }
368
369 fn can_fit(&mut self, size: usize) -> Option<bool> {
373 debug!("Can fit for {size} bytes");
374 let info = syscall!(self.trussed.fs_info(Location::Internal));
375 debug!("Got filesystem info: {info:?}");
376 debug!(
377 "Available storage: {:?}",
378 Self::estimate_remaining_inner(&info)
379 );
380 Self::can_fit_inner(&info, size)
381 }
382
383 fn hash(&mut self, data: &[u8]) -> [u8; 32] {
384 let hash = syscall!(self.trussed.hash_sha256(data)).hash;
385 hash.as_slice().try_into().expect("hash should fit")
386 }
387
388 fn nonce(&mut self) -> [u8; 12] {
389 let bytes = syscall!(self.trussed.random_bytes(12)).bytes;
390 bytes.as_slice().try_into().expect("hash should fit")
391 }
392
393 fn skip_up_check(&mut self) -> bool {
394 if let Some(timeout) = self.config.skip_up_timeout.take() {
397 let uptime = syscall!(self.trussed.uptime()).uptime;
398 if uptime < timeout {
399 info_now!("skip up check directly after boot");
400 return true;
401 }
402 }
403 false
404 }
405}
406
407#[cfg(test)]
408mod test {
409 use super::*;
410
411 #[test]
412 fn hex() {
413 let data = [0x01, 0x02, 0xB1, 0xA1];
414 let buffer = &mut [0; 8];
415 assert_eq!(format_hex(&data, buffer), "0102b1a1");
416 assert_eq!(buffer, b"0102b1a1");
417 }
418}