//! This module implements the payload verification for GitHub webhook events. use openssl::{hash::MessageDigest, memcmp, pkey::PKey, sign::Signer}; use rocket::{ data::{self, Data, FromDataSimple}, http::Status, request::Request, Outcome, }; use std::{env, io::Read}; pub struct SignedPayload(Vec); impl FromDataSimple for SignedPayload { type Error = String; fn from_data(req: &Request, data: Data) -> data::Outcome { let signature = match req.headers().get_one("X-Hub-Signature") { Some(s) => s, None => { return Outcome::Failure(( Status::Unauthorized, "Unauthorized, no signature".into(), )); } }; let signature = &signature["sha1=".len()..]; let signature = match hex::decode(&signature) { Ok(e) => e, Err(e) => { return Outcome::Failure(( Status::BadRequest, format!( "failed to convert signature {:?} from hex: {:?}", signature, e ), )); } }; let mut stream = data.open().take(1024 * 1024 * 5); // 5 Megabytes let mut buf = Vec::new(); if let Err(err) = stream.read_to_end(&mut buf) { return Outcome::Failure(( Status::InternalServerError, format!("failed to read request body to string: {:?}", err), )); } let key = PKey::hmac(env::var("GITHUB_WEBHOOK_SECRET").unwrap().as_bytes()).unwrap(); let mut signer = Signer::new(MessageDigest::sha1(), &key).unwrap(); signer.update(&buf).unwrap(); let hmac = signer.sign_to_vec().unwrap(); if !memcmp::eq(&hmac, &signature) { return Outcome::Failure((Status::Unauthorized, "HMAC not correct".into())); } Outcome::Success(SignedPayload(buf)) } } impl SignedPayload { pub fn deserialize(self) -> Result { serde_json::from_slice(&self.0) } }