Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use ed25519-dalek fork with digest verifier #34

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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/mkj/curve25519-dalek.git", branch = "parts2" }
env_logger = "0.11"
futures-util = { version = "0.3.17", default-features = false, features = ["std", "io"] }
generic-array = "0.14"
Expand Down
48 changes: 22 additions & 26 deletions actix-web-lab/examples/discord_webhook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ use actix_web::{
App, Error, HttpRequest, HttpServer,
};
use actix_web_lab::extract::{Json, RequestSignature, RequestSignatureScheme};
use bytes::{BufMut as _, BytesMut};
use ed25519_dalek::{Signature, Verifier as _, VerifyingKey};
use ed25519_dalek::{Signature, StreamVerifier, VerifyingKey};
use hex_literal::hex;
use once_cell::sync::Lazy;
use rustls::{pki_types::PrivateKeyDer, ServerConfig};
Expand All @@ -24,13 +23,15 @@ 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());

#[derive(Debug)]
/// Signature scheme for Discord interactions/webhooks.
///
/// Verification is done in `finalize` so this does not support optional verification.
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 +59,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 +114,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
Loading