From dd817fa4cc414755bd49436a93e7be939df12f02 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 23 Apr 2022 02:28:43 +0100 Subject: [PATCH] use ed25519-dalek fork with digest verifier --- actix-web-lab/Cargo.toml | 3 +- actix-web-lab/examples/discord_webhook.rs | 47 +++++++++++------------ 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/actix-web-lab/Cargo.toml b/actix-web-lab/Cargo.toml index 07663a22..f72ff74c 100644 --- a/actix-web-lab/Cargo.toml +++ b/actix-web-lab/Cargo.toml @@ -57,7 +57,6 @@ tracing = { version = "0.1.30", features = ["log"] } actix-web = { version = "4", features = ["rustls"] } base64 = "0.13" -ed25519-dalek = "1.0.1" env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false, features = ["std"] } hex = "0.4" @@ -68,3 +67,5 @@ rustls-pemfile = "1.0.0" serde = { version = "1", features = ["derive"] } sha2 = "0.10" static_assertions = "1.1" + +ed25519-dalek = { git = "https://github.com/robjtede/ed25519-dalek.git", branch = "verify-digest" } diff --git a/actix-web-lab/examples/discord_webhook.rs b/actix-web-lab/examples/discord_webhook.rs index fb6b528c..1c20f6b8 100644 --- a/actix-web-lab/examples/discord_webhook.rs +++ b/actix-web-lab/examples/discord_webhook.rs @@ -12,8 +12,7 @@ use actix_web::{ }; use actix_web_lab::extract::{Json, RequestSignature, RequestSignatureScheme}; use async_trait::async_trait; -use bytes::{BufMut as _, BytesMut}; -use ed25519_dalek::{PublicKey, Signature, Verifier as _}; +use ed25519_dalek::{PublicKey, Signature, StreamVerifier}; use hex_literal::hex; use once_cell::sync::Lazy; use rustls::{Certificate, PrivateKey, ServerConfig}; @@ -30,13 +29,16 @@ static SIG_HDR_NAME: Lazy = static TS_HDR_NAME: Lazy = Lazy::new(|| HeaderName::from_static("x-signature-timestamp")); +/// Signature scheme for Discord interactions/webhooks. +/// +/// Verification is done in `finalize` so this does not support optional verification. #[derive(Debug)] struct DiscordWebhook { /// Signature taken from webhook request header. candidate_signature: Signature, - /// Cloned payload state. - chunks: Vec, + /// Signature verifier. + verifier: StreamVerifier, } impl DiscordWebhook { @@ -65,7 +67,7 @@ impl DiscordWebhook { #[async_trait(?Send)] impl RequestSignatureScheme for DiscordWebhook { - type Signature = (BytesMut, Signature); + type Signature = Signature; type Error = Error; @@ -73,42 +75,36 @@ impl RequestSignatureScheme for DiscordWebhook { let ts = Self::get_timestamp(req)?.to_owned(); let candidate_signature = Self::get_signature(req)?; + let mut verifier = APP_PUBLIC_KEY + .verify_stream(&candidate_signature) + .map_err(error::ErrorBadRequest)?; + + verifier.update(ts); + Ok(Self { candidate_signature, - chunks: vec![Bytes::from(ts)], + verifier, }) } async fn consume_chunk(&mut self, _req: &HttpRequest, chunk: Bytes) -> Result<(), Self::Error> { - self.chunks.push(chunk); + self.verifier.update(chunk); Ok(()) } async fn finalize(self, _req: &HttpRequest) -> Result { - let buf_len = self.chunks.iter().map(|chunk| chunk.len()).sum(); - let mut buf = BytesMut::with_capacity(buf_len); - - for chunk in self.chunks { - buf.put(chunk); - } + self.verifier.finalize_and_verify().map_err(|_| { + error::ErrorUnauthorized("given signature does not match calculated signature") + })?; - Ok((buf, self.candidate_signature)) + Ok(self.candidate_signature) } fn verify( - (payload, candidate_signature): Self::Signature, + signature: Self::Signature, _req: &HttpRequest, ) -> Result { - if APP_PUBLIC_KEY - .verify(&payload, &candidate_signature) - .is_ok() - { - Ok((payload, candidate_signature)) - } else { - Err(error::ErrorUnauthorized( - "given signature does not match calculated signature", - )) - } + Ok(signature) } } @@ -126,6 +122,7 @@ async fn main() -> io::Result<()> { let (Json(form), _) = body.into_parts(); println!("{}", serde_json::to_string_pretty(&form).unwrap()); + // reply with PONG code web::Json(serde_json::json!({ "type": 1 }))