Skip to content
Open
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
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
26 changes: 18 additions & 8 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 Expand Up @@ -561,7 +561,14 @@ pub trait Authenticator {
Ok(response)
}

fn get_next_assertion(&mut self) -> Result<get_assertion::Response>;
fn get_next_assertion_into(&mut self, response: &mut get_assertion::Response) -> Result<()>;

fn get_next_assertion(&mut self) -> Result<get_assertion::Response> {
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<client_pin::Response>;
fn credential_management(
Expand Down Expand Up @@ -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)]
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
38 changes: 37 additions & 1 deletion src/sizes.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
58 changes: 55 additions & 3 deletions src/webauthn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
}
}
Expand All @@ -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<PublicKeyCredentialParameters> for KnownPublicKeyCredentialParameters {
type Error = UnknownPKCredentialParam;
Expand All @@ -186,6 +232,12 @@ impl TryFrom<PublicKeyCredentialParameters> 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),
}
}
Expand Down
Loading