switched to blake3

This commit is contained in:
Hlars 2025-05-31 17:57:05 +02:00
parent 7d933e647b
commit ef9f0fd31c
7 changed files with 516 additions and 472 deletions

854
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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());
}
}

View File

@ -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),
// }
}
}

View File

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

View File

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

View File

@ -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")
)