payload.rs 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566
  1. //! This module implements the payload verification for GitHub webhook events.
  2. use openssl::{hash::MessageDigest, memcmp, pkey::PKey, sign::Signer};
  3. use rocket::{
  4. data::{self, Data, FromDataSimple},
  5. http::Status,
  6. request::Request,
  7. Outcome,
  8. };
  9. use std::{env, io::Read};
  10. pub struct SignedPayload(Vec<u8>);
  11. impl FromDataSimple for SignedPayload {
  12. type Error = String;
  13. fn from_data(req: &Request, data: Data) -> data::Outcome<Self, Self::Error> {
  14. let signature = match req.headers().get_one("X-Hub-Signature") {
  15. Some(s) => s,
  16. None => {
  17. return Outcome::Failure((
  18. Status::Unauthorized,
  19. "Unauthorized, no signature".into(),
  20. ));
  21. }
  22. };
  23. let signature = &signature["sha1=".len()..];
  24. let signature = match hex::decode(&signature) {
  25. Ok(e) => e,
  26. Err(e) => {
  27. return Outcome::Failure((
  28. Status::BadRequest,
  29. format!(
  30. "failed to convert signature {:?} from hex: {:?}",
  31. signature, e
  32. ),
  33. ));
  34. }
  35. };
  36. let mut stream = data.open().take(1024 * 1024 * 5); // 5 Megabytes
  37. let mut buf = Vec::new();
  38. if let Err(err) = stream.read_to_end(&mut buf) {
  39. return Outcome::Failure((
  40. Status::InternalServerError,
  41. format!("failed to read request body to string: {:?}", err),
  42. ));
  43. }
  44. let key = PKey::hmac(env::var("GITHUB_WEBHOOK_SECRET").unwrap().as_bytes()).unwrap();
  45. let mut signer = Signer::new(MessageDigest::sha1(), &key).unwrap();
  46. signer.update(&buf).unwrap();
  47. let hmac = signer.sign_to_vec().unwrap();
  48. if !memcmp::eq(&hmac, &signature) {
  49. return Outcome::Failure((Status::Unauthorized, "HMAC not correct".into()));
  50. }
  51. Outcome::Success(SignedPayload(buf))
  52. }
  53. }
  54. impl SignedPayload {
  55. pub fn deserialize<T: serde::de::DeserializeOwned>(self) -> Result<T, serde_json::Error> {
  56. serde_json::from_slice(&self.0)
  57. }
  58. }