diff --git a/Cargo.toml b/Cargo.toml index 862e2e7..44e0989 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,10 @@ arbitrary = ["dep:arbitrary", "std"] get-info-full = [] # enables support for implementing the large-blobs extension, see src/sizes.rs large-blobs = [] +# Size bumps in src/sizes.rs for ML-DSA packed attestation. +mldsa44 = [] +mldsa65 = [] +mldsa87 = [] third-party-payment = [] log-all = [] diff --git a/src/ctap2.rs b/src/ctap2.rs index 3a07a0e..b145210 100644 --- a/src/ctap2.rs +++ b/src/ctap2.rs @@ -292,8 +292,8 @@ impl<'de> Deserialize<'de> for AttestationStatement { use serde::de::Error as _; let mut alg: Option = None; - let mut sig: Option> = None; - let mut x5c: Option, 1>> = None; + let mut sig: Option> = None; + let mut x5c: Option, 1>> = None; let mut has_values = false; while let Some(key) = map.next_key::<&str>()? { has_values = true; @@ -366,9 +366,9 @@ pub struct NoneAttestationStatement {} #[cfg_attr(feature = "platform-serde", derive(Deserialize))] pub struct PackedAttestationStatement { pub alg: i32, - pub sig: Bytes, + pub sig: Bytes, #[serde(skip_serializing_if = "Option::is_none")] - pub x5c: Option, 1>>, + pub x5c: Option, 1>>, } #[derive(Clone, Debug, Default, Eq, PartialEq)] @@ -561,7 +561,14 @@ pub trait Authenticator { Ok(response) } - fn get_next_assertion(&mut self) -> Result; + fn get_next_assertion_into(&mut self, response: &mut get_assertion::Response) -> Result<()>; + + fn get_next_assertion(&mut self) -> Result { + let mut response = get_assertion::Response::empty(); + self.get_next_assertion_into(&mut response)?; + Ok(response) + } + fn reset(&mut self) -> Result<()>; fn client_pin(&mut self, request: &client_pin::Request) -> Result; fn credential_management( @@ -709,10 +716,13 @@ pub trait Authenticator { #[inline(never)] fn dispatch_get_next_assertion(&mut self, response: &mut Response) -> Result<()> { - *response = Response::GetNextAssertion(self.get_next_assertion().inspect_err(|_e| { + *response = Response::GetNextAssertion(get_assertion::Response::empty()); + let Response::GetNextAssertion(inner) = response else { + unreachable!() + }; + self.get_next_assertion_into(inner).inspect_err(|_e| { debug!("error: {:?}", _e); - })?); - Ok(()) + }) } #[inline(never)] diff --git a/src/ctap2/get_assertion.rs b/src/ctap2/get_assertion.rs index 9e139c0..f9befbf 100644 --- a/src/ctap2/get_assertion.rs +++ b/src/ctap2/get_assertion.rs @@ -135,7 +135,7 @@ pub struct Request<'a> { pub struct Response { pub credential: PublicKeyCredentialDescriptor, pub auth_data: Bytes, - pub signature: Bytes, + pub signature: Bytes, #[serde(skip_serializing_if = "Option::is_none")] pub user: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -158,7 +158,7 @@ pub struct Response { pub struct ResponseBuilder { pub credential: PublicKeyCredentialDescriptor, pub auth_data: Bytes, - pub signature: Bytes, + pub signature: Bytes, } impl ResponseBuilder { diff --git a/src/sizes.rs b/src/sizes.rs index eb2c321..cc02e81 100644 --- a/src/sizes.rs +++ b/src/sizes.rs @@ -1,9 +1,45 @@ -pub const AUTHENTICATOR_DATA_LENGTH: usize = 676; +// Largest enabled ML-DSA parameter set: public key / signature bytes (FIPS 204). +const MLDSA: bool = cfg!(any( + feature = "mldsa44", + feature = "mldsa65", + feature = "mldsa87" +)); +const MLDSA_PK: usize = if cfg!(feature = "mldsa87") { + 2592 +} else if cfg!(feature = "mldsa65") { + 1952 +} else if cfg!(feature = "mldsa44") { + 1312 +} else { + 0 +}; +const MLDSA_SIG: usize = if cfg!(feature = "mldsa87") { + 4627 +} else if cfg!(feature = "mldsa65") { + 3309 +} else if cfg!(feature = "mldsa44") { + 2420 +} else { + 0 +}; + +// authData holds the key in a COSE_Key map, plus AAGUID and credId. +pub const AUTHENTICATOR_DATA_LENGTH: usize = if MLDSA { MLDSA_PK + 736 } else { 676 }; // pub const AUTHENTICATOR_DATA_LENGTH_BYTES: usize = 512; pub const ASN1_SIGNATURE_LENGTH: usize = 77; // pub const ASN1_SIGNATURE_LENGTH_BYTES: usize = 72; +// P-256 fits ASN1_SIGNATURE_LENGTH; ML-DSA needs the raw signature. +pub const MAX_PACKED_SIG_LENGTH: usize = if MLDSA { + MLDSA_SIG + 12 +} else { + ASN1_SIGNATURE_LENGTH +}; + +// One x5c entry, bounded by what trussed's Reply.der carries. +pub const MAX_X5C_CERT_LENGTH: usize = if MLDSA { 2048 } else { 1024 }; + pub const COSE_KEY_LENGTH: usize = 256; // pub const COSE_KEY_LENGTH_BYTES: usize = 256; diff --git a/src/webauthn.rs b/src/webauthn.rs index 59a908c..0c26598 100644 --- a/src/webauthn.rs +++ b/src/webauthn.rs @@ -142,15 +142,49 @@ impl PublicKeyCredentialUserEntity { pub enum KnownPublicKeyCredentialParameters { ES256, EdDSA, + #[cfg(feature = "mldsa44")] + MLDSA44, + #[cfg(feature = "mldsa65")] + MLDSA65, + #[cfg(feature = "mldsa87")] + MLDSA87, } impl KnownPublicKeyCredentialParameters { - pub const ALL: [Self; COUNT_KNOWN_ALGS] = [Self::ES256, Self::EdDSA]; + pub const ALL: [Self; COUNT_KNOWN_ALGS] = { + let mut all = [Self::ES256; COUNT_KNOWN_ALGS]; + let mut i = 1; + all[i] = Self::EdDSA; + i += 1; + #[cfg(feature = "mldsa44")] + { + all[i] = Self::MLDSA44; + i += 1; + } + #[cfg(feature = "mldsa65")] + { + all[i] = Self::MLDSA65; + i += 1; + } + #[cfg(feature = "mldsa87")] + { + all[i] = Self::MLDSA87; + i += 1; + } + let _ = i; + all + }; pub fn alg(&self) -> i32 { match self { Self::ES256 => ES256, Self::EdDSA => ED_DSA, + #[cfg(feature = "mldsa44")] + Self::MLDSA44 => ML_DSA_44, + #[cfg(feature = "mldsa65")] + Self::MLDSA65 => ML_DSA_65, + #[cfg(feature = "mldsa87")] + Self::MLDSA87 => ML_DSA_87, } } } @@ -173,8 +207,20 @@ pub enum UnknownPKCredentialParam { const ES256: i32 = -7; /// EdDSA const ED_DSA: i32 = -8; - -pub const COUNT_KNOWN_ALGS: usize = 2; +/// ML-DSA-44 (FIPS 204, NIST level 2) +#[cfg(feature = "mldsa44")] +pub const ML_DSA_44: i32 = -48; +/// ML-DSA-65 (FIPS 204, NIST level 3) +#[cfg(feature = "mldsa65")] +pub const ML_DSA_65: i32 = -49; +/// ML-DSA-87 (FIPS 204, NIST level 5) +#[cfg(feature = "mldsa87")] +pub const ML_DSA_87: i32 = -50; + +pub const COUNT_KNOWN_ALGS: usize = 2 + + cfg!(feature = "mldsa44") as usize + + cfg!(feature = "mldsa65") as usize + + cfg!(feature = "mldsa87") as usize; impl TryFrom for KnownPublicKeyCredentialParameters { type Error = UnknownPKCredentialParam; @@ -186,6 +232,12 @@ impl TryFrom for KnownPublicKeyCredentialParamete match value.alg { ES256 => Ok(Self::ES256), ED_DSA => Ok(Self::EdDSA), + #[cfg(feature = "mldsa44")] + ML_DSA_44 => Ok(Self::MLDSA44), + #[cfg(feature = "mldsa65")] + ML_DSA_65 => Ok(Self::MLDSA65), + #[cfg(feature = "mldsa87")] + ML_DSA_87 => Ok(Self::MLDSA87), _ => Err(UnknownPKCredentialParam::UnknownAlg), } }