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"
|
serde_json = "1.0"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
xmpp = "0.5"
|
xmpp = "0.5"
|
||||||
|
hmac = "0.12"
|
||||||
|
sha2 = "0.10"
|
||||||
|
hex = "0.4"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
forgejo-hooks = { path = "forgejo-hooks" }
|
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::io::Error as IoError;
|
||||||
use std::str::Utf8Error;
|
use std::str::Utf8Error;
|
||||||
|
|
||||||
|
use hex::FromHexError;
|
||||||
|
use hmac::digest::InvalidLength as HmacInvalidLength;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) enum Error {
|
pub(crate) enum Error {
|
||||||
MethodMismatch,
|
MethodMismatch,
|
||||||
InvalidToken,
|
InvalidToken,
|
||||||
InvalidContentType,
|
InvalidContentType,
|
||||||
|
InvalidSignature,
|
||||||
|
InvalidRequest,
|
||||||
|
Hex(FromHexError),
|
||||||
|
Hmac(HmacInvalidLength),
|
||||||
Hyper(hyper::Error),
|
Hyper(hyper::Error),
|
||||||
Io(IoError),
|
Io(IoError),
|
||||||
SerdeJson(serde_json::Error),
|
SerdeJson(serde_json::Error),
|
||||||
|
@ -39,6 +46,10 @@ impl std::fmt::Display for Error {
|
||||||
Error::MethodMismatch => write!(fmt, "the method is invalid"),
|
Error::MethodMismatch => write!(fmt, "the method is invalid"),
|
||||||
Error::InvalidToken => write!(fmt, "the token is invalid"),
|
Error::InvalidToken => write!(fmt, "the token is invalid"),
|
||||||
Error::InvalidContentType => write!(fmt, "the content-type 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::Hyper(e) => write!(fmt, "hyper error: {}", e),
|
||||||
Error::Io(e) => write!(fmt, "Io error: {}", e),
|
Error::Io(e) => write!(fmt, "Io error: {}", e),
|
||||||
Error::SerdeJson(e) => write!(fmt, "serde_json 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 {
|
impl From<hyper::Error> for Error {
|
||||||
fn from(err: hyper::Error) -> Error {
|
fn from(err: hyper::Error) -> Error {
|
||||||
Error::Hyper(err)
|
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 crate::webhook::{ForgejoHook, GitlabHook, Hook};
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
use std::io::Read;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use bytes::{Buf, Bytes};
|
use bytes::{Buf, Bytes};
|
||||||
|
use hmac::{Hmac, Mac};
|
||||||
use http_body_util::{BodyExt, Full};
|
use http_body_util::{BodyExt, Full};
|
||||||
use hyper::{body::Incoming, header, Method, Request, Response};
|
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;
|
use tokio::sync::mpsc::UnboundedSender;
|
||||||
|
|
||||||
|
type HmacSha256 = Hmac<Sha256>;
|
||||||
|
|
||||||
fn error_res<E: std::fmt::Debug>(e: E) -> Result<Response<Full<Bytes>>, Infallible> {
|
fn error_res<E: std::fmt::Debug>(e: E) -> Result<Response<Full<Bytes>>, Infallible> {
|
||||||
error!("error response: {:?}", e);
|
error!("error response: {:?}", e);
|
||||||
|
|
||||||
|
@ -43,7 +48,8 @@ async fn webhooks_inner(req: Request<Incoming>, token: &str) -> Result<Hook, Err
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("Headers: {:?}", req.headers());
|
debug!("Headers: {:?}", req.headers());
|
||||||
let headers = req.headers();
|
|
||||||
|
let headers = req.headers().clone();
|
||||||
if let Some(content_type) = headers.get(header::CONTENT_TYPE)
|
if let Some(content_type) = headers.get(header::CONTENT_TYPE)
|
||||||
&& content_type != "application/json"
|
&& content_type != "application/json"
|
||||||
{
|
{
|
||||||
|
@ -56,15 +62,38 @@ async fn webhooks_inner(req: Request<Incoming>, token: &str) -> Result<Hook, Err
|
||||||
return Err(Error::InvalidToken);
|
return Err(Error::InvalidToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(content_type) = headers.get(header::CONTENT_TYPE)
|
// Get payload and generate hmac signature
|
||||||
&& content_type != "application/json"
|
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();
|
// No match found for the payload
|
||||||
let hook = serde_json::from_reader(whole_body.reader())?;
|
Err(Error::InvalidRequest)
|
||||||
Ok(Hook::Gitlab(hook))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn webhooks(
|
pub async fn webhooks(
|
||||||
|
|
Loading…
Reference in a new issue