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