Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Token Status List & Bitstring Status List v1.0 #551

Merged
merged 14 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,15 @@ sha3 = "0.10.8"
rsa = "0.6"

# other common dependencies
log = "0.4.21"
async-std = "1.9"
async-trait = "0.1.68"
thiserror = "1.0.40"
chrono = "0.4.24"
iref = "3.1.2"
static-iref = "3.0"
rdf-types = "0.22.3"
xsd-types = "0.9.2"
xsd-types = "0.9.4"
locspan = "0.8"
json-syntax = "0.12.2"
nquads-syntax = "0.19"
Expand Down
6 changes: 6 additions & 0 deletions crates/claims/crates/jws/src/compact/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,9 @@ impl Deref for CompactJWSBuf {
#[derive(Debug, thiserror::Error)]
#[error("invalid compact JWS")]
pub struct InvalidCompactJWS<B = String>(pub B);

impl<'a> InvalidCompactJWS<&'a [u8]> {
pub fn into_owned(self) -> InvalidCompactJWS<Vec<u8>> {
InvalidCompactJWS(self.0.to_owned())
}
}
2 changes: 1 addition & 1 deletion crates/claims/crates/jws/src/compact/str.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ impl CompactJWSStr {

pub fn from_string(data: &str) -> Result<&Self, InvalidCompactJWS<&str>> {
let inner = CompactJWS::new(data.as_bytes()).map_err(|_| InvalidCompactJWS(data))?;
Ok(unsafe { std::mem::transmute(inner) })
Ok(unsafe { std::mem::transmute::<&CompactJWS, &Self>(inner) })
}

/// Creates a new compact JWS without checking the data.
Expand Down
8 changes: 8 additions & 0 deletions crates/claims/crates/jwt/src/claims/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ use crate::{Claim, ClaimSet};
pub struct AnyClaims(BTreeMap<String, serde_json::Value>);

impl AnyClaims {
pub fn contains(&self, key: &str) -> bool {
self.0.contains_key(key)
}

pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
self.0.get(key)
}
Expand Down Expand Up @@ -48,6 +52,10 @@ impl IntoIterator for AnyClaims {
impl ClaimSet for AnyClaims {
type Error = serde_json::Error;

fn contains<C: Claim>(&self) -> bool {
self.contains(C::JWT_CLAIM_NAME)
}

fn try_get<C: Claim>(&self) -> Result<Option<Cow<C>>, Self::Error> {
self.get(C::JWT_CLAIM_NAME)
.cloned()
Expand Down
17 changes: 17 additions & 0 deletions crates/claims/crates/jwt/src/claims/matching.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@ impl<A, B> CastClaim<A, B> for () {
}
}

impl<A, B> CastClaim<A, B> for bool {
type Target = Self;

unsafe fn cast_claim(value: Self) -> Self::Target {
value
}
}

impl<A, B, T: CastClaim<A, B>> CastClaim<A, B> for Option<T> {
type Target = Option<T::Target>;

Expand Down Expand Up @@ -171,6 +179,15 @@ mod tests {
impl ClaimSet for CustomClaimSet {
type Error = serde_json::Error;

fn contains<C: Claim>(&self) -> bool {
match_claim_type! {
match C {
CustomClaim => self.custom.is_some(),
_ => ClaimSet::contains::<C>(&self.other_claims)
}
}
}

fn try_get<C: Claim>(&self) -> Result<Option<Cow<C>>, Self::Error> {
match_claim_type! {
match C {
Expand Down
4 changes: 4 additions & 0 deletions crates/claims/crates/jwt/src/claims/mixed/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ impl<T> JWTClaims<T> {
impl<T: ClaimSet> ClaimSet for JWTClaims<T> {
type Error = T::Error;

fn contains<C: Claim>(&self) -> bool {
ClaimSet::contains::<C>(&self.registered) || self.private.contains::<C>()
}

fn try_get<C: Claim>(&self) -> Result<Option<Cow<C>>, Self::Error> {
match InfallibleClaimSet::get(&self.registered) {
Some(claim) => Ok(Some(claim)),
Expand Down
2 changes: 2 additions & 0 deletions crates/claims/crates/jwt/src/claims/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub trait Claim: 'static + Clone + Serialize + DeserializeOwned {
pub trait ClaimSet {
type Error: fmt::Display;

fn contains<C: Claim>(&self) -> bool;

fn try_get<C: Claim>(&self) -> Result<Option<Cow<C>>, Self::Error>;

fn try_set<C: Claim>(&mut self, claim: C) -> Result<Result<(), C>, Self::Error>;
Expand Down
55 changes: 14 additions & 41 deletions crates/claims/crates/jwt/src/claims/registered.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ impl RegisteredClaims {
self.0.values()
}

pub fn contains<C: RegisteredClaim>(&self) -> bool {
self.0.contains_key(&C::JWT_REGISTERED_CLAIM_KIND)
}

pub fn get<C: RegisteredClaim>(&self) -> Option<&C> {
self.0
.get(&C::JWT_REGISTERED_CLAIM_KIND)
Expand Down Expand Up @@ -71,10 +75,6 @@ impl RegisteredClaims {
}
}

// impl ClaimSet for RegisteredClaims {
// type Error = std::convert::Infallible;
// }

impl JWSPayload for RegisteredClaims {
fn typ(&self) -> Option<&'static str> {
Some("JWT")
Expand All @@ -85,43 +85,6 @@ impl JWSPayload for RegisteredClaims {
}
}

// impl<C: RegisteredClaim> TryGetClaim<C> for RegisteredClaims {
// fn try_get_claim(&self) -> Result<Option<Cow<C>>, Self::Error> {
// Ok(self.get().map(Cow::Borrowed))
// }
// }

// impl<C: RegisteredClaim> GetClaim<C> for RegisteredClaims {
// fn get_claim(&self) -> Option<Cow<C>> {
// self.get().map(Cow::Borrowed)
// }
// }

// impl<C: RegisteredClaim> TrySetClaim<C> for RegisteredClaims {
// fn try_set_claim(&mut self, claim: C) -> Result<(), Self::Error> {
// self.set(claim);
// Ok(())
// }
// }

// impl<C: RegisteredClaim> SetClaim<C> for RegisteredClaims {
// fn set_claim(&mut self, claim: C) {
// self.set(claim);
// }
// }

// impl<C: RegisteredClaim> TryRemoveClaim<C> for RegisteredClaims {
// fn try_remove_claim(&mut self) -> Result<Option<C>, Self::Error> {
// Ok(self.remove::<C>())
// }
// }

// impl<C: RegisteredClaim> RemoveClaim<C> for RegisteredClaims {
// fn remove_claim(&mut self) -> Option<C> {
// self.remove::<C>()
// }
// }

impl<E, P: Proof> Validate<E, P> for RegisteredClaims
where
E: DateTimeEnvironment,
Expand Down Expand Up @@ -232,6 +195,16 @@ macro_rules! registered_claims {
impl ClaimSet for RegisteredClaims {
type Error = std::convert::Infallible;

fn contains<C: Claim>(&self) -> bool {
$(
if std::any::TypeId::of::<C>() == std::any::TypeId::of::<$variant>() {
return self.contains::<$variant>();
}
)*

false
}

fn try_get<C: Claim>(&self) -> Result<Option<Cow<C>>, Self::Error> {
$(
if std::any::TypeId::of::<C>() == std::any::TypeId::of::<$variant>() {
Expand Down
24 changes: 12 additions & 12 deletions crates/claims/crates/jwt/src/decoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ssi_jws::{
DecodedJWS, JWSVerifier,
};

use crate::JWTClaims;
use crate::{AnyClaims, JWTClaims};

#[derive(Debug, thiserror::Error)]
pub enum DecodeError {
Expand All @@ -25,16 +25,16 @@ impl From<DecodeError> for ProofValidationError {
/// Decoded JWT.
///
/// By definition this is a decoded JWS with JWT claims as payload.
pub type DecodedJWT = DecodedJWS<JWTClaims>;
pub type DecodedJWT<T = AnyClaims> = DecodedJWS<JWTClaims<T>>;

/// JWT borrowing decoding.
pub trait ToDecodedJWT {
/// Decodes a JWT with custom claims.
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWS<C>, DecodeError>;
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWT<C>, DecodeError>;

/// Decodes a JWT.
fn to_decoded_jwt(&self) -> Result<DecodedJWT, DecodeError> {
self.to_decoded_custom_jwt::<JWTClaims>()
self.to_decoded_custom_jwt::<AnyClaims>()
}

/// Verify the JWS signature.
Expand All @@ -56,47 +56,47 @@ pub trait ToDecodedJWT {
/// JWT consuming decoding.
pub trait IntoDecodedJWT: Sized {
/// Decodes a JWT with custom claims.
fn into_decoded_custom_jwt<C: DeserializeOwned>(self) -> Result<DecodedJWS<C>, DecodeError>;
fn into_decoded_custom_jwt<C: DeserializeOwned>(self) -> Result<DecodedJWT<C>, DecodeError>;

fn into_decoded_jwt(self) -> Result<DecodedJWT, DecodeError> {
self.into_decoded_custom_jwt::<JWTClaims>()
self.into_decoded_custom_jwt::<AnyClaims>()
}
}

impl ToDecodedJWT for CompactJWS {
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWS<C>, DecodeError> {
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWT<C>, DecodeError> {
self.to_decoded()?
.try_map(|bytes| serde_json::from_slice(&bytes).map_err(Into::into))
}
}

impl ToDecodedJWT for CompactJWSStr {
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWS<C>, DecodeError> {
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWT<C>, DecodeError> {
CompactJWS::to_decoded_custom_jwt(self)
}
}

impl ToDecodedJWT for CompactJWSBuf {
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWS<C>, DecodeError> {
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWT<C>, DecodeError> {
CompactJWS::to_decoded_custom_jwt(self)
}
}

impl IntoDecodedJWT for CompactJWSBuf {
fn into_decoded_custom_jwt<C: DeserializeOwned>(self) -> Result<DecodedJWS<C>, DecodeError> {
fn into_decoded_custom_jwt<C: DeserializeOwned>(self) -> Result<DecodedJWT<C>, DecodeError> {
self.into_decoded()?
.try_map(|bytes| serde_json::from_slice(&bytes).map_err(Into::into))
}
}

impl ToDecodedJWT for CompactJWSString {
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWS<C>, DecodeError> {
fn to_decoded_custom_jwt<C: DeserializeOwned>(&self) -> Result<DecodedJWT<C>, DecodeError> {
CompactJWS::to_decoded_custom_jwt(self)
}
}

impl IntoDecodedJWT for CompactJWSString {
fn into_decoded_custom_jwt<C: DeserializeOwned>(self) -> Result<DecodedJWS<C>, DecodeError> {
fn into_decoded_custom_jwt<C: DeserializeOwned>(self) -> Result<DecodedJWT<C>, DecodeError> {
self.into_decoded()?
.try_map(|bytes| serde_json::from_slice(&bytes).map_err(Into::into))
}
Expand Down
45 changes: 45 additions & 0 deletions crates/claims/crates/status/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[package]
name = "ssi-status"
version = "0.1.0"
edition = "2021"
authors = ["Spruce Systems, Inc."]
license = "Apache-2.0"
description = "Status Lists implementations for the `ssi` library."
repository = "https://github.com/spruceid/ssi/"
documentation = "https://docs.rs/ssi-token-status-list/"

[dependencies]
ssi-claims-core.workspace = true
ssi-jwt.workspace = true
ssi-jws.workspace = true
ssi-vc.workspace = true
ssi-verification-methods.workspace = true
ssi-data-integrity.workspace = true
ssi-json-ld.workspace = true
json-ld.workspace = true
serde = { workspace = true, features = ["derive"] }
thiserror.workspace = true
iref.workspace = true
multibase.workspace = true
base64.workspace = true
serde_json.workspace = true
json-syntax.workspace = true
rdf-types.workspace = true
xsd-types.workspace = true
log.workspace = true
reqwest = "0.12.3"
parking_lot = "0.12.1"
flate2 = "1.0.28"

[dev-dependencies]
ssi-jws = { workspace = true, features = ["secp256r1"] }
ssi-jwk.workspace = true
ssi-dids.workspace = true
ssi-data-integrity = { workspace = true, features = ["w3c"] }
tokio = { version = "1.0", features = ["macros", "rt"] }
clap = { version = "4.5.4", features = ["derive"] }
env_logger = "0.11.3"
hyper = "1.2.0"
hyper-util = { version = "0.1", features = ["full"] }
http-body-util = "0.1"
rand.workspace = true
15 changes: 15 additions & 0 deletions crates/claims/crates/status/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Status List Examples

Start the demo status list server:
```console
$ cargo run --example status_list_server -- -t application/vc+ld+json examples/files/local-status-list-credential.jsonld
serving /#statusList at 127.0.0.1:3000...
```

Use the demo status list client to check the revocation status of a credential:
```console
$ cargo run --example status_list_client -- -t application/vc+ld+json examples/files/status_list_revocable_1.jsonld
unrevoked
$ cargo run --example status_list_client -- -t application/vc+ld+json examples/files/status_list_revocable_3.jsonld
REVOKED
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"@context": [
"https://www.w3.org/ns/credentials/v2"
],
"id": "http://127.0.0.1/#statusList",
"type": [
"VerifiableCredential",
"BitstringStatusListCredential"
],
"credentialSubject": {
"type": "BitstringStatusList",
"statusPurpose": "revocation",
"encodedList": "uH4sIAAAAAAAA_-3AsQAAAAACsBDypw2jZ2sAAAAAAAAAAAAAAAAAAACAtwF6YgrEAEAAAA"
},
"proof": {
"@context": "https://w3id.org/security/suites/jws-2020/v1",
"type": "JsonWebSignature2020",
"created": "2024-04-15T14:53:55.745Z",
"verificationMethod": "did:jwk:eyJhbGciOiJFUzI1NiIsImNydiI6IlAtMjU2Iiwia3R5IjoiRUMiLCJ1c2UiOiJzaWciLCJ4IjoiRnlHbnZLcXJ5cWlzX3gwWHJTbENTbnpFeGlrSk5XLUgzY0hQWnRfZjBmUSIsInkiOiJKZGJUM1BJWllIdHdzNVprZUlLakM5RnhJaGZwYV9rbHVJZms5ekZwczZzIn0#0",
"proofPurpose": "assertionMethod",
"jws": "eyJhbGciOiJFUzI1NiIsImNyaXQiOlsiYjY0Il0sImI2NCI6ZmFsc2V9..VcTAx3Rnt6rHn6EQTOdA1T6bywuySmiq3ik8Ldhbb1TETNFGnrWR80210yikaZKJGtduEdPquQFNTZZ0xvRyiw"
}
}
8 changes: 8 additions & 0 deletions crates/claims/crates/status/examples/files/public-key.jwk
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"kty": "EC",
"use": "sig",
"crv": "P-256",
"x": "FyGnvKqryqis_x0XrSlCSnzExikJNW-H3cHPZt_f0fQ",
"y": "JdbT3PIZYHtws5ZkeIKjC9FxIhfpa_kluIfk9zFps6s",
"alg": "ES256"
}
9 changes: 9 additions & 0 deletions crates/claims/crates/status/examples/files/secret-key.jwk
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"kty": "EC",
"d": "ccxnWuheShtmdnA4cCiUG_Uo7ckuyx9na2bPnLp2iBA",
"use": "sig",
"crv": "P-256",
"x": "FyGnvKqryqis_x0XrSlCSnzExikJNW-H3cHPZt_f0fQ",
"y": "JdbT3PIZYHtws5ZkeIKjC9FxIhfpa_kluIfk9zFps6s",
"alg": "ES256"
}
Loading