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