ssi_vc/v1/revocation/
v2021.rs1use bitvec::prelude::Lsb0;
5use bitvec::vec::BitVec;
6use iref::UriBuf;
7use serde::{Deserialize, Serialize};
8use ssi_claims_core::VerificationParameters;
9use ssi_data_integrity::AnyDataIntegrity;
10use ssi_json_ld::STATUS_LIST_2021_V1_CONTEXT;
11use ssi_verification_methods::{AnyMethod, VerificationMethodResolver};
12use static_iref::iri;
13use std::collections::BTreeMap;
14
15use crate::{
16 syntax::RequiredType,
17 v1::{
18 revocation::{load_resource, Reason},
19 RequiredContext, SpecializedJsonCredential,
20 },
21};
22
23use super::{
24 CredentialStatus, EncodedList, List, NewEncodedListError, RevocationListIndex, SetStatusError,
25 StatusCheck, StatusCheckError, MIN_BITSTRING_LENGTH,
26};
27
28pub struct StatusList2021Context;
29
30impl RequiredContext for StatusList2021Context {
31 const CONTEXT_IRI: &'static iref::Iri = iri!("https://w3id.org/vc/status-list/2021/v1");
32}
33
34#[derive(Debug, Serialize, Deserialize, Clone)]
38#[serde(rename_all = "camelCase")]
39pub struct StatusList2021Entry {
40 pub id: UriBuf,
43
44 pub status_purpose: String,
52
53 pub status_list_index: RevocationListIndex,
55
56 pub status_list_credential: UriBuf,
58}
59
60#[derive(Debug, Serialize, Deserialize, Clone)]
62#[serde(tag = "type")]
63pub enum StatusList2021Subject {
64 StatusList2021(StatusList2021),
65}
66
67pub struct StatusList2021CredentialType;
68
69impl RequiredType for StatusList2021CredentialType {
70 const REQUIRED_TYPE: &'static str = "StatusList2021Credential";
71}
72
73pub type StatusList2021Credential = SpecializedJsonCredential<
77 StatusList2021Subject,
78 StatusList2021Context,
79 StatusList2021CredentialType,
80>;
81
82#[derive(Debug, Serialize, Deserialize, Clone, Default)]
84#[serde(rename_all = "camelCase")]
85pub struct StatusList2021 {
86 pub encoded_list: EncodedList,
87
88 #[serde(flatten)]
89 pub more_properties: BTreeMap<String, json_syntax::Value>,
90}
91
92#[derive(Debug, thiserror::Error)]
94pub enum NewStatusListError {
95 #[error("Unable to encode list")]
96 EncodedList(#[source] NewEncodedListError),
97}
98
99impl StatusList2021 {
100 pub fn new(len: usize) -> Result<Self, NewStatusListError> {
102 Ok(StatusList2021 {
103 encoded_list: EncodedList::new(len).map_err(NewStatusListError::EncodedList)?,
104 more_properties: BTreeMap::new(),
105 })
106 }
107
108 pub fn set_status(&mut self, index: usize, revoked: bool) -> Result<(), SetStatusError> {
111 let mut list = List::try_from(&self.encoded_list)?;
112 let bitstring_len = list.0.len() * 8;
113 let mut bitstring = BitVec::<Lsb0, u8>::try_from_vec(list.0)
114 .map_err(|_| SetStatusError::ListTooLarge(bitstring_len))?;
115
116 if bitstring_len < MIN_BITSTRING_LENGTH {
117 return Err(SetStatusError::ListTooSmall(
118 bitstring_len,
119 MIN_BITSTRING_LENGTH,
120 ));
121 }
122
123 if let Some(mut bitref) = bitstring.get_mut(index) {
124 *bitref = revoked;
125 } else {
126 return Err(SetStatusError::OutOfBounds(index, bitstring_len));
127 }
128
129 list.0 = bitstring.into_vec();
130 self.encoded_list = EncodedList::try_from(&list)?;
131 Ok(())
132 }
133}
134
135impl CredentialStatus for StatusList2021Entry {
136 async fn check(
141 &self,
142 credential: &AnyDataIntegrity<SpecializedJsonCredential>,
143 resolver: &impl VerificationMethodResolver<Method = AnyMethod>,
144 ) -> Result<StatusCheck, StatusCheckError> {
145 use bitvec::prelude::*;
146
147 if !credential.context.contains_iri(STATUS_LIST_2021_V1_CONTEXT) {
149 return Ok(StatusCheck::Invalid(Reason::MissingRequiredLdContext(
151 STATUS_LIST_2021_V1_CONTEXT.to_owned(),
152 )));
153 }
154
155 if self.id == self.status_list_credential {
156 return Ok(StatusCheck::Invalid(Reason::StatusIdMatchesCredentialId(
157 self.id.clone(),
158 )));
159 }
160
161 if self.status_list_credential.scheme().as_str() != "https" {
165 return Ok(StatusCheck::Invalid(Reason::UnsupportedUriScheme(
166 self.status_list_credential.scheme().to_owned(),
167 )));
168 }
169
170 let credential_data = load_resource(&self.status_list_credential).await?;
171 let status_list_credential =
172 serde_json::from_slice::<AnyDataIntegrity<StatusList2021Credential>>(&credential_data)?;
173
174 if credential.issuer.id() != status_list_credential.issuer.id() {
175 return Ok(StatusCheck::Invalid(Reason::IssuerMismatch(
176 credential.issuer.id().to_owned(),
177 status_list_credential.issuer.id().to_owned(),
178 )));
179 }
180
181 let params = VerificationParameters::from_resolver(resolver);
182 let vc_result = status_list_credential.verify(params).await?;
183
184 if let Err(e) = vc_result {
185 return Ok(StatusCheck::Invalid(Reason::CredentialVerification(e)));
186 }
187
188 if status_list_credential.id.as_deref() != Some(self.status_list_credential.as_uri()) {
189 return Ok(StatusCheck::Invalid(Reason::IdMismatch(
190 credential.issuer.id().to_owned(),
191 status_list_credential.issuer.id().to_owned(),
192 )));
193 }
194
195 let revocation_list = match status_list_credential.credential_subjects.as_ref() {
196 [StatusList2021Subject::StatusList2021(l)] => l,
197 [] => return Ok(StatusCheck::Invalid(Reason::MissingCredentialSubject)),
198 _ => return Ok(StatusCheck::Invalid(Reason::TooManyCredentialSubjects)),
199 };
200
201 let list = match List::try_from(&revocation_list.encoded_list) {
202 Ok(list) => list,
203 Err(e) => return Ok(StatusCheck::Invalid(Reason::DecodeListError(e))),
204 };
205
206 let credential_index = self.status_list_index.0;
207 let bitstring = match BitVec::<Lsb0, u8>::try_from_vec(list.0) {
208 Ok(bitstring) => bitstring,
209 Err(list) => return Err(StatusCheckError::RevocationListTooLarge(list.len())),
210 };
211
212 let revoked = match bitstring.get(credential_index) {
213 Some(bitref) => *bitref,
214 None => return Ok(StatusCheck::Invalid(Reason::InvalidRevocationListIndex)),
215 };
216
217 if revoked {
218 return Ok(StatusCheck::Invalid(Reason::Revoked));
219 }
220
221 Ok(StatusCheck::Valid)
222 }
223}