Skip to content

Commit a929510

Browse files
UMR1352abdulmth
andauthored
Credentials cannot be unrevoked with StatusList2021 (#1284)
* credentials cannot be unrevoked with StatusList2021 * Add test for unsuspension of credentials * fmt clippy * unrevoking valid credential does nothing * change error name, fix test * Update identity_credential/src/revocation/status_list_2021/credential.rs Co-authored-by: Abdulrahim Al Methiab <31316147+abdulmth@users.noreply.github.com> * Update identity_credential/src/revocation/status_list_2021/credential.rs Co-authored-by: Abdulrahim Al Methiab <31316147+abdulmth@users.noreply.github.com> --------- Co-authored-by: Abdulrahim Al Methiab <31316147+abdulmth@users.noreply.github.com>
1 parent 5e8953e commit a929510

File tree

1 file changed

+45
-2
lines changed

1 file changed

+45
-2
lines changed

identity_credential/src/revocation/status_list_2021/credential.rs

+45-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ const CREDENTIAL_SUBJECT_TYPE: &str = "StatusList2021";
2020

2121
/// [Error](std::error::Error) type that represents the possible errors that can be
2222
/// encountered when dealing with [`StatusList2021Credential`]s.
23-
#[derive(Clone, Debug, Error, strum::IntoStaticStr)]
23+
#[derive(Clone, Debug, Error, strum::IntoStaticStr, PartialEq, Eq)]
2424
pub enum StatusList2021CredentialError {
2525
/// The provided [`Credential`] has more than one `credentialSubject`.
2626
#[error("A StatusList2021Credential may only have one credentialSubject")]
@@ -34,9 +34,12 @@ pub enum StatusList2021CredentialError {
3434
/// Inner status list failures.
3535
#[error(transparent)]
3636
StatusListError(#[from] StatusListError),
37-
/// Missing status list id
37+
/// Missing status list id.
3838
#[error("Cannot set the status of a credential without a \"credentialSubject.id\".")]
3939
Unreferenceable,
40+
/// Credentials cannot be unrevoked.
41+
#[error("A previously revoked credential cannot be unrevoked.")]
42+
UnreversibleRevocation,
4043
}
4144

4245
use crate::credential::Credential;
@@ -117,6 +120,11 @@ impl StatusList2021Credential {
117120

118121
/// Sets the credential status of a given [`Credential`],
119122
/// mapping it to the `index`-th entry of this [`StatusList2021Credential`].
123+
///
124+
/// ## Note:
125+
/// - A revoked credential cannot ever be unrevoked and will lead to a
126+
/// [`StatusList2021CredentialError::UnreversibleRevocation`].
127+
/// - Trying to set `revoked_or_suspended` to `false` for an already valid credential will have no impact.
120128
pub fn set_credential_status(
121129
&mut self,
122130
credential: &mut Credential,
@@ -138,6 +146,10 @@ impl StatusList2021Credential {
138146
/// Sets the `index`-th entry to `value`
139147
pub(crate) fn set_entry(&mut self, index: usize, value: bool) -> Result<(), StatusList2021CredentialError> {
140148
let mut status_list = self.status_list()?;
149+
let entry_status = status_list.get(index)?;
150+
if self.purpose() == StatusPurpose::Revocation && !value && entry_status {
151+
return Err(StatusList2021CredentialError::UnreversibleRevocation);
152+
}
141153
status_list.set(index, value)?;
142154
self.subject.encoded_list = status_list.into_encoded_str();
143155

@@ -403,4 +415,35 @@ mod tests {
403415
.expect("Failed to deserialize");
404416
assert_eq!(credential.purpose(), StatusPurpose::Revocation);
405417
}
418+
#[test]
419+
fn revoked_credential_cannot_be_unrevoked() {
420+
let url = Url::parse("http://example.com").unwrap();
421+
let mut status_list_credential = StatusList2021CredentialBuilder::new(StatusList2021::default())
422+
.issuer(Issuer::Url(url.clone()))
423+
.purpose(StatusPurpose::Revocation)
424+
.subject_id(url)
425+
.build()
426+
.unwrap();
427+
428+
assert!(status_list_credential.set_entry(420, false).is_ok());
429+
status_list_credential.set_entry(420, true).unwrap();
430+
assert_eq!(
431+
status_list_credential.set_entry(420, false),
432+
Err(StatusList2021CredentialError::UnreversibleRevocation)
433+
);
434+
}
435+
#[test]
436+
fn suspended_credential_can_be_unsuspended() {
437+
let url = Url::parse("http://example.com").unwrap();
438+
let mut status_list_credential = StatusList2021CredentialBuilder::new(StatusList2021::default())
439+
.issuer(Issuer::Url(url.clone()))
440+
.purpose(StatusPurpose::Suspension)
441+
.subject_id(url)
442+
.build()
443+
.unwrap();
444+
445+
assert!(status_list_credential.set_entry(420, false).is_ok());
446+
status_list_credential.set_entry(420, true).unwrap();
447+
assert!(status_list_credential.set_entry(420, false).is_ok());
448+
}
406449
}

0 commit comments

Comments
 (0)