1use std::fmt;
11
12use jsonwebtoken::jwk::{AlgorithmParameters, EllipticCurve, KeyAlgorithm};
13
14pub const DEFAULT_ALGORITHMS: &[&str] = &[
21 "RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "EdDSA",
22];
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36#[non_exhaustive]
37#[allow(clippy::upper_case_acronyms)]
38pub enum Algorithm {
39 RS256,
41 RS384,
43 RS512,
45 PS256,
47 PS384,
49 PS512,
51 ES256,
53 ES384,
55 EdDSA,
57}
58
59impl Algorithm {
60 pub fn as_str(self) -> &'static str {
62 match self {
63 Algorithm::RS256 => "RS256",
64 Algorithm::RS384 => "RS384",
65 Algorithm::RS512 => "RS512",
66 Algorithm::PS256 => "PS256",
67 Algorithm::PS384 => "PS384",
68 Algorithm::PS512 => "PS512",
69 Algorithm::ES256 => "ES256",
70 Algorithm::ES384 => "ES384",
71 Algorithm::EdDSA => "EdDSA",
72 }
73 }
74
75 pub(crate) fn to_jwt(self) -> jsonwebtoken::Algorithm {
77 match self {
78 Algorithm::RS256 => jsonwebtoken::Algorithm::RS256,
79 Algorithm::RS384 => jsonwebtoken::Algorithm::RS384,
80 Algorithm::RS512 => jsonwebtoken::Algorithm::RS512,
81 Algorithm::PS256 => jsonwebtoken::Algorithm::PS256,
82 Algorithm::PS384 => jsonwebtoken::Algorithm::PS384,
83 Algorithm::PS512 => jsonwebtoken::Algorithm::PS512,
84 Algorithm::ES256 => jsonwebtoken::Algorithm::ES256,
85 Algorithm::ES384 => jsonwebtoken::Algorithm::ES384,
86 Algorithm::EdDSA => jsonwebtoken::Algorithm::EdDSA,
87 }
88 }
89
90 pub(crate) fn from_jwt(alg: jsonwebtoken::Algorithm) -> Option<Self> {
93 Some(match alg {
94 jsonwebtoken::Algorithm::RS256 => Algorithm::RS256,
95 jsonwebtoken::Algorithm::RS384 => Algorithm::RS384,
96 jsonwebtoken::Algorithm::RS512 => Algorithm::RS512,
97 jsonwebtoken::Algorithm::PS256 => Algorithm::PS256,
98 jsonwebtoken::Algorithm::PS384 => Algorithm::PS384,
99 jsonwebtoken::Algorithm::PS512 => Algorithm::PS512,
100 jsonwebtoken::Algorithm::ES256 => Algorithm::ES256,
101 jsonwebtoken::Algorithm::ES384 => Algorithm::ES384,
102 jsonwebtoken::Algorithm::EdDSA => Algorithm::EdDSA,
103 _ => return None,
104 })
105 }
106}
107
108impl fmt::Display for Algorithm {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 f.write_str(self.as_str())
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
119#[non_exhaustive]
120pub enum AlgorithmError {
121 #[error("\"{name}\" — an unsigned token is never acceptable")]
123 #[non_exhaustive]
124 Unsigned {
125 name: String,
127 },
128 #[error(
130 "\"{name}\" — HMAC algorithms verify with a shared secret, which a resource \
131 server must never hold, and accepting one alongside a public key set is the \
132 classic key-confusion attack (a token signed with the PUBLIC key as the HMAC \
133 secret)"
134 )]
135 #[non_exhaustive]
136 Hmac {
137 name: String,
139 },
140 #[error("\"{name}\" — not a JWS algorithm this server can verify (supported: {supported})")]
144 #[non_exhaustive]
145 Unsupported {
146 name: String,
148 supported: String,
150 },
151}
152
153pub fn parse_algorithm(name: &str) -> Result<Algorithm, AlgorithmError> {
180 let name = name.trim();
181 if name.eq_ignore_ascii_case("none") {
182 return Err(AlgorithmError::Unsigned {
183 name: name.to_string(),
184 });
185 }
186 match name.parse::<jsonwebtoken::Algorithm>() {
187 Ok(
188 jsonwebtoken::Algorithm::HS256
189 | jsonwebtoken::Algorithm::HS384
190 | jsonwebtoken::Algorithm::HS512,
191 ) => Err(AlgorithmError::Hmac {
192 name: name.to_string(),
193 }),
194 Ok(alg) => Algorithm::from_jwt(alg).ok_or_else(|| unsupported(name)),
195 Err(_) => Err(unsupported(name)),
196 }
197}
198
199fn unsupported(name: &str) -> AlgorithmError {
200 AlgorithmError::Unsupported {
201 name: name.to_string(),
202 supported: DEFAULT_ALGORITHMS.join(", "),
203 }
204}
205
206pub(crate) fn key_algorithms(params: &AlgorithmParameters) -> Option<Vec<Algorithm>> {
212 use Algorithm::*;
213 match params {
214 AlgorithmParameters::RSA(_) => Some(vec![RS256, RS384, RS512, PS256, PS384, PS512]),
215 AlgorithmParameters::EllipticCurve(p) => match p.curve {
216 EllipticCurve::P256 => Some(vec![ES256]),
217 EllipticCurve::P384 => Some(vec![ES384]),
218 _ => None,
219 },
220 AlgorithmParameters::OctetKeyPair(p) => match p.curve {
221 EllipticCurve::Ed25519 => Some(vec![EdDSA]),
222 _ => None,
223 },
224 AlgorithmParameters::OctetKey(_) => None,
225 }
226}
227
228pub(crate) fn signing_algorithm(alg: &KeyAlgorithm) -> Option<Algorithm> {
231 Some(match alg {
232 KeyAlgorithm::RS256 => Algorithm::RS256,
233 KeyAlgorithm::RS384 => Algorithm::RS384,
234 KeyAlgorithm::RS512 => Algorithm::RS512,
235 KeyAlgorithm::PS256 => Algorithm::PS256,
236 KeyAlgorithm::PS384 => Algorithm::PS384,
237 KeyAlgorithm::PS512 => Algorithm::PS512,
238 KeyAlgorithm::ES256 => Algorithm::ES256,
239 KeyAlgorithm::ES384 => Algorithm::ES384,
240 KeyAlgorithm::EdDSA => Algorithm::EdDSA,
241 _ => return None,
242 })
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 #[test]
250 fn hmac_and_none_can_never_be_configured() {
251 for bad in [
252 "HS256", "HS384", "HS512", "none", "None", "ES512", "rs256", "",
253 ] {
254 assert!(parse_algorithm(bad).is_err(), "{bad:?} must be refused");
255 }
256 assert!(
257 parse_algorithm("HS256")
258 .unwrap_err()
259 .to_string()
260 .contains("key-confusion")
261 );
262 for good in DEFAULT_ALGORITHMS {
263 let alg = parse_algorithm(good).expect("every default parses");
264 assert_eq!(alg.as_str(), *good);
265 assert_eq!(alg.to_string(), *good);
266 assert_eq!(format!("{alg:?}"), *good);
267 assert_eq!(Algorithm::from_jwt(alg.to_jwt()), Some(alg));
268 }
269 }
270
271 #[test]
272 fn refusals_are_typed_and_keep_their_text() {
273 assert_eq!(
274 parse_algorithm(" none "),
275 Err(AlgorithmError::Unsigned {
276 name: "none".into()
277 })
278 );
279 assert_eq!(
280 parse_algorithm("none").unwrap_err().to_string(),
281 "\"none\" — an unsigned token is never acceptable"
282 );
283 assert!(matches!(
284 parse_algorithm("HS384"),
285 Err(AlgorithmError::Hmac { .. })
286 ));
287 let err = parse_algorithm("ES512").unwrap_err();
288 assert!(matches!(err, AlgorithmError::Unsupported { .. }));
289 assert_eq!(
290 err.to_string(),
291 "\"ES512\" — not a JWS algorithm this server can verify (supported: RS256, RS384, \
292 RS512, PS256, PS384, PS512, ES256, ES384, EdDSA)"
293 );
294 let _: Box<dyn std::error::Error + Send + Sync> = Box::new(err);
296 }
297
298 #[test]
299 fn hmac_has_no_crate_algorithm() {
300 for hmac in [
301 jsonwebtoken::Algorithm::HS256,
302 jsonwebtoken::Algorithm::HS384,
303 jsonwebtoken::Algorithm::HS512,
304 ] {
305 assert_eq!(Algorithm::from_jwt(hmac), None);
306 }
307 }
308}