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