From aea0ccad9594a9bcdd75fe1ccacadaaddabbae82 Mon Sep 17 00:00:00 2001 From: Christian_Yemele Date: Wed, 19 Mar 2025 14:03:10 +0100 Subject: [PATCH] feat(utils): add key generation and token parsing utilities; update dependencies --- Cargo.lock | 121 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 33 ++++++----- src/model.rs | 18 +++++- src/utils/keygen.rs | 29 +++++++++ src/utils/mod.rs | 2 + src/utils/token_parser.rs | 41 +++++++++++++ 6 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 src/utils/keygen.rs create mode 100644 src/utils/token_parser.rs diff --git a/Cargo.lock b/Cargo.lock index c7bb92dc..3517387a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,6 +285,33 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half 2.5.0", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -332,6 +359,16 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "coset" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8cc80f631f8307b887faca24dcc3abc427cd0367f6eb6188f6e8f5b7ad8fb" +dependencies = [ + "ciborium", + "ciborium-io", +] + [[package]] name = "cpufeatures" version = "0.2.17" @@ -717,8 +754,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -739,6 +778,22 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "half" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b43ede17f21864e81be2fa654110bf1e793774238d86ef8555c37e6519c0403" + +[[package]] +name = "half" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db2ff139bba50379da6aa0766b52fdcb62cb5b263009b09ed58ba604e14bbd1" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1165,6 +1220,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64 0.22.1", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1412,6 +1482,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint-dig" version = "0.8.4" @@ -1524,6 +1604,16 @@ dependencies = [ "digest", ] +[[package]] +name = "pem" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38af38e8470ac9dee3ce1bae1af9c1671fffc44ddfd8bd1d0a3445bf349a8ef3" +dependencies = [ + "base64 0.22.1", + "serde", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -1688,9 +1778,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" +checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" dependencies = [ "const-oid", "digest", @@ -1827,6 +1917,16 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_cbor" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" +dependencies = [ + "half 1.8.3", + "serde", +] + [[package]] name = "serde_derive" version = "1.0.218" @@ -1970,6 +2070,18 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simple_asn1" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "297f631f50729c8c99b84667867963997ec0b50f32b2a7dbcab828ef0541e8bb" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.11", + "time", +] + [[package]] name = "slab" version = "0.4.9" @@ -2216,10 +2328,15 @@ version = "0.1.0" dependencies = [ "async-trait", "axum", + "base64 0.22.1", + "coset", "dotenvy", + "jsonwebtoken", "mongodb", "once_cell", + "rsa", "serde", + "serde_cbor", "serde_json", "sqlx", "thiserror 2.0.11", diff --git a/Cargo.toml b/Cargo.toml index 54b75911..365479a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,17 +5,22 @@ version = "0.1.0" edition = "2021" [dependencies] -async-trait = "0.1.86" -axum = { version = "0.8.1", features = ["macros"] } -dotenvy = "0.15.7" -mongodb = "3.2.1" -once_cell = "1.20.3" -serde = { version = "1.0.218", features = ["derive"] } -serde_json = "1.0.139" -sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio"] } -thiserror = "2.0.11" -tokio = { version = "1.16.1", features = ["full"] } -tower = "0.5.2" -tower-http = { version = "0.6.2", features = ["cors", "trace", "catch-panic"] } -tracing = "0.1.41" -tracing-subscriber = "0.3.19" +async-trait = "0.1" +axum = { version = "0.8", features = ["macros"] } +base64 = "0.22" +coset = "0.3" +dotenvy = "0.15" +jsonwebtoken = "9.3" +mongodb = "3.2" +once_cell = "1.20" +rsa = "0.9" +serde = { version = "1.0", features = ["derive"] } +serde_cbor = "0.11" +serde_json = "1.0" +sqlx = { version = "0.8", features = ["postgres", "runtime-tokio"] } +thiserror = "2.0" +tokio = { version = "1.16", features = ["full"] } +tower = "0.5" +tower-http = { version = "0.6", features = ["cors", "trace", "catch-panic"] } +tracing = "0.1" +tracing-subscriber = "0.3" diff --git a/src/model.rs b/src/model.rs index b8024e4b..533621b8 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,4 +1,4 @@ -use std::error::Error; +use std::{error::Error, str::FromStr}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -85,3 +85,19 @@ impl StatusListToken { } } } + +pub enum StatusType { + JWT, + CWT, +} + +impl FromStr for StatusType { + type Err = String; + fn from_str(s: &str) -> Result { + match s.to_uppercase().as_str() { + "jwt" => Ok(Self::JWT), + "cwt" => Ok(Self::CWT), + _ => Err("Unknown status type".to_string()), + } + } +} diff --git a/src/utils/keygen.rs b/src/utils/keygen.rs new file mode 100644 index 00000000..53fd718c --- /dev/null +++ b/src/utils/keygen.rs @@ -0,0 +1,29 @@ +use std::{env, fs, path::Path}; + +use rsa::{pkcs1::EncodeRsaPrivateKey, pkcs8::DecodePrivateKey, rand_core::OsRng, RsaPrivateKey}; + +#[inline] +pub fn get_or_generate_private_key() -> Result { + let path_str = + env::var("KEY_STORAGE").map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let path = Path::new(&path_str); + + if path.exists() && path.metadata()?.len() > 0 { + tracing::info!("Loading existing private key..."); + let pem = fs::read_to_string(path)?; + let private_key = RsaPrivateKey::from_pkcs8_pem(&pem) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + Ok(private_key) + } else { + tracing::info!("Generating new RSA key pair..."); + let private_key = RsaPrivateKey::new(&mut OsRng, 2048) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + let pem = private_key + .to_pkcs1_pem(rsa::pkcs8::LineEnding::LF) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))? + .to_string(); + fs::write(path, pem)?; + tracing::info!("Private key saved."); + Ok(private_key) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 266c62ac..8dba7009 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1 +1,3 @@ pub mod state; +pub mod keygen; +pub mod token_parser; \ No newline at end of file diff --git a/src/utils/token_parser.rs b/src/utils/token_parser.rs new file mode 100644 index 00000000..e2dfd7fa --- /dev/null +++ b/src/utils/token_parser.rs @@ -0,0 +1,41 @@ +use jsonwebtoken::{encode, EncodingKey, Header}; +use std::io::Error; + +use crate::model::{StatusListToken, StatusType}; + +pub fn parse_token( + token: StatusListToken, + key: EncodingKey, + status_type: StatusType, +) -> Result { + match status_type { + StatusType::JWT => { + let header = Header::default(); + let jwt = match encode(&header, &token, &key) { + Ok(jwt) => Ok(jwt), + Err(e) => { + tracing::error!("{}", e); + Err(std::io::Error::new( + std::io::ErrorKind::Other, + "failed to encode jwt", + )) + } + }; + jwt + } + + StatusType::CWT => { + + // for feature implmentation + Ok(String::new()) + // Serialize the token (claims) to CBOR bytes + // let claims_bytes = serde_cbor::to_vec(&token).map_err(|e| { + // tracing::error!("CBOR serialization failed: {}", e); + // std::io::Error::new(std::io::ErrorKind::Other, "failed to serialize CBOR claims") + // })?; + + // // Return the CWT as a base64 or hex string if needed + // Ok(Base64Encoder::encode(&claims_bytes)) + } + } +}