identity_did/
did_jwk.rs

1// Copyright 2020-2024 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4use std::fmt::Debug;
5use std::fmt::Display;
6use std::str::FromStr;
7
8use identity_jose::jwk::Jwk;
9use identity_jose::jwu::decode_b64_json;
10
11use crate::CoreDID;
12use crate::Error;
13use crate::DID;
14
15#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Deserialize, serde::Serialize)]
16#[repr(transparent)]
17#[serde(into = "CoreDID", try_from = "CoreDID")]
18/// A type representing a `did:jwk` DID.
19pub struct DIDJwk(CoreDID);
20
21impl DIDJwk {
22  /// [`DIDJwk`]'s method.
23  pub const METHOD: &'static str = "jwk";
24
25  /// Tries to parse a [`DIDJwk`] from a string.
26  pub fn parse(s: &str) -> Result<Self, Error> {
27    s.parse()
28  }
29
30  /// Returns the JWK encoded inside this did:jwk.
31  pub fn jwk(&self) -> Jwk {
32    decode_b64_json(self.method_id()).expect("did:jwk encodes a valid JWK")
33  }
34}
35
36impl AsRef<CoreDID> for DIDJwk {
37  fn as_ref(&self) -> &CoreDID {
38    &self.0
39  }
40}
41
42impl From<DIDJwk> for CoreDID {
43  fn from(value: DIDJwk) -> Self {
44    value.0
45  }
46}
47
48impl<'a> TryFrom<&'a str> for DIDJwk {
49  type Error = Error;
50  fn try_from(value: &'a str) -> Result<Self, Self::Error> {
51    value.parse()
52  }
53}
54
55impl Display for DIDJwk {
56  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57    write!(f, "{}", self.0)
58  }
59}
60
61impl FromStr for DIDJwk {
62  type Err = Error;
63  fn from_str(s: &str) -> Result<Self, Self::Err> {
64    s.parse::<CoreDID>().and_then(TryFrom::try_from)
65  }
66}
67
68impl From<DIDJwk> for String {
69  fn from(value: DIDJwk) -> Self {
70    value.to_string()
71  }
72}
73
74impl TryFrom<CoreDID> for DIDJwk {
75  type Error = Error;
76  fn try_from(value: CoreDID) -> Result<Self, Self::Error> {
77    let Self::METHOD = value.method() else {
78      return Err(Error::InvalidMethodName);
79    };
80    decode_b64_json::<Jwk>(value.method_id())
81      .map(|_| Self(value))
82      .map_err(|_| Error::InvalidMethodId)
83  }
84}
85
86#[cfg(test)]
87mod tests {
88  use identity_core::convert::FromJson;
89
90  use super::*;
91
92  #[test]
93  fn test_valid_deserialization() -> Result<(), Error> {
94    "did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9".parse::<DIDJwk>()?;
95    "did:jwk:eyJjcnYiOiJQLTI1NiIsImt0eSI6IkVDIiwieCI6ImFjYklRaXVNczNpOF91c3pFakoydHBUdFJNNEVVM3l6OTFQSDZDZEgyVjAiLCJ5IjoiX0tjeUxqOXZXTXB0bm1LdG00NkdxRHo4d2Y3NEk1TEtncmwyR3pIM25TRSJ9".parse::<DIDJwk>()?;
96
97    Ok(())
98  }
99
100  #[test]
101  fn test_jwk() {
102    let did = DIDJwk::parse("did:jwk:eyJrdHkiOiJPS1AiLCJjcnYiOiJYMjU1MTkiLCJ1c2UiOiJlbmMiLCJ4IjoiM3A3YmZYdDl3YlRUVzJIQzdPUTFOei1EUThoYmVHZE5yZngtRkctSUswOCJ9").unwrap();
103    let target_jwk = Jwk::from_json_value(serde_json::json!({
104      "kty":"OKP","crv":"X25519","use":"enc","x":"3p7bfXt9wbTTW2HC7OQ1Nz-DQ8hbeGdNrfx-FG-IK08"
105    }))
106    .unwrap();
107
108    assert_eq!(did.jwk(), target_jwk);
109  }
110
111  #[test]
112  fn test_invalid_deserialization() {
113    assert!(
114      "did:iota:0xf4d6f08f5a1b80dd578da7dc1b49c886d580acd4cf7d48119dfeb82b538ad88a"
115        .parse::<DIDJwk>()
116        .is_err()
117    );
118    assert!("did:jwk:".parse::<DIDJwk>().is_err());
119    assert!("did:jwk:z6MkiTBz1ymuepAQ4HEHYSF1H8quG5GLVVQR3djdX3mDooWp"
120      .parse::<DIDJwk>()
121      .is_err());
122  }
123}