pub trait RequestSignatureScheme: Sized {
    type Signature;
    type Error: Into<Error>;

    // Required methods
    fn init<'life0, 'async_trait>(
        req: &'life0 HttpRequest
    ) -> Pin<Box<dyn Future<Output = Result<Self, Self::Error>> + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;
    fn consume_chunk<'life0, 'life1, 'async_trait>(
        &'life0 mut self,
        req: &'life1 HttpRequest,
        chunk: Bytes
    ) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait,
             'life1: 'async_trait;
    fn finalize<'life0, 'async_trait>(
        self,
        req: &'life0 HttpRequest
    ) -> Pin<Box<dyn Future<Output = Result<Self::Signature, Self::Error>> + 'async_trait>>
       where Self: 'async_trait,
             'life0: 'async_trait;

    // Provided method
    fn verify(
        signature: Self::Signature,
        req: &HttpRequest
    ) -> Result<Self::Signature, Self::Error> { ... }
}
Expand description

Define a scheme for deriving and verifying some kind of signature from request parts.

There are 4 phases to calculating a signature while a request is being received:

  1. Initialize: Construct the signature scheme type and perform any pre-body calculation steps with request head parts.
  2. Consume body: For each body chunk received, fold it to the signature calculation.
  3. Finalize: Perform post-body calculation steps and finalize signature type.
  4. Verify: Check the true signature against a candidate signature; for example, a header added by the client. This phase is optional.

You’ll need to use the async-trait when implementing. Annotate your implementations with #[async_trait(?Send)].

Bring Your Own Crypto

It is up to the implementor to ensure that best security practices are being followed when implementing this trait, and in particular the verify method. There is no inherent preference for certain crypto ecosystems though many of the examples shown here will use types from RustCrypto.

RequestSignature Extractor

Types that implement this trait can be used with the RequestSignature extractor to declaratively derive the request signature alongside the desired body extractor.

Examples

This trait can be used to define:

  • API authentication schemes that requires a signature to be attached to the request, either with static keys or dynamic, per-user keys that are looked asynchronously from a database.
  • Request hashes derived from specific parts for cache lookups.

This example implementation does a simple HMAC calculation on the body using a static key. It does not implement verification.

use actix_web::{Error, HttpRequest, web::Bytes};
use actix_web_lab::extract::RequestSignatureScheme;
use async_trait::async_trait;
use hmac::{SimpleHmac, Mac, digest::CtOutput};
use sha2::Sha256;

struct AbcApi {
    /// Running state.
    hmac: SimpleHmac<Sha256>,
}

#[async_trait(?Send)]
impl RequestSignatureScheme for AbcApi {
    /// The constant-time verifiable output of the HMAC type.
    type Signature = CtOutput<SimpleHmac<Sha256>>;
    type Error = Error;

    async fn init(req: &HttpRequest) -> Result<Self, Self::Error> {
        // acquire HMAC signing key
        let key = req.app_data::<[u8; 32]>().unwrap();

        // construct HMAC signer
        let hmac = SimpleHmac::new_from_slice(&key[..]).unwrap();
        Ok(AbcApi { hmac })
    }

    async fn consume_chunk(&mut self, _req: &HttpRequest, chunk: Bytes) -> Result<(), Self::Error> {
        // digest body chunk
        self.hmac.update(&chunk);
        Ok(())
    }

    async fn finalize(self, _req: &HttpRequest) -> Result<Self::Signature, Self::Error> {
        // construct signature type
        Ok(self.hmac.finalize())
    }
}

Required Associated Types§

source

type Signature

The signature type returned from finalize and checked in verify.

Ideally, this type has constant-time equality capabilities.

source

type Error: Into<Error>

Error type used by all trait methods to signal missing precondition, processing errors, or verification failures.

Must be convertible to an error response; i.e., implements ResponseError.

Required Methods§

source

fn init<'life0, 'async_trait>( req: &'life0 HttpRequest ) -> Pin<Box<dyn Future<Output = Result<Self, Self::Error>> + 'async_trait>>where Self: 'async_trait, 'life0: 'async_trait,

Initialize signature scheme for incoming request.

Possible steps that should be included in init implementations:

  • initialization of signature scheme type
  • key lookup / initialization
  • pre-body digest updates
source

fn consume_chunk<'life0, 'life1, 'async_trait>( &'life0 mut self, req: &'life1 HttpRequest, chunk: Bytes ) -> Pin<Box<dyn Future<Output = Result<(), Self::Error>> + 'async_trait>>where Self: 'async_trait, 'life0: 'async_trait, 'life1: 'async_trait,

Fold received body chunk into signature.

If processing the request body one chunk at a time is not equivalent to processing it all at once, then the chunks will need to be added to a buffer.

source

fn finalize<'life0, 'async_trait>( self, req: &'life0 HttpRequest ) -> Pin<Box<dyn Future<Output = Result<Self::Signature, Self::Error>> + 'async_trait>>where Self: 'async_trait, 'life0: 'async_trait,

Finalize and output Signature type.

Possible steps that should be included in finalize implementations:

  • post-body digest updates
  • signature finalization

Provided Methods§

source

fn verify( signature: Self::Signature, req: &HttpRequest ) -> Result<Self::Signature, Self::Error>

Verify true signature against candidate signature.

The true signature is what has been calculated during request processing by the other methods in this trait. The candidate signature might be a signature provided by the client in order to prove ownership of a key or some other known signature to validate against.

Implementations should return signature if it is valid and return an error if it is not. The default implementation does no checks and just returns signature as-is.

Security

To avoid timing attacks, equality checks should be constant-time; check the docs of your chosen crypto library.

Implementors§