Skip to content

Commit 559f30e

Browse files
Support for SD-JWT+KB (#691)
* feat: Convenience impls for JWS types. * refactor: make clippy happy. * feat!: Support for SD-JWT+KB.
1 parent 206fc05 commit 559f30e

File tree

8 files changed

+515
-126
lines changed

8 files changed

+515
-126
lines changed

crates/claims/crates/jwt/src/claims/mixed/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ impl JWTClaimsBuilder {
143143
})
144144
}
145145
}
146+
147+
pub fn build(self) -> Result<JWTClaims, InvalidJWTClaims> {
148+
self.with_private_claims(AnyClaims::default())
149+
}
146150
}
147151

148152
#[derive(Debug, thiserror::Error)]

crates/claims/crates/jwt/src/claims/registered.rs

Lines changed: 72 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use super::{Claim, InvalidClaimValue, JWTClaims};
22
use crate::{CastClaim, ClaimSet, InfallibleClaimSet, NumericDate, StringOrURI};
3-
use ssi_claims_core::{ClaimsValidity, DateTimeProvider, ValidateClaims};
3+
use chrono::{DateTime, Utc};
4+
use ssi_claims_core::{ClaimsValidity, DateTimeProvider, InvalidClaims, ValidateClaims};
45
use ssi_core::OneOrMany;
56
use ssi_jws::JwsPayload;
67
use std::{borrow::Cow, collections::BTreeMap};
@@ -409,3 +410,73 @@ registered_claims! {
409410

410411
"vp": VerifiablePresentation(json_syntax::Value)
411412
}
413+
414+
pub enum JwtClaimValidationFailed {
415+
Premature {
416+
now: DateTime<Utc>,
417+
valid_from: DateTime<Utc>,
418+
},
419+
Expired {
420+
now: DateTime<Utc>,
421+
valid_until: DateTime<Utc>,
422+
},
423+
}
424+
425+
impl From<JwtClaimValidationFailed> for InvalidClaims {
426+
fn from(value: JwtClaimValidationFailed) -> Self {
427+
match value {
428+
JwtClaimValidationFailed::Premature { now, valid_from } => {
429+
Self::Premature { now, valid_from }
430+
}
431+
JwtClaimValidationFailed::Expired { now, valid_until } => {
432+
Self::Expired { now, valid_until }
433+
}
434+
}
435+
}
436+
}
437+
438+
impl ExpirationTime {
439+
pub fn verify(&self, now: DateTime<Utc>) -> Result<(), JwtClaimValidationFailed> {
440+
let exp: DateTime<Utc> = self.0.into();
441+
if exp > now {
442+
Ok(())
443+
} else {
444+
Err(JwtClaimValidationFailed::Expired {
445+
now,
446+
valid_until: exp,
447+
})
448+
}
449+
}
450+
}
451+
452+
impl NotBefore {
453+
pub fn verify(&self, now: DateTime<Utc>) -> Result<(), JwtClaimValidationFailed> {
454+
let nbf: DateTime<Utc> = self.0.into();
455+
if nbf <= now {
456+
Ok(())
457+
} else {
458+
Err(JwtClaimValidationFailed::Premature {
459+
now,
460+
valid_from: nbf,
461+
})
462+
}
463+
}
464+
}
465+
466+
impl IssuedAt {
467+
pub fn now() -> Self {
468+
Self(Utc::now().try_into().unwrap())
469+
}
470+
471+
pub fn verify(&self, now: DateTime<Utc>) -> Result<(), JwtClaimValidationFailed> {
472+
let iat: DateTime<Utc> = self.0.into();
473+
if iat <= now {
474+
Ok(())
475+
} else {
476+
Err(JwtClaimValidationFailed::Premature {
477+
now,
478+
valid_from: iat,
479+
})
480+
}
481+
}
482+
}

crates/claims/crates/sd-jwt/src/digest.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
55
use sha2::Digest;
66
use ssi_jwt::Claim;
77

8-
use crate::{disclosure::Disclosure, DecodeError, SD_ALG_CLAIM_NAME};
8+
use crate::{DecodeError, SD_ALG_CLAIM_NAME};
99

1010
/// Elements of the _sd_alg claim
1111
#[non_exhaustive]
@@ -25,15 +25,29 @@ impl SdAlg {
2525
}
2626
}
2727

28-
/// Hash the given disclosure.
29-
pub fn hash(&self, disclosure: &Disclosure) -> String {
28+
/// Hash the given bytes.
29+
pub fn hash(&self, bytes: impl AsRef<[u8]>) -> String {
3030
match self {
3131
Self::Sha256 => {
32-
let digest = sha2::Sha256::digest(disclosure.as_bytes());
32+
let digest = sha2::Sha256::digest(bytes.as_ref());
3333
BASE64_URL_SAFE_NO_PAD.encode(digest)
3434
}
3535
}
3636
}
37+
38+
/// Verifies the given hash.
39+
#[allow(deprecated)] // TODO bump the `digest` crate whenever possible.
40+
pub fn verify(&self, bytes: impl AsRef<[u8]>, hash: &str) -> bool {
41+
match self {
42+
Self::Sha256 => {
43+
let Ok(rdigest) = BASE64_URL_SAFE_NO_PAD.decode(hash) else {
44+
return false;
45+
};
46+
let digest = sha2::Sha256::digest(bytes.as_ref());
47+
digest.as_slice() == rdigest
48+
}
49+
}
50+
}
3751
}
3852

3953
impl Claim for SdAlg {
@@ -89,6 +103,8 @@ impl<'de> Deserialize<'de> for SdAlg {
89103
mod tests {
90104
use super::*;
91105

106+
use crate::Disclosure;
107+
92108
#[test]
93109
fn test_disclosure_hashing() {
94110
assert_eq!(

0 commit comments

Comments
 (0)