@@ -20,7 +20,7 @@ const CREDENTIAL_SUBJECT_TYPE: &str = "StatusList2021";
20
20
21
21
/// [Error](std::error::Error) type that represents the possible errors that can be
22
22
/// encountered when dealing with [`StatusList2021Credential`]s.
23
- #[ derive( Clone , Debug , Error , strum:: IntoStaticStr ) ]
23
+ #[ derive( Clone , Debug , Error , strum:: IntoStaticStr , PartialEq , Eq ) ]
24
24
pub enum StatusList2021CredentialError {
25
25
/// The provided [`Credential`] has more than one `credentialSubject`.
26
26
#[ error( "A StatusList2021Credential may only have one credentialSubject" ) ]
@@ -34,9 +34,12 @@ pub enum StatusList2021CredentialError {
34
34
/// Inner status list failures.
35
35
#[ error( transparent) ]
36
36
StatusListError ( #[ from] StatusListError ) ,
37
- /// Missing status list id
37
+ /// Missing status list id.
38
38
#[ error( "Cannot set the status of a credential without a \" credentialSubject.id\" ." ) ]
39
39
Unreferenceable ,
40
+ /// Credentials cannot be unrevoked.
41
+ #[ error( "A previously revoked credential cannot be unrevoked." ) ]
42
+ UnreversibleRevocation ,
40
43
}
41
44
42
45
use crate :: credential:: Credential ;
@@ -117,6 +120,11 @@ impl StatusList2021Credential {
117
120
118
121
/// Sets the credential status of a given [`Credential`],
119
122
/// 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.
120
128
pub fn set_credential_status (
121
129
& mut self ,
122
130
credential : & mut Credential ,
@@ -138,6 +146,10 @@ impl StatusList2021Credential {
138
146
/// Sets the `index`-th entry to `value`
139
147
pub ( crate ) fn set_entry ( & mut self , index : usize , value : bool ) -> Result < ( ) , StatusList2021CredentialError > {
140
148
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
+ }
141
153
status_list. set ( index, value) ?;
142
154
self . subject . encoded_list = status_list. into_encoded_str ( ) ;
143
155
@@ -403,4 +415,35 @@ mod tests {
403
415
. expect ( "Failed to deserialize" ) ;
404
416
assert_eq ! ( credential. purpose( ) , StatusPurpose :: Revocation ) ;
405
417
}
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
+ }
406
449
}
0 commit comments