webauthn_rp

WebAuthn Level 3 RP library.
git clone https://git.philomathiclife.com/repos/webauthn_rp
Log | Files | Refs | README

commit 96ac16a178abccc48d0d508209801eb091f06bc7
parent 0d5eb451b10d74f5c83e2bc376d4abb82d532a09
Author: Zack Newman <zack@philomathiclife.com>
Date:   Fri, 13 Jun 2025 18:12:33 -0600

allow more rsa keys

Diffstat:
Msrc/response/auth.rs | 23++++++++++-------------
Msrc/response/register.rs | 56+++++++++++++++++++++++++-------------------------------
Msrc/response/register/error.rs | 12+++++++-----
3 files changed, 42 insertions(+), 49 deletions(-)

diff --git a/src/response/auth.rs b/src/response/auth.rs @@ -484,19 +484,16 @@ impl<const USER_LEN: usize, const DISCOVERABLE: bool> AuthResponse }) .map_err(|_e| AuthRespErr::Signature) }), - CompressedPubKey::Rsa(k) => k - .as_ver_key() - .map_err(AuthRespErr::PubKey) - .and_then(|ver_key| { - pkcs1v15::Signature::try_from(self.signature.as_slice()) - .and_then(|sig| { - ver_key.verify( - self.authenticator_data_and_c_data_hash.as_slice(), - &sig, - ) - }) - .map_err(|_e| AuthRespErr::Signature) - }), + CompressedPubKey::Rsa(k) => { + pkcs1v15::Signature::try_from(self.signature.as_slice()) + .and_then(|sig| { + k.as_ver_key().verify( + self.authenticator_data_and_c_data_hash.as_slice(), + &sig, + ) + }) + .map_err(|_e| AuthRespErr::Signature) + } } .map(|()| (client_data_json, val.data)) }) diff --git a/src/response/register.rs b/src/response/register.rs @@ -1408,22 +1408,12 @@ impl<T> RsaPubKey<T> { } } impl<T: AsRef<[u8]>> RsaPubKey<T> { - /// Validates `self` is in fact a valid RSA public key. - /// - /// # Errors - /// - /// Errors iff `self` is not a valid RSA public key. - #[inline] - pub fn validate(&self) -> Result<(), PubKeyErr> { - self.as_ver_key().map(|_| ()) - } /// Converts `self` into [`RsaVerKey`]. - pub(super) fn as_ver_key(&self) -> Result<RsaVerKey<Sha256>, PubKeyErr> { - let n = BigUint::from_bytes_be(self.0.as_ref()); - let bits = n.bits(); - RsaPublicKey::new_with_max_size(n, self.1.into(), bits) - .map_err(|_e| PubKeyErr::Rsa) - .map(RsaVerKey::new) + pub(super) fn as_ver_key(&self) -> RsaVerKey<Sha256> { + RsaVerKey::new(RsaPublicKey::new_unchecked( + BigUint::from_bytes_be(self.0.as_ref()), + self.1.into(), + )) } } impl RsaPubKey<&[u8]> { @@ -1437,8 +1427,7 @@ impl RsaPubKey<&[u8]> { impl<'a: 'b, 'b> TryFrom<(&'a [u8], u32)> for RsaPubKey<&'b [u8]> { type Error = RsaPubKeyErr; /// The first item is the big-endian modulus, and the second item is the exponent. - /// - /// Note the first byte of `n` must not be `0`. + #[expect(clippy::unreachable, reason = "we want to crash when there is a bug")] #[expect( clippy::arithmetic_side_effects, clippy::as_conversions, @@ -1446,7 +1435,7 @@ impl<'a: 'b, 'b> TryFrom<(&'a [u8], u32)> for RsaPubKey<&'b [u8]> { )] #[inline] fn try_from((n, e): (&'a [u8], u32)) -> Result<Self, Self::Error> { - n.first().map_or(Err(RsaPubKeyErr::N), |fst| { + n.first().map_or(Err(RsaPubKeyErr::NSize), |fst| { if *fst == 0 { Err(RsaPubKeyErr::NLeading0) } else if let Some(bits) = n.len().checked_mul(8) { @@ -1456,16 +1445,23 @@ impl<'a: 'b, 'b> TryFrom<(&'a [u8], u32)> for RsaPubKey<&'b [u8]> { // `bits` is at least 8 since `n.len()` is at least 1; thus underflow cannot occur. .contains(&(bits - fst.leading_zeros() as usize)) { - if e >= MIN_RSA_E { + // We know `n` is not `empty`, so this won't `panic`. + if n.last() + .unwrap_or_else(|| unreachable!("there is a bug in RsaPubKey::try_from")) + & 1 + == 0 + { + Err(RsaPubKeyErr::NEven) + } else if e >= MIN_RSA_E { Ok(Self(n, e)) } else { Err(RsaPubKeyErr::E) } } else { - Err(RsaPubKeyErr::N) + Err(RsaPubKeyErr::NSize) } } else { - Err(RsaPubKeyErr::N) + Err(RsaPubKeyErr::NSize) } }) } @@ -1910,7 +1906,7 @@ impl UncompressedPubKey<'_> { Self::Ed25519(k) => k.validate(), Self::P256(k) => k.validate(), Self::P384(k) => k.validate(), - Self::Rsa(k) => k.validate(), + Self::Rsa(_) => Ok(()), } } /// Transforms `self` into the compressed version that owns the data. @@ -1982,7 +1978,7 @@ impl CompressedPubKey<&[u8], &[u8], &[u8], &[u8]> { Self::Ed25519(k) => k.validate(), Self::P256(k) => k.validate(), Self::P384(k) => k.validate(), - Self::Rsa(k) => k.validate(), + Self::Rsa(_) => Ok(()), } } } @@ -2967,15 +2963,13 @@ impl AuthResponse for AuthenticatorAttestation { } } }), - UncompressedPubKey::Rsa(key) => key.as_ver_key().map_err(AuthRespErr::PubKey).and_then(|ver_key| { - match val.data.attestation { - AttestationFormat::None => Ok(()), - AttestationFormat::Packed(packed) => match packed.signature { - Sig::Rs256(sig) => pkcs1v15::Signature::try_from(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| ver_key.verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)), - Sig::Ed25519(_) | Sig::P256(_) | Sig::P384(_) => unreachable!("there is a bug in AttestationObject::from_data"), - } + UncompressedPubKey::Rsa(key) => match val.data.attestation { + AttestationFormat::None => Ok(()), + AttestationFormat::Packed(packed) => match packed.signature { + Sig::Rs256(sig) => pkcs1v15::Signature::try_from(sig).map_err(|_e| AuthRespErr::Signature).and_then(|s| key.as_ver_key().verify(val.auth_data_and_32_trailing_bytes, &s).map_err(|_e| AuthRespErr::Signature)), + Sig::Ed25519(_) | Sig::P256(_) | Sig::P384(_) => unreachable!("there is a bug in AttestationObject::from_data"), } - }), + }, }.map(|()| (client_data_json, val.data)) }) }) diff --git a/src/response/register/error.rs b/src/response/register/error.rs @@ -108,7 +108,9 @@ pub enum RsaPubKeyErr { NLeading0, /// Variant returned when the modulus has fewer than [`MIN_RSA_N_BITS`] or more than /// [`MAX_RSA_N_BITS`]. - N, + NSize, + /// Variant returned when the modulus is even. + NEven, /// Variant returned when the exponent is less than [`MIN_RSA_E`]. E, } @@ -117,7 +119,10 @@ impl Display for RsaPubKeyErr { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.write_str(match *self { Self::NLeading0 => "the RSA public key modulus had a leading 0", - Self::N => "the RSA public key modulus was less than 2048 bits or greater than 16384", + Self::NSize => { + "the RSA public key modulus was less than 2048 bits or greater than 16384" + } + Self::NEven => "the RSA public key modulus was even", Self::E => "the RSA public key exponent was less than 3", }) } @@ -132,8 +137,6 @@ pub enum PubKeyErr { P256, /// Error when [`UncompressedP384PubKey`] or [`CompressedP384PubKey`] is not valid. P384, - /// Error when [`RsaPubKey`] is not valid. - Rsa, } impl Display for PubKeyErr { #[inline] @@ -142,7 +145,6 @@ impl Display for PubKeyErr { Self::Ed25519 => "Ed25519 public key is invalid", Self::P256 => "P-256 public key is invalid", Self::P384 => "P-384 public key is invalid", - Self::Rsa => "RSA public key is invalid", }) } }