Compare payload's hmac signature for forgejo

Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
Maxime “pep” Buquet 2024-07-10 13:19:04 +02:00
parent c7a5740ee5
commit 1301275788
Signed by: pep
GPG key ID: DEDA74AEECA9D0F2
3 changed files with 63 additions and 8 deletions

View file

@ -22,6 +22,9 @@ serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
toml = "0.8"
xmpp = "0.5"
hmac = "0.12"
sha2 = "0.10"
hex = "0.4"
[patch.crates-io]
forgejo-hooks = { path = "forgejo-hooks" }

View file

@ -18,11 +18,18 @@ use std::error::Error as StdError;
use std::io::Error as IoError;
use std::str::Utf8Error;
use hex::FromHexError;
use hmac::digest::InvalidLength as HmacInvalidLength;
#[derive(Debug)]
pub(crate) enum Error {
MethodMismatch,
InvalidToken,
InvalidContentType,
InvalidSignature,
InvalidRequest,
Hex(FromHexError),
Hmac(HmacInvalidLength),
Hyper(hyper::Error),
Io(IoError),
SerdeJson(serde_json::Error),
@ -39,6 +46,10 @@ impl std::fmt::Display for Error {
Error::MethodMismatch => write!(fmt, "the method is invalid"),
Error::InvalidToken => write!(fmt, "the token is invalid"),
Error::InvalidContentType => write!(fmt, "the content-type is invalid"),
Error::InvalidSignature => write!(fmt, "the signature is invalid"),
Error::InvalidRequest => write!(fmt, "the request is invalid"),
Error::Hex(e) => write!(fmt, "hex error: {}", e),
Error::Hmac(e) => write!(fmt, "hmac error: {}", e),
Error::Hyper(e) => write!(fmt, "hyper error: {}", e),
Error::Io(e) => write!(fmt, "Io error: {}", e),
Error::SerdeJson(e) => write!(fmt, "serde_json error: {}", e),
@ -49,6 +60,18 @@ impl std::fmt::Display for Error {
}
}
impl From<FromHexError> for Error {
fn from(err: FromHexError) -> Error {
Error::Hex(err)
}
}
impl From<HmacInvalidLength> for Error {
fn from(err: HmacInvalidLength) -> Error {
Error::Hmac(err)
}
}
impl From<hyper::Error> for Error {
fn from(err: hyper::Error) -> Error {
Error::Hyper(err)

View file

@ -17,14 +17,19 @@ use crate::error::Error;
use crate::webhook::{ForgejoHook, GitlabHook, Hook};
use std::convert::Infallible;
use std::io::Read;
use std::sync::{Arc, Mutex};
use bytes::{Buf, Bytes};
use hmac::{Hmac, Mac};
use http_body_util::{BodyExt, Full};
use hyper::{body::Incoming, header, Method, Request, Response};
use log::{debug, error};
use log::{debug, error, trace};
use sha2::Sha256;
use tokio::sync::mpsc::UnboundedSender;
type HmacSha256 = Hmac<Sha256>;
fn error_res<E: std::fmt::Debug>(e: E) -> Result<Response<Full<Bytes>>, Infallible> {
error!("error response: {:?}", e);
@ -43,7 +48,8 @@ async fn webhooks_inner(req: Request<Incoming>, token: &str) -> Result<Hook, Err
}
debug!("Headers: {:?}", req.headers());
let headers = req.headers();
let headers = req.headers().clone();
if let Some(content_type) = headers.get(header::CONTENT_TYPE)
&& content_type != "application/json"
{
@ -56,15 +62,38 @@ async fn webhooks_inner(req: Request<Incoming>, token: &str) -> Result<Hook, Err
return Err(Error::InvalidToken);
}
if let Some(content_type) = headers.get(header::CONTENT_TYPE)
&& content_type != "application/json"
{
return Err(Error::InvalidContentType);
// Get payload and generate hmac signature
let mut payload: Vec<u8> = vec![];
let whole_body = req.collect().await?.aggregate();
whole_body.reader().read_to_end(&mut payload)?;
let mut mac = HmacSha256::new_from_slice(token.as_bytes())?;
mac.update(&payload);
let result = mac.finalize().into_bytes();
trace!("Payload calculated signature: {:?}", hex::encode(result));
if let Some(val) = headers.get("X-Forgejo-Signature") {
trace!("Payload advertized signature: {:?}", val);
let slice: Vec<u8> = hex::decode(val)?;
if slice[..] != result[..] {
return Err(Error::InvalidSignature);
}
let whole_body = req.collect().await?.aggregate();
let hook = serde_json::from_reader(whole_body.reader())?;
Ok(Hook::Gitlab(hook))
let hook: ForgejoHook = serde_json::from_slice(&payload[..])?;
return Ok(Hook::Forgejo(hook));
} else if let Some(val) = headers.get("X-Gitlab-Token")
&& token != val
{
if token != val {
return Err(Error::InvalidToken);
}
let hook: GitlabHook = serde_json::from_slice(&payload[..])?;
return Ok(Hook::Gitlab(hook));
}
// No match found for the payload
Err(Error::InvalidRequest)
}
pub async fn webhooks(