Skip to main content

identity_kit/methods/
jwk.rs

1//! `did:jwk`: a DID that is a base64url-encoded JSON Web Key.
2//!
3//! The [did:jwk method] is a deterministic transformation of a public JWK into a DID document:
4//! the method-specific id is `base64url(JWK)`, the document has exactly one verification method,
5//! `#0`, of type `JsonWebKey2020`, and the verification relationships follow the key's `use`
6//! member (all five when it is absent, no `keyAgreement` for `sig`, only `keyAgreement` for
7//! `enc`). The method cannot update or deactivate.
8//!
9//! [did:jwk method]: https://github.com/quartzjer/did-jwk/blob/main/spec.md
10
11use crate::did::{
12    Context, DEFAULT_MAX_LEN, Did, DidDocument, DidError, DocumentError, MethodType,
13    ResolutionError, ResolutionOptions, ResolutionResult, VerificationMaterial, VerificationMethod,
14    VerificationRelationship,
15};
16use crate::jose::Jwk;
17use crate::resolver::{DidProducer, DidResolver, Hosting, MethodCapabilities, ProduceError};
18
19use super::check_accept;
20
21/// The `did:jwk` method: a [`DidResolver`] that expands the DID and a [`DidProducer`] that
22/// encodes one public key.
23///
24/// # Examples
25///
26/// ```
27/// use identity_kit::did::{Did, VerificationRelationship};
28/// use identity_kit::jose::Jwk;
29/// use identity_kit::methods::DidJwk;
30///
31/// let jwk: Jwk = serde_json::from_str(
32///     r#"{"kty":"OKP","crv":"Ed25519","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}"#,
33/// )?;
34/// let did = DidJwk::did(&jwk)?;
35/// assert!(did.as_str().starts_with("did:jwk:eyJ"));
36///
37/// let document = DidJwk::document(&did)?;
38/// let key = did.join("#0")?;
39/// let method = document.resolve_verification_method(&key, Some(VerificationRelationship::Authentication))?;
40/// assert_eq!(method.public_key_jwk(), Some(&jwk));
41/// # Ok::<(), Box<dyn std::error::Error>>(())
42/// ```
43#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
44pub struct DidJwk;
45
46impl DidJwk {
47    /// The method name, `jwk`.
48    pub const METHOD: &'static str = "jwk";
49
50    /// The fragment of the one verification method, `0`.
51    pub const FRAGMENT: &'static str = "0";
52
53    /// The longest `did:jwk` whose document this method can represent: the verification
54    /// method id `did#0` must fit [`DEFAULT_MAX_LEN`] bytes.
55    pub const MAX_DID_LEN: usize = DEFAULT_MAX_LEN - 1 - Self::FRAGMENT.len();
56
57    /// The `@context` of every `did:jwk` document.
58    pub const CONTEXT: [&'static str; 2] = [
59        Context::DID_V1,
60        "https://w3id.org/security/suites/jws-2020/v1",
61    ];
62
63    /// The `did:jwk` of the public part of `jwk`, serialized as compact JSON in this crate's
64    /// member order. Any other serialization of the same key is another, equally valid, DID.
65    ///
66    /// # Errors
67    ///
68    /// [`DidError::TooLong`] when the encoded key exceeds [`MAX_DID_LEN`](Self::MAX_DID_LEN)
69    /// bytes, as a JWK carrying an `x5c` chain or large extension members may; base64url is
70    /// otherwise valid DID syntax.
71    pub fn did(jwk: &Jwk) -> Result<Did, DidError> {
72        let json = serde_json::to_vec(&jwk.public()).expect("a JWK serializes");
73        let id = crate::jose::base64url::encode(&json);
74        Did::parse_with_max_len(&format!("did:jwk:{id}"), Self::MAX_DID_LEN)
75    }
76
77    /// The DID document `did` expands to (the method's Read operation).
78    ///
79    /// # Errors
80    ///
81    /// - [`ResolutionError::MethodNotSupported`] when `did` is not a `did:jwk`.
82    /// - [`ResolutionError::InvalidDid`] when the method-specific id is not base64url of a
83    ///   JSON Web Key, the key carries private parameters, or the DID is longer than
84    ///   [`MAX_DID_LEN`](Self::MAX_DID_LEN).
85    pub fn document(did: &Did) -> Result<DidDocument, ResolutionError> {
86        if did.method() != Self::METHOD {
87            return Err(ResolutionError::MethodNotSupported);
88        }
89        if did.as_str().len() > Self::MAX_DID_LEN {
90            return Err(ResolutionError::InvalidDid);
91        }
92        let json = crate::jose::base64url::decode(did.method_specific_id())
93            .map_err(|_| ResolutionError::InvalidDid)?;
94        let jwk: Jwk = serde_json::from_slice(&json).map_err(|_| ResolutionError::InvalidDid)?;
95        if jwk.is_private() {
96            return Err(ResolutionError::InvalidDid);
97        }
98        build(did, jwk).map_err(|_| ResolutionError::InvalidDid)
99    }
100}
101
102/// The document of `did` for `jwk`, whose relationships follow `use`.
103fn build(did: &Did, jwk: Jwk) -> Result<DidDocument, DocumentError> {
104    let relationships: &[VerificationRelationship] = match jwk.key_use() {
105        Some("sig") => &[
106            VerificationRelationship::Authentication,
107            VerificationRelationship::AssertionMethod,
108            VerificationRelationship::CapabilityInvocation,
109            VerificationRelationship::CapabilityDelegation,
110        ],
111        Some("enc") => &[VerificationRelationship::KeyAgreement],
112        _ => VerificationRelationship::ALL,
113    };
114    let method = VerificationMethod::new(
115        did.to_url().with_fragment(DidJwk::FRAGMENT),
116        MethodType::JSON_WEB_KEY_2020,
117        did.clone(),
118        VerificationMaterial::PublicKeyJwk(Box::new(jwk)),
119    );
120    DidDocument::builder(did.clone())
121        .context(Context::uris(DidJwk::CONTEXT))
122        .verification_method(method, relationships.iter().copied())
123        .build()
124}
125
126#[async_trait::async_trait]
127impl DidResolver for DidJwk {
128    fn method(&self) -> &'static str {
129        Self::METHOD
130    }
131
132    /// Expands `did` with [`DidJwk::document`]; `options` may only ask for a JSON
133    /// representation.
134    async fn resolve(
135        &self,
136        did: &Did,
137        options: &ResolutionOptions,
138    ) -> Result<ResolutionResult, ResolutionError> {
139        check_accept(options)?;
140        Self::document(did).map(ResolutionResult::new)
141    }
142}
143
144#[async_trait::async_trait]
145impl DidProducer for DidJwk {
146    type Params = ();
147
148    fn method(&self) -> &'static str {
149        Self::METHOD
150    }
151
152    /// Embedded, one key, never updated, verification method `#0`.
153    fn capabilities(&self) -> MethodCapabilities {
154        MethodCapabilities::new(Hosting::Embedded)
155            .with_single_key(true)
156            .with_fixed_fragment(Self::FRAGMENT)
157    }
158
159    /// The document of [`DidJwk::did`] for the one key in `keys`.
160    async fn produce(&self, keys: &[Jwk], _: ()) -> Result<DidDocument, ProduceError> {
161        let jwk = super::single_key(keys)?;
162        let did = Self::did(jwk).map_err(|error| ProduceError::UnsupportedKey {
163            index: 0,
164            reason: error.to_string(),
165        })?;
166        Ok(build(&did, jwk.public())?)
167    }
168}