commit 7b98598bba8ee7bf187f80f99d3b415365f63517
parent 9122de4849b2743665778898c615462791d53af4
Author: Zack Newman <zack@philomathiclife.com>
Date: Thu, 20 Feb 2025 14:14:57 -0700
rust 2024
Diffstat:
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;