diff --git a/virtweb_backend/Cargo.lock b/virtweb_backend/Cargo.lock index 22d0e51..67934a7 100644 --- a/virtweb_backend/Cargo.lock +++ b/virtweb_backend/Cargo.lock @@ -598,6 +598,20 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +[[package]] +name = "basic-jwt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20108a39851e5d33abc309904eb8f65c9b7c08c1ef3f107dfbe455de75111699" +dependencies = [ + "anyhow", + "elliptic-curve", + "jsonwebtoken", + "p384", + "rand", + "serde", +] + [[package]] name = "bincode" version = "2.0.0-rc.3" @@ -2720,18 +2734,18 @@ checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.198" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" dependencies = [ "proc-macro2", "quote", @@ -3372,16 +3386,15 @@ dependencies = [ "actix-web", "actix-web-actors", "anyhow", + "basic-jwt", "bytes", "clap", "dotenvy", - "elliptic-curve", "env_logger", "futures", "futures-util", "image", "ipnetwork", - "jsonwebtoken", "lazy-regex", "lazy_static", "light-openid", @@ -3389,7 +3402,6 @@ dependencies = [ "mime_guess", "nix", "num", - "p384", "quick-xml", "rand", "reqwest", diff --git a/virtweb_backend/Cargo.toml b/virtweb_backend/Cargo.toml index a5f7346..5cc663f 100644 --- a/virtweb_backend/Cargo.toml +++ b/virtweb_backend/Cargo.toml @@ -45,6 +45,4 @@ rust-embed = { version = "8.3.0" } mime_guess = "2.0.4" dotenvy = "0.15.7" nix = { version = "0.28.0", features = ["net"] } -jsonwebtoken = "9.3.0" -elliptic-curve = { version = "0.13.8", features = ["pkcs8","pem" ] } -p384 = { version = "0.13.0", features = ["ecdsa", "pkcs8", "pem"] } \ No newline at end of file +basic-jwt = "0.1.0" \ No newline at end of file diff --git a/virtweb_backend/examples/api_curl.rs b/virtweb_backend/examples/api_curl.rs index cf363b8..0a5c3eb 100644 --- a/virtweb_backend/examples/api_curl.rs +++ b/virtweb_backend/examples/api_curl.rs @@ -1,10 +1,10 @@ +use basic_jwt::{sign_jwt, TokenPrivKey}; use clap::Parser; use std::os::unix::prelude::CommandExt; use std::process::Command; use std::str::FromStr; use virtweb_backend::api_tokens::TokenVerb; use virtweb_backend::extractors::api_auth_extractor::TokenClaims; -use virtweb_backend::utils::jwt_utils::{sign_jwt, TokenPrivKey}; use virtweb_backend::utils::time_utils::time; /// curl wrapper to query Virtweb backend API diff --git a/virtweb_backend/src/api_tokens.rs b/virtweb_backend/src/api_tokens.rs index 6479d99..be9fa6c 100644 --- a/virtweb_backend/src/api_tokens.rs +++ b/virtweb_backend/src/api_tokens.rs @@ -2,10 +2,9 @@ use crate::app_config::AppConfig; use crate::constants; -use crate::utils::jwt_utils; -use crate::utils::jwt_utils::{TokenPrivKey, TokenPubKey}; use crate::utils::time_utils::time; use actix_http::Method; +use basic_jwt::{TokenPrivKey, TokenPubKey}; use std::path::Path; use std::str::FromStr; @@ -197,7 +196,7 @@ impl NewToken { /// Create a new Token pub async fn create(t: &NewToken) -> anyhow::Result<(Token, TokenPrivKey)> { - let (pub_key, priv_key) = jwt_utils::generate_key_pair()?; + let (pub_key, priv_key) = basic_jwt::generate_ec384_keypair()?; let token = Token { name: t.name.to_string(), diff --git a/virtweb_backend/src/controllers/api_tokens_controller.rs b/virtweb_backend/src/controllers/api_tokens_controller.rs index c47647e..39db79c 100644 --- a/virtweb_backend/src/controllers/api_tokens_controller.rs +++ b/virtweb_backend/src/controllers/api_tokens_controller.rs @@ -4,8 +4,8 @@ use crate::api_tokens; use crate::api_tokens::{NewToken, TokenID, TokenRights}; use crate::controllers::api_tokens_controller::rest_token::RestToken; use crate::controllers::HttpResult; -use crate::utils::jwt_utils::TokenPrivKey; use actix_web::{web, HttpResponse}; +use basic_jwt::TokenPrivKey; /// Create a special module for REST token to enforce usage of constructor function mod rest_token { diff --git a/virtweb_backend/src/extractors/api_auth_extractor.rs b/virtweb_backend/src/extractors/api_auth_extractor.rs index 8a26a71..0a5aaea 100644 --- a/virtweb_backend/src/extractors/api_auth_extractor.rs +++ b/virtweb_backend/src/extractors/api_auth_extractor.rs @@ -1,7 +1,6 @@ use crate::api_tokens::{Token, TokenID, TokenVerb}; use crate::api_tokens; -use crate::utils::jwt_utils; use crate::utils::time_utils::time; use actix_remote_ip::RemoteIP; use actix_web::dev::Payload; @@ -72,7 +71,7 @@ impl FromRequest for ApiAuthExtractor { return Err(ErrorBadRequest("Unable to validate token!")); } - let claims = match jwt_utils::validate_jwt::( + let claims = match basic_jwt::validate_jwt::( &token .pub_key .clone() diff --git a/virtweb_backend/src/utils/jwt_utils.rs b/virtweb_backend/src/utils/jwt_utils.rs deleted file mode 100644 index 4b0b5c6..0000000 --- a/virtweb_backend/src/utils/jwt_utils.rs +++ /dev/null @@ -1,132 +0,0 @@ -use elliptic_curve::pkcs8::EncodePublicKey; -use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Validation}; -use p384::ecdsa::{SigningKey, VerifyingKey}; -use p384::pkcs8::{EncodePrivateKey, LineEnding}; -use rand::rngs::OsRng; -use serde::de::DeserializeOwned; -use serde::Serialize; - -#[derive(serde::Serialize, serde::Deserialize, Clone, Debug, Eq, PartialEq)] -#[serde(tag = "alg")] -pub enum TokenPubKey { - /// ECDSA with SHA2-384 variant - ES384 { r#pub: String }, -} - -#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] -#[serde(tag = "alg")] -pub enum TokenPrivKey { - ES384 { r#priv: String }, -} - -/// Generate a new token keypair -pub fn generate_key_pair() -> 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(); - - let pub_key = VerifyingKey::from(signing_key); - let pub_pem = pub_key.to_public_key_pem(LineEnding::LF)?; - - Ok(( - TokenPubKey::ES384 { r#pub: pub_pem }, - TokenPrivKey::ES384 { r#priv: priv_pem }, - )) -} - -/// Sign JWT with a private key -pub fn sign_jwt(key: &TokenPrivKey, claims: &C) -> anyhow::Result { - match key { - TokenPrivKey::ES384 { r#priv } => { - let encoding_key = EncodingKey::from_ec_pem(r#priv.as_bytes())?; - - Ok(jsonwebtoken::encode( - &jsonwebtoken::Header::new(Algorithm::ES384), - &claims, - &encoding_key, - )?) - } - } -} - -/// Validate a given JWT -pub fn validate_jwt(key: &TokenPubKey, token: &str) -> anyhow::Result { - match key { - TokenPubKey::ES384 { r#pub } => { - let decoding_key = DecodingKey::from_ec_pem(r#pub.as_bytes())?; - - let validation = Validation::new(Algorithm::ES384); - Ok(jsonwebtoken::decode::(token, &decoding_key, &validation)?.claims) - } - } -} - -#[cfg(test)] -mod test { - use crate::utils::jwt_utils::{generate_key_pair, sign_jwt, validate_jwt}; - use crate::utils::time_utils::time; - use serde::{Deserialize, Serialize}; - - #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] - pub struct Claims { - sub: String, - exp: u64, - } - - impl Default for Claims { - fn default() -> Self { - Self { - sub: "my-sub".to_string(), - exp: time() + 100, - } - } - } - - #[test] - fn jwt_encode_sign_verify_valid() { - let (pub_key, priv_key) = generate_key_pair().unwrap(); - let claims = Claims::default(); - let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!"); - let claims_out = validate_jwt(&pub_key, &jwt).expect("Failed to validate JWT!"); - - assert_eq!(claims, claims_out) - } - - #[test] - fn jwt_encode_sign_verify_invalid_key() { - let (_pub_key, priv_key) = generate_key_pair().unwrap(); - let (pub_key_2, _priv_key_2) = generate_key_pair().unwrap(); - let claims = Claims::default(); - let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!"); - validate_jwt::(&pub_key_2, &jwt).expect_err("JWT should not have validated!"); - } - - #[test] - fn jwt_verify_random_string() { - let (pub_key, _priv_key) = generate_key_pair().unwrap(); - validate_jwt::(&pub_key, "random_string") - .expect_err("JWT should not have validated!"); - } - - #[test] - fn jwt_expired() { - let (pub_key, priv_key) = generate_key_pair().unwrap(); - let claims = Claims { - exp: time() - 100, - ..Default::default() - }; - let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!"); - validate_jwt::(&pub_key, &jwt).expect_err("JWT should not have validated!"); - } - - #[test] - fn jwt_invalid_signature() { - let (pub_key, priv_key) = generate_key_pair().unwrap(); - let claims = Claims::default(); - let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!"); - validate_jwt::(&pub_key, &format!("{jwt}bad")) - .expect_err("JWT should not have validated!"); - } -} diff --git a/virtweb_backend/src/utils/mod.rs b/virtweb_backend/src/utils/mod.rs index 2c0528e..f10b3e2 100644 --- a/virtweb_backend/src/utils/mod.rs +++ b/virtweb_backend/src/utils/mod.rs @@ -1,6 +1,5 @@ pub mod disks_utils; pub mod files_utils; -pub mod jwt_utils; pub mod net_utils; pub mod rand_utils; pub mod time_utils;