diff --git a/Cargo.lock b/Cargo.lock index 8c7eed4..7010fc0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,7 +40,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "basic-jwt" -version = "0.1.0" +version = "0.2.0" dependencies = [ "anyhow", "elliptic-curve", diff --git a/Cargo.toml b/Cargo.toml index 9dc90e8..e1e37cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "basic-jwt" -version = "0.1.0" +version = "0.2.0" edition = "2021" authors = ["Pierre Hubert "] description = "Basic JWT signing and verification library" @@ -17,4 +17,4 @@ serde = { version = "1.0.198", features = ["derive"] } anyhow = "1.0.82" elliptic-curve = { version = "0.13.8", features = ["pkcs8", "pem"] } p384 = { version = "0.13.0", features = ["ecdsa", "pkcs8", "pem"] } -jsonwebtoken = "9.3.0" \ No newline at end of file +jsonwebtoken = "9.3.0" diff --git a/README.md b/README.md index 4c8f5ad..1dc1e9a 100644 --- a/README.md +++ b/README.md @@ -10,13 +10,16 @@ Basic usage: ```rust let claims = ...; // note : claims must be serializable -// Generate a key pair. Public and private key are both serializable -let (pub_key, priv_key) = generate_ec384_keypair().unwrap(); +// Generate a key pair. Private and public key are both serializable +let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap(); +let pub_key = priv_key.to_public_key().unwrap(); // Create a JWT for the given claims (note: standard claims: sub, iss, ...) are not // automatically added if they are missing -let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!"); +let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!"); // Validate signed JWT -let claims_out = validate_jwt::(&pub_key, &jwt).expect("Failed to validate JWT!"); +let claims_out = pub_key + .validate_jwt::(&jwt) + .expect("Failed to validate JWT!"); ``` diff --git a/src/lib.rs b/src/lib.rs index eb791b6..00c2c39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,41 +5,54 @@ use p384::pkcs8::{EncodePrivateKey, LineEnding}; use rand::rngs::OsRng; use serde::de::DeserializeOwned; use serde::Serialize; +use std::str::FromStr; #[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)] #[serde(tag = "alg")] -pub enum TokenPubKey { +pub enum JWTPublicKey { /// ECDSA with SHA2-384 variant - ES384 { r#pub: String }, + ES384 { + #[serde(rename = "pub")] + public: String, + }, } #[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] #[serde(tag = "alg")] -pub enum TokenPrivKey { +pub enum JWTPrivateKey { ES384 { r#priv: String }, } -/// Generate a new ES384 keypair -pub fn generate_ec384_keypair() -> anyhow::Result<(TokenPubKey, TokenPrivKey)> { - let signing_key = SigningKey::random(&mut OsRng); - let priv_pem = signing_key - .to_pkcs8_der()? - .to_pem("PRIVATE KEY", LineEnding::LF)? - .to_string(); +impl JWTPrivateKey { + /// Generate a new ES384 signing key + pub fn generate_ec384_signing_key() -> anyhow::Result { + let signing_key = SigningKey::random(&mut OsRng); + let priv_pem = signing_key + .to_pkcs8_der()? + .to_pem("PRIVATE KEY", LineEnding::LF)? + .to_string(); - let pub_key = VerifyingKey::from(signing_key); - let pub_pem = pub_key.to_public_key_pem(LineEnding::LF)?; + Ok(Self::ES384 { r#priv: priv_pem }) + } + + /// Get associated public key + pub fn to_public_key(&self) -> anyhow::Result { + match self { + JWTPrivateKey::ES384 { r#priv } => { + let signing_key = SigningKey::from_str(r#priv)?; + + let pub_key = VerifyingKey::from(signing_key); + let pub_pem = pub_key.to_public_key_pem(LineEnding::LF)?; + + Ok(JWTPublicKey::ES384 { public: pub_pem }) + } + } + } - Ok(( - TokenPubKey::ES384 { r#pub: pub_pem }, - TokenPrivKey::ES384 { r#priv: priv_pem }, - )) -} -impl TokenPrivKey { /// Sign a JWT pub fn sign_jwt(&self, claims: &C) -> anyhow::Result { match self { - TokenPrivKey::ES384 { r#priv } => { + JWTPrivateKey::ES384 { r#priv } => { let encoding_key = EncodingKey::from_ec_pem(r#priv.as_bytes())?; Ok(jsonwebtoken::encode( @@ -52,12 +65,12 @@ impl TokenPrivKey { } } -impl TokenPubKey { +impl JWTPublicKey { /// Validate a given JWT pub fn validate_jwt(&self, jwt: &str) -> anyhow::Result { match self { - TokenPubKey::ES384 { r#pub } => { - let decoding_key = DecodingKey::from_ec_pem(r#pub.as_bytes())?; + JWTPublicKey::ES384 { public } => { + let decoding_key = DecodingKey::from_ec_pem(public.as_bytes())?; let validation = Validation::new(Algorithm::ES384); Ok(jsonwebtoken::decode::(jwt, &decoding_key, &validation)?.claims) @@ -68,9 +81,9 @@ impl TokenPubKey { #[cfg(test)] mod test { - use crate::generate_ec384_keypair; use std::time::{SystemTime, UNIX_EPOCH}; + use crate::JWTPrivateKey; use serde::{Deserialize, Serialize}; fn time() -> u64 { @@ -97,7 +110,9 @@ mod test { #[test] fn jwt_encode_sign_verify_valid() { - let (pub_key, priv_key) = generate_ec384_keypair().unwrap(); + let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap(); + let pub_key = priv_key.to_public_key().unwrap(); + let claims = Claims::default(); let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!"); let claims_out = pub_key @@ -109,8 +124,12 @@ mod test { #[test] fn jwt_encode_sign_verify_invalid_key() { - let (_pub_key, priv_key) = generate_ec384_keypair().unwrap(); - let (pub_key_2, _priv_key_2) = generate_ec384_keypair().unwrap(); + let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap(); + let pub_key_2 = JWTPrivateKey::generate_ec384_signing_key() + .unwrap() + .to_public_key() + .unwrap(); + let claims = Claims::default(); let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!"); pub_key_2 @@ -120,7 +139,9 @@ mod test { #[test] fn jwt_verify_random_string() { - let (pub_key, _priv_key) = generate_ec384_keypair().unwrap(); + let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap(); + let pub_key = priv_key.to_public_key().unwrap(); + pub_key .validate_jwt::("random_string") .expect_err("JWT should not have validated!"); @@ -128,7 +149,9 @@ mod test { #[test] fn jwt_expired() { - let (pub_key, priv_key) = generate_ec384_keypair().unwrap(); + let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap(); + let pub_key = priv_key.to_public_key().unwrap(); + let claims = Claims { exp: time() - 100, ..Default::default() @@ -141,7 +164,9 @@ mod test { #[test] fn jwt_invalid_signature() { - let (pub_key, priv_key) = generate_ec384_keypair().unwrap(); + let priv_key = JWTPrivateKey::generate_ec384_signing_key().unwrap(); + let pub_key = priv_key.to_public_key().unwrap(); + let claims = Claims::default(); let jwt = priv_key.sign_jwt(&claims).expect("Failed to sign JWT!"); pub_key