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//! # Plain JWTs
51//!
52//! [`JwtValidator`] validates a JWT that is neither a credential nor a presentation, an OAuth
53//! 2.0 access token say, as RFC 7519 §7.2 and RFC 8725 describe: an allowed `alg`, the
54//! expected `typ`, the signature under the key a [`KeyResolver`] finds, that key bound to
55//! `iss` ([`IssuerBinding`]), the expected `iss` and `aud`, `exp`, `nbf` and `iat` within a
56//! leeway, and the claims the [`JwtValidationOptions`] require.
57//!
58//! # Examples
59//!
60//! A method is a type implementing [`DidResolver`]; here one that serves documents from memory.
61//!
62//! ```
63//! use std::collections::HashMap;
64//!
65//! use identity_kit::did::{
66//! Did, DidDocument, DidUrl, ResolutionError, ResolutionErrorKind, ResolutionOptions,
67//! ResolutionResult,
68//! };
69//! use identity_kit::resolver::{Dereferenced, DidResolver, Resolver};
70//!
71//! struct Static(HashMap<Did, DidDocument>);
72//!
73//! #[async_trait::async_trait]
74//! impl DidResolver for Static {
75//! fn method(&self) -> &'static str {
76//! "example"
77//! }
78//!
79//! async fn resolve(
80//! &self,
81//! did: &Did,
82//! _: &ResolutionOptions,
83//! ) -> Result<ResolutionResult, ResolutionError> {
84//! self.0.get(did).cloned().map(ResolutionResult::new).ok_or_else(|| {
85//! ResolutionError::new(ResolutionErrorKind::NotFound)
86//! .with_detail(format!("no document is stored for `{did}`"))
87//! })
88//! }
89//! }
90//!
91//! # tokio::runtime::Builder::new_current_thread().build()?.block_on(async {
92//! let document: DidDocument = serde_json::from_str(r##"{
93//! "id": "did:example:123",
94//! "verificationMethod": [{
95//! "id": "did:example:123#key-1",
96//! "type": "JsonWebKey2020",
97//! "controller": "did:example:123",
98//! "publicKeyJwk": {
99//! "kty": "OKP", "crv": "Ed25519", "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"
100//! }
101//! }],
102//! "authentication": ["#key-1"]
103//! }"##)?;
104//! let did = document.id.clone();
105//! let resolver = Resolver::builder()
106//! .with(Static(HashMap::from([(did.clone(), document)])))
107//! .cache(std::time::Duration::from_secs(60))
108//! .build();
109//!
110//! assert_eq!(resolver.resolve_document(&did).await?.id, did);
111//!
112//! let key = DidUrl::parse("did:example:123#key-1")?;
113//! let jwk = resolver.resolve_public_key(&key, None).await?;
114//! assert_eq!(jwk.crv(), Some("Ed25519"));
115//! assert!(matches!(resolver.dereference(&key).await?, Dereferenced::VerificationMethod(_)));
116//!
117//! let unknown = Did::parse("did:other:123")?;
118//! let error = resolver.resolve(&unknown).await.unwrap_err();
119//! assert_eq!(error, ResolutionErrorKind::MethodNotSupported);
120//! assert_eq!(error.detail(), Some("no resolver is registered for `did:other`"));
121//! # Ok::<(), Box<dyn std::error::Error>>(())
122//! # })?;
123//! # Ok::<(), Box<dyn std::error::Error>>(())
124//! ```
125
126use std::sync::Arc;
127
128#[cfg(doc)]
129use crate::did::ResolutionErrorKind;
130use crate::did::{Did, ResolutionError, ResolutionOptions, ResolutionResult};
131
132mod cache;
133mod dereference;
134#[cfg(feature = "jwks")]
135mod jwks;
136mod jwt;
137mod key;
138mod producer;
139mod registry;
140
141pub use cache::DocumentCache;
142pub use dereference::{DereferenceError, Dereferenced};
143#[cfg(feature = "jwks")]
144#[cfg_attr(docsrs, doc(cfg(feature = "jwks")))]
145pub use jwks::{JwksError, JwksResolver};
146pub use jwt::{
147 IssuerBinding, JwtValidationError, JwtValidationOptions, JwtValidator, ValidatedJwt,
148};
149pub use key::{
150 CompositeKeyResolver, JwkResolver, KeyReference, KeyReferenceError, KeyResolutionError,
151 KeyResolver, KeyRole,
152};
153pub use producer::{DidProducer, Hosting, MethodCapabilities, ProduceError};
154pub use registry::{Resolver, ResolverBuilder};
155
156/// Resolves the DIDs of one method (DID Resolution "DID Resolution Algorithm").
157///
158/// A resolver is registered with a [`Resolver`] under [`method`](Self::method) and receives
159/// only DIDs of that method. It is `Send + Sync` and resolution is `async` because most methods
160/// fetch the document over the network or from a registry.
161///
162/// A failed resolution may be reported either as `Err`, or as an `Ok` result whose resolution
163/// metadata carries the error, which is the shape a method that builds a full
164/// [`ResolutionResult`] (a client of a remote resolver, say) produces anyway. The [`Resolver`]
165/// normalizes both to `Err`.
166#[async_trait::async_trait]
167pub trait DidResolver: Send + Sync {
168 /// The method name this resolver handles: `web` for `did:web`.
169 fn method(&self) -> &'static str;
170
171 /// Resolves `did`, whose method is [`method`](Self::method), with `options`.
172 ///
173 /// On success the result holds the document with `id` equal to `did` (or to the
174 /// `canonicalId` of the document metadata) and whatever metadata the method knows. A
175 /// deactivated DID resolves to a result without a document whose document metadata has
176 /// `deactivated: true`.
177 ///
178 /// # Errors
179 ///
180 /// A [`ResolutionError`] whose kind is [`NotFound`](ResolutionErrorKind::NotFound) for a DID
181 /// that does not exist,
182 /// [`RepresentationNotSupported`](ResolutionErrorKind::RepresentationNotSupported) for an
183 /// `accept` the method cannot produce, or [`InternalError`](ResolutionErrorKind::InternalError)
184 /// for an unexpected failure such as a network error. Attach what caused it with
185 /// [`ResolutionError::with_source`] and [`ResolutionError::with_detail`], so callers can tell
186 /// one failure from another.
187 async fn resolve(
188 &self,
189 did: &Did,
190 options: &ResolutionOptions,
191 ) -> Result<ResolutionResult, ResolutionError>;
192}
193
194macro_rules! forward_did_resolver {
195 ($($ty:ty),* $(,)?) => {$(
196 #[async_trait::async_trait]
197 impl<T: DidResolver + ?Sized> DidResolver for $ty {
198 fn method(&self) -> &'static str {
199 (**self).method()
200 }
201
202 async fn resolve(
203 &self,
204 did: &Did,
205 options: &ResolutionOptions,
206 ) -> Result<ResolutionResult, ResolutionError> {
207 (**self).resolve(did, options).await
208 }
209 }
210 )*};
211}
212
213forward_did_resolver!(&T, Box<T>, Arc<T>);