basic_jwt/
lib.rs

1use elliptic_curve::pkcs8::EncodePublicKey;
2use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Validation};
3use p384::ecdsa::signature::rand_core::OsRng;
4use p384::ecdsa::{SigningKey, VerifyingKey};
5use p384::pkcs8::{EncodePrivateKey, LineEnding};
6use serde::Serialize;
7use serde::de::DeserializeOwned;
8use std::str::FromStr;
9
10#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)]
11#[serde(tag = "alg")]
12pub enum JWTPublicKey {
13    /// ECDSA with SHA2-384 variant
14    ES384 {
15        #[serde(rename = "pub")]
16        public: String,
17    },
18}
19
20#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
21#[serde(tag = "alg")]
22pub enum JWTPrivateKey {
23    ES384 { r#priv: String },
24}
25
26impl JWTPrivateKey {
27    /// Generate a new ES384 signing key
28    pub fn generate_ec384_signing_key() -> anyhow::Result<Self> {
29        let signing_key = SigningKey::random(&mut OsRng);
30        let priv_pem = signing_key
31            .to_pkcs8_der()?
32            .to_pem("PRIVATE KEY", LineEnding::LF)?
33            .to_string();
34
35        Ok(Self::ES384 { r#priv: priv_pem })
36    }
37
38    /// Get associated public key
39    pub fn to_public_key(&self) -> anyhow::Result<JWTPublicKey> {
40        match self {
41            JWTPrivateKey::ES384 { r#priv } => {
42                let signing_key = SigningKey::from_str(r#priv)?;
43
44                let pub_key = VerifyingKey::from(signing_key);
45                let pub_pem = pub_key.to_public_key_pem(LineEnding::LF)?;
46
47                Ok(JWTPublicKey::ES384 { public: pub_pem })
48            }
49        }
50    }
51
52    /// Sign a JWT
53    pub fn sign_jwt<C: Serialize>(&self, claims: &C) -> anyhow::Result<String> {
54        match self {
55            JWTPrivateKey::ES384 { r#priv } => {
56                let encoding_key = EncodingKey::from_ec_pem(r#priv.as_bytes())?;
57
58                Ok(jsonwebtoken::encode(
59                    &jsonwebtoken::Header::new(Algorithm::ES384),
60                    &claims,
61                    &encoding_key,
62                )?)
63            }
64        }
65    }
66}
67
68impl JWTPublicKey {
69    /// Validate a given JWT
70    pub fn validate_jwt<E: DeserializeOwned + Clone>(&self, jwt: &str) -> anyhow::Result<E> {
71        match self {
72            JWTPublicKey::ES384 { public } => {
73                let decoding_key = DecodingKey::from_ec_pem(public.as_bytes())?;
74
75                let validation = Validation::new(Algorithm::ES384);
76                Ok(jsonwebtoken::decode::<E>(jwt, &decoding_key, &validation)?.claims)
77            }
78        }
79    }
80}
81
82#[cfg(test)]
83mod test {
84    use std::time::{SystemTime, UNIX_EPOCH};
85
86    use crate::JWTPrivateKey;
87    use serde::{Deserialize, Serialize};
88
89    fn time() -> u64 {
90        SystemTime::now()
91            .duration_since(UNIX_EPOCH)
92            .unwrap()
93            .as_secs()
94    }
95
96    #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)]
97    pub struct Claims {
98        sub: String,
99        exp: u64,
100    }
101
102    impl Default for Claims {
103        fn default() -> Self {
104            Self {
105                sub: "my-sub".to_string(),
106                exp: time() + 100,
107            }
108        }
109    }
110
111    #[test]
112    fn jwt_encode_sign_verify_valid() {
113        let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
114        let pub_key = priv_key.to_public_key().unwrap();
115
116        let claims = Claims::default();
117        let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
118        let claims_out = pub_key
119            .validate_jwt::<Claims>(&jwt)
120            .expect("Failed to validate JWT!");
121
122        assert_eq!(claims, claims_out)
123    }
124
125    #[test]
126    fn jwt_encode_sign_verify_invalid_key() {
127        let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
128        let pub_key_2 = JWTPrivateKey::generate_ec384_signing_key()
129            .unwrap()
130            .to_public_key()
131            .unwrap();
132
133        let claims = Claims::default();
134        let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
135        pub_key_2
136            .validate_jwt::<Claims>(&jwt)
137            .expect_err("JWT should not have validated!");
138    }
139
140    #[test]
141    fn jwt_verify_random_string() {
142        let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
143        let pub_key = priv_key.to_public_key().unwrap();
144
145        pub_key
146            .validate_jwt::<Claims>("random_string")
147            .expect_err("JWT should not have validated!");
148    }
149
150    #[test]
151    fn jwt_expired() {
152        let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
153        let pub_key = priv_key.to_public_key().unwrap();
154
155        let claims = Claims {
156            exp: time() - 100,
157            ..Default::default()
158        };
159        let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
160        pub_key
161            .validate_jwt::<Claims>(&jwt)
162            .expect_err("JWT should not have validated!");
163    }
164
165    #[test]
166    fn jwt_invalid_signature() {
167        let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap();
168        let pub_key = priv_key.to_public_key().unwrap();
169
170        let claims = Claims::default();
171        let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!");
172        pub_key
173            .validate_jwt::<Claims>(&format!("{jwt}bad"))
174            .expect_err("JWT should not have validated!");
175    }
176}