bh_status_list/
lib.rs

1// Copyright (C) 2020-2025  The Blockhouse Technology Limited (TBTL).
2//
3// This program is free software: you can redistribute it and/or modify it
4// under the terms of the GNU Affero General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or (at your
6// option) any later version.
7//
8// This program is distributed in the hope that it will be useful, but
9// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10// or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
11// License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16#![deny(missing_docs)]
17#![deny(rustdoc::broken_intra_doc_links)]
18
19//! A `crate` dedicated to dealing with Status Lists for Verifiable Credentials.
20//!
21//! The implementation is based on [this specification][1]. Status Lists are
22//! used to keep track of statuses of specific Verifiable Credentials (in the
23//! specification called the *Referenced Tokens*), e.g. active, revoked, etc.
24//!
25//! The Status Lists are created, updated and **signed** by issuers of
26//! Verifiable Credentials, and are publicly available. They contain statuses of
27//! multiple credentials, where each credential contains an URI to fetch a
28//! Status List and an index of its status on that list.
29//!
30//! Note: only the JSON format is currently supported for the Status List (CBOR
31//! is not supported) and the JWT format for the Status List Token (CWT is not
32//! supported).
33//!
34//! # Details
35//!
36//! The main data structures available in the crate are the [`StatusList`],
37//! [`StatusListInternal`], [`StatusClaim`] and [`StatusListToken`]. The
38//! [`StatusList`] and [`StatusListInternal`] structs are used to create and
39//! manage the Status List, while the [`StatusListToken`] struct is used to
40//! create and manage the Status List Token while [`StatusClaim`] represents a
41//! single status claim within a Status List.
42//!
43//! The trait [`StatusListClient`] is provided to allow the user to implement
44//! their own client to fetch the Status List from a given URI.
45//!
46//! # Example
47//!
48//! Construct a [`StatusList`], [`StatusListToken`], implement a dummy
49//! [`StatusListClient`] and verify a [`StatusClaim`].
50//! ```
51//! use bh_jws_utils::{Es256Signer, Es256Verifier};
52//! use bh_status_list::{UriBuf, StatusBits, StatusList,
53//!  StatusListInternal, StatusClaim, StatusListResponse,
54//!  StatusListToken, StatusListTokenClaims, StatusListClient};
55//!
56//!
57//! // Struct representing our dummy client
58//! struct DummyClient(Es256Signer);
59//!
60//! // Dummy error type for the client.
61//! struct DummyErr;
62//!
63//! // `BhError` trait requires `std::fmt::Display` to be implemented.
64//! impl std::fmt::Display for DummyErr {
65//!     fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66//!         Ok(())
67//!     }
68//! }
69//!
70//! impl bherror::BhError for DummyErr {}
71//!
72//! fn iss_uri() -> UriBuf {
73//!     UriBuf::new(b"http://example.com/issuer".to_vec()).unwrap()
74//! }
75//!
76//! fn status_list_uri() -> UriBuf {
77//!    UriBuf::new(b"http://example.com/status_list".to_vec()).unwrap()
78//! }
79//!
80//! // Dummy client implementation of the `StatusListClient` trait.
81//! impl StatusListClient for DummyClient {
82//!     type Err = bherror::Error<DummyErr>;
83//!     async fn get_status(&self, _uri: &UriBuf) -> Result<StatusListResponse, Self::Err> {
84//!         let mut status_list = StatusListInternal::new(StatusBits::Two, None);
85//!         status_list.push(0b00).unwrap();
86//!         status_list.push(0b01).unwrap();
87//!         status_list.push(0b10).unwrap();
88//!         status_list.push(0b11).unwrap();
89//!         let status_list_claims = StatusListTokenClaims::new(
90//!             iss_uri(),
91//!             status_list_uri(),
92//!             1000,
93//!             None,
94//!             None,
95//!             status_list.status_list().clone(),
96//!         );
97//!         let status_list_token =
98//!             StatusListToken::new(status_list_claims, "example_kid".to_string(), &self.0)
99//!                 .unwrap();
100//!
101//!         Ok(StatusListResponse::Jwt(
102//!             status_list_token.as_str().to_string(),
103//!         ))
104//!     }
105//! }
106//!
107//! // Generate a new key pair for the signer using `bh-jws-utils`.
108//! let signer = Es256Signer::generate("example_kid".to_string()).unwrap();
109//! let public_jwk = signer.public_jwk().unwrap();
110//! let client = DummyClient(signer);
111//!
112//! // Create a new claim to verify.
113//! let status_claim = StatusClaim::new(
114//!     status_list_uri(),
115//!     1,
116//! );
117//!
118//! // Evaluate the status claim using the dummy client
119//! // and the public key of the signer.
120//! let (_, status) = tokio_test::block_on(status_claim
121//!     .evaluate(
122//!         &client,
123//!         &Es256Verifier,
124//!         &public_jwk,
125//!         1000,
126//!         &iss_uri(),
127//!     ))
128//!     .unwrap();
129//!
130//! // Check the status we retrieved from the client.
131//! assert_eq!(status, 0b01);
132//! ```
133//!
134//! [1]: https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-03
135
136pub mod client;
137mod error;
138mod status_list;
139mod status_list_token;
140mod utils;
141mod vc_claim;
142
143pub use bh_jws_utils::jwt::Error as JwtError;
144pub use client::{StatusListClient, StatusListResponse};
145pub use error::{Error, Result};
146pub use iref::{InvalidUri, UriBuf};
147pub use status_list::{StatusBits, StatusList, StatusListInternal};
148pub use status_list_token::{StatusListToken, StatusListTokenClaims, StatusListTokenHeader};
149pub use vc_claim::StatusClaim;
150
151/// Reexporting the [`bh_jws_utils`] crate's JWT types for convenience.
152pub mod token {
153    pub use bh_jws_utils::jwt::token::{Signed, Verified};
154}