commit 13aba926a476a81e2c94f0971bfecba3457ca51a
parent 5239f6a43dda53268908438de4449f56910c8405
Author: Zack Newman <zack@philomathiclife.com>
Date: Wed, 29 Jan 2025 23:58:22 -0700
prevent panics from encoding::decode_mut
Diffstat:
13 files changed, 190 insertions(+), 158 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
@@ -10,7 +10,7 @@ name = "webauthn_rp"
readme = "README.md"
repository = "https://git.philomathiclife.com/repos/webauthn_rp/"
rust-version = "1.83.0"
-version = "0.2.2"
+version = "0.2.3"
[package.metadata.docs.rs]
all-features = true
@@ -22,10 +22,10 @@ ed25519-dalek = { version = "2.1.1", default-features = false, features = ["fast
p256 = { version = "0.13.2", default-features = false, features = ["ecdsa"] }
p384 = { version = "0.13.0", default-features = false, features = ["ecdsa"] }
precis-profiles = { version = "0.1.11", default-features = false }
-rand = { version = "0.8.5", default-features = false, features = ["std", "std_rng"] }
+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.137", default-features = false, features = ["alloc"], optional = true }
+serde_json = { version = "1.0.138", 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.6.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.0", default-features = false, features = ["pkcs8"] }
-serde_json = { version = "1.0.137", default-features = false, features = ["preserve_order"] }
+serde_json = { version = "1.0.138", default-features = false, features = ["preserve_order"] }
### FEATURES #################################################################
diff --git a/src/lib.rs b/src/lib.rs
@@ -200,6 +200,7 @@
clippy::implicit_return,
clippy::min_ident_chars,
clippy::missing_trait_methods,
+ clippy::multiple_crate_versions,
clippy::pub_with_shorthand,
clippy::pub_use,
clippy::ref_patterns,
@@ -279,7 +280,6 @@ use core::{
error::Error,
fmt::{self, Display, Formatter},
};
-use data_encoding::{Encoding, BASE64URL_NOPAD};
#[cfg(all(doc, feature = "serde_relaxed"))]
use response::register::ser_relaxed::RegistrationRelaxed;
#[cfg(all(doc, feature = "serde"))]
@@ -1134,14 +1134,12 @@ impl Error for AggErr {}
///
/// # Panics
///
-/// `panic`s iff `n >= 0x4000`.
+/// `panic`s iff `4 * n` overflows.
+#[expect(
+ clippy::expect_used,
+ reason = "a precondition for calling this function ensures expect won't panic"
+)]
const fn base64url_nopad_len(n: usize) -> usize {
- // `usize::MAX >= u16::MAX`; thus we conservatively cap `n` to the largest value that would not overflow
- // from multiplying by 4 on all platforms.
- assert!(
- n < 0x4000,
- "webauthn_rp::base64url_nopad_len must be passed an integer smaller than 0x4000"
- );
// 256^n is the number of distinct values of the input. Let the base64 encoding in a URL safe
// way without padding of the input be O. There are 64 possible values each byte in O can be; thus we must find
// the minimum nonnegative integer m such that:
@@ -1151,19 +1149,19 @@ const fn base64url_nopad_len(n: usize) -> usize {
// <==>
// m >= 8n/6 = 4n/3
// Clearly that corresponds to m = ⌈4n/3⌉; thus:
- (n << 2).div_ceil(3)
+
+ n.checked_mul(4).expect("webauthn_rp::base64url_nopad_len should not have been passed a usize that when multiplied by 4 causes overflow").div_ceil(3)
}
/// Calculates the number of bytes a base64url-encoded input of length `n` bytes will be decoded into.
///
-/// `Some` is returned iff the value could be calculated without overflow; otherwise `None` is returned.
-///
-/// Note this does not verify if `n` is a possible value.
+/// `Some` is returned iff `n` is a valid input length.
+#[cfg(feature = "serde")]
#[expect(
- clippy::unseparated_literal_suffix,
- reason = "noisy, opinionated, and likely doesn't prevent bugs or improve APIs"
+ clippy::arithmetic_side_effects,
+ clippy::integer_division_remainder_used,
+ reason = "proof and comment justifies its correctness"
)]
-#[cfg(feature = "serde")]
-fn base64url_nopad_decode_len(n: usize) -> Option<usize> {
+const fn base64url_nopad_decode_len(n: usize) -> Option<usize> {
// 64^n is the number of distinct values of the input. Let the decoded output be O.
// There are 256 possible values each byte in O can be; thus we must find
// the maximum nonnegative integer m such that:
@@ -1172,9 +1170,42 @@ fn base64url_nopad_decode_len(n: usize) -> Option<usize> {
// lg(2^(8m)) = 8m <= lg(2^(6n)) = 6n lg is defined on all positive reals which 2^(8m) and 2^(6n) are
// <==>
// m <= 6n/8 = 3n/4
- // Clearly that corresponds to m = ⌊3n/4⌋; thus:
- n.checked_mul(3).map(|len| len >> 2u8)
+ // Clearly that corresponds to m = ⌊3n/4⌋.
+ //
+ // There are three partitions for m:
+ // (1) m ≡ 0 (mod 3) = 3i
+ // (2) m ≡ 1 (mod 3) = 3i + 1
+ // (3) m ≡ 2 (mod 3) = 3i + 2
+ //
+ // From `crate::base64url_nopad_len`, we know that the encoded length, n, of an input of length m is m = ⌈4n/3⌉.
+ // The encoded length of (1) is thus n = ⌈4(3i)/3⌉ = ⌈4i⌉ = 4i ≡ 0 (mod 4).
+ // The encoded length of (2) is thus n = ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + 2 ≡ 2 (mod 4).
+ // The encoded length of (3) is thus n = ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + 3 ≡ 3 (mod 4).
+ //
+ // Thus if n ≡ 1 (mod 4), it is never a valid length.
+ //
+ // Let n be the length of a possible encoded output of an input of length m.
+ // We know from above that n ≢ 1 (mod 4), this leaves three possibilities:
+ // (1) n ≡ 0 (mod 4) = 4i
+ // (2) n ≡ 2 (mod 4) = 4i + 2
+ // (3) n ≡ 3 (mod 4) = 4i + 3
+ //
+ // For (1) an input of length 3i is the inverse since ⌈4(3i)/3⌉ = 4i.
+ // For (2) an input of length 3i + 1 is the inverse since ⌈4(3i + 1)/3⌉ = ⌈4i + 4/3⌉ = 4i + 2.
+ // For (3) an input of length 3i + 2 is the inverse since ⌈4(3i + 2)/3⌉ = ⌈4i + 8/3⌉ = 4i + 3.
+ //
+ // Consequently n is a valid length of an encoded output iff n ≢ 1 (mod 4).
+
+ if n % 4 == 1 {
+ None
+ } else {
+ let (quot, rem) = (n >> 2u8, n % 4);
+ // 4quot + rem = n
+ // rem <= 3
+ // 3rem <= 9
+ // 3rem/4 <= 2
+ // 3quot + 3rem/4 <= 4quot + rem
+ // Thus no operation causes overflow or underflow.
+ Some((3 * quot) + ((3 * rem) >> 2u8))
+ }
}
-// We cache a `static` instance for performance reasons.
-/// Encoder/decoder for base64url.
-const BASE64URL_NOPAD_ENC: &Encoding = &BASE64URL_NOPAD;
diff --git a/src/request.rs b/src/request.rs
@@ -28,7 +28,6 @@ use core::{
num::NonZeroU32,
str::FromStr,
};
-use rand::Rng as _;
use rsa::sha2::{Digest as _, Sha256};
#[cfg(feature = "serializable_server_state")]
use std::time::SystemTime;
@@ -210,7 +209,7 @@ impl Challenge {
#[inline]
#[must_use]
pub fn new() -> Self {
- Self(rand::thread_rng().r#gen())
+ Self(rand::random())
}
/// Returns the contained `u128` consuming `self`.
#[inline]
diff --git a/src/request/auth/ser.rs b/src/request/auth/ser.rs
@@ -1,7 +1,8 @@
use super::{
- super::super::BASE64URL_NOPAD_ENC, AllowedCredential, AllowedCredentials,
- AuthenticationClientState, Extension, PrfInput, PrfInputOwned,
+ AllowedCredential, AllowedCredentials, AuthenticationClientState, Extension, PrfInput,
+ PrfInputOwned,
};
+use data_encoding::BASE64URL_NOPAD;
use serde::ser::{Serialize, SerializeMap as _, SerializeStruct as _, Serializer};
impl Serialize for PrfInput<'_> {
/// Serializes `self` to conform with
@@ -34,14 +35,14 @@ impl Serialize for PrfInput<'_> {
// The max value is 1 + 1 = 2, so overflow is not an issue.
.serialize_struct("PrfInput", 1 + usize::from(self.second.is_some()))
.and_then(|mut ser| {
- ser.serialize_field("first", BASE64URL_NOPAD_ENC.encode(self.first).as_str())
+ ser.serialize_field("first", BASE64URL_NOPAD.encode(self.first).as_str())
.and_then(|()| {
self.second
.as_ref()
.map_or(Ok(()), |second| {
ser.serialize_field(
"second",
- BASE64URL_NOPAD_ENC.encode(second).as_str(),
+ BASE64URL_NOPAD.encode(second).as_str(),
)
})
.and_then(|()| ser.end())
diff --git a/src/request/register.rs b/src/request/register.rs
@@ -32,7 +32,6 @@ use core::{
time::Duration,
};
use precis_profiles::{precis_core::profile::Profile as _, UsernameCasePreserved};
-use rand::RngCore as _;
#[cfg(any(doc, not(feature = "serializable_server_state")))]
use std::time::Instant;
#[cfg(any(doc, feature = "serializable_server_state"))]
@@ -725,9 +724,7 @@ impl UserHandle<Vec<u8>> {
pub fn rand(len: usize) -> Result<Self, UserHandleErr> {
if (USER_HANDLE_MIN_LEN..=USER_HANDLE_MAX_LEN).contains(&len) {
let mut data = vec![0; len];
- // [`ThreadRng` is infallible](https://docs.rs/rand_core/latest/src/rand_core/block.rs.html#237);
- // thus there is no point in calling `try_fill_bytes`.
- rand::thread_rng().fill_bytes(data.as_mut_slice());
+ rand::fill(data.as_mut_slice());
Ok(Self(data))
} else {
Err(UserHandleErr)
diff --git a/src/request/register/ser.rs b/src/request/register/ser.rs
@@ -5,7 +5,7 @@
)]
extern crate alloc;
use super::{
- super::super::BASE64URL_NOPAD_ENC, AuthenticatorAttachmentReq, AuthenticatorSelectionCriteria,
+ AuthenticatorAttachmentReq, AuthenticatorSelectionCriteria,
CoseAlgorithmIdentifier, CoseAlgorithmIdentifiers, CredProtect, CrossPlatformHint, Extension,
ExtensionInfo, Hint, Nickname, PlatformHint, PublicKeyCredentialUserEntity,
RegistrationClientState, ResidentKeyRequirement, RpId, UserHandle, Username,
@@ -19,6 +19,7 @@ use core::{
marker::PhantomData,
str,
};
+use data_encoding::BASE64URL_NOPAD;
use serde::{
de::{Deserialize, Deserializer, Error, MapAccess, Unexpected, Visitor},
ser::{Serialize, SerializeSeq as _, SerializeStruct, Serializer},
@@ -202,7 +203,7 @@ impl<T: AsRef<[u8]>> Serialize for UserHandle<T> {
where
S: Serializer,
{
- serializer.serialize_str(BASE64URL_NOPAD_ENC.encode(self.0.as_ref()).as_str())
+ serializer.serialize_str(BASE64URL_NOPAD.encode(self.0.as_ref()).as_str())
}
}
/// `"displayName"`.
@@ -840,7 +841,7 @@ impl<'de> Deserialize<'de> for UserHandle<Vec<u8>> {
..=crate::base64url_nopad_len(USER_HANDLE_MAX_LEN))
.contains(&v.len())
{
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode(v.as_bytes())
.map_err(E::custom)
.map(UserHandle)
diff --git a/src/request/ser.rs b/src/request/ser.rs
@@ -1,8 +1,8 @@
use super::{
- super::BASE64URL_NOPAD_ENC, Challenge, CredentialId, Hint, PublicKeyCredentialDescriptor, RpId,
- UserVerificationRequirement,
+ Challenge, CredentialId, Hint, PublicKeyCredentialDescriptor, RpId, UserVerificationRequirement,
};
use core::str;
+use data_encoding::BASE64URL_NOPAD;
use serde::ser::{Serialize, SerializeSeq as _, SerializeStruct as _, Serializer};
impl Serialize for Challenge {
/// Serializes `self` to conform with
@@ -30,7 +30,7 @@ impl Serialize for Challenge {
S: Serializer,
{
let mut data = [0; Self::BASE64_LEN];
- BASE64URL_NOPAD_ENC.encode_mut(self.0.to_le_bytes().as_slice(), data.as_mut_slice());
+ BASE64URL_NOPAD.encode_mut(self.0.to_le_bytes().as_slice(), data.as_mut_slice());
serializer.serialize_str(
str::from_utf8(data.as_slice())
// There is a bug, so crash and burn.
diff --git a/src/response.rs b/src/response.rs
@@ -9,7 +9,6 @@ use crate::{
error::{CollectedClientDataErr, CredentialIdErr},
register::error::{AttestationObjectErr, AttestedCredentialDataErr, AuthenticatorDataErr as RegAuthDataErr, AuthenticatorExtensionOutputErr as RegAuthExtErr, PubKeyErr, RegCeremonyErr},
},
- BASE64URL_NOPAD_ENC,
};
use alloc::borrow::Cow;
use core::{
@@ -20,6 +19,7 @@ use core::{
hash::{Hash, Hasher},
str,
};
+use data_encoding::BASE64URL_NOPAD;
use rsa::sha2::{digest::OutputSizeUser as _, Sha256};
#[cfg(feature = "serde_relaxed")]
use ser_relaxed::SerdeJsonErr;
@@ -1059,7 +1059,7 @@ impl<const R: bool> ClientDataJsonParser for LimitedVerificationParser<R> {
if chall_key == AFTER_TYPE {
chall_key_rem.split_at_checked(Challenge::BASE64_LEN).ok_or(CollectedClientDataErr::Len).and_then(|(base64_chall, base64_chall_rem)| {
let mut chall = [0; 16];
- BASE64URL_NOPAD_ENC.decode_mut(base64_chall, chall.as_mut_slice()).map_err(|_e| CollectedClientDataErr::Challenge).and_then(|chall_len| {
+ BASE64URL_NOPAD.decode_mut(base64_chall, chall.as_mut_slice()).map_err(|_e| CollectedClientDataErr::Challenge).and_then(|chall_len| {
assert_eq!(chall_len, 16, "there is a bug in BASE64URL_NOPAD::decode_mut");
base64_chall_rem.split_at_checked(AFTER_CHALLENGE.len()).ok_or(CollectedClientDataErr::Len).and_then(|(origin_key, origin_key_rem)| {
if origin_key == AFTER_CHALLENGE {
@@ -1149,7 +1149,7 @@ impl<const R: bool> ClientDataJsonParser for LimitedVerificationParser<R> {
// This maxes at 39 + 22 = 61; thus overflow is not an issue.
json.get(idx..idx + Challenge::BASE64_LEN).ok_or(CollectedClientDataErr::Len).and_then(|chall_slice| {
let mut chall = [0; 16];
- BASE64URL_NOPAD_ENC.decode_mut(chall_slice, chall.as_mut_slice()).map_err(|_e| CollectedClientDataErr::Challenge).map(|len| {
+ BASE64URL_NOPAD.decode_mut(chall_slice, chall.as_mut_slice()).map_err(|_e| CollectedClientDataErr::Challenge).map(|len| {
assert_eq!(len, 16, "there is a bug in BASE64URL_NOPAD::decode_mut");
SentChallenge(u128::from_le_bytes(chall))
})
diff --git a/src/response/auth/ser.rs b/src/response/auth/ser.rs
@@ -9,7 +9,6 @@ use super::{
AuthenticationExtensionsPrfOutputsHelper, AuthenticationExtensionsPrfValues,
ClientExtensions,
},
- BASE64URL_NOPAD_ENC,
},
error::UnknownCredentialOptions,
Authentication, AuthenticatorAssertion,
@@ -21,7 +20,6 @@ use core::{
marker::PhantomData,
str,
};
-#[cfg(doc)]
use data_encoding::BASE64URL_NOPAD;
use rsa::sha2::{digest::OutputSizeUser as _, Sha256};
use serde::{
@@ -109,25 +107,39 @@ impl<'d, const R: bool> Visitor<'d> for AuthenticatorAssertionVisitor<R> {
}
#[expect(
clippy::panic_in_result_fn,
- clippy::unreachable,
reason = "we want to crash when there is a bug"
)]
+ #[expect(
+ clippy::arithmetic_side_effects,
+ reason = "comment justifies its correctness"
+ )]
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
- crate::base64url_nopad_decode_len(v.len()).ok_or_else(|| E::invalid_value(Unexpected::Str(v), &"a shorter base64url-encoded value")).and_then(|len| {
- // The decoded length is 3/4 of the encoded length, so overflow could only occur
- // if usize::MAX / 4 < 32 => usize::MAX < 128 < u8::MAX; thus overflow is not possible.
- // We add 32 since the SHA-256 hash of `clientDataJSON` will be added to the
- // raw authenticator data by `AuthenticatorDataAssertion::new`.
- let mut auth_data = vec![0; len.checked_add(Sha256::output_size()).unwrap_or_else(|| unreachable!("there is a bug webauthn_rp::base64url_nopad_decode_len"))];
- auth_data.truncate(len);
- BASE64URL_NOPAD_ENC.decode_mut(v.as_bytes(), auth_data.as_mut_slice()).map_err(|e| E::custom(e.error)).map(|dec_len| {
- assert_eq!(len, dec_len, "there is a bug in BASE64URL_NOPAD::decode_mut");
- AuthData(auth_data)
+ crate::base64url_nopad_decode_len(v.len())
+ .ok_or_else(|| {
+ E::invalid_value(Unexpected::Str(v), &"base64url-encoded value")
+ })
+ .and_then(|len| {
+ // The decoded length is 3/4 of the encoded length, so overflow could only occur
+ // if usize::MAX / 4 < 32 => usize::MAX < 128 < u8::MAX; thus overflow is not
+ // possible.
+ // We add 32 since the SHA-256 hash of `clientDataJSON` will be added to the
+ // raw authenticator data by `AuthenticatorDataAssertion::new`.
+ let mut auth_data = vec![0; len + Sha256::output_size()];
+ auth_data.truncate(len);
+ BASE64URL_NOPAD
+ .decode_mut(v.as_bytes(), auth_data.as_mut_slice())
+ .map_err(|e| E::custom(e.error))
+ .map(|dec_len| {
+ assert_eq!(
+ len, dec_len,
+ "there is a bug in BASE64URL_NOPAD::decode_mut"
+ );
+ AuthData(auth_data)
+ })
})
- })
}
}
deserializer.deserialize_str(AuthDataVisitor)
@@ -425,10 +437,8 @@ impl Serialize for UnknownCredentialOptions<'_, '_> {
}
#[cfg(test)]
mod tests {
- use super::{
- super::{Authentication, AuthenticatorAttachment},
- BASE64URL_NOPAD_ENC,
- };
+ use super::super::{Authentication, AuthenticatorAttachment};
+ use data_encoding::BASE64URL_NOPAD;
use rsa::sha2::{Digest as _, Sha256};
use serde::de::{Error as _, Unexpected};
use serde_json::Error;
@@ -477,10 +487,10 @@ mod tests {
0,
0,
];
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(auth_data.as_slice());
- let b64_sig = BASE64URL_NOPAD_ENC.encode([].as_slice());
- let b64_user = BASE64URL_NOPAD_ENC.encode(b"\x00".as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(auth_data.as_slice());
+ let b64_sig = BASE64URL_NOPAD.encode([].as_slice());
+ let b64_user = BASE64URL_NOPAD.encode(b"\x00".as_slice());
// Base case is valid.
assert!(serde_json::from_str::<Authentication>(
serde_json::json!({
@@ -511,7 +521,7 @@ mod tests {
// `id` and `rawId` mismatch.
let mut err = Error::invalid_value(
Unexpected::Bytes(
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode("ABABABABABABABABABABAA".as_bytes())
.unwrap()
.as_slice(),
@@ -1265,10 +1275,10 @@ mod tests {
0,
0,
];
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(auth_data.as_slice());
- let b64_sig = BASE64URL_NOPAD_ENC.encode([].as_slice());
- let b64_user = BASE64URL_NOPAD_ENC.encode(b"\x00".as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(auth_data.as_slice());
+ let b64_sig = BASE64URL_NOPAD.encode([].as_slice());
+ let b64_user = BASE64URL_NOPAD.encode(b"\x00".as_slice());
// Base case is valid.
assert!(serde_json::from_str::<Authentication>(
serde_json::json!({
diff --git a/src/response/auth/ser_relaxed.rs b/src/response/auth/ser_relaxed.rs
@@ -119,10 +119,8 @@ impl<'de> Deserialize<'de> for AuthenticationRelaxed {
}
#[cfg(test)]
mod tests {
- use super::{
- super::{super::BASE64URL_NOPAD_ENC, AuthenticatorAttachment},
- AuthenticationRelaxed,
- };
+ use super::{super::AuthenticatorAttachment, AuthenticationRelaxed};
+ use data_encoding::BASE64URL_NOPAD;
use rsa::sha2::{Digest as _, Sha256};
use serde::de::{Error as _, Unexpected};
use serde_json::Error;
@@ -171,10 +169,10 @@ mod tests {
0,
0,
];
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(auth_data.as_slice());
- let b64_sig = BASE64URL_NOPAD_ENC.encode([].as_slice());
- let b64_user = BASE64URL_NOPAD_ENC.encode(b"\x00".as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(auth_data.as_slice());
+ let b64_sig = BASE64URL_NOPAD.encode([].as_slice());
+ let b64_user = BASE64URL_NOPAD.encode(b"\x00".as_slice());
// Base case is valid.
assert!(serde_json::from_str::<AuthenticationRelaxed>(
serde_json::json!({
@@ -205,7 +203,7 @@ mod tests {
// `id` and `rawId` mismatch.
let mut err = Error::invalid_value(
Unexpected::Bytes(
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode("ABABABABABABABABABABAA".as_bytes())
.unwrap()
.as_slice(),
@@ -880,10 +878,10 @@ mod tests {
0,
0,
];
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(auth_data.as_slice());
- let b64_sig = BASE64URL_NOPAD_ENC.encode([].as_slice());
- let b64_user = BASE64URL_NOPAD_ENC.encode(b"\x00".as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(auth_data.as_slice());
+ let b64_sig = BASE64URL_NOPAD.encode([].as_slice());
+ let b64_user = BASE64URL_NOPAD.encode(b"\x00".as_slice());
// Base case is valid.
assert!(serde_json::from_str::<AuthenticationRelaxed>(
serde_json::json!({
diff --git a/src/response/register/ser.rs b/src/response/register/ser.rs
@@ -9,7 +9,6 @@ use super::{
AuthenticationExtensionsPrfOutputsHelper, AuthenticationExtensionsPrfValues,
Base64DecodedVal, ClientExtensions, PublicKeyCredential,
},
- BASE64URL_NOPAD_ENC,
},
AttestationObject, AttestedCredentialData, AuthTransports, AuthenticationExtensionsPrfOutputs,
AuthenticatorAttestation, ClientExtensionsOutputs, CredentialPropertiesOutput, FromCbor as _,
@@ -22,6 +21,7 @@ use core::{
marker::PhantomData,
str,
};
+use data_encoding::BASE64URL_NOPAD;
use rsa::sha2::{digest::OutputSizeUser as _, Sha256};
use serde::de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, Unexpected, Visitor};
/// Functionality for deserializing DER-encoded `SubjectPublicKeyInfo` _without_ making copies of data or
@@ -760,13 +760,8 @@ impl<'e> Deserialize<'e> for AttObj {
where
E: Error,
{
- // If the encoded length is greater than `usize::MAX / 3`, then it's quite close
- // or more than `isize::MAX` which is the max capacity of a `str` and `Vec`. At which
- // point, we just error instead of attempting to decode such a large payload.
crate::base64url_nopad_decode_len(v.len())
- .ok_or_else(|| {
- E::invalid_value(Unexpected::Str(v), &"a shorter base64url-encoded value")
- })
+ .ok_or_else(|| E::invalid_value(Unexpected::Str(v), &"base64url-encoded value"))
.and_then(|len| {
// The decoded length is 3/4 of the encoded length, so overflow could only occur
// if usize::MAX / 4 < 32 => usize::MAX < 128 < u8::MAX; thus overflow is not
@@ -774,7 +769,7 @@ impl<'e> Deserialize<'e> for AttObj {
// the raw attestation object by `AuthenticatorAttestation::new`.
let mut att_obj = vec![0; len + Sha256::output_size()];
att_obj.truncate(len);
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode_mut(v.as_bytes(), &mut att_obj)
.map_err(|e| E::custom(e.error))
.map(|dec_len| {
@@ -1387,13 +1382,14 @@ impl<'de> Deserialize<'de> for Registration {
mod tests {
use super::{
super::{
- super::BASE64URL_NOPAD_ENC, cbor, AuthenticatorAttachment, Ed25519PubKey, Registration,
- RsaPubKey, UncompressedP256PubKey, UncompressedP384PubKey, ALG, EC2, EDDSA, ES256,
- ES384, KTY, OKP, RSA,
+ cbor, AuthenticatorAttachment, Ed25519PubKey, Registration, RsaPubKey,
+ UncompressedP256PubKey, UncompressedP384PubKey, ALG, EC2, EDDSA, ES256, ES384, KTY,
+ OKP, RSA,
},
spki::SubjectPublicKeyInfo,
CoseAlgorithmIdentifier,
};
+ use data_encoding::BASE64URL_NOPAD;
use ed25519_dalek::{pkcs8::EncodePublicKey, VerifyingKey};
use p256::{
elliptic_curve::sec1::{FromEncodedPoint as _, ToEncodedPoint as _},
@@ -1691,10 +1687,10 @@ mod tests {
.unwrap()
.to_public_key_der()
.unwrap();
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 113..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 113..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<Registration>(
serde_json::json!({
@@ -1731,7 +1727,7 @@ mod tests {
// `id` and `rawId` mismatch.
let mut err = Error::invalid_value(
Unexpected::Bytes(
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode("ABABABABABABABABABABAA".as_bytes())
.unwrap()
.as_slice(),
@@ -1873,7 +1869,7 @@ mod tests {
// `id` and the credential id in authenticator data mismatch.
err = Error::invalid_value(
Unexpected::Bytes(
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode("ABABABABABABABABABABAA".as_bytes())
.unwrap()
.as_slice(),
@@ -1921,7 +1917,7 @@ mod tests {
"rawId": "AAAAAAAAAAAAAAAAAAAAAA",
"response": {
"clientDataJSON": b64_cdata,
- "authenticatorData": BASE64URL_NOPAD_ENC.encode(bad_auth.as_slice()),
+ "authenticatorData": BASE64URL_NOPAD.encode(bad_auth.as_slice()),
"transports": [],
"publicKey": b64_key,
"publicKeyAlgorithm": -8,
@@ -2096,7 +2092,7 @@ mod tests {
"clientDataJSON": b64_cdata,
"authenticatorData": b64_adata,
"transports": [],
- "publicKey": BASE64URL_NOPAD_ENC.encode(VerifyingKey::from_bytes(&[0; 32]).unwrap().to_public_key_der().unwrap().as_bytes()),
+ "publicKey": BASE64URL_NOPAD.encode(VerifyingKey::from_bytes(&[0; 32]).unwrap().to_public_key_der().unwrap().as_bytes()),
"publicKeyAlgorithm": -8,
"attestationObject": b64_aobj,
},
@@ -2958,10 +2954,10 @@ mod tests {
.unwrap()
.to_public_key_der()
.unwrap();
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 113..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 113..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<Registration>(
serde_json::json!({
@@ -4051,10 +4047,10 @@ mod tests {
att_obj[att_obj_len - 67..att_obj_len - 35]
.copy_from_slice(enc_key.x().unwrap().as_slice());
att_obj[att_obj_len - 32..].copy_from_slice(enc_key.y().unwrap().as_slice());
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 148..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 148..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<Registration>(
serde_json::json!({
@@ -4202,7 +4198,7 @@ mod tests {
"clientDataJSON": b64_cdata,
"authenticatorData": b64_adata,
"transports": [],
- "publicKey": BASE64URL_NOPAD_ENC.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
+ "publicKey": BASE64URL_NOPAD.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
"publicKeyAlgorithm": -7,
"attestationObject": b64_aobj,
},
@@ -4514,10 +4510,10 @@ mod tests {
att_obj[att_obj_len - 99..att_obj_len - 51]
.copy_from_slice(enc_key.x().unwrap().as_slice());
att_obj[att_obj_len - 48..].copy_from_slice(enc_key.y().unwrap().as_slice());
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 181..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 181..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<Registration>(
serde_json::json!({
@@ -4667,7 +4663,7 @@ mod tests {
"clientDataJSON": b64_cdata,
"authenticatorData": b64_adata,
"transports": [],
- "publicKey": BASE64URL_NOPAD_ENC.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
+ "publicKey": BASE64URL_NOPAD.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
"publicKeyAlgorithm": -35,
"attestationObject": b64_aobj,
},
@@ -5235,10 +5231,10 @@ mod tests {
let att_obj_len = att_obj.len();
att_obj[att_obj_len - 261..att_obj_len - 5]
.copy_from_slice(key.n().to_bytes_be().as_slice());
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 343..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 343..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<Registration>(
serde_json::json!({
@@ -5432,7 +5428,7 @@ mod tests {
"clientDataJSON": b64_cdata,
"authenticatorData": b64_adata,
"transports": [],
- "publicKey": BASE64URL_NOPAD_ENC.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
+ "publicKey": BASE64URL_NOPAD.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
"publicKeyAlgorithm": -257,
"attestationObject": b64_aobj,
},
diff --git a/src/response/register/ser_relaxed.rs b/src/response/register/ser_relaxed.rs
@@ -205,11 +205,12 @@ impl<'de> Deserialize<'de> for RegistrationRelaxed {
mod tests {
use super::{
super::{
- super::{super::request::register::CoseAlgorithmIdentifier, BASE64URL_NOPAD_ENC},
- cbor, AuthenticatorAttachment, ALG, EC2, EDDSA, ES256, ES384, KTY, OKP, RSA,
+ super::super::request::register::CoseAlgorithmIdentifier, cbor,
+ AuthenticatorAttachment, ALG, EC2, EDDSA, ES256, ES384, KTY, OKP, RSA,
},
RegistrationRelaxed,
};
+ use data_encoding::BASE64URL_NOPAD;
use ed25519_dalek::{pkcs8::EncodePublicKey, VerifyingKey};
use p256::{
elliptic_curve::sec1::{FromEncodedPoint as _, ToEncodedPoint as _},
@@ -386,10 +387,10 @@ mod tests {
.unwrap()
.to_public_key_der()
.unwrap();
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 113..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 113..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<RegistrationRelaxed>(
serde_json::json!({
@@ -426,7 +427,7 @@ mod tests {
// `id` and `rawId` mismatch.
let mut err = Error::invalid_value(
Unexpected::Bytes(
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode("ABABABABABABABABABABAA".as_bytes())
.unwrap()
.as_slice(),
@@ -540,7 +541,7 @@ mod tests {
// `id` and the credential id in authenticator data mismatch.
err = Error::invalid_value(
Unexpected::Bytes(
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode("ABABABABABABABABABABAA".as_bytes())
.unwrap()
.as_slice(),
@@ -588,7 +589,7 @@ mod tests {
"rawId": "AAAAAAAAAAAAAAAAAAAAAA",
"response": {
"clientDataJSON": b64_cdata,
- "authenticatorData": BASE64URL_NOPAD_ENC.encode(bad_auth.as_slice()),
+ "authenticatorData": BASE64URL_NOPAD.encode(bad_auth.as_slice()),
"transports": [],
"publicKey": b64_key,
"publicKeyAlgorithm": -8,
@@ -731,7 +732,7 @@ mod tests {
"clientDataJSON": b64_cdata,
"authenticatorData": b64_adata,
"transports": [],
- "publicKey": BASE64URL_NOPAD_ENC.encode(VerifyingKey::from_bytes(&[0; 32]).unwrap().to_public_key_der().unwrap().as_bytes()),
+ "publicKey": BASE64URL_NOPAD.encode(VerifyingKey::from_bytes(&[0; 32]).unwrap().to_public_key_der().unwrap().as_bytes()),
"publicKeyAlgorithm": -8,
"attestationObject": b64_aobj,
},
@@ -1496,10 +1497,10 @@ mod tests {
.unwrap()
.to_public_key_der()
.unwrap();
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 113..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 113..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<RegistrationRelaxed>(
serde_json::json!({
@@ -2570,10 +2571,10 @@ mod tests {
att_obj[att_obj_len - 67..att_obj_len - 35]
.copy_from_slice(enc_key.x().unwrap().as_slice());
att_obj[att_obj_len - 32..].copy_from_slice(enc_key.y().unwrap().as_slice());
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 148..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 148..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<RegistrationRelaxed>(
serde_json::json!({
@@ -2708,7 +2709,7 @@ mod tests {
"clientDataJSON": b64_cdata,
"authenticatorData": b64_adata,
"transports": [],
- "publicKey": BASE64URL_NOPAD_ENC.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
+ "publicKey": BASE64URL_NOPAD.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
"publicKeyAlgorithm": -7,
"attestationObject": b64_aobj,
},
@@ -3006,10 +3007,10 @@ mod tests {
att_obj[att_obj_len - 99..att_obj_len - 51]
.copy_from_slice(enc_key.x().unwrap().as_slice());
att_obj[att_obj_len - 48..].copy_from_slice(enc_key.y().unwrap().as_slice());
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 181..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 181..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<RegistrationRelaxed>(
serde_json::json!({
@@ -3146,7 +3147,7 @@ mod tests {
"clientDataJSON": b64_cdata,
"authenticatorData": b64_adata,
"transports": [],
- "publicKey": BASE64URL_NOPAD_ENC.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
+ "publicKey": BASE64URL_NOPAD.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
"publicKeyAlgorithm": -35,
"attestationObject": b64_aobj,
},
@@ -3714,10 +3715,10 @@ mod tests {
let att_obj_len = att_obj.len();
att_obj[att_obj_len - 261..att_obj_len - 5]
.copy_from_slice(key.n().to_bytes_be().as_slice());
- let b64_cdata = BASE64URL_NOPAD_ENC.encode(c_data_json.as_bytes());
- let b64_adata = BASE64URL_NOPAD_ENC.encode(&att_obj[att_obj.len() - 343..]);
- let b64_key = BASE64URL_NOPAD_ENC.encode(pub_key.as_bytes());
- let b64_aobj = BASE64URL_NOPAD_ENC.encode(att_obj.as_slice());
+ let b64_cdata = BASE64URL_NOPAD.encode(c_data_json.as_bytes());
+ let b64_adata = BASE64URL_NOPAD.encode(&att_obj[att_obj.len() - 343..]);
+ let b64_key = BASE64URL_NOPAD.encode(pub_key.as_bytes());
+ let b64_aobj = BASE64URL_NOPAD.encode(att_obj.as_slice());
// Base case is valid.
assert!(serde_json::from_str::<RegistrationRelaxed>(
serde_json::json!({
@@ -3898,7 +3899,7 @@ mod tests {
"clientDataJSON": b64_cdata,
"authenticatorData": b64_adata,
"transports": [],
- "publicKey": BASE64URL_NOPAD_ENC.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
+ "publicKey": BASE64URL_NOPAD.encode(bad_pub_key.to_public_key_der().unwrap().as_bytes()),
"publicKeyAlgorithm": -257,
"attestationObject": b64_aobj,
},
diff --git a/src/response/ser.rs b/src/response/ser.rs
@@ -5,14 +5,13 @@
extern crate alloc;
use super::{
AllAcceptedCredentialsOptions, AuthTransports, AuthenticatorAttachment, AuthenticatorTransport,
- Challenge, CredentialId, CurrentUserDetailsOptions, Origin, SentChallenge, BASE64URL_NOPAD_ENC,
+ Challenge, CredentialId, CurrentUserDetailsOptions, Origin, SentChallenge,
};
use alloc::borrow::Cow;
use core::{
fmt::{self, Formatter},
marker::PhantomData,
};
-#[cfg(doc)]
use data_encoding::BASE64URL_NOPAD;
use serde::{
de::{Deserialize, Deserializer, Error, IgnoredAny, MapAccess, SeqAccess, Unexpected, Visitor},
@@ -245,7 +244,7 @@ impl<T: AsRef<[u8]>> Serialize for CredentialId<T> {
where
S: Serializer,
{
- serializer.serialize_str(BASE64URL_NOPAD_ENC.encode(self.0.as_ref()).as_str())
+ serializer.serialize_str(BASE64URL_NOPAD.encode(self.0.as_ref()).as_str())
}
}
impl<'de> Deserialize<'de> for CredentialId<Vec<u8>> {
@@ -285,7 +284,7 @@ impl<'de> Deserialize<'de> for CredentialId<Vec<u8>> {
..=crate::base64url_nopad_len(super::CRED_ID_MAX_LEN))
.contains(&v.len())
{
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode(v.as_bytes())
.map_err(E::custom)
.map(CredentialId)
@@ -376,7 +375,7 @@ impl<'de> Deserialize<'de> for Base64DecodedVal {
where
E: Error,
{
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode(v.as_bytes())
.map_err(E::custom)
.map(Base64DecodedVal)
@@ -428,10 +427,9 @@ impl<'de> Deserialize<'de> for SentChallenge {
where
E: Error,
{
- // We try to avoid decoding when possible by at least ensuring the input length is correct.
if v.len() == Challenge::BASE64_LEN {
let mut data = [0; 16];
- BASE64URL_NOPAD_ENC
+ BASE64URL_NOPAD
.decode_mut(v, data.as_mut_slice())
.map_err(|err| E::custom(err.error))
.map(|len| {