Skip to main content

o_sfu_rfc/
jwt.rs

1//! RFC references for this module:
2//! - JSON Web Token (JWT): <https://www.rfc-editor.org/rfc/rfc7519>
3//! - JSON Web Algorithms (JWA): <https://www.rfc-editor.org/rfc/rfc7518>
4
5use std::{fmt, time::Duration};
6
7pub use base64::engine::general_purpose::URL_SAFE_NO_PAD;
8use serde::{
9    Deserialize, Deserializer, Serialize, Serializer,
10    de::{self, Visitor},
11};
12
13/// JWT `typ` header value.
14///
15/// Reference: RFC 7519 section 5.1.
16pub const TYPE_JWT: &str = "JWT";
17
18/// JWS `alg` header value for HMAC using SHA-256.
19///
20/// Reference: RFC 7518 section 3.2.
21pub const ALGORITHM_HS256: &str = "HS256";
22
23/// minimum HS256 key length from RFC 7518 section 3.2
24pub const HS256_MIN_KEY_BYTES: usize = 32;
25
26/// RFC 7519 seconds since the Unix epoch with subsecond precision.
27///
28/// Both encodings are JSON numbers. Whole seconds serialize through `u64` and
29/// fractional values serialize through `f64`.
30#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
31pub struct NumericDate(Duration);
32
33impl From<u64> for NumericDate {
34    fn from(seconds: u64) -> Self {
35        Self(Duration::from_secs(seconds))
36    }
37}
38
39impl From<Duration> for NumericDate {
40    fn from(duration: Duration) -> Self {
41        Self(duration)
42    }
43}
44
45impl Serialize for NumericDate {
46    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
47    where
48        S: Serializer,
49    {
50        if self.0.subsec_nanos() == 0 {
51            serializer.serialize_u64(self.0.as_secs())
52        } else {
53            serializer.serialize_f64(self.0.as_secs_f64())
54        }
55    }
56}
57
58impl<'de> Deserialize<'de> for NumericDate {
59    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60    where
61        D: Deserializer<'de>,
62    {
63        deserializer.deserialize_any(NumericDateVisitor)
64    }
65}
66
67struct NumericDateVisitor;
68
69impl Visitor<'_> for NumericDateVisitor {
70    type Value = NumericDate;
71
72    fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73        formatter.write_str("a nonnegative RFC 7519 NumericDate")
74    }
75
76    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
77    where
78        E: de::Error,
79    {
80        Ok(NumericDate::from(value))
81    }
82
83    fn visit_f64<E>(self, value: f64) -> Result<Self::Value, E>
84    where
85        E: de::Error,
86    {
87        Duration::try_from_secs_f64(value)
88            .map(NumericDate)
89            .map_err(|_error| E::custom("NumericDate must be nonnegative, finite and in range"))
90    }
91}
92
93/// Registered JWT claims from RFC 7519 section 4.1.
94#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
95pub struct RegisteredJwtClaims {
96    /// expiration time
97    #[serde(skip_serializing_if = "Option::is_none")]
98    pub exp: Option<NumericDate>,
99    /// issued at time
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub iat: Option<NumericDate>,
102    /// not before time
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub nbf: Option<NumericDate>,
105    /// Issuer
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub iss: Option<String>,
108    /// Subject
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub sub: Option<String>,
111    /// Audience
112    #[serde(skip_serializing_if = "Option::is_none")]
113    pub aud: Option<JwtAudience>,
114    /// JWT ID
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub jti: Option<String>,
117}
118
119/// JWT `aud` claim.
120///
121/// RFC 7519 allows the audience claim to be either one case-sensitive string
122/// or an array of case-sensitive strings.
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124#[serde(untagged)]
125pub enum JwtAudience {
126    Single(String),
127    Multiple(Vec<String>),
128}
129
130/// JOSE header fields used by `o-sfu`'s JWT handling.
131///
132/// Reference: RFC 7519 section 5.
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134pub struct JwtHeader {
135    pub alg: String,
136    #[serde(default, skip_serializing_if = "Option::is_none")]
137    pub typ: Option<String>,
138}
139
140#[cfg(test)]
141#[path = "TESTS/jwt.rs"]
142mod tests;