identity_kit/resolver/mod.rs
1//! DID resolution and production: the [`DidResolver`] and [`DidProducer`] traits a DID method
2//! implements, the [`Resolver`] registry that routes a DID to the resolver of its method, an
3//! optional TTL [`DocumentCache`], and DID URL dereferencing. Beside DIDs, finding the key a
4//! token was signed with: [`KeyReference`] and [`KeyResolver`].
5//!
6//! The module ships no DID method. Each method is a type implementing [`DidResolver`] and/or
7//! [`DidProducer`], registered with [`ResolverBuilder::with`]; adding one needs no change here.
8//!
9//! # Resolution
10//!
11//! [`Resolver::resolve`] implements the DID Resolution algorithm around the method's resolver:
12//! it reports [`ResolutionErrorKind::MethodNotSupported`] for a method nobody registered, turns a
13//! result whose metadata reports an error into `Err`, refuses a document whose `id` is not the
14//! DID being resolved (nor its `canonicalId`), and serves repeated resolutions from the cache
15//! when one is configured. [`Resolver::resolve_document`] is the shortcut to the document.
16//!
17//! # Dereferencing
18//!
19//! [`Resolver::dereference`] resolves the DID of a DID URL and then selects the resource the URL
20//! names: the document itself, or the verification method or service whose `id` is the URL
21//! ([`Dereferenced`]). The `versionId` and `versionTime` DID parameters are passed to the
22//! method's resolver as resolution options; a path, any other query parameter and the
23//! `service`/`relativeRef` parameters are method-specific and reported as
24//! [`DereferenceError::MethodSpecific`]. [`Resolver::resolve_public_key`] goes one step further
25//! and returns the [`Jwk`](crate::jose::Jwk) of a verification method, optionally checked to be
26//! usable for a verification relationship, decoding `publicKeyMultibase` material as a Multikey,
27//! which is what a JWS verifier needs.
28//!
29//! # Key references
30//!
31//! A JWT names the key that signed it in one of several ways (ADR 0006), and
32//! [`KeyReference::from_jws`] reads which, in this order: a `kid` that is a DID URL
33//! (`did:…#fragment`, without a path or DID parameters); a `kid` that is a `#fragment`, joined
34//! with a DID `iss`; another `kid` with an HTTPS `iss`, naming a key of the issuer's key set; the
35//! key itself in a `jwk` header; an `x5c` certificate chain; a `jku` key set URL. A
36//! [`KeyResolver`] turns the reference into a key for a [`KeyRole`], the issuer's
37//! (`assertionMethod` in a DID document) or the subject's (`authentication`):
38//!
39//! | Reference | Resolved by |
40//! |---|---|
41//! | [`KeyReference::DidUrl`] | [`Resolver`], dereferencing the verification method |
42//! | [`KeyReference::Jwk`] | [`JwkResolver`], the key itself |
43//! | [`KeyReference::Jwks`] | `JwksResolver` (feature `jwks`), the JWT VC issuer metadata of the issuer |
44//! | [`KeyReference::X509Chain`], [`KeyReference::Jku`] | nothing in the crate yet; an application's own [`KeyResolver`] |
45//!
46//! [`CompositeKeyResolver`] tries several in order. Resolving says nothing about trust: after
47//! the signature verifies, [`KeyReference::binds_to`] checks that the key belongs to the party
48//! the token claims, which the validators of the upper layer do.
49//!
50//! # Examples
51//!
52//! A method is a type implementing [`DidResolver`]; here one that serves documents from memory.
53//!
54//! ```
55//! use std::collections::HashMap;
56//!
57//! use identity_kit::did::{
58//! Did, DidDocument, DidUrl, ResolutionError, ResolutionErrorKind, ResolutionOptions,
59//! ResolutionResult,
60//! };
61//! use identity_kit::resolver::{Dereferenced, DidResolver, Resolver};
62//!
63//! struct Static(HashMap<Did, DidDocument>);
64//!
65//! #[async_trait::async_trait]
66//! impl DidResolver for Static {
67//! fn method(&self) -> &'static str {
68//! "example"
69//! }
70//!
71//! async fn resolve(
72//! &self,
73//! did: &Did,
74//! _: &ResolutionOptions,
75//! ) -> Result<ResolutionResult, ResolutionError> {
76//! self.0.get(did).cloned().map(ResolutionResult::new).ok_or_else(|| {
77//! ResolutionError::new(ResolutionErrorKind::NotFound)
78//! .with_detail(format!("no document is stored for `{did}`"))
79//! })
80//! }
81//! }
82//!
83//! # tokio::runtime::Builder::new_current_thread().build()?.block_on(async {
84//! let document: DidDocument = serde_json::from_str(r##"{
85//! "id": "did:example:123",
86//! "verificationMethod": [{
87//! "id": "did:example:123#key-1",
88//! "type": "JsonWebKey2020",
89//! "controller": "did:example:123",
90//! "publicKeyJwk": {
91//! "kty": "OKP", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
92//! }
93//! }],
94//! "authentication": ["#key-1"]
95//! }"##)?;
96//! let did = document.id.clone();
97//! let resolver = Resolver::builder()
98//! .with(Static(HashMap::from([(did.clone(), document)])))
99//! .cache(std::time::Duration::from_secs(60))
100//! .build();
101//!
102//! assert_eq!(resolver.resolve_document(&did).await?.id, did);
103//!
104//! let key = DidUrl::parse("did:example:123#key-1")?;
105//! let jwk = resolver.resolve_public_key(&key, None).await?;
106//! assert_eq!(jwk.crv(), Some("Ed25519"));
107//! assert!(matches!(resolver.dereference(&key).await?, Dereferenced::VerificationMethod(_)));
108//!
109//! let unknown = Did::parse("did:other:123")?;
110//! let error = resolver.resolve(&unknown).await.unwrap_err();
111//! assert_eq!(error, ResolutionErrorKind::MethodNotSupported);
112//! assert_eq!(error.detail(), Some("no resolver is registered for `did:other`"));
113//! # Ok::<(), Box<dyn std::error::Error>>(())
114//! # })?;
115//! # Ok::<(), Box<dyn std::error::Error>>(())
116//! ```
117
118use std::sync::Arc;
119
120#[cfg(doc)]
121use crate::did::ResolutionErrorKind;
122use crate::did::{Did, ResolutionError, ResolutionOptions, ResolutionResult};
123
124mod cache;
125mod dereference;
126#[cfg(feature = "jwks")]
127mod jwks;
128mod key;
129mod producer;
130mod registry;
131
132pub use cache::DocumentCache;
133pub use dereference::{DereferenceError, Dereferenced};
134#[cfg(feature = "jwks")]
135#[cfg_attr(docsrs, doc(cfg(feature = "jwks")))]
136pub use jwks::{JwksError, JwksResolver};
137pub use key::{
138 CompositeKeyResolver, JwkResolver, KeyReference, KeyReferenceError, KeyResolutionError,
139 KeyResolver, KeyRole,
140};
141pub use producer::{DidProducer, Hosting, MethodCapabilities, ProduceError};
142pub use registry::{Resolver, ResolverBuilder};
143
144/// Resolves the DIDs of one method (DID Resolution "DID Resolution Algorithm").
145///
146/// A resolver is registered with a [`Resolver`] under [`method`](Self::method) and receives
147/// only DIDs of that method. It is `Send + Sync` and resolution is `async` because most methods
148/// fetch the document over the network or from a registry.
149///
150/// A failed resolution may be reported either as `Err`, or as an `Ok` result whose resolution
151/// metadata carries the error, which is the shape a method that builds a full
152/// [`ResolutionResult`] (a client of a remote resolver, say) produces anyway. The [`Resolver`]
153/// normalizes both to `Err`.
154#[async_trait::async_trait]
155pub trait DidResolver: Send + Sync {
156 /// The method name this resolver handles: `web` for `did:web`.
157 fn method(&self) -> &'static str;
158
159 /// Resolves `did`, whose method is [`method`](Self::method), with `options`.
160 ///
161 /// On success the result holds the document with `id` equal to `did` (or to the
162 /// `canonicalId` of the document metadata) and whatever metadata the method knows. A
163 /// deactivated DID resolves to a result without a document whose document metadata has
164 /// `deactivated: true`.
165 ///
166 /// # Errors
167 ///
168 /// A [`ResolutionError`] whose kind is [`NotFound`](ResolutionErrorKind::NotFound) for a DID
169 /// that does not exist,
170 /// [`RepresentationNotSupported`](ResolutionErrorKind::RepresentationNotSupported) for an
171 /// `accept` the method cannot produce, or [`InternalError`](ResolutionErrorKind::InternalError)
172 /// for an unexpected failure such as a network error. Attach what caused it with
173 /// [`ResolutionError::with_source`] and [`ResolutionError::with_detail`], so callers can tell
174 /// one failure from another.
175 async fn resolve(
176 &self,
177 did: &Did,
178 options: &ResolutionOptions,
179 ) -> Result<ResolutionResult, ResolutionError>;
180}
181
182macro_rules! forward_did_resolver {
183 ($($ty:ty),* $(,)?) => {$(
184 #[async_trait::async_trait]
185 impl<T: DidResolver + ?Sized> DidResolver for $ty {
186 fn method(&self) -> &'static str {
187 (**self).method()
188 }
189
190 async fn resolve(
191 &self,
192 did: &Did,
193 options: &ResolutionOptions,
194 ) -> Result<ResolutionResult, ResolutionError> {
195 (**self).resolve(did, options).await
196 }
197 }
198 )*};
199}
200
201forward_did_resolver!(&T, Box<T>, Arc<T>);