use hyper::service::Service; use hyper::{body::to_bytes, HeaderMap}; use hyper::{Body, Method, Request, Response, StatusCode}; use libsodium_sys::crypto_sign_ed25519_verify_detached; use log::info; use serde_json::Value; use std::future; use std::future::Future; use std::pin::Pin; use std::str::from_utf8; use std::task::{Context, Poll}; use serde::{Deserialize, Serialize}; use crate::config::Config; use nats::{Connection, connect}; pub fn validate_signature(b64_public_key: &str, data: &Vec, b64_signature: &str) -> bool { // First, we need to check if the signature & private key is valid base64. let signature_result = hex::decode(b64_signature); let public_key_result = hex::decode(b64_public_key); if signature_result.is_ok() && public_key_result.is_ok() { // Since we now have the signatures in u8 vectors. We will initialize all the // parameters for the ffi call to sodium. let signature_pointer = signature_result.unwrap(); let private_key_pointer = public_key_result.unwrap(); let data_pointer = data.as_ptr(); let data_len = data.len() as u64; // A ffi call is considered unsafe by the Rust compiler // we assume all the parameters are correct for the call unsafe { // If the signature is valid, sodium will return 0 return crypto_sign_ed25519_verify_detached( signature_pointer.as_ptr(), data_pointer, data_len, private_key_pointer.as_ptr(), ) == 0; } } false } fn get_signature(headers: &HeaderMap) -> Option<(&str, &str)> { let signature = headers.get("X-Signature-Ed25519"); let timestamp = headers.get("X-Signature-Timestamp"); if signature.is_some() && timestamp.is_some() { return Some(( signature.unwrap().to_str().unwrap(), timestamp.unwrap().to_str().unwrap(), )); } None } pub struct HandlerService { pub config: Config, pub nats: Box } #[derive(Debug, Serialize, Deserialize)] pub struct Ping { #[serde(rename = "type")] t: i32 } impl Service> for HandlerService { type Response = Response; type Error = hyper::Error; type Future = Pin> + Send>>; fn poll_ready(&mut self, _: &mut Context) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: Request) -> Self::Future { if req.method() == Method::POST { let public_key = self.config.discord.public_key.clone(); let nats = self.nats.clone(); return Box::pin(async move { let headers = req.headers().clone(); if let Some((signature, timestamp)) = get_signature(&headers) { if let Ok(data) = to_bytes(req.into_body()).await { let contatenated_data = [timestamp.as_bytes().to_vec(), data.to_vec()].concat(); if validate_signature(public_key.as_str(), &contatenated_data, signature) { let d: Value = serde_json::from_str(from_utf8(&data).unwrap()).unwrap(); let t = d.get("type").unwrap().as_i64().unwrap(); if t == 1 { return Ok(Response::builder().header("Content-Type", "application/json").body(serde_json::to_string(&Ping { t: 1 }).unwrap().into()).unwrap()); } else { info!("Handled event"); nats.publish(&format!("nova.dispatch.interaction_raw"), data).unwrap(); return Ok(Response::builder().header("Content-Type", "application/json").body(serde_json::to_string(&Ping { t: 5 }).unwrap().into()).unwrap()); } } else { Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body("signature verification failed".into()).unwrap()) } } else { Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body("failed to read body".into()).unwrap()) } } else { Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body("no signature specified".into()).unwrap()) } }); } else { return Box::pin(async { Ok(Response::builder().status(StatusCode::UNAUTHORIZED).body("bad method".into()).unwrap()) }); } } } pub struct MakeSvc { pub settings: Config, pub nats: Box, } impl Service for MakeSvc { type Response = HandlerService; type Error = std::io::Error; type Future = future::Ready>; fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Ok(()).into() } fn call(&mut self, _: T) -> Self::Future { future::ready(Ok(HandlerService { config: self.settings.clone(), nats: self.nats.clone(), })) } } #[cfg(test)] mod test { use crate::handle::validate_signature; #[test] fn validate_signature_test() { let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002"; let public_key = "eefe0c24473737cb2035232e3b4eb91c206f0a14684168f3503f7d8316058d6f"; let content = "message de test incroyable".as_bytes().to_vec(); assert!(validate_signature(public_key, &content, signature)) } #[test] fn validate_signature_reverse_test() { let signature = "543ec3547d57f9ddb1ec4c5c36503ebf288ffda3da3d510764c9a49c2abb57690ef974c63d174771bdd2481de1066966f57abbec12a3ec171b9f6e2373837002"; let public_key = "c029eea18437292c87c62aec34e7d1bd4e38fe6126f3f7c446de6375dc666044"; let content = "ceci est un test qui ne fonctionnera pas!" .as_bytes() .to_vec(); assert!(!validate_signature(public_key, &content, signature)) } #[test] fn invalid_hex() { let signature = "zzz"; let public_key = "zzz"; let content = "ceci est un test qui ne fonctionnera pas!" .as_bytes() .to_vec(); assert!(!validate_signature(public_key, &content, signature)) } }