Skip to main content

identity_kit/resolver/
key.rs

1//! `KeyReference`, `KeyRole` and `KeyResolver`: where a token says its key is, and the trait
2//! that finds it.
3
4use core::fmt;
5use std::error::Error;
6use std::sync::Arc;
7
8use base64::Engine as _;
9use base64::engine::general_purpose::STANDARD;
10use url::Url;
11
12use super::{DereferenceError, Resolver};
13use crate::did::{Did, DidError, DidUrl, VerificationRelationship};
14use crate::jose::{CompactJws, Jwk, base64url};
15
16/// Where a token says the key that signed it is (ADR 0006).
17///
18/// A JWT names its key in one of several ways: a DID URL in `kid`, a `#fragment` in `kid`
19/// relative to a DID `iss`, a `kid` into the key set an HTTPS `iss` publishes, the key itself in
20/// a `jwk` header, a certificate chain in `x5c`, or a key set URL in `jku`.
21/// [`from_jws`](Self::from_jws) reads which from a JWS header; `sd_jwt_vc` builds one from a
22/// `cnf` claim. A [`KeyResolver`] turns it into a key.
23///
24/// Resolving a reference says nothing about trust: a key by value resolves to itself whoever
25/// made the token. Once the signature verifies, [`binds_to`](Self::binds_to) checks that the key
26/// belongs to the party the token claims.
27///
28/// # Examples
29///
30/// ```
31/// use identity_kit::jose::{CompactJws, JwtClaims};
32/// use identity_kit::resolver::{KeyReference, KeyReferenceError};
33///
34/// // {"alg":"EdDSA","kid":"#key-1"}.{"iss":"did:example:123"}
35/// let jws = CompactJws::decode(
36///     "eyJhbGciOiJFZERTQSIsImtpZCI6IiNrZXktMSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZToxMjMifQ.c2lnbmF0dXJl",
37/// )?;
38/// let claims: JwtClaims = jws.claims()?;
39/// let reference = KeyReference::from_jws(&jws, claims.iss.as_deref())?;
40/// assert_eq!(reference.to_string(), "did:example:123#key-1");
41///
42/// // A relative `kid` needs a DID to resolve against.
43/// assert!(matches!(
44///     KeyReference::from_jws(&jws, None),
45///     Err(KeyReferenceError::RelativeKidWithoutIssuer(_))
46/// ));
47/// # Ok::<(), Box<dyn std::error::Error>>(())
48/// ```
49#[derive(Debug, Clone, PartialEq, Eq)]
50#[non_exhaustive]
51pub enum KeyReference {
52    /// A verification method: a `kid` that is an absolute DID URL, or a `#fragment` joined with
53    /// a DID `iss`.
54    DidUrl(DidUrl),
55    /// The public key itself, from a `jwk` header or `cnf.jwk`.
56    Jwk(Box<Jwk>),
57    /// An `x5c` certificate chain, DER, leaf first. No resolver in the crate handles it yet.
58    X509Chain(Vec<Vec<u8>>),
59    /// The key `kid` names in the key set of the HTTPS issuer `issuer` (SD-JWT VC §3.5).
60    Jwks {
61        /// The issuer, the `iss` of the token.
62        issuer: Url,
63        /// The `kid` of the key in the issuer's key set.
64        kid: String,
65    },
66    /// The key set at `url`, from `jku`, and the `kid` that selects a key in it. No resolver in
67    /// the crate handles it yet.
68    Jku {
69        /// The key set URL.
70        url: Url,
71        /// The `kid`, if the token has one.
72        kid: Option<String>,
73    },
74}
75
76impl KeyReference {
77    /// Where the key of `jws` is, given the `iss` of its claims (when `iss` is a string).
78    ///
79    /// The header parameters are tried in this order, and the first that applies decides:
80    ///
81    /// 1. A `kid` that parses as a DID URL is [`DidUrl`](Self::DidUrl). A path or DID
82    ///    parameters such as `versionId` are refused: they could select a document version
83    ///    that still lists a retired key (ADR 0004).
84    /// 2. A `kid` starting with `#` is joined with `iss` into a [`DidUrl`](Self::DidUrl) when
85    ///    `iss` is a DID, and refused otherwise.
86    /// 3. Another `kid`, with an `https` `iss`, is [`Jwks`](Self::Jwks).
87    /// 4. A `jwk` header is [`Jwk`](Self::Jwk); a private key there is refused.
88    /// 5. An `x5c` header is [`X509Chain`](Self::X509Chain), each certificate base64-decoded
89    ///    but not parsed.
90    /// 6. A `jku` header is [`Jku`](Self::Jku), with the `kid` when there is one.
91    ///
92    /// A `kid` that none of the first three cases takes is an error when no `jwk`, `x5c` or
93    /// `jku` follows.
94    ///
95    /// # Errors
96    ///
97    /// A [`KeyReferenceError`]: [`Missing`](KeyReferenceError::Missing) when the header names no
98    /// key, otherwise the parameter that is wrong.
99    pub fn from_jws(jws: &CompactJws, iss: Option<&str>) -> Result<Self, KeyReferenceError> {
100        let header = jws.header();
101        let mut kid_error = None;
102        if let Some(kid) = header.kid.as_deref() {
103            match Self::from_kid(kid, iss)? {
104                Ok(reference) => return Ok(reference),
105                Err(source) => {
106                    kid_error = Some(KeyReferenceError::InvalidKid {
107                        kid: kid.to_owned(),
108                        source,
109                    });
110                }
111            }
112        }
113        if let Some(jwk) = &header.jwk {
114            if jwk.is_private() {
115                return Err(KeyReferenceError::InvalidJwk(
116                    "the `jwk` header holds a private key".to_owned(),
117                ));
118            }
119            return Ok(Self::Jwk(Box::new(jwk.clone())));
120        }
121        if let Some(x5c) = header.additional.get("x5c") {
122            return x5c_chain(x5c).map(Self::X509Chain);
123        }
124        if let Some(jku) = header.additional.get("jku") {
125            let url = jku
126                .as_str()
127                .ok_or_else(|| KeyReferenceError::InvalidUrl {
128                    parameter: "jku",
129                    value: jku.to_string(),
130                    reason: "not a string".to_owned(),
131                })
132                .and_then(|jku| parse_url("jku", jku))?;
133            return Ok(Self::Jku {
134                url,
135                kid: header.kid.clone(),
136            });
137        }
138        Err(kid_error.unwrap_or(KeyReferenceError::Missing))
139    }
140
141    /// Rules 1 to 3 of [`from_jws`](Self::from_jws): the reference `kid` makes with `iss`, or
142    /// why `kid` is not a DID URL when it makes none.
143    fn from_kid(kid: &str, iss: Option<&str>) -> Result<Result<Self, DidError>, KeyReferenceError> {
144        let not_did_url = match DidUrl::parse(kid) {
145            Ok(url) => return did_url(url).map(Ok),
146            Err(error) => error,
147        };
148        if kid.starts_with('#') {
149            let did = iss
150                .and_then(|iss| Did::parse(iss).ok())
151                .ok_or_else(|| KeyReferenceError::RelativeKidWithoutIssuer(kid.to_owned()))?;
152            let url = did
153                .join(kid)
154                .map_err(|source| KeyReferenceError::InvalidKid {
155                    kid: kid.to_owned(),
156                    source,
157                })?;
158            return did_url(url).map(Ok);
159        }
160        if let Some(issuer) = iss
161            .and_then(|iss| Url::parse(iss).ok())
162            .filter(|issuer| issuer.scheme() == "https")
163        {
164            return Ok(Ok(Self::Jwks {
165                issuer,
166                kid: kid.to_owned(),
167            }));
168        }
169        Ok(Err(not_did_url))
170    }
171
172    /// Whether `key`, which this reference resolved to and which verified the signature,
173    /// belongs to `party`, the issuer or holder the token claims:
174    ///
175    /// | Reference | Binds when |
176    /// |---|---|
177    /// | [`DidUrl`](Self::DidUrl) | `party` is the DID of the URL |
178    /// | [`Jwk`](Self::Jwk) | `party` is the `did:jwk` of `key`, or its RFC 7638 SHA-256 thumbprint, or absent (a SIOPv2 self-issued `sub`) |
179    /// | [`Jwks`](Self::Jwks) | `party` is the issuer URL (compared as parsed URLs) |
180    /// | [`X509Chain`](Self::X509Chain), [`Jku`](Self::Jku) | never, until the crate resolves them |
181    ///
182    /// Only a key by value can bind to no party; a validator that requires an issuer or holder
183    /// passes it.
184    ///
185    /// # Examples
186    ///
187    /// ```
188    /// use identity_kit::did::DidUrl;
189    /// use identity_kit::jose::Jwk;
190    /// use identity_kit::resolver::KeyReference;
191    ///
192    /// // RFC 8037 Appendix A.2.
193    /// let key: Jwk = serde_json::from_str(
194    ///     r#"{"kty":"OKP","crv":"Ed25519","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}"#,
195    /// )?;
196    /// let by_value = KeyReference::Jwk(Box::new(key.clone()));
197    /// // RFC 8037 Appendix A.3.
198    /// assert!(by_value.binds_to(Some("kPrK_qmxVWaYVA9wwBF6Iuo3vVzz7TxHCTwXBygrS4k"), &key));
199    /// assert!(by_value.binds_to(None, &key));
200    /// assert!(!by_value.binds_to(Some("did:example:123"), &key));
201    ///
202    /// let by_url = KeyReference::DidUrl(DidUrl::parse("did:example:123#key-1")?);
203    /// assert!(by_url.binds_to(Some("did:example:123"), &key));
204    /// assert!(!by_url.binds_to(Some("did:example:456"), &key));
205    /// assert!(!by_url.binds_to(None, &key));
206    /// # Ok::<(), Box<dyn std::error::Error>>(())
207    /// ```
208    pub fn binds_to(&self, party: Option<&str>, key: &Jwk) -> bool {
209        match (self, party) {
210            (Self::DidUrl(url), Some(party)) => {
211                Did::parse(party).is_ok_and(|did| did == *url.did())
212            }
213            (Self::Jwk(_), None) => true,
214            (Self::Jwk(_), Some(party)) => is_key_identifier(party, key),
215            (Self::Jwks { issuer, .. }, Some(party)) => {
216                Url::parse(party).is_ok_and(|party| party == *issuer)
217            }
218            _ => false,
219        }
220    }
221}
222
223impl fmt::Display for KeyReference {
224    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
225        match self {
226            Self::DidUrl(url) => fmt::Display::fmt(url, f),
227            Self::Jwk(jwk) => match jwk.thumbprint_sha256() {
228                Ok(thumbprint) => write!(f, "JWK {thumbprint}"),
229                Err(_) => f.write_str("JWK"),
230            },
231            Self::X509Chain(chain) => write!(f, "`x5c` chain of {} certificates", chain.len()),
232            Self::Jwks { issuer, kid } => write!(f, "`{kid}` of issuer `{issuer}`"),
233            Self::Jku {
234                url,
235                kid: Some(kid),
236            } => write!(f, "`{kid}` of `{url}`"),
237            Self::Jku { url, kid: None } => write!(f, "the key of `{url}`"),
238        }
239    }
240}
241
242/// `url`, refused when it carries a path or DID parameters.
243fn did_url(url: DidUrl) -> Result<KeyReference, KeyReferenceError> {
244    if url.path().is_some() || url.query().is_some() {
245        return Err(KeyReferenceError::KidWithParameters(url));
246    }
247    Ok(KeyReference::DidUrl(url))
248}
249
250/// The certificates of an `x5c` header: an array of standard base64 DER (RFC 7515 §4.1.6).
251fn x5c_chain(x5c: &serde_json::Value) -> Result<Vec<Vec<u8>>, KeyReferenceError> {
252    let invalid = |reason: &str| KeyReferenceError::InvalidX5c(reason.to_owned());
253    let certificates = x5c.as_array().ok_or_else(|| invalid("not an array"))?;
254    if certificates.is_empty() {
255        return Err(invalid("the chain is empty"));
256    }
257    certificates
258        .iter()
259        .map(|certificate| {
260            let certificate = certificate
261                .as_str()
262                .ok_or_else(|| invalid("a certificate is not a string"))?;
263            STANDARD
264                .decode(certificate)
265                .map_err(|_| invalid("a certificate is not base64"))
266        })
267        .collect()
268}
269
270/// `value` of `parameter` as an absolute URL.
271fn parse_url(parameter: &'static str, value: &str) -> Result<Url, KeyReferenceError> {
272    Url::parse(value).map_err(|e| KeyReferenceError::InvalidUrl {
273        parameter,
274        value: value.to_owned(),
275        reason: e.to_string(),
276    })
277}
278
279/// Whether `party` is the `did:jwk` or the RFC 7638 SHA-256 thumbprint of `key`.
280fn is_key_identifier(party: &str, key: &Jwk) -> bool {
281    let Ok(thumbprint) = key.thumbprint_sha256() else {
282        return false;
283    };
284    if party == thumbprint {
285        return true;
286    }
287    // A `did:jwk` names the key by value, in whatever member order its creator serialized it;
288    // the thumbprint compares the required members only.
289    Did::parse(party)
290        .ok()
291        .filter(|did| did.method() == "jwk")
292        .and_then(|did| base64url::decode(did.method_specific_id()).ok())
293        .and_then(|json| serde_json::from_slice::<Jwk>(&json).ok())
294        .and_then(|jwk| jwk.thumbprint_sha256().ok())
295        .is_some_and(|named| named == thumbprint)
296}
297
298/// Why a token names no key this crate can look for.
299#[derive(Debug, thiserror::Error)]
300#[non_exhaustive]
301pub enum KeyReferenceError {
302    /// The token names no key: no `kid`, `jwk`, `x5c` or `jku` (or, in `cnf`, no `jwk`, `kid`
303    /// or `jku`).
304    #[error("the token names no key: no `kid`, `jwk`, `x5c` or `jku`")]
305    Missing,
306    /// The `kid` is a `#fragment` and `iss` is not a DID to resolve it against.
307    #[error("`kid` {0:?} is relative and `iss` is not a DID")]
308    RelativeKidWithoutIssuer(String),
309    /// The `kid` is neither a DID URL nor anything else the token resolves it with.
310    #[error("`kid` {kid:?} is not a DID URL: {source}")]
311    InvalidKid {
312        /// The `kid`.
313        kid: String,
314        /// Why it is not a DID URL.
315        #[source]
316        source: DidError,
317    },
318    /// The `kid` carries a path or DID parameters. `versionId` and `versionTime` could select a
319    /// document version that still lists a retired key, so only `did:…#fragment` keys are
320    /// accepted.
321    #[error("`kid` `{0}` carries a path or DID parameters")]
322    KidWithParameters(DidUrl),
323    /// The key by value is not usable: it holds private material, say.
324    #[error("invalid `jwk`: {0}")]
325    InvalidJwk(String),
326    /// The `x5c` header is not an array of base64 certificates.
327    #[error("invalid `x5c`: {0}")]
328    InvalidX5c(String),
329    /// A URL parameter (`jku`) is not an absolute URL.
330    #[error("`{parameter}` {value:?} is not a URL: {reason}")]
331    InvalidUrl {
332        /// The parameter.
333        parameter: &'static str,
334        /// Its value.
335        value: String,
336        /// Why it did not parse.
337        reason: String,
338    },
339    /// The confirmation names its key only by a method the crate does not resolve, such as
340    /// `x5t#S256` or `jwe`.
341    #[error("`cnf` names its key only by `{0}`, which is not supported")]
342    UnsupportedConfirmation(String),
343}
344
345/// The role a key plays in a token, which decides what a DID document must say about it.
346#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
347#[non_exhaustive]
348pub enum KeyRole {
349    /// The key signs as the issuer of a credential or status list: `assertionMethod`. The
350    /// default.
351    #[default]
352    Issuer,
353    /// The key proves possession by the subject or holder (a presentation, a key binding JWT,
354    /// an ID token): `authentication`.
355    Subject,
356}
357
358impl KeyRole {
359    /// The verification relationship a DID document must list a key of this role under.
360    pub fn relationship(self) -> VerificationRelationship {
361        match self {
362            Self::Issuer => VerificationRelationship::AssertionMethod,
363            Self::Subject => VerificationRelationship::Authentication,
364        }
365    }
366}
367
368/// Finds the public key a [`KeyReference`] names.
369///
370/// [`Resolver`] implements it for DID URLs, [`JwkResolver`] for keys by value and, with the
371/// `jwks` feature, `JwksResolver` for the key sets of HTTPS issuers;
372/// [`CompositeKeyResolver`] combines them. An application can implement it over a cache, a
373/// fixed set of trusted keys, or a reference the crate does not resolve.
374#[async_trait::async_trait]
375pub trait KeyResolver: Send + Sync {
376    /// The public key `key` names, usable in `role`.
377    ///
378    /// # Errors
379    ///
380    /// [`KeyResolutionError::Unsupported`] for a kind of reference this resolver does not
381    /// handle, which lets a [`CompositeKeyResolver`] try the next one; any other
382    /// [`KeyResolutionError`] when the key cannot be found or used.
383    async fn resolve_key(
384        &self,
385        key: &KeyReference,
386        role: KeyRole,
387    ) -> Result<Jwk, KeyResolutionError>;
388}
389
390macro_rules! forward_key_resolver {
391    ($($ty:ty),* $(,)?) => {$(
392        #[async_trait::async_trait]
393        impl<T: KeyResolver + ?Sized> KeyResolver for $ty {
394            async fn resolve_key(
395                &self,
396                key: &KeyReference,
397                role: KeyRole,
398            ) -> Result<Jwk, KeyResolutionError> {
399                (**self).resolve_key(key, role).await
400            }
401        }
402    )*};
403}
404
405forward_key_resolver!(&T, Box<T>, Arc<T>);
406
407/// Resolves a [`KeyReference::DidUrl`] by dereferencing it to a verification method listed
408/// under the relationship of the role; every other reference is
409/// [`Unsupported`](KeyResolutionError::Unsupported).
410#[async_trait::async_trait]
411impl KeyResolver for Resolver {
412    async fn resolve_key(
413        &self,
414        key: &KeyReference,
415        role: KeyRole,
416    ) -> Result<Jwk, KeyResolutionError> {
417        match key {
418            KeyReference::DidUrl(url) => self
419                .resolve_public_key(url, Some(role.relationship()))
420                .await
421                .map_err(KeyResolutionError::from),
422            other => Err(KeyResolutionError::unsupported(other)),
423        }
424    }
425}
426
427/// Resolves a [`KeyReference::Jwk`] to its public part; every other reference is
428/// [`Unsupported`](KeyResolutionError::Unsupported).
429///
430/// Anybody can put a key in a `jwk` header, so the key says nothing about who signed: a
431/// validator accepts it only when the claimed party is that key's `did:jwk` or thumbprint
432/// ([`KeyReference::binds_to`]). Add it to a [`CompositeKeyResolver`] to accept such tokens.
433#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
434pub struct JwkResolver;
435
436#[async_trait::async_trait]
437impl KeyResolver for JwkResolver {
438    async fn resolve_key(&self, key: &KeyReference, _: KeyRole) -> Result<Jwk, KeyResolutionError> {
439        match key {
440            KeyReference::Jwk(jwk) => Ok(jwk.public()),
441            other => Err(KeyResolutionError::unsupported(other)),
442        }
443    }
444}
445
446/// Resolvers tried in order: the first answer other than
447/// [`Unsupported`](KeyResolutionError::Unsupported) is the answer, an error included.
448///
449/// # Examples
450///
451/// ```
452/// # #[cfg(feature = "did-key")]
453/// # tokio::runtime::Builder::new_current_thread().build()?.block_on(async {
454/// use identity_kit::jose::Jwk;
455/// use identity_kit::methods::DidKey;
456/// use identity_kit::resolver::{CompositeKeyResolver, JwkResolver, KeyReference, KeyResolver as _, KeyRole, Resolver};
457///
458/// let resolver = CompositeKeyResolver::new()
459///     .with(Resolver::builder().with(DidKey::default()).build())
460///     .with(JwkResolver);
461///
462/// // RFC 8037 Appendix A.2.
463/// let key: Jwk = serde_json::from_str(
464///     r#"{"kty":"OKP","crv":"Ed25519","x":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"}"#,
465/// )?;
466/// let resolved = resolver.resolve_key(&KeyReference::Jwk(Box::new(key.clone())), KeyRole::Issuer).await?;
467/// assert_eq!(resolved, key);
468/// # Ok::<(), Box<dyn std::error::Error>>(())
469/// # })?;
470/// # Ok::<(), Box<dyn std::error::Error>>(())
471/// ```
472#[derive(Clone, Default)]
473pub struct CompositeKeyResolver {
474    resolvers: Vec<Arc<dyn KeyResolver>>,
475}
476
477impl CompositeKeyResolver {
478    /// A resolver with nothing to try: every reference is unsupported.
479    pub fn new() -> Self {
480        Self::default()
481    }
482
483    /// This resolver trying `resolver` after the ones added before.
484    #[must_use]
485    pub fn with(mut self, resolver: impl KeyResolver + 'static) -> Self {
486        self.resolvers.push(Arc::new(resolver));
487        self
488    }
489
490    /// This resolver trying the shared `resolver` after the ones added before.
491    #[must_use]
492    pub fn with_shared(mut self, resolver: Arc<dyn KeyResolver>) -> Self {
493        self.resolvers.push(resolver);
494        self
495    }
496
497    /// How many resolvers it tries.
498    pub fn len(&self) -> usize {
499        self.resolvers.len()
500    }
501
502    /// Whether it has no resolver to try.
503    pub fn is_empty(&self) -> bool {
504        self.resolvers.is_empty()
505    }
506}
507
508impl fmt::Debug for CompositeKeyResolver {
509    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
510        f.debug_struct("CompositeKeyResolver")
511            .field("resolvers", &self.resolvers.len())
512            .finish()
513    }
514}
515
516#[async_trait::async_trait]
517impl KeyResolver for CompositeKeyResolver {
518    async fn resolve_key(
519        &self,
520        key: &KeyReference,
521        role: KeyRole,
522    ) -> Result<Jwk, KeyResolutionError> {
523        for resolver in &self.resolvers {
524            match resolver.resolve_key(key, role).await {
525                Err(KeyResolutionError::Unsupported(_)) => {}
526                answer => return answer,
527            }
528        }
529        Err(KeyResolutionError::unsupported(key))
530    }
531}
532
533/// Why a [`KeyResolver`] found no key.
534#[derive(Debug)]
535#[non_exhaustive]
536pub enum KeyResolutionError {
537    /// The resolver does not handle this kind of reference.
538    Unsupported(Box<KeyReference>),
539    /// The resolver handles the reference and could not find or use the key.
540    Failed(Box<dyn Error + Send + Sync + 'static>),
541}
542
543impl KeyResolutionError {
544    /// The resolver does not handle `key`.
545    pub fn unsupported(key: &KeyReference) -> Self {
546        Self::Unsupported(Box::new(key.clone()))
547    }
548
549    /// A failure caused by `error`.
550    pub fn new<E: Error + Send + Sync + 'static>(error: E) -> Self {
551        Self::Failed(Box::new(error))
552    }
553
554    /// A failure described by `message` only.
555    pub fn msg<M: fmt::Display>(message: M) -> Self {
556        Self::Failed(message.to_string().into())
557    }
558
559    /// Whether the resolver does not handle the reference.
560    pub fn is_unsupported(&self) -> bool {
561        matches!(self, Self::Unsupported(_))
562    }
563
564    /// The error that caused a failure.
565    pub fn inner(&self) -> Option<&(dyn Error + Send + Sync + 'static)> {
566        match self {
567            Self::Failed(error) => Some(&**error),
568            Self::Unsupported(_) => None,
569        }
570    }
571}
572
573impl fmt::Display for KeyResolutionError {
574    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
575        match self {
576            Self::Unsupported(reference) => write!(f, "no resolver handles {reference}"),
577            Self::Failed(error) => fmt::Display::fmt(error, f),
578        }
579    }
580}
581
582impl Error for KeyResolutionError {
583    fn source(&self) -> Option<&(dyn Error + 'static)> {
584        match self {
585            Self::Failed(error) => error.source(),
586            Self::Unsupported(_) => None,
587        }
588    }
589}
590
591impl From<DereferenceError> for KeyResolutionError {
592    fn from(error: DereferenceError) -> Self {
593        Self::new(error)
594    }
595}