ssi_vc/v1/revocation/
v2020.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::REVOCATION_LIST_2020_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, StatusCheckError},
19 RequiredContext, SpecializedJsonCredential,
20 },
21};
22
23use super::{
24 CredentialStatus, EncodedList, List, RevocationListIndex, SetStatusError, StatusCheck,
25 MIN_BITSTRING_LENGTH,
26};
27pub struct RevocationList2020Context;
28
29impl RequiredContext for RevocationList2020Context {
30 const CONTEXT_IRI: &'static iref::Iri = iri!("https://w3id.org/vc-revocation-list-2020/v1");
31}
32
33#[derive(Debug, Serialize, Deserialize, Clone)]
37#[serde(rename_all = "camelCase")]
38pub struct RevocationList2020Status {
39 pub id: UriBuf,
42
43 pub revocation_list_index: RevocationListIndex,
45
46 pub revocation_list_credential: UriBuf,
48}
49pub struct RevocationList2020CredentialType;
50
51impl RequiredType for RevocationList2020CredentialType {
52 const REQUIRED_TYPE: &'static str = "RevocationList2020Credential";
53}
54
55pub type RevocationList2020Credential = SpecializedJsonCredential<
59 RevocationList2020Subject,
60 RevocationList2020Context,
61 RevocationList2020CredentialType,
62>;
63
64#[derive(Debug, Serialize, Deserialize, Clone)]
66#[serde(tag = "type")]
67pub enum RevocationList2020Subject {
68 RevocationList2020(RevocationList2020),
69}
70
71#[derive(Debug, Serialize, Deserialize, Clone, Default)]
74#[serde(rename_all = "camelCase")]
75pub struct RevocationList2020 {
76 pub encoded_list: EncodedList,
77
78 #[serde(flatten)]
79 pub more_properties: BTreeMap<String, json_syntax::Value>,
80}
81
82impl RevocationList2020 {
83 pub fn set_status(&mut self, index: usize, revoked: bool) -> Result<(), SetStatusError> {
85 let mut list = super::List::try_from(&self.encoded_list)?;
86 let bitstring_len = list.0.len() * 8;
87 let mut bitstring = BitVec::<Lsb0, u8>::try_from_vec(list.0)
88 .map_err(|_| SetStatusError::ListTooLarge(bitstring_len))?;
89 if bitstring_len < MIN_BITSTRING_LENGTH {
90 return Err(SetStatusError::ListTooSmall(
91 bitstring_len,
92 MIN_BITSTRING_LENGTH,
93 ));
94 }
95 if let Some(mut bitref) = bitstring.get_mut(index) {
96 *bitref = revoked;
97 } else {
98 return Err(SetStatusError::OutOfBounds(index, bitstring_len));
99 }
100 list.0 = bitstring.into_vec();
101 self.encoded_list = EncodedList::try_from(&list)?;
102 Ok(())
103 }
104}
105
106impl CredentialStatus for RevocationList2020Status {
107 async fn check(
112 &self,
113 credential: &AnyDataIntegrity<SpecializedJsonCredential>,
114 resolver: &impl VerificationMethodResolver<Method = AnyMethod>,
115 ) -> Result<StatusCheck, StatusCheckError> {
116 use bitvec::prelude::*;
117
118 if !credential
120 .context
121 .contains_iri(REVOCATION_LIST_2020_V1_CONTEXT)
122 {
123 return Ok(StatusCheck::Invalid(Reason::MissingRequiredLdContext(
125 REVOCATION_LIST_2020_V1_CONTEXT.to_owned(),
126 )));
127 }
128
129 if self.id == self.revocation_list_credential {
130 return Ok(StatusCheck::Invalid(Reason::StatusIdMatchesCredentialId(
131 self.id.clone(),
132 )));
133 }
134
135 if self.revocation_list_credential.scheme().as_str() != "https" {
139 return Ok(StatusCheck::Invalid(Reason::UnsupportedUriScheme(
140 self.revocation_list_credential.scheme().to_owned(),
141 )));
142 }
143
144 let credential_data = load_resource(&self.revocation_list_credential).await?;
145 let revocation_list_credential = serde_json::from_slice::<
146 AnyDataIntegrity<RevocationList2020Credential>,
147 >(&credential_data)?;
148
149 if credential.issuer.id() != revocation_list_credential.issuer.id() {
150 return Ok(StatusCheck::Invalid(Reason::IssuerMismatch(
151 credential.issuer.id().to_owned(),
152 revocation_list_credential.issuer.id().to_owned(),
153 )));
154 }
155
156 let params = VerificationParameters::from_resolver(resolver);
157 let vc_result = revocation_list_credential.verify(¶ms).await?;
158
159 if let Err(e) = vc_result {
160 return Ok(StatusCheck::Invalid(Reason::CredentialVerification(e)));
161 }
162
163 if revocation_list_credential.id.as_deref()
164 != Some(self.revocation_list_credential.as_uri())
165 {
166 return Ok(StatusCheck::Invalid(Reason::IdMismatch(
167 credential.issuer.id().to_owned(),
168 revocation_list_credential.issuer.id().to_owned(),
169 )));
170 }
171
172 let revocation_list = match revocation_list_credential.credential_subjects.as_ref() {
173 [RevocationList2020Subject::RevocationList2020(l)] => l,
174 [] => return Ok(StatusCheck::Invalid(Reason::MissingCredentialSubject)),
175 _ => return Ok(StatusCheck::Invalid(Reason::TooManyCredentialSubjects)),
176 };
177
178 let list = match List::try_from(&revocation_list.encoded_list) {
179 Ok(list) => list,
180 Err(e) => return Ok(StatusCheck::Invalid(Reason::DecodeListError(e))),
181 };
182
183 let credential_index = self.revocation_list_index.0;
184 let bitstring = match BitVec::<Lsb0, u8>::try_from_vec(list.0) {
185 Ok(bitstring) => bitstring,
186 Err(list) => return Err(StatusCheckError::RevocationListTooLarge(list.len())),
187 };
188
189 let revoked = match bitstring.get(credential_index) {
190 Some(bitref) => *bitref,
191 None => return Ok(StatusCheck::Invalid(Reason::InvalidRevocationListIndex)),
192 };
193
194 if revoked {
195 return Ok(StatusCheck::Invalid(Reason::Revoked));
196 }
197
198 Ok(StatusCheck::Valid)
199 }
200}