identity_credential/credential/
jwt.rs

1// Copyright 2020-2023 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use identity_core::common::Object;
5use identity_verification::jws::Decoder;
6use serde::Deserialize;
7use serde::Serialize;
8
9use crate::credential::CredentialV2;
10use crate::credential::EnvelopedVc;
11use crate::credential::VcDataUrl;
12
13/// A wrapper around a JSON Web Token (JWK).
14#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
15pub struct Jwt(String);
16
17impl Jwt {
18  /// Creates a new `Jwt` from the given string.
19  pub fn new(jwt_string: String) -> Self {
20    Self(jwt_string)
21  }
22
23  /// Returns a reference of the JWT string.
24  pub fn as_str(&self) -> &str {
25    &self.0
26  }
27}
28
29impl From<String> for Jwt {
30  fn from(jwt: String) -> Self {
31    Self::new(jwt)
32  }
33}
34
35impl From<Jwt> for String {
36  fn from(jwt: Jwt) -> Self {
37    jwt.0
38  }
39}
40
41impl AsRef<str> for Jwt {
42  fn as_ref(&self) -> &str {
43    &self.0
44  }
45}
46
47/// A compact JWT containing within its payload a data model 2.0 Verifiable Credential.
48#[derive(Debug, Clone, PartialEq, Eq, Hash)]
49pub struct JwtVcV2(Box<str>);
50
51impl AsRef<str> for JwtVcV2 {
52  fn as_ref(&self) -> &str {
53    &self.0
54  }
55}
56
57impl JwtVcV2 {
58  /// Returns the string representation of this [JwtVcV2].
59  pub const fn as_str(&self) -> &str {
60    &self.0
61  }
62
63  /// Converts this [JwtVcV2] into an [EnvelopedVc] of media type "application/vc+jwt" to be used within a verifiable
64  /// presentation.
65  pub fn into_enveloped_vc(self) -> EnvelopedVc {
66    let data_url = VcDataUrl::parse(&format!("data:application/vc+jwt,{}", self.as_str())).expect("valid data url");
67    EnvelopedVc::new(data_url)
68  }
69
70  /// Parses a compact JWT string into a [JwtVcV2].
71  pub fn parse(jwt: &str) -> Result<Self, JwtVcV2ParsingError> {
72    let decoded_jws = Decoder::new()
73      .decode_compact_serialization(jwt.as_bytes(), None)
74      .map_err(|e| JwtVcV2ParsingError { source: e.into() })?;
75
76    // Ensure the payload can be deserialized as a CredentialV2.
77    let _credential: CredentialV2<Object> =
78      serde_json::from_slice(decoded_jws.claims()).map_err(|e| JwtVcV2ParsingError { source: e.into() })?;
79
80    Ok(Self(jwt.to_owned().into_boxed_str()))
81  }
82}
83
84/// An attempt to parse a [JwtVcV2] failed.
85#[derive(Debug, thiserror::Error)]
86#[error("failed to parse a JWT-encoded Verifiable Credential v2.0")]
87#[non_exhaustive]
88pub struct JwtVcV2ParsingError {
89  source: Box<dyn std::error::Error + Send + Sync>,
90}
91
92impl TryFrom<EnvelopedVc> for JwtVcV2 {
93  type Error = JwtVcV2ParsingError;
94
95  fn try_from(enveloped_vc: EnvelopedVc) -> Result<Self, Self::Error> {
96    let data_url = &enveloped_vc.id;
97    if data_url.media_type() != "application/vc+jwt" {
98      return Err(JwtVcV2ParsingError {
99        source: format!(
100          "invalid media type: `{}`, expected `application/vc+jwt`",
101          data_url.media_type()
102        )
103        .into(),
104      });
105    }
106
107    let jwt_str = enveloped_vc.id.encoded_data();
108    Self::parse(jwt_str)
109  }
110}