identity_credential/sd_jwt_vc/
claims.rs

1// Copyright 2020-2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use 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/// JOSE payload claims for SD-JWT VC.
21#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
22#[non_exhaustive]
23pub struct SdJwtVcClaims {
24  /// Issuer.
25  pub iss: Url,
26  /// Not before.
27  /// See [RFC7519 section 4.1.5](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.5) for more information.
28  pub nbf: Option<Timestamp>,
29  /// Expiration.
30  /// See [RFC7519 section 4.1.4](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.4) for more information.
31  pub exp: Option<Timestamp>,
32  /// Verifiable credential type.
33  /// See [SD-JWT VC specification](https://www.ietf.org/archive/id/draft-ietf-oauth-sd-jwt-vc-04.html#type-claim)
34  /// for more information.
35  pub vct: StringOrUrl,
36  /// Token's status.
37  /// See [OAuth status list specification](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-status-list-02)
38  /// for more information.
39  pub status: Option<Status>,
40  /// Issued at.
41  /// See [RFC7519 section 4.1.6](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.6) for more information.
42  pub iat: Option<Timestamp>,
43  /// Subject.
44  /// See [RFC7519 section 4.1.2](https://www.rfc-editor.org/rfc/rfc7519.html#section-4.1.2) for more information.
45  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}