mas_jose/jwk/
mod.rs

1// Copyright 2022 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Ref: <https://www.rfc-editor.org/rfc/rfc7517.html>
16
17use mas_iana::jose::{
18    JsonWebKeyEcEllipticCurve, JsonWebKeyOperation, JsonWebKeyType, JsonWebKeyUse,
19    JsonWebSignatureAlg,
20};
21use schemars::JsonSchema;
22use serde::{Deserialize, Serialize};
23use serde_with::skip_serializing_none;
24use url::Url;
25
26use crate::{
27    base64::{Base64, Base64UrlNoPad},
28    constraints::{Constrainable, Constraint, ConstraintSet},
29};
30
31pub(crate) mod private_parameters;
32pub(crate) mod public_parameters;
33
34use self::private_parameters::SymetricKeyError;
35pub use self::{
36    private_parameters::JsonWebKeyPrivateParameters, public_parameters::JsonWebKeyPublicParameters,
37};
38
39pub trait ParametersInfo {
40    fn kty(&self) -> JsonWebKeyType;
41    fn possible_algs(&self) -> &[JsonWebSignatureAlg];
42}
43
44/// An utilitary trait to figure out the [`JsonWebKeyEcEllipticCurve`] value for
45/// elliptic curves
46trait JwkEcCurve {
47    const CRV: JsonWebKeyEcEllipticCurve;
48}
49
50impl JwkEcCurve for p256::NistP256 {
51    const CRV: JsonWebKeyEcEllipticCurve = JsonWebKeyEcEllipticCurve::P256;
52}
53
54impl JwkEcCurve for p384::NistP384 {
55    const CRV: JsonWebKeyEcEllipticCurve = JsonWebKeyEcEllipticCurve::P384;
56}
57
58impl JwkEcCurve for k256::Secp256k1 {
59    const CRV: JsonWebKeyEcEllipticCurve = JsonWebKeyEcEllipticCurve::Secp256K1;
60}
61
62#[skip_serializing_none]
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
64pub struct JsonWebKey<P> {
65    #[serde(flatten)]
66    parameters: P,
67
68    #[serde(default)]
69    r#use: Option<JsonWebKeyUse>,
70
71    #[serde(default)]
72    key_ops: Option<Vec<JsonWebKeyOperation>>,
73
74    #[serde(default)]
75    alg: Option<JsonWebSignatureAlg>,
76
77    #[serde(default)]
78    kid: Option<String>,
79
80    #[schemars(with = "Option<String>")]
81    #[serde(default)]
82    x5u: Option<Url>,
83
84    #[schemars(with = "Vec<String>")]
85    #[serde(default)]
86    x5c: Option<Vec<Base64>>,
87
88    #[schemars(with = "Option<String>")]
89    #[serde(default)]
90    x5t: Option<Base64UrlNoPad>,
91
92    #[schemars(with = "Option<String>")]
93    #[serde(default, rename = "x5t#S256")]
94    x5t_s256: Option<Base64UrlNoPad>,
95}
96
97pub type PublicJsonWebKey = JsonWebKey<self::public_parameters::JsonWebKeyPublicParameters>;
98pub type PrivateJsonWebKey = JsonWebKey<self::private_parameters::JsonWebKeyPrivateParameters>;
99
100impl TryFrom<PrivateJsonWebKey> for PublicJsonWebKey {
101    type Error = SymetricKeyError;
102
103    fn try_from(value: PrivateJsonWebKey) -> Result<Self, Self::Error> {
104        value.try_map(JsonWebKeyPublicParameters::try_from)
105    }
106}
107
108impl<P> JsonWebKey<P> {
109    /// Create a new [`JsonWebKey`] with the given parameters.
110    #[must_use]
111    pub const fn new(parameters: P) -> Self {
112        Self {
113            parameters,
114            r#use: None,
115            key_ops: None,
116            alg: None,
117            kid: None,
118            x5u: None,
119            x5c: None,
120            x5t: None,
121            x5t_s256: None,
122        }
123    }
124
125    /// Map the parameters of this [`JsonWebKey`] to a new type, with a fallible
126    /// mapper, consuming the original key.
127    ///
128    /// # Errors
129    ///
130    /// Returns an error if the mapper returns an error.
131    pub fn try_map<M, O, E>(self, mapper: M) -> Result<JsonWebKey<O>, E>
132    where
133        M: FnOnce(P) -> Result<O, E>,
134    {
135        Ok(JsonWebKey {
136            parameters: mapper(self.parameters)?,
137            r#use: self.r#use,
138            key_ops: self.key_ops,
139            alg: self.alg,
140            kid: self.kid,
141            x5u: self.x5u,
142            x5c: self.x5c,
143            x5t: self.x5t,
144            x5t_s256: self.x5t_s256,
145        })
146    }
147
148    /// Map the parameters of this [`JsonWebKey`] to a new type, consuming the
149    /// original key.
150    pub fn map<M, O>(self, mapper: M) -> JsonWebKey<O>
151    where
152        M: FnOnce(P) -> O,
153    {
154        JsonWebKey {
155            parameters: mapper(self.parameters),
156            r#use: self.r#use,
157            key_ops: self.key_ops,
158            alg: self.alg,
159            kid: self.kid,
160            x5u: self.x5u,
161            x5c: self.x5c,
162            x5t: self.x5t,
163            x5t_s256: self.x5t_s256,
164        }
165    }
166
167    /// Map the parameters of this [`JsonWebKey`] to a new type, with a fallible
168    /// mapper, cloning the other fields.
169    ///
170    /// # Errors
171    ///
172    /// Returns an error if the mapper returns an error.
173    pub fn try_cloned_map<M, O, E>(&self, mapper: M) -> Result<JsonWebKey<O>, E>
174    where
175        M: FnOnce(&P) -> Result<O, E>,
176    {
177        Ok(JsonWebKey {
178            parameters: mapper(&self.parameters)?,
179            r#use: self.r#use.clone(),
180            key_ops: self.key_ops.clone(),
181            alg: self.alg.clone(),
182            kid: self.kid.clone(),
183            x5u: self.x5u.clone(),
184            x5c: self.x5c.clone(),
185            x5t: self.x5t.clone(),
186            x5t_s256: self.x5t_s256.clone(),
187        })
188    }
189
190    /// Map the parameters of this [`JsonWebKey`] to a new type, cloning the
191    /// other fields.
192    pub fn cloned_map<M, O>(&self, mapper: M) -> JsonWebKey<O>
193    where
194        M: FnOnce(&P) -> O,
195    {
196        JsonWebKey {
197            parameters: mapper(&self.parameters),
198            r#use: self.r#use.clone(),
199            key_ops: self.key_ops.clone(),
200            alg: self.alg.clone(),
201            kid: self.kid.clone(),
202            x5u: self.x5u.clone(),
203            x5c: self.x5c.clone(),
204            x5t: self.x5t.clone(),
205            x5t_s256: self.x5t_s256.clone(),
206        }
207    }
208
209    /// Set the `use` field of this [`JsonWebKey`].
210    #[must_use]
211    pub fn with_use(mut self, value: JsonWebKeyUse) -> Self {
212        self.r#use = Some(value);
213        self
214    }
215
216    /// Set the `key_ops` field of this [`JsonWebKey`].
217    #[must_use]
218    pub fn with_key_ops(mut self, key_ops: Vec<JsonWebKeyOperation>) -> Self {
219        self.key_ops = Some(key_ops);
220        self
221    }
222
223    /// Set the `alg` field of this [`JsonWebKey`].
224    #[must_use]
225    pub fn with_alg(mut self, alg: JsonWebSignatureAlg) -> Self {
226        self.alg = Some(alg);
227        self
228    }
229
230    /// Set the `kid` field of this [`JsonWebKey`].
231    #[must_use]
232    pub fn with_kid(mut self, kid: impl Into<String>) -> Self {
233        self.kid = Some(kid.into());
234        self
235    }
236
237    /// Get the `kid` field of this [`JsonWebKey`], if set.
238    #[must_use]
239    pub const fn alg(&self) -> Option<&JsonWebSignatureAlg> {
240        self.alg.as_ref()
241    }
242
243    /// Get the inner parameters of this [`JsonWebKey`].
244    #[must_use]
245    pub const fn params(&self) -> &P {
246        &self.parameters
247    }
248}
249
250impl<P> Constrainable for JsonWebKey<P>
251where
252    P: ParametersInfo,
253{
254    fn kid(&self) -> Option<&str> {
255        self.kid.as_deref()
256    }
257
258    fn kty(&self) -> JsonWebKeyType {
259        self.parameters.kty()
260    }
261
262    fn algs(&self) -> &[JsonWebSignatureAlg] {
263        self.parameters.possible_algs()
264    }
265
266    fn alg(&self) -> Option<&JsonWebSignatureAlg> {
267        self.alg.as_ref()
268    }
269
270    fn use_(&self) -> Option<&JsonWebKeyUse> {
271        self.r#use.as_ref()
272    }
273}
274
275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
276pub struct JsonWebKeySet<P> {
277    keys: Vec<JsonWebKey<P>>,
278}
279
280impl<P> Default for JsonWebKeySet<P> {
281    fn default() -> Self {
282        Self {
283            keys: Vec::default(),
284        }
285    }
286}
287
288pub type PublicJsonWebKeySet = JsonWebKeySet<self::public_parameters::JsonWebKeyPublicParameters>;
289pub type PrivateJsonWebKeySet =
290    JsonWebKeySet<self::private_parameters::JsonWebKeyPrivateParameters>;
291
292impl From<PrivateJsonWebKeySet> for PublicJsonWebKeySet {
293    fn from(value: PrivateJsonWebKeySet) -> Self {
294        let keys = value
295            .keys
296            .into_iter()
297            .filter_map(|key: PrivateJsonWebKey| key.try_into().ok())
298            .collect();
299        Self { keys }
300    }
301}
302
303impl<P> std::ops::Deref for JsonWebKeySet<P> {
304    type Target = Vec<JsonWebKey<P>>;
305
306    fn deref(&self) -> &Self::Target {
307        &self.keys
308    }
309}
310
311impl<P> JsonWebKeySet<P> {
312    #[must_use]
313    pub fn new(keys: Vec<JsonWebKey<P>>) -> Self {
314        Self { keys }
315    }
316
317    /// Find the best key given the constraints
318    #[must_use]
319    pub fn find_key(&self, constraints: &ConstraintSet) -> Option<&JsonWebKey<P>>
320    where
321        P: ParametersInfo,
322    {
323        constraints.filter(&self.keys).pop()
324    }
325
326    /// Find the list of keys which match the given constraints
327    #[must_use]
328    pub fn find_keys(&self, constraints: &ConstraintSet) -> Vec<&JsonWebKey<P>>
329    where
330        P: ParametersInfo,
331    {
332        constraints.filter(&self.keys)
333    }
334
335    /// Find a key for the given algorithm. Returns `None` if no suitable key
336    /// was found.
337    #[must_use]
338    pub fn signing_key_for_algorithm(&self, alg: &JsonWebSignatureAlg) -> Option<&JsonWebKey<P>>
339    where
340        P: ParametersInfo,
341    {
342        let constraints = ConstraintSet::new([
343            Constraint::alg(alg),
344            Constraint::use_(&mas_iana::jose::JsonWebKeyUse::Sig),
345        ]);
346        self.find_key(&constraints)
347    }
348
349    /// Get a list of available signing algorithms for this [`JsonWebKeySet`]
350    #[must_use]
351    pub fn available_signing_algorithms(&self) -> Vec<JsonWebSignatureAlg>
352    where
353        P: ParametersInfo,
354    {
355        let mut algs: Vec<_> = self
356            .keys
357            .iter()
358            .flat_map(|key| key.params().possible_algs())
359            .cloned()
360            .collect();
361        algs.sort();
362        algs.dedup();
363        algs
364    }
365}
366
367impl<P> FromIterator<JsonWebKey<P>> for JsonWebKeySet<P> {
368    fn from_iter<T: IntoIterator<Item = JsonWebKey<P>>>(iter: T) -> Self {
369        let keys = iter.into_iter().collect();
370        Self { keys }
371    }
372}
373
374#[cfg(test)]
375mod tests {
376    use super::*;
377    use crate::constraints::ConstraintSet;
378
379    #[test]
380    fn load_google_keys() {
381        let jwks = serde_json::json!({
382          "keys": [
383            {
384              "alg": "RS256",
385              "kty": "RSA",
386              "n": "tCwhHOxX_ylh5kVwfVqW7QIBTIsPjkjCjVCppDrynuF_3msEdtEaG64eJUz84ODFNMCC0BQ57G7wrKQVWkdSDxWUEqGk2BixBiHJRWZdofz1WOBTdPVicvHW5Zl_aIt7uXWMdOp_SODw-O2y2f05EqbFWFnR2-1y9K8KbiOp82CD72ny1Jbb_3PxTs2Z0F4ECAtTzpDteaJtjeeueRjr7040JAjQ-5fpL5D1g8x14LJyVIo-FL_y94NPFbMp7UCi69CIfVHXFO8WYFz949og-47mWRrID5lS4zpx-QLuvNhUb_lSqmylUdQB3HpRdOcYdj3xwy4MHJuu7tTaf0AmCQ",
387              "use": "sig",
388              "kid": "d98f49bc6ca4581eae8dfadd494fce10ea23aab0",
389              "e": "AQAB"
390            },
391            {
392              "use": "sig",
393              "kty": "RSA",
394              "kid": "03e84aed4ef4431014e8617567864c4efaaaede9",
395              "n": "ma2uRyBeSEOatGuDpCiV9oIxlDWix_KypDYuhQfEzqi_BiF4fV266OWfyjcABbam59aJMNvOnKW3u_eZM-PhMCBij5MZ-vcBJ4GfxDJeKSn-GP_dJ09rpDcILh8HaWAnPmMoi4DC0nrfE241wPISvZaaZnGHkOrfN_EnA5DligLgVUbrA5rJhQ1aSEQO_gf1raEOW3DZ_ACU3qhtgO0ZBG3a5h7BPiRs2sXqb2UCmBBgwyvYLDebnpE7AotF6_xBIlR-Cykdap3GHVMXhrIpvU195HF30ZoBU4dMd-AeG6HgRt4Cqy1moGoDgMQfbmQ48Hlunv9_Vi2e2CLvYECcBw",
396              "e": "AQAB",
397              "alg": "RS256"
398            }
399          ]
400        });
401
402        let jwks: PublicJsonWebKeySet = serde_json::from_value(jwks).unwrap();
403        // Both keys are RSA public keys
404        for jwk in &jwks.keys {
405            let p = jwk.params().rsa().expect("an RSA key");
406            rsa::RsaPublicKey::try_from(p).unwrap();
407        }
408
409        let constraints = ConstraintSet::default()
410            .use_(&JsonWebKeyUse::Sig)
411            .kty(&JsonWebKeyType::Rsa)
412            .alg(&JsonWebSignatureAlg::Rs256);
413        let candidates = constraints.filter(&jwks.keys);
414        assert_eq!(candidates.len(), 2);
415
416        let constraints = ConstraintSet::default()
417            .use_(&JsonWebKeyUse::Sig)
418            .kty(&JsonWebKeyType::Rsa)
419            .kid("03e84aed4ef4431014e8617567864c4efaaaede9");
420        let candidates = constraints.filter(&jwks.keys);
421        assert_eq!(candidates.len(), 1);
422    }
423
424    #[allow(clippy::too_many_lines)]
425    #[test]
426    fn load_keycloak_keys() {
427        let jwks = serde_json::json!({
428          "keys": [
429            {
430              "kid": "SuGUPE9Sr-1Gha2NLse33r5NQu3XoS_I3Qds3bcmfQE",
431              "kty": "RSA",
432              "alg": "RS256",
433              "use": "sig",
434              "n": "j21ih2m1RPeTXtIPFas2ZclhW8v2RitLdXJTqOFviWonaSObUWNZUkVvIdDKDyJhU7caGPnz52zXX1Trhbbq1uoCalAuIPw9UgJUJhUhlH7lqaRtYdbOrOzXZ7kVsApe1OdlezgShnyMhW5ChEJXQrCkR_LktBJQ8-6ZBNLHx3ps-pQrpXky_XdYZM_I_f1R8z36gnXagklAMMNKciFRURBMAsPbOgaly-slEDdVcuNtcoccSYdo9kRS5wjQlK6LZ3lniJrLRkUMvN6ZQcMLUWMDpghH5bdbhaaOb28HQWwpRDEBIMIH9Fi9aiKxwHa5YAqW1yetOq_9XXyYiuP9G6hZozSnkkfAOzYFqfr92vIPHddVVUUVLvH8UL4u1o553uVtOExA_pJVRghfO0IPZhJ6rUaZR7krvUMdCYngGznuD_V2-TAL9Nu8YXHIrZSU4WBKIvQC2HDOogSjj5dNDBUuAmOhI2OjuLjiOXpRPlaGcMIIlLALwQ76gFTEhTDlRXar7oLU8wj1KHLkc6d__lwdBkR-2Fr4dAewW4bHVFsPeDSM_vJZpK0XACrNgrrNBax48_hOlK9YfzSopyVCHwewxmC743eNYWEhE9LY-cc3ZGK9tHXgQG2l1tOZ_JK9wo1HsIuu3gdl2SV3ZOs6Ggi812GMfrgijnthC7e4Mv8",
435              "e": "AQAB",
436              "x5c": [
437                "MIIElTCCAn0CBgF95wE6HzANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDDANkZXYwHhcNMjExMjIzMTExNDE3WhcNMzExMjIzMTExNTU3WjAOMQwwCgYDVQQDDANkZXYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCPbWKHabVE95Ne0g8VqzZlyWFby/ZGK0t1clOo4W+JaidpI5tRY1lSRW8h0MoPImFTtxoY+fPnbNdfVOuFturW6gJqUC4g/D1SAlQmFSGUfuWppG1h1s6s7NdnuRWwCl7U52V7OBKGfIyFbkKEQldCsKRH8uS0ElDz7pkE0sfHemz6lCuleTL9d1hkz8j9/VHzPfqCddqCSUAww0pyIVFREEwCw9s6BqXL6yUQN1Vy421yhxxJh2j2RFLnCNCUrotneWeImstGRQy83plBwwtRYwOmCEflt1uFpo5vbwdBbClEMQEgwgf0WL1qIrHAdrlgCpbXJ606r/1dfJiK4/0bqFmjNKeSR8A7NgWp+v3a8g8d11VVRRUu8fxQvi7Wjnne5W04TED+klVGCF87Qg9mEnqtRplHuSu9Qx0JieAbOe4P9Xb5MAv027xhccitlJThYEoi9ALYcM6iBKOPl00MFS4CY6EjY6O4uOI5elE+VoZwwgiUsAvBDvqAVMSFMOVFdqvugtTzCPUocuRzp3/+XB0GRH7YWvh0B7BbhsdUWw94NIz+8lmkrRcAKs2Cus0FrHjz+E6Ur1h/NKinJUIfB7DGYLvjd41hYSET0tj5xzdkYr20deBAbaXW05n8kr3CjUewi67eB2XZJXdk6zoaCLzXYYx+uCKOe2ELt7gy/wIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQB+mzE9ZA/hX/GAM74ZXs+ZEjV+qzUsGNpHkXyzdRc1ic28Go5ujAIMxwwsJ4PUSmw6MjPpCKV3kSXoyc7kUDZ/NQ7gwanP4DN8wDq7GLGqT3QRzMLfVy+el2Vjwd3Q6BhXNAK/jPzv0DFu1GG4WCpc1PcM8p/zWkbKWf9u4nBl7RBsMddn7KLxV+D2Y2eGshZ81YVaJiKF9y+gpgyxBOOsTFITu8SxBpXSwBIP4jTv7NllicxI8G9mk87XX3DdA+NHPKsKj35RbDAXyMid8tMl4R3IQ34F3ADuquHpdAdfTNDSm5lwilyWjV35O+8mKA2n/3LAhfCNgxMU0m9Jm8kI/pu9qTXnIx+HMr8IsAMseGxl+dZ/jJjGGPw1VZhHhU78dN+DZlUSKOVjOSQF+8CGuCxMnOx7+leGafs6G6LtsF/vQvJBTB9DRlM3ag0hQRT2ZEXPWSvcz3ARXqWyaHTzhR4F/+rRX1CyBsCdG3b3iicjGp7EPeaqXEki1K3SNwwv1byeJfqP785auswpojpUYfp/J850VAfA4xuVvxK3xuJrvbpS4DR6JQPY0fs6g8JEDahYa6rSB8H9toLC2r92gerqcGFpEU8uHRHxm9QZjIyFh78LWqpfegz0HMjYqaULgZJxqqZH2sVIu+nPuKC7tIjYWtODR0A13Ar3lH8aZg=="
438              ],
439              "x5t": "fvgfH2gggONL7t4ZTvOdBpI94kM",
440              "x5t#S256": "uwHwO2crQ74jak2bmAeAt_4nrqGDQoElaiVvOlSGOOw"
441            },
442            {
443              "kid": "7pW7bkOM27LQ-KJGHzT1dt3yBmhcj20xj7A-itsuY6U",
444              "kty": "RSA",
445              "alg": "RS384",
446              "use": "sig",
447              "n": "lI1actdwWsMY8BpY68x8No7fwokLTTcZ8-qpqF9CDwX40X70ql9JPqTpLAHp7H7byfO-8VqZVKYKdzFCLjaEqs6Vx6YYuu4BsM2RIDI2CmClngUE5RMXnaEj8XP-h8Q4FnGcXL47n2UNr9mbZSp85W0TWOLtMczuqwwJ2jcYkDFtvLY0UirioKzN5Vr29WdDiCm9i4jHvHE7W41LFCOFLOLxGOq9wLVRNRMRcC3YS6WlrfiMFkPQIGxzFH2OiW2iR9x8QHmxqrqdfidmFsosgG5_2tbX3Q5PnHjYTNHh-iY4uIQ6bsBj1Enoj5h5kudwtgHDyn9OAiljTqLMXsoK9KEZrjE8zPnxQtvfXLCby2CI69X5JZ2lQJCch4cn1eIxn-jJ9Z0aE9EML1Bfp6w5sKELXt1aRtu5HQ5IQ__y2sBJd91NdiBxAzCK5kZjhRIRtt57J5ZHTLsBeHvr2L7SwZ_FojrQly7mI5PMGthZoGoVAr-bJcInzICpcsLKWdW-C6jxhXwRtnJOuTizEOr33vnLohMlmJUZiomYnKv8MEFAmihK5GAHTJ-4QIUuUeC13Dl5aRJacxvoKfgR_zw9P6HCUb7Nq7uzN3oqUdmDYYng1OFVo-1liYuCLbH6ep5LTmAstQY3IjkIFKeY-tvSPdpC9y1TwaHqEktXckvRGx0",
448              "e": "AQAB",
449              "x5c": [
450                "MIIElTCCAn0CBgF95wGjLjANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDDANkZXYwHhcNMjExMjIzMTExNDQzWhcNMzExMjIzMTExNjIzWjAOMQwwCgYDVQQDDANkZXYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCUjVpy13BawxjwGljrzHw2jt/CiQtNNxnz6qmoX0IPBfjRfvSqX0k+pOksAensftvJ877xWplUpgp3MUIuNoSqzpXHphi67gGwzZEgMjYKYKWeBQTlExedoSPxc/6HxDgWcZxcvjufZQ2v2ZtlKnzlbRNY4u0xzO6rDAnaNxiQMW28tjRSKuKgrM3lWvb1Z0OIKb2LiMe8cTtbjUsUI4Us4vEY6r3AtVE1ExFwLdhLpaWt+IwWQ9AgbHMUfY6JbaJH3HxAebGqup1+J2YWyiyAbn/a1tfdDk+ceNhM0eH6Jji4hDpuwGPUSeiPmHmS53C2AcPKf04CKWNOosxeygr0oRmuMTzM+fFC299csJvLYIjr1fklnaVAkJyHhyfV4jGf6Mn1nRoT0QwvUF+nrDmwoQte3VpG27kdDkhD//LawEl33U12IHEDMIrmRmOFEhG23nsnlkdMuwF4e+vYvtLBn8WiOtCXLuYjk8wa2FmgahUCv5slwifMgKlywspZ1b4LqPGFfBG2ck65OLMQ6vfe+cuiEyWYlRmKiZicq/wwQUCaKErkYAdMn7hAhS5R4LXcOXlpElpzG+gp+BH/PD0/ocJRvs2ru7M3eipR2YNhieDU4VWj7WWJi4Itsfp6nktOYCy1BjciOQgUp5j629I92kL3LVPBoeoSS1dyS9EbHQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCBKgIXOSH8cLgKHq1Q5Zn69YdVpC8W8gp3hfjqa9lpER8MHyZVw0isOzdICrNZdgsatq/uaYBMkc3LwxDRWJVN8AmKabqy6UDlAwHf7IUJJMcu3ODG+2tsy3U1SGIVWIffpOfv3F/gXxU76IXWnHiUzjCMYnWJg0Oy0G2oCDHk/7h82Dmq688UmPW+ycZhktjZ8lXqlopVZhjssTa48xJjtDdwN8OVmPGpV/uVzlDTCuYbyVWTYrEfnKwwVhzmAoIYc4XxDKZQ/z1zqE3HtIGrems7lGpgry55JMIRSYxoD2gg2YscDvuCnfzITwTPjijuyI7ocP6eA13FHriIcfHYEzKENUoEgWeybgs09JyIp3yE7YelL94vY4xJRVeL1jMmP5Wi6pM9cMKgQwkUzq7tmupkh9c6jF+tPStByDvD11ybJi5A/S2Rmer2qhlgnsml4NHkMZgIcWtokxoGmXoMcz6AOx31nRvvBHjC2emVnUmzojTCc5mPY3TRgzlAb+cQE/JIreZMfhfLwk4ny5dq+r4ya02fo7BrDA8oJJAP0gC82KNW5aZVpZSbkeRdogTVWdmiNYxvq95gI4ijLneYwSgWb1PM+CRhlNY7neJEv0VT5fbMd0XQZnxzSzQVymPiBHMEJBUul6UuxjVlJb7cdCtIty0zEWO3/uaEzqQl3w=="
451              ],
452              "x5t": "Fk9zR2uLwBS6fHJbxM08TjDhUi8",
453              "x5t#S256": "ZiBGLQCaqehbgYF5A2dicp7WaL-zE4UTbFYyHKXDU_o"
454            },
455            {
456              "kid": "Jnf5fTyMpeiUyJnc3PHJaM9pR6VjWejv9RVyJgPugFs",
457              "kty": "RSA",
458              "alg": "RS512",
459              "use": "sig",
460              "n": "m3Y_aeHLL00X-bBPF3ySQ5ebOQ0dz40IQ4uWwWzL59zxn1AwzqrfrfAkKt_RJvJycfmy4zFeu89bNI86r6PtQVSvLqRYKo9UI4Y5jXs4HyvGvSL-DOXl8b8ybpo-o3bEiTgGOvIw2NGv49xT-_3SJ4Rba6awqVxkj334eZunrfvwYG9bjbAgPqWgMcuLVQNdNpytRHMB8Cjnd0SouL1dVxHlgHpYsZcRbsTsvPO1fRHcQRel44CgQRCZ08BvgETrF_9eATiRKBz18XbhaCZfSqh3a7IA-w9e236w6oD4ATOigeMHYZ0sfqKeoCsSd4rQ9kVc-U_EtL73_BVV7pmM4Xcl8JB8vzi_FMQVotzj5SgawylIxRdWUOGjyVFcUJ_u-DikoneVway0T4fXFJkWUflIoqf5-lHmMupb32q0E_pNL728yOlBfqm3bfJF9SF9w-h2SFMHWdRUzVOrtDRdrJVReGPPWvUHByALLL6B33FEcHDIcw4wqSfEmD6ypYJQxX8Er3_X9QFCgkn_rYUitUx90jOZ0n5vhubYnhiXX3RpeOCh9gF2O3h9Tv-DrynUO6OOgUSsBBbI-tGC5ebT51P0IJRkK3i4TkIYZnv7lj2auGWMC0-o7w24k_fG4U0EAr9N2cenR3Pepl6pjTa2g3y3C5_0LDUrcd67QPKl6ZE",
461              "e": "AQAB",
462              "x5c": [
463                "MIIElTCCAn0CBgF95wHdoDANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDDANkZXYwHhcNMjExMjIzMTExNDU4WhcNMzExMjIzMTExNjM4WjAOMQwwCgYDVQQDDANkZXYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCbdj9p4csvTRf5sE8XfJJDl5s5DR3PjQhDi5bBbMvn3PGfUDDOqt+t8CQq39Em8nJx+bLjMV67z1s0jzqvo+1BVK8upFgqj1QjhjmNezgfK8a9Iv4M5eXxvzJumj6jdsSJOAY68jDY0a/j3FP7/dInhFtrprCpXGSPffh5m6et+/Bgb1uNsCA+paAxy4tVA102nK1EcwHwKOd3RKi4vV1XEeWAelixlxFuxOy887V9EdxBF6XjgKBBEJnTwG+AROsX/14BOJEoHPXxduFoJl9KqHdrsgD7D17bfrDqgPgBM6KB4wdhnSx+op6gKxJ3itD2RVz5T8S0vvf8FVXumYzhdyXwkHy/OL8UxBWi3OPlKBrDKUjFF1ZQ4aPJUVxQn+74OKSid5XBrLRPh9cUmRZR+Uiip/n6UeYy6lvfarQT+k0vvbzI6UF+qbdt8kX1IX3D6HZIUwdZ1FTNU6u0NF2slVF4Y89a9QcHIAssvoHfcURwcMhzDjCpJ8SYPrKlglDFfwSvf9f1AUKCSf+thSK1TH3SM5nSfm+G5tieGJdfdGl44KH2AXY7eH1O/4OvKdQ7o46BRKwEFsj60YLl5tPnU/QglGQreLhOQhhme/uWPZq4ZYwLT6jvDbiT98bhTQQCv03Zx6dHc96mXqmNNraDfLcLn/QsNStx3rtA8qXpkQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQAf2H6GjobSvc50L+cXeizzG6rg6Sm3x31PB7AH7XlVI+cytWA0X04IhuX+9H2VdEqujSApY/WM9voneyEm1eC3L6p4StO7icB+H4GYctzY+KV0qlbH3iMQkz+xngOTaEj+c9lSZlG7FSlL7Eybjjlj9mLyNJv4aiW7lQCxTWu7RcFq+w2ogvR7iv4uwbY9SHO/Fs5qbwzNIP65W9abcZvEAZKXQ69jOZ01VhNqiIA2D0OstjLWTfGaO0WxrUxvBVRqB3a86qIIwHjatrqdoGasLLGz8bAU3rY2b/DwZ7VBljUuZ+7PlysSK3w22k6eQe5G+XgxSl4Mzn+6lzCdoXeSVUzvQZrk+JBaDTVN5V5fteHSjLcaGNwIg9qYOHdx7PBYhbHP/hXADSQH90xIMipG168NOGBaxw+ybCaD6Eg+PfsPGnXO0Wnnd0PN/Dz4LggTLBwlbWaIDltj++0Xxlf375MrK1A9mDkhcdAOzZtkBkTD9UeXqL6UD0R0CFHp0B+TQEZuOuKRMKmlA2eo8f8z70vGToYk5TW/lvi8Li44+Y7UGLlLirpOtfBI35TPLK0OGfLh1dfqnuFQACObk+Ia+ON//r203sSQYQf3Qcq7u5KC/S406W+dSJ+c7Cf+8piMVc42PhYemdrkEPgzuTmzTJga2HFQk8BCUwoL1euMdw=="
464              ],
465              "x5t": "bPku6_PBAoke1DpEcT0ghZYp6Fc",
466              "x5t#S256": "kIo7Hxj-A4jrwOBfo87c2kmAZzs87OHSd8tS4s_PGgk"
467            },
468            {
469              "kid": "WerdZfF_9ZgxLyHepk92CsKAEubvCs3rIAAy6wrUZUc",
470              "kty": "RSA",
471              "alg": "PS256",
472              "use": "sig",
473              "n": "85fgcXq_tB48BI8oeF9gjeWqL1opGtHoXv4rmwaxwfwzFU2ywJWRIEjwcJ_ypMPdC1im_kz_VCqWZBFyXfpuaEFkcsIAlLLnklI2TPUD3SV5taV_TXA61fm59K59iJDJr9EaQ_j5WJRGRluJpAi_q55U1vBWAHtnweL9RveQ-Ykc_qhpCcGDIek3-tAvJtVCpKQb764tkvmBD3pUPYTdVKHW4TAp4wFcgcj78E-xWELfm0T1nr7kZu-mV9DGYBZhFIWkf0lm4KA6NVDwWe-d1k-20FpT1tNsugK2Zx7SX2N5ytM2bCLH88Fcphvh9Bw_t7GgtZ9PvihJXdJcHR8nqlCsRMsGpeS6tnEl4E8StcTccgOkw1n2FJ-xxLM9eMOcfY--B9eKSaLRjLrhvWfa5-MGpB5JFrB4Rv17SD02Uoz1lwogCXPzTbKkBJhiA-YDinTRyGzyHTNXWsrmOLXrVRXUqdNYG32mpy1m3cSpoz9fOWne2dKKj9eawxFHa-GCzdfX3JBfgVKGGgaL5E_HlkJxx9OHNfQQQ4_OjyzqQGCoPG7jDCn9svb7hOE2epmYywShCgCsL_DZmTm3OdVWMLZ6oi77SIytWSx8QDy5KNCx3YsSLDg7sWv6t58gerWv1gkjhFzhyi3mqsw53WkeUyInrLoDYzEPkjWv3kSKQeM",
474              "e": "AQAB",
475              "x5c": [
476                "MIIElTCCAn0CBgF95wIdDjANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDDANkZXYwHhcNMjExMjIzMTExNTE1WhcNMzExMjIzMTExNjU1WjAOMQwwCgYDVQQDDANkZXYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDzl+Bxer+0HjwEjyh4X2CN5aovWika0ehe/iubBrHB/DMVTbLAlZEgSPBwn/Kkw90LWKb+TP9UKpZkEXJd+m5oQWRywgCUsueSUjZM9QPdJXm1pX9NcDrV+bn0rn2IkMmv0RpD+PlYlEZGW4mkCL+rnlTW8FYAe2fB4v1G95D5iRz+qGkJwYMh6Tf60C8m1UKkpBvvri2S+YEPelQ9hN1UodbhMCnjAVyByPvwT7FYQt+bRPWevuRm76ZX0MZgFmEUhaR/SWbgoDo1UPBZ753WT7bQWlPW02y6ArZnHtJfY3nK0zZsIsfzwVymG+H0HD+3saC1n0++KEld0lwdHyeqUKxEywal5Lq2cSXgTxK1xNxyA6TDWfYUn7HEsz14w5x9j74H14pJotGMuuG9Z9rn4wakHkkWsHhG/XtIPTZSjPWXCiAJc/NNsqQEmGID5gOKdNHIbPIdM1dayuY4tetVFdSp01gbfaanLWbdxKmjP185ad7Z0oqP15rDEUdr4YLN19fckF+BUoYaBovkT8eWQnHH04c19BBDj86PLOpAYKg8buMMKf2y9vuE4TZ6mZjLBKEKAKwv8NmZObc51VYwtnqiLvtIjK1ZLHxAPLko0LHdixIsODuxa/q3nyB6ta/WCSOEXOHKLeaqzDndaR5TIiesugNjMQ+SNa/eRIpB4wIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQDtW7hL3dWY0Nu87SkAPweBLocyI/S2/XZogBByzqdEWZru+26xQoUacqgYbrmQ6frQfWwlfpuzp7HBheDAHVobjlhl2jUQ7xO5vzTiB1bd/X1cQgOdTHosqiyTXLRBJKr3GQyfjrS3ruWKScGg5Y4jYGbsAoO3cNProddFeLbak0aQXGkhyWib2CzqtIpBA9Zy7EJYIWd5O+tExNIv+mjhSZZ6s3qdWXo/4RkVzBeGx5PApdoI/B7y0vwg4Dlt8qB9JcV9WL4nzI4s8foPMXuXgg+HJllB+NkSnTlQj77oU3pbrBoYgVhEdbfYkQuIdwYOWBQi/hdmV0YjUQQTAjYKBFKWQWCoAVKnfMpbDkdjN8KhOzohZ7KEahvHsnFt/PnS5MlFseZN9e6k4MB96EQ4fem7n/sPx4zqvhZMrPCaUT616hfCTa3DPoHzi2CxebE7GE95veQOtk9jCsXEbqKPvZ83/dfz5ftWu5wGHnhIK9S5sCCgjo2RA8bCLBl6/tBpmE0BwWqQqSZEs4zyXTplko822aJyxJtYprmDK0Ktxm6IEjSpEDCLuirnpQ0+Z8w19Key58Kx+OhNHczJK9wEaygKBQC1vvPV8ZvcHOx4XJgL8QwbPhaR5706YRfXTBceK2aw+oWzoNLJ4X7B2LB9IA84pJZKW+VfmnBz52iiqw=="
477              ],
478              "x5t": "Xdy9viGu6isFknWeThJbh2_r4Qo",
479              "x5t#S256": "-toFY0ysJ3uopRPDNIQBo2VV_XT5YkniW2I-6XK_2oc"
480            },
481            {
482              "kid": "JjGFU4NwBkjaNRmIEw5BpoggXtG8dsl4s7gs29eYvno",
483              "kty": "RSA",
484              "alg": "PS384",
485              "use": "sig",
486              "n": "h3RNtfVqZPTQuFYBN54gOgcLX7bK-3qUyXstFso_V09RCHLHbFZV_czEC30lRQ6U5QeZ7iFpu7GbiM1csBk4HqhQ2v0TnjlQxIv9-71VV1JPZHrKsDFZlSr4HlZhkt6myBH16aDBT56U8pKg4oAVkoYS4dpzsR0q30zzrKAMgHRLTYWbCaGGGa1BuEUF9WgUhVnuiMu4ay9Tv0auu1UsdTkXjdR2YcWv2AihvFb4xYUSMBQr0bvUeMF_AAJ0B0VrGWIb51nARO2PNimKviHnFTrlaOyFJsnzwiiijuaOx2HZMQfcObzTz4Hx_YYIexOS83bYYkyGgvgUdu0wqls7ChgaZ_qiQdNnr_RWahIN2iVhjyOJuqsFsXufvHYo0nB1BFm1gnDHgYXdJIrSPql4g9gh1NZD_P0PuniPq3jvPoiQJ2u_9a8RDe9Scb_KzRgrBk0tkaXELDw1Q7ccJx9HUUbTxNkzNtZ6Z4MiKT4n0Bx4joglnL1BXvM5yrlO89brXAmfZgx6OmH7Dractz_Bny6QUHwF5vLMhMuVXsC5dU6UbkSZq82S5SnwnLHAe4JBOC-FTB08wAKgQXat16MIqmrBuKVtdNSshAxMk0wd_jPe-G4A_2RJ6pXSrQOkUFNPrOfV_PQMqI92zCYbIByWEwdfQAkavR2HC0-iDd202NM",
487              "e": "AQAB",
488              "x5c": [
489                "MIIElTCCAn0CBgF95wJajzANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDDANkZXYwHhcNMjExMjIzMTExNTMwWhcNMzExMjIzMTExNzEwWjAOMQwwCgYDVQQDDANkZXYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCHdE219Wpk9NC4VgE3niA6Bwtftsr7epTJey0Wyj9XT1EIcsdsVlX9zMQLfSVFDpTlB5nuIWm7sZuIzVywGTgeqFDa/ROeOVDEi/37vVVXUk9kesqwMVmVKvgeVmGS3qbIEfXpoMFPnpTykqDigBWShhLh2nOxHSrfTPOsoAyAdEtNhZsJoYYZrUG4RQX1aBSFWe6Iy7hrL1O/Rq67VSx1OReN1HZhxa/YCKG8VvjFhRIwFCvRu9R4wX8AAnQHRWsZYhvnWcBE7Y82KYq+IecVOuVo7IUmyfPCKKKO5o7HYdkxB9w5vNPPgfH9hgh7E5LzdthiTIaC+BR27TCqWzsKGBpn+qJB02ev9FZqEg3aJWGPI4m6qwWxe5+8dijScHUEWbWCcMeBhd0kitI+qXiD2CHU1kP8/Q+6eI+reO8+iJAna7/1rxEN71Jxv8rNGCsGTS2RpcQsPDVDtxwnH0dRRtPE2TM21npngyIpPifQHHiOiCWcvUFe8znKuU7z1utcCZ9mDHo6YfsOtpy3P8GfLpBQfAXm8syEy5VewLl1TpRuRJmrzZLlKfCcscB7gkE4L4VMHTzAAqBBdq3XowiqasG4pW101KyEDEyTTB3+M974bgD/ZEnqldKtA6RQU0+s59X89Ayoj3bMJhsgHJYTB19ACRq9HYcLT6IN3bTY0wIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQAoudkN4cTAnT2b7cd/JklLFLBnw+mwSgj0ZYyRByBiC0AXU+LmM+D1Bs0TRqXKICBZ2dxKRr8Z1PdQe8BghWcl84iLXEjHVdw08/xVaQ5GKcGLOfSRG+3Suj6UyZfwcMJtX4GO919fX10mAlk6ySHe6SViSVMup5ePwA0C7Jws9/aXNLIvw82hIX8IVM1kuuu3DICQlr1nsvbu6XVQT5kdhIpApr+IDrBvNFWKPdH+vA8Kxb8wkhk9HIUbAi3WqftHoiI8Qq92BYcB5gjwocAkzmrDDoAulEM24+IJoK87DWEeC0Vu9kOB7i5PKXqUANJ7ebQJJhgXy+xNq1Alh4f95mqolXCxdo1jJi/OFExLDr93Fk5QVRQxi2aSEDkoz/h7stzuUvvTyT75pJAILSL+xv8Gd/bYhL5lfCXcHA9uPDQwM/9gnA1ojIdF1bvgaEo2r1xoY/LAScTB0nzvRh1EVoZYxBHid+79MJWQq0vpJ58pyKcxgKaoD1pUQ2brAlYFNflNiMN18VnCF7vnY8Ol9Po881ee2TWLex0i5cLREo4fvPNg0QgoaQvDqlvJqr1nJll/Mzv2w9s3agQxPwKRkTOTb4jNOV23Uy0SbxBD42EOllLmUN897ra3pdmacHHMatw75Sfcu4WhuMrN13RzVUARMjFN+nNI8i7ay9WJOA=="
490              ],
491              "x5t": "4ovci1k_HPeLoL2PhUrmoDlLQhU",
492              "x5t#S256": "PJsKbXoQ7tZoR7aRDli60V65BPtO-Q7QSpk5P5hDcLY"
493            },
494            {
495              "kid": "zesnP0SwjgVGBU5RPhqccF0W4BbMkbwtZpjAeTAgwz8",
496              "kty": "RSA",
497              "alg": "PS512",
498              "use": "sig",
499              "n": "x4NHNpmzOgqWgQGsiWTpyhdIkSSiO0hMKr_5oNNecp254CSO_zEPS6wWKMNwwZRteKIPzPafCkXvmGEuQo716CL9OP5T8BR25sXkws0llygfbbSK2dTWVN4lhM1Rm6zFJ4aK0BZo6EXDp0E0Od8SQSN7FooRAWOiO7HvjgpIdRyqkANElBSL7aNdsPP7dgVMua5P6MNfVjKCe93C-iqsOVadUV5UM3oblf6M_KkDV9GNr6oAizfrXHpPnHjG29u-DSsmCbLimgZaJ3LDnLrmzxbbl9b4mHJQqe00rNDUF6Q6BmmDgJGDMdPH4J8i6w_1z4Xll8Ul-UGHS6rJZeTVsEdKGSOoIbhQa9iuGxC_I_YIjkVbV3O8LcYBzDKetzups4R5CVFpwvAK03UCdM7yLkbDglWcSOYtbPVBafumCzyjWX9u7CpBAcVWe9KpEMVCYgi90TSkX2Vw1bPP07mTBFmK0fwmU2ZlDR0S9Q2NT9St7zWP6teuOeue7PAFlPUVotFdoh8ltZVLEfUTo81E1tiNycDCy9QTP9CzwplqpPIkmTdjmMCO6lollLrTm9SuXGp2FSUdE43tYEzRGNqsGpcwskkvzQWtl7bETaS5vCwPH76k6qGf-TpOHnOH1G7vDzDkewqJ-oscqwkdw4ONo_KxT-CGwv-JwMoSXWEtMKE",
500              "e": "AQAB",
501              "x5c": [
502                "MIIElTCCAn0CBgF95wKLoDANBgkqhkiG9w0BAQsFADAOMQwwCgYDVQQDDANkZXYwHhcNMjExMjIzMTExNTQzWhcNMzExMjIzMTExNzIzWjAOMQwwCgYDVQQDDANkZXYwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDHg0c2mbM6CpaBAayJZOnKF0iRJKI7SEwqv/mg015ynbngJI7/MQ9LrBYow3DBlG14og/M9p8KRe+YYS5CjvXoIv04/lPwFHbmxeTCzSWXKB9ttIrZ1NZU3iWEzVGbrMUnhorQFmjoRcOnQTQ53xJBI3sWihEBY6I7se+OCkh1HKqQA0SUFIvto12w8/t2BUy5rk/ow19WMoJ73cL6Kqw5Vp1RXlQzehuV/oz8qQNX0Y2vqgCLN+tcek+ceMbb274NKyYJsuKaBloncsOcuubPFtuX1viYclCp7TSs0NQXpDoGaYOAkYMx08fgnyLrD/XPheWXxSX5QYdLqsll5NWwR0oZI6ghuFBr2K4bEL8j9giORVtXc7wtxgHMMp63O6mzhHkJUWnC8ArTdQJ0zvIuRsOCVZxI5i1s9UFp+6YLPKNZf27sKkEBxVZ70qkQxUJiCL3RNKRfZXDVs8/TuZMEWYrR/CZTZmUNHRL1DY1P1K3vNY/q1645657s8AWU9RWi0V2iHyW1lUsR9ROjzUTW2I3JwMLL1BM/0LPCmWqk8iSZN2OYwI7qWiWUutOb1K5canYVJR0Tje1gTNEY2qwalzCySS/NBa2XtsRNpLm8LA8fvqTqoZ/5Ok4ec4fUbu8PMOR7Con6ixyrCR3Dg42j8rFP4IbC/4nAyhJdYS0woQIDAQABMA0GCSqGSIb3DQEBCwUAA4ICAQCr+GGCVS/sBHukLZay8WlBXtowJ6qyX8hMFClDGDN9/c3mUbLJsCCVN6Jbr33BgNZ/ZuvLhUvhWGPlOXUB3Rf+qRzNEzoLVwanw2yCUEKFi6AvuBUY9twNnifH4y1Cg34NVaZoPvQ0hlOLGYl9CCxen7VMLJ5QbTC8H3fPX1prWOic5x46Bu7IqoEqZtDszt8F+uteruRsHVHCiWx5dW7goeIa8YsUK0A4mnOy5kViSvs5L6Kq0N5uCB9EDu/Ew5R0/mi/UTm5L8CpzQig1pmvDtIy7ZnosHu7zYGSQiR04jn3Od0rdWzTCcs8W79+ewgJ0bdYmfvSnVehs1BR+cjivzBqMWMqdyz6eQXCy/esiG5KDIxH4F0HGLiiwXqHUYjJPex8TId+fz0MFScrEN5fjE+XltGzsPwlcgnAqE0pN0ExJSHwzBHNkJJQpjHrsEurWn9QGBqD75Vt9yVeHE8MZ4zMGj3ZkRmn1x6wVBdv1V12P3e4b8V5aG02FbREkJzFTXtGyDHtw/hlWGz9M9w0c5TAI6xYPa1gS6/Fw95J6S0V3n3JH+xqi6yv2H2cQHukFxFSPJW1cc/hh5DJ4Ag8+pKuO1Vdo9p+DltaGLWBabON7GZZojlYdx2WtBZK9CMRgrxobg+OBA44AHkiWkhflrqGLYul866wiNu6zLEfdQ=="
503              ],
504              "x5t": "0lMdqEAhOWfUXDivtS-KwPvwKNY",
505              "x5t#S256": "aOjQ1awJmcaF7Yiz75ifjBKbjr4Eo-Ha5uNMi-TtuGw"
506            },
507            {
508              "kid": "VlsIs1LssBo6r8EuXJo81rDEoTYpUjiMkeq_PlapKfY",
509              "kty": "EC",
510              "alg": "ES256",
511              "use": "sig",
512              "crv": "P-256",
513              "x": "3kqy7us0mepJJblWwj0Exg2S7PtWaJvB7SI_ptg0jrA",
514              "y": "S5Z8d4AfCvRL-hUd6Pv-L3tH6H9T4RIwO2tvBS0hj1A"
515            },
516            {
517              "kid": "1yWLiqf8sa-em0hSbtZEjKmrardmQdYLR9gpzsypMCU",
518              "kty": "EC",
519              "alg": "ES384",
520              "use": "sig",
521              "crv": "P-384",
522              "x": "i4YYGQZd5QQ1JpUXcrZe5wpCid3pqFLnzxxy89Chn-NQ1oYDPTP2M8V9sfazeuB0",
523              "y": "xf4qN2ZuMLVh4GmRVt1PHhQooB2o61pF0lHrBlIod5hVamiRtUo_Np9PikPD8Uap"
524            },
525            {
526              "kid": "V5EwcLp9vmwAnstzI1Ndba-iWkX5oTBHK7GnYTyfuOE",
527              "kty": "EC",
528              "alg": "ES512",
529              "use": "sig",
530              "crv": "P-521",
531              "x": "rScgdd_n2cHLyzZvP8zw0u9vQyhu0VsbfQypheS7aDoHRLcXccPQTsmrQLrLuKX8PPkITjL_BJDSm7Bo8gv5Sd4",
532              "y": "Vu3rTFNn_9zWTki95UGT1Bd9PN84KDXmttCrJ1bsYHTWQCaEONk8iwA3U6mEDrg4xtZSTXXKCFdFP13ONWB9oZ4"
533            }
534          ]
535        });
536
537        let jwks: PublicJsonWebKeySet = serde_json::from_value(jwks).unwrap();
538        // The first 6 keys are RSA, 7th is P-256
539        let mut keys = jwks.keys.into_iter();
540        rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
541        rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
542        rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
543        rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
544        rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
545        rsa::RsaPublicKey::try_from(keys.next().unwrap().params().rsa().unwrap()).unwrap();
546        // 7th is P-256
547        elliptic_curve::PublicKey::<p256::NistP256>::try_from(
548            keys.next().unwrap().params().ec().unwrap(),
549        )
550        .unwrap();
551        // 8th is P-384
552        elliptic_curve::PublicKey::<p384::NistP384>::try_from(
553            keys.next().unwrap().params().ec().unwrap(),
554        )
555        .unwrap();
556        // 8th is P-521, but we don't support it yet
557        keys.next().unwrap().params().ec().unwrap();
558    }
559}