rocket_webhook/webhooks/interface/
public_key.rs

1//! Interface for webhooks that use asymmetric keys for signatures
2
3use rocket::{
4    Request,
5    data::Outcome,
6    http::Status,
7    outcome::try_outcome,
8    tokio::io::{AsyncRead, AsyncReadExt},
9};
10use tokio_util::bytes::Bytes;
11
12use crate::{
13    WebhookError,
14    webhooks::{Webhook, utils::body_size},
15};
16
17/// Public key algorithms
18pub mod algorithms;
19
20/// Trait for algorithms to use for asymmetric key verification
21pub trait WebhookPublicKeyAlgorithm {
22    fn verify(public_key: &Bytes, message: &[u8], signature: &[u8]) -> Result<(), String>;
23}
24
25/// Trait for webhooks that use asymmetric keys for signatures
26pub trait WebhookPublicKey: Webhook {
27    /// Algorithm used for verification
28    type ALG: WebhookPublicKeyAlgorithm;
29
30    /// Get the public key for the webhook signature. This is async in case the public key needs
31    /// to be fetched externally. The public key can be cached in Rocket state, accessible via
32    /// `req.rocket().state()`.
33    ///
34    /// Uses the [tokio_util::bytes::Bytes] struct to avoid unnecessary cloning.
35    fn public_key(
36        &self,
37        req: &Request<'_>,
38    ) -> impl Future<Output = Outcome<'_, Bytes, WebhookError>> + Send + Sync;
39
40    /// Get the expected signature from the request
41    fn expected_signature(&self, req: &Request<'_>) -> Outcome<'_, Vec<u8>, WebhookError>;
42
43    /// Get the message that needs to be verified. Any adjustments can be made to the body here
44    /// before calculating the signature (e.g. prefixes or hashes, etc.)
45    ///
46    /// Uses the [tokio_util::bytes::Bytes] struct to avoid unnecessary cloning of the body.
47    #[allow(unused_variables)]
48    fn message_to_verify(
49        &self,
50        req: &Request<'_>,
51        body: &Bytes,
52        time_bounds: (u32, u32),
53    ) -> Outcome<'_, Bytes, WebhookError> {
54        Outcome::Success(body.clone())
55    }
56
57    /// Read the raw body and verify with the public key and configured algorithm
58    fn validate_with_public_key(
59        &self,
60        req: &Request<'_>,
61        mut body: impl AsyncRead + Unpin + Send + Sync,
62        time_bounds: (u32, u32),
63    ) -> impl Future<Output = Outcome<'_, Vec<u8>, WebhookError>> + Send + Sync
64    where
65        Self: Sync,
66    {
67        async move {
68            // Get expected signature from request
69            let expected_signature = try_outcome!(self.expected_signature(req));
70
71            // Get public key
72            let public_key = try_outcome!(self.public_key(req).await);
73
74            // Read body stream
75            let mut raw_body = Vec::with_capacity(body_size(req.headers()).unwrap_or(512));
76            if let Err(e) = body.read_to_end(&mut raw_body).await {
77                return Outcome::Error((Status::BadRequest, WebhookError::Read(e)));
78            }
79            let raw_body = Bytes::from(raw_body);
80
81            // Verify signature with public key
82            let message = try_outcome!(self.message_to_verify(req, &raw_body, time_bounds));
83            if let Err(e) = Self::ALG::verify(&public_key, &message, &expected_signature) {
84                return Outcome::Error((Status::Unauthorized, WebhookError::Signature(e)));
85            }
86
87            Outcome::Success(raw_body.into())
88        }
89    }
90}