webauthn_rp

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

commit 7b98598bba8ee7bf187f80f99d3b415365f63517
parent 9122de4849b2743665778898c615462791d53af4
Author: Zack Newman <zack@philomathiclife.com>
Date:   Thu, 20 Feb 2025 14:14:57 -0700

rust 2024

Diffstat:
MCargo.toml | 10+++++-----
Msrc/lib.rs | 42+++++++++++++++++++++---------------------
Msrc/request.rs | 41++++++++++++++++++++++++++++++++++-------
3 files changed, 60 insertions(+), 33 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -3,13 +3,13 @@ authors = ["Zack Newman <zack@philomathiclife.com>"] categories = ["api-bindings", "authentication", "web-programming"] description = "Server-side Web Authentication (WebAuthn) Relying Party (RP) API." documentation = "https://docs.rs/webauthn_rp/latest/webauthn_rp/" -edition = "2021" +edition = "2024" keywords = ["authentication", "fido2", "passkey", "rp", "webauthn"] license = "MIT OR Apache-2.0" name = "webauthn_rp" readme = "README.md" repository = "https://git.philomathiclife.com/repos/webauthn_rp/" -rust-version = "1.84.0" +rust-version = "1.85.0" version = "0.2.4" [package.metadata.docs.rs] @@ -24,8 +24,8 @@ p384 = { version = "0.13.1", default-features = false, features = ["ecdsa"] } precis-profiles = { version = "0.1.11", default-features = false } rand = { version = "0.9.0", default-features = false, features = ["thread_rng"] } rsa = { version = "0.9.7", default-features = false, features = ["sha2"] } -serde = { version = "1.0.217", default-features = false, features = ["alloc"], optional = true } -serde_json = { version = "1.0.138", default-features = false, features = ["alloc"], optional = true } +serde = { version = "1.0.218", default-features = false, features = ["alloc"], optional = true } +serde_json = { version = "1.0.139", default-features = false, features = ["alloc"], optional = true } url = { version = "2.5.4", default-features = false } [dev-dependencies] @@ -33,7 +33,7 @@ data-encoding = { version = "2.8.0", default-features = false, features = ["allo ed25519-dalek = { version = "2.1.1", default-features = false, features = ["alloc", "pkcs8"] } p256 = { version = "0.13.2", default-features = false, features = ["pem"] } p384 = { version = "0.13.1", default-features = false, features = ["pkcs8"] } -serde_json = { version = "1.0.138", default-features = false, features = ["preserve_order"] } +serde_json = { version = "1.0.139", default-features = false, features = ["preserve_order"] } ### FEATURES ################################################################# diff --git a/src/lib.rs b/src/lib.rs @@ -231,44 +231,44 @@ use crate::{ register::bin::{DecodeDynamicStateErr, DecodeStaticStateErr}, }, }; +#[cfg(doc)] +use crate::{ + request::{ + AsciiDomain, DomainOrigin, FixedCapHashSet, Port, PublicKeyCredentialDescriptor, RpId, + Scheme, ServerState, Url, + auth::{AllowedCredential, AllowedCredentials}, + register::{CoseAlgorithmIdentifier, Nickname, Username}, + }, + response::{ + CollectedClientData, Flag, + auth::{self, AuthenticatorAssertion}, + register::{ + self, Aaguid, Attestation, AttestationObject, AttestedCredentialData, + AuthenticatorExtensionOutput, ClientExtensionsOutputs, CompressedPubKey, + CredentialPropertiesOutput, + }, + }, +}; use crate::{ request::{ auth::error::{RequestOptionsErr, SecondFactorErr}, error::{AsciiDomainErr, DomainOriginParseErr, PortParseErr, SchemeParseErr, UrlErr}, register::{ - error::{CreationOptionsErr, NicknameErr, UserHandleErr, UsernameErr}, ResidentKeyRequirement, UserHandle, + error::{CreationOptionsErr, NicknameErr, UserHandleErr, UsernameErr}, }, }, response::{ + AuthTransports, CredentialId, auth::error::{AuthCeremonyErr, AuthenticatorDataErr as AuthAuthDataErr}, error::CollectedClientDataErr, register::{ + CredentialProtectionPolicy, DynamicState, Metadata, StaticState, UncompressedPubKey, error::{ AaguidErr, AttestationObjectErr, AuthenticatorDataErr as RegAuthDataErr, RegCeremonyErr, }, - CredentialProtectionPolicy, DynamicState, Metadata, StaticState, UncompressedPubKey, }, - AuthTransports, CredentialId, - }, -}; -#[cfg(doc)] -use crate::{ - request::{ - auth::{AllowedCredential, AllowedCredentials}, - register::{CoseAlgorithmIdentifier, Nickname, Username}, - AsciiDomain, DomainOrigin, FixedCapHashSet, Port, PublicKeyCredentialDescriptor, RpId, - Scheme, ServerState, Url, - }, - response::{ - auth::{self, AuthenticatorAssertion}, - register::{ - self, Aaguid, Attestation, AttestationObject, AttestedCredentialData, - AuthenticatorExtensionOutput, ClientExtensionsOutputs, CompressedPubKey, - CredentialPropertiesOutput, - }, - CollectedClientData, Flag, }, }; #[cfg(all(doc, feature = "bin"))] diff --git a/src/request.rs b/src/request.rs @@ -253,6 +253,32 @@ impl From<&Challenge> for u128 { /// allowed. #[derive(Clone, Debug, Eq, PartialEq)] pub struct AsciiDomain(String); +impl AsciiDomain { + /// Removes a trailing `'.'` if it exists. + /// + /// # Examples + /// + /// ``` + /// # use webauthn_rp::request::{error::AsciiDomainErr, AsciiDomain}; + /// let mut dom = AsciiDomain::try_from("example.com.".to_owned())?; + /// dom.remove_trailing_dot(); + /// assert_eq!(dom.as_ref(), "example.com"); + /// # Ok::<_, AsciiDomainErr>(()) + /// ``` + #[expect(clippy::unreachable, reason = "want to crash when there is a bug")] + #[inline] + pub fn remove_trailing_dot(&mut self) { + if *self + .0 + .as_bytes() + .last() + .unwrap_or_else(|| unreachable!("there is a bug in AsciiDomain::try_from")) + == b'.' + { + self.0.pop(); + } + } +} impl AsRef<str> for AsciiDomain { #[inline] fn as_ref(&self) -> &str { @@ -291,8 +317,9 @@ impl TryFrom<String> for AsciiDomain { /// Note it is _strongly_ encouraged for `value` to only contain letters, numbers, hyphens, and underscores; /// otherwise certain applications may consider it not a domain. If the original domain contains non-ASCII, then /// one must encode it in Punycode _before_ calling this function. Domains that have a trailing `'.'` will be - /// considered differently than domains without it; thus one will likely want to trim it if it does exist. - /// Because this allows any ASCII, one may want to ensure `value` is not an IPv4 address. + /// considered differently than domains without it; thus one will likely want to trim it if it does exist + /// (e.g., [`AsciiDomain::remove_trailing_dot`]). Because this allows any ASCII, one may want to ensure `value` + /// is not an IP address. /// /// # Examples /// @@ -1800,7 +1827,9 @@ mod tests { #[cfg(feature = "custom")] use super::{ super::{ + AggErr, AuthenticatedCredential, response::{ + AuthTransports, AuthenticatorAttachment, Backup, CredentialId, auth::{Authentication, AuthenticatorAssertion}, register::{ AuthenticationExtensionsPrfOutputs, AuthenticatorAttestation, @@ -1809,10 +1838,10 @@ mod tests { CredentialProtectionPolicy, DynamicState, Ed25519PubKey, Registration, RsaPubKey, StaticState, UncompressedPubKey, }, - AuthTransports, AuthenticatorAttachment, Backup, CredentialId, }, - AggErr, AuthenticatedCredential, }, + AsciiDomain, Challenge, Credentials, ExtensionInfo, ExtensionReq, + PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement, auth::{ AllowedCredential, AllowedCredentials, AuthenticationVerificationOptions, CredentialSpecificExtension, Extension as AuthExt, PrfInputOwned, @@ -1822,8 +1851,6 @@ mod tests { CredProtect, Extension as RegExt, PublicKeyCredentialCreationOptions, PublicKeyCredentialUserEntity, RegistrationVerificationOptions, UserHandle, }, - AsciiDomain, Challenge, Credentials, ExtensionInfo, ExtensionReq, - PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement, }; #[cfg(feature = "custom")] use ed25519_dalek::{Signer, SigningKey}; @@ -1836,11 +1863,11 @@ mod tests { use p384::ecdsa::{DerSignature as P384DerSig, SigningKey as P384Key}; #[cfg(feature = "custom")] use rsa::{ + BigUint, RsaPrivateKey, pkcs1v15::SigningKey as RsaKey, sha2::{Digest, Sha256}, signature::{Keypair, SignatureEncoding}, traits::PublicKeyParts, - BigUint, RsaPrivateKey, }; #[cfg(feature = "custom")] const CBOR_UINT: u8 = 0b000_00000;