Skip to main content

fido_authenticator/
ctap2.rs

1//! The `ctap_types::ctap2::Authenticator` implementation.
2
3use credential_management::CredentialManagement;
4use ctap_types::{
5    ctap2::{
6        self, client_pin::Permissions, AttestationFormatsPreference, AttestationStatement,
7        AttestationStatementFormat, Authenticator, NoneAttestationStatement,
8        PackedAttestationStatement, VendorOperation,
9    },
10    heapless::{String, Vec},
11    heapless_bytes::Bytes,
12    sizes,
13    webauthn::PublicKeyCredentialUserEntity,
14    ByteArray, Error,
15};
16use littlefs2_core::{path, Path, PathBuf};
17use sha2::{Digest as _, Sha256};
18
19use trussed_core::{
20    syscall, try_syscall,
21    types::{KeyId, Location, Mechanism, MediumData, Message, StorageAttributes},
22};
23
24use crate::{
25    constants::{self, MAX_RESIDENT_CREDENTIALS_GUESSTIMATE},
26    credential::{self, Credential, FullCredential, Key, StrippedCredential},
27    format_hex, state, Result, SigningAlgorithm, TrussedRequirements, UserPresence,
28};
29
30#[allow(unused_imports)]
31use crate::msp;
32
33pub mod credential_management;
34pub mod large_blobs;
35pub mod pin;
36
37use pin::{PinProtocol, PinProtocolVersion, RpScope, SharedSecret};
38
39pub const RK_DIR: &Path = path!("rk");
40
41/// Implement `ctap2::Authenticator` for our Authenticator.
42impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenticator<UP, T> {
43    #[inline(never)]
44    fn get_info(&mut self) -> ctap2::get_info::Response {
45        use ctap2::get_info::{Extension, Transport, Version};
46
47        debug_now!("remaining stack size: {} bytes", msp() - 0x2000_0000);
48
49        let mut versions = Vec::new();
50        versions.push(Version::U2fV2).unwrap();
51        versions.push(Version::Fido2_0).unwrap();
52        versions.push(Version::Fido2_1).unwrap();
53
54        let mut extensions = Vec::new();
55        extensions.push(Extension::CredProtect).unwrap();
56        extensions.push(Extension::HmacSecret).unwrap();
57        if self.config.supports_large_blobs() {
58            extensions.push(Extension::LargeBlobKey).unwrap();
59        }
60        extensions.push(Extension::ThirdPartyPayment).unwrap();
61
62        let mut pin_protocols = Vec::new();
63        for pin_protocol in self.pin_protocols() {
64            pin_protocols.push(u8::from(*pin_protocol)).unwrap();
65        }
66
67        let mut options = ctap2::get_info::CtapOptions::default();
68        options.rk = true;
69        options.up = true;
70        options.plat = Some(false);
71        options.cred_mgmt = Some(true);
72        options.client_pin = match self.state.persistent.pin_is_set() {
73            true => Some(true),
74            false => Some(false),
75        };
76        options.large_blobs = Some(self.config.supports_large_blobs());
77        options.pin_uv_auth_token = Some(true);
78        options.make_cred_uv_not_rqd = Some(true);
79
80        let mut transports = Vec::new();
81        if self.config.nfc_transport {
82            transports.push(Transport::Nfc).unwrap();
83        }
84        transports.push(Transport::Usb).unwrap();
85
86        let mut attestation_formats = Vec::new();
87        attestation_formats
88            .push(AttestationStatementFormat::Packed)
89            .unwrap();
90        attestation_formats
91            .push(AttestationStatementFormat::None)
92            .unwrap();
93
94        let (_, aaguid) = self.state.identity.attestation(&mut self.trussed);
95
96        let mut response = ctap2::get_info::Response::default();
97        response.versions = versions;
98        response.extensions = Some(extensions);
99        response.aaguid = Bytes::from(&aaguid);
100        response.options = Some(options);
101        response.transports = Some(transports);
102        // 1200
103        response.max_msg_size = Some(self.config.max_msg_size);
104        response.pin_protocols = Some(pin_protocols);
105        response.max_creds_in_list = Some(ctap_types::sizes::MAX_CREDENTIAL_COUNT_IN_LIST);
106        response.max_cred_id_length = Some(ctap_types::sizes::MAX_CREDENTIAL_ID_LENGTH);
107        response.attestation_formats = Some(attestation_formats);
108        response
109    }
110
111    #[inline(never)]
112    fn get_next_assertion(&mut self) -> Result<ctap2::get_assertion::Response> {
113        // 3. previous GA/GNA >30s ago -> discard stat
114        // this is optional over NFC
115        if false {
116            self.state.runtime.clear_credential_cache();
117            self.state.runtime.active_get_assertion = None;
118            return Err(Error::NotAllowed);
119        }
120        //
121        // 1./2. don't remember / don't have left any credentials
122        // 4. select credential
123        // let data = syscall!(self.trussed.read_file(
124        //     timestamp_hash.location,
125        //     timestamp_hash.path,
126        // )).data;
127        if self.state.runtime.active_get_assertion.is_none() {
128            return Err(Error::NotAllowed);
129        }
130        let credential = self
131            .state
132            .runtime
133            .pop_credential(&mut self.trussed)
134            .ok_or(Error::NotAllowed)?;
135
136        // 5. suppress PII if no UV was performed in original GA
137
138        // 6. sign
139        // 7. reset timer
140        // 8. increment credential counter (not applicable)
141
142        self.assert_with_credential(None, Credential::Full(credential))
143    }
144
145    #[inline(never)]
146    fn make_credential(
147        &mut self,
148        parameters: &ctap2::make_credential::Request,
149    ) -> Result<ctap2::make_credential::Response> {
150        let rp_id_hash = self.hash(parameters.rp.id.as_ref());
151
152        // 1-4.
153        if let Some(options) = parameters.options.as_ref() {
154            // up option is not valid for make_credential
155            if options.up.is_some() {
156                return Err(Error::InvalidOption);
157            }
158        }
159        if parameters.enterprise_attestation.is_some() {
160            return Err(Error::InvalidParameter);
161        }
162        let uv_performed = self.pin_prechecks(
163            &parameters.options,
164            parameters.pin_auth.map(AsRef::as_ref),
165            parameters.pin_protocol,
166            parameters.client_data_hash.as_ref(),
167            Permissions::MAKE_CREDENTIAL,
168            &parameters.rp.id,
169        )?;
170
171        // 5. "persist credProtect value for this credential"
172        // --> seems out of place here, see 9.
173
174        // 6. excludeList present, contains credential ID on this authenticator bound to RP?
175        // --> wait for UP, error CredentialExcluded
176        if let Some(exclude_list) = &parameters.exclude_list {
177            for descriptor in exclude_list.iter() {
178                let result = Credential::try_from(self, &rp_id_hash, descriptor);
179                if let Ok(excluded_cred) = result {
180                    use credential::CredentialProtectionPolicy;
181                    // If UV is not performed, than CredProtectRequired credentials should not be visibile.
182                    if !(excluded_cred.cred_protect() == Some(CredentialProtectionPolicy::Required))
183                        || uv_performed
184                    {
185                        info_now!("Excluded!");
186                        self.up
187                            .user_present(&mut self.trussed, constants::FIDO2_UP_TIMEOUT)?;
188                        return Err(Error::CredentialExcluded);
189                    }
190                }
191            }
192        }
193
194        // 7. check pubKeyCredParams algorithm is valid + supported COSE identifier
195
196        let mut algorithm: Option<SigningAlgorithm> = None;
197        for param in parameters.pub_key_cred_params.0.iter() {
198            match param.alg {
199                -7 => {
200                    if algorithm.is_none() {
201                        algorithm = Some(SigningAlgorithm::P256);
202                    }
203                }
204                -8 => {
205                    algorithm = Some(SigningAlgorithm::Ed25519);
206                }
207                _ => {}
208            }
209        }
210        let algorithm = algorithm.ok_or(Error::UnsupportedAlgorithm)?;
211        info_now!("algo: {:?}", algorithm as i32);
212
213        // 8. process options; on known but unsupported error UnsupportedOption
214
215        let mut rk_requested = false;
216        // TODO: why is this unused?
217        let mut _uv_requested = false;
218        let _up_requested = true; // can't be toggled
219
220        info_now!("MC options: {:?}", &parameters.options);
221        if let Some(ref options) = &parameters.options {
222            if Some(true) == options.rk {
223                rk_requested = true;
224            }
225            if Some(true) == options.uv {
226                _uv_requested = true;
227            }
228        }
229
230        // 9. process extensions
231        let mut hmac_secret_requested = None;
232        // let mut cred_protect_requested = CredentialProtectionPolicy::Optional;
233        let mut cred_protect_requested = None;
234        let mut large_blob_key_requested = false;
235        let mut third_party_payment_requested = false;
236        if let Some(extensions) = &parameters.extensions {
237            hmac_secret_requested = extensions.hmac_secret;
238
239            if let Some(policy) = &extensions.cred_protect {
240                cred_protect_requested =
241                    Some(credential::CredentialProtectionPolicy::try_from(*policy)?);
242            }
243
244            if self.config.supports_large_blobs() {
245                if let Some(large_blob_key) = extensions.large_blob_key {
246                    if large_blob_key {
247                        if !rk_requested {
248                            // the largeBlobKey extension is only available for resident keys
249                            return Err(Error::InvalidOption);
250                        }
251                        large_blob_key_requested = true;
252                    } else {
253                        // large_blob_key must be Some(true) or omitted, Some(false) is invalid
254                        return Err(Error::InvalidOption);
255                    }
256                }
257            }
258
259            third_party_payment_requested = extensions.third_party_payment.unwrap_or_default();
260        }
261
262        // debug_now!("hmac-secret = {:?}, credProtect = {:?}", hmac_secret_requested, cred_protect_requested);
263
264        // 10. get UP, if denied error OperationDenied
265        self.up
266            .user_present(&mut self.trussed, constants::FIDO2_UP_TIMEOUT)?;
267
268        // 11. generate credential keypair
269        let location = match rk_requested {
270            true => Location::Internal,
271            false => Location::Volatile,
272        };
273        let private_key = algorithm.generate_private_key(&mut self.trussed, location);
274        let cose_public_key = algorithm.derive_public_key(&mut self.trussed, private_key);
275
276        // 12. if `rk` is set, store or overwrite key pair, if full error KeyStoreFull
277
278        // 12.a generate credential
279        let key_parameter = match rk_requested {
280            true => Key::ResidentKey(private_key),
281            false => {
282                // WrappedKey version
283                let wrapping_key = self.state.persistent.key_wrapping_key(&mut self.trussed)?;
284                let wrapped_key = syscall!(self.trussed.wrap_key_chacha8poly1305(
285                    wrapping_key,
286                    private_key,
287                    &[],
288                    None
289                ))
290                .wrapped_key;
291
292                // 32B key, 12B nonce, 16B tag + some info on algorithm (P256/Ed25519)
293                // Turns out it's size 92 (enum serialization not optimized yet...)
294                // let mut wrapped_key = Bytes::<60>::new();
295                // wrapped_key.extend_from_slice(&wrapped_key_msg).unwrap();
296                Key::WrappedKey(Bytes::try_from(&*wrapped_key).map_err(|_| Error::Other)?)
297            }
298        };
299
300        // injecting this is a bit mehhh..
301        let nonce = self.nonce();
302        info_now!("nonce = {:?}", &nonce);
303
304        // 12.b generate credential ID { = AEAD(Serialize(Credential)) }
305        let kek = self
306            .state
307            .persistent
308            .key_encryption_key(&mut self.trussed)?;
309
310        // store it.
311        // TODO: overwrite, error handling with KeyStoreFull
312
313        let large_blob_key = if large_blob_key_requested {
314            let key = syscall!(self.trussed.random_bytes(32)).bytes;
315            Some(ByteArray::new(key.as_slice().try_into().unwrap()))
316        } else {
317            None
318        };
319
320        let credential = FullCredential::new(
321            credential::CtapVersion::Fido21Pre,
322            &parameters.rp,
323            &parameters.user,
324            algorithm as i32,
325            key_parameter,
326            self.state.persistent.timestamp(&mut self.trussed)?,
327            hmac_secret_requested,
328            cred_protect_requested,
329            large_blob_key,
330            third_party_payment_requested.then_some(true),
331            nonce,
332        );
333
334        // note that this does the "stripping" of OptionalUI etc.
335        let credential_id =
336            StrippedCredential::from(&credential).id(&mut self.trussed, kek, &rp_id_hash)?;
337
338        if rk_requested {
339            // serialization with all metadata
340            let serialized_credential = credential.serialize()?;
341
342            // first delete any other RK cred with same RP + UserId if there is one.
343            self.delete_resident_key_by_user_id(&rp_id_hash, credential.user.id())
344                .ok();
345
346            let mut key_store_full = self.can_fit(serialized_credential.len()) == Some(false)
347                || CredentialManagement::new(self).count_credentials()?
348                    >= self
349                        .config
350                        .max_resident_credential_count
351                        .unwrap_or(MAX_RESIDENT_CREDENTIALS_GUESSTIMATE);
352
353            if !key_store_full {
354                // then store key, making it resident
355                let credential_id_hash = self.hash(credential_id.0.as_ref());
356                let result = try_syscall!(self.trussed.write_file(
357                    Location::Internal,
358                    rk_path(&rp_id_hash, &credential_id_hash),
359                    serialized_credential,
360                    // user attribute for later easy lookup
361                    // Some(rp_id_hash.clone()),
362                    None,
363                ));
364                key_store_full = result.is_err();
365            }
366
367            if key_store_full {
368                return Err(Error::KeyStoreFull);
369            }
370        }
371
372        // 13. generate and return attestation statement using clientDataHash
373
374        // 13.a AuthenticatorData and its serialization
375        use ctap2::AuthenticatorDataFlags as Flags;
376        info_now!("MC created cred id");
377
378        let (attestation_maybe, aaguid) = self.state.identity.attestation(&mut self.trussed);
379
380        let authenticator_data = ctap2::make_credential::AuthenticatorData {
381            rp_id_hash: &rp_id_hash,
382
383            flags: {
384                let mut flags = Flags::USER_PRESENCE;
385                if uv_performed {
386                    flags |= Flags::USER_VERIFIED;
387                }
388                if true {
389                    flags |= Flags::ATTESTED_CREDENTIAL_DATA;
390                }
391                if hmac_secret_requested.is_some() || cred_protect_requested.is_some() {
392                    flags |= Flags::EXTENSION_DATA;
393                }
394                flags
395            },
396
397            sign_count: self.state.persistent.timestamp(&mut self.trussed)?,
398
399            attested_credential_data: {
400                // debug_now!("acd in, cid len {}, pk len {}", credential_id.0.len(), cose_public_key.len());
401                let attested_credential_data = ctap2::make_credential::AttestedCredentialData {
402                    aaguid: &aaguid,
403                    credential_id: &credential_id.0,
404                    credential_public_key: &cose_public_key,
405                };
406                // debug_now!("cose PK = {:?}", &attested_credential_data.credential_public_key);
407                Some(attested_credential_data)
408            },
409
410            extensions: {
411                if hmac_secret_requested.is_some() || cred_protect_requested.is_some() {
412                    let mut extensions = ctap2::make_credential::Extensions::default();
413                    extensions.cred_protect = parameters.extensions.as_ref().unwrap().cred_protect;
414                    extensions.hmac_secret = parameters.extensions.as_ref().unwrap().hmac_secret;
415                    Some(extensions)
416                } else {
417                    None
418                }
419            },
420        };
421        // debug_now!("authData = {:?}", &authenticator_data);
422
423        let serialized_auth_data = authenticator_data.serialize()?;
424
425        // select attestation format or use packed attestation as default
426        let att_stmt_fmt = parameters
427            .attestation_formats_preference
428            .as_ref()
429            .map(SupportedAttestationFormat::select)
430            .unwrap_or(Some(SupportedAttestationFormat::Packed));
431        let att_stmt = if let Some(format) = att_stmt_fmt {
432            match format {
433                SupportedAttestationFormat::None => {
434                    Some(AttestationStatement::None(NoneAttestationStatement {}))
435                }
436                SupportedAttestationFormat::Packed => {
437                    let mut commitment = Bytes::<1024>::new();
438                    commitment
439                        .extend_from_slice(&serialized_auth_data)
440                        .map_err(|_| Error::Other)?;
441                    commitment
442                        .extend_from_slice(parameters.client_data_hash)
443                        .map_err(|_| Error::Other)?;
444
445                    let (attestation_key, attestation_algorithm) = attestation_maybe
446                        .as_ref()
447                        .map(|attestation| (attestation.0, SigningAlgorithm::P256))
448                        .unwrap_or((private_key, algorithm));
449                    let signature =
450                        attestation_algorithm.sign(&mut self.trussed, attestation_key, &commitment);
451                    let packed = PackedAttestationStatement {
452                        alg: attestation_algorithm.into(),
453                        sig: Bytes::try_from(&*signature).map_err(|_| Error::Other)?,
454                        x5c: attestation_maybe.as_ref().map(|attestation| {
455                            // See: https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
456                            let cert = attestation.1.clone();
457                            let mut x5c = Vec::new();
458                            x5c.push(cert).ok();
459                            x5c
460                        }),
461                    };
462                    Some(AttestationStatement::Packed(packed))
463                }
464            }
465        } else {
466            None
467        };
468
469        if !rk_requested {
470            let _success = syscall!(self.trussed.delete(private_key)).success;
471            info_now!("deleted private credential key: {}", _success);
472        }
473
474        let mut attestation_object = ctap2::make_credential::ResponseBuilder {
475            fmt: att_stmt_fmt
476                .map(From::from)
477                .unwrap_or(AttestationStatementFormat::None),
478            auth_data: serialized_auth_data,
479        }
480        .build();
481        attestation_object.att_stmt = att_stmt;
482        attestation_object.large_blob_key = large_blob_key;
483        Ok(attestation_object)
484    }
485
486    #[inline(never)]
487    fn reset(&mut self) -> Result<()> {
488        // 1. >10s after bootup -> NotAllowed
489        let uptime = syscall!(self.trussed.uptime()).uptime;
490        debug_now!("uptime: {:?}", uptime);
491        if uptime.as_secs() > 10 {
492            #[cfg(not(feature = "disable-reset-time-window"))]
493            return Err(Error::NotAllowed);
494        }
495        // 2. check for user presence
496        // denied -> OperationDenied
497        // timeout -> UserActionTimeout
498        self.up
499            .user_present(&mut self.trussed, constants::FIDO2_UP_TIMEOUT)?;
500
501        // Delete resident keys
502        syscall!(self.trussed.delete_all(Location::Internal));
503        syscall!(self
504            .trussed
505            .remove_dir_all(Location::Internal, RK_DIR.into()));
506
507        // Delete large-blob array
508        large_blobs::reset(&mut self.trussed);
509
510        // b. delete persistent state
511        self.state.persistent.reset(&mut self.trussed)?;
512
513        // c. Reset runtime state
514        self.state.runtime.reset(&mut self.trussed);
515
516        Ok(())
517    }
518
519    fn selection(&mut self) -> Result<()> {
520        self.up
521            .user_present(&mut self.trussed, constants::FIDO2_UP_TIMEOUT)
522    }
523
524    #[inline(never)]
525    fn client_pin(
526        &mut self,
527        parameters: &ctap2::client_pin::Request<'_>,
528    ) -> Result<ctap2::client_pin::Response> {
529        use ctap2::client_pin::PinV1Subcommand as Subcommand;
530        debug_now!("CTAP2.PIN...");
531        // info_now!("{:?}", parameters);
532
533        let pin_protocol = parameters
534            .pin_protocol
535            .ok_or(Error::MissingParameter)
536            .and_then(|pin_protocol| self.parse_pin_protocol(pin_protocol));
537        let mut response = ctap2::client_pin::Response::default();
538
539        match parameters.sub_command {
540            Subcommand::GetRetries => {
541                debug_now!("CTAP2.Pin.GetRetries");
542
543                response.retries = Some(self.state.persistent.retries());
544            }
545
546            Subcommand::GetKeyAgreement => {
547                debug_now!("CTAP2.Pin.GetKeyAgreement");
548
549                let pin_protocol = pin_protocol?;
550                response.key_agreement = Some(self.pin_protocol(pin_protocol).key_agreement_key());
551            }
552
553            Subcommand::SetPin => {
554                debug_now!("CTAP2.Pin.SetPin");
555                // 1. check mandatory parameters
556                let platform_kek = match parameters.key_agreement.as_ref() {
557                    Some(key) => key,
558                    None => {
559                        return Err(Error::MissingParameter);
560                    }
561                };
562                let new_pin_enc = match parameters.new_pin_enc.as_ref() {
563                    Some(pin) => pin,
564                    None => {
565                        return Err(Error::MissingParameter);
566                    }
567                };
568                let pin_auth = match parameters.pin_auth.as_ref() {
569                    Some(auth) => auth,
570                    None => {
571                        return Err(Error::MissingParameter);
572                    }
573                };
574                let pin_protocol = pin_protocol?;
575
576                // 2. is pin already set
577                if self.state.persistent.pin_is_set() {
578                    return Err(Error::NotAllowed);
579                }
580
581                // 3. generate shared secret
582                let mut pin_protocol = self.pin_protocol(pin_protocol);
583                let shared_secret = pin_protocol.shared_secret(platform_kek)?;
584
585                // TODO: there are moar early returns!!
586                // - implement Drop?
587                // - do garbage collection outside of this?
588
589                // 4. verify pinAuth
590                pin_protocol.verify_pin_auth(&shared_secret, new_pin_enc, pin_auth)?;
591
592                // 5. decrypt and verify new PIN
593                let new_pin = self.decrypt_pin_check_length(&shared_secret, new_pin_enc)?;
594
595                shared_secret.delete(&mut self.trussed);
596
597                // 6. store LEFT(SHA-256(newPin), 16), set retries to 8
598                self.hash_store_pin(&new_pin)?;
599                self.state
600                    .reset_retries(&mut self.trussed)
601                    .map_err(|_| Error::Other)?;
602            }
603
604            Subcommand::ChangePin => {
605                debug_now!("CTAP2.Pin.ChangePin");
606
607                // 1. check mandatory parameters
608                let platform_kek = match parameters.key_agreement.as_ref() {
609                    Some(key) => key,
610                    None => {
611                        return Err(Error::MissingParameter);
612                    }
613                };
614                let pin_hash_enc = match parameters.pin_hash_enc.as_ref() {
615                    Some(hash) => hash,
616                    None => {
617                        return Err(Error::MissingParameter);
618                    }
619                };
620                let new_pin_enc = match parameters.new_pin_enc.as_ref() {
621                    Some(pin) => pin,
622                    None => {
623                        return Err(Error::MissingParameter);
624                    }
625                };
626                let pin_auth = match parameters.pin_auth.as_ref() {
627                    Some(auth) => auth,
628                    None => {
629                        return Err(Error::MissingParameter);
630                    }
631                };
632                let pin_protocol = pin_protocol?;
633
634                // 2. fail if no retries left
635                self.state.pin_blocked()?;
636
637                // 3. generate shared secret
638                let mut pin_protocol_impl = self.pin_protocol(pin_protocol);
639                let shared_secret = pin_protocol_impl.shared_secret(platform_kek)?;
640
641                // 4. verify pinAuth
642                let mut data = MediumData::new();
643                data.extend_from_slice(new_pin_enc)
644                    .map_err(|_| Error::InvalidParameter)?;
645                data.extend_from_slice(pin_hash_enc)
646                    .map_err(|_| Error::InvalidParameter)?;
647                pin_protocol_impl.verify_pin_auth(&shared_secret, &data, pin_auth)?;
648
649                // 5. decrement retries
650                self.state.decrement_retries(&mut self.trussed)?;
651
652                // 6. decrypt pinHashEnc, compare with stored
653                self.decrypt_pin_hash_and_maybe_escalate(
654                    pin_protocol,
655                    &shared_secret,
656                    pin_hash_enc,
657                )?;
658
659                // 7. reset retries
660                self.state.reset_retries(&mut self.trussed)?;
661
662                // 8. decrypt and verify new PIN
663                let new_pin = self.decrypt_pin_check_length(&shared_secret, new_pin_enc)?;
664
665                shared_secret.delete(&mut self.trussed);
666
667                // 9. store hashed PIN
668                self.hash_store_pin(&new_pin)?;
669
670                self.pin_protocol(pin_protocol).reset_pin_tokens();
671            }
672
673            // § 6.5.5.7.1 No 4
674            Subcommand::GetPinToken => {
675                debug_now!("CTAP2.Pin.GetPinToken");
676
677                // 1. Check mandatory parameters
678                let key_agreement = parameters
679                    .key_agreement
680                    .as_ref()
681                    .ok_or(Error::MissingParameter)?;
682                let pin_hash_enc = parameters
683                    .pin_hash_enc
684                    .as_ref()
685                    .ok_or(Error::MissingParameter)?;
686
687                // 2. Check PIN protocol
688                let pin_protocol = pin_protocol?;
689
690                // 3. + 4. Check invalid parameters
691                if parameters.permissions.is_some() || parameters.rp_id.is_some() {
692                    return Err(Error::InvalidParameter);
693                }
694
695                // 5. Check PIN retries
696                self.state.pin_blocked()?;
697
698                // 6. Obtain shared secret
699                let shared_secret = self
700                    .pin_protocol(pin_protocol)
701                    .shared_secret(key_agreement)?;
702
703                // 7. Request user consent using display -- skipped
704
705                // 8. Decrement PIN retries
706                self.state.decrement_retries(&mut self.trussed)?;
707
708                // 9. Check PIN
709                self.decrypt_pin_hash_and_maybe_escalate(
710                    pin_protocol,
711                    &shared_secret,
712                    pin_hash_enc,
713                )?;
714
715                // 10. Reset PIN retries
716                self.state.reset_retries(&mut self.trussed)?;
717
718                // 11. Check forcePINChange -- skipped
719
720                // 12. Reset all PIN tokens
721                // 13. Call beginUsingPinUvAuthToken
722                let mut pin_protocol = self.pin_protocol(pin_protocol);
723                let mut pin_token = pin_protocol.reset_and_begin_using_pin_token(false);
724
725                // 14. Assign the default permissions
726                let mut permissions = Permissions::empty();
727                permissions.insert(Permissions::MAKE_CREDENTIAL);
728                permissions.insert(Permissions::GET_ASSERTION);
729                pin_token.restrict(permissions, None);
730
731                // 15. Return PIN token
732                response.pin_token = Some(pin_token.encrypt(&shared_secret)?);
733
734                shared_secret.delete(&mut self.trussed);
735            }
736
737            // § 6.5.5.7.2 No 4
738            Subcommand::GetPinUvAuthTokenUsingPinWithPermissions => {
739                debug_now!("CTAP2.Pin.GetPinUvAuthTokenUsingPinWithPermissions");
740
741                // 1. Check mandatory parameters
742                let key_agreement = parameters
743                    .key_agreement
744                    .as_ref()
745                    .ok_or(Error::MissingParameter)?;
746                let pin_hash_enc = parameters
747                    .pin_hash_enc
748                    .as_ref()
749                    .ok_or(Error::MissingParameter)?;
750                let permissions = parameters.permissions.ok_or(Error::MissingParameter)?;
751
752                // 2. Check PIN protocol
753                let pin_protocol = pin_protocol?;
754
755                // 3. Check that permissions are not empty
756                let permissions = Permissions::from_bits_truncate(permissions);
757                if permissions.is_empty() {
758                    return Err(Error::InvalidParameter);
759                }
760
761                // 4. Check that all requested permissions are supported
762                let mut unauthorized_permissions = Permissions::empty();
763                unauthorized_permissions.insert(Permissions::BIO_ENROLLMENT);
764                if !self.config.supports_large_blobs() {
765                    unauthorized_permissions.insert(Permissions::LARGE_BLOB_WRITE);
766                }
767                unauthorized_permissions.insert(Permissions::AUTHENTICATOR_CONFIGURATION);
768                if permissions.intersects(unauthorized_permissions) {
769                    return Err(Error::UnauthorizedPermission);
770                }
771
772                // 5. Check PIN retries
773                self.state.pin_blocked()?;
774
775                // 6. Obtain shared secret
776                let shared_secret = self
777                    .pin_protocol(pin_protocol)
778                    .shared_secret(key_agreement)?;
779
780                // 7. Request user consent using display -- skipped
781
782                // 8. Decrement PIN retries
783                self.state.decrement_retries(&mut self.trussed)?;
784
785                // 9. Check PIN
786                self.decrypt_pin_hash_and_maybe_escalate(
787                    pin_protocol,
788                    &shared_secret,
789                    pin_hash_enc,
790                )?;
791
792                // 10. Reset PIN retries
793                self.state.reset_retries(&mut self.trussed)?;
794
795                // 11. Check forcePINChange -- skipped
796
797                // 12. Reset all PIN tokens
798                // 13. Call beginUsingPinUvAuthToken
799                let mut pin_protocol = self.pin_protocol(pin_protocol);
800                let mut pin_token = pin_protocol.reset_and_begin_using_pin_token(false);
801
802                // 14. Assign the requested permissions
803                // 15. Assign the requested RP id
804                let rp_id = parameters
805                    .rp_id
806                    .map(TryInto::try_into)
807                    .transpose()
808                    .map_err(|_| Error::InvalidParameter)?;
809                pin_token.restrict(permissions, rp_id);
810
811                // 16. Return PIN token
812                response.pin_token = Some(pin_token.encrypt(&shared_secret)?);
813
814                shared_secret.delete(&mut self.trussed);
815            }
816
817            Subcommand::GetPinUvAuthTokenUsingUvWithPermissions | Subcommand::GetUVRetries => {
818                // todo!("not implemented yet")
819                return Err(Error::InvalidParameter);
820            }
821
822            _ => {
823                return Err(Error::InvalidParameter);
824            }
825        }
826
827        Ok(response)
828    }
829
830    #[inline(never)]
831    fn credential_management(
832        &mut self,
833        parameters: &ctap2::credential_management::Request<'_>,
834    ) -> Result<ctap2::credential_management::Response> {
835        use credential_management as cm;
836        use ctap2::credential_management::Subcommand;
837
838        self.verify_credential_management_pin_auth(parameters)?;
839
840        let mut cred_mgmt = cm::CredentialManagement::new(self);
841        let sub_parameters = &parameters.sub_command_params;
842        // TODO: use custom enum of known commands
843        match parameters.sub_command {
844            // 0x1
845            Subcommand::GetCredsMetadata => cred_mgmt.get_creds_metadata(),
846
847            // 0x2
848            Subcommand::EnumerateRpsBegin => cred_mgmt.first_relying_party(),
849
850            // 0x3
851            Subcommand::EnumerateRpsGetNextRp => cred_mgmt.next_relying_party(),
852
853            // 0x4
854            Subcommand::EnumerateCredentialsBegin => {
855                let sub_parameters = sub_parameters.as_ref().ok_or(Error::MissingParameter)?;
856
857                cred_mgmt.first_credential(
858                    sub_parameters
859                        .rp_id_hash
860                        .as_ref()
861                        .ok_or(Error::MissingParameter)?,
862                )
863            }
864
865            // 0x5
866            Subcommand::EnumerateCredentialsGetNextCredential => cred_mgmt.next_credential(),
867
868            // 0x6
869            Subcommand::DeleteCredential => {
870                let sub_parameters = sub_parameters.as_ref().ok_or(Error::MissingParameter)?;
871
872                cred_mgmt.delete_credential(
873                    sub_parameters
874                        .credential_id
875                        .as_ref()
876                        .ok_or(Error::MissingParameter)?,
877                )
878            }
879
880            // 0x7
881            Subcommand::UpdateUserInformation => {
882                let sub_parameters = sub_parameters.as_ref().ok_or(Error::MissingParameter)?;
883                let credential_id = sub_parameters
884                    .credential_id
885                    .as_ref()
886                    .ok_or(Error::MissingParameter)?;
887                let user = sub_parameters
888                    .user
889                    .as_ref()
890                    .ok_or(Error::MissingParameter)?;
891
892                cred_mgmt.update_user_information(credential_id, user)
893            }
894
895            _ => Err(Error::InvalidParameter),
896        }
897    }
898
899    #[inline(never)]
900    fn vendor(&mut self, op: VendorOperation) -> Result<()> {
901        info_now!("hello VO {:?}", &op);
902        match op.into() {
903            0x79 => {
904                #[allow(deprecated)]
905                {
906                    syscall!(self.trussed.debug_dump_store());
907                }
908                Err(Error::InvalidCommand)
909            }
910            _ => Err(Error::InvalidCommand),
911        }
912    }
913
914    #[inline(never)]
915    fn get_assertion(
916        &mut self,
917        parameters: &ctap2::get_assertion::Request,
918    ) -> Result<ctap2::get_assertion::Response> {
919        debug_now!("remaining stack size: {} bytes", msp() - 0x2000_0000);
920
921        let rp_id_hash = self.hash(parameters.rp_id.as_ref());
922
923        // 1-4.
924        let uv_performed = match self.pin_prechecks(
925            &parameters.options,
926            parameters.pin_auth.map(AsRef::as_ref),
927            parameters.pin_protocol,
928            parameters.client_data_hash.as_ref(),
929            Permissions::GET_ASSERTION,
930            parameters.rp_id,
931        ) {
932            Ok(b) => b,
933            Err(Error::PinRequired) => {
934                // UV is optional for get_assertion
935                false
936            }
937            Err(err) => return Err(err),
938        };
939
940        // 5. Locate eligible credentials
941        //
942        // Note: If allowList is passed, credential is Some(credential)
943        // If no allowList is passed, credential is None and the retrieved credentials
944        // are stored in state.runtime.credential_heap
945        let (credential, num_credentials) = self
946            .prepare_credentials(&rp_id_hash, &parameters.allow_list, uv_performed)?
947            .ok_or(Error::NoCredentials)?;
948
949        info_now!("found {:?} applicable credentials", num_credentials);
950        info_now!("{:?}", &credential);
951
952        // 6. process any options present
953
954        // RK is not supported in get_assertion
955        if parameters
956            .options
957            .as_ref()
958            .and_then(|options| options.rk)
959            .is_some()
960        {
961            return Err(Error::InvalidOption);
962        }
963
964        // UP occurs by default, but option could specify not to.
965        let do_up = if let Some(options) = parameters.options.as_ref() {
966            options.up.unwrap_or(true)
967        } else {
968            true
969        };
970
971        // 7. collect user presence
972        let up_performed = if do_up {
973            if !self.skip_up_check() {
974                info_now!("asking for up");
975                self.up
976                    .user_present(&mut self.trussed, constants::FIDO2_UP_TIMEOUT)?;
977            }
978            true
979        } else {
980            info_now!("not asking for up");
981            false
982        };
983
984        let multiple_credentials = num_credentials > 1;
985        self.state.runtime.active_get_assertion = Some(state::ActiveGetAssertionData {
986            rp_id_hash: {
987                let mut buf = [0u8; 32];
988                buf.copy_from_slice(&rp_id_hash);
989                buf
990            },
991            client_data_hash: {
992                let mut buf = [0u8; 32];
993                buf.copy_from_slice(parameters.client_data_hash);
994                buf
995            },
996            uv_performed,
997            up_performed,
998            multiple_credentials,
999            extensions: parameters.extensions.clone(),
1000            attestation_formats_preference: parameters.attestation_formats_preference.clone(),
1001        });
1002
1003        let num_credentials = match num_credentials {
1004            1 => None,
1005            n => Some(n),
1006        };
1007
1008        self.assert_with_credential(num_credentials, credential)
1009    }
1010
1011    #[inline(never)]
1012    fn large_blobs(
1013        &mut self,
1014        request: &ctap2::large_blobs::Request,
1015    ) -> Result<ctap2::large_blobs::Response> {
1016        let Some(config) = self.config.large_blobs else {
1017            return Err(Error::InvalidCommand);
1018        };
1019
1020        // 1. offset is validated by serde
1021
1022        // 2.-3. Exactly one of get or set must be present
1023        match (request.get, request.set) {
1024            (None, None) | (Some(_), Some(_)) => Err(Error::InvalidParameter),
1025            // 4. Implement get subcommand
1026            (Some(get), None) => self.large_blobs_get(request, config, get),
1027            // 5. Implement set subcommand
1028            (None, Some(set)) => self.large_blobs_set(request, config, set),
1029        }
1030    }
1031}
1032
1033// impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenticator<UP, T>
1034impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
1035    fn parse_pin_protocol(&self, version: impl TryInto<u8>) -> Result<PinProtocolVersion> {
1036        if let Ok(version) = version.try_into() {
1037            for pin_protocol in self.pin_protocols() {
1038                if u8::from(*pin_protocol) == version {
1039                    return Ok(*pin_protocol);
1040                }
1041            }
1042        }
1043        Err(Error::InvalidParameter)
1044    }
1045
1046    // This is the single source of truth for the supported PIN protocols.
1047    fn pin_protocols(&self) -> &'static [PinProtocolVersion] {
1048        &[PinProtocolVersion::V2, PinProtocolVersion::V1]
1049    }
1050
1051    fn pin_protocol(&mut self, pin_protocol: PinProtocolVersion) -> PinProtocol<'_, T> {
1052        let state = self.state.runtime.pin_protocol(&mut self.trussed);
1053        PinProtocol::new(&mut self.trussed, state, pin_protocol)
1054    }
1055
1056    #[inline(never)]
1057    fn check_credential_applicable(
1058        &mut self,
1059        credential: &Credential,
1060        allowlist_passed: bool,
1061        uv_performed: bool,
1062    ) -> bool {
1063        if !self.check_key_exists(credential.algorithm(), credential.key()) {
1064            return false;
1065        }
1066
1067        if !{
1068            use credential::CredentialProtectionPolicy as Policy;
1069            debug_now!("CredentialProtectionPolicy {:?}", credential.cred_protect());
1070            match credential.cred_protect() {
1071                None | Some(Policy::Optional) => true,
1072                Some(Policy::OptionalWithCredentialIdList) => allowlist_passed || uv_performed,
1073                Some(Policy::Required) => uv_performed,
1074            }
1075        } {
1076            return false;
1077        }
1078        true
1079    }
1080
1081    #[inline(never)]
1082    fn prepare_credentials(
1083        &mut self,
1084        rp_id_hash: &[u8; 32],
1085        allow_list: &Option<ctap2::get_assertion::AllowList>,
1086        uv_performed: bool,
1087    ) -> Result<Option<(Credential, u32)>> {
1088        debug_now!("remaining stack size: {} bytes", msp() - 0x2000_0000);
1089
1090        self.state.runtime.clear_credential_cache();
1091        self.state.runtime.active_get_assertion = None;
1092
1093        // NB: CTAP 2.1 specifies to return the first applicable credential, and set
1094        // numberOfCredentials to None.
1095        // However, CTAP 2.0 says to send numberOfCredentials that are applicable,
1096        // which implies we'd have to respond to GetNextAssertion.
1097        //
1098        // We are using CTAP 2.1 behaviour here, as it allows us not to cache the (length)
1099        // credential IDs. Presumably, most clients use this to just get any old signatures,
1100        // but we did change the github.com/solokeys/fido2-tests to accommodate this change
1101        // of behaviour.
1102        if let Some(allow_list) = allow_list {
1103            debug_now!("Allowlist of len {} passed, filtering", allow_list.len());
1104            // we will have at most one credential, and an empty cache.
1105
1106            // client is not supposed to send Some(empty list):
1107            // <https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#:~:text=A%20platform%20MUST%20NOT%20send%20an%20empty%20allowList%E2%80%94if%20it%20would%20be%20empty%20it%20MUST%20be%20omitted>
1108            // but some still do (and CTAP 2.0 does not rule it out).
1109            // they probably meant to send None.
1110            if !allow_list.is_empty() {
1111                for credential_id in allow_list {
1112                    let credential = match Credential::try_from(self, rp_id_hash, credential_id) {
1113                        Ok(credential) => credential,
1114                        _ => continue,
1115                    };
1116
1117                    if !self.check_credential_applicable(&credential, true, uv_performed) {
1118                        continue;
1119                    }
1120
1121                    return Ok(Some((credential, 1)));
1122                }
1123
1124                // we don't recognize any credentials in the allowlist
1125                return Ok(None);
1126            }
1127        }
1128
1129        // we are only dealing with discoverable credentials.
1130        debug_now!("Allowlist not passed, fetching RKs");
1131        self.prepare_cache(rp_id_hash, uv_performed)?;
1132
1133        let num_credentials = self.state.runtime.remaining_credentials();
1134        let credential = self.state.runtime.pop_credential(&mut self.trussed);
1135        Ok(credential.map(|credential| (Credential::Full(credential), num_credentials)))
1136    }
1137
1138    /// Populate the cache with the RP credentials.
1139    #[inline(never)]
1140    fn prepare_cache(&mut self, rp_id_hash: &[u8; 32], uv_performed: bool) -> Result<()> {
1141        use crate::state::CachedCredential;
1142        use core::str::FromStr;
1143
1144        let file_name_prefix = rp_file_name_prefix(rp_id_hash);
1145        let mut maybe_entry = syscall!(self.trussed.read_dir_first_alphabetical(
1146            Location::Internal,
1147            PathBuf::from(RK_DIR),
1148            Some(file_name_prefix.clone())
1149        ))
1150        .entry;
1151
1152        while let Some(entry) = maybe_entry.take() {
1153            if !entry
1154                .file_name()
1155                .as_ref()
1156                .starts_with(file_name_prefix.as_ref())
1157            {
1158                // We got past all credentials for the relevant RP
1159                break;
1160            }
1161
1162            if entry.file_name() == &*file_name_prefix {
1163                debug_assert!(entry.metadata().is_dir());
1164                error!("Migration missing");
1165                return Err(Error::Other);
1166            }
1167
1168            let credential_data = syscall!(self
1169                .trussed
1170                .read_file(Location::Internal, entry.path().into(),))
1171            .data;
1172
1173            let credential = FullCredential::deserialize(&credential_data).map_err(|_err| {
1174                error!("Failed to deserialize credential: {_err:?}");
1175                Error::Other
1176            })?;
1177            let timestamp = credential.creation_time;
1178            let credential = Credential::Full(credential);
1179
1180            if self.check_credential_applicable(&credential, false, uv_performed) {
1181                self.state.runtime.push_credential(CachedCredential {
1182                    timestamp,
1183                    path: String::from_str(entry.path().as_str_ref_with_trailing_nul())
1184                        .map_err(|_| Error::Other)?,
1185                });
1186            }
1187
1188            maybe_entry = syscall!(self.trussed.read_dir_next()).entry;
1189        }
1190        Ok(())
1191    }
1192
1193    fn decrypt_pin_hash_and_maybe_escalate(
1194        &mut self,
1195        pin_protocol: PinProtocolVersion,
1196        shared_secret: &SharedSecret,
1197        pin_hash_enc: &[u8],
1198    ) -> Result<()> {
1199        let pin_hash = shared_secret
1200            .decrypt(&mut self.trussed, pin_hash_enc)
1201            .ok_or(Error::Other)?;
1202
1203        let stored_pin_hash = match self.state.persistent.pin_hash() {
1204            Some(hash) => hash,
1205            None => {
1206                return Err(Error::PinNotSet);
1207            }
1208        };
1209
1210        if pin_hash != stored_pin_hash {
1211            // I) generate new KEK
1212            self.pin_protocol(pin_protocol).regenerate();
1213            self.state.pin_blocked()?;
1214            return Err(Error::PinInvalid);
1215        }
1216
1217        Ok(())
1218    }
1219
1220    fn hash_store_pin(&mut self, pin: &Message) -> Result<()> {
1221        let pin_hash_32 = syscall!(self.trussed.hash_sha256(pin)).hash;
1222        let pin_hash: [u8; 16] = pin_hash_32[..16].try_into().unwrap();
1223        self.state
1224            .persistent
1225            .set_pin_hash(&mut self.trussed, pin_hash)
1226            .unwrap();
1227
1228        Ok(())
1229    }
1230
1231    fn decrypt_pin_check_length(
1232        &mut self,
1233        shared_secret: &SharedSecret,
1234        pin_enc: &[u8],
1235    ) -> Result<Message> {
1236        // pin is expected to be filled with null bytes to length at least 64
1237        if pin_enc.len() < 64 {
1238            // correct error?
1239            return Err(Error::PinPolicyViolation);
1240        }
1241
1242        let mut pin = shared_secret
1243            .decrypt(&mut self.trussed, pin_enc)
1244            .ok_or(Error::Other)?;
1245
1246        // // temp
1247        // let pin_length = pin.iter().position(|&b| b == b'\0').unwrap_or(pin.len());
1248        // info_now!("pin.len() = {}, pin_length = {}, = {:?}",
1249        //           pin.len(), pin_length, &pin);
1250        // chop off null bytes
1251        let pin_length = pin.iter().position(|&b| b == b'\0').unwrap_or(pin.len());
1252        if !(4..64).contains(&pin_length) {
1253            return Err(Error::PinPolicyViolation);
1254        }
1255
1256        pin.resize_zero(pin_length).unwrap();
1257
1258        Ok(pin)
1259    }
1260
1261    fn verify_credential_management_pin_auth(
1262        &mut self,
1263        parameters: &ctap2::credential_management::Request,
1264    ) -> Result<()> {
1265        use ctap2::credential_management::Subcommand;
1266        let rp_scope = match parameters.sub_command {
1267            Subcommand::EnumerateCredentialsBegin => {
1268                let rp_id_hash = parameters
1269                    .sub_command_params
1270                    .as_ref()
1271                    .and_then(|subparams| subparams.rp_id_hash)
1272                    .ok_or(Error::MissingParameter)?;
1273                RpScope::RpIdHash(rp_id_hash)
1274            }
1275            Subcommand::DeleteCredential | Subcommand::UpdateUserInformation => {
1276                // TODO: determine RP ID from credential ID
1277                RpScope::All
1278            }
1279            _ => RpScope::All,
1280        };
1281        match parameters.sub_command {
1282            Subcommand::GetCredsMetadata
1283            | Subcommand::EnumerateRpsBegin
1284            | Subcommand::EnumerateCredentialsBegin
1285            | Subcommand::DeleteCredential
1286            | Subcommand::UpdateUserInformation => {
1287                // check pinProtocol
1288                let pin_protocol = parameters.pin_protocol.ok_or(Error::MissingParameter)?;
1289                let pin_protocol = self.parse_pin_protocol(pin_protocol)?;
1290
1291                // check pinAuth
1292                let mut data: Bytes<{ sizes::MAX_CREDENTIAL_ID_LENGTH_PLUS_256 }> =
1293                    Bytes::from(&[parameters.sub_command as u8]);
1294                let len = 1 + match parameters.sub_command {
1295                    Subcommand::EnumerateCredentialsBegin
1296                    | Subcommand::DeleteCredential
1297                    | Subcommand::UpdateUserInformation => {
1298                        data.resize_to_capacity();
1299                        // ble, need to reserialize
1300                        ctap_types::serde::cbor_serialize(
1301                            &parameters
1302                                .sub_command_params
1303                                .as_ref()
1304                                .ok_or(Error::MissingParameter)?,
1305                            &mut data[1..],
1306                        )
1307                        .map_err(|_| Error::LimitExceeded)?
1308                        .len()
1309                    }
1310                    _ => 0,
1311                };
1312
1313                let pin_auth = parameters
1314                    .pin_auth
1315                    .as_ref()
1316                    .ok_or(Error::MissingParameter)?;
1317
1318                let mut pin_protocol = self.pin_protocol(pin_protocol);
1319                if let Ok(pin_token) = pin_protocol.verify_pin_token(&data[..len], pin_auth) {
1320                    info_now!("passed pinauth");
1321                    pin_token.require_permissions(Permissions::CREDENTIAL_MANAGEMENT)?;
1322                    pin_token.require_valid_for_rp(rp_scope)?;
1323                    Ok(())
1324                } else {
1325                    info_now!("failed pinauth!");
1326                    self.state.decrement_retries(&mut self.trussed)?;
1327                    let maybe_blocked = self.state.pin_blocked();
1328                    if maybe_blocked.is_err() {
1329                        info_now!("blocked");
1330                        maybe_blocked
1331                    } else {
1332                        info_now!("pinAuthInvalid");
1333                        Err(Error::PinAuthInvalid)
1334                    }
1335                }
1336            }
1337
1338            // don't need the PIN auth, they're continuations
1339            // of already checked CredMgmt subcommands
1340            Subcommand::EnumerateRpsGetNextRp
1341            | Subcommand::EnumerateCredentialsGetNextCredential => Ok(()),
1342
1343            _ => Err(Error::InvalidParameter),
1344        }
1345    }
1346
1347    /// Returns whether UV was performed.
1348    fn pin_prechecks(
1349        &mut self,
1350        options: &Option<ctap2::AuthenticatorOptions>,
1351        pin_auth: Option<&[u8]>,
1352        pin_protocol: Option<u32>,
1353        data: &[u8],
1354        permissions: Permissions,
1355        rp_id: &str,
1356    ) -> Result<bool> {
1357        // 1. pinAuth zero length -> wait for user touch, then
1358        // return PinNotSet if not set, PinInvalid if set
1359        //
1360        // the idea is for multi-authnr scenario where platform
1361        // wants to enforce PIN and needs to figure out which authnrs support PIN
1362        if let Some(pin_auth) = pin_auth {
1363            if pin_auth.is_empty() {
1364                self.up
1365                    .user_present(&mut self.trussed, constants::FIDO2_UP_TIMEOUT)?;
1366                if !self.state.persistent.pin_is_set() {
1367                    return Err(Error::PinNotSet);
1368                } else {
1369                    return Err(Error::PinAuthInvalid);
1370                }
1371            }
1372        }
1373
1374        // 2. check PIN protocol is 1 if pinAuth was sent
1375        let pin_protocol = if pin_auth.is_some() {
1376            let pin_protocol = pin_protocol.ok_or(Error::MissingParameter)?;
1377            let pin_protocol = self.parse_pin_protocol(pin_protocol)?;
1378            Some(pin_protocol)
1379        } else {
1380            None
1381        };
1382
1383        // 3. if no PIN is set (we have no other form of UV),
1384        // and platform sent `uv` or `pinAuth`, return InvalidOption
1385        if !self.state.persistent.pin_is_set() {
1386            if let Some(ref options) = &options {
1387                if Some(true) == options.uv {
1388                    return Err(Error::InvalidOption);
1389                }
1390            }
1391            if pin_auth.is_some() {
1392                return Err(Error::InvalidOption);
1393            }
1394        }
1395
1396        // 4. If authenticator is protected by som form of user verification, do it
1397
1398        // Reject uv = true as we do not support built-in user verification
1399        if pin_auth.is_none() && options.as_ref().and_then(|options| options.uv) == Some(true) {
1400            return Err(Error::InvalidOption);
1401        }
1402
1403        if self.state.persistent.pin_is_set() {
1404            // let mut uv_performed = false;
1405            if let Some(pin_auth) = pin_auth {
1406                // seems a bit redundant to check here in light of 2.
1407                // I guess the CTAP spec writers aren't implementers :D
1408                if let Some(pin_protocol) = pin_protocol {
1409                    // 5. if pinAuth is present and pinProtocol = 1, verify
1410                    // success --> set uv = 1
1411                    // error --> PinAuthInvalid
1412                    let mut pin_protocol = self.pin_protocol(pin_protocol);
1413                    let pin_token = pin_protocol.verify_pin_token(data, pin_auth)?;
1414                    pin_token.require_permissions(permissions)?;
1415                    pin_token.require_valid_for_rp(RpScope::RpId(rp_id))?;
1416
1417                    return Ok(true);
1418                } else {
1419                    // 7. pinAuth present + pinProtocol != 1 --> error PinAuthInvalid
1420                    return Err(Error::PinAuthInvalid);
1421                }
1422            } else {
1423                // 6. pinAuth not present + clientPin set + rk = true --> error PinRequired
1424                if options.as_ref().and_then(|options| options.rk) == Some(true) {
1425                    return Err(Error::PinRequired);
1426                }
1427            }
1428        }
1429
1430        Ok(false)
1431    }
1432
1433    #[inline(never)]
1434    fn check_key_exists(&mut self, alg: i32, key: &Key) -> bool {
1435        match key {
1436            // TODO: should check if wrapped key is valid AEAD
1437            // On the other hand, we already decrypted a valid AEAD
1438            Key::WrappedKey(_) => true,
1439            Key::ResidentKey(key) => {
1440                debug_now!("checking if ResidentKey {:?} exists", key);
1441                SigningAlgorithm::try_from(alg)
1442                    .map(|alg| syscall!(self.trussed.exists(alg.mechanism(), *key)).exists)
1443                    .unwrap_or_default()
1444            }
1445        }
1446    }
1447
1448    #[inline(never)]
1449    fn process_assertion_extensions(
1450        &mut self,
1451        get_assertion_state: &state::ActiveGetAssertionData,
1452        extensions: &ctap2::get_assertion::ExtensionsInput,
1453        credential: &Credential,
1454        credential_key: KeyId,
1455    ) -> Result<Option<ctap2::get_assertion::ExtensionsOutput>> {
1456        let mut output = ctap2::get_assertion::ExtensionsOutput::default();
1457
1458        if let Some(hmac_secret) = &extensions.hmac_secret {
1459            let pin_protocol = hmac_secret
1460                .pin_protocol
1461                .map(|i| self.parse_pin_protocol(i))
1462                .transpose()?
1463                .unwrap_or(PinProtocolVersion::V1);
1464
1465            if !get_assertion_state.up_performed {
1466                return Err(Error::UnsupportedOption);
1467            }
1468
1469            // We derive credRandom as an hmac of the existing private key.
1470            // UV is used as input data since credRandom should depend UV
1471            // i.e. credRandom = HMAC(private_key, uv)
1472            let cred_random = syscall!(self.trussed.derive_key(
1473                Mechanism::HmacSha256,
1474                credential_key,
1475                Some(Bytes::from(&[get_assertion_state.uv_performed as u8])),
1476                StorageAttributes::new().set_persistence(Location::Volatile)
1477            ))
1478            .key;
1479
1480            // Verify the auth tag, which uses the same process as the pinAuth
1481            let mut pin_protocol = self.pin_protocol(pin_protocol);
1482            let shared_secret = pin_protocol.shared_secret(&hmac_secret.key_agreement)?;
1483            pin_protocol.verify_pin_auth(
1484                &shared_secret,
1485                &hmac_secret.salt_enc,
1486                &hmac_secret.salt_auth,
1487            )?;
1488
1489            // decrypt input salt_enc to get salt1 or (salt1 || salt2)
1490            let salts = shared_secret
1491                .decrypt(&mut self.trussed, &hmac_secret.salt_enc)
1492                .ok_or(Error::InvalidOption)?;
1493
1494            if salts.len() != 32 && salts.len() != 64 {
1495                debug_now!("invalid hmac-secret length");
1496                return Err(Error::InvalidLength);
1497            }
1498
1499            let mut salt_output: Bytes<64> = Bytes::new();
1500
1501            // output1 = hmac_sha256(credRandom, salt1)
1502            let output1 =
1503                syscall!(self.trussed.sign_hmacsha256(cred_random, &salts[0..32])).signature;
1504
1505            salt_output.extend_from_slice(&output1).unwrap();
1506
1507            if salts.len() == 64 {
1508                // output2 = hmac_sha256(credRandom, salt2)
1509                let output2 =
1510                    syscall!(self.trussed.sign_hmacsha256(cred_random, &salts[32..64])).signature;
1511
1512                salt_output.extend_from_slice(&output2).unwrap();
1513            }
1514
1515            syscall!(self.trussed.delete(cred_random));
1516
1517            // output_enc = aes256-cbc(sharedSecret, IV=0, output1 || output2)
1518            let output_enc = shared_secret.encrypt(&mut self.trussed, &salt_output);
1519
1520            shared_secret.delete(&mut self.trussed);
1521
1522            output.hmac_secret = Some(Bytes::try_from(&*output_enc).unwrap());
1523        }
1524
1525        if extensions.third_party_payment.unwrap_or_default() {
1526            output.third_party_payment = Some(credential.third_party_payment().unwrap_or_default());
1527        }
1528
1529        Ok(output.is_set().then_some(output))
1530    }
1531
1532    #[inline(never)]
1533    fn assert_with_credential(
1534        &mut self,
1535        num_credentials: Option<u32>,
1536        credential: Credential,
1537    ) -> Result<ctap2::get_assertion::Response> {
1538        let data = self.state.runtime.active_get_assertion.clone().unwrap();
1539        let rp_id_hash = &data.rp_id_hash;
1540
1541        let (key, is_rk) = match credential.key().clone() {
1542            Key::ResidentKey(key) => (key, true),
1543            Key::WrappedKey(bytes) => {
1544                let wrapping_key = self.state.persistent.key_wrapping_key(&mut self.trussed)?;
1545                // info_now!("unwrapping {:?} with wrapping key {:?}", &bytes, &wrapping_key);
1546                let key_result = syscall!(self.trussed.unwrap_key_chacha8poly1305(
1547                    wrapping_key,
1548                    &bytes,
1549                    &[],
1550                    Location::Volatile,
1551                ))
1552                .key;
1553                // debug_now!("key result: {:?}", &key_result);
1554                info_now!("key result");
1555                match key_result {
1556                    Some(key) => (key, false),
1557                    None => {
1558                        return Err(Error::Other);
1559                    }
1560                }
1561            }
1562        };
1563
1564        // 8. process any extensions present
1565        let mut large_blob_key_requested = false;
1566        let extensions_output = if let Some(extensions) = &data.extensions {
1567            if self.config.supports_large_blobs() {
1568                if extensions.large_blob_key == Some(false) {
1569                    // large_blob_key must be Some(true) or omitted
1570                    return Err(Error::InvalidOption);
1571                }
1572                large_blob_key_requested = extensions.large_blob_key == Some(true);
1573            }
1574            self.process_assertion_extensions(&data, extensions, &credential, key)?
1575        } else {
1576            None
1577        };
1578
1579        // 9./10. sign clientDataHash || authData with "first" credential
1580
1581        // info_now!("signing with credential {:?}", &credential);
1582        let kek = self
1583            .state
1584            .persistent
1585            .key_encryption_key(&mut self.trussed)?;
1586        let credential_id = credential.id(&mut self.trussed, kek, rp_id_hash)?;
1587
1588        use ctap2::AuthenticatorDataFlags as Flags;
1589
1590        let sig_count = self.state.persistent.timestamp(&mut self.trussed)?;
1591
1592        let authenticator_data = ctap2::get_assertion::AuthenticatorData {
1593            rp_id_hash,
1594
1595            flags: {
1596                let mut flags = Flags::empty();
1597                if data.up_performed {
1598                    flags |= Flags::USER_PRESENCE;
1599                }
1600                if data.uv_performed {
1601                    flags |= Flags::USER_VERIFIED;
1602                }
1603                if extensions_output.is_some() {
1604                    flags |= Flags::EXTENSION_DATA;
1605                }
1606                flags
1607            },
1608
1609            sign_count: sig_count,
1610            attested_credential_data: None,
1611            extensions: extensions_output,
1612        };
1613
1614        let serialized_auth_data = authenticator_data.serialize()?;
1615
1616        let mut commitment = Bytes::<1024>::new();
1617        commitment
1618            .extend_from_slice(&serialized_auth_data)
1619            .map_err(|_| Error::Other)?;
1620        commitment
1621            .extend_from_slice(&data.client_data_hash)
1622            .map_err(|_| Error::Other)?;
1623
1624        let signing_algorithm =
1625            SigningAlgorithm::try_from(credential.algorithm()).map_err(|_| Error::Other)?;
1626        let signature =
1627            Bytes::try_from(&*signing_algorithm.sign(&mut self.trussed, key, &commitment)).unwrap();
1628
1629        // select preferred format or skip attestation statement
1630        let att_stmt_fmt = data
1631            .attestation_formats_preference
1632            .as_ref()
1633            .and_then(SupportedAttestationFormat::select);
1634        let att_stmt = if let Some(format) = att_stmt_fmt {
1635            match format {
1636                SupportedAttestationFormat::None => {
1637                    Some(AttestationStatement::None(NoneAttestationStatement {}))
1638                }
1639                SupportedAttestationFormat::Packed => {
1640                    let (attestation_maybe, _) = self.state.identity.attestation(&mut self.trussed);
1641                    let (signature, attestation_algorithm) = {
1642                        if let Some(attestation) = attestation_maybe.as_ref() {
1643                            let signing_algorithm = SigningAlgorithm::P256;
1644                            let signature = signing_algorithm.sign(
1645                                &mut self.trussed,
1646                                attestation.0,
1647                                &commitment,
1648                            );
1649                            (
1650                                Bytes::try_from(&*signature).map_err(|_| Error::Other)?,
1651                                signing_algorithm.into(),
1652                            )
1653                        } else {
1654                            (signature.clone(), credential.algorithm())
1655                        }
1656                    };
1657                    let packed = PackedAttestationStatement {
1658                        alg: attestation_algorithm,
1659                        sig: signature,
1660                        x5c: attestation_maybe.as_ref().map(|attestation| {
1661                            // See: https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
1662                            let cert = attestation.1.clone();
1663                            let mut x5c = Vec::new();
1664                            x5c.push(cert).ok();
1665                            x5c
1666                        }),
1667                    };
1668                    Some(AttestationStatement::Packed(packed))
1669                }
1670            }
1671        } else {
1672            None
1673        };
1674
1675        if !is_rk {
1676            syscall!(self.trussed.delete(key));
1677        }
1678
1679        let mut response = ctap2::get_assertion::ResponseBuilder {
1680            credential: credential_id.into(),
1681            auth_data: serialized_auth_data,
1682            signature,
1683        }
1684        .build();
1685        response.number_of_credentials = num_credentials;
1686        response.att_stmt = att_stmt;
1687
1688        // User with empty IDs are ignored for compatibility
1689        if is_rk {
1690            if let Credential::Full(credential) = &credential {
1691                if !credential.user.id().is_empty() {
1692                    let mut user: PublicKeyCredentialUserEntity = credential.user.clone().into();
1693                    // User identifiable information (name, DisplayName, icon) MUST not
1694                    // be returned if user verification is not done by the authenticator.
1695                    // For single account per RP case, authenticator returns "id" field.
1696                    if !data.uv_performed || !data.multiple_credentials {
1697                        user.icon = None;
1698                        user.name = None;
1699                        user.display_name = None;
1700                    }
1701                    response.user = Some(user);
1702                }
1703            }
1704
1705            if large_blob_key_requested {
1706                debug!("Sending largeBlobKey in getAssertion");
1707                response.large_blob_key = match credential {
1708                    Credential::Stripped(stripped) => stripped.large_blob_key,
1709                    Credential::Full(full) => full.data.large_blob_key,
1710                };
1711            }
1712        }
1713
1714        Ok(response)
1715    }
1716
1717    #[inline(never)]
1718    fn delete_resident_key_by_user_id(
1719        &mut self,
1720        rp_id_hash: &[u8; 32],
1721        user_id: &Bytes<64>,
1722    ) -> Result<()> {
1723        // Prepare to iterate over all credentials associated to RP.
1724        let file_name_prefix = rp_file_name_prefix(rp_id_hash);
1725        let mut maybe_entry = syscall!(self.trussed.read_dir_first_alphabetical(
1726            Location::Internal,
1727            PathBuf::from(RK_DIR),
1728            Some(file_name_prefix.clone())
1729        ))
1730        .entry;
1731
1732        while let Some(entry) = maybe_entry.take() {
1733            if !entry
1734                .file_name()
1735                .as_ref()
1736                .starts_with(file_name_prefix.as_ref())
1737            {
1738                // We got past all credentials for the relevant RP
1739                break;
1740            }
1741
1742            if entry.file_name() == &*file_name_prefix {
1743                debug_assert!(entry.metadata().is_dir());
1744                error!("Migration missing");
1745                return Err(Error::Other);
1746            }
1747
1748            info_now!("this may be an RK: {:?}", &entry);
1749            let rk_path = PathBuf::from(entry.path());
1750
1751            info_now!("checking RK {:?} for userId ", &rk_path);
1752            let credential_data =
1753                syscall!(self.trussed.read_file(Location::Internal, rk_path.clone(),)).data;
1754            let credential_maybe = FullCredential::deserialize(&credential_data);
1755
1756            if let Ok(old_credential) = credential_maybe {
1757                if old_credential.user.id() == user_id {
1758                    match old_credential.key {
1759                        credential::Key::ResidentKey(key) => {
1760                            info_now!(":: deleting resident key");
1761                            syscall!(self.trussed.delete(key));
1762                        }
1763                        _ => {
1764                            warn_now!(":: WARNING: unexpected server credential in rk.");
1765                        }
1766                    }
1767                    syscall!(self.trussed.remove_file(Location::Internal, rk_path,));
1768
1769                    info_now!("Overwriting previous rk tied to this userId.");
1770                    break;
1771                }
1772            } else {
1773                warn_now!("WARNING: Could not read RK.");
1774            }
1775
1776            // prepare for next loop iteration
1777            maybe_entry = syscall!(self.trussed.read_dir_next()).entry;
1778        }
1779
1780        Ok(())
1781    }
1782
1783    #[inline(never)]
1784    pub(crate) fn delete_resident_key_by_path(&mut self, rk_path: &Path) -> Result<()> {
1785        info_now!("deleting RK {:?}", &rk_path);
1786        let credential_data = syscall!(self
1787            .trussed
1788            .read_file(Location::Internal, PathBuf::from(rk_path),))
1789        .data;
1790        let credential_maybe = FullCredential::deserialize(&credential_data);
1791        // info_now!("deleting credential {:?}", &credential);
1792
1793        if let Ok(credential) = credential_maybe {
1794            match credential.key {
1795                credential::Key::ResidentKey(key) => {
1796                    info_now!(":: deleting resident key");
1797                    syscall!(self.trussed.delete(key));
1798                }
1799                credential::Key::WrappedKey(_) => {}
1800            }
1801        } else {
1802            // If for some reason there becomes a corrupt credential,
1803            // we can still at least orphan the key rather then crash.
1804            info_now!("Warning!  Orpaning a key.");
1805        }
1806
1807        info_now!(":: deleting RK file {:?} itself", &rk_path);
1808        syscall!(self
1809            .trussed
1810            .remove_file(Location::Internal, PathBuf::from(rk_path),));
1811
1812        Ok(())
1813    }
1814
1815    fn large_blobs_get(
1816        &mut self,
1817        request: &ctap2::large_blobs::Request,
1818        config: large_blobs::Config,
1819        length: u32,
1820    ) -> Result<ctap2::large_blobs::Response> {
1821        debug!(
1822            "large_blobs_get: length = {length}, offset = {}",
1823            request.offset
1824        );
1825        // 1.-2. Validate parameters
1826        if request.length.is_some()
1827            || request.pin_uv_auth_param.is_some()
1828            || request.pin_uv_auth_protocol.is_some()
1829        {
1830            error!("length/pin set");
1831            return Err(Error::InvalidParameter);
1832        }
1833        // 3. Validate length
1834        let Ok(length) = usize::try_from(length) else {
1835            return Err(Error::InvalidLength);
1836        };
1837        if length > self.config.max_msg_size.saturating_sub(64) {
1838            return Err(Error::InvalidLength);
1839        }
1840        // 4. Validate offset
1841        let Ok(offset) = usize::try_from(request.offset) else {
1842            error!("offset too large");
1843            return Err(Error::InvalidParameter);
1844        };
1845        let stored_length = large_blobs::size(&mut self.trussed, config.location)?;
1846        if offset > stored_length {
1847            error!("offset: {offset}, stored_length: {stored_length}");
1848            return Err(Error::InvalidParameter);
1849        };
1850        // 5. Return requested data
1851        info!("Reading large-blob array from offset {offset}");
1852        let data = large_blobs::read_chunk(&mut self.trussed, config.location, offset, length)?;
1853        let mut response = ctap2::large_blobs::Response::default();
1854        response.config = Some(data);
1855        Ok(response)
1856    }
1857
1858    fn large_blobs_set(
1859        &mut self,
1860        request: &ctap2::large_blobs::Request,
1861        config: large_blobs::Config,
1862        data: &[u8],
1863    ) -> Result<ctap2::large_blobs::Response> {
1864        debug!(
1865            "large_blobs_set: |data| = {}, offset = {}, length = {:?}",
1866            data.len(),
1867            request.offset,
1868            request.length
1869        );
1870        // 1. Validate data
1871        if data.len() > self.config.max_msg_size.saturating_sub(64) {
1872            return Err(Error::InvalidLength);
1873        }
1874        if request.offset == 0 {
1875            // 2. Calculate expected length and offset
1876            // 2.1. Require length
1877            let Some(length) = request.length else {
1878                return Err(Error::InvalidParameter);
1879            };
1880            // 2.2. Check that length is not too big
1881            let Ok(length) = usize::try_from(length) else {
1882                return Err(Error::LargeBlobStorageFull);
1883            };
1884            if length > config.max_size() {
1885                return Err(Error::LargeBlobStorageFull);
1886            }
1887            // 2.3. Check that length is not too small
1888            if length < large_blobs::MIN_SIZE {
1889                return Err(Error::InvalidParameter);
1890            }
1891            // 2.4-5. Set expected length and offset
1892            self.state.runtime.large_blobs.expected_length = length;
1893            self.state.runtime.large_blobs.expected_next_offset = 0;
1894        } else {
1895            // 3. Validate parameters
1896            if request.length.is_some() {
1897                return Err(Error::InvalidParameter);
1898            }
1899        }
1900
1901        // 4. Validate offset
1902        let Ok(offset) = usize::try_from(request.offset) else {
1903            return Err(Error::InvalidSeq);
1904        };
1905        if offset != self.state.runtime.large_blobs.expected_next_offset {
1906            return Err(Error::InvalidSeq);
1907        }
1908
1909        // 5. Perform uv
1910        // TODO: support alwaysUv
1911        if self.state.persistent.pin_is_set() {
1912            let Some(pin_uv_auth_param) = request.pin_uv_auth_param else {
1913                return Err(Error::PinRequired);
1914            };
1915            let Some(pin_uv_auth_protocol) = request.pin_uv_auth_protocol else {
1916                return Err(Error::PinRequired);
1917            };
1918            if pin_uv_auth_protocol != 1 {
1919                return Err(Error::PinAuthInvalid);
1920            }
1921            let pin_protocol = self.parse_pin_protocol(pin_uv_auth_protocol)?;
1922            // TODO: check pinUvAuthToken
1923            let pin_auth: [u8; 16] = pin_uv_auth_param
1924                .as_ref()
1925                .try_into()
1926                .map_err(|_| Error::PinAuthInvalid)?;
1927
1928            let mut auth_data: Bytes<70> = Bytes::new();
1929            // 32x 0xff
1930            auth_data.resize(32, 0xff).unwrap();
1931            // h'0c00'
1932            auth_data.push(0x0c).unwrap();
1933            auth_data.push(0x00).unwrap();
1934            // uint32LittleEndian(offset)
1935            auth_data
1936                .extend_from_slice(&request.offset.to_le_bytes())
1937                .unwrap();
1938            // SHA-256(data)
1939            auth_data.extend_from_slice(&Sha256::digest(data)).unwrap();
1940
1941            let mut pin_protocol = self.pin_protocol(pin_protocol);
1942            let pin_token = pin_protocol.verify_pin_token(&pin_auth, &auth_data)?;
1943            pin_token.require_permissions(Permissions::LARGE_BLOB_WRITE)?;
1944        }
1945
1946        // 6. Validate data length
1947        if offset + data.len() > self.state.runtime.large_blobs.expected_length {
1948            return Err(Error::InvalidParameter);
1949        }
1950
1951        // 7.-11. Write the buffer
1952        info!("Writing large-blob array to offset {offset}");
1953        large_blobs::write_chunk(
1954            &mut self.trussed,
1955            &mut self.state.runtime.large_blobs,
1956            config.location,
1957            data,
1958        )?;
1959
1960        Ok(ctap2::large_blobs::Response::default())
1961    }
1962}
1963
1964#[derive(Clone, Copy, Debug)]
1965enum SupportedAttestationFormat {
1966    None,
1967    Packed,
1968}
1969
1970impl SupportedAttestationFormat {
1971    fn select(preference: &AttestationFormatsPreference) -> Option<Self> {
1972        if preference.known_formats() == [AttestationStatementFormat::None]
1973            && !preference.includes_unknown_formats()
1974        {
1975            // platform requested only None --> omit attestation statement
1976            return None;
1977        }
1978        // use first known and supported format, or default to packed format
1979        let format = preference
1980            .known_formats()
1981            .iter()
1982            .copied()
1983            .flat_map(Self::try_from)
1984            .next()
1985            .unwrap_or(Self::Packed);
1986        Some(format)
1987    }
1988}
1989
1990impl From<SupportedAttestationFormat> for AttestationStatementFormat {
1991    fn from(format: SupportedAttestationFormat) -> Self {
1992        match format {
1993            SupportedAttestationFormat::None => Self::None,
1994            SupportedAttestationFormat::Packed => Self::Packed,
1995        }
1996    }
1997}
1998
1999impl TryFrom<AttestationStatementFormat> for SupportedAttestationFormat {
2000    type Error = Error;
2001
2002    fn try_from(format: AttestationStatementFormat) -> core::result::Result<Self, Self::Error> {
2003        match format {
2004            AttestationStatementFormat::None => Ok(Self::None),
2005            AttestationStatementFormat::Packed => Ok(Self::Packed),
2006            _ => Err(Error::Other),
2007        }
2008    }
2009}
2010
2011// The new path scheme for disvoerable credentials (= resident keys) is:
2012//   rk/<rp_id_hash>.<credential_id_hash>
2013// The hashes are truncated to the first eight bytes and formatted as hex strings.
2014// We use the following terms for the components:
2015//   rk_path:              rk/<rp_id_hash>.<credential_id_hash>
2016//   rp_file_name_prefix:  <rp_id_hash>
2017
2018fn rp_file_name_prefix(rp_id_hash: &[u8; 32]) -> PathBuf {
2019    let mut hex = [b'0'; 16];
2020    super::format_hex(&rp_id_hash[..8], &mut hex);
2021    PathBuf::try_from(&hex).unwrap()
2022}
2023
2024fn rk_path(rp_id_hash: &[u8; 32], credential_id_hash: &[u8; 32]) -> PathBuf {
2025    // 16 bytes per hash + dot + trailing zero = 34
2026    let mut buf = [0; 34];
2027    buf[16] = b'.';
2028    format_hex(&rp_id_hash[..8], &mut buf[..16]);
2029    format_hex(&credential_id_hash[..8], &mut buf[17..33]);
2030
2031    let mut path = PathBuf::from(RK_DIR);
2032    path.push(Path::from_bytes_with_nul(&buf).unwrap());
2033    path
2034}
2035
2036#[cfg(test)]
2037mod tests {
2038    use super::{rk_path, rp_file_name_prefix};
2039
2040    const TEST_HASH: &[u8; 32] = &[
2041        134, 54, 157, 96, 10, 28, 233, 79, 219, 59, 195, 125, 165, 251, 120, 14, 49, 152, 212, 191,
2042        114, 137, 180, 207, 255, 177, 187, 106, 173, 1, 203, 171,
2043    ];
2044    const TEST_HASH_HEX: &str = "86369d600a1ce94f";
2045
2046    #[test]
2047    fn test_rp_file_name_prefix() {
2048        assert_eq!(rp_file_name_prefix(&[0; 32]).as_str(), "0000000000000000");
2049        assert_eq!(rp_file_name_prefix(TEST_HASH).as_str(), TEST_HASH_HEX);
2050    }
2051
2052    #[test]
2053    fn test_rk_path() {
2054        fn test(rp_id_hash: &[u8; 32], credential_id_hash: &[u8; 32], expected: &str) {
2055            println!("rp_id_hash: {rp_id_hash:?}");
2056            println!("credential_id_hash: {credential_id_hash:?}");
2057            let actual = rk_path(rp_id_hash, credential_id_hash);
2058            assert_eq!(actual.as_str(), expected);
2059        }
2060
2061        let input_zero = &[0; 32];
2062        let output_zero = "0000000000000000";
2063        let input_nonzero = TEST_HASH;
2064        let output_nonzero = TEST_HASH_HEX;
2065
2066        test(
2067            input_zero,
2068            input_zero,
2069            &format!("rk/{output_zero}.{output_zero}"),
2070        );
2071        test(
2072            input_zero,
2073            input_nonzero,
2074            &format!("rk/{output_zero}.{output_nonzero}"),
2075        );
2076        test(
2077            input_nonzero,
2078            input_zero,
2079            &format!("rk/{output_nonzero}.{output_zero}"),
2080        );
2081        test(
2082            input_nonzero,
2083            input_nonzero,
2084            &format!("rk/{output_nonzero}.{output_nonzero}"),
2085        );
2086    }
2087}