Skip to content

Commit

Permalink
use ed25519-dalek fork with digest verifier
Browse files Browse the repository at this point in the history
  • Loading branch information
robjtede committed Jun 8, 2024
1 parent f040265 commit 4154ad1
Show file tree
Hide file tree
Showing 2 changed files with 24 additions and 25 deletions.
2 changes: 1 addition & 1 deletion actix-web-lab/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ actix-web = { version = "4", features = ["rustls-0_23"] }
async_zip = { version = "0.0.17", features = ["deflate", "tokio"] }
base64 = "0.22"
digest = "0.10"
ed25519-dalek = "2"
ed25519-dalek = { git = "https://github.com/robjtede/ed25519-dalek.git", branch = "verify-digest" }
env_logger = "0.11"
futures-util = { version = "0.3.17", default-features = false, features = ["std", "io"] }
generic-array = "0.14"
Expand Down
47 changes: 23 additions & 24 deletions actix-web-lab/examples/discord_webhook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use actix_web::{
App, Error, HttpRequest, HttpServer,
};
use actix_web_lab::extract::{Json, RequestSignature, RequestSignatureScheme};
use async_trait::async_trait;
use bytes::{BufMut as _, BytesMut};
use ed25519_dalek::{Signature, Verifier as _, VerifyingKey};
use ed25519_dalek::{PublicKey, Signature, StreamVerifier, Verifier as _, VerifyingKey};
use hex_literal::hex;
use once_cell::sync::Lazy;
use rustls::{pki_types::PrivateKeyDer, ServerConfig};
Expand All @@ -24,13 +25,16 @@ static TS_HDR_NAME: HeaderName = HeaderName::from_static("x-signature-timestamp"
static APP_PUBLIC_KEY: Lazy<VerifyingKey> =
Lazy::new(|| VerifyingKey::from_bytes(APP_PUBLIC_KEY_BYTES).unwrap());

/// 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<Bytes>,
/// Signature verifier.
verifier: StreamVerifier,
}

impl DiscordWebhook {
Expand Down Expand Up @@ -58,50 +62,44 @@ impl DiscordWebhook {
}

impl RequestSignatureScheme for DiscordWebhook {
type Signature = (BytesMut, Signature);
type Signature = Signature;

type Error = Error;

async fn init(req: &HttpRequest) -> Result<Self, Self::Error> {
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<Self::Signature, Self::Error> {
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<Self::Signature, Self::Error> {
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)
}
}

Expand All @@ -119,6 +117,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
}))
Expand Down

0 comments on commit 4154ad1

Please sign in to comment.