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