Include payload in integrity controls

This commit is contained in:
2025-02-03 20:50:25 +01:00
parent af1dd4d122
commit 8df3afe75e
4 changed files with 145 additions and 30 deletions

View File

@ -3,25 +3,34 @@ use crate::utils::curr_time;
use actix_remote_ip::RemoteIP;
use actix_web::dev::Payload;
use actix_web::{FromRequest, HttpRequest};
use bytes::Bytes;
use jwt_simple::common::VerificationOptions;
use jwt_simple::prelude::{Duration, HS256Key, MACLike};
use sha2::{Digest, Sha256};
use std::net::IpAddr;
use std::str::FromStr;
pub struct APIClientAuth {
pub user: UserConfig,
client: APIClient,
payload: Option<Vec<u8>>,
pub payload: Option<Vec<u8>>,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct TokenClaims {
#[serde(rename = "met")]
pub method: String,
pub uri: String,
#[serde(rename = "pay", skip_serializing_if = "Option::is_none")]
pub payload_sha256: Option<String>,
}
impl APIClientAuth {
async fn extract_auth(req: &HttpRequest, remote_ip: IpAddr) -> Result<Self, actix_web::Error> {
async fn extract_auth(
req: &HttpRequest,
remote_ip: IpAddr,
payload_bytes: Option<Bytes>,
) -> Result<Self, actix_web::Error> {
let Some(token) = req.headers().get("x-client-auth") else {
return Err(actix_web::error::ErrorBadRequest(
"Missing authentication header!",
@ -129,7 +138,27 @@ impl APIClientAuth {
));
}
// TODO : handle payload
let payload = match (payload_bytes, claims.custom.payload_sha256) {
(None, _) => None,
(Some(_), None) => {
return Err(actix_web::error::ErrorBadRequest(
"A payload digest must be included in the JWT when the request has a payload!",
));
}
(Some(payload), Some(provided_digest)) => {
let computed_digest = base16ct::lower::encode_string(&Sha256::digest(&payload));
if computed_digest != provided_digest {
log::error!(
"Expected digest {provided_digest} but computed {computed_digest}!"
);
return Err(actix_web::error::ErrorBadRequest(
"Computed digest is different from the one provided in the JWT!",
));
}
Some(payload.to_vec())
}
};
// Update last use (if needed)
if client.need_update_last_used() {
@ -145,7 +174,7 @@ impl APIClientAuth {
Ok(Self {
client: client.clone(),
payload: None,
payload,
user,
})
}
@ -163,6 +192,24 @@ impl FromRequest for APIClientAuth {
Err(e) => return Box::pin(async { Err(e) }),
};
Box::pin(async move { Self::extract_auth(&req, remote_ip.0).await })
let mut payload = payload.take();
Box::pin(async move {
let payload_bytes = match Bytes::from_request(&req, &mut payload).await {
Ok(b) => {
if b.is_empty() {
None
} else {
Some(b)
}
}
Err(e) => {
log::error!("Failed to extract request payload! {e}");
None
}
};
Self::extract_auth(&req, remote_ip.0, payload_bytes).await
})
}
}