Use basic-jwt crate to sign / validate JWT
All checks were successful
continuous-integration/drone/pr Build is passing
continuous-integration/drone/push Build is passing

This commit is contained in:
2024-04-20 13:33:32 +02:00
parent 9432b3a8fd
commit 23e0b33b43
8 changed files with 25 additions and 150 deletions

View File

@ -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(),

View File

@ -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 {

View File

@ -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::<TokenClaims>(
let claims = match basic_jwt::validate_jwt::<TokenClaims>(
&token
.pub_key
.clone()

View File

@ -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<C: Serialize>(key: &TokenPrivKey, claims: &C) -> anyhow::Result<String> {
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<E: DeserializeOwned>(key: &TokenPubKey, token: &str) -> anyhow::Result<E> {
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::<E>(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::<Claims>(&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::<Claims>(&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::<Claims>(&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::<Claims>(&pub_key, &format!("{jwt}bad"))
.expect_err("JWT should not have validated!");
}
}

View File

@ -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;