Trait actix_web_lab::extract::RequestSignatureScheme
source · 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:
- Initialize: Construct the signature scheme type and perform any pre-body calculation steps with request head parts.
- Consume body: For each body chunk received, fold it to the signature calculation.
- Finalize: Perform post-body calculation steps and finalize signature type.
- 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§
sourcetype Error: Into<Error>
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§
sourcefn 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 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
sourcefn 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 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.
sourcefn 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,
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§
sourcefn verify(
signature: Self::Signature,
req: &HttpRequest
) -> Result<Self::Signature, Self::Error>
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.