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_slice(&aaguid).unwrap();
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(wrapped_key.to_bytes().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: signature.to_bytes().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 => syscall!(self.trussed.debug_dump_store()),
904            _ => return Err(Error::InvalidCommand),
905        };
906
907        Ok(())
908    }
909
910    #[inline(never)]
911    fn get_assertion(
912        &mut self,
913        parameters: &ctap2::get_assertion::Request,
914    ) -> Result<ctap2::get_assertion::Response> {
915        debug_now!("remaining stack size: {} bytes", msp() - 0x2000_0000);
916
917        let rp_id_hash = self.hash(parameters.rp_id.as_ref());
918
919        // 1-4.
920        let uv_performed = match self.pin_prechecks(
921            &parameters.options,
922            parameters.pin_auth.map(AsRef::as_ref),
923            parameters.pin_protocol,
924            parameters.client_data_hash.as_ref(),
925            Permissions::GET_ASSERTION,
926            parameters.rp_id,
927        ) {
928            Ok(b) => b,
929            Err(Error::PinRequired) => {
930                // UV is optional for get_assertion
931                false
932            }
933            Err(err) => return Err(err),
934        };
935
936        // 5. Locate eligible credentials
937        //
938        // Note: If allowList is passed, credential is Some(credential)
939        // If no allowList is passed, credential is None and the retrieved credentials
940        // are stored in state.runtime.credential_heap
941        let (credential, num_credentials) = self
942            .prepare_credentials(&rp_id_hash, &parameters.allow_list, uv_performed)?
943            .ok_or(Error::NoCredentials)?;
944
945        info_now!("found {:?} applicable credentials", num_credentials);
946        info_now!("{:?}", &credential);
947
948        // 6. process any options present
949
950        // RK is not supported in get_assertion
951        if parameters
952            .options
953            .as_ref()
954            .and_then(|options| options.rk)
955            .is_some()
956        {
957            return Err(Error::InvalidOption);
958        }
959
960        // UP occurs by default, but option could specify not to.
961        let do_up = if let Some(options) = parameters.options.as_ref() {
962            options.up.unwrap_or(true)
963        } else {
964            true
965        };
966
967        // 7. collect user presence
968        let up_performed = if do_up {
969            if !self.skip_up_check() {
970                info_now!("asking for up");
971                self.up
972                    .user_present(&mut self.trussed, constants::FIDO2_UP_TIMEOUT)?;
973            }
974            true
975        } else {
976            info_now!("not asking for up");
977            false
978        };
979
980        let multiple_credentials = num_credentials > 1;
981        self.state.runtime.active_get_assertion = Some(state::ActiveGetAssertionData {
982            rp_id_hash: {
983                let mut buf = [0u8; 32];
984                buf.copy_from_slice(&rp_id_hash);
985                buf
986            },
987            client_data_hash: {
988                let mut buf = [0u8; 32];
989                buf.copy_from_slice(parameters.client_data_hash);
990                buf
991            },
992            uv_performed,
993            up_performed,
994            multiple_credentials,
995            extensions: parameters.extensions.clone(),
996            attestation_formats_preference: parameters.attestation_formats_preference.clone(),
997        });
998
999        let num_credentials = match num_credentials {
1000            1 => None,
1001            n => Some(n),
1002        };
1003
1004        self.assert_with_credential(num_credentials, credential)
1005    }
1006
1007    #[inline(never)]
1008    fn large_blobs(
1009        &mut self,
1010        request: &ctap2::large_blobs::Request,
1011    ) -> Result<ctap2::large_blobs::Response> {
1012        let Some(config) = self.config.large_blobs else {
1013            return Err(Error::InvalidCommand);
1014        };
1015
1016        // 1. offset is validated by serde
1017
1018        // 2.-3. Exactly one of get or set must be present
1019        match (request.get, request.set) {
1020            (None, None) | (Some(_), Some(_)) => Err(Error::InvalidParameter),
1021            // 4. Implement get subcommand
1022            (Some(get), None) => self.large_blobs_get(request, config, get),
1023            // 5. Implement set subcommand
1024            (None, Some(set)) => self.large_blobs_set(request, config, set),
1025        }
1026    }
1027}
1028
1029// impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenticator<UP, T>
1030impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
1031    fn parse_pin_protocol(&self, version: impl TryInto<u8>) -> Result<PinProtocolVersion> {
1032        if let Ok(version) = version.try_into() {
1033            for pin_protocol in self.pin_protocols() {
1034                if u8::from(*pin_protocol) == version {
1035                    return Ok(*pin_protocol);
1036                }
1037            }
1038        }
1039        Err(Error::InvalidParameter)
1040    }
1041
1042    // This is the single source of truth for the supported PIN protocols.
1043    fn pin_protocols(&self) -> &'static [PinProtocolVersion] {
1044        &[PinProtocolVersion::V2, PinProtocolVersion::V1]
1045    }
1046
1047    fn pin_protocol(&mut self, pin_protocol: PinProtocolVersion) -> PinProtocol<'_, T> {
1048        let state = self.state.runtime.pin_protocol(&mut self.trussed);
1049        PinProtocol::new(&mut self.trussed, state, pin_protocol)
1050    }
1051
1052    #[inline(never)]
1053    fn check_credential_applicable(
1054        &mut self,
1055        credential: &Credential,
1056        allowlist_passed: bool,
1057        uv_performed: bool,
1058    ) -> bool {
1059        if !self.check_key_exists(credential.algorithm(), credential.key()) {
1060            return false;
1061        }
1062
1063        if !{
1064            use credential::CredentialProtectionPolicy as Policy;
1065            debug_now!("CredentialProtectionPolicy {:?}", credential.cred_protect());
1066            match credential.cred_protect() {
1067                None | Some(Policy::Optional) => true,
1068                Some(Policy::OptionalWithCredentialIdList) => allowlist_passed || uv_performed,
1069                Some(Policy::Required) => uv_performed,
1070            }
1071        } {
1072            return false;
1073        }
1074        true
1075    }
1076
1077    #[inline(never)]
1078    fn prepare_credentials(
1079        &mut self,
1080        rp_id_hash: &[u8; 32],
1081        allow_list: &Option<ctap2::get_assertion::AllowList>,
1082        uv_performed: bool,
1083    ) -> Result<Option<(Credential, u32)>> {
1084        debug_now!("remaining stack size: {} bytes", msp() - 0x2000_0000);
1085
1086        self.state.runtime.clear_credential_cache();
1087        self.state.runtime.active_get_assertion = None;
1088
1089        // NB: CTAP 2.1 specifies to return the first applicable credential, and set
1090        // numberOfCredentials to None.
1091        // However, CTAP 2.0 says to send numberOfCredentials that are applicable,
1092        // which implies we'd have to respond to GetNextAssertion.
1093        //
1094        // We are using CTAP 2.1 behaviour here, as it allows us not to cache the (length)
1095        // credential IDs. Presumably, most clients use this to just get any old signatures,
1096        // but we did change the github.com/solokeys/fido2-tests to accommodate this change
1097        // of behaviour.
1098        if let Some(allow_list) = allow_list {
1099            debug_now!("Allowlist of len {} passed, filtering", allow_list.len());
1100            // we will have at most one credential, and an empty cache.
1101
1102            // client is not supposed to send Some(empty list):
1103            // <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>
1104            // but some still do (and CTAP 2.0 does not rule it out).
1105            // they probably meant to send None.
1106            if !allow_list.is_empty() {
1107                for credential_id in allow_list {
1108                    let credential = match Credential::try_from(self, rp_id_hash, credential_id) {
1109                        Ok(credential) => credential,
1110                        _ => continue,
1111                    };
1112
1113                    if !self.check_credential_applicable(&credential, true, uv_performed) {
1114                        continue;
1115                    }
1116
1117                    return Ok(Some((credential, 1)));
1118                }
1119
1120                // we don't recognize any credentials in the allowlist
1121                return Ok(None);
1122            }
1123        }
1124
1125        // we are only dealing with discoverable credentials.
1126        debug_now!("Allowlist not passed, fetching RKs");
1127        self.prepare_cache(rp_id_hash, uv_performed)?;
1128
1129        let num_credentials = self.state.runtime.remaining_credentials();
1130        let credential = self.state.runtime.pop_credential(&mut self.trussed);
1131        Ok(credential.map(|credential| (Credential::Full(credential), num_credentials)))
1132    }
1133
1134    /// Populate the cache with the RP credentials.
1135    #[inline(never)]
1136    fn prepare_cache(&mut self, rp_id_hash: &[u8; 32], uv_performed: bool) -> Result<()> {
1137        use crate::state::CachedCredential;
1138        use core::str::FromStr;
1139
1140        let file_name_prefix = rp_file_name_prefix(rp_id_hash);
1141        let mut maybe_entry = syscall!(self.trussed.read_dir_first_alphabetical(
1142            Location::Internal,
1143            PathBuf::from(RK_DIR),
1144            Some(file_name_prefix.clone())
1145        ))
1146        .entry;
1147
1148        while let Some(entry) = maybe_entry.take() {
1149            if !entry
1150                .file_name()
1151                .as_ref()
1152                .starts_with(file_name_prefix.as_ref())
1153            {
1154                // We got past all credentials for the relevant RP
1155                break;
1156            }
1157
1158            if entry.file_name() == &*file_name_prefix {
1159                debug_assert!(entry.metadata().is_dir());
1160                error!("Migration missing");
1161                return Err(Error::Other);
1162            }
1163
1164            let credential_data = syscall!(self
1165                .trussed
1166                .read_file(Location::Internal, entry.path().into(),))
1167            .data;
1168
1169            let credential = FullCredential::deserialize(&credential_data).map_err(|_err| {
1170                error!("Failed to deserialize credential: {_err:?}");
1171                Error::Other
1172            })?;
1173            let timestamp = credential.creation_time;
1174            let credential = Credential::Full(credential);
1175
1176            if self.check_credential_applicable(&credential, false, uv_performed) {
1177                self.state.runtime.push_credential(CachedCredential {
1178                    timestamp,
1179                    path: String::from_str(entry.path().as_str_ref_with_trailing_nul())
1180                        .map_err(|_| Error::Other)?,
1181                });
1182            }
1183
1184            maybe_entry = syscall!(self.trussed.read_dir_next()).entry;
1185        }
1186        Ok(())
1187    }
1188
1189    fn decrypt_pin_hash_and_maybe_escalate(
1190        &mut self,
1191        pin_protocol: PinProtocolVersion,
1192        shared_secret: &SharedSecret,
1193        pin_hash_enc: &[u8],
1194    ) -> Result<()> {
1195        let pin_hash = shared_secret
1196            .decrypt(&mut self.trussed, pin_hash_enc)
1197            .ok_or(Error::Other)?;
1198
1199        let stored_pin_hash = match self.state.persistent.pin_hash() {
1200            Some(hash) => hash,
1201            None => {
1202                return Err(Error::PinNotSet);
1203            }
1204        };
1205
1206        if pin_hash != stored_pin_hash {
1207            // I) generate new KEK
1208            self.pin_protocol(pin_protocol).regenerate();
1209            self.state.pin_blocked()?;
1210            return Err(Error::PinInvalid);
1211        }
1212
1213        Ok(())
1214    }
1215
1216    fn hash_store_pin(&mut self, pin: &Message) -> Result<()> {
1217        let pin_hash_32 = syscall!(self.trussed.hash_sha256(pin)).hash;
1218        let pin_hash: [u8; 16] = pin_hash_32[..16].try_into().unwrap();
1219        self.state
1220            .persistent
1221            .set_pin_hash(&mut self.trussed, pin_hash)
1222            .unwrap();
1223
1224        Ok(())
1225    }
1226
1227    fn decrypt_pin_check_length(
1228        &mut self,
1229        shared_secret: &SharedSecret,
1230        pin_enc: &[u8],
1231    ) -> Result<Message> {
1232        // pin is expected to be filled with null bytes to length at least 64
1233        if pin_enc.len() < 64 {
1234            // correct error?
1235            return Err(Error::PinPolicyViolation);
1236        }
1237
1238        let mut pin = shared_secret
1239            .decrypt(&mut self.trussed, pin_enc)
1240            .ok_or(Error::Other)?;
1241
1242        // // temp
1243        // let pin_length = pin.iter().position(|&b| b == b'\0').unwrap_or(pin.len());
1244        // info_now!("pin.len() = {}, pin_length = {}, = {:?}",
1245        //           pin.len(), pin_length, &pin);
1246        // chop off null bytes
1247        let pin_length = pin.iter().position(|&b| b == b'\0').unwrap_or(pin.len());
1248        if !(4..64).contains(&pin_length) {
1249            return Err(Error::PinPolicyViolation);
1250        }
1251
1252        pin.resize_default(pin_length).unwrap();
1253
1254        Ok(pin)
1255    }
1256
1257    fn verify_credential_management_pin_auth(
1258        &mut self,
1259        parameters: &ctap2::credential_management::Request,
1260    ) -> Result<()> {
1261        use ctap2::credential_management::Subcommand;
1262        let rp_scope = match parameters.sub_command {
1263            Subcommand::EnumerateCredentialsBegin => {
1264                let rp_id_hash = parameters
1265                    .sub_command_params
1266                    .as_ref()
1267                    .and_then(|subparams| subparams.rp_id_hash)
1268                    .ok_or(Error::MissingParameter)?;
1269                RpScope::RpIdHash(rp_id_hash)
1270            }
1271            Subcommand::DeleteCredential | Subcommand::UpdateUserInformation => {
1272                // TODO: determine RP ID from credential ID
1273                RpScope::All
1274            }
1275            _ => RpScope::All,
1276        };
1277        match parameters.sub_command {
1278            Subcommand::GetCredsMetadata
1279            | Subcommand::EnumerateRpsBegin
1280            | Subcommand::EnumerateCredentialsBegin
1281            | Subcommand::DeleteCredential
1282            | Subcommand::UpdateUserInformation => {
1283                // check pinProtocol
1284                let pin_protocol = parameters.pin_protocol.ok_or(Error::MissingParameter)?;
1285                let pin_protocol = self.parse_pin_protocol(pin_protocol)?;
1286
1287                // check pinAuth
1288                let mut data: Bytes<{ sizes::MAX_CREDENTIAL_ID_LENGTH_PLUS_256 }> =
1289                    Bytes::from_slice(&[parameters.sub_command as u8]).unwrap();
1290                let len = 1 + match parameters.sub_command {
1291                    Subcommand::EnumerateCredentialsBegin
1292                    | Subcommand::DeleteCredential
1293                    | Subcommand::UpdateUserInformation => {
1294                        data.resize_to_capacity();
1295                        // ble, need to reserialize
1296                        ctap_types::serde::cbor_serialize(
1297                            &parameters
1298                                .sub_command_params
1299                                .as_ref()
1300                                .ok_or(Error::MissingParameter)?,
1301                            &mut data[1..],
1302                        )
1303                        .map_err(|_| Error::LimitExceeded)?
1304                        .len()
1305                    }
1306                    _ => 0,
1307                };
1308
1309                let pin_auth = parameters
1310                    .pin_auth
1311                    .as_ref()
1312                    .ok_or(Error::MissingParameter)?;
1313
1314                let mut pin_protocol = self.pin_protocol(pin_protocol);
1315                if let Ok(pin_token) = pin_protocol.verify_pin_token(&data[..len], pin_auth) {
1316                    info_now!("passed pinauth");
1317                    pin_token.require_permissions(Permissions::CREDENTIAL_MANAGEMENT)?;
1318                    pin_token.require_valid_for_rp(rp_scope)?;
1319                    Ok(())
1320                } else {
1321                    info_now!("failed pinauth!");
1322                    self.state.decrement_retries(&mut self.trussed)?;
1323                    let maybe_blocked = self.state.pin_blocked();
1324                    if maybe_blocked.is_err() {
1325                        info_now!("blocked");
1326                        maybe_blocked
1327                    } else {
1328                        info_now!("pinAuthInvalid");
1329                        Err(Error::PinAuthInvalid)
1330                    }
1331                }
1332            }
1333
1334            // don't need the PIN auth, they're continuations
1335            // of already checked CredMgmt subcommands
1336            Subcommand::EnumerateRpsGetNextRp
1337            | Subcommand::EnumerateCredentialsGetNextCredential => Ok(()),
1338
1339            _ => Err(Error::InvalidParameter),
1340        }
1341    }
1342
1343    /// Returns whether UV was performed.
1344    fn pin_prechecks(
1345        &mut self,
1346        options: &Option<ctap2::AuthenticatorOptions>,
1347        pin_auth: Option<&[u8]>,
1348        pin_protocol: Option<u32>,
1349        data: &[u8],
1350        permissions: Permissions,
1351        rp_id: &str,
1352    ) -> Result<bool> {
1353        // 1. pinAuth zero length -> wait for user touch, then
1354        // return PinNotSet if not set, PinInvalid if set
1355        //
1356        // the idea is for multi-authnr scenario where platform
1357        // wants to enforce PIN and needs to figure out which authnrs support PIN
1358        if let Some(pin_auth) = pin_auth {
1359            if pin_auth.is_empty() {
1360                self.up
1361                    .user_present(&mut self.trussed, constants::FIDO2_UP_TIMEOUT)?;
1362                if !self.state.persistent.pin_is_set() {
1363                    return Err(Error::PinNotSet);
1364                } else {
1365                    return Err(Error::PinAuthInvalid);
1366                }
1367            }
1368        }
1369
1370        // 2. check PIN protocol is 1 if pinAuth was sent
1371        let pin_protocol = if pin_auth.is_some() {
1372            let pin_protocol = pin_protocol.ok_or(Error::MissingParameter)?;
1373            let pin_protocol = self.parse_pin_protocol(pin_protocol)?;
1374            Some(pin_protocol)
1375        } else {
1376            None
1377        };
1378
1379        // 3. if no PIN is set (we have no other form of UV),
1380        // and platform sent `uv` or `pinAuth`, return InvalidOption
1381        if !self.state.persistent.pin_is_set() {
1382            if let Some(ref options) = &options {
1383                if Some(true) == options.uv {
1384                    return Err(Error::InvalidOption);
1385                }
1386            }
1387            if pin_auth.is_some() {
1388                return Err(Error::InvalidOption);
1389            }
1390        }
1391
1392        // 4. If authenticator is protected by som form of user verification, do it
1393
1394        // Reject uv = true as we do not support built-in user verification
1395        if pin_auth.is_none() && options.as_ref().and_then(|options| options.uv) == Some(true) {
1396            return Err(Error::InvalidOption);
1397        }
1398
1399        if self.state.persistent.pin_is_set() {
1400            // let mut uv_performed = false;
1401            if let Some(pin_auth) = pin_auth {
1402                // seems a bit redundant to check here in light of 2.
1403                // I guess the CTAP spec writers aren't implementers :D
1404                if let Some(pin_protocol) = pin_protocol {
1405                    // 5. if pinAuth is present and pinProtocol = 1, verify
1406                    // success --> set uv = 1
1407                    // error --> PinAuthInvalid
1408                    let mut pin_protocol = self.pin_protocol(pin_protocol);
1409                    let pin_token = pin_protocol.verify_pin_token(data, pin_auth)?;
1410                    pin_token.require_permissions(permissions)?;
1411                    pin_token.require_valid_for_rp(RpScope::RpId(rp_id))?;
1412
1413                    return Ok(true);
1414                } else {
1415                    // 7. pinAuth present + pinProtocol != 1 --> error PinAuthInvalid
1416                    return Err(Error::PinAuthInvalid);
1417                }
1418            } else {
1419                // 6. pinAuth not present + clientPin set + rk = true --> error PinRequired
1420                if options.as_ref().and_then(|options| options.rk) == Some(true) {
1421                    return Err(Error::PinRequired);
1422                }
1423            }
1424        }
1425
1426        Ok(false)
1427    }
1428
1429    #[inline(never)]
1430    fn check_key_exists(&mut self, alg: i32, key: &Key) -> bool {
1431        match key {
1432            // TODO: should check if wrapped key is valid AEAD
1433            // On the other hand, we already decrypted a valid AEAD
1434            Key::WrappedKey(_) => true,
1435            Key::ResidentKey(key) => {
1436                debug_now!("checking if ResidentKey {:?} exists", key);
1437                SigningAlgorithm::try_from(alg)
1438                    .map(|alg| syscall!(self.trussed.exists(alg.mechanism(), *key)).exists)
1439                    .unwrap_or_default()
1440            }
1441        }
1442    }
1443
1444    #[inline(never)]
1445    fn process_assertion_extensions(
1446        &mut self,
1447        get_assertion_state: &state::ActiveGetAssertionData,
1448        extensions: &ctap2::get_assertion::ExtensionsInput,
1449        credential: &Credential,
1450        credential_key: KeyId,
1451    ) -> Result<Option<ctap2::get_assertion::ExtensionsOutput>> {
1452        let mut output = ctap2::get_assertion::ExtensionsOutput::default();
1453
1454        if let Some(hmac_secret) = &extensions.hmac_secret {
1455            let pin_protocol = hmac_secret
1456                .pin_protocol
1457                .map(|i| self.parse_pin_protocol(i))
1458                .transpose()?
1459                .unwrap_or(PinProtocolVersion::V1);
1460
1461            if !get_assertion_state.up_performed {
1462                return Err(Error::UnsupportedOption);
1463            }
1464
1465            // We derive credRandom as an hmac of the existing private key.
1466            // UV is used as input data since credRandom should depend UV
1467            // i.e. credRandom = HMAC(private_key, uv)
1468            let cred_random = syscall!(self.trussed.derive_key(
1469                Mechanism::HmacSha256,
1470                credential_key,
1471                Some(Bytes::from_slice(&[get_assertion_state.uv_performed as u8]).unwrap()),
1472                StorageAttributes::new().set_persistence(Location::Volatile)
1473            ))
1474            .key;
1475
1476            // Verify the auth tag, which uses the same process as the pinAuth
1477            let mut pin_protocol = self.pin_protocol(pin_protocol);
1478            let shared_secret = pin_protocol.shared_secret(&hmac_secret.key_agreement)?;
1479            pin_protocol.verify_pin_auth(
1480                &shared_secret,
1481                &hmac_secret.salt_enc,
1482                &hmac_secret.salt_auth,
1483            )?;
1484
1485            // decrypt input salt_enc to get salt1 or (salt1 || salt2)
1486            let salts = shared_secret
1487                .decrypt(&mut self.trussed, &hmac_secret.salt_enc)
1488                .ok_or(Error::InvalidOption)?;
1489
1490            if salts.len() != 32 && salts.len() != 64 {
1491                debug_now!("invalid hmac-secret length");
1492                return Err(Error::InvalidLength);
1493            }
1494
1495            let mut salt_output: Bytes<64> = Bytes::new();
1496
1497            // output1 = hmac_sha256(credRandom, salt1)
1498            let output1 =
1499                syscall!(self.trussed.sign_hmacsha256(cred_random, &salts[0..32])).signature;
1500
1501            salt_output.extend_from_slice(&output1).unwrap();
1502
1503            if salts.len() == 64 {
1504                // output2 = hmac_sha256(credRandom, salt2)
1505                let output2 =
1506                    syscall!(self.trussed.sign_hmacsha256(cred_random, &salts[32..64])).signature;
1507
1508                salt_output.extend_from_slice(&output2).unwrap();
1509            }
1510
1511            syscall!(self.trussed.delete(cred_random));
1512
1513            // output_enc = aes256-cbc(sharedSecret, IV=0, output1 || output2)
1514            let output_enc = shared_secret.encrypt(&mut self.trussed, &salt_output);
1515
1516            shared_secret.delete(&mut self.trussed);
1517
1518            output.hmac_secret = Some(Bytes::from_slice(&output_enc).unwrap());
1519        }
1520
1521        if extensions.third_party_payment.unwrap_or_default() {
1522            output.third_party_payment = Some(credential.third_party_payment().unwrap_or_default());
1523        }
1524
1525        Ok(output.is_set().then_some(output))
1526    }
1527
1528    #[inline(never)]
1529    fn assert_with_credential(
1530        &mut self,
1531        num_credentials: Option<u32>,
1532        credential: Credential,
1533    ) -> Result<ctap2::get_assertion::Response> {
1534        let data = self.state.runtime.active_get_assertion.clone().unwrap();
1535        let rp_id_hash = &data.rp_id_hash;
1536
1537        let (key, is_rk) = match credential.key().clone() {
1538            Key::ResidentKey(key) => (key, true),
1539            Key::WrappedKey(bytes) => {
1540                let wrapping_key = self.state.persistent.key_wrapping_key(&mut self.trussed)?;
1541                // info_now!("unwrapping {:?} with wrapping key {:?}", &bytes, &wrapping_key);
1542                let key_result = syscall!(self.trussed.unwrap_key_chacha8poly1305(
1543                    wrapping_key,
1544                    &bytes,
1545                    &[],
1546                    Location::Volatile,
1547                ))
1548                .key;
1549                // debug_now!("key result: {:?}", &key_result);
1550                info_now!("key result");
1551                match key_result {
1552                    Some(key) => (key, false),
1553                    None => {
1554                        return Err(Error::Other);
1555                    }
1556                }
1557            }
1558        };
1559
1560        // 8. process any extensions present
1561        let mut large_blob_key_requested = false;
1562        let extensions_output = if let Some(extensions) = &data.extensions {
1563            if self.config.supports_large_blobs() {
1564                if extensions.large_blob_key == Some(false) {
1565                    // large_blob_key must be Some(true) or omitted
1566                    return Err(Error::InvalidOption);
1567                }
1568                large_blob_key_requested = extensions.large_blob_key == Some(true);
1569            }
1570            self.process_assertion_extensions(&data, extensions, &credential, key)?
1571        } else {
1572            None
1573        };
1574
1575        // 9./10. sign clientDataHash || authData with "first" credential
1576
1577        // info_now!("signing with credential {:?}", &credential);
1578        let kek = self
1579            .state
1580            .persistent
1581            .key_encryption_key(&mut self.trussed)?;
1582        let credential_id = credential.id(&mut self.trussed, kek, rp_id_hash)?;
1583
1584        use ctap2::AuthenticatorDataFlags as Flags;
1585
1586        let sig_count = self.state.persistent.timestamp(&mut self.trussed)?;
1587
1588        let authenticator_data = ctap2::get_assertion::AuthenticatorData {
1589            rp_id_hash,
1590
1591            flags: {
1592                let mut flags = Flags::empty();
1593                if data.up_performed {
1594                    flags |= Flags::USER_PRESENCE;
1595                }
1596                if data.uv_performed {
1597                    flags |= Flags::USER_VERIFIED;
1598                }
1599                if extensions_output.is_some() {
1600                    flags |= Flags::EXTENSION_DATA;
1601                }
1602                flags
1603            },
1604
1605            sign_count: sig_count,
1606            attested_credential_data: None,
1607            extensions: extensions_output,
1608        };
1609
1610        let serialized_auth_data = authenticator_data.serialize()?;
1611
1612        let mut commitment = Bytes::<1024>::new();
1613        commitment
1614            .extend_from_slice(&serialized_auth_data)
1615            .map_err(|_| Error::Other)?;
1616        commitment
1617            .extend_from_slice(&data.client_data_hash)
1618            .map_err(|_| Error::Other)?;
1619
1620        let signing_algorithm =
1621            SigningAlgorithm::try_from(credential.algorithm()).map_err(|_| Error::Other)?;
1622        let signature = signing_algorithm
1623            .sign(&mut self.trussed, key, &commitment)
1624            .to_bytes()
1625            .unwrap();
1626
1627        // select preferred format or skip attestation statement
1628        let att_stmt_fmt = data
1629            .attestation_formats_preference
1630            .as_ref()
1631            .and_then(SupportedAttestationFormat::select);
1632        let att_stmt = if let Some(format) = att_stmt_fmt {
1633            match format {
1634                SupportedAttestationFormat::None => {
1635                    Some(AttestationStatement::None(NoneAttestationStatement {}))
1636                }
1637                SupportedAttestationFormat::Packed => {
1638                    let (attestation_maybe, _) = self.state.identity.attestation(&mut self.trussed);
1639                    let (signature, attestation_algorithm) = {
1640                        if let Some(attestation) = attestation_maybe.as_ref() {
1641                            let signing_algorithm = SigningAlgorithm::P256;
1642                            let signature = signing_algorithm.sign(
1643                                &mut self.trussed,
1644                                attestation.0,
1645                                &commitment,
1646                            );
1647                            (
1648                                signature.to_bytes().map_err(|_| Error::Other)?,
1649                                signing_algorithm.into(),
1650                            )
1651                        } else {
1652                            (signature.clone(), credential.algorithm())
1653                        }
1654                    };
1655                    let packed = PackedAttestationStatement {
1656                        alg: attestation_algorithm,
1657                        sig: signature,
1658                        x5c: attestation_maybe.as_ref().map(|attestation| {
1659                            // See: https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
1660                            let cert = attestation.1.clone();
1661                            let mut x5c = Vec::new();
1662                            x5c.push(cert).ok();
1663                            x5c
1664                        }),
1665                    };
1666                    Some(AttestationStatement::Packed(packed))
1667                }
1668            }
1669        } else {
1670            None
1671        };
1672
1673        if !is_rk {
1674            syscall!(self.trussed.delete(key));
1675        }
1676
1677        let mut response = ctap2::get_assertion::ResponseBuilder {
1678            credential: credential_id.into(),
1679            auth_data: serialized_auth_data,
1680            signature,
1681        }
1682        .build();
1683        response.number_of_credentials = num_credentials;
1684        response.att_stmt = att_stmt;
1685
1686        // User with empty IDs are ignored for compatibility
1687        if is_rk {
1688            if let Credential::Full(credential) = &credential {
1689                if !credential.user.id().is_empty() {
1690                    let mut user: PublicKeyCredentialUserEntity = credential.user.clone().into();
1691                    // User identifiable information (name, DisplayName, icon) MUST not
1692                    // be returned if user verification is not done by the authenticator.
1693                    // For single account per RP case, authenticator returns "id" field.
1694                    if !data.uv_performed || !data.multiple_credentials {
1695                        user.icon = None;
1696                        user.name = None;
1697                        user.display_name = None;
1698                    }
1699                    response.user = Some(user);
1700                }
1701            }
1702
1703            if large_blob_key_requested {
1704                debug!("Sending largeBlobKey in getAssertion");
1705                response.large_blob_key = match credential {
1706                    Credential::Stripped(stripped) => stripped.large_blob_key,
1707                    Credential::Full(full) => full.data.large_blob_key,
1708                };
1709            }
1710        }
1711
1712        Ok(response)
1713    }
1714
1715    #[inline(never)]
1716    fn delete_resident_key_by_user_id(
1717        &mut self,
1718        rp_id_hash: &[u8; 32],
1719        user_id: &Bytes<64>,
1720    ) -> Result<()> {
1721        // Prepare to iterate over all credentials associated to RP.
1722        let file_name_prefix = rp_file_name_prefix(rp_id_hash);
1723        let mut maybe_entry = syscall!(self.trussed.read_dir_first_alphabetical(
1724            Location::Internal,
1725            PathBuf::from(RK_DIR),
1726            Some(file_name_prefix.clone())
1727        ))
1728        .entry;
1729
1730        while let Some(entry) = maybe_entry.take() {
1731            if !entry
1732                .file_name()
1733                .as_ref()
1734                .starts_with(file_name_prefix.as_ref())
1735            {
1736                // We got past all credentials for the relevant RP
1737                break;
1738            }
1739
1740            if entry.file_name() == &*file_name_prefix {
1741                debug_assert!(entry.metadata().is_dir());
1742                error!("Migration missing");
1743                return Err(Error::Other);
1744            }
1745
1746            info_now!("this may be an RK: {:?}", &entry);
1747            let rk_path = PathBuf::from(entry.path());
1748
1749            info_now!("checking RK {:?} for userId ", &rk_path);
1750            let credential_data =
1751                syscall!(self.trussed.read_file(Location::Internal, rk_path.clone(),)).data;
1752            let credential_maybe = FullCredential::deserialize(&credential_data);
1753
1754            if let Ok(old_credential) = credential_maybe {
1755                if old_credential.user.id() == user_id {
1756                    match old_credential.key {
1757                        credential::Key::ResidentKey(key) => {
1758                            info_now!(":: deleting resident key");
1759                            syscall!(self.trussed.delete(key));
1760                        }
1761                        _ => {
1762                            warn_now!(":: WARNING: unexpected server credential in rk.");
1763                        }
1764                    }
1765                    syscall!(self.trussed.remove_file(Location::Internal, rk_path,));
1766
1767                    info_now!("Overwriting previous rk tied to this userId.");
1768                    break;
1769                }
1770            } else {
1771                warn_now!("WARNING: Could not read RK.");
1772            }
1773
1774            // prepare for next loop iteration
1775            maybe_entry = syscall!(self.trussed.read_dir_next()).entry;
1776        }
1777
1778        Ok(())
1779    }
1780
1781    #[inline(never)]
1782    pub(crate) fn delete_resident_key_by_path(&mut self, rk_path: &Path) -> Result<()> {
1783        info_now!("deleting RK {:?}", &rk_path);
1784        let credential_data = syscall!(self
1785            .trussed
1786            .read_file(Location::Internal, PathBuf::from(rk_path),))
1787        .data;
1788        let credential_maybe = FullCredential::deserialize(&credential_data);
1789        // info_now!("deleting credential {:?}", &credential);
1790
1791        if let Ok(credential) = credential_maybe {
1792            match credential.key {
1793                credential::Key::ResidentKey(key) => {
1794                    info_now!(":: deleting resident key");
1795                    syscall!(self.trussed.delete(key));
1796                }
1797                credential::Key::WrappedKey(_) => {}
1798            }
1799        } else {
1800            // If for some reason there becomes a corrupt credential,
1801            // we can still at least orphan the key rather then crash.
1802            info_now!("Warning!  Orpaning a key.");
1803        }
1804
1805        info_now!(":: deleting RK file {:?} itself", &rk_path);
1806        syscall!(self
1807            .trussed
1808            .remove_file(Location::Internal, PathBuf::from(rk_path),));
1809
1810        Ok(())
1811    }
1812
1813    fn large_blobs_get(
1814        &mut self,
1815        request: &ctap2::large_blobs::Request,
1816        config: large_blobs::Config,
1817        length: u32,
1818    ) -> Result<ctap2::large_blobs::Response> {
1819        debug!(
1820            "large_blobs_get: length = {length}, offset = {}",
1821            request.offset
1822        );
1823        // 1.-2. Validate parameters
1824        if request.length.is_some()
1825            || request.pin_uv_auth_param.is_some()
1826            || request.pin_uv_auth_protocol.is_some()
1827        {
1828            error!("length/pin set");
1829            return Err(Error::InvalidParameter);
1830        }
1831        // 3. Validate length
1832        let Ok(length) = usize::try_from(length) else {
1833            return Err(Error::InvalidLength);
1834        };
1835        if length > self.config.max_msg_size.saturating_sub(64) {
1836            return Err(Error::InvalidLength);
1837        }
1838        // 4. Validate offset
1839        let Ok(offset) = usize::try_from(request.offset) else {
1840            error!("offset too large");
1841            return Err(Error::InvalidParameter);
1842        };
1843        let stored_length = large_blobs::size(&mut self.trussed, config.location)?;
1844        if offset > stored_length {
1845            error!("offset: {offset}, stored_length: {stored_length}");
1846            return Err(Error::InvalidParameter);
1847        };
1848        // 5. Return requested data
1849        info!("Reading large-blob array from offset {offset}");
1850        let data = large_blobs::read_chunk(&mut self.trussed, config.location, offset, length)?;
1851        let mut response = ctap2::large_blobs::Response::default();
1852        response.config = Some(data);
1853        Ok(response)
1854    }
1855
1856    fn large_blobs_set(
1857        &mut self,
1858        request: &ctap2::large_blobs::Request,
1859        config: large_blobs::Config,
1860        data: &[u8],
1861    ) -> Result<ctap2::large_blobs::Response> {
1862        debug!(
1863            "large_blobs_set: |data| = {}, offset = {}, length = {:?}",
1864            data.len(),
1865            request.offset,
1866            request.length
1867        );
1868        // 1. Validate data
1869        if data.len() > self.config.max_msg_size.saturating_sub(64) {
1870            return Err(Error::InvalidLength);
1871        }
1872        if request.offset == 0 {
1873            // 2. Calculate expected length and offset
1874            // 2.1. Require length
1875            let Some(length) = request.length else {
1876                return Err(Error::InvalidParameter);
1877            };
1878            // 2.2. Check that length is not too big
1879            let Ok(length) = usize::try_from(length) else {
1880                return Err(Error::LargeBlobStorageFull);
1881            };
1882            if length > config.max_size() {
1883                return Err(Error::LargeBlobStorageFull);
1884            }
1885            // 2.3. Check that length is not too small
1886            if length < large_blobs::MIN_SIZE {
1887                return Err(Error::InvalidParameter);
1888            }
1889            // 2.4-5. Set expected length and offset
1890            self.state.runtime.large_blobs.expected_length = length;
1891            self.state.runtime.large_blobs.expected_next_offset = 0;
1892        } else {
1893            // 3. Validate parameters
1894            if request.length.is_some() {
1895                return Err(Error::InvalidParameter);
1896            }
1897        }
1898
1899        // 4. Validate offset
1900        let Ok(offset) = usize::try_from(request.offset) else {
1901            return Err(Error::InvalidSeq);
1902        };
1903        if offset != self.state.runtime.large_blobs.expected_next_offset {
1904            return Err(Error::InvalidSeq);
1905        }
1906
1907        // 5. Perform uv
1908        // TODO: support alwaysUv
1909        if self.state.persistent.pin_is_set() {
1910            let Some(pin_uv_auth_param) = request.pin_uv_auth_param else {
1911                return Err(Error::PinRequired);
1912            };
1913            let Some(pin_uv_auth_protocol) = request.pin_uv_auth_protocol else {
1914                return Err(Error::PinRequired);
1915            };
1916            if pin_uv_auth_protocol != 1 {
1917                return Err(Error::PinAuthInvalid);
1918            }
1919            let pin_protocol = self.parse_pin_protocol(pin_uv_auth_protocol)?;
1920            // TODO: check pinUvAuthToken
1921            let pin_auth: [u8; 16] = pin_uv_auth_param
1922                .as_ref()
1923                .try_into()
1924                .map_err(|_| Error::PinAuthInvalid)?;
1925
1926            let mut auth_data: Bytes<70> = Bytes::new();
1927            // 32x 0xff
1928            auth_data.resize(32, 0xff).unwrap();
1929            // h'0c00'
1930            auth_data.push(0x0c).unwrap();
1931            auth_data.push(0x00).unwrap();
1932            // uint32LittleEndian(offset)
1933            auth_data
1934                .extend_from_slice(&request.offset.to_le_bytes())
1935                .unwrap();
1936            // SHA-256(data)
1937            auth_data.extend_from_slice(&Sha256::digest(data)).unwrap();
1938
1939            let mut pin_protocol = self.pin_protocol(pin_protocol);
1940            let pin_token = pin_protocol.verify_pin_token(&pin_auth, &auth_data)?;
1941            pin_token.require_permissions(Permissions::LARGE_BLOB_WRITE)?;
1942        }
1943
1944        // 6. Validate data length
1945        if offset + data.len() > self.state.runtime.large_blobs.expected_length {
1946            return Err(Error::InvalidParameter);
1947        }
1948
1949        // 7.-11. Write the buffer
1950        info!("Writing large-blob array to offset {offset}");
1951        large_blobs::write_chunk(
1952            &mut self.trussed,
1953            &mut self.state.runtime.large_blobs,
1954            config.location,
1955            data,
1956        )?;
1957
1958        Ok(ctap2::large_blobs::Response::default())
1959    }
1960}
1961
1962#[derive(Clone, Copy, Debug)]
1963enum SupportedAttestationFormat {
1964    None,
1965    Packed,
1966}
1967
1968impl SupportedAttestationFormat {
1969    fn select(preference: &AttestationFormatsPreference) -> Option<Self> {
1970        if preference.known_formats() == [AttestationStatementFormat::None]
1971            && !preference.includes_unknown_formats()
1972        {
1973            // platform requested only None --> omit attestation statement
1974            return None;
1975        }
1976        // use first known and supported format, or default to packed format
1977        let format = preference
1978            .known_formats()
1979            .iter()
1980            .copied()
1981            .flat_map(Self::try_from)
1982            .next()
1983            .unwrap_or(Self::Packed);
1984        Some(format)
1985    }
1986}
1987
1988impl From<SupportedAttestationFormat> for AttestationStatementFormat {
1989    fn from(format: SupportedAttestationFormat) -> Self {
1990        match format {
1991            SupportedAttestationFormat::None => Self::None,
1992            SupportedAttestationFormat::Packed => Self::Packed,
1993        }
1994    }
1995}
1996
1997impl TryFrom<AttestationStatementFormat> for SupportedAttestationFormat {
1998    type Error = Error;
1999
2000    fn try_from(format: AttestationStatementFormat) -> core::result::Result<Self, Self::Error> {
2001        match format {
2002            AttestationStatementFormat::None => Ok(Self::None),
2003            AttestationStatementFormat::Packed => Ok(Self::Packed),
2004            _ => Err(Error::Other),
2005        }
2006    }
2007}
2008
2009// The new path scheme for disvoerable credentials (= resident keys) is:
2010//   rk/<rp_id_hash>.<credential_id_hash>
2011// The hashes are truncated to the first eight bytes and formatted as hex strings.
2012// We use the following terms for the components:
2013//   rk_path:              rk/<rp_id_hash>.<credential_id_hash>
2014//   rp_file_name_prefix:  <rp_id_hash>
2015
2016fn rp_file_name_prefix(rp_id_hash: &[u8; 32]) -> PathBuf {
2017    let mut hex = [b'0'; 16];
2018    super::format_hex(&rp_id_hash[..8], &mut hex);
2019    PathBuf::try_from(&hex).unwrap()
2020}
2021
2022fn rk_path(rp_id_hash: &[u8; 32], credential_id_hash: &[u8; 32]) -> PathBuf {
2023    // 16 bytes per hash + dot + trailing zero = 34
2024    let mut buf = [0; 34];
2025    buf[16] = b'.';
2026    format_hex(&rp_id_hash[..8], &mut buf[..16]);
2027    format_hex(&credential_id_hash[..8], &mut buf[17..33]);
2028
2029    let mut path = PathBuf::from(RK_DIR);
2030    path.push(Path::from_bytes_with_nul(&buf).unwrap());
2031    path
2032}
2033
2034#[cfg(test)]
2035mod tests {
2036    use super::{rk_path, rp_file_name_prefix};
2037
2038    const TEST_HASH: &[u8; 32] = &[
2039        134, 54, 157, 96, 10, 28, 233, 79, 219, 59, 195, 125, 165, 251, 120, 14, 49, 152, 212, 191,
2040        114, 137, 180, 207, 255, 177, 187, 106, 173, 1, 203, 171,
2041    ];
2042    const TEST_HASH_HEX: &str = "86369d600a1ce94f";
2043
2044    #[test]
2045    fn test_rp_file_name_prefix() {
2046        assert_eq!(rp_file_name_prefix(&[0; 32]).as_str(), "0000000000000000");
2047        assert_eq!(rp_file_name_prefix(TEST_HASH).as_str(), TEST_HASH_HEX);
2048    }
2049
2050    #[test]
2051    fn test_rk_path() {
2052        fn test(rp_id_hash: &[u8; 32], credential_id_hash: &[u8; 32], expected: &str) {
2053            println!("rp_id_hash: {rp_id_hash:?}");
2054            println!("credential_id_hash: {credential_id_hash:?}");
2055            let actual = rk_path(rp_id_hash, credential_id_hash);
2056            assert_eq!(actual.as_str(), expected);
2057        }
2058
2059        let input_zero = &[0; 32];
2060        let output_zero = "0000000000000000";
2061        let input_nonzero = TEST_HASH;
2062        let output_nonzero = TEST_HASH_HEX;
2063
2064        test(
2065            input_zero,
2066            input_zero,
2067            &format!("rk/{output_zero}.{output_zero}"),
2068        );
2069        test(
2070            input_zero,
2071            input_nonzero,
2072            &format!("rk/{output_zero}.{output_nonzero}"),
2073        );
2074        test(
2075            input_nonzero,
2076            input_zero,
2077            &format!("rk/{output_nonzero}.{output_zero}"),
2078        );
2079        test(
2080            input_nonzero,
2081            input_nonzero,
2082            &format!("rk/{output_nonzero}.{output_nonzero}"),
2083        );
2084    }
2085}