Skip to main content

huskarl_core/crypto/verifier/
multi.rs

1use std::sync::Arc;
2
3use futures_util::future::join_all;
4
5use crate::{
6    crypto::{
7        KeyMatchStrength,
8        verifier::{CreateVerifierError, JwsVerifier, JwsVerifierPlatform, KeyMatch, VerifyError},
9    },
10    error::{Error, ErrorKind},
11    jwk::PublicJwks,
12    platform::MaybeSendBoxFuture,
13};
14
15/// A [`JwsVerifier`] that holds multiple keys and applies RFC 7517 key selection semantics.
16///
17/// Key selection follows [`KeyMatchStrength`] priority:
18/// - A [`ByKeyId`](KeyMatchStrength::ByKeyId) match (algorithm + kid) is definitive — that
19///   key is used exclusively.
20/// - Multiple [`ByAlgorithm`](KeyMatchStrength::ByAlgorithm) matches are ambiguous; returns
21///   [`AmbiguousKeyMatch`](VerifyError::AmbiguousKeyMatch) unless
22///   [`try_all_on_ambiguous_match`](Self::try_all_on_ambiguous_match) is set, in which case
23///   each candidate is tried in order.
24/// - A single `ByAlgorithm` match is used directly.
25///
26/// For a JWKS-backed verifier with automatic refresh and retry wrapped around
27/// this type, use [`JwksSource`](crate::jwk::JwksSource); construct
28/// `MultiKeyVerifier` directly only for a custom stack.
29#[derive(Debug)]
30pub struct MultiKeyVerifier {
31    verifiers: Vec<Arc<dyn JwsVerifier>>,
32    try_all_on_ambiguous_match: bool,
33}
34
35enum GetVerifierResult {
36    ByKeyId(Arc<dyn JwsVerifier>),
37    ByAlgorithm(Vec<Arc<dyn JwsVerifier>>),
38    None,
39}
40
41impl GetVerifierResult {
42    fn key_match_strength(&self) -> Option<KeyMatchStrength> {
43        match self {
44            Self::ByKeyId(_) => Some(KeyMatchStrength::ByKeyId),
45            Self::ByAlgorithm(_) => Some(KeyMatchStrength::ByAlgorithm),
46            Self::None => None,
47        }
48    }
49}
50
51impl MultiKeyVerifier {
52    /// Creates a `MultiKeyVerifier` from an explicit list of verifiers.
53    #[must_use]
54    pub fn new(verifiers: Vec<Arc<dyn JwsVerifier>>) -> Self {
55        Self {
56            verifiers,
57            try_all_on_ambiguous_match: false,
58        }
59    }
60
61    /// Builds a `MultiKeyVerifier` from a JWKS document.
62    ///
63    /// Keys with unsupported algorithms are silently skipped.
64    ///
65    /// This is a general-purpose constructor with no key-count limit: it may be
66    /// fed from a trusted source (a KMS, an enclave, a local file) as well as a
67    /// remote JWKS. Bounding the key count of an untrusted, remotely-fetched
68    /// document is the fetcher's job — see [`JwksSource`](crate::jwk::JwksSource).
69    ///
70    /// # Errors
71    ///
72    /// Returns [`ErrorKind::Crypto`] if a supported key fails to construct a
73    /// verifier.
74    pub async fn from_jwks(
75        jwks: &PublicJwks,
76        platform: &dyn JwsVerifierPlatform,
77    ) -> Result<Self, Error> {
78        let verifiers: Vec<Arc<dyn JwsVerifier>> = join_all(
79            jwks.keys
80                .iter()
81                .map(|jwk| platform.create_verifier_from_jwk(jwk.clone())),
82        )
83        .await
84        .into_iter()
85        .filter_map(|result| match result {
86            Ok(v) => Some(Ok(v)),
87            Err(CreateVerifierError::UnsupportedKey) => None,
88            Err(e) => Some(Err(e)),
89        })
90        .collect::<Result<_, _>>()
91        .map_err(|source| {
92            Error::new(ErrorKind::Crypto, source).with_context("creating verifier from JWK")
93        })?;
94
95        Ok(Self {
96            verifiers,
97            try_all_on_ambiguous_match: false,
98        })
99    }
100
101    /// Configures whether to try all matching keys when no `kid` is present and multiple
102    /// keys match by algorithm.
103    ///
104    /// When `false` (the default), multiple algorithm matches without a `kid` return
105    /// [`AmbiguousKeyMatch`](VerifyError::AmbiguousKeyMatch). When `true`, each candidate
106    /// is tried in order and the first success is accepted.
107    #[must_use]
108    pub fn try_all_on_ambiguous_match(mut self, value: bool) -> Self {
109        self.try_all_on_ambiguous_match = value;
110        self
111    }
112
113    async fn dispatch_verify(
114        &self,
115        input: &[u8],
116        signature: &[u8],
117        key_match: &KeyMatch<'_>,
118    ) -> Result<(), VerifyError> {
119        let by_algorithm_verifiers = match self.get_verifier(key_match) {
120            GetVerifierResult::ByKeyId(verifier) => {
121                return verifier.verify(input, signature, key_match).await;
122            }
123            GetVerifierResult::ByAlgorithm(verifiers) => verifiers,
124            GetVerifierResult::None => return Err(VerifyError::NoMatchingKey),
125        };
126
127        if by_algorithm_verifiers.len() > 1 && !self.try_all_on_ambiguous_match {
128            return Err(VerifyError::AmbiguousKeyMatch);
129        }
130
131        let mut last_retryable = None;
132        let mut last_non_retryable = None;
133        for verifier in by_algorithm_verifiers {
134            match verifier.verify(input, signature, key_match).await {
135                Ok(()) => return Ok(()),
136                // NoMatchingKey means the verifier didn't attempt verification —
137                // it is the implicit fallback, not a result to prefer over others.
138                Err(VerifyError::NoMatchingKey) => {}
139                Err(e) => {
140                    if e.is_retryable() {
141                        last_retryable = Some(e);
142                    } else {
143                        last_non_retryable = Some(e);
144                    }
145                }
146            }
147        }
148
149        Err(last_non_retryable
150            .or(last_retryable)
151            .unwrap_or(VerifyError::NoMatchingKey))
152    }
153
154    fn get_verifier(&self, key_match: &KeyMatch) -> GetVerifierResult {
155        let mut by_algorithm_verifiers: Vec<Arc<dyn JwsVerifier>> = Vec::new();
156
157        for verifier in &self.verifiers {
158            match verifier.key_match(key_match) {
159                Some(KeyMatchStrength::ByKeyId) => {
160                    return GetVerifierResult::ByKeyId(verifier.clone());
161                }
162                Some(KeyMatchStrength::ByAlgorithm) => {
163                    by_algorithm_verifiers.push(verifier.clone());
164                }
165                None => {}
166            }
167        }
168
169        if by_algorithm_verifiers.is_empty() {
170            GetVerifierResult::None
171        } else {
172            GetVerifierResult::ByAlgorithm(by_algorithm_verifiers)
173        }
174    }
175}
176
177impl JwsVerifier for MultiKeyVerifier {
178    fn key_match(&self, key_match: &KeyMatch<'_>) -> Option<KeyMatchStrength> {
179        self.get_verifier(key_match).key_match_strength()
180    }
181
182    fn verify<'a>(
183        &'a self,
184        input: &'a [u8],
185        signature: &'a [u8],
186        key_match: &'a KeyMatch<'a>,
187    ) -> MaybeSendBoxFuture<'a, Result<(), VerifyError>> {
188        Box::pin(self.dispatch_verify(input, signature, key_match))
189    }
190
191    fn try_refresh(&self) -> MaybeSendBoxFuture<'_, bool> {
192        Box::pin(async move {
193            join_all(self.verifiers.iter().map(JwsVerifier::try_refresh))
194                .await
195                .into_iter()
196                .any(|b| b)
197        })
198    }
199}