Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ arbitrary = ["dep:arbitrary", "std"]
get-info-full = []
# enables support for implementing the large-blobs extension, see src/sizes.rs
large-blobs = []
# Bumps `MAX_PACKED_SIG_LENGTH`, `MAX_X5C_CERT_LENGTH`, and
# `AUTHENTICATOR_DATA_LENGTH` so packed attestation can carry an
# ML-DSA-44 (CTAP 2.3 alg -50) signature (2420 B) alongside the larger
# COSE_Key (1322 B) inside authData. See `src/sizes.rs`.
mldsa44 = []
third-party-payment = []

log-all = []
Expand Down
8 changes: 4 additions & 4 deletions src/ctap2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,8 +292,8 @@ impl<'de> Deserialize<'de> for AttestationStatement {
use serde::de::Error as _;

let mut alg: Option<i32> = None;
let mut sig: Option<Bytes<ASN1_SIGNATURE_LENGTH>> = None;
let mut x5c: Option<Vec<Bytes<1024>, 1>> = None;
let mut sig: Option<Bytes<MAX_PACKED_SIG_LENGTH>> = None;
let mut x5c: Option<Vec<Bytes<MAX_X5C_CERT_LENGTH>, 1>> = None;
let mut has_values = false;
while let Some(key) = map.next_key::<&str>()? {
has_values = true;
Expand Down Expand Up @@ -366,9 +366,9 @@ pub struct NoneAttestationStatement {}
#[cfg_attr(feature = "platform-serde", derive(Deserialize))]
pub struct PackedAttestationStatement {
pub alg: i32,
pub sig: Bytes<ASN1_SIGNATURE_LENGTH>,
pub sig: Bytes<MAX_PACKED_SIG_LENGTH>,
#[serde(skip_serializing_if = "Option::is_none")]
pub x5c: Option<Vec<Bytes<1024>, 1>>,
pub x5c: Option<Vec<Bytes<MAX_X5C_CERT_LENGTH>, 1>>,
}

#[derive(Clone, Debug, Default, Eq, PartialEq)]
Expand Down
4 changes: 2 additions & 2 deletions src/ctap2/get_assertion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ pub struct Request<'a> {
pub struct Response {
pub credential: PublicKeyCredentialDescriptor,
pub auth_data: Bytes<AUTHENTICATOR_DATA_LENGTH>,
pub signature: Bytes<ASN1_SIGNATURE_LENGTH>,
pub signature: Bytes<MAX_PACKED_SIG_LENGTH>,
#[serde(skip_serializing_if = "Option::is_none")]
pub user: Option<PublicKeyCredentialUserEntity>,
#[serde(skip_serializing_if = "Option::is_none")]
Expand All @@ -158,7 +158,7 @@ pub struct Response {
pub struct ResponseBuilder {
pub credential: PublicKeyCredentialDescriptor,
pub auth_data: Bytes<AUTHENTICATOR_DATA_LENGTH>,
pub signature: Bytes<ASN1_SIGNATURE_LENGTH>,
pub signature: Bytes<MAX_PACKED_SIG_LENGTH>,
}

impl ResponseBuilder {
Expand Down
25 changes: 25 additions & 0 deletions src/sizes.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,34 @@
// Sized to hold ML-DSA-44 authData (header + 1312-byte raw public key
// wrapped in a ~10-byte COSE_Key map + AAGUID + credId + extensions).
// Pre-mldsa44 builds used 676 bytes which overflowed silently on alg=-50,
// surfacing as `extend_from_slice` Err → CTAP `Error::Other` (0x7F).
// Gated so non-mldsa44 builds keep the historical footprint.
#[cfg(feature = "mldsa44")]
pub const AUTHENTICATOR_DATA_LENGTH: usize = 2048;
#[cfg(not(feature = "mldsa44"))]
pub const AUTHENTICATOR_DATA_LENGTH: usize = 676;
// pub const AUTHENTICATOR_DATA_LENGTH_BYTES: usize = 512;

pub const ASN1_SIGNATURE_LENGTH: usize = 77;
// pub const ASN1_SIGNATURE_LENGTH_BYTES: usize = 72;

/// Max length of a packed-attestation signature. ECDSA over P-256 fits in
/// `ASN1_SIGNATURE_LENGTH` (77 B). With `mldsa44`, the authenticator may
/// sign with ML-DSA-44 whose signature is 2420 bytes, so we bump.
#[cfg(feature = "mldsa44")]
pub const MAX_PACKED_SIG_LENGTH: usize = 2432;
#[cfg(not(feature = "mldsa44"))]
pub const MAX_PACKED_SIG_LENGTH: usize = ASN1_SIGNATURE_LENGTH;

/// Max length of one x5c entry (the attestation certificate carried in
/// `PackedAttestationStatement.x5c`). Matches what trussed's
/// `read_certificate` Reply.der fits in (`Message`); 1024 historically,
/// 2048 with `mldsa44` so larger Message buffers don't truncate.
#[cfg(feature = "mldsa44")]
pub const MAX_X5C_CERT_LENGTH: usize = 2048;
#[cfg(not(feature = "mldsa44"))]
pub const MAX_X5C_CERT_LENGTH: usize = 1024;

pub const COSE_KEY_LENGTH: usize = 256;
// pub const COSE_KEY_LENGTH_BYTES: usize = 256;

Expand Down
6 changes: 4 additions & 2 deletions src/webauthn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,11 @@ pub enum UnknownPKCredentialParam {
pub const ES256: i32 = -7;
/// EdDSA
pub const ED_DSA: i32 = -8;
/// ML-DSA-44 (FIPS 204, NIST level 2)
pub const ML_DSA_44: i32 = -50;

pub const COUNT_KNOWN_ALGS: usize = 2;
pub const KNOWN_ALGS: [i32; COUNT_KNOWN_ALGS] = [ES256, ED_DSA];
pub const COUNT_KNOWN_ALGS: usize = 3;
pub const KNOWN_ALGS: [i32; COUNT_KNOWN_ALGS] = [ES256, ED_DSA, ML_DSA_44];

impl TryFrom<PublicKeyCredentialParameters> for KnownPublicKeyCredentialParameters {
type Error = UnknownPKCredentialParam;
Expand Down
Loading