identity_credential/sd_jwt_vc/
claims.rs1use std::ops::Deref;
5use std::ops::DerefMut;
6
7use identity_core::common::StringOrUrl;
8use identity_core::common::Timestamp;
9use identity_core::common::Url;
10use sd_jwt_payload_rework::Disclosure;
11use sd_jwt_payload_rework::SdJwtClaims;
12use serde::Deserialize;
13use serde::Serialize;
14use serde_json::Value;
15
16use super::Error;
17use super::Result;
18use super::Status;
19
20#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[non_exhaustive]
23pub struct SdJwtVcClaims {
24 pub iss: Url,
26 pub nbf: Option<Timestamp>,
29 pub exp: Option<Timestamp>,
32 pub vct: StringOrUrl,
36 pub status: Option<Status>,
40 pub iat: Option<Timestamp>,
43 pub sub: Option<StringOrUrl>,
46 #[serde(flatten)]
47 pub(crate) sd_jwt_claims: SdJwtClaims,
48}
49
50impl Deref for SdJwtVcClaims {
51 type Target = SdJwtClaims;
52 fn deref(&self) -> &Self::Target {
53 &self.sd_jwt_claims
54 }
55}
56
57impl DerefMut for SdJwtVcClaims {
58 fn deref_mut(&mut self) -> &mut Self::Target {
59 &mut self.sd_jwt_claims
60 }
61}
62
63impl SdJwtVcClaims {
64 pub(crate) fn try_from_sd_jwt_claims(mut claims: SdJwtClaims, disclosures: &[Disclosure]) -> Result<Self> {
65 let check_disclosed = |claim_name: &'static str| {
66 disclosures
67 .iter()
68 .any(|disclosure| disclosure.claim_name.as_deref() == Some(claim_name))
69 .then_some(Error::DisclosedClaim(claim_name))
70 };
71 let iss = claims
72 .remove("iss")
73 .ok_or(Error::MissingClaim("iss"))
74 .map_err(|e| check_disclosed("iss").unwrap_or(e))
75 .and_then(|value| {
76 value
77 .as_str()
78 .and_then(|s| Url::parse(s).ok())
79 .ok_or_else(|| Error::InvalidClaimValue {
80 name: "iss",
81 expected: "URL",
82 found: value,
83 })
84 })?;
85 let nbf = {
86 if let Some(value) = claims.remove("nbf") {
87 value
88 .as_number()
89 .and_then(|t| t.as_i64())
90 .and_then(|t| Timestamp::from_unix(t).ok())
91 .ok_or_else(|| Error::InvalidClaimValue {
92 name: "nbf",
93 expected: "unix timestamp",
94 found: value,
95 })
96 .map(Some)?
97 } else {
98 if let Some(err) = check_disclosed("nbf") {
99 return Err(err);
100 }
101 None
102 }
103 };
104 let exp = {
105 if let Some(value) = claims.remove("exp") {
106 value
107 .as_number()
108 .and_then(|t| t.as_i64())
109 .and_then(|t| Timestamp::from_unix(t).ok())
110 .ok_or_else(|| Error::InvalidClaimValue {
111 name: "exp",
112 expected: "unix timestamp",
113 found: value,
114 })
115 .map(Some)?
116 } else {
117 if let Some(err) = check_disclosed("exp") {
118 return Err(err);
119 }
120 None
121 }
122 };
123 let vct = claims
124 .remove("vct")
125 .ok_or(Error::MissingClaim("vct"))
126 .map_err(|e| check_disclosed("vct").unwrap_or(e))
127 .and_then(|value| {
128 value
129 .as_str()
130 .and_then(|s| StringOrUrl::parse(s).ok())
131 .ok_or_else(|| Error::InvalidClaimValue {
132 name: "vct",
133 expected: "String or URL",
134 found: value,
135 })
136 })?;
137 let status = {
138 if let Some(value) = claims.remove("status") {
139 serde_json::from_value::<Status>(value.clone())
140 .map_err(|_| Error::InvalidClaimValue {
141 name: "status",
142 expected: "credential's status object",
143 found: value,
144 })
145 .map(Some)?
146 } else {
147 if let Some(err) = check_disclosed("status") {
148 return Err(err);
149 }
150 None
151 }
152 };
153 let sub = claims
154 .remove("sub")
155 .map(|value| {
156 value
157 .as_str()
158 .and_then(|s| StringOrUrl::parse(s).ok())
159 .ok_or_else(|| Error::InvalidClaimValue {
160 name: "sub",
161 expected: "String or URL",
162 found: value,
163 })
164 })
165 .transpose()?;
166 let iat = claims
167 .remove("iat")
168 .map(|value| {
169 value
170 .as_number()
171 .and_then(|t| t.as_i64())
172 .and_then(|t| Timestamp::from_unix(t).ok())
173 .ok_or_else(|| Error::InvalidClaimValue {
174 name: "iat",
175 expected: "unix timestamp",
176 found: value,
177 })
178 })
179 .transpose()?;
180
181 Ok(Self {
182 iss,
183 nbf,
184 exp,
185 vct,
186 status,
187 iat,
188 sub,
189 sd_jwt_claims: claims,
190 })
191 }
192}
193
194impl From<SdJwtVcClaims> for SdJwtClaims {
195 fn from(claims: SdJwtVcClaims) -> Self {
196 let SdJwtVcClaims {
197 iss,
198 nbf,
199 exp,
200 vct,
201 status,
202 iat,
203 sub,
204 mut sd_jwt_claims,
205 } = claims;
206
207 sd_jwt_claims.insert("iss".to_string(), Value::String(iss.into_string()));
208 nbf.and_then(|t| sd_jwt_claims.insert("nbf".to_string(), Value::Number(t.to_unix().into())));
209 exp.and_then(|t| sd_jwt_claims.insert("exp".to_string(), Value::Number(t.to_unix().into())));
210 sd_jwt_claims.insert("vct".to_string(), Value::String(vct.into()));
211 status.and_then(|status| sd_jwt_claims.insert("status".to_string(), serde_json::to_value(status).unwrap()));
212 iat.and_then(|t| sd_jwt_claims.insert("iat".to_string(), Value::Number(t.to_unix().into())));
213 sub.and_then(|sub| sd_jwt_claims.insert("sub".to_string(), Value::String(sub.into())));
214
215 sd_jwt_claims
216 }
217}