@@ -114,6 +114,10 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
114114 algorithms
115115 . push ( KnownPublicKeyCredentialParameters { alg : ED_DSA } )
116116 . unwrap ( ) ;
117+ #[ cfg( feature = "mldsa44" ) ]
118+ algorithms
119+ . push ( KnownPublicKeyCredentialParameters { alg : -50 } )
120+ . ok ( ) ;
117121 let algorithms = FilteredPublicKeyCredentialParameters ( algorithms) ;
118122
119123 let remaining_discoverable_credentials = self . estimate_remaining ( ) ;
@@ -131,8 +135,8 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
131135 response. max_cred_id_length = Some ( ctap_types:: sizes:: MAX_CREDENTIAL_ID_LENGTH ) ;
132136 response. algorithms = Some ( algorithms) ;
133137 response. firmware_version = Some ( self . config . firmware_version as usize ) ;
134- response. remaining_discoverable_credentials = remaining_discoverable_credentials
135- . map ( |count| count as usize ) ;
138+ response. remaining_discoverable_credentials =
139+ remaining_discoverable_credentials . map ( |count| count as usize ) ;
136140 response. max_cred_blob_length = Some ( constants:: MAX_CRED_BLOB_LENGTH ) ;
137141 response. min_pin_length = Some ( self . state . persistent . min_pin_length ( ) as usize ) ;
138142 response. force_pin_change = Some ( self . state . persistent . force_pin_change ( ) ) ;
@@ -233,17 +237,21 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
233237
234238 // 7. check pubKeyCredParams algorithm is valid + supported COSE identifier
235239
240+ // CTAP §6.1.2: walk pubKeyCredParams in order and pick the first
241+ // supported algorithm. The guard on every arm matters — without
242+ // it later entries silently overwrite earlier ones and we end
243+ // up with last-match instead of first-match (caught by the
244+ // ML-DSA-44 vs EdDSA preference test).
236245 let mut algorithm: Option < SigningAlgorithm > = None ;
237246 for param in parameters. pub_key_cred_params . 0 . iter ( ) {
247+ if algorithm. is_some ( ) {
248+ break ;
249+ }
238250 match param. alg {
239- -7 => {
240- if algorithm. is_none ( ) {
241- algorithm = Some ( SigningAlgorithm :: P256 ) ;
242- }
243- }
244- -8 => {
245- algorithm = Some ( SigningAlgorithm :: Ed25519 ) ;
246- }
251+ -7 => algorithm = Some ( SigningAlgorithm :: P256 ) ,
252+ -8 => algorithm = Some ( SigningAlgorithm :: Ed25519 ) ,
253+ #[ cfg( feature = "mldsa44" ) ]
254+ -50 => algorithm = Some ( SigningAlgorithm :: MlDsa44 ) ,
247255 _ => { }
248256 }
249257 }
@@ -556,7 +564,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
556564 } ;
557565 // debug_now!("authData = {:?}", &authenticator_data);
558566
559- let serialized_auth_data = authenticator_data. serialize ( ) ?;
567+ let mut serialized_auth_data = authenticator_data. serialize ( ) ?;
560568
561569 // select attestation format or use packed attestation as default
562570 let att_stmt_fmt = parameters
@@ -570,20 +578,28 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
570578 Some ( AttestationStatement :: None ( NoneAttestationStatement { } ) )
571579 }
572580 SupportedAttestationFormat :: Packed => {
573- let mut commitment = Bytes :: < 1024 > :: new ( ) ;
574- commitment
575- . extend_from_slice ( & serialized_auth_data)
576- . map_err ( |_| Error :: Other ) ?;
577- commitment
581+ // Build the "commitment" (auth_data ‖ cdh) IN PLACE inside
582+ // `serialized_auth_data` to avoid a separate 2.3 KB local.
583+ // With `mldsa44`, `SerializedAuthenticatorData` has 2048 B
584+ // capacity, comfortably fitting the ~1577 B auth_data + 32 B
585+ // cdh = ~1609 B. After signing we truncate to restore the
586+ // original auth_data length so the buffer can be moved into
587+ // `response.auth_data`.
588+ let auth_data_len = serialized_auth_data. len ( ) ;
589+ serialized_auth_data
578590 . extend_from_slice ( parameters. client_data_hash )
579591 . map_err ( |_| Error :: Other ) ?;
580592
581593 let ( attestation_key, attestation_algorithm) = attestation_maybe
582594 . as_ref ( )
583595 . map ( |attestation| ( attestation. 0 , SigningAlgorithm :: P256 ) )
584596 . unwrap_or ( ( private_key, algorithm) ) ;
585- let signature =
586- attestation_algorithm. sign ( & mut self . trussed , attestation_key, & commitment) ;
597+ let signature = attestation_algorithm. sign (
598+ & mut self . trussed ,
599+ attestation_key,
600+ & serialized_auth_data,
601+ ) ;
602+ serialized_auth_data. truncate ( auth_data_len) ;
587603 let packed = PackedAttestationStatement {
588604 alg : attestation_algorithm. into ( ) ,
589605 sig : Bytes :: try_from ( & * signature) . map_err ( |_| Error :: Other ) ?,
@@ -607,6 +623,11 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
607623 info_now ! ( "deleted private credential key: {}" , _success) ;
608624 }
609625
626+ // Write fields directly into the caller-provided slot — avoids
627+ // the 6 KB Response by-value return + move through the dispatch
628+ // chain. `serialized_auth_data` still lives transiently on this
629+ // function's stack (≈2 KB); future work could write it directly
630+ // into `response.auth_data` via a mutable serialize sink.
610631 response. fmt = att_stmt_fmt
611632 . map ( From :: from)
612633 . unwrap_or ( AttestationStatementFormat :: None ) ;
@@ -682,10 +703,7 @@ impl<UP: UserPresence, T: TrussedRequirements> Authenticator for crate::Authenti
682703
683704 match request. sub_command {
684705 Subcommand :: SetMinPINLength => self . config_set_min_pin_length ( request) ,
685- Subcommand :: ToggleAlwaysUv => self
686- . state
687- . persistent
688- . toggle_always_uv ( & mut self . trussed ) ,
706+ Subcommand :: ToggleAlwaysUv => self . state . persistent . toggle_always_uv ( & mut self . trussed ) ,
689707 // CTAP 2.3 §6.11.5: long-touch is the only reset gesture we
690708 // support, hard-wired on. The subcommand is therefore a no-op:
691709 // already enabled, so we just acknowledge.
@@ -1238,10 +1256,7 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
12381256 let mut owned = heapless:: Vec :: new ( ) ;
12391257 for id in rp_ids {
12401258 owned
1241- . push (
1242- heapless:: String :: try_from ( * id)
1243- . map_err ( |_| Error :: PinPolicyViolation ) ?,
1244- )
1259+ . push ( heapless:: String :: try_from ( * id) . map_err ( |_| Error :: PinPolicyViolation ) ?)
12451260 . map_err ( |_| Error :: PinPolicyViolation ) ?;
12461261 }
12471262 self . state
@@ -1788,12 +1803,7 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
17881803 if extensions. cred_blob . unwrap_or ( false ) {
17891804 // Spec: if the extension was requested but no blob is associated
17901805 // with the credential, return an empty byte string (not absent).
1791- output. cred_blob = Some (
1792- credential
1793- . cred_blob ( )
1794- . cloned ( )
1795- . unwrap_or_else ( Bytes :: new) ,
1796- ) ;
1806+ output. cred_blob = Some ( credential. cred_blob ( ) . cloned ( ) . unwrap_or_else ( Bytes :: new) ) ;
17971807 }
17981808
17991809 Ok ( output. is_set ( ) . then_some ( output) )
@@ -1882,66 +1892,53 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
18821892 extensions : extensions_output,
18831893 } ;
18841894
1885- let serialized_auth_data = authenticator_data. serialize ( ) ?;
1895+ let mut serialized_auth_data = authenticator_data. serialize ( ) ?;
18861896
1887- let mut commitment = Bytes :: < 1024 > :: new ( ) ;
1888- commitment
1889- . extend_from_slice ( & serialized_auth_data )
1890- . map_err ( |_| Error :: Other ) ? ;
1891- commitment
1897+ // Build commitment in place: append client_data_hash to serialized_auth_data,
1898+ // sign over the concatenation, then truncate back. Mirrors the elision
1899+ // done in make_credential — avoids a separate Bytes<1024> commitment buffer.
1900+ let auth_data_len = serialized_auth_data . len ( ) ;
1901+ serialized_auth_data
18921902 . extend_from_slice ( & data. client_data_hash )
18931903 . map_err ( |_| Error :: Other ) ?;
18941904
18951905 let signing_algorithm =
18961906 SigningAlgorithm :: try_from ( credential. algorithm ( ) ) . map_err ( |_| Error :: Other ) ?;
1897- let signature =
1898- Bytes :: try_from ( & * signing_algorithm. sign ( & mut self . trussed , key, & commitment) ) . unwrap ( ) ;
1907+ let signature = Bytes :: try_from (
1908+ & * signing_algorithm. sign ( & mut self . trussed , key, & serialized_auth_data) ,
1909+ )
1910+ . unwrap ( ) ;
18991911
1900- // select preferred format or skip attestation statement
1912+ // select preferred format or skip attestation statement.
1913+ //
1914+ // The Packed branch's `PackedAttestationStatement` carries a
1915+ // `Bytes<MAX_PACKED_SIG_LENGTH>` sig (2436 B) and an x5c
1916+ // `Bytes<MAX_X5C_CERT_LENGTH>` cert (2052 B) — ~4.5 KB total
1917+ // with `mldsa44`. Outline the construction into a `#[inline(never)]`
1918+ // helper so those temporaries live in the helper's frame, not
1919+ // in `assert_with_credential`'s (which is preserved on the lower
1920+ // task's stack during the 72 KB libcrux_sign call above us).
19011921 let att_stmt_fmt = data
19021922 . attestation_formats_preference
19031923 . as_ref ( )
19041924 . and_then ( SupportedAttestationFormat :: select) ;
1905- let att_stmt = if let Some ( format) = att_stmt_fmt {
1906- match format {
1907- SupportedAttestationFormat :: None => {
1908- Some ( AttestationStatement :: None ( NoneAttestationStatement { } ) )
1909- }
1910- SupportedAttestationFormat :: Packed => {
1911- let ( attestation_maybe, _) = self . state . identity . attestation ( & mut self . trussed ) ;
1912- let ( signature, attestation_algorithm) = {
1913- if let Some ( attestation) = attestation_maybe. as_ref ( ) {
1914- let signing_algorithm = SigningAlgorithm :: P256 ;
1915- let signature = signing_algorithm. sign (
1916- & mut self . trussed ,
1917- attestation. 0 ,
1918- & commitment,
1919- ) ;
1920- (
1921- Bytes :: try_from ( & * signature) . map_err ( |_| Error :: Other ) ?,
1922- signing_algorithm. into ( ) ,
1923- )
1924- } else {
1925- ( signature. clone ( ) , credential. algorithm ( ) )
1926- }
1927- } ;
1928- let packed = PackedAttestationStatement {
1929- alg : attestation_algorithm,
1930- sig : signature,
1931- x5c : attestation_maybe. as_ref ( ) . map ( |attestation| {
1932- // See: https://www.w3.org/TR/webauthn-2/#sctn-packed-attestation-cert-requirements
1933- let cert = attestation. 1 . clone ( ) ;
1934- let mut x5c = Vec :: new ( ) ;
1935- x5c. push ( cert) . ok ( ) ;
1936- x5c
1937- } ) ,
1938- } ;
1939- Some ( AttestationStatement :: Packed ( packed) )
1940- }
1925+ match att_stmt_fmt {
1926+ Some ( SupportedAttestationFormat :: None ) => {
1927+ response. att_stmt = Some ( AttestationStatement :: None ( NoneAttestationStatement { } ) ) ;
19411928 }
1942- } else {
1943- None
1944- } ;
1929+ Some ( SupportedAttestationFormat :: Packed ) => {
1930+ self . build_packed_att_stmt (
1931+ & serialized_auth_data,
1932+ & signature,
1933+ credential. algorithm ( ) ,
1934+ response,
1935+ ) ?;
1936+ }
1937+ None => { }
1938+ }
1939+
1940+ // Truncate back so the response carries only authData (without cdh).
1941+ serialized_auth_data. truncate ( auth_data_len) ;
19451942
19461943 if !is_rk {
19471944 syscall ! ( self . trussed. delete( key) ) ;
@@ -1951,7 +1948,6 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
19511948 response. auth_data = serialized_auth_data;
19521949 response. signature = signature;
19531950 response. number_of_credentials = num_credentials;
1954- response. att_stmt = att_stmt;
19551951
19561952 // User with empty IDs are ignored for compatibility
19571953 if is_rk {
@@ -1982,6 +1978,43 @@ impl<UP: UserPresence, T: TrussedRequirements> crate::Authenticator<UP, T> {
19821978 Ok ( ( ) )
19831979 }
19841980
1981+ /// Build a `Packed` attestation statement for `get_assertion` and
1982+ /// write it into `response.att_stmt`. Outlined so its ~4.5 KB worth
1983+ /// of temporaries (`Bytes<MAX_PACKED_SIG_LENGTH>` re-sign output plus
1984+ /// the x5c cert clone) live here instead of inflating the caller's
1985+ /// preserved stack while libcrux_sign runs above us.
1986+ #[ inline( never) ]
1987+ fn build_packed_att_stmt (
1988+ & mut self ,
1989+ message : & [ u8 ] ,
1990+ fallback_sig : & Bytes < { ctap_types:: sizes:: MAX_PACKED_SIG_LENGTH } > ,
1991+ fallback_alg : i32 ,
1992+ response : & mut ctap2:: get_assertion:: Response ,
1993+ ) -> Result < ( ) > {
1994+ let ( attestation_maybe, _) = self . state . identity . attestation ( & mut self . trussed ) ;
1995+ let ( sig, alg) = if let Some ( attestation) = attestation_maybe. as_ref ( ) {
1996+ let signing_algorithm = SigningAlgorithm :: P256 ;
1997+ let att_sig = signing_algorithm. sign ( & mut self . trussed , attestation. 0 , message) ;
1998+ (
1999+ Bytes :: try_from ( & * att_sig) . map_err ( |_| Error :: Other ) ?,
2000+ signing_algorithm. into ( ) ,
2001+ )
2002+ } else {
2003+ ( fallback_sig. clone ( ) , fallback_alg)
2004+ } ;
2005+ response. att_stmt = Some ( AttestationStatement :: Packed ( PackedAttestationStatement {
2006+ alg,
2007+ sig,
2008+ x5c : attestation_maybe. as_ref ( ) . map ( |attestation| {
2009+ let cert = attestation. 1 . clone ( ) ;
2010+ let mut x5c = Vec :: new ( ) ;
2011+ x5c. push ( cert) . ok ( ) ;
2012+ x5c
2013+ } ) ,
2014+ } ) ) ;
2015+ Ok ( ( ) )
2016+ }
2017+
19852018 #[ inline( never) ]
19862019 fn delete_resident_key_by_user_id (
19872020 & mut self ,
0 commit comments