baseid_sd_jwt/
verifier.rs1use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine as _};
4use baseid_core::error::CryptoError;
5use baseid_crypto::jwt;
6use baseid_crypto::signer::Verifier;
7use serde_json::Value;
8use sha2::{Digest, Sha256};
9
10use crate::disclosure::Disclosure;
11use crate::SdJwt;
12
13pub struct SdJwtVerifier<'a> {
15 verifier: &'a dyn Verifier,
16}
17
18impl<'a> SdJwtVerifier<'a> {
19 pub fn new(verifier: &'a dyn Verifier) -> Self {
21 Self { verifier }
22 }
23
24 pub fn verify(&self, sd_jwt: &SdJwt) -> baseid_core::Result<Value> {
32 let (_header, mut claims) = jwt::decode_jwt(&sd_jwt.jwt, self.verifier)?;
34
35 let sd_digests: Vec<String> = match claims.get("_sd") {
37 Some(Value::Array(arr)) => arr
38 .iter()
39 .filter_map(|v| v.as_str().map(|s| s.to_string()))
40 .collect(),
41 Some(_) => return Err(CryptoError::VerificationFailed.into()),
42 None => Vec::new(),
43 };
44
45 let claims_obj = claims
47 .as_object_mut()
48 .ok_or(CryptoError::VerificationFailed)?;
49
50 for encoded_disclosure in &sd_jwt.disclosures {
51 let digest = {
53 let hash = Sha256::digest(encoded_disclosure.as_bytes());
54 URL_SAFE_NO_PAD.encode(hash)
55 };
56
57 if !sd_digests.contains(&digest) {
59 return Err(CryptoError::VerificationFailed.into());
60 }
61
62 let disclosure = Disclosure::decode(encoded_disclosure)?;
64 if let Some(name) = &disclosure.claim_name {
65 claims_obj.insert(name.clone(), disclosure.claim_value);
66 }
67 }
68
69 claims_obj.remove("_sd");
71 claims_obj.remove("_sd_alg");
72
73 Ok(claims)
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use crate::issuer::SdJwtIssuer;
81 use baseid_core::types::KeyType;
82 use baseid_crypto::KeyPair;
83
84 #[test]
85 fn full_issue_verify_roundtrip() {
86 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
87 let kid = "did:key:test#key-0";
88
89 let sd_jwt = SdJwtIssuer::new(&kp, kid)
90 .add_plain_claim("iss", Value::String("did:key:test".to_string()))
91 .add_sd_claim("name", Value::String("Alice".to_string()))
92 .add_sd_claim("age", Value::Number(30.into()))
93 .add_sd_claim("email", Value::String("alice@example.com".to_string()))
94 .build()
95 .unwrap();
96
97 assert_eq!(sd_jwt.disclosures.len(), 3);
98
99 let verifier = SdJwtVerifier::new(&kp.public);
101 let claims = verifier.verify(&sd_jwt).unwrap();
102
103 assert_eq!(claims["iss"], "did:key:test");
104 assert_eq!(claims["name"], "Alice");
105 assert_eq!(claims["age"], 30);
106 assert_eq!(claims["email"], "alice@example.com");
107 assert!(claims.get("_sd").is_none());
109 assert!(claims.get("_sd_alg").is_none());
110 }
111
112 #[test]
113 fn wrong_key_rejected() {
114 let kp1 = KeyPair::generate(KeyType::Ed25519).unwrap();
115 let kp2 = KeyPair::generate(KeyType::Ed25519).unwrap();
116
117 let sd_jwt = SdJwtIssuer::new(&kp1, "kid")
118 .add_sd_claim("name", Value::String("Alice".to_string()))
119 .build()
120 .unwrap();
121
122 let verifier = SdJwtVerifier::new(&kp2.public);
123 assert!(verifier.verify(&sd_jwt).is_err());
124 }
125
126 #[test]
127 fn tampered_disclosure_rejected() {
128 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
129
130 let mut sd_jwt = SdJwtIssuer::new(&kp, "kid")
131 .add_sd_claim("name", Value::String("Alice".to_string()))
132 .build()
133 .unwrap();
134
135 let fake = crate::disclosure::Disclosure::new(
137 Some("name".to_string()),
138 Value::String("Eve".to_string()),
139 );
140 sd_jwt.disclosures = vec![fake.encode().unwrap()];
141
142 let verifier = SdJwtVerifier::new(&kp.public);
143 assert!(verifier.verify(&sd_jwt).is_err());
144 }
145
146 #[test]
147 fn selective_presentation() {
148 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
149
150 let sd_jwt = SdJwtIssuer::new(&kp, "kid")
151 .add_plain_claim("iss", Value::String("issuer".to_string()))
152 .add_sd_claim("name", Value::String("Alice".to_string()))
153 .add_sd_claim("age", Value::Number(30.into()))
154 .add_sd_claim("email", Value::String("alice@example.com".to_string()))
155 .build()
156 .unwrap();
157
158 let mut presented = sd_jwt.clone();
160 presented.disclosures.pop(); let verifier = SdJwtVerifier::new(&kp.public);
163 let claims = verifier.verify(&presented).unwrap();
164
165 assert_eq!(claims["iss"], "issuer");
166 assert_eq!(claims["name"], "Alice");
167 assert_eq!(claims["age"], 30);
168 assert!(claims.get("email").is_none());
170 }
171
172 #[test]
173 fn compact_serialization_roundtrip() {
174 let kp = KeyPair::generate(KeyType::Ed25519).unwrap();
175
176 let sd_jwt = SdJwtIssuer::new(&kp, "kid")
177 .add_sd_claim("name", Value::String("Alice".to_string()))
178 .build()
179 .unwrap();
180
181 let compact = sd_jwt.serialize();
182 let parsed = SdJwt::parse(&compact).unwrap();
183
184 let verifier = SdJwtVerifier::new(&kp.public);
185 let claims = verifier.verify(&parsed).unwrap();
186 assert_eq!(claims["name"], "Alice");
187 }
188}