identity_credential/sd_jwt_vc/metadata/
issuer.rs

1// Copyright 2020-2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use identity_core::common::Url;
5use identity_verification::jwk::JwkSet;
6use serde::Deserialize;
7use serde::Serialize;
8
9use crate::sd_jwt_vc::Error;
10use crate::sd_jwt_vc::SdJwtVc;
11
12/// Path used to query [`IssuerMetadata`] for a given JWT VC issuer.
13pub const WELL_KNOWN_VC_ISSUER: &str = "/.well-known/jwt-vc-issuer";
14
15/// SD-JWT VC issuer's metadata. Contains information about one issuer's
16/// public keys, either as an embedded JWK Set or a reference to one.
17/// ## Notes
18/// - [`IssuerMetadata::issuer`] must exactly match [`SdJwtVcClaims::iss`](crate::sd_jwt_vc::SdJwtVcClaims::iss) in
19///   order to be considered valid.
20#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
21pub struct IssuerMetadata {
22  /// Issuer URI.
23  pub issuer: Url,
24  /// JWK Set containing the issuer's public keys.
25  #[serde(flatten)]
26  pub jwks: Jwks,
27}
28
29impl IssuerMetadata {
30  /// Checks the validity of this [`IssuerMetadata`].
31  /// [`IssuerMetadata::issuer`] must match `sd_jwt_vc`'s iss claim's value.
32  pub fn validate(&self, sd_jwt_vc: &SdJwtVc) -> Result<(), Error> {
33    let expected_issuer = sd_jwt_vc.claims().iss.as_ref().ok_or_else(|| {
34      Error::InvalidIssuerMetadata(anyhow::anyhow!(
35        "SD-JWT VC is missing 'iss' claim required for issuer metadata validation"
36      ))
37    })?;
38    let actual_issuer = &self.issuer;
39    if actual_issuer != expected_issuer {
40      Err(Error::InvalidIssuerMetadata(anyhow::anyhow!(
41        "expected issuer \"{expected_issuer}\", but found \"{actual_issuer}\""
42      )))
43    } else {
44      Ok(())
45    }
46  }
47}
48
49/// JWK Set containing the issuer's public keys or a URL string referencing them.
50#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)]
51pub enum Jwks {
52  /// Reference to a JWK set.
53  #[serde(rename = "jwks_uri")]
54  Uri(Url),
55  /// An embedded JWK set.
56  #[serde(rename = "jwks")]
57  Object(JwkSet),
58}
59
60#[cfg(test)]
61mod tests {
62  use super::*;
63
64  const EXAMPLE_URI_ISSUER_METADATA: &str = r#"
65{
66  "issuer":"https://example.com",
67  "jwks_uri":"https://jwt-vc-issuer.example.org/my_public_keys.jwks"
68}
69  "#;
70  const EXAMPLE_JWKS_ISSUER_METADATA: &str = r#"
71{
72  "issuer":"https://example.com",
73  "jwks":{
74    "keys":[
75      {
76        "kid":"doc-signer-05-25-2022",
77        "e":"AQAB",
78        "n":"nj3YJwsLUFl9BmpAbkOswCNVx17Eh9wMO-_AReZwBqfaWFcfGHrZXsIV2VMCNVNU8Tpb4obUaSXcRcQ-VMsfQPJm9IzgtRdAY8NN8Xb7PEcYyklBjvTtuPbpzIaqyiUepzUXNDFuAOOkrIol3WmflPUUgMKULBN0EUd1fpOD70pRM0rlp_gg_WNUKoW1V-3keYUJoXH9NztEDm_D2MQXj9eGOJJ8yPgGL8PAZMLe2R7jb9TxOCPDED7tY_TU4nFPlxptw59A42mldEmViXsKQt60s1SLboazxFKveqXC_jpLUt22OC6GUG63p-REw-ZOr3r845z50wMuzifQrMI9bQ",
79        "kty":"RSA"
80      }
81    ]
82  }
83}
84  "#;
85
86  #[test]
87  fn deserializing_uri_metadata_works() {
88    let issuer_metadata: IssuerMetadata = serde_json::from_str(EXAMPLE_URI_ISSUER_METADATA).unwrap();
89    assert!(matches!(issuer_metadata.jwks, Jwks::Uri(_)));
90  }
91
92  #[test]
93  fn deserializing_jwks_metadata_works() {
94    let issuer_metadata: IssuerMetadata = serde_json::from_str(EXAMPLE_JWKS_ISSUER_METADATA).unwrap();
95    assert!(matches!(issuer_metadata.jwks, Jwks::Object { .. }));
96  }
97}