1use std::collections::HashMap;
4
5use jsonwebtoken::jwk::{Jwk, JwkSet};
6use thiserror::Error;
7
8use crate::bundle::{Bundle, BundleRefSource};
9use crate::spiffe_id::TrustDomain;
10
11#[derive(Debug, Clone, Eq, PartialEq)]
13pub struct JwtBundle {
14 trust_domain: TrustDomain,
15 jwt_authorities: HashMap<String, Jwk>,
16}
17
18impl Bundle for JwtBundle {}
19
20#[derive(Debug, Clone, Eq, PartialEq)]
22pub struct JwtBundleSet {
23 bundles: HashMap<TrustDomain, JwtBundle>,
24}
25
26#[derive(Debug, Error)]
28#[non_exhaustive]
29pub enum JwtBundleError {
30 #[error("missing key ID")]
32 MissingKeyId,
33 #[error("cannot deserialize json jwk set")]
35 Deserialize(#[from] serde_json::Error),
36}
37
38impl JwtBundle {
39 pub fn new(trust_domain: TrustDomain) -> Self {
41 Self {
42 trust_domain,
43 jwt_authorities: HashMap::new(),
44 }
45 }
46
47 pub fn from_jwt_authorities(
84 trust_domain: TrustDomain,
85 jwt_authorities: &[u8],
86 ) -> Result<Self, JwtBundleError> {
87 let mut authorities = HashMap::new();
88 let jwk_set: JwkSet = serde_json::from_slice(jwt_authorities)?;
89
90 for key in jwk_set.keys.into_iter() {
91 let key_id = match &key.common.key_id {
92 Some(k) => k,
93 None => return Err(JwtBundleError::MissingKeyId),
94 };
95 authorities.insert(key_id.to_owned(), key);
96 }
97
98 Ok(Self {
99 trust_domain,
100 jwt_authorities: authorities,
101 })
102 }
103 pub fn find_jwt_authority(&self, key_id: &str) -> Option<&Jwk> {
105 self.jwt_authorities.get(key_id)
106 }
107
108 pub fn add_jwt_authority(&mut self, authority: Jwk) -> Result<(), JwtBundleError> {
110 let key_id = match &authority.common.key_id {
111 Some(k) => k.to_owned(),
112 None => return Err(JwtBundleError::MissingKeyId),
113 };
114
115 self.jwt_authorities.insert(key_id, authority);
116 Ok(())
117 }
118
119 pub fn trust_domain(&self) -> &TrustDomain {
121 &self.trust_domain
122 }
123}
124
125impl JwtBundleSet {
126 pub fn new() -> Self {
128 Self {
129 bundles: HashMap::new(),
130 }
131 }
132
133 pub fn add_bundle(&mut self, bundle: JwtBundle) {
136 self.bundles.insert(bundle.trust_domain().clone(), bundle);
137 }
138
139 pub fn get_bundle(&self, trust_domain: &TrustDomain) -> Option<&JwtBundle> {
141 self.bundles.get(trust_domain)
142 }
143}
144
145impl Default for JwtBundleSet {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151impl BundleRefSource for JwtBundleSet {
152 type Item = JwtBundle;
153
154 fn get_bundle_for_trust_domain(
156 &self,
157 trust_domain: &TrustDomain,
158 ) -> Result<Option<&Self::Item>, Box<dyn std::error::Error + Send + Sync + 'static>> {
159 Ok(self.bundles.get(trust_domain))
160 }
161}
162
163#[cfg(test)]
164mod jwt_bundle_test {
165
166 use super::*;
167
168 #[test]
169 fn test_parse_bundle_from_json_single_authority() {
170 let bundle_bytes = r#"{
171 "keys": [
172 {
173 "kty": "EC",
174 "kid": "C6vs25welZOx6WksNYfbMfiw9l96pMnD",
175 "crv": "P-256",
176 "x": "ngLYQnlfF6GsojUwqtcEE3WgTNG2RUlsGhK73RNEl5k",
177 "y": "tKbiDSUSsQ3F1P7wteeHNXIcU-cx6CgSbroeQrQHTLM"
178 }
179 ]
180}"#
181 .as_bytes();
182 let trust_domain = TrustDomain::new("example.org").unwrap();
183 let jwt_bundle = JwtBundle::from_jwt_authorities(trust_domain, bundle_bytes).unwrap();
184 assert!(jwt_bundle
185 .find_jwt_authority("C6vs25welZOx6WksNYfbMfiw9l96pMnD")
186 .is_some());
187 }
188
189 #[test]
190 fn test_parse_bundle_from_json_multiple_authorities() {
191 let bundle_bytes = r#"{
192 "keys": [
193 {
194 "kty": "EC",
195 "kid": "C6vs25welZOx6WksNYfbMfiw9l96pMnD",
196 "crv": "P-256",
197 "x": "ngLYQnlfF6GsojUwqtcEE3WgTNG2RUlsGhK73RNEl5k",
198 "y": "tKbiDSUSsQ3F1P7wteeHNXIcU-cx6CgSbroeQrQHTLM"
199 },
200 {
201 "kty": "EC",
202 "kid": "gHTCunJbefYtnZnTctd84xeRWyMrEsWD",
203 "crv": "P-256",
204 "x": "7MGOl06DP9df2u8oHY6lqYFIoQWzCj9UYlp-MFeEYeY",
205 "y": "PSLLy5Pg0_kNGFFXq_eeq9kYcGDM3MPHJ6ncteNOr6w"
206 }
207 ]
208}"#
209 .as_bytes();
210
211 let trust_domain = TrustDomain::new("example.org").unwrap();
212 let jwt_bundle = JwtBundle::from_jwt_authorities(trust_domain, bundle_bytes).unwrap();
213 assert!(jwt_bundle
214 .find_jwt_authority("C6vs25welZOx6WksNYfbMfiw9l96pMnD")
215 .is_some());
216 assert!(jwt_bundle
217 .find_jwt_authority("gHTCunJbefYtnZnTctd84xeRWyMrEsWD")
218 .is_some());
219 }
220
221 #[test]
222 fn test_parse_bundle_from_authority_missing_key_id() {
223 let bundle_bytes = r#"{{
224 "keys": [
225 {
226 "kty": "EC",
227 "kid": "C6vs25welZOx6WksNYfbMfiw9l96pMnD",
228 "crv": "P-256",
229 "x": "ngLYQnlfF6GsojUwqtcEE3WgTNG2RUlsGhK73RNEl5k",
230 "y": "tKbiDSUSsQ3F1P7wteeHNXIcU-cx6CgSbroeQrQHTLM"
231 },
232 {
233 "kty": "EC",
234 "crv": "P-256",
235 "x": "7MGOl06DP9df2u8oHY6lqYFIoQWzCj9UYlp-MFeEYeY",
236 "y": "PSLLy5Pg0_kNGFFXq_eeq9kYcGDM3MPHJ6ncteNOr6w"
237 }
238 ]
239}"#
240 .as_bytes();
241
242 let trust_domain = TrustDomain::new("example.org").unwrap();
243 let result = JwtBundle::from_jwt_authorities(trust_domain, bundle_bytes);
244
245 assert!(matches!(
246 result.unwrap_err(),
247 JwtBundleError::Deserialize(..)
248 ));
249 }
250
251 #[test]
252 fn test_parse_jwks_with_empty_keys_array() {
253 let bundle_bytes = r#"{"keys": []}"#.as_bytes();
254 let trust_domain = TrustDomain::new("domain.test").unwrap();
255 let jwt_bundle = JwtBundle::from_jwt_authorities(trust_domain, bundle_bytes)
256 .expect("Failed to parse JWKS with empty keys array");
257
258 assert!(
259 jwt_bundle.jwt_authorities.is_empty(),
260 "JWT authorities should be empty"
261 );
262 }
263}