#[macro_use] extern crate rocket; use rocket::data::{self, Data, FromData, Limits, Outcome, ToByteUnit}; use rocket::http::Status; use rocket::outcome::Outcome::{Failure, Success}; use rocket::request::{self, Request}; use hmac::{Hmac, Mac}; use sha2::Sha256; use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::BufReader; const CONFIG_FILENAME: &str = "config.json"; struct SignatureDataGuard; fn is_valid_signature(received_signature: &str, payload: &Vec<u8>) -> bool { let config_file = File::open(CONFIG_FILENAME).unwrap(); let config_reader = BufReader::new(config_file); let config: Config = serde_json::from_reader(config_reader).unwrap(); type HmacSha256 = Hmac<Sha256>; let mut mac = HmacSha256::new_from_slice(config.secret.as_bytes()).unwrap(); mac.update(payload); let expected_signature = mac.finalize().into_bytes(); received_signature.as_bytes()[..] == expected_signature[..] } #[derive(Debug)] enum Error { PayloadTooLarge, MissingSignature, MoreThatOneSignature, InvalidSignature, Io(std::io::Error), } #[rocket::async_trait] impl<'r> FromData<'r> for SignatureDataGuard { type Error = Error; async fn from_data(req: &'r Request<'_>, data: Data<'r>) -> data::Outcome<'r, Self> { let payload = match data.open(256.bytes()).into_bytes().await { Ok(payload) if payload.is_complete() => payload.into_inner(), Ok(_) => return Failure((Status::PayloadTooLarge, Error::PayloadTooLarge)), Err(e) => return Failure((Status::InternalServerError, Error::Io(e))), }; let mut received_signatures = req.headers().get("X-GITEA-SIGNATURE"); let received_signature = match received_signatures.next() { Some(signature) => signature, None => return Outcome::Failure((Status::BadRequest, Error::MissingSignature)), }; if received_signatures.next().is_some() { return Outcome::Failure((Status::BadRequest, Error::MoreThatOneSignature)); } if !is_valid_signature(received_signature, &payload) { return Outcome::Failure((Status::BadRequest, Error::InvalidSignature)); } Outcome::Success(SignatureDataGuard) } } #[derive(Deserialize)] struct Hook { repo_url: String, commands: Vec<String>, } #[derive(Deserialize)] struct Config { secret: String, hooks: Vec<Hook>, } #[get("/")] fn index() -> &'static str { "Hello, world!" } #[post("/trigger", data = "<payload>")] fn trigger(payload: SignatureDataGuard) -> &'static str { "Sensitive data." } #[launch] fn rocket() -> _ { rocket::build() .mount("/", routes![index]) .mount("/api", routes![trigger]) }