1use crate::error::{Error, Result};
2use axum::extract::FromRef;
3use base64::Engine;
4use jsonwebtoken::{
5 jwk::{AlgorithmParameters, RSAKeyParameters},
6 Algorithm, DecodingKey,
7};
8use rsa::{
9 pkcs1::{DecodeRsaPublicKey, EncodeRsaPublicKey},
10 PublicKeyParts, RsaPublicKey,
11};
12
13#[derive(Clone)]
16pub struct PublicKey {
17 raw: Vec<u8>,
18 key: DecodingKey,
19 rsa_key: rsa::RsaPublicKey,
20}
21
22impl PublicKey {
23 pub fn new(raw: Vec<u8>) -> Result<Self> {
25 let contents = raw.clone();
26 let str = std::str::from_utf8(&contents)?;
27 Ok(Self {
28 key: DecodingKey::from_rsa_pem(&raw)?,
29 raw,
30 rsa_key: rsa::RsaPublicKey::from_pkcs1_pem(str)?,
31 })
32 }
33
34 pub fn parse_from_jwks(jwks_str: &str) -> Result<Vec<Self>> {
35 let jwks: jsonwebtoken::jwk::JwkSet = serde_json::from_str(jwks_str)?;
36
37 jwks.keys.into_iter().map(Self::try_from).collect()
38 }
39
40 pub fn parse_from_pem(pem_str: &str) -> Result<Self> {
41 let rsa_key = rsa::RsaPublicKey::from_pkcs1_pem(pem_str)?;
42 Self::try_from(rsa_key)
43 }
44}
45
46impl TryFrom<rsa::RsaPublicKey> for PublicKey {
47 type Error = Error;
48
49 fn try_from(key: RsaPublicKey) -> Result<Self> {
50 let data = key.to_pkcs1_pem(rsa::pkcs1::LineEnding::LF)?;
51 let data = data.as_bytes().to_vec();
52 Self::new(data)
53 }
54}
55
56impl TryFrom<jsonwebtoken::jwk::Jwk> for PublicKey {
57 type Error = Error;
58
59 fn try_from(jwk: jsonwebtoken::jwk::Jwk) -> Result<Self> {
60 match jwk.common.algorithm {
61 Some(Algorithm::RS256) => {
62 let rsa_params = match jwk.algorithm {
63 AlgorithmParameters::RSA(params) => params,
64 _ => unimplemented!(),
65 };
66
67 let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
68
69 let n_bytes = engine.decode(rsa_params.n.as_bytes())?;
70 let e_bytes = engine.decode(rsa_params.e.as_bytes())?;
71
72 let n = rsa::BigUint::from_bytes_be(&n_bytes);
73 let e = rsa::BigUint::from_bytes_be(&e_bytes);
74
75 let rsa_key = rsa::RsaPublicKey::new(n, e)?;
76 Self::try_from(rsa_key)
77 }
78 _ => unimplemented!(),
79 }
80 }
81}
82
83impl From<PublicKey> for jsonwebtoken::jwk::Jwk {
84 fn from(key: PublicKey) -> Self {
85 let common = jsonwebtoken::jwk::CommonParameters {
86 public_key_use: Some(jsonwebtoken::jwk::PublicKeyUse::Signature),
87 key_id: Some("1".to_string()),
88 algorithm: Some(Algorithm::RS256),
89 ..Default::default()
90 };
91
92 let n = key.rsa_key.n().to_bytes_be();
93 let e = key.rsa_key.e().to_bytes_be();
94
95 let engine = base64::engine::general_purpose::URL_SAFE_NO_PAD;
96 let n_base64 = engine.encode(n);
97 let e_base64 = engine.encode(e);
98
99 let algorithm = AlgorithmParameters::RSA(RSAKeyParameters {
100 key_type: jsonwebtoken::jwk::RSAKeyType::RSA,
101 n: n_base64,
102 e: e_base64,
103 });
104
105 jsonwebtoken::jwk::Jwk { common, algorithm }
106 }
107}
108
109impl AsRef<[u8]> for PublicKey {
110 fn as_ref(&self) -> &[u8] {
111 &self.raw
112 }
113}
114
115impl FromRef<PublicKey> for DecodingKey {
116 fn from_ref(key: &PublicKey) -> Self {
117 key.key.clone()
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::{error::Result, Claims};
125
126 const RSA_PUBLIC_KEY: &str = "-----BEGIN RSA PUBLIC KEY-----
127MIIBCgKCAQEAwmNDtlawWXevdB5fDT4gkmBl/al/Ij9KcyAZc78O2m3Cnb9nfcSl
128MuiChPP3RsehaoyXGl8dCV39gI0bEViAX5/OF5x39mFJdhe00+bUe4X7mKgItA6L
129N10PeIhJR59zis8URSzVDm7OsA2HLbrRxM5k0kapNBmFN1+Us2XnNn0V2/QR9ite
130Qgkpe4I5/adAwj+fQo+dY13j5N11GP7jhnEmw7MiNjo0Kv9h1ZJJHDlb9edSmA6d
131g6jOxhlE4kQAJYObCF2TKhBcO+szl/zFVm9E55yiVYjWKk0R4uVXp9natMsQcbOL
132OAc5t3RZmw5L0Nikhso62g9oefgwWOIPJwIDAQAB
133-----END RSA PUBLIC KEY-----";
134
135 const JWK_JSON: &str = r#"{"use":"sig","alg":"RS256","kid":"1","kty":"RSA","n":"wmNDtlawWXevdB5fDT4gkmBl_al_Ij9KcyAZc78O2m3Cnb9nfcSlMuiChPP3RsehaoyXGl8dCV39gI0bEViAX5_OF5x39mFJdhe00-bUe4X7mKgItA6LN10PeIhJR59zis8URSzVDm7OsA2HLbrRxM5k0kapNBmFN1-Us2XnNn0V2_QR9iteQgkpe4I5_adAwj-fQo-dY13j5N11GP7jhnEmw7MiNjo0Kv9h1ZJJHDlb9edSmA6dg6jOxhlE4kQAJYObCF2TKhBcO-szl_zFVm9E55yiVYjWKk0R4uVXp9natMsQcbOLOAc5t3RZmw5L0Nikhso62g9oefgwWOIPJw","e":"AQAB"}"#;
136
137 const JWKS_JSON: &str = r#"{"keys":[{"use":"sig","alg":"RS256","kid":"1","kty":"RSA","n":"wmNDtlawWXevdB5fDT4gkmBl_al_Ij9KcyAZc78O2m3Cnb9nfcSlMuiChPP3RsehaoyXGl8dCV39gI0bEViAX5_OF5x39mFJdhe00-bUe4X7mKgItA6LN10PeIhJR59zis8URSzVDm7OsA2HLbrRxM5k0kapNBmFN1-Us2XnNn0V2_QR9iteQgkpe4I5_adAwj-fQo-dY13j5N11GP7jhnEmw7MiNjo0Kv9h1ZJJHDlb9edSmA6dg6jOxhlE4kQAJYObCF2TKhBcO-szl_zFVm9E55yiVYjWKk0R4uVXp9natMsQcbOLOAc5t3RZmw5L0Nikhso62g9oefgwWOIPJw","e":"AQAB"}]}"#;
138
139 const JWT: &str = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIwMUdWRjRCQ00zMlRUTTE5WUJGQjJaTkU0UiIsImV4cCI6MTY3OTUzNTg5MSwiaWF0IjoxNjc4OTMxMDkxfQ.Uug8lF11bjBUYzEnNr-HIgwlkckVxtUh5RQyEAinmUzB4nZiMQEb9cTx9LsU0mLTLz7lHOdo49CmjyNqEnn-KDX9p0dn97AZISHihIjEtfBbbmh3DqsLBN_QzKyB_h4gLGT5Fx33tgYkx8Sbpk8GmIlidUsjrHqENPOoI3dZssdgi4z0TcZ7cSJgpnNy-fb63oHy0_Gmfu5viNGi8V4ydGR7hBFt6-SBr9aOF2Y6FeePmxKDdIz4PuOa0gQCGbiIdX1agx2SXnWAUYdnW4IlEUo_Yv-N-Rbf_YhAG0uENdcL6p-NfkMhey0stb9UMj8KVU6KKrP6TRn_TfO9jiF7qQ";
140
141 #[tokio::test]
144 async fn jwk_to_key() -> Result<()> {
145 let jwk: jsonwebtoken::jwk::Jwk = serde_json::from_str(JWK_JSON)?;
146 let key = PublicKey::try_from(jwk)?;
147
148 let mut validation = jsonwebtoken::Validation::new(Algorithm::RS256);
150 validation.validate_exp = false;
151
152 let key: jsonwebtoken::DecodingKey = axum::extract::FromRef::from_ref(&key);
153 Claims::decode_validation(JWT, &key, &validation).await?;
154
155 Ok(())
156 }
157
158 #[tokio::test]
161 async fn jwks_to_key() -> Result<()> {
162 let key_set = PublicKey::parse_from_jwks(JWKS_JSON)?;
163 let key = key_set.first().unwrap();
164
165 let mut validation = jsonwebtoken::Validation::new(Algorithm::RS256);
167 validation.validate_exp = false;
168
169 let key: jsonwebtoken::DecodingKey = axum::extract::FromRef::from_ref(key);
170 Claims::decode_validation(JWT, &key, &validation).await?;
171
172 Ok(())
173 }
174
175 #[tokio::test]
178 async fn pem_to_key() -> Result<()> {
179 let key = PublicKey::parse_from_pem(RSA_PUBLIC_KEY)?;
180
181 let mut validation = jsonwebtoken::Validation::new(Algorithm::RS256);
182 validation.validate_exp = false;
183
184 let key: jsonwebtoken::DecodingKey = axum::extract::FromRef::from_ref(&key);
185 Claims::decode_validation(JWT, &key, &validation).await?;
186
187 Ok(())
188 }
189}