switched to blake3
This commit is contained in:
parent
7d933e647b
commit
ef9f0fd31c
854
Cargo.lock
generated
854
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
18
Cargo.toml
18
Cargo.toml
@ -18,18 +18,16 @@ error-stack = "0.5.0"
|
||||
# CLI
|
||||
# ========================================
|
||||
dotenv = "0.15"
|
||||
clap = { version = "4.5.37", features = ["derive"] }
|
||||
clap = { version = "4.5.39", features = ["derive"] }
|
||||
config = "0.15.11"
|
||||
colored = "3.0.0"
|
||||
|
||||
# User Authentication
|
||||
# ========================================
|
||||
uuid = { version = "1.16.0", features = ["v4"] }
|
||||
sha2 = "0.10.9"
|
||||
hmac = "0.12.1"
|
||||
uuid = { version = "1.17.0", features = ["v4"] }
|
||||
blake3 = "1.1.18"
|
||||
minisign = "0.7.9"
|
||||
# axum-jwt-login = { path = "../axum-login-jwt" }
|
||||
axum-jwt-login = { version = "0.1.0", registry = "kellnr" }
|
||||
axum-jwt-login = { version = "0.1.1", registry = "kellnr" }
|
||||
rust-argon2 = "2.1.0"
|
||||
rand = "0.9.1"
|
||||
ldap3 = "0.11.5"
|
||||
@ -43,7 +41,7 @@ validator = { version = "0.20.0", features = ["derive"] }
|
||||
strum = { version = "0.27", features = ["derive"] }
|
||||
utoipa = { version = "5.3.1", features = ["axum_extras"] }
|
||||
utoipa-axum = "0.2.0"
|
||||
utoipa-swagger-ui = { version = "9.0.1", features = ["axum"] }
|
||||
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||
utoipa-redoc = { version = "6.0.0", features = ["axum"] }
|
||||
utoipa-scalar = { version = "0.3.0", features = ["axum"] }
|
||||
ts-rs = { version = "10.1.0", features = ["chrono-impl"] }
|
||||
@ -52,11 +50,11 @@ ts-rs = { version = "10.1.0", features = ["chrono-impl"] }
|
||||
# ========================================
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tokio = { version = "1.44.2", features = ["full"] }
|
||||
tokio = { version = "1.45.1", features = ["full"] }
|
||||
tokio-util = { version = "0.7.15", features = ["rt"] }
|
||||
tokio-stream = { version = "0.1.17", features = ["sync"] }
|
||||
futures = "0.3"
|
||||
once_cell = "1.21.3"
|
||||
sqlx = { version = "0.8.5", features = ["runtime-tokio", "postgres", "chrono"] }
|
||||
sqlx = { version = "0.8.6", features = ["runtime-tokio", "postgres", "chrono"] }
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
zip = "2.6.1"
|
||||
zip = "4.0.0"
|
||||
|
@ -1,62 +1,61 @@
|
||||
use hmac::{Hmac, Mac};
|
||||
use sha2::{Digest, Sha512};
|
||||
use blake3::{Hash, Hasher};
|
||||
|
||||
use crate::errors::ApiError;
|
||||
|
||||
type HashType = Sha512;
|
||||
|
||||
pub trait CustomHash {
|
||||
fn updater(&self, hasher: &mut Sha512);
|
||||
fn updater(&self, hasher: &mut Hasher);
|
||||
|
||||
fn hash(&self) -> Vec<u8> {
|
||||
let mut hasher = Sha512::new();
|
||||
fn hash(&self) -> Hash {
|
||||
let mut hasher = Hasher::new();
|
||||
Self::updater(&self, &mut hasher);
|
||||
hasher.finalize().to_vec()
|
||||
hasher.finalize()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct IntegrityVerifier {
|
||||
mac: Hmac<HashType>,
|
||||
mac: Hasher,
|
||||
}
|
||||
|
||||
impl IntegrityVerifier {
|
||||
pub fn new() -> Result<Self, ApiError> {
|
||||
Ok(Self {
|
||||
mac: Hmac::<HashType>::new_from_slice(
|
||||
include_str!("./verification_secret").as_bytes(),
|
||||
)?,
|
||||
mac: Hasher::new_derive_key(include_str!("./verification_secret")),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn sign<T>(&self, data: &T) -> Result<Vec<u8>, ApiError>
|
||||
pub fn sign<T>(&self, data: &T) -> Result<Hash, ApiError>
|
||||
where
|
||||
T: CustomHash,
|
||||
{
|
||||
let mut mac = self.mac.clone();
|
||||
|
||||
mac.update(&data.hash());
|
||||
let signature = mac.finalize_reset().into_bytes();
|
||||
mac.update(data.hash().as_bytes());
|
||||
let signature = mac.finalize();
|
||||
mac.reset();
|
||||
|
||||
Ok(signature.to_vec())
|
||||
Ok(signature)
|
||||
}
|
||||
|
||||
pub fn verify<T>(&self, data: &T, signature: &[u8]) -> Result<(), ApiError>
|
||||
pub fn verify<T>(&self, data: &T, signature: Hash) -> Result<(), ApiError>
|
||||
where
|
||||
T: CustomHash,
|
||||
{
|
||||
let mut mac = self.mac.clone();
|
||||
mac.update(&data.hash());
|
||||
mac.update(data.hash().as_bytes());
|
||||
let verify = mac.finalize();
|
||||
mac.reset();
|
||||
|
||||
match mac.verify_slice_reset(signature) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_e) => Err(ApiError::AccessDenied),
|
||||
match verify == signature {
|
||||
true => Ok(()),
|
||||
false => Err(ApiError::AccessDenied),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use hmac::digest::Update;
|
||||
|
||||
use blake3::Hasher;
|
||||
|
||||
use crate::api::backend::integrity_verification::{CustomHash, IntegrityVerifier};
|
||||
|
||||
@ -68,7 +67,7 @@ mod test {
|
||||
}
|
||||
|
||||
impl CustomHash for TestData {
|
||||
fn updater(&self, hasher: &mut sha2::Sha512) {
|
||||
fn updater(&self, hasher: &mut Hasher) {
|
||||
hasher.update(&self.one.to_be_bytes());
|
||||
hasher.update(&self.two.as_bytes());
|
||||
}
|
||||
@ -82,9 +81,9 @@ mod test {
|
||||
};
|
||||
|
||||
let signature = verifier.sign(&item).unwrap();
|
||||
assert!(verifier.verify(&item, &signature).is_ok());
|
||||
assert!(verifier.verify(&item, signature).is_ok());
|
||||
|
||||
item.two = "Wrong".to_string();
|
||||
assert!(verifier.verify(&item, &signature).is_err());
|
||||
assert!(verifier.verify(&item, signature).is_err());
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use axum_jwt_login::ApiKey as ApiKeyTrait;
|
||||
use blake3::{Hash, Hasher};
|
||||
use chrono::NaiveDateTime;
|
||||
use hmac::{Hmac, Mac};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::Sha512;
|
||||
use sqlx::PgPool;
|
||||
use ts_rs::TS;
|
||||
use utoipa::ToSchema;
|
||||
@ -27,7 +26,7 @@ const KEY_LENGTH: usize = 40;
|
||||
pub struct ApiKey {
|
||||
pub id: String,
|
||||
#[serde(skip)] // Don't leak Hash
|
||||
pub hash: Vec<u8>,
|
||||
pub hash: String,
|
||||
pub name: String,
|
||||
pub auth_required: bool,
|
||||
#[schema(inline)]
|
||||
@ -68,9 +67,9 @@ impl ApiKey {
|
||||
// create API Key secret part
|
||||
let key: String = create_random(KEY_LENGTH);
|
||||
// calculate API Key signature
|
||||
let mut mac = Hmac::<Sha512>::new_from_slice(config.token_secret.as_bytes())?;
|
||||
mac.update(key.as_bytes());
|
||||
let signature = mac.finalize();
|
||||
let mut hasher = Hasher::new_derive_key(&config.token_secret);
|
||||
hasher.update(key.as_bytes());
|
||||
let signature = hasher.finalize();
|
||||
|
||||
// Return Api Key Secret and Api Key
|
||||
Ok((
|
||||
@ -79,7 +78,7 @@ impl ApiKey {
|
||||
name: name.to_string(),
|
||||
auth_required: requires_auth,
|
||||
id: uuid.to_string(),
|
||||
hash: signature.into_bytes().as_slice().to_vec(),
|
||||
hash: signature.to_hex().to_string(),
|
||||
permissions: PermissionContainer(permissions),
|
||||
api_config_secret: Some(config.token_secret.clone()),
|
||||
creation_date: None,
|
||||
@ -91,20 +90,29 @@ impl ApiKey {
|
||||
|
||||
pub fn validate(&self, key_secret_part: &str) -> Result<(), ApiError> {
|
||||
// calculate API Key signature
|
||||
let mut mac = Hmac::<Sha512>::new_from_slice(
|
||||
self.api_config_secret
|
||||
.clone()
|
||||
.ok_or(ApiError::InternalError(
|
||||
"Missing API Config Secret".to_string(),
|
||||
))?
|
||||
.as_bytes(),
|
||||
)?;
|
||||
mac.update(key_secret_part.as_bytes());
|
||||
// let mut mac = Hmac::<Sha512>::new_from_slice(
|
||||
// self.api_config_secret
|
||||
// .clone()
|
||||
// .ok_or(ApiError::InternalError(
|
||||
// "Missing API Config Secret".to_string(),
|
||||
// ))?
|
||||
// .as_bytes(),
|
||||
// )?;
|
||||
// mac.update(key_secret_part.as_bytes());
|
||||
let mut hasher = Hasher::new_derive_key(&self.api_config_secret.clone().ok_or(
|
||||
ApiError::InternalError("Missing API Config Secret".to_string()),
|
||||
)?);
|
||||
hasher.update(key_secret_part.as_bytes());
|
||||
let signature = hasher.finalize();
|
||||
|
||||
match mac.verify_slice(&self.hash) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(_e) => Err(ApiError::AccessDenied),
|
||||
match signature == Hash::from_hex(self.hash.clone())? {
|
||||
true => Ok(()),
|
||||
false => Err(ApiError::AccessDenied),
|
||||
}
|
||||
// match mac.verify_slice(&self.hash) {
|
||||
// Ok(_) => Ok(()),
|
||||
// Err(_e) => Err(ApiError::AccessDenied),
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
use blake3::Hasher;
|
||||
use serde::Serialize;
|
||||
use sha2::{Digest, Sha256};
|
||||
use tokio_util::bytes::Bytes;
|
||||
use ts_rs::TS;
|
||||
use utoipa::ToSchema;
|
||||
@ -23,10 +23,10 @@ pub struct File {
|
||||
|
||||
impl File {
|
||||
pub fn hash(&self) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
let mut hasher = Hasher::new();
|
||||
hasher.update(&self.data);
|
||||
let hash = hasher.finalize();
|
||||
|
||||
format!("{hash:X}")
|
||||
hash.to_hex().to_string()
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ use axum::{
|
||||
use error_stack::Context;
|
||||
use minisign::PError;
|
||||
use serde::Serialize;
|
||||
use sha2::digest::InvalidLength;
|
||||
use tracing::error;
|
||||
use validator::ValidationErrors;
|
||||
use zip::result::ZipError;
|
||||
@ -111,15 +110,15 @@ impl From<axum_jwt_login::Error<User, ApiBackend>> for ApiError {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<argon2::Error> for ApiError {
|
||||
fn from(_: argon2::Error) -> Self {
|
||||
Self::InvalidCredentials
|
||||
impl From<blake3::HexError> for ApiError {
|
||||
fn from(e: blake3::HexError) -> Self {
|
||||
Self::InternalError(format!("Could not create Hash from Hex: {e}"))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InvalidLength> for ApiError {
|
||||
fn from(value: InvalidLength) -> Self {
|
||||
Self::InternalError(format!("Invalid HMac Key length: {value}"))
|
||||
impl From<argon2::Error> for ApiError {
|
||||
fn from(_: argon2::Error) -> Self {
|
||||
Self::InvalidCredentials
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ CREATE TABLE IF NOT EXISTS public.apikeys
|
||||
"UserAuthRequired" boolean NOT NULL DEFAULT true,
|
||||
"CreationDate" timestamp without time zone NOT NULL DEFAULT now(),
|
||||
"LastChanged" timestamp without time zone NOT NULL DEFAULT now(),
|
||||
"Hash" bytea NOT NULL,
|
||||
"Hash" character varying COLLATE pg_catalog."default" NOT NULL,
|
||||
"Status" smallint NOT NULL DEFAULT 1,
|
||||
CONSTRAINT apikeys_pkey PRIMARY KEY ("KeyID")
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user