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