openidconnect/types/
jwks.rs

1use crate::http_utils::{check_content_type, MIME_TYPE_JSON, MIME_TYPE_JWKS};
2use crate::types::jwk::{JsonWebKey, JsonWebKeyId, JwsSigningAlgorithm};
3use crate::{
4    AsyncHttpClient, DiscoveryError, HttpRequest, HttpResponse, JsonWebKeyUse, SyncHttpClient,
5};
6
7use http::header::ACCEPT;
8use http::{HeaderValue, Method, StatusCode};
9use serde::{Deserialize, Serialize};
10use serde_with::{serde_as, VecSkipError};
11
12use std::future::Future;
13
14new_url_type![
15    /// JSON Web Key Set URL.
16    JsonWebKeySetUrl
17];
18
19/// JSON Web Key Set.
20#[serde_as]
21#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
22pub struct JsonWebKeySet<K>
23where
24    K: JsonWebKey,
25{
26    // FIXME: write a test that ensures duplicate object member names cause an error
27    // (see https://tools.ietf.org/html/rfc7517#section-5)
28    #[serde(bound = "K: JsonWebKey")]
29    // Ignores invalid keys rather than failing. That way, clients can function using the keys that
30    // they do understand, which is fine if they only ever get JWTs signed with those keys.
31    #[serde_as(as = "VecSkipError<_>")]
32    keys: Vec<K>,
33}
34
35/// Checks whether a JWK key can be used with a given signing algorithm.
36pub(crate) fn check_key_compatibility<K>(
37    key: &K,
38    signing_algorithm: &K::SigningAlgorithm,
39) -> Result<(), &'static str>
40where
41    K: JsonWebKey,
42{
43    // if this key isn't suitable for signing
44    if let Some(use_) = key.key_use() {
45        if !use_.allows_signature() {
46            return Err("key usage not permitted for digital signatures");
47        }
48    }
49
50    // if this key doesn't have the right key type
51    if signing_algorithm.key_type().as_ref() != Some(key.key_type()) {
52        return Err("key type does not match signature algorithm");
53    }
54
55    match key.signing_alg() {
56        // if no specific algorithm is mandated, any will do
57        crate::JsonWebKeyAlgorithm::Unspecified => Ok(()),
58        crate::JsonWebKeyAlgorithm::Unsupported => Err("key algorithm is not a signing algorithm"),
59        crate::JsonWebKeyAlgorithm::Algorithm(key_alg) if key_alg == signing_algorithm => Ok(()),
60        crate::JsonWebKeyAlgorithm::Algorithm(_) => Err("incompatible key algorithm"),
61    }
62}
63
64impl<K> JsonWebKeySet<K>
65where
66    K: JsonWebKey,
67{
68    /// Create a new JSON Web Key Set.
69    pub fn new(keys: Vec<K>) -> Self {
70        Self { keys }
71    }
72
73    /// Return a list of suitable keys, given a key ID and signature algorithm
74    pub(crate) fn filter_keys(
75        &self,
76        key_id: Option<&JsonWebKeyId>,
77        signature_alg: &K::SigningAlgorithm,
78    ) -> Vec<&K> {
79        self.keys()
80        .iter()
81        .filter(|key|
82            // Either the JWT doesn't include a 'kid' (in which case any 'kid'
83            // is acceptable), or the 'kid' matches the key's ID.
84            if key_id.is_some() && key_id != key.key_id() {
85                false
86            } else {
87                check_key_compatibility(*key, signature_alg).is_ok()
88            }
89        )
90        .collect()
91    }
92
93    /// Fetch a remote JSON Web Key Set from the specified `url` using the given `http_client`
94    /// (e.g., [`reqwest::blocking::Client`](crate::reqwest::blocking::Client) or
95    /// [`CurlHttpClient`](crate::CurlHttpClient)).
96    pub fn fetch<C>(
97        url: &JsonWebKeySetUrl,
98        http_client: &C,
99    ) -> Result<Self, DiscoveryError<<C as SyncHttpClient>::Error>>
100    where
101        C: SyncHttpClient,
102    {
103        http_client
104            .call(Self::fetch_request(url).map_err(|err| {
105                DiscoveryError::Other(format!("failed to prepare request: {err}"))
106            })?)
107            .map_err(DiscoveryError::Request)
108            .and_then(Self::fetch_response)
109    }
110
111    /// Fetch a remote JSON Web Key Set from the specified `url` using the given async `http_client`
112    /// (e.g., [`reqwest::Client`](crate::reqwest::Client)).
113    pub fn fetch_async<'c, C>(
114        url: &JsonWebKeySetUrl,
115        http_client: &'c C,
116    ) -> impl Future<Output = Result<Self, DiscoveryError<<C as AsyncHttpClient<'c>>::Error>>> + 'c
117    where
118        Self: 'c,
119        C: AsyncHttpClient<'c>,
120    {
121        let fetch_request = Self::fetch_request(url)
122            .map_err(|err| DiscoveryError::Other(format!("failed to prepare request: {err}")));
123        Box::pin(async move {
124            http_client
125                .call(fetch_request?)
126                .await
127                .map_err(DiscoveryError::Request)
128                .and_then(Self::fetch_response)
129        })
130    }
131
132    fn fetch_request(url: &JsonWebKeySetUrl) -> Result<HttpRequest, http::Error> {
133        http::Request::builder()
134            .uri(url.to_string())
135            .method(Method::GET)
136            .header(ACCEPT, HeaderValue::from_static(MIME_TYPE_JSON))
137            .body(Vec::new())
138    }
139
140    fn fetch_response<RE>(http_response: HttpResponse) -> Result<Self, DiscoveryError<RE>>
141    where
142        RE: std::error::Error + 'static,
143    {
144        if http_response.status() != StatusCode::OK {
145            return Err(DiscoveryError::Response(
146                http_response.status(),
147                http_response.body().to_owned(),
148                format!("HTTP status code {}", http_response.status()),
149            ));
150        }
151
152        check_content_type(http_response.headers(), MIME_TYPE_JSON)
153            .or_else(|err| {
154                check_content_type(http_response.headers(), MIME_TYPE_JWKS).map_err(|_| err)
155            })
156            .map_err(|err_msg| {
157                DiscoveryError::Response(
158                    http_response.status(),
159                    http_response.body().to_owned(),
160                    err_msg,
161                )
162            })?;
163
164        serde_path_to_error::deserialize(&mut serde_json::Deserializer::from_slice(
165            http_response.body(),
166        ))
167        .map_err(DiscoveryError::Parse)
168    }
169
170    /// Return the keys in this JSON Web Key Set.
171    pub fn keys(&self) -> &Vec<K> {
172        &self.keys
173    }
174}
175impl<K> Clone for JsonWebKeySet<K>
176where
177    K: JsonWebKey,
178{
179    fn clone(&self) -> Self {
180        Self::new(self.keys.clone())
181    }
182}
183impl<K> Default for JsonWebKeySet<K>
184where
185    K: JsonWebKey,
186{
187    fn default() -> Self {
188        Self::new(Vec::new())
189    }
190}