#[cfg(any(feature = "crypto-use-rustcrypto", feature = "crypto-use-ring"))]
use hex::FromHex;
#[cfg(feature = "crypto-use-rustcrypto")]
use hmac::{Hmac, Mac};
#[cfg(feature = "crypto-use-ring")]
use ring::digest;
#[cfg(feature = "crypto-use-ring")]
use ring::hmac;
#[cfg(feature = "crypto-use-rustcrypto")]
use sha1::Sha1;
use std::sync::Arc;
use super::handler::Delivery;
use super::handler::DeliveryType;
#[cfg(feature = "crypto-use-rustcrypto")]
type HmacSha1 = Hmac<Sha1>;
#[macro_export]
macro_rules! unwrap_or_false {
($e:expr) => {
match $e {
Some(content) => content,
_ => return false,
}
};
}
pub trait HookFunc: Sync + Send {
fn run(&self, delivery: &Delivery);
}
#[derive(Clone)]
pub struct Hook {
pub event: &'static str,
pub secret: Option<String>,
pub func: Arc<HookFunc>, }
impl<F> HookFunc for F
where
F: Fn(&Delivery) + Clone + Sync + Send + 'static,
{
fn run(&self, delivery: &Delivery) {
self(delivery)
}
}
impl Hook {
pub fn new(event: &'static str, secret: Option<String>, func: impl HookFunc + 'static) -> Self {
Self {
event,
secret,
func: Arc::new(func),
}
}
#[cfg(feature = "crypto-use-ring")]
pub fn auth(&self, delivery: &Delivery) -> bool {
if let Some(secret) = &self.secret {
let signature = unwrap_or_false!(&delivery.signature);
debug!("Signature/Token: {}", signature);
match delivery.delivery_type {
DeliveryType::GitHub => {
let request_body = unwrap_or_false!(&delivery.request_body);
debug!("Request body: {}", &request_body);
let signature_hex = signature[5..signature.len()].as_bytes();
if let Ok(signature_bytes) = Vec::from_hex(signature_hex) {
let secret_bytes = secret.as_bytes();
let request_body_bytes = request_body.as_bytes();
let key = hmac::SigningKey::new(&digest::SHA1, &secret_bytes);
debug!("Validating payload with given secret");
return hmac::verify_with_own_key(
&key,
&request_body_bytes,
&signature_bytes,
)
.is_ok();
}
debug!("Invalid signature");
return false;
}
DeliveryType::GitLab => {
if signature == secret {
return true;
} else {
debug!("Invalid token");
return false;
}
}
}
} else {
debug!("No secret given, passing...");
return true;
}
}
#[cfg(feature = "crypto-use-rustcrypto")]
pub fn auth(&self, delivery: &Delivery) -> bool {
if let Some(secret) = &self.secret {
let signature = unwrap_or_false!(&delivery.signature);
debug!("Signature/Token: {}", &signature);
match delivery.delivery_type {
DeliveryType::GitHub => {
let request_body = unwrap_or_false!(&delivery.request_body);
debug!("Request body: {}", &request_body);
let signature_hex = signature[5..signature.len()].as_bytes();
if let Ok(signature_bytes) = Vec::from_hex(signature_hex) {
let secret_bytes = secret.as_bytes();
let request_body_bytes = request_body.as_bytes();
let mut mac = unwrap_or_false!(HmacSha1::new_varkey(secret_bytes).ok());
mac.input(request_body_bytes);
debug!("Validating payload with given secret");
return mac.verify(&signature_bytes).is_ok();
}
debug!("Invalid signature");
return false;
}
DeliveryType::GitLab => {
if signature == secret {
return true;
} else {
debug!("Invalid token");
return false;
}
}
}
} else {
debug!("No secret given, passing...");
return false;
}
}
#[cfg(all(
not(feature = "crypto-use-rustcrypto"),
not(feature = "crypto-use-ring")
))]
pub fn auth(&self, delivery: &Delivery) -> bool {
if let Some(secret) = &self.secret {
match delivery.delivery_type {
DeliveryType::GitHub => {
info!(
"Payload authentication not enabled for requests from GitHub, passing..."
);
true
}
DeliveryType::GitLab => {
let signature = unwrap_or_false!(&delivery.signature);
debug!("Signature/token: {}", &signature);
if signature == secret {
true
} else {
debug!("Invalid token");
false
}
}
}
} else {
debug!("No secret given, passing...");
true
}
}
pub fn handle_delivery(self, delivery: &Delivery) {
if self.auth(delivery) {
debug!("Valid payload found");
self.func.run(delivery);
return;
}
debug!("Invalid payload");
}
}
#[cfg(any(feature = "crypto-use-rustcrypto", feature = "crypto-use-ring"))]
#[cfg(test)]
mod tests {
use super::super::handler::ContentType;
use super::super::handler::DeliveryType;
#[cfg(feature = "crypto-use-rustcrypto")]
use super::HmacSha1;
use super::*;
use hex::ToHex;
#[cfg(feature = "crypto-use-ring")]
use ring::digest;
#[cfg(feature = "crypto-use-ring")]
use ring::hmac;
#[cfg(feature = "crypto-use-ring")]
#[test]
fn payload_authentication_ring() {
let secret = String::from("secret");
let hook = Hook::new("*", Some(secret.clone()), |_: &Delivery| {});
let payload = String::from(r#"{"zen": "Bazinga!"}"#);
let request_body = payload.clone();
let secret_bytes = secret.as_bytes();
let request_bytes = request_body.as_bytes();
let key = hmac::SigningKey::new(&digest::SHA1, &secret_bytes);
let mut signature = String::new();
hmac::sign(&key, &request_bytes)
.as_ref()
.write_hex(&mut signature)
.unwrap();
let signature_field = String::from(format!("sha1={}", signature));
let delivery = Delivery::new(
DeliveryType::GitHub,
None,
Some(String::from("push")),
Some(signature_field),
ContentType::JSON,
Some(request_body),
);
assert!(hook.auth(&delivery));
}
#[cfg(feature = "crypto-use-rustcrypto")]
#[test]
fn payload_authentication_rustcrypto() {
let secret = String::from("secret");
let hook = Hook::new("*", Some(secret.clone()), |_: &Delivery| {});
let payload = String::from(r#"{"zen": "Bazinga!"}"#);
let request_body = payload.clone();
let secret_bytes = secret.as_bytes();
let request_bytes = request_body.as_bytes();
let mut mac = HmacSha1::new_varkey(&secret_bytes).expect("Invalid key");
mac.input(&request_bytes);
let mut signature = String::new();
mac.result()
.code()
.as_ref()
.write_hex(&mut signature)
.expect("Invalid signature");
let signature_field = String::from(format!("sha1={}", signature));
let delivery = Delivery::new(
DeliveryType::GitHub,
None,
Some(String::from("push")),
Some(signature_field),
ContentType::JSON,
Some(request_body),
);
assert!(hook.auth(&delivery));
}
#[test]
fn payload_authentication_fail() {
let secret = String::from("secret");
let hook = Hook::new("*", Some(secret.clone()), |_: &Delivery| {});
let payload = String::from(r#"{"zen": "Another test!"}"#);
let request_body = payload.clone();
let signature = String::from("sha1=ec760ee6d10bf638089f078b5a0c23f6575821e7");
let delivery = Delivery::new(
DeliveryType::GitHub,
None,
Some(String::from("push")),
Some(signature),
ContentType::JSON,
Some(request_body),
);
assert_eq!(hook.auth(&delivery), false);
}
}