From 0084256898f9914b4e022484110fb68fca5bd314 Mon Sep 17 00:00:00 2001 From: Emanuele Cesena Date: Fri, 15 May 2026 12:15:34 +0200 Subject: [PATCH] ctap2: refactor make_credential / get_assertion to take &mut Response --- CHANGELOG.md | 2 +- Cargo.toml | 2 +- fuzz/fuzz_targets/ctap.rs | 9 +++++-- src/ctap2.rs | 53 +++++++++++++++++++-------------------- src/dispatch.rs | 21 ++++++---------- 5 files changed, 43 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f20e3c9..f8a30df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased -- +- Update to `ctap-types` v0.6.0-rc.4. ## [v0.4.0-rc.2](https://github.com/trussed-dev/fido-authenticator/releases/tag/v0.4.0-rc.2) (2026-05-31) diff --git a/Cargo.toml b/Cargo.toml index 0f5b166..a727e67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ description = "FIDO authenticator Trussed app" apdu-app = { version = "0.2", optional = true } cbor-smol = "0.5" cosey = "0.4" -ctap-types = { version = "=0.6.0-rc.3", features = ["get-info-full", "large-blobs", "third-party-payment"] } +ctap-types = { version = "=0.6.0-rc.4", features = ["get-info-full", "large-blobs", "third-party-payment"] } ctaphid-app = { version = "0.2", optional = true } delog = "0.1" heapless = "0.9" diff --git a/fuzz/fuzz_targets/ctap.rs b/fuzz/fuzz_targets/ctap.rs index 127fc83..002b8de 100644 --- a/fuzz/fuzz_targets/ctap.rs +++ b/fuzz/fuzz_targets/ctap.rs @@ -1,6 +1,10 @@ #![no_main] -use ctap_types::{authenticator::Request, ctap1::Authenticator as _, ctap2::Authenticator as _}; +use ctap_types::{ + authenticator::Request, + ctap1::Authenticator as _, + ctap2::{Authenticator as _, Response}, +}; use fido_authenticator::{Authenticator, Config, Conforming}; use trussed::virt::StoreConfig; use trussed_staging::virt; @@ -30,7 +34,8 @@ fuzz_target!(|requests: Vec>| { authenticator.call_ctap1(&request).ok(); } Request::Ctap2(request) => { - authenticator.call_ctap2(&request).ok(); + let mut response = Response::Reset; + authenticator.call_ctap2(&request, &mut response).ok(); } } } diff --git a/src/ctap2.rs b/src/ctap2.rs index 4bfb452..d084079 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -205,14 +205,17 @@ impl Authenticator for crate::Authenti // 7. reset timer // 8. increment credential counter (not applicable) - self.assert_with_credential(None, Credential::Full(credential)) + let mut response = ctap2::get_assertion::Response::empty(); + self.assert_with_credential(None, &Credential::Full(credential), &mut response)?; + Ok(response) } #[inline(never)] - fn make_credential( + fn make_credential_into( &mut self, parameters: &ctap2::make_credential::Request, - ) -> Result { + response: &mut ctap2::make_credential::Response, + ) -> Result<()> { let rp_id_hash = self.hash(parameters.rp.id.as_ref()); // 1-4. @@ -614,16 +617,13 @@ impl Authenticator for crate::Authenti info_now!("deleted private credential key: {}", _success); } - let mut attestation_object = ctap2::make_credential::ResponseBuilder { - fmt: att_stmt_fmt - .map(From::from) - .unwrap_or(AttestationStatementFormat::None), - auth_data: serialized_auth_data, - } - .build(); - attestation_object.att_stmt = att_stmt; - attestation_object.large_blob_key = large_blob_key; - Ok(attestation_object) + response.fmt = att_stmt_fmt + .map(From::from) + .unwrap_or(AttestationStatementFormat::None); + response.auth_data = serialized_auth_data; + response.att_stmt = att_stmt; + response.large_blob_key = large_blob_key; + Ok(()) } #[inline(never)] @@ -1207,10 +1207,11 @@ impl Authenticator for crate::Authenti } #[inline(never)] - fn get_assertion( + fn get_assertion_into( &mut self, parameters: &ctap2::get_assertion::Request, - ) -> Result { + response: &mut ctap2::get_assertion::Response, + ) -> Result<()> { debug_now!("remaining stack size: {} bytes", msp() - 0x2000_0000); let rp_id_hash = self.hash(parameters.rp_id.as_ref()); @@ -1315,7 +1316,7 @@ impl Authenticator for crate::Authenti n => Some(n), }; - self.assert_with_credential(num_credentials, credential) + self.assert_with_credential(num_credentials, &credential, response) } #[inline(never)] @@ -2044,8 +2045,9 @@ impl crate::Authenticator { fn assert_with_credential( &mut self, num_credentials: Option, - credential: Credential, - ) -> Result { + credential: &Credential, + response: &mut ctap2::get_assertion::Response, + ) -> Result<()> { let data = self.state.runtime.active_get_assertion.clone().unwrap(); let credential_id_version = self.state.persistent.credential_id_version(); let rp_id_hash = &data.rp_id_hash; @@ -2078,7 +2080,7 @@ impl crate::Authenticator { } large_blob_key_requested = extensions.large_blob_key == Some(true); } - self.process_assertion_extensions(&data, extensions, &credential, key)? + self.process_assertion_extensions(&data, extensions, credential, key)? } else { None }; @@ -2184,18 +2186,15 @@ impl crate::Authenticator { syscall!(self.trussed.delete(key)); } - let mut response = ctap2::get_assertion::ResponseBuilder { - credential: credential_id.into(), - auth_data: serialized_auth_data, - signature, - } - .build(); + response.credential = credential_id.into(); + response.auth_data = serialized_auth_data; + response.signature = signature; response.number_of_credentials = num_credentials; response.att_stmt = att_stmt; // User with empty IDs are ignored for compatibility if is_rk { - if let Credential::Full(credential) = &credential { + if let Credential::Full(credential) = credential { if !credential.user.id().is_empty() { let mut user: PublicKeyCredentialUserEntity = credential.user.clone().into(); // User identifiable information (name, DisplayName, icon) MUST not @@ -2219,7 +2218,7 @@ impl crate::Authenticator { } } - Ok(response) + Ok(()) } #[inline(never)] diff --git a/src/dispatch.rs b/src/dispatch.rs index 6c8cb8f..c671f5a 100644 --- a/src/dispatch.rs +++ b/src/dispatch.rs @@ -145,13 +145,11 @@ where msp() - 0x2000_0000 ); - // let ctap_request = ctap2::Request::deserialize(data) - // .map_err(|error| error as u8)?; - // let ctap_response = ctap2::Authenticator::call_ctap2(authenticator, &ctap_request) - // .map_err(|error| error as u8)?; - - // Goal of these nested scopes is to keep stack small. - let ctap_response = try_get_ctap2_response(authenticator, data)?; + // ctap_response lives here (this is the only stack slot for the + // ~6 KB ctap2::Response with mldsa44). Inner layers fill it in + // place via &mut, avoiding by-value copies. + let mut ctap_response = ctap2::Response::Reset; + try_get_ctap2_response(authenticator, data, &mut ctap_response)?; ctap_response.serialize(response); Ok(()) } @@ -160,7 +158,8 @@ where fn try_get_ctap2_response( authenticator: &mut Authenticator, data: &[u8], -) -> Result + ctap_response: &mut ctap2::Response, +) -> Result<(), u8> where T: TrussedRequirements, UP: UserPresence, @@ -190,11 +189,7 @@ where debug!("2a SP: {:X}", msp()); use ctap2::Authenticator; authenticator - .call_ctap2(&ctap_request) - .inspect(|_response| { - info!("Sending CTAP2 response {:?}", response_operation(_response)); - trace!("CTAP2 response: {:?}", _response); - }) + .call_ctap2(&ctap_request, ctap_response) .map_err(|error| { info!("CTAP2 error: {:?}", error); error as u8