webgates_codecs/jwt/
authority.rs1use std::sync::Arc;
40
41use crate::Result;
42use crate::jwt::jwks::JwksProvider;
43use crate::jwt::{JsonWebToken, JsonWebTokenOptions};
44
45use serde::{Serialize, de::DeserializeOwned};
46
47#[derive(Clone)]
55pub struct JwtAuthority<P>
56where
57 P: Serialize + DeserializeOwned + Clone,
58{
59 codec: Arc<JsonWebToken<P>>,
60 jwks_provider: JwksProvider,
61 key_id: String,
62}
63
64impl<P> JwtAuthority<P>
65where
66 P: Serialize + DeserializeOwned + Clone,
67{
68 pub fn from_es384_pem(private_key_pem: &[u8], public_key_pem: &[u8]) -> Result<Self> {
78 let options = JsonWebTokenOptions::from_es384_pem(private_key_pem, public_key_pem)?;
79 let key_id = options.key_id().to_string();
80 let jwks_provider =
81 JwksProvider::from_es384_public_pem_with_kid(public_key_pem, key_id.clone())?;
82 let codec = Arc::new(JsonWebToken::new_with_options(options));
83 Ok(Self {
84 codec,
85 jwks_provider,
86 key_id,
87 })
88 }
89
90 pub fn codec(&self) -> Arc<JsonWebToken<P>> {
94 Arc::clone(&self.codec)
95 }
96
97 pub fn jwks_provider(&self) -> &JwksProvider {
101 &self.jwks_provider
102 }
103
104 pub fn key_id(&self) -> &str {
108 &self.key_id
109 }
110}
111
112impl<P> std::fmt::Debug for JwtAuthority<P>
113where
114 P: Serialize + DeserializeOwned + Clone,
115{
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 f.debug_struct("JwtAuthority")
118 .field("key_id", &self.key_id)
119 .finish_non_exhaustive()
120 }
121}
122
123#[cfg(test)]
124#[allow(clippy::unwrap_used, clippy::expect_used)]
125mod tests {
126 use super::*;
127 use crate::Codec as _;
128 use crate::jwt::{JwtClaims, RegisteredClaims};
129
130 const PRIVATE_PEM: &[u8] = br#"-----BEGIN PRIVATE KEY-----
131MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCFT7MfRqWZfNgVX/cH
132bxFTlPkBeCKqjsLkZXD/J3ZYHV1EtQksdrKtOzTr2hMs6pmhZANiAASyND9eQ5Qk
1337ZteSEPMpExbVJenRWwyobExJMb62mmp3eA7Fszy8uBbLj8HRB16y3QbLcTxCBoo
134ldBXfNFzM133OuTV2bBWXq5h34l+A0h4gU/odZ678LfAgnrRYMG4ZjU=
135-----END PRIVATE KEY-----
136"#;
137
138 const PUBLIC_PEM: &[u8] = br#"-----BEGIN PUBLIC KEY-----
139MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEsjQ/XkOUJO2bXkhDzKRMW1SXp0VsMqGx
140MSTG+tppqd3gOxbM8vLgWy4/B0Qdest0Gy3E8QgaKJXQV3zRczNd9zrk1dmwVl6u
141Yd+JfgNIeIFP6HWeu/C3wIJ60WDBuGY1
142-----END PUBLIC KEY-----
143"#;
144
145 #[test]
146 fn authority_builds_from_es384_pem() {
147 let authority = JwtAuthority::<JwtClaims<()>>::from_es384_pem(PRIVATE_PEM, PUBLIC_PEM)
148 .expect("authority should be created from valid ES384 PEM");
149
150 assert!(!authority.key_id().is_empty());
151 assert_eq!(
152 authority.key_id(),
153 authority.jwks_provider().key_id().expect("kid must be set")
154 );
155 assert_eq!(authority.jwks_provider().document().keys.len(), 1);
156 }
157
158 #[test]
159 fn authority_kid_is_stable_across_constructions() {
160 let a1 = JwtAuthority::<JwtClaims<()>>::from_es384_pem(PRIVATE_PEM, PUBLIC_PEM)
161 .expect("first construction");
162 let a2 = JwtAuthority::<JwtClaims<()>>::from_es384_pem(PRIVATE_PEM, PUBLIC_PEM)
163 .expect("second construction");
164
165 assert_eq!(a1.key_id(), a2.key_id());
166 }
167
168 #[test]
169 fn authority_codec_can_sign_and_verify() {
170 use jsonwebtoken::crypto::rust_crypto::DEFAULT_PROVIDER as JWT_CRYPTO_PROVIDER;
171 use webgates_core::accounts::Account;
172 use webgates_core::groups::Group;
173 use webgates_core::roles::Role;
174
175 let _ = JWT_CRYPTO_PROVIDER.install_default();
176
177 let authority = JwtAuthority::<JwtClaims<Account<Role, Group>>>::from_es384_pem(
178 PRIVATE_PEM,
179 PUBLIC_PEM,
180 )
181 .expect("authority should be created");
182
183 let account = Account::<Role, Group>::new("test-user");
184 let exp = chrono::Utc::now().timestamp() as u64 + 60;
185 let claims = JwtClaims::new(account, RegisteredClaims::new("test-issuer", exp));
186
187 let codec = authority.codec();
188 let encoded = codec.encode(&claims).expect("encoding should succeed");
189 let decoded = codec.decode(&encoded).expect("decoding should succeed");
190
191 assert_eq!(decoded.registered_claims.issuer, "test-issuer");
192 }
193}