Add API tokens support #9
63
virtweb_backend/Cargo.lock
generated
63
virtweb_backend/Cargo.lock
generated
@ -1224,8 +1224,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1596,6 +1598,21 @@ dependencies = [
|
|||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jsonwebtoken"
|
||||||
|
version = "9.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.21.7",
|
||||||
|
"js-sys",
|
||||||
|
"pem",
|
||||||
|
"ring",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"simple_asn1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "language-tags"
|
name = "language-tags"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -2042,6 +2059,16 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pem"
|
||||||
|
version = "3.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.0",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
@ -2376,6 +2403,21 @@ dependencies = [
|
|||||||
"bytemuck",
|
"bytemuck",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ring"
|
||||||
|
version = "0.17.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"getrandom",
|
||||||
|
"libc",
|
||||||
|
"spin",
|
||||||
|
"untrusted",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rust-embed"
|
name = "rust-embed"
|
||||||
version = "8.3.0"
|
version = "8.3.0"
|
||||||
@ -2620,6 +2662,18 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "simple_asn1"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085"
|
||||||
|
dependencies = [
|
||||||
|
"num-bigint",
|
||||||
|
"num-traits",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "slab"
|
name = "slab"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@ -3020,6 +3074,12 @@ dependencies = [
|
|||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "untrusted"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "url"
|
name = "url"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@ -3137,6 +3197,7 @@ dependencies = [
|
|||||||
"futures-util",
|
"futures-util",
|
||||||
"image",
|
"image",
|
||||||
"ipnetwork",
|
"ipnetwork",
|
||||||
|
"jsonwebtoken",
|
||||||
"lazy-regex",
|
"lazy-regex",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"light-openid",
|
"light-openid",
|
||||||
@ -3144,9 +3205,11 @@ dependencies = [
|
|||||||
"mime_guess",
|
"mime_guess",
|
||||||
"nix",
|
"nix",
|
||||||
"num",
|
"num",
|
||||||
|
"pem",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"rand",
|
"rand",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
|
"ring",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -45,3 +45,6 @@ rust-embed = { version = "8.3.0" }
|
|||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
nix = { version = "0.28.0", features = ["net"] }
|
nix = { version = "0.28.0", features = ["net"] }
|
||||||
|
jsonwebtoken = "9.3.0"
|
||||||
|
ring = "0.17.8"
|
||||||
|
pem = "3.0.4"
|
105
virtweb_backend/src/api_tokens.rs
Normal file
105
virtweb_backend/src/api_tokens.rs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
//! # API tokens management
|
||||||
|
|
||||||
|
use crate::constants;
|
||||||
|
use crate::utils::jwt_utils;
|
||||||
|
use crate::utils::jwt_utils::{TokenPrivKey, TokenPubKey};
|
||||||
|
use crate::utils::time_utils::time;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, Debug)]
|
||||||
|
pub struct TokenID(pub uuid::Uuid);
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
|
pub struct Token {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub id: TokenID,
|
||||||
|
created: u64,
|
||||||
|
updated: u64,
|
||||||
|
pub pub_key: TokenPubKey,
|
||||||
|
pub rights: Vec<TokenRights>,
|
||||||
|
pub last_used: Option<u64>,
|
||||||
|
pub ip_restriction: Option<ipnetwork::IpNetwork>,
|
||||||
|
pub delete_after_inactivity: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
|
pub enum TokenVerb {
|
||||||
|
GET,
|
||||||
|
POST,
|
||||||
|
PUT,
|
||||||
|
PATCH,
|
||||||
|
DELETE,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
|
pub struct TokenRights {
|
||||||
|
verb: TokenVerb,
|
||||||
|
uri: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Structure used to create a token
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
|
pub struct NewToken {
|
||||||
|
pub name: String,
|
||||||
|
pub description: String,
|
||||||
|
pub rights: Vec<TokenRights>,
|
||||||
|
pub ip_restriction: Option<ipnetwork::IpNetwork>,
|
||||||
|
pub delete_after_inactivity: Option<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NewToken {
|
||||||
|
/// Check for error in token
|
||||||
|
pub fn check_error(&self) -> Option<&'static str> {
|
||||||
|
if self.name.len() < constants::API_TOKEN_NAME_MIN_LENGTH {
|
||||||
|
return Some("Name is too short!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.name.len() > constants::API_TOKEN_NAME_MAX_LENGTH {
|
||||||
|
return Some("Name is too long!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.description.len() < constants::API_TOKEN_DESCRIPTION_MIN_LENGTH {
|
||||||
|
return Some("Description is too short!");
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.description.len() > constants::API_TOKEN_DESCRIPTION_MAX_LENGTH {
|
||||||
|
return Some("Description is too long!");
|
||||||
|
}
|
||||||
|
|
||||||
|
for r in &self.rights {
|
||||||
|
if !r.uri.starts_with("/api/") {
|
||||||
|
return Some("All API rights shall start with /api/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(t) = self.delete_after_inactivity {
|
||||||
|
if t < 3600 {
|
||||||
|
return Some("API tokens shall be valid for at least 1 hour!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new Token
|
||||||
|
pub async fn create(token: &NewToken) -> anyhow::Result<(Token, TokenPrivKey)> {
|
||||||
|
let (pub_key, priv_key) = jwt_utils::generate_key_pair()?;
|
||||||
|
|
||||||
|
let full_token = Token {
|
||||||
|
name: token.name.to_string(),
|
||||||
|
description: token.description.to_string(),
|
||||||
|
id: TokenID(uuid::Uuid::new_v4()),
|
||||||
|
created: time(),
|
||||||
|
updated: time(),
|
||||||
|
pub_key,
|
||||||
|
rights: token.rights.clone(),
|
||||||
|
last_used: Some(time()),
|
||||||
|
ip_restriction: token.ip_restriction,
|
||||||
|
delete_after_inactivity: token.delete_after_inactivity,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO : save
|
||||||
|
|
||||||
|
Ok((full_token, priv_key))
|
||||||
|
}
|
@ -268,6 +268,10 @@ impl AppConfig {
|
|||||||
self.definitions_path()
|
self.definitions_path()
|
||||||
.join(format!("nwfilter-{}.json", name.0))
|
.join(format!("nwfilter-{}.json", name.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn api_tokens_path(&self) -> PathBuf {
|
||||||
|
self.storage_path().join(constants::STORAGE_TOKENS_DIR)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, serde::Serialize)]
|
#[derive(Debug, Clone, serde::Serialize)]
|
||||||
|
@ -89,3 +89,18 @@ pub const NAT_MODE_ENV_VAR_NAME: &str = "NAT_MODE";
|
|||||||
|
|
||||||
/// Nat hook file path
|
/// Nat hook file path
|
||||||
pub const NAT_HOOK_PATH: &str = "/etc/libvirt/hooks/network";
|
pub const NAT_HOOK_PATH: &str = "/etc/libvirt/hooks/network";
|
||||||
|
|
||||||
|
/// Directory where API tokens are stored, inside storage directory
|
||||||
|
pub const STORAGE_TOKENS_DIR: &str = "tokens";
|
||||||
|
|
||||||
|
/// API token name min length
|
||||||
|
pub const API_TOKEN_NAME_MIN_LENGTH: usize = 3;
|
||||||
|
|
||||||
|
/// API token name max length
|
||||||
|
pub const API_TOKEN_NAME_MAX_LENGTH: usize = 30;
|
||||||
|
|
||||||
|
/// API token description min length
|
||||||
|
pub const API_TOKEN_DESCRIPTION_MIN_LENGTH: usize = 5;
|
||||||
|
|
||||||
|
/// API token description max length
|
||||||
|
pub const API_TOKEN_DESCRIPTION_MAX_LENGTH: usize = 30;
|
||||||
|
49
virtweb_backend/src/controllers/api_tokens_controller.rs
Normal file
49
virtweb_backend/src/controllers/api_tokens_controller.rs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
//! # API tokens management
|
||||||
|
|
||||||
|
use crate::api_tokens;
|
||||||
|
use crate::api_tokens::NewToken;
|
||||||
|
use crate::controllers::api_tokens_controller::rest_token::RestToken;
|
||||||
|
use crate::controllers::HttpResult;
|
||||||
|
use crate::utils::jwt_utils::TokenPrivKey;
|
||||||
|
use actix_web::{web, HttpResponse};
|
||||||
|
|
||||||
|
/// Create a special module for REST token to enforce usage of constructor function
|
||||||
|
mod rest_token {
|
||||||
|
use crate::api_tokens::Token;
|
||||||
|
use crate::utils::jwt_utils::TokenPubKey;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub struct RestToken {
|
||||||
|
token: Token,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RestToken {
|
||||||
|
pub fn new(mut token: Token) -> Self {
|
||||||
|
token.pub_key = TokenPubKey::None;
|
||||||
|
Self { token }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
struct CreateTokenResult {
|
||||||
|
token: RestToken,
|
||||||
|
priv_key: TokenPrivKey,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new API token
|
||||||
|
pub async fn create(new_token: web::Json<NewToken>) -> HttpResult {
|
||||||
|
if let Some(err) = new_token.check_error() {
|
||||||
|
log::error!("Failed to validate new API token information! {err}");
|
||||||
|
return Ok(HttpResponse::BadRequest().json(format!(
|
||||||
|
"Failed to validate new API token information! {err}"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let (token, priv_key) = api_tokens::create(&new_token).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(CreateTokenResult {
|
||||||
|
token: RestToken::new(token),
|
||||||
|
priv_key,
|
||||||
|
}))
|
||||||
|
}
|
@ -6,6 +6,7 @@ use std::error::Error;
|
|||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
|
pub mod api_tokens_controller;
|
||||||
pub mod auth_controller;
|
pub mod auth_controller;
|
||||||
pub mod iso_controller;
|
pub mod iso_controller;
|
||||||
pub mod network_controller;
|
pub mod network_controller;
|
||||||
|
@ -51,6 +51,8 @@ struct ServerConstraints {
|
|||||||
nwfilter_comment_size: LenConstraints,
|
nwfilter_comment_size: LenConstraints,
|
||||||
nwfilter_priority: SLenConstraints,
|
nwfilter_priority: SLenConstraints,
|
||||||
nwfilter_selectors_count: LenConstraints,
|
nwfilter_selectors_count: LenConstraints,
|
||||||
|
api_token_name_size: LenConstraints,
|
||||||
|
api_token_description_size: LenConstraints,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
||||||
@ -98,6 +100,16 @@ pub async fn static_config(local_auth: LocalAuthEnabled) -> impl Responder {
|
|||||||
max: 1000,
|
max: 1000,
|
||||||
},
|
},
|
||||||
nwfilter_selectors_count: LenConstraints { min: 0, max: 1 },
|
nwfilter_selectors_count: LenConstraints { min: 0, max: 1 },
|
||||||
|
|
||||||
|
api_token_name_size: LenConstraints {
|
||||||
|
min: constants::API_TOKEN_NAME_MIN_LENGTH,
|
||||||
|
max: constants::API_TOKEN_NAME_MAX_LENGTH,
|
||||||
|
},
|
||||||
|
|
||||||
|
api_token_description_size: LenConstraints {
|
||||||
|
min: constants::API_TOKEN_DESCRIPTION_MIN_LENGTH,
|
||||||
|
max: constants::API_TOKEN_DESCRIPTION_MAX_LENGTH,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
pub mod actors;
|
pub mod actors;
|
||||||
|
pub mod api_tokens;
|
||||||
pub mod app_config;
|
pub mod app_config;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
pub mod controllers;
|
pub mod controllers;
|
||||||
|
@ -22,8 +22,8 @@ use virtweb_backend::constants::{
|
|||||||
MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME,
|
MAX_INACTIVITY_DURATION, MAX_SESSION_DURATION, SESSION_COOKIE_NAME,
|
||||||
};
|
};
|
||||||
use virtweb_backend::controllers::{
|
use virtweb_backend::controllers::{
|
||||||
auth_controller, iso_controller, network_controller, nwfilter_controller, server_controller,
|
api_tokens_controller, auth_controller, iso_controller, network_controller,
|
||||||
static_controller, vm_controller,
|
nwfilter_controller, server_controller, static_controller, vm_controller,
|
||||||
};
|
};
|
||||||
use virtweb_backend::libvirt_client::LibVirtClient;
|
use virtweb_backend::libvirt_client::LibVirtClient;
|
||||||
use virtweb_backend::middlewares::auth_middleware::AuthChecker;
|
use virtweb_backend::middlewares::auth_middleware::AuthChecker;
|
||||||
@ -50,6 +50,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
files_utils::create_directory_if_missing(AppConfig::get().disks_storage_path()).unwrap();
|
files_utils::create_directory_if_missing(AppConfig::get().disks_storage_path()).unwrap();
|
||||||
files_utils::create_directory_if_missing(AppConfig::get().nat_path()).unwrap();
|
files_utils::create_directory_if_missing(AppConfig::get().nat_path()).unwrap();
|
||||||
files_utils::create_directory_if_missing(AppConfig::get().definitions_path()).unwrap();
|
files_utils::create_directory_if_missing(AppConfig::get().definitions_path()).unwrap();
|
||||||
|
files_utils::create_directory_if_missing(AppConfig::get().api_tokens_path()).unwrap();
|
||||||
|
|
||||||
let conn = Data::new(LibVirtClient(
|
let conn = Data::new(LibVirtClient(
|
||||||
LibVirtActor::connect()
|
LibVirtActor::connect()
|
||||||
@ -276,6 +277,27 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/api/nwfilter/{uid}",
|
"/api/nwfilter/{uid}",
|
||||||
web::delete().to(nwfilter_controller::delete),
|
web::delete().to(nwfilter_controller::delete),
|
||||||
)
|
)
|
||||||
|
// API tokens controller
|
||||||
|
.route(
|
||||||
|
"/api/tokens/create",
|
||||||
|
web::post().to(api_tokens_controller::create),
|
||||||
|
)
|
||||||
|
/* TODO .route(
|
||||||
|
"/api/tokens/list",
|
||||||
|
web::get().to(api_tokens_controller::list),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/tokens/{uid}",
|
||||||
|
web::get().to(api_tokens_controller::get_single),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/tokens/{uid}",
|
||||||
|
web::put().to(api_tokens_controller::update),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/api/tokens/{uid}",
|
||||||
|
web::delete().to(api_tokens_controller::delete),
|
||||||
|
)*/
|
||||||
// Static assets
|
// Static assets
|
||||||
.route("/", web::get().to(static_controller::root_index))
|
.route("/", web::get().to(static_controller::root_index))
|
||||||
.route(
|
.route(
|
||||||
|
109
virtweb_backend/src/utils/jwt_utils.rs
Normal file
109
virtweb_backend/src/utils/jwt_utils.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Validation};
|
||||||
|
use ring::signature::{KeyPair, UnparsedPublicKey};
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
|
||||||
|
#[serde(tag = "alg")]
|
||||||
|
pub enum TokenPubKey {
|
||||||
|
/// This variant DOES make crash the program. It MUST NOT be serialized.
|
||||||
|
///
|
||||||
|
/// It is a hack to hide public key when getting the list of tokens
|
||||||
|
None,
|
||||||
|
|
||||||
|
/// 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 doc = ring::signature::EcdsaKeyPair::generate_pkcs8(
|
||||||
|
&ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING,
|
||||||
|
&ring::rand::SystemRandom::new(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let priv_pem = pem::encode(&pem::Pem::new("PRIVATE KEY", doc.as_ref()));
|
||||||
|
|
||||||
|
let pair = ring::signature::EcdsaKeyPair::from_pkcs8(
|
||||||
|
&ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING,
|
||||||
|
doc.as_ref(),
|
||||||
|
&ring::rand::SystemRandom::new(),
|
||||||
|
)?;
|
||||||
|
let pub_pem = pem::encode(&pem::Pem::new("PUBLIC KEY", pair.public_key().as_ref()));
|
||||||
|
|
||||||
|
|
||||||
|
let pk = pair.public_key();
|
||||||
|
let unp = UnparsedPublicKey::new(&ring::signature::ECDSA_P384_SHA384_ASN1_SIGNING, pk.as_ref());
|
||||||
|
|
||||||
|
let decoding_key = DecodingKey::from_ec_pem(pub_pem.as_bytes()).expect("aie ai");
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
TokenPubKey::None => {
|
||||||
|
panic!("A public key is required!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn jwt_encode_sign_verify_valid() {
|
||||||
|
let (pub_key, priv_key) = generate_key_pair().unwrap();
|
||||||
|
let claims = Claims {
|
||||||
|
sub: "my-sub".to_string(),
|
||||||
|
exp: time() + 100,
|
||||||
|
};
|
||||||
|
let jwt = sign_jwt(&priv_key, &claims).expect("Failed to sign JWT!");
|
||||||
|
|
||||||
|
println!("pub {pub_key:?}");
|
||||||
|
println!("priv {priv_key:?}");
|
||||||
|
let claims_out = validate_jwt(&pub_key, &jwt).expect("Failed to validate JWT!");
|
||||||
|
|
||||||
|
assert_eq!(claims, claims_out)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
pub mod disks_utils;
|
pub mod disks_utils;
|
||||||
pub mod files_utils;
|
pub mod files_utils;
|
||||||
|
pub mod jwt_utils;
|
||||||
pub mod net_utils;
|
pub mod net_utils;
|
||||||
pub mod rand_utils;
|
pub mod rand_utils;
|
||||||
pub mod time_utils;
|
pub mod time_utils;
|
||||||
|
Loading…
Reference in New Issue
Block a user