Compare payload's hmac signature for forgejo
Signed-off-by: Maxime “pep” Buquet <pep@bouah.net>
This commit is contained in:
parent
c7a5740ee5
commit
1301275788
3 changed files with 63 additions and 8 deletions
|
@ -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" }
|
||||
|
|
23
src/error.rs
23
src/error.rs
|
@ -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)
|
||||
|
|
45
src/web.rs
45
src/web.rs
|
@ -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"
|
||||
// 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 hook: ForgejoHook = serde_json::from_slice(&payload[..])?;
|
||||
return Ok(Hook::Forgejo(hook));
|
||||
} else if let Some(val) = headers.get("X-Gitlab-Token")
|
||||
&& token != val
|
||||
{
|
||||
return Err(Error::InvalidContentType);
|
||||
if token != val {
|
||||
return Err(Error::InvalidToken);
|
||||
}
|
||||
|
||||
let hook: GitlabHook = serde_json::from_slice(&payload[..])?;
|
||||
return Ok(Hook::Gitlab(hook));
|
||||
}
|
||||
|
||||
let whole_body = req.collect().await?.aggregate();
|
||||
let hook = serde_json::from_reader(whole_body.reader())?;
|
||||
Ok(Hook::Gitlab(hook))
|
||||
// No match found for the payload
|
||||
Err(Error::InvalidRequest)
|
||||
}
|
||||
|
||||
pub async fn webhooks(
|
||||
|
|
Loading…
Reference in a new issue