Skip to content
Merged
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
5 changes: 4 additions & 1 deletion crates/claims/crates/jwt/src/claims/registered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,7 @@ registered_claims! {
/// processing. The processing of the `exp` claim requires that the current
/// date/time MUST be before the expiration date/time listed in the `exp`
/// claim.
#[derive(Copy)]
"exp": ExpirationTime(NumericDate),

/// Not Before (`nbf`) claim.
Expand All @@ -385,12 +386,14 @@ registered_claims! {
/// be after or equal to the not-before date/time listed in the "nbf" claim.
/// Implementers MAY provide for some small leeway, usually no more than a
/// few minutes, to account for clock skew.
#[derive(Copy)]
"nbf": NotBefore(NumericDate),

/// Issued At (`iat`) claim.
///
/// Time at which the JWT was issued. This claim can be used to determine
/// the age of the JWT.
#[derive(Copy)]
"iat": IssuedAt(NumericDate),

/// JWT ID (`jti`) claim.
Expand Down Expand Up @@ -465,7 +468,7 @@ impl NotBefore {

impl IssuedAt {
pub fn now() -> Self {
Self(Utc::now().try_into().unwrap())
Self(Utc::now().into())
}

pub fn verify(&self, now: DateTime<Utc>) -> Result<(), JwtClaimValidationFailed> {
Expand Down
55 changes: 16 additions & 39 deletions crates/claims/crates/jwt/src/datatype/numeric_date.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,11 @@ use chrono::{prelude::*, Duration, LocalResult};
use ordered_float::NotNan;
use serde::{Deserialize, Serialize, Serializer};

/// Represents NumericDate (see <https://datatracker.ietf.org/doc/html/rfc7519#section-2>)
/// where the range is restricted to those in which microseconds can be exactly represented,
/// which is approximately between the years 1685 and 2255, which was considered to be sufficient
/// for the purposes of this crate. Note that leap seconds are ignored by this type, just as
/// they're ignored by NumericDate in the JWT standard.
/// JSON numeric value representing the number of seconds from
/// 1970-01-01T00:00:00Z UTC until the specified UTC date/time, ignoring leap
/// seconds.
///
/// An f64 value has 52 explicit mantissa bits, meaning that the biggest contiguous range
/// of integer values is from -2^53 to 2^53 (52 zeros after the mantissa's implicit 1).
/// Using this value to represent exact microseconds gives a maximum range of
/// +-2^53 / (1000000 * 60 * 60 * 24 * 365.25) ~= +-285,
/// which is centered around the Unix epoch start date Jan 1, 1970, 00:00:00 UTC, giving
/// the years 1685 to 2255.
/// See: <https://datatracker.ietf.org/doc/html/rfc7519#section-2>
#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NumericDate(#[serde(serialize_with = "interop_serialize")] NotNan<f64>);

Expand All @@ -40,9 +33,6 @@ pub enum NumericDateConversionError {

#[error("Invalid float literal")]
InvalidFloatLiteral,

#[error("Out of valid microsecond-precision range of NumericDate")]
OutOfMicrosecondPrecisionRange,
}

impl From<ordered_float::FloatIsNan> for NumericDateConversionError {
Expand All @@ -52,28 +42,17 @@ impl From<ordered_float::FloatIsNan> for NumericDateConversionError {
}

impl NumericDate {
/// This is -2^53 / 1_000_000, which is the smallest NumericDate that faithfully
/// represents full microsecond precision.
pub const MIN: NumericDate =
NumericDate(unsafe { NotNan::new_unchecked(-9_007_199_254.740_992) });
/// This is 2^53 / 1_000_000, which is the largest NumericDate that faithfully
/// represents full microsecond precision.
pub const MAX: NumericDate =
NumericDate(unsafe { NotNan::new_unchecked(9_007_199_254.740_992) });

/// Return the f64-valued number of seconds represented by this NumericDate.
pub fn as_seconds(self) -> f64 {
*self.0
}

/// Try to create NumericDate from a f64 value, returning error upon out-of-range.
pub fn try_from_seconds(seconds: f64) -> Result<Self, NumericDateConversionError> {
let seconds = NotNan::new(seconds)?;
if seconds.abs() > *Self::MAX.0 {
Err(NumericDateConversionError::OutOfMicrosecondPrecisionRange)
} else {
Ok(NumericDate(seconds))
}
Ok(NumericDate(seconds))
}

/// Decompose NumericDate for use in Utc.timestamp and Utc.timestamp_opt
fn into_whole_seconds_and_fractional_nanoseconds(self) -> (i64, u32) {
let whole_seconds = self.0.floor() as i64;
Expand All @@ -88,7 +67,7 @@ impl std::ops::Add<Duration> for NumericDate {
type Output = NumericDate;
fn add(self, rhs: Duration) -> Self::Output {
let self_dtu: DateTime<Utc> = self.into();
Self::Output::try_from(self_dtu + rhs).unwrap()
Self::Output::from(self_dtu + rhs)
}
}

Expand All @@ -107,7 +86,7 @@ impl std::ops::Sub<Duration> for NumericDate {
type Output = NumericDate;
fn sub(self, rhs: Duration) -> Self::Output {
let self_dtu: DateTime<Utc> = self.into();
Self::Output::try_from(self_dtu - rhs).unwrap()
Self::Output::from(self_dtu - rhs)
}
}

Expand All @@ -133,10 +112,8 @@ impl TryFrom<f64> for NumericDate {
}
}

impl TryFrom<DateTime<Utc>> for NumericDate {
type Error = NumericDateConversionError;

fn try_from(dtu: DateTime<Utc>) -> Result<Self, Self::Error> {
impl From<DateTime<Utc>> for NumericDate {
fn from(dtu: DateTime<Utc>) -> Self {
// Have to take seconds and nanoseconds separately in order to get the full allowable
// range of microsecond-precision values as described above.
let whole_seconds = dtu.timestamp() as f64;
Expand All @@ -146,14 +123,14 @@ impl TryFrom<DateTime<Utc>> for NumericDate {
};

Self::try_from_seconds(whole_seconds + fractional_seconds)
// UNWRAP SAFETY: input value can't be NaN nor infinite.
.unwrap()
}
}

impl TryFrom<DateTime<FixedOffset>> for NumericDate {
type Error = NumericDateConversionError;
fn try_from(dtfo: DateTime<FixedOffset>) -> Result<Self, Self::Error> {
let dtu = DateTime::<Utc>::from(dtfo);
NumericDate::try_from(dtu)
impl From<DateTime<FixedOffset>> for NumericDate {
fn from(dtfo: DateTime<FixedOffset>) -> Self {
DateTime::<Utc>::from(dtfo).into()
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/claims/crates/vc/src/v1/jwt/encode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub fn encode_jwt_vc_claims<T: Serialize>(
let date_value: xsd_types::DateTime = date_value
.parse()
.map_err(|_| JwtVcEncodeError::InvalidDateValue)?;
claims.set(ssi_jwt::ExpirationTime(date_value.earliest().try_into()?));
claims.set(ssi_jwt::ExpirationTime(date_value.earliest().into()));
}
None => return Err(JwtVcEncodeError::InvalidDateValue),
}
Expand All @@ -65,7 +65,7 @@ pub fn encode_jwt_vc_claims<T: Serialize>(
let issuance_date_value: xsd_types::DateTime = issuance_date_value
.parse()
.map_err(|_| JwtVcEncodeError::InvalidDateValue)?;
claims.set(ssi_jwt::NotBefore(issuance_date_value.latest().try_into()?));
claims.set(ssi_jwt::NotBefore(issuance_date_value.latest().into()));
}
None => return Err(JwtVcEncodeError::InvalidDateValue),
}
Expand Down Expand Up @@ -145,7 +145,7 @@ pub fn encode_jwt_vp_claims<T: Serialize>(
let issuance_date_value: xsd_types::DateTime = issuance_date_value
.parse()
.map_err(|_| JwtVpEncodeError::InvalidDateValue)?;
claims.set(ssi_jwt::NotBefore(issuance_date_value.latest().try_into()?));
claims.set(ssi_jwt::NotBefore(issuance_date_value.latest().into()));
}
None => return Err(JwtVpEncodeError::InvalidDateValue),
}
Expand Down