1use core::cmp::Ordering;
4
5use serde::Serialize;
6use serde_bytes::ByteArray;
7use trussed_core::{
8 mechanisms::{Chacha8Poly1305, Sha256},
9 syscall, try_syscall,
10 types::{EncryptedData, KeyId},
11 CryptoClient, FilesystemClient,
12};
13
14pub(crate) use ctap_types::{
15 ctap2::credential_management::CredentialProtectionPolicy,
17 sizes::*,
18 webauthn::{
19 PublicKeyCredentialDescriptor, PublicKeyCredentialDescriptorRef,
20 PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity,
21 },
22 Bytes,
23 String,
24};
25
26use crate::{Authenticator, Error, Result, UserPresence};
27
28#[derive(Copy, Clone, Debug, serde::Deserialize, serde::Serialize)]
32pub enum CtapVersion {
33 U2fV2,
34 Fido20,
35 Fido21Pre,
36}
37
38#[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)]
40pub struct CredentialId(pub Bytes<MAX_CREDENTIAL_ID_LENGTH>);
41
42impl CredentialId {
43 fn new<T: Chacha8Poly1305, C: Serialize>(
44 trussed: &mut T,
45 credential: &C,
46 key_encryption_key: KeyId,
47 rp_id_hash: &[u8; 32],
48 nonce: &[u8; 12],
49 ) -> Result<Self> {
50 let mut serialized_credential = SerializedCredential::new();
51 cbor_smol::cbor_serialize_to(credential, &mut serialized_credential)
52 .map_err(|_| Error::Other)?;
53 let message = &serialized_credential;
54 let associated_data = &rp_id_hash[..];
56 let encrypted_serialized_credential = syscall!(trussed.encrypt_chacha8poly1305(
57 key_encryption_key,
58 message,
59 associated_data,
60 Some(nonce)
61 ));
62 let mut credential_id = Bytes::new();
63 cbor_smol::cbor_serialize_to(
64 &EncryptedData::from(encrypted_serialized_credential),
65 &mut credential_id,
66 )
67 .map_err(|_| Error::RequestTooLarge)?;
68 Ok(Self(credential_id))
69 }
70}
71
72struct CredentialIdRef<'a>(&'a [u8]);
73
74impl CredentialIdRef<'_> {
75 fn deserialize(&self) -> Result<EncryptedData> {
76 cbor_smol::cbor_deserialize(self.0).map_err(|_| Error::InvalidCredential)
77 }
78}
79
80pub(crate) type SerializedCredential = trussed_core::types::Message;
84
85#[derive(Clone, Debug, PartialEq, serde::Deserialize, serde::Serialize)]
90pub enum Key {
91 ResidentKey(KeyId),
92 WrappedKey(Bytes<128>),
94}
95
96#[derive(Clone, Debug)]
106#[allow(clippy::large_enum_variant)]
107pub enum Credential {
108 Full(FullCredential),
109 Stripped(StrippedCredential),
110}
111
112impl Credential {
113 pub fn try_from<UP: UserPresence, T: CryptoClient + Chacha8Poly1305 + FilesystemClient>(
114 authnr: &mut Authenticator<UP, T>,
115 rp_id_hash: &[u8; 32],
116 descriptor: &PublicKeyCredentialDescriptorRef,
117 ) -> Result<Self> {
118 Self::try_from_bytes(authnr, rp_id_hash, descriptor.id)
119 }
120
121 pub fn try_from_bytes<
122 UP: UserPresence,
123 T: CryptoClient + Chacha8Poly1305 + FilesystemClient,
124 >(
125 authnr: &mut Authenticator<UP, T>,
126 rp_id_hash: &[u8; 32],
127 id: &[u8],
128 ) -> Result<Self> {
129 let encrypted_serialized = CredentialIdRef(id).deserialize()?;
130
131 let kek = authnr
132 .state
133 .persistent
134 .key_encryption_key(&mut authnr.trussed)?;
135
136 let serialized = try_syscall!(authnr.trussed.decrypt_chacha8poly1305(
137 kek,
138 &encrypted_serialized.ciphertext,
139 &rp_id_hash[..],
140 &encrypted_serialized.nonce,
141 &encrypted_serialized.tag,
142 ))
143 .map_err(|_| Error::InvalidCredential)?
144 .plaintext
145 .ok_or(Error::InvalidCredential)?;
146
147 FullCredential::deserialize(&serialized)
150 .map(Self::Full)
151 .or_else(|_| StrippedCredential::deserialize(&serialized).map(Self::Stripped))
152 .map_err(|_| Error::InvalidCredential)
153 }
154
155 pub fn id<T: Chacha8Poly1305 + Sha256>(
156 &self,
157 trussed: &mut T,
158 key_encryption_key: KeyId,
159 rp_id_hash: &[u8; 32],
160 ) -> Result<CredentialId> {
161 match self {
162 Self::Full(credential) => credential.id(trussed, key_encryption_key, Some(rp_id_hash)),
163 Self::Stripped(credential) => CredentialId::new(
164 trussed,
165 credential,
166 key_encryption_key,
167 rp_id_hash,
168 &credential.nonce,
169 ),
170 }
171 }
172
173 pub fn algorithm(&self) -> i32 {
174 match self {
175 Self::Full(credential) => credential.algorithm,
176 Self::Stripped(credential) => credential.algorithm,
177 }
178 }
179
180 pub fn cred_protect(&self) -> Option<CredentialProtectionPolicy> {
181 match self {
182 Self::Full(credential) => credential.cred_protect,
183 Self::Stripped(credential) => credential.cred_protect,
184 }
185 }
186
187 pub fn key(&self) -> &Key {
188 match self {
189 Self::Full(credential) => &credential.key,
190 Self::Stripped(credential) => &credential.key,
191 }
192 }
193
194 pub fn third_party_payment(&self) -> Option<bool> {
195 match self {
196 Self::Full(credential) => credential.data.third_party_payment,
197 Self::Stripped(credential) => credential.third_party_payment,
198 }
199 }
200}
201
202fn deserialize_bytes<E: serde::de::Error, const N: usize>(
203 s: &[u8],
204) -> core::result::Result<Bytes<N>, E> {
205 Bytes::try_from(s).map_err(|_| E::invalid_length(s.len(), &"a fixed-size sequence of bytes"))
206}
207
208fn deserialize_str<E: serde::de::Error, const N: usize>(
209 s: &str,
210) -> core::result::Result<String<N>, E> {
211 s.try_into()
212 .map_err(|_| E::custom("Serialized string doesn't fit "))
213}
214
215#[derive(Clone, Copy, Debug, PartialEq)]
216pub enum SerializationFormat {
217 Short,
218 Long,
219}
220
221#[derive(Clone, Debug, PartialEq)]
222pub struct Rp {
223 format: SerializationFormat,
224 inner: PublicKeyCredentialRpEntity,
225}
226
227impl Rp {
228 fn new(inner: PublicKeyCredentialRpEntity) -> Self {
229 Self {
230 format: SerializationFormat::Short,
231 inner,
232 }
233 }
234
235 fn raw(&self) -> RawRp<'_> {
236 let mut raw = RawRp::default();
237 match self.format {
238 SerializationFormat::Short => {
239 raw.i = Some(&self.inner.id);
240 raw.n = self.inner.name.as_deref();
241 }
242 SerializationFormat::Long => {
243 raw.id = Some(&self.inner.id);
244 raw.name = self.inner.name.as_deref();
245 }
246 }
247 raw
248 }
249
250 pub fn id(&self) -> &str {
251 &self.inner.id
252 }
253}
254
255impl AsRef<PublicKeyCredentialRpEntity> for Rp {
256 fn as_ref(&self) -> &PublicKeyCredentialRpEntity {
257 &self.inner
258 }
259}
260
261impl AsMut<PublicKeyCredentialRpEntity> for Rp {
262 fn as_mut(&mut self) -> &mut PublicKeyCredentialRpEntity {
263 &mut self.inner
264 }
265}
266
267impl<'de> serde::Deserialize<'de> for Rp {
268 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
269 where
270 D: serde::Deserializer<'de>,
271 {
272 use serde::de::Error as _;
273
274 let r = RawRp::deserialize(deserializer)?;
275
276 if r.i.is_some() && r.id.is_some() {
277 return Err(D::Error::duplicate_field("i"));
278 }
279
280 let (format, id, name) = if let Some(i) = r.i {
281 if r.name.is_some() {
282 return Err(D::Error::unknown_field("name", &["i", "n"]));
283 }
284 (SerializationFormat::Short, i, r.n)
285 } else if let Some(id) = r.id {
286 if r.n.is_some() {
287 return Err(D::Error::unknown_field("n", &["id", "name"]));
288 }
289 (SerializationFormat::Long, id, r.name)
290 } else {
291 return Err(D::Error::missing_field("i"));
292 };
293
294 let inner = PublicKeyCredentialRpEntity {
295 id: deserialize_str(id)?,
296 name: name.map(deserialize_str).transpose()?,
297 icon: None,
298 };
299 Ok(Self { format, inner })
300 }
301}
302
303impl serde::Serialize for Rp {
304 fn serialize<S: serde::Serializer>(
305 &self,
306 serializer: S,
307 ) -> core::result::Result<S::Ok, S::Error> {
308 self.raw().serialize(serializer)
309 }
310}
311
312impl From<Rp> for PublicKeyCredentialRpEntity {
313 fn from(rp: Rp) -> PublicKeyCredentialRpEntity {
314 rp.inner
315 }
316}
317
318#[derive(Default, serde::Deserialize, serde::Serialize)]
319struct RawRp<'a> {
320 #[serde(skip_serializing_if = "Option::is_none")]
321 i: Option<&'a str>,
322 #[serde(skip_serializing_if = "Option::is_none")]
323 id: Option<&'a str>,
324 #[serde(skip_serializing_if = "Option::is_none")]
325 n: Option<&'a str>,
326 #[serde(skip_serializing_if = "Option::is_none")]
327 name: Option<&'a str>,
328}
329
330#[derive(Clone, Debug, PartialEq)]
331pub struct User {
332 format: SerializationFormat,
333 inner: PublicKeyCredentialUserEntity,
334}
335
336impl User {
337 fn new(inner: PublicKeyCredentialUserEntity) -> Self {
338 Self {
339 format: SerializationFormat::Short,
340 inner,
341 }
342 }
343
344 fn raw(&self) -> RawUser<'_> {
345 let mut raw = RawUser::default();
346 match self.format {
347 SerializationFormat::Short => {
348 raw.i = Some(self.inner.id.as_slice().into());
349 raw.ii = self.inner.icon.as_deref();
350 raw.n = self.inner.name.as_deref();
351 raw.d = self.inner.display_name.as_deref();
352 }
353 SerializationFormat::Long => {
354 raw.id = Some(self.inner.id.as_slice().into());
355 raw.icon = self.inner.icon.as_deref();
356 raw.name = self.inner.name.as_deref();
357 raw.display_name = self.inner.display_name.as_deref();
358 }
359 }
360 raw
361 }
362
363 pub fn id(&self) -> &Bytes<64> {
364 &self.inner.id
365 }
366}
367
368impl AsRef<PublicKeyCredentialUserEntity> for User {
369 fn as_ref(&self) -> &PublicKeyCredentialUserEntity {
370 &self.inner
371 }
372}
373
374impl AsMut<PublicKeyCredentialUserEntity> for User {
375 fn as_mut(&mut self) -> &mut PublicKeyCredentialUserEntity {
376 &mut self.inner
377 }
378}
379
380impl<'de> serde::Deserialize<'de> for User {
381 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
382 where
383 D: serde::Deserializer<'de>,
384 {
385 use serde::de::Error as _;
386
387 let u = RawUser::deserialize(deserializer)?;
388
389 if u.i.is_some() && u.id.is_some() {
390 return Err(D::Error::duplicate_field("i"));
391 }
392
393 let (format, id, icon, name, display_name) = if let Some(i) = u.i {
394 let fields = &["i", "I", "n", "d"];
396 if u.icon.is_some() {
397 return Err(D::Error::unknown_field("icon", fields));
398 }
399 if u.name.is_some() {
400 return Err(D::Error::unknown_field("name", fields));
401 }
402 if u.display_name.is_some() {
403 return Err(D::Error::unknown_field("display_name", fields));
404 }
405
406 (SerializationFormat::Short, i, u.ii, u.n, u.d)
407 } else if let Some(id) = u.id {
408 let fields = &["id", "icon", "name", "display_name"];
410 if u.ii.is_some() {
411 return Err(D::Error::unknown_field("ii", fields));
412 }
413 if u.n.is_some() {
414 return Err(D::Error::unknown_field("n", fields));
415 }
416 if u.d.is_some() {
417 return Err(D::Error::unknown_field("d", fields));
418 }
419
420 (
421 SerializationFormat::Long,
422 id,
423 u.icon,
424 u.name,
425 u.display_name,
426 )
427 } else {
428 return Err(D::Error::missing_field("i"));
430 };
431
432 let inner = PublicKeyCredentialUserEntity {
433 id: deserialize_bytes(id)?,
434 icon: icon.map(deserialize_str).transpose()?,
435 name: name.map(deserialize_str).transpose()?,
436 display_name: display_name.map(deserialize_str).transpose()?,
437 };
438 Ok(Self { format, inner })
439 }
440}
441
442impl serde::Serialize for User {
443 fn serialize<S: serde::Serializer>(
444 &self,
445 serializer: S,
446 ) -> core::result::Result<S::Ok, S::Error> {
447 self.raw().serialize(serializer)
448 }
449}
450
451impl From<User> for PublicKeyCredentialUserEntity {
452 fn from(user: User) -> PublicKeyCredentialUserEntity {
453 user.inner
454 }
455}
456
457#[derive(Default, serde::Deserialize, serde::Serialize)]
458struct RawUser<'a> {
459 #[serde(skip_serializing_if = "Option::is_none")]
460 i: Option<&'a serde_bytes::Bytes>,
461 #[serde(skip_serializing_if = "Option::is_none")]
462 id: Option<&'a serde_bytes::Bytes>,
463 #[serde(skip_serializing_if = "Option::is_none", rename = "I")]
464 ii: Option<&'a str>,
465 #[serde(skip_serializing_if = "Option::is_none")]
466 icon: Option<&'a str>,
467 #[serde(skip_serializing_if = "Option::is_none")]
468 n: Option<&'a str>,
469 #[serde(skip_serializing_if = "Option::is_none")]
470 name: Option<&'a str>,
471 #[serde(skip_serializing_if = "Option::is_none")]
472 d: Option<&'a str>,
473 #[serde(skip_serializing_if = "Option::is_none", rename = "displayName")]
474 display_name: Option<&'a str>,
475}
476
477#[derive(
479 Clone, Debug, PartialEq, serde_indexed::DeserializeIndexed, serde_indexed::SerializeIndexed,
480)]
481pub struct CredentialData {
482 pub rp: Rp,
484 pub user: User,
486
487 pub creation_time: u32,
489 use_counter: bool,
491 pub algorithm: i32,
493 pub key: Key,
499
500 #[serde(skip_serializing_if = "Option::is_none")]
502 pub hmac_secret: Option<bool>,
503 #[serde(skip_serializing_if = "Option::is_none")]
504 pub cred_protect: Option<CredentialProtectionPolicy>,
505 #[serde(skip_serializing_if = "Option::is_none")]
513 use_short_id: Option<bool>,
514
515 #[serde(skip_serializing_if = "Option::is_none")]
517 pub large_blob_key: Option<ByteArray<32>>,
518
519 #[serde(skip_serializing_if = "Option::is_none")]
520 pub third_party_payment: Option<bool>,
521}
522
523#[derive(Clone, Debug, serde_indexed::DeserializeIndexed, serde_indexed::SerializeIndexed)]
527pub struct FullCredential {
528 ctap: CtapVersion,
529 pub data: CredentialData,
530 nonce: ByteArray<12>,
531}
532
533impl core::ops::Deref for FullCredential {
542 type Target = CredentialData;
543
544 fn deref(&self) -> &Self::Target {
545 &self.data
546 }
547}
548
549impl PartialEq for FullCredential {
553 fn eq(&self, other: &Self) -> bool {
554 (self.creation_time == other.creation_time) && (self.key == other.key)
555 }
556}
557
558impl PartialEq<&FullCredential> for FullCredential {
559 fn eq(&self, other: &&Self) -> bool {
560 self == *other
561 }
562}
563
564impl Eq for FullCredential {}
565
566impl Ord for FullCredential {
567 fn cmp(&self, other: &Self) -> Ordering {
568 self.data.creation_time.cmp(&other.data.creation_time)
569 }
570}
571
572impl PartialOrd for FullCredential {
574 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
575 Some(self.cmp(other))
576 }
577}
578
579impl PartialOrd<&FullCredential> for FullCredential {
580 fn partial_cmp(&self, other: &&Self) -> Option<Ordering> {
581 Some(self.cmp(*other))
582 }
583}
584
585impl From<CredentialId> for PublicKeyCredentialDescriptor {
589 fn from(id: CredentialId) -> PublicKeyCredentialDescriptor {
590 PublicKeyCredentialDescriptor {
591 id: id.0,
592 key_type: {
593 let mut key_type = String::new();
594 key_type.push_str("public-key").unwrap();
595 key_type
596 },
597 }
598 }
599}
600
601impl FullCredential {
602 #[allow(clippy::too_many_arguments)]
603 pub fn new(
604 ctap: CtapVersion,
605 rp: &ctap_types::webauthn::PublicKeyCredentialRpEntity,
607 user: &ctap_types::webauthn::PublicKeyCredentialUserEntity,
608 algorithm: i32,
609 key: Key,
610 timestamp: u32,
611 hmac_secret: Option<bool>,
612 cred_protect: Option<CredentialProtectionPolicy>,
613 large_blob_key: Option<ByteArray<32>>,
614 third_party_payment: Option<bool>,
615 nonce: [u8; 12],
616 ) -> Self {
617 info!("credential for algorithm {}", algorithm);
618 let data = CredentialData {
619 rp: Rp::new(rp.clone()),
620 user: User::new(user.clone()),
621
622 creation_time: timestamp,
623 use_counter: true,
624 algorithm,
625 key,
626
627 hmac_secret,
628 cred_protect,
629 large_blob_key,
630 third_party_payment,
631
632 use_short_id: Some(true),
633 };
634
635 FullCredential {
636 ctap,
637 data,
638 nonce: ByteArray::new(nonce),
639 }
640 }
641
642 pub fn id<T: Chacha8Poly1305 + Sha256>(
656 &self,
657 trussed: &mut T,
658 key_encryption_key: KeyId,
659 rp_id_hash: Option<&[u8; 32]>,
660 ) -> Result<CredentialId> {
661 let rp_id_hash: [u8; 32] = if let Some(hash) = rp_id_hash {
662 *hash
663 } else {
664 syscall!(trussed.hash_sha256(self.rp.id().as_ref()))
665 .hash
666 .as_slice()
667 .try_into()
668 .map_err(|_| Error::Other)?
669 };
670 if self.use_short_id.unwrap_or_default() {
671 StrippedCredential::from(self).id(trussed, key_encryption_key, &rp_id_hash)
672 } else {
673 let stripped_credential = self.strip();
674 CredentialId::new(
675 trussed,
676 &stripped_credential,
677 key_encryption_key,
678 &rp_id_hash,
679 &self.nonce,
680 )
681 }
682 }
683
684 pub fn serialize(&self) -> Result<SerializedCredential> {
685 let mut serialized_credential = SerializedCredential::new();
686 cbor_smol::cbor_serialize_to(self, &mut serialized_credential).map_err(|_| Error::Other)?;
687 Ok(serialized_credential)
688 }
689
690 pub fn deserialize(bytes: &SerializedCredential) -> Result<Self> {
691 match cbor_smol::cbor_deserialize(bytes) {
692 Ok(s) => Ok(s),
693 Err(_) => {
694 info_now!("could not deserialize {:?}", bytes);
695 Err(Error::Other)
696 }
697 }
698 }
699
700 #[must_use]
703 fn strip(&self) -> Self {
704 info_now!(":: stripping ID");
705 let mut stripped = self.clone();
706 let rp = stripped.data.rp.as_mut();
707 rp.name = None;
708 let user = stripped.data.user.as_mut();
709 user.icon = None;
710 user.name = None;
711 user.display_name = None;
712 stripped
713 }
714}
715
716#[derive(Clone, Debug, serde_indexed::DeserializeIndexed, serde_indexed::SerializeIndexed)]
721pub struct StrippedCredential {
722 pub ctap: CtapVersion,
723 pub creation_time: u32,
724 pub use_counter: bool,
725 pub algorithm: i32,
726 pub key: Key,
727 pub nonce: ByteArray<12>,
728 #[serde(skip_serializing_if = "Option::is_none")]
730 pub hmac_secret: Option<bool>,
731 #[serde(skip_serializing_if = "Option::is_none")]
732 pub cred_protect: Option<CredentialProtectionPolicy>,
733 #[serde(skip_serializing_if = "Option::is_none")]
735 pub large_blob_key: Option<ByteArray<32>>,
736 #[serde(skip_serializing_if = "Option::is_none")]
737 pub third_party_payment: Option<bool>,
738}
739
740impl StrippedCredential {
741 fn deserialize(bytes: &SerializedCredential) -> Result<Self> {
742 match cbor_smol::cbor_deserialize(bytes) {
743 Ok(s) => Ok(s),
744 Err(_) => {
745 info_now!("could not deserialize {:?}", bytes);
746 Err(Error::Other)
747 }
748 }
749 }
750
751 pub fn id<T: Chacha8Poly1305>(
752 &self,
753 trussed: &mut T,
754 key_encryption_key: KeyId,
755 rp_id_hash: &[u8; 32],
756 ) -> Result<CredentialId> {
757 CredentialId::new(trussed, self, key_encryption_key, rp_id_hash, &self.nonce)
758 }
759}
760
761impl From<&FullCredential> for StrippedCredential {
762 fn from(credential: &FullCredential) -> Self {
763 Self {
764 ctap: credential.ctap,
765 creation_time: credential.data.creation_time,
766 use_counter: credential.data.use_counter,
767 algorithm: credential.data.algorithm,
768 key: credential.data.key.clone(),
769 nonce: credential.nonce,
770 hmac_secret: credential.data.hmac_secret,
771 cred_protect: credential.data.cred_protect,
772 large_blob_key: credential.data.large_blob_key,
773 third_party_payment: credential.data.third_party_payment,
774 }
775 }
776}
777
778#[cfg(test)]
779mod test {
780 use super::*;
781 use hex_literal::hex;
782 use littlefs2_core::path;
783 use rand::SeedableRng as _;
784 use rand_chacha::ChaCha8Rng;
785 use serde_test::{assert_de_tokens, assert_tokens, Token};
786 use trussed::{
787 client::{Chacha8Poly1305, Sha256},
788 key::{Kind, Secrecy},
789 store::keystore::{ClientKeystore, Keystore as _},
790 types::Location,
791 virt::{self, StoreConfig},
792 Platform as _,
793 };
794
795 fn credential_data() -> CredentialData {
796 CredentialData {
797 rp: Rp::new(PublicKeyCredentialRpEntity {
798 id: String::try_from("John Doe").unwrap(),
799 name: None,
800 icon: None,
801 }),
802 user: User::new(PublicKeyCredentialUserEntity {
803 id: Bytes::from(&[1, 2, 3]),
804 icon: None,
805 name: None,
806 display_name: None,
807 }),
808 creation_time: 123,
809 use_counter: false,
810 algorithm: -7,
811 key: Key::WrappedKey(Bytes::from(&[1, 2, 3])),
812 hmac_secret: Some(false),
813 cred_protect: None,
814 use_short_id: Some(true),
815 large_blob_key: Some(ByteArray::new([0xff; 32])),
816 third_party_payment: Some(true),
817 }
818 }
819
820 fn old_credential_data() -> CredentialData {
821 CredentialData {
822 rp: Rp {
823 format: SerializationFormat::Long,
824 inner: PublicKeyCredentialRpEntity {
825 id: String::try_from("John Doe").unwrap(),
826 name: None,
827 icon: None,
828 },
829 },
830 user: User {
831 format: SerializationFormat::Long,
832 inner: PublicKeyCredentialUserEntity {
833 id: Bytes::from(&[1, 2, 3]),
834 icon: None,
835 name: None,
836 display_name: None,
837 },
838 },
839 creation_time: 123,
840 use_counter: false,
841 algorithm: -7,
842 key: Key::WrappedKey(Bytes::from(&[1, 2, 3])),
843 hmac_secret: Some(false),
844 cred_protect: None,
845 use_short_id: None,
846 large_blob_key: None,
847 third_party_payment: None,
848 }
849 }
850
851 fn random_byte_array<const N: usize>() -> ByteArray<N> {
852 use rand::{rngs::OsRng, RngCore};
853 let mut bytes = [0; N];
854 OsRng.fill_bytes(&mut bytes);
855 ByteArray::new(bytes)
856 }
857
858 fn random_bytes<const N: usize>() -> Bytes<N> {
859 use rand::{
860 distributions::{Distribution, Uniform},
861 rngs::OsRng,
862 RngCore,
863 };
864 let mut bytes = Bytes::default();
865
866 let between = Uniform::from(0..(N + 1));
867 let n = between.sample(&mut OsRng);
868
869 bytes.resize_zero(n).unwrap();
870
871 OsRng.fill_bytes(&mut bytes);
872 bytes
873 }
874
875 #[allow(dead_code)]
876 fn maybe_random_bytes<const N: usize>() -> Option<Bytes<N>> {
877 use rand::{rngs::OsRng, RngCore};
878 if OsRng.next_u32() & 1 != 0 {
879 Some(random_bytes())
880 } else {
881 None
882 }
883 }
884
885 fn random_string<const N: usize>() -> String<N> {
886 use rand::{
887 distributions::{Alphanumeric, Distribution, Uniform},
888 rngs::OsRng,
889 Rng,
890 };
891 use std::str::FromStr;
892
893 let between = Uniform::from(0..(N + 1));
894 let n = between.sample(&mut OsRng);
895
896 let std_string: std::string::String = OsRng
897 .sample_iter(&Alphanumeric)
898 .take(n)
899 .map(char::from)
900 .collect();
901 String::from_str(&std_string).unwrap()
902 }
903
904 fn maybe_random_string<const N: usize>() -> Option<String<N>> {
905 use rand::{rngs::OsRng, RngCore};
906 if OsRng.next_u32() & 1 != 0 {
907 Some(random_string())
908 } else {
909 None
910 }
911 }
912
913 fn random_credential_data() -> CredentialData {
914 CredentialData {
915 rp: Rp::new(PublicKeyCredentialRpEntity {
916 id: random_string(),
917 name: maybe_random_string(),
918 icon: None,
919 }),
920 user: User::new(PublicKeyCredentialUserEntity {
921 id: random_bytes(), icon: maybe_random_string(),
923 name: maybe_random_string(),
924 display_name: maybe_random_string(),
925 }),
926 creation_time: 123,
927 use_counter: false,
928 algorithm: -7,
929 key: Key::WrappedKey(random_bytes()),
930 hmac_secret: Some(false),
931 cred_protect: None,
932 use_short_id: Some(true),
933 large_blob_key: Some(random_byte_array()),
934 third_party_payment: Some(false),
935 }
936 }
937
938 #[test]
939 fn skip_credential_data_options() {
940 use trussed::{cbor_deserialize as deserialize, cbor_serialize_bytes as serialize};
941
942 let credential_data = credential_data();
943 let serialization: Bytes<1024> = serialize(&credential_data).unwrap();
944 let deserialized: CredentialData = deserialize(&serialization).unwrap();
945
946 assert_eq!(credential_data, deserialized);
947
948 let credential_data = random_credential_data();
949 let serialization: Bytes<1024> = serialize(&credential_data).unwrap();
950 let deserialized: CredentialData = deserialize(&serialization).unwrap();
951
952 assert_eq!(credential_data, deserialized);
953 }
954
955 #[test]
956 fn old_credential_id() {
957 const OLD_ID: &[u8] = &hex!("A300583A71AEF80C4DA56033D66EB3266E9ACB8D84923D13F89BCBCE9FF30D8CD77ED968A436CA3D39C49999EC0F69A289CB2A65A08ABF251DEB21BB4B56014C00000000000000000000000002504DF499ABDAE80F5615C870985B74A799");
959 const SERIALIZED_DATA: &[u8] = &hex!(
960 "A700A1626964684A6F686E20446F6501A16269644301020302187B03F404260582014301020306F4"
961 );
962 const SERIALIZED_CREDENTIAL: &[u8] = &hex!("A3000201A700A1626964684A6F686E20446F6501A16269644301020302187B03F404260582014301020306F4024C000000000000000000000000");
963
964 virt::with_platform(StoreConfig::ram(), |mut platform| {
965 let kek = [0; 44];
966 let client_id = path!("fido");
967 let kek = {
968 let rng = ChaCha8Rng::from_rng(platform.rng()).unwrap();
969 let mut keystore = ClientKeystore::new(client_id.into(), rng, platform.store());
970 keystore
971 .store_key(
972 Location::Internal,
973 Secrecy::Secret,
974 Kind::Symmetric32Nonce(12),
975 &kek,
976 )
977 .unwrap()
978 };
979 platform.run_client(client_id.as_str(), |mut client| {
980 let data = old_credential_data();
981 let rp_id_hash = syscall!(client.hash_sha256(data.rp.id().as_ref())).hash;
982 let encrypted_serialized = CredentialIdRef(OLD_ID).deserialize().unwrap();
983 let serialized = syscall!(client.decrypt_chacha8poly1305(
984 kek,
985 &encrypted_serialized.ciphertext,
986 &rp_id_hash,
987 &encrypted_serialized.nonce,
988 &encrypted_serialized.tag,
989 ))
990 .plaintext
991 .unwrap();
992
993 let full = FullCredential::deserialize(&serialized).unwrap();
994 assert_eq!(
995 full,
996 FullCredential {
997 ctap: CtapVersion::Fido21Pre,
998 data,
999 nonce: [0; 12].into(),
1000 }
1001 );
1002
1003 let stripped_credential = full.strip();
1004
1005 let serialized_data: Bytes<1024> =
1006 trussed::cbor_serialize_bytes(&stripped_credential.data).unwrap();
1007 assert_eq!(
1008 delog::hexstr!(&serialized_data).to_string(),
1009 delog::hexstr!(SERIALIZED_DATA).to_string()
1010 );
1011
1012 let serialized_credential: Bytes<1024> =
1013 trussed::cbor_serialize_bytes(&stripped_credential).unwrap();
1014 assert_eq!(
1015 delog::hexstr!(&serialized_credential).to_string(),
1016 delog::hexstr!(SERIALIZED_CREDENTIAL).to_string()
1017 );
1018
1019 let credential = Credential::Full(full);
1020 let id = credential
1021 .id(&mut client, kek, rp_id_hash.as_ref().try_into().unwrap())
1022 .unwrap()
1023 .0;
1024 assert_eq!(
1025 delog::hexstr!(&id).to_string(),
1026 delog::hexstr!(OLD_ID).to_string()
1027 );
1028 });
1029 });
1030 }
1031
1032 #[test]
1033 fn credential_ids() {
1034 trussed::virt::with_client(StoreConfig::ram(), "fido", |mut client| {
1035 let kek = syscall!(client.generate_chacha8poly1305_key(Location::Internal)).key;
1036 let nonce = ByteArray::new([0; 12]);
1037 let data = credential_data();
1038 let mut full_credential = FullCredential {
1039 ctap: CtapVersion::Fido21Pre,
1040 data,
1041 nonce,
1042 };
1043 let rp_id_hash = syscall!(client.hash_sha256(full_credential.rp.id().as_ref()))
1044 .hash
1045 .as_slice()
1046 .try_into()
1047 .unwrap();
1048
1049 full_credential.data.use_short_id = Some(true);
1051 let stripped_credential = StrippedCredential::from(&full_credential);
1052 let full_id = full_credential
1053 .id(&mut client, kek, Some(&rp_id_hash))
1054 .unwrap();
1055 let short_id = stripped_credential
1056 .id(&mut client, kek, &rp_id_hash)
1057 .unwrap();
1058 assert_eq!(full_id.0, short_id.0);
1059
1060 full_credential.data.use_short_id = None;
1062 let stripped_credential = full_credential.strip();
1063 let full_id = full_credential
1064 .id(&mut client, kek, Some(&rp_id_hash))
1065 .unwrap();
1066 let long_id = CredentialId::new(
1067 &mut client,
1068 &stripped_credential,
1069 kek,
1070 &rp_id_hash,
1071 &full_credential.nonce,
1072 )
1073 .unwrap();
1074 assert_eq!(full_id.0, long_id.0);
1075
1076 assert!(short_id.0.len() < long_id.0.len());
1077 });
1078 }
1079
1080 #[test]
1081 fn max_credential_id() {
1082 let rp_id: String<256> = core::iter::repeat_n('?', 256).collect();
1083 let key = Bytes::from(&[u8::MAX; 128]);
1084 let credential = StrippedCredential {
1085 ctap: CtapVersion::Fido21Pre,
1086 creation_time: u32::MAX,
1087 use_counter: true,
1088 algorithm: i32::MAX,
1089 key: Key::WrappedKey(key),
1090 nonce: ByteArray::new([u8::MAX; 12]),
1091 hmac_secret: Some(true),
1092 cred_protect: Some(CredentialProtectionPolicy::Required),
1093 large_blob_key: Some(ByteArray::new([0xff; 32])),
1094 third_party_payment: Some(true),
1095 };
1096 trussed::virt::with_client(StoreConfig::ram(), "fido", |mut client| {
1097 let kek = syscall!(client.generate_chacha8poly1305_key(Location::Internal)).key;
1098 let rp_id_hash = syscall!(client.hash_sha256(rp_id.as_ref()))
1099 .hash
1100 .as_slice()
1101 .try_into()
1102 .unwrap();
1103 let id = credential.id(&mut client, kek, &rp_id_hash).unwrap();
1104 assert_eq!(id.0.len(), 241);
1105 });
1106 }
1107
1108 fn test_serde<T>(item: &T, name: &'static str, fields: &[(&'static str, Token)])
1109 where
1110 for<'a> T: core::fmt::Debug + PartialEq + serde::Deserialize<'a> + serde::Serialize,
1111 {
1112 let len = fields.len();
1113
1114 let mut struct_tokens = vec![Token::Struct { name, len }];
1115 let mut map_tokens = vec![Token::Map { len: Some(len) }];
1116 for (key, value) in fields {
1117 struct_tokens.push(Token::Str(key));
1118 struct_tokens.push(Token::Some);
1119 struct_tokens.push(*value);
1120
1121 map_tokens.push(Token::Str(key));
1122 map_tokens.push(Token::Some);
1123 map_tokens.push(*value);
1124 }
1125 struct_tokens.push(Token::StructEnd);
1126 map_tokens.push(Token::MapEnd);
1127
1128 assert_tokens(item, &struct_tokens);
1129 assert_de_tokens(item, &map_tokens);
1130 }
1131
1132 struct RpValues {
1133 id: &'static str,
1134 name: Option<&'static str>,
1135 }
1136
1137 impl RpValues {
1138 fn test(&self) {
1139 for format in [SerializationFormat::Short, SerializationFormat::Long] {
1140 self.test_format(format);
1141 }
1142 }
1143
1144 fn test_format(&self, format: SerializationFormat) {
1145 let (id_field, name_field) = match format {
1146 SerializationFormat::Short => ("i", "n"),
1147 SerializationFormat::Long => ("id", "name"),
1148 };
1149 let rp = Rp {
1150 format,
1151 inner: self.inner(),
1152 };
1153
1154 let mut fields = vec![(id_field, Token::BorrowedStr(self.id))];
1155 if let Some(name) = self.name {
1156 fields.push((name_field, Token::BorrowedStr(name)));
1157 }
1158
1159 test_serde(&rp, "RawRp", &fields);
1160 }
1161
1162 fn inner(&self) -> PublicKeyCredentialRpEntity {
1163 PublicKeyCredentialRpEntity {
1164 id: self.id.try_into().unwrap(),
1165 name: self.name.map(|n| n.try_into().unwrap()),
1166 icon: None,
1167 }
1168 }
1169 }
1170
1171 #[test]
1172 fn serde_rp_name_none() {
1173 RpValues {
1174 id: "Testing rp id",
1175 name: None,
1176 }
1177 .test()
1178 }
1179
1180 #[test]
1181 fn serde_rp_name_some() {
1182 RpValues {
1183 id: "Testing rp id",
1184 name: Some("Testing rp name"),
1185 }
1186 .test()
1187 }
1188
1189 struct UserValues {
1190 id: &'static [u8],
1191 icon: Option<&'static str>,
1192 name: Option<&'static str>,
1193 display_name: Option<&'static str>,
1194 }
1195
1196 impl UserValues {
1197 fn test(&self) {
1198 for format in [SerializationFormat::Short, SerializationFormat::Long] {
1199 self.test_format(format);
1200 }
1201 }
1202
1203 fn test_format(&self, format: SerializationFormat) {
1204 let (id_field, icon_field, name_field, display_name_field) = match format {
1205 SerializationFormat::Short => ("i", "I", "n", "d"),
1206 SerializationFormat::Long => ("id", "icon", "name", "displayName"),
1207 };
1208 let user = User {
1209 format,
1210 inner: self.inner(),
1211 };
1212
1213 let mut fields = vec![(id_field, Token::BorrowedBytes(self.id))];
1214 if let Some(icon) = self.icon {
1215 fields.push((icon_field, Token::BorrowedStr(icon)));
1216 }
1217 if let Some(name) = self.name {
1218 fields.push((name_field, Token::BorrowedStr(name)));
1219 }
1220 if let Some(display_name) = self.display_name {
1221 fields.push((display_name_field, Token::BorrowedStr(display_name)));
1222 }
1223
1224 test_serde(&user, "RawUser", &fields);
1225 }
1226
1227 fn inner(&self) -> PublicKeyCredentialUserEntity {
1228 PublicKeyCredentialUserEntity {
1229 id: Bytes::try_from(self.id).unwrap(),
1230 icon: self.icon.map(|v| v.try_into().unwrap()),
1231 name: self.name.map(|v| v.try_into().unwrap()),
1232 display_name: self.display_name.map(|v| v.try_into().unwrap()),
1233 }
1234 }
1235 }
1236
1237 #[test]
1238 fn serde_user_full() {
1239 UserValues {
1240 id: b"Testing user id",
1241 icon: Some("Testing user icon"),
1242 name: Some("Testing user name"),
1243 display_name: Some("Testing user display_name"),
1244 }
1245 .test();
1246 }
1247
1248 #[test]
1249 fn serde_user_display_name() {
1250 UserValues {
1251 id: b"Testing user id",
1252 icon: None,
1253 name: None,
1254 display_name: Some("Testing user display_name"),
1255 }
1256 .test();
1257 }
1258
1259 #[test]
1260 fn serde_user_icon_display_name() {
1261 UserValues {
1262 id: b"Testing user id",
1263 icon: Some("Testing user icon"),
1264 name: None,
1265 display_name: Some("Testing user display_name"),
1266 }
1267 .test();
1268 }
1269
1270 #[test]
1271 fn serde_user_icon() {
1272 UserValues {
1273 id: b"Testing user id",
1274 icon: Some("Testing user icon"),
1275 name: None,
1276 display_name: None,
1277 }
1278 .test();
1279 }
1280
1281 #[test]
1282 fn serde_user_empty() {
1283 UserValues {
1284 id: b"Testing user id",
1285 icon: None,
1286 name: None,
1287 display_name: None,
1288 }
1289 .test();
1290 }
1291
1292 #[test]
1294 fn legacy_full_credential() {
1295 use hex_literal::hex;
1296 let data = hex!(
1297 "
1298 a3000201a700a16269646b776562617574686e2e696f01a2626964476447
1299 567a644445646e616d65657465737431020003f504260582005037635754
1300 c9882b21565a9f8a47b0ece408f5024cf62ca01ed181a3d03d561fc7
1301 "
1302 );
1303
1304 let credential = FullCredential::deserialize(&Bytes::from(&data)).unwrap();
1305 assert!(matches!(credential.ctap, CtapVersion::Fido21Pre));
1306 assert_eq!(credential.nonce, &hex!("F62CA01ED181A3D03D561FC7"));
1307 assert_eq!(
1308 credential.data,
1309 CredentialData {
1310 rp: Rp {
1311 format: SerializationFormat::Long,
1312 inner: PublicKeyCredentialRpEntity {
1313 id: "webauthn.io".try_into().unwrap(),
1314 name: None,
1315 icon: None,
1316 },
1317 },
1318 user: User {
1319 format: SerializationFormat::Long,
1320 inner: PublicKeyCredentialUserEntity {
1321 id: Bytes::from(&hex!("6447567A644445")),
1322 icon: None,
1323 name: Some("test1".try_into().unwrap()),
1324 display_name: None,
1325 },
1326 },
1327 creation_time: 0,
1328 use_counter: true,
1329 algorithm: -7,
1330 key: Key::ResidentKey(KeyId::from_value(0x37635754C9882B21565A9F8A47B0ECE4)),
1331 hmac_secret: None,
1332 cred_protect: None,
1333 use_short_id: Some(true),
1334 large_blob_key: None,
1335 third_party_payment: None,
1336 },
1337 );
1338 }
1339
1340 }