Skip to main content

identity_kit/sd_jwt_vc/
status.rs

1//! The Token Status List: the `status` claim of a referenced token, the status list and the
2//! status list token.
3
4use core::fmt;
5use core::str::FromStr;
6
7use chrono::{DateTime, Utc};
8use indexmap::IndexMap;
9use serde::{Deserialize, Deserializer, Serialize, Serializer};
10use serde_json::Value;
11
12use crate::jose::{JwsError, JwsHeader, Signer, base64url};
13use crate::vc::{Jwt, JwtClaims};
14
15/// The `status` claim of a referenced token: at least one status mechanism, `status_list`
16/// being the one this crate implements.
17///
18/// # Examples
19///
20/// ```
21/// use identity_kit::sd_jwt_vc::{Status, StatusListRef};
22/// use serde_json::json;
23///
24/// // Token Status List §6.2.
25/// let status: Status = serde_json::from_value(json!({
26///   "status_list": { "idx": 0, "uri": "https://example.com/statuslists/1" }
27/// }))?;
28/// let reference = status.status_list.as_ref().unwrap();
29/// assert_eq!(reference.idx, 0);
30/// assert_eq!(reference.uri, "https://example.com/statuslists/1");
31/// assert_eq!(status, Status::status_list(StatusListRef::new("https://example.com/statuslists/1", 0)));
32/// # Ok::<(), serde_json::Error>(())
33/// ```
34#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
35#[non_exhaustive]
36pub struct Status {
37    /// The `status_list` mechanism.
38    #[serde(default, skip_serializing_if = "Option::is_none")]
39    pub status_list: Option<StatusListRef>,
40    /// Every other mechanism, by name.
41    #[serde(flatten)]
42    pub additional: IndexMap<String, Value>,
43}
44
45impl Status {
46    /// A `status` claim with no mechanism yet.
47    pub fn new() -> Self {
48        Self::default()
49    }
50
51    /// A `status` claim referencing a status list.
52    pub fn status_list(reference: StatusListRef) -> Self {
53        Self {
54            status_list: Some(reference),
55            additional: IndexMap::new(),
56        }
57    }
58
59    /// This claim with the mechanism `name` set to `value`.
60    #[must_use]
61    pub fn with_mechanism(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
62        self.additional.insert(name.into(), value.into());
63        self
64    }
65
66    /// Whether no mechanism is given.
67    pub fn is_empty(&self) -> bool {
68        self.status_list.is_none() && self.additional.is_empty()
69    }
70
71    /// The mechanisms, the status list first.
72    pub fn mechanisms(&self) -> impl Iterator<Item = StatusMechanism<'_>> {
73        self.status_list
74            .iter()
75            .map(StatusMechanism::StatusList)
76            .chain(
77                self.additional
78                    .iter()
79                    .map(|(name, value)| StatusMechanism::Other { name, value }),
80            )
81    }
82}
83
84/// One mechanism of a [`Status`] claim.
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
86#[non_exhaustive]
87pub enum StatusMechanism<'a> {
88    /// A Token Status List reference.
89    StatusList(&'a StatusListRef),
90    /// A mechanism this crate does not implement.
91    Other {
92        /// Its name, the member of `status` it is given under.
93        name: &'a str,
94        /// Its value.
95        value: &'a Value,
96    },
97}
98
99/// A reference to a status list token (`status.status_list`): the token's URI and the index
100/// of the referenced token in its list.
101#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
102#[non_exhaustive]
103pub struct StatusListRef {
104    /// `idx`: the index in the status list.
105    pub idx: u64,
106    /// `uri`: where the status list token is.
107    pub uri: String,
108}
109
110impl StatusListRef {
111    /// A reference to index `idx` of the status list token at `uri`.
112    pub fn new(uri: impl Into<String>, idx: u64) -> Self {
113        Self {
114            idx,
115            uri: uri.into(),
116        }
117    }
118}
119
120/// A status type value (Token Status List §7): a number in `0..=255`, with the registered
121/// meanings as constants.
122///
123/// It serializes as its registered name when it has one of its own (`"VALID"`, `"INVALID"`,
124/// `"SUSPENDED"`) and as a number otherwise. `APPLICATION_SPECIFIC` names `0x03` and
125/// `0x0C`-`0x0F` alike, so those serialize as numbers too. It deserializes from a registered
126/// name, a number in `0..=255`, or that number as a decimal string; [`parse`](Self::parse) and
127/// [`FromStr`] accept the same strings. Names are case-sensitive. Since the form depends on the
128/// value, deserializing needs a self-describing format such as JSON or CBOR.
129///
130/// # Examples
131///
132/// ```
133/// use identity_kit::sd_jwt_vc::StatusType;
134/// use serde_json::json;
135///
136/// assert_eq!(serde_json::to_value(StatusType::SUSPENDED)?, json!("SUSPENDED"));
137/// assert_eq!(serde_json::to_value(StatusType(3))?, json!(3));
138/// assert_eq!(serde_json::from_value::<StatusType>(json!("INVALID"))?, StatusType::INVALID);
139/// assert_eq!(serde_json::from_value::<StatusType>(json!(12))?, StatusType(0x0C));
140/// assert_eq!("VALID".parse::<StatusType>()?, StatusType::VALID);
141/// assert!("APPLICATION_SPECIFIC".parse::<StatusType>().is_err());
142/// # Ok::<(), Box<dyn std::error::Error>>(())
143/// ```
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
145pub struct StatusType(pub u8);
146
147impl StatusType {
148    /// `0x00`: the referenced token is valid.
149    pub const VALID: Self = Self(0);
150    /// `0x01`: the referenced token is revoked.
151    pub const INVALID: Self = Self(1);
152    /// `0x02`: the referenced token is suspended.
153    pub const SUSPENDED: Self = Self(2);
154
155    /// The value.
156    pub const fn value(self) -> u8 {
157        self.0
158    }
159
160    /// Whether this is [`VALID`](Self::VALID).
161    pub const fn is_valid(self) -> bool {
162        self.0 == 0
163    }
164
165    /// The registered name of the value, if it has one.
166    pub const fn name(self) -> Option<&'static str> {
167        match self.0 {
168            0 => Some("VALID"),
169            1 => Some("INVALID"),
170            2 => Some("SUSPENDED"),
171            _ => None,
172        }
173    }
174
175    /// The value named `s`: `VALID`, `INVALID`, `SUSPENDED`, or a decimal number in
176    /// `0..=255`.
177    ///
178    /// # Errors
179    ///
180    /// [`StatusListError::UnknownStatusType`] for anything else, `APPLICATION_SPECIFIC`
181    /// included: it names several values.
182    pub fn parse(s: &str) -> Result<Self, StatusListError> {
183        match s {
184            "VALID" => Ok(Self::VALID),
185            "INVALID" => Ok(Self::INVALID),
186            "SUSPENDED" => Ok(Self::SUSPENDED),
187            digits if !digits.is_empty() && digits.bytes().all(|b| b.is_ascii_digit()) => digits
188                .parse()
189                .map(Self)
190                .map_err(|_| StatusListError::UnknownStatusType(s.to_owned())),
191            _ => Err(StatusListError::UnknownStatusType(s.to_owned())),
192        }
193    }
194}
195
196impl fmt::Display for StatusType {
197    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
198        match self.name() {
199            Some(name) => write!(f, "{name} ({:#04x})", self.0),
200            None => write!(f, "{:#04x}", self.0),
201        }
202    }
203}
204
205impl From<u8> for StatusType {
206    fn from(value: u8) -> Self {
207        Self(value)
208    }
209}
210
211impl FromStr for StatusType {
212    type Err = StatusListError;
213
214    fn from_str(s: &str) -> Result<Self, Self::Err> {
215        Self::parse(s)
216    }
217}
218
219impl Serialize for StatusType {
220    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
221        match self.name() {
222            Some(name) => serializer.serialize_str(name),
223            None => serializer.serialize_u8(self.0),
224        }
225    }
226}
227
228impl<'de> Deserialize<'de> for StatusType {
229    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
230        struct StatusTypeVisitor;
231
232        impl serde::de::Visitor<'_> for StatusTypeVisitor {
233            type Value = StatusType;
234
235            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
236                f.write_str("VALID, INVALID, SUSPENDED or a number in 0..=255")
237            }
238
239            fn visit_str<E: serde::de::Error>(self, s: &str) -> Result<StatusType, E> {
240                StatusType::parse(s).map_err(E::custom)
241            }
242
243            fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<StatusType, E> {
244                u8::try_from(value)
245                    .map(StatusType)
246                    .map_err(|_| E::invalid_value(serde::de::Unexpected::Unsigned(value), &self))
247            }
248
249            fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<StatusType, E> {
250                u8::try_from(value)
251                    .map(StatusType)
252                    .map_err(|_| E::invalid_value(serde::de::Unexpected::Signed(value), &self))
253            }
254        }
255
256        deserializer.deserialize_any(StatusTypeVisitor)
257    }
258}
259
260/// Why a status list or status list token could not be used.
261#[derive(Debug, thiserror::Error)]
262#[non_exhaustive]
263pub enum StatusListError {
264    /// `bits` is not 1, 2, 4 or 8.
265    #[error("`bits` is {0}, not 1, 2, 4 or 8")]
266    InvalidBits(u8),
267    /// A status value does not fit in `bits`.
268    #[error("status value {value} does not fit in {bits} bits")]
269    InvalidStatusValue {
270        /// The value.
271        value: u8,
272        /// The bits per status.
273        bits: u8,
274    },
275    /// The index is beyond the end of the list.
276    #[error("index {idx} is out of range of a status list of {len} entries")]
277    IndexOutOfRange {
278        /// The index.
279        idx: u64,
280        /// The number of entries in the list.
281        len: usize,
282    },
283    /// `lst` is not base64url, or not a ZLIB-compressed byte array.
284    #[error("invalid status list: {0}")]
285    Compression(String),
286    /// The decompressed list is larger than the limit.
287    #[error("the status list decompresses to more than {limit} bytes")]
288    TooLarge {
289        /// The limit.
290        limit: usize,
291    },
292    /// The status list token is not a compact JWS this crate decodes.
293    #[error("invalid JWS: {0}")]
294    Jws(#[from] JwsError),
295    /// A string that is neither a registered status type name of its own nor a number in
296    /// `0..=255`.
297    #[error("{0:?} is not VALID, INVALID, SUSPENDED or a number in 0..=255")]
298    UnknownStatusType(String),
299    /// The status list token's `typ` is not `statuslist+jwt`.
300    #[error("header `typ` is {0:?}, not `statuslist+jwt`")]
301    UnexpectedTyp(Option<String>),
302    /// The status list token's payload is not a status list token claims set.
303    #[error("the payload is not a status list token: {0}")]
304    Payload(String),
305    /// The token's `sub` is not the `uri` of the reference.
306    #[error("status list token `sub` {sub:?} is not the referenced {uri:?}")]
307    SubjectMismatch {
308        /// The token's `sub`.
309        sub: String,
310        /// The reference's `uri`.
311        uri: String,
312    },
313    /// The status list token has expired.
314    #[error("status list token expired at {} (now is {})", expired_at.to_rfc3339(), now.to_rfc3339())]
315    Expired {
316        /// Its `exp`.
317        expired_at: DateTime<Utc>,
318        /// The validation time.
319        now: DateTime<Utc>,
320    },
321}
322
323/// A status list (Token Status List §4): one status of `bits` bits per referenced token,
324/// packed into a byte array from the least significant bit up, ZLIB-compressed and
325/// base64url-encoded as `lst` in JSON.
326///
327/// # Examples
328///
329/// ```
330/// use identity_kit::sd_jwt_vc::{StatusList, StatusType};
331/// use serde_json::json;
332///
333/// // Token Status List §4.1: 16 statuses of 1 bit.
334/// let list: StatusList = serde_json::from_value(json!({ "bits": 1, "lst": "eNrbuRgAAhcBXQ" }))?;
335/// assert_eq!(list.as_bytes(), &[0xb9, 0xa3]);
336/// assert_eq!(list.get(0), Some(StatusType::INVALID));
337/// assert_eq!(list.get(1), Some(StatusType::VALID));
338/// assert_eq!(list.get(15), Some(StatusType::INVALID));
339/// assert_eq!(list.get(16), None);
340///
341/// let mut rebuilt = StatusList::new(1, 16)?;
342/// for index in [0, 3, 4, 5, 7, 8, 9, 13, 15] {
343///     rebuilt.set(index, StatusType::INVALID)?;
344/// }
345/// assert_eq!(rebuilt.as_bytes(), list.as_bytes());
346/// # Ok::<(), Box<dyn std::error::Error>>(())
347/// ```
348#[derive(Debug, Clone, PartialEq, Eq)]
349pub struct StatusList {
350    bits: u8,
351    bytes: Vec<u8>,
352    aggregation_uri: Option<String>,
353}
354
355impl StatusList {
356    /// The largest decompressed list [`Deserialize`] accepts: 16 MiB, room for 16 million
357    /// 8-bit statuses.
358    pub const MAX_BYTES: usize = 1 << 24;
359
360    /// An all-`VALID` list of `size` statuses of `bits` bits each.
361    ///
362    /// # Errors
363    ///
364    /// [`StatusListError::InvalidBits`] unless `bits` is 1, 2, 4 or 8.
365    pub fn new(bits: u8, size: usize) -> Result<Self, StatusListError> {
366        check_bits(bits)?;
367        let bytes = vec![0; (size * usize::from(bits)).div_ceil(8)];
368        Ok(Self {
369            bits,
370            bytes,
371            aggregation_uri: None,
372        })
373    }
374
375    /// A list over the uncompressed byte array `bytes`.
376    ///
377    /// # Errors
378    ///
379    /// [`StatusListError::InvalidBits`] unless `bits` is 1, 2, 4 or 8.
380    pub fn from_bytes(bits: u8, bytes: Vec<u8>) -> Result<Self, StatusListError> {
381        check_bits(bits)?;
382        Ok(Self {
383            bits,
384            bytes,
385            aggregation_uri: None,
386        })
387    }
388
389    /// This list with `aggregation_uri`.
390    #[must_use]
391    pub fn with_aggregation_uri(mut self, uri: impl Into<String>) -> Self {
392        self.aggregation_uri = Some(uri.into());
393        self
394    }
395
396    /// The bits per status: 1, 2, 4 or 8.
397    pub const fn bits(&self) -> u8 {
398        self.bits
399    }
400
401    /// The uncompressed byte array.
402    pub fn as_bytes(&self) -> &[u8] {
403        &self.bytes
404    }
405
406    /// The `aggregation_uri`, if any.
407    pub fn aggregation_uri(&self) -> Option<&str> {
408        self.aggregation_uri.as_deref()
409    }
410
411    /// The number of statuses the list holds.
412    pub fn len(&self) -> usize {
413        self.bytes.len() * 8 / usize::from(self.bits)
414    }
415
416    /// Whether the list holds no status.
417    pub fn is_empty(&self) -> bool {
418        self.bytes.is_empty()
419    }
420
421    /// The status at `index`, or `None` beyond the end of the list.
422    pub fn get(&self, index: u64) -> Option<StatusType> {
423        let (byte, shift) = self.position(index)?;
424        let mask = self.mask();
425        Some(StatusType((self.bytes.get(byte)? >> shift) & mask))
426    }
427
428    /// Sets the status at `index`.
429    ///
430    /// # Errors
431    ///
432    /// [`StatusListError::IndexOutOfRange`] or [`StatusListError::InvalidStatusValue`] when
433    /// the value does not fit in `bits`.
434    pub fn set(&mut self, index: u64, status: StatusType) -> Result<(), StatusListError> {
435        let mask = self.mask();
436        if status.0 & !mask != 0 {
437            return Err(StatusListError::InvalidStatusValue {
438                value: status.0,
439                bits: self.bits,
440            });
441        }
442        let len = self.len();
443        let out_of_range = || StatusListError::IndexOutOfRange { idx: index, len };
444        let (byte, shift) = self.position(index).ok_or_else(out_of_range)?;
445        let slot = self.bytes.get_mut(byte).ok_or_else(out_of_range)?;
446        *slot = (*slot & !(mask << shift)) | (status.0 << shift);
447        Ok(())
448    }
449
450    /// The byte and the bit offset of `index`.
451    fn position(&self, index: u64) -> Option<(usize, u8)> {
452        let bits = u64::from(self.bits);
453        let bit = index.checked_mul(bits)?;
454        let byte = usize::try_from(bit / 8).ok()?;
455        let shift = u8::try_from(bit % 8).ok()?;
456        (byte < self.bytes.len()).then_some((byte, shift))
457    }
458
459    /// The mask of one status.
460    const fn mask(&self) -> u8 {
461        if self.bits == 8 {
462            0xff
463        } else {
464            (1 << self.bits) - 1
465        }
466    }
467
468    /// The ZLIB-compressed byte array, base64url-encoded: the `lst` member.
469    pub fn to_lst(&self) -> String {
470        let compressed = miniz_oxide::deflate::compress_to_vec_zlib(&self.bytes, 10);
471        base64url::encode(&compressed)
472    }
473
474    /// Decodes `lst`, refusing a list that decompresses to more than `max_bytes`.
475    ///
476    /// # Errors
477    ///
478    /// [`StatusListError::InvalidBits`], [`StatusListError::Compression`] or
479    /// [`StatusListError::TooLarge`].
480    pub fn from_lst(bits: u8, lst: &str, max_bytes: usize) -> Result<Self, StatusListError> {
481        check_bits(bits)?;
482        let compressed = base64url::decode(lst)
483            .map_err(|e| StatusListError::Compression(format!("`lst` is not base64url: {e}")))?;
484        let bytes = miniz_oxide::inflate::decompress_to_vec_zlib_with_limit(&compressed, max_bytes)
485            .map_err(|e| match e.status {
486                miniz_oxide::inflate::TINFLStatus::HasMoreOutput => {
487                    StatusListError::TooLarge { limit: max_bytes }
488                }
489                status => {
490                    StatusListError::Compression(format!("`lst` is not ZLIB data: {status:?}"))
491                }
492            })?;
493        Ok(Self {
494            bits,
495            bytes,
496            aggregation_uri: None,
497        })
498    }
499}
500
501/// Checks `bits` is 1, 2, 4 or 8.
502fn check_bits(bits: u8) -> Result<(), StatusListError> {
503    match bits {
504        1 | 2 | 4 | 8 => Ok(()),
505        _ => Err(StatusListError::InvalidBits(bits)),
506    }
507}
508
509/// The JSON form of a [`StatusList`].
510#[derive(Serialize, Deserialize)]
511struct StatusListJson {
512    bits: u8,
513    lst: String,
514    #[serde(default, skip_serializing_if = "Option::is_none")]
515    aggregation_uri: Option<String>,
516}
517
518impl Serialize for StatusList {
519    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
520        StatusListJson {
521            bits: self.bits,
522            lst: self.to_lst(),
523            aggregation_uri: self.aggregation_uri.clone(),
524        }
525        .serialize(serializer)
526    }
527}
528
529impl<'de> Deserialize<'de> for StatusList {
530    /// Decodes `lst` with [`StatusList::MAX_BYTES`] as the limit.
531    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
532        let json = StatusListJson::deserialize(deserializer)?;
533        let mut list = Self::from_lst(json.bits, &json.lst, Self::MAX_BYTES)
534            .map_err(serde::de::Error::custom)?;
535        list.aggregation_uri = json.aggregation_uri;
536        Ok(list)
537    }
538}
539
540/// The claims of a status list token in JWT format (Token Status List §5.1).
541///
542/// # Examples
543///
544/// ```
545/// use chrono::{TimeZone as _, Utc};
546/// use identity_kit::sd_jwt_vc::{StatusListRef, StatusListToken, StatusType};
547/// use identity_kit::vc::Jwt;
548///
549/// // Token Status List §8.2.
550/// let jwt: Jwt = "eyJhbGciOiJFUzI1NiIsImtpZCI6IjEyIiwidHlwIjoic3RhdHVzbGlzdCtqd3QifQ.e\
551///     yJleHAiOjIyOTE3MjAxNzAsImlhdCI6MTY4NjkyMDE3MCwiaXNzIjoiaHR0cHM6Ly9leGFtcGxlLmNvbSIsInN0YXR1\
552///     c19saXN0Ijp7ImJpdHMiOjEsImxzdCI6ImVOcmJ1UmdBQWhjQlhRIn0sInN1YiI6Imh0dHBzOi8vZXhhbXBsZS5jb20\
553///     vc3RhdHVzbGlzdHMvMSIsInR0bCI6NDMyMDB9.2lKUUNG503R9htu4aHAYi7vjmr3sgApbfoDvPrl65N3URUO1EYqq\
554///     Ql45Jfzd-Av4QzlKa3oVALpLwOEUOq-U_g".parse()?;
555/// let (header, token) = StatusListToken::decode(&jwt)?;
556/// assert_eq!(header.kid.as_deref(), Some("12"));
557/// assert_eq!(token.sub, "https://example.com/statuslists/1");
558/// assert_eq!(token.ttl, Some(43200));
559///
560/// let now = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
561/// let reference = StatusListRef::new("https://example.com/statuslists/1", 0);
562/// assert_eq!(token.status_of(&reference, now)?, StatusType::INVALID);
563/// assert_eq!(token.status_of(&StatusListRef::new(&reference.uri, 1), now)?, StatusType::VALID);
564/// assert!(token.status_of(&StatusListRef::new(&reference.uri, 16), now).is_err());
565/// # Ok::<(), Box<dyn std::error::Error>>(())
566/// ```
567#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
568#[non_exhaustive]
569pub struct StatusListToken {
570    /// `sub`: the URI of the token, the `uri` referenced tokens use.
571    pub sub: String,
572    /// `iat`: when the token was issued. A `NumericDate` with a fraction is floored.
573    #[serde(deserialize_with = "crate::vc::required_numeric_date")]
574    pub iat: i64,
575    /// `exp`: when the token expires. A `NumericDate` with a fraction is floored.
576    #[serde(
577        default,
578        skip_serializing_if = "Option::is_none",
579        deserialize_with = "crate::vc::numeric_date"
580    )]
581    pub exp: Option<i64>,
582    /// `ttl`: how many seconds the token may be cached.
583    #[serde(default, skip_serializing_if = "Option::is_none")]
584    pub ttl: Option<u64>,
585    /// `status_list`: the list.
586    pub status_list: StatusList,
587    /// Every other claim, in insertion order.
588    #[serde(flatten)]
589    pub additional: IndexMap<String, Value>,
590}
591
592impl StatusListToken {
593    /// The `typ` of a status list token, `statuslist+jwt`.
594    pub const TYP: &'static str = "statuslist+jwt";
595
596    /// The media type of a status list token, `application/statuslist+jwt`.
597    pub const MEDIA_TYPE: &'static str = "application/statuslist+jwt";
598
599    /// A token for the list at `sub`, issued at `iat`.
600    pub fn new(sub: impl Into<String>, iat: DateTime<Utc>, status_list: StatusList) -> Self {
601        Self {
602            sub: sub.into(),
603            iat: JwtClaims::numeric_date(iat),
604            exp: None,
605            ttl: None,
606            status_list,
607            additional: IndexMap::new(),
608        }
609    }
610
611    /// This token with `exp`.
612    #[must_use]
613    pub fn with_expires_at(mut self, time: DateTime<Utc>) -> Self {
614        self.exp = Some(JwtClaims::numeric_date(time));
615        self
616    }
617
618    /// This token with `ttl`.
619    #[must_use]
620    pub fn with_ttl(mut self, seconds: u64) -> Self {
621        self.ttl = Some(seconds);
622        self
623    }
624
625    /// This token with the claim `name` set to `value`.
626    #[must_use]
627    pub fn with_claim(mut self, name: impl Into<String>, value: impl Into<Value>) -> Self {
628        self.additional.insert(name.into(), value.into());
629        self
630    }
631
632    /// Decodes `jwt` without verifying it: the header, whose `typ` must be
633    /// `statuslist+jwt`, and the claims.
634    ///
635    /// # Errors
636    ///
637    /// [`StatusListError::Jws`], [`StatusListError::UnexpectedTyp`] or
638    /// [`StatusListError::Payload`].
639    pub fn decode(jwt: &Jwt) -> Result<(JwsHeader, Self), StatusListError> {
640        let jws = jwt.decode()?;
641        if jws.header().typ.as_deref() != Some(Self::TYP) {
642            return Err(StatusListError::UnexpectedTyp(jws.header().typ.clone()));
643        }
644        let token = serde_json::from_slice(jws.payload())
645            .map_err(|e| StatusListError::Payload(e.to_string()))?;
646        Ok((jws.header().clone(), token))
647    }
648
649    /// Signs this token with `signer`, `typ: statuslist+jwt` and the signer's `kid`.
650    ///
651    /// # Errors
652    ///
653    /// [`StatusListError::Jws`] when signing fails.
654    pub async fn sign(&self, signer: &dyn Signer) -> Result<Jwt, StatusListError> {
655        let mut header = JwsHeader::new(signer.alg()).with_typ(Self::TYP);
656        header.kid = signer.kid().map(str::to_owned);
657        let payload = serde_json::to_vec(self).expect("a token serializes");
658        let jwt = crate::jose::encode_compact(&header, &payload, signer).await?;
659        Ok(Jwt::new(jwt).expect("a freshly encoded JWS decodes"))
660    }
661
662    /// The status of the token `reference` points at (Token Status List §8.3, steps 4 to 6):
663    /// `sub` must be the referenced URI, the token must not have expired at `now`, the index
664    /// must be in range.
665    ///
666    /// # Errors
667    ///
668    /// [`StatusListError::SubjectMismatch`], [`StatusListError::Expired`] or
669    /// [`StatusListError::IndexOutOfRange`].
670    pub fn status_of(
671        &self,
672        reference: &StatusListRef,
673        now: DateTime<Utc>,
674    ) -> Result<StatusType, StatusListError> {
675        if self.sub != reference.uri {
676            return Err(StatusListError::SubjectMismatch {
677                sub: self.sub.clone(),
678                uri: reference.uri.clone(),
679            });
680        }
681        if let Some(exp) = self.exp {
682            let expired_at = JwtClaims::timestamp(exp).unwrap_or(DateTime::<Utc>::MIN_UTC);
683            if expired_at <= now {
684                return Err(StatusListError::Expired { expired_at, now });
685            }
686        }
687        self.status_list
688            .get(reference.idx)
689            .ok_or(StatusListError::IndexOutOfRange {
690                idx: reference.idx,
691                len: self.status_list.len(),
692            })
693    }
694}