Skip to main content

tuwunel_core/config/
jwt.rs

1//! Turns the JWT key settings into signing and verifying keys and an algorithm.
2//!
3//! Every use of the key, format and algorithm settings goes through these
4//! conversions. The startup and reload check parses the format alone, so an
5//! unknown format is refused before the first login while a key that does not
6//! decode fails at login.
7
8use super::JwtConfig;
9use crate::{
10	Err, Error, Result, err, implement,
11	jwt::{Algorithm, DecodingKey, EncodingKey, errors::Error as JwtError},
12};
13
14#[derive(Clone, Copy)]
15pub(super) enum KeyFormat {
16	Hmac,
17	B64Hmac,
18	Ecdsa,
19	Eddsa,
20}
21
22/// Spellings of the `format` directive, matched without regard to case.
23///
24/// B64HMAC is the documented spelling; HMACB64 was the only one accepted before
25/// it and is kept for existing configurations.
26const KEY_FORMATS: [(&str, KeyFormat); 5] = [
27	("HMAC", KeyFormat::Hmac),
28	("B64HMAC", KeyFormat::B64Hmac),
29	("HMACB64", KeyFormat::B64Hmac),
30	("ECDSA", KeyFormat::Ecdsa),
31	("EDDSA", KeyFormat::Eddsa),
32];
33
34/// Decodes the configured key for verifying tokens.
35///
36/// Fails when the format is not one of the supported spellings or when the key
37/// does not parse in that format.
38#[implement(JwtConfig)]
39pub fn decoding_key(&self) -> Result<DecodingKey> {
40	let key = self.key.as_str();
41
42	match self.key_format()? {
43		| KeyFormat::Hmac => Ok(DecodingKey::from_secret(key.as_bytes())),
44		| KeyFormat::B64Hmac =>
45			DecodingKey::from_base64_secret(key).map_err(invalid_key("base64")),
46		| KeyFormat::Ecdsa =>
47			DecodingKey::from_ec_pem(key.as_bytes()).map_err(invalid_key("ECDSA PEM")),
48		| KeyFormat::Eddsa =>
49			DecodingKey::from_ed_pem(key.as_bytes()).map_err(invalid_key("EDDSA PEM")),
50	}
51}
52
53/// Decodes the configured key for signing tokens.
54///
55/// Only the shared-secret formats can sign. An ECDSA or EDDSA key is the
56/// issuer's public key, which verifies but never signs.
57#[implement(JwtConfig)]
58pub fn encoding_key(&self) -> Result<EncodingKey> {
59	let key = self.key.as_str();
60	let format = self.format.as_str();
61
62	match self.key_format()? {
63		| KeyFormat::Hmac => Ok(EncodingKey::from_secret(key.as_bytes())),
64		| KeyFormat::B64Hmac =>
65			EncodingKey::from_base64_secret(key).map_err(invalid_key("base64")),
66		| KeyFormat::Ecdsa | KeyFormat::Eddsa => Err!(Config(
67			"jwt.format",
68			"An {format} key is a public key; signing needs the HMAC or B64HMAC format."
69		)),
70	}
71}
72
73/// Parses the configured signature algorithm.
74///
75/// Fails with a `jwt.algorithm` configuration error naming the value when the
76/// algorithm name is not recognized.
77#[implement(JwtConfig)]
78pub fn algorithm(&self) -> Result<Algorithm> {
79	let algorithm = self.algorithm.as_str();
80
81	algorithm.parse().map_err(|e| {
82		err!(Config("jwt.algorithm", "JWT algorithm {algorithm:?} is not recognized: {e}"))
83	})
84}
85
86#[implement(JwtConfig)]
87pub(super) fn key_format(&self) -> Result<KeyFormat> {
88	let format = self.format.as_str();
89
90	KEY_FORMATS
91		.iter()
92		.find(|(name, _)| name.eq_ignore_ascii_case(format))
93		.map(|&(_, kind)| kind)
94		.ok_or_else(|| {
95			err!(Config(
96				"jwt.format",
97				"Key format {format:?} is not supported; use HMAC, B64HMAC, ECDSA or EDDSA."
98			))
99		})
100}
101
102fn invalid_key(encoding: &'static str) -> impl FnOnce(JwtError) -> Error {
103	move |e| err!(Config("jwt.key", "JWT key is not valid {encoding}: {e}"))
104}