http_signature_normalization_actix_extractor/
lib.rs

1#![deny(missing_docs)]
2
3//! # Http Signature Normalization Actix Extractor
4//! _Experimental Extractor for request signatures_
5//!
6//! This library takes a different approach from the other implementation, which prefers
7//! Middlewares.
8//!
9//! ```rust
10//! use actix_web::{http::StatusCode, web, App, HttpRequest, HttpResponse, HttpServer, ResponseError};
11//! use http_signature_normalization_actix_extractor::{
12//!     Algorithm, Config, ConfigGenerator, DeprecatedAlgorithm, Signed, VerifyKey,
13//! };
14//! use sha2::Sha256;
15//!
16//! #[actix_web::main]
17//! async fn main() -> std::io::Result<()> {
18//!     /*
19//!     HttpServer::new(|| App::new().route("/", web::post().to(protected)))
20//!         .bind("127.0.0.1:8010")?
21//!         .run()
22//!         .await
23//!     */
24//!     Ok(())
25//! }
26//!
27//! async fn protected(signed_request: Signed<String, Cfg, Sha256, Key>) -> &'static str {
28//!     let (value, signature) = signed_request.into_parts();
29//!
30//!     println!("{}", value);
31//!     println!("{:#?}", signature);
32//!
33//!     "hewwo, mr obama"
34//! }
35//!
36//! pub struct Cfg;
37//!
38//! #[derive(Debug)]
39//! pub struct Key;
40//!
41//! #[derive(Debug, thiserror::Error)]
42//! pub enum VerifyError {
43//!     #[error("Unsupported algorithm")]
44//!     Algorithm,
45//!
46//!     #[error("Couldn't decode signature")]
47//!     Decode,
48//!
49//!     #[error("Invalid key")]
50//!     Key,
51//! }
52//!
53//! impl ConfigGenerator for Cfg {
54//!     fn config() -> Config {
55//!         Config::new().require_header("accept")
56//!     }
57//! }
58//!
59//! #[async_trait::async_trait(?Send)]
60//! impl VerifyKey for Key {
61//!     type Error = VerifyError;
62//!
63//!     async fn init(
64//!         _: &HttpRequest,
65//!         key_id: &str,
66//!         algorithm: Option<&Algorithm>,
67//!     ) -> Result<Self, Self::Error> {
68//!         match algorithm {
69//!             Some(Algorithm::Hs2019 | Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => (),
70//!             _ => return Err(VerifyError::Algorithm),
71//!         };
72//!
73//!         if key_id != "my-key-id" {
74//!             return Err(VerifyError::Key);
75//!         }
76//!
77//!         Ok(Key)
78//!     }
79//!
80//!     fn verify(&mut self, signature: &str, signing_string: &str) -> Result<bool, Self::Error> {
81//!         use subtle::ConstantTimeEq;
82//!
83//!         let decoded = base64::decode(&signature).map_err(|_| VerifyError::Decode)?;
84//!
85//!         Ok(decoded.ct_eq(signing_string.as_bytes()).into())
86//!     }
87//! }
88//!
89//! impl ResponseError for VerifyError {
90//!     fn status_code(&self) -> StatusCode {
91//!         StatusCode::BAD_REQUEST
92//!     }
93//!
94//!     fn error_response(&self) -> HttpResponse {
95//!         HttpResponse::BadRequest().finish()
96//!     }
97//! }
98//! ```
99
100pub use actix_web_lab::extract::RequestSignature;
101pub use http_signature_normalization::{
102    verify::{Algorithm, DeprecatedAlgorithm},
103    Config, PrepareVerifyError,
104};
105
106/// An alias to simplify extracting a signed request
107/// ```rust,ignore
108/// async fn protected(_: Signed<String, (), (), Key>) -> &'static str {
109///     "hewwo, mr obama"
110/// }
111/// ```
112pub type Signed<Extractor, Config, Digest, VerifyKey> =
113    RequestSignature<Extractor, SignedRequest<Config, Digest, VerifyKey>>;
114
115#[cfg(feature = "sha-2")]
116mod sha2_digest;
117
118#[derive(Debug)]
119/// Errors produced by the Extractor
120pub enum Error {
121    /// The provided Digest header was invalid
122    Digest,
123
124    /// The provided Signature or Authorization header was invalid
125    Signature,
126
127    /// Another header required for verifying the signature is invalid
128    InvalidHeaderValue,
129
130    /// There was an error preparing for verification
131    PrepareVerify(PrepareVerifyError),
132}
133
134/// The SignedRequest Signature scheme
135///
136/// This implements SignatureScheme to allow extracing a signed request
137pub struct SignedRequest<C, D, K> {
138    config_generator: std::marker::PhantomData<C>,
139    key: std::marker::PhantomData<K>,
140    digest_verifier: Option<D>,
141    unverified: http_signature_normalization::verify::Unverified,
142}
143
144#[derive(Debug)]
145/// A parsed part of a Digest header
146pub struct DigestPart {
147    /// The Algorithm this digest was computed with
148    pub algorithm: String,
149
150    /// The base64-encoded digest
151    pub digest: String,
152}
153
154#[derive(Debug)]
155/// All the required pieces for validating a request signature
156pub struct Signature<D, K> {
157    key: K,
158    unverified: http_signature_normalization::verify::Unverified,
159    digest_verifier: D,
160    digest_parts: Option<Vec<DigestPart>>,
161}
162
163/// Injects a customized [`Config`] type for signature verification
164///
165/// If you don't need to customize the `Config`, you can use `()`
166///
167/// ```rust
168/// use http_signature_normalization_actix_extractor::{Config, ConfigGenerator};
169///
170/// struct Gen;
171///
172/// impl ConfigGenerator for Gen {
173///     fn config() -> Config {
174///         Config::new()
175///     }
176/// }
177/// ```
178pub trait ConfigGenerator {
179    /// Produce a new `Config`
180    fn config() -> Config;
181}
182
183#[async_trait::async_trait(?Send)]
184/// Extracts a key from the request
185///
186/// ```rust
187/// use actix_web::{http::StatusCode, web::Data, HttpRequest, HttpResponse, ResponseError};
188/// use http_signature_normalization_actix_extractor::{Algorithm, DeprecatedAlgorithm, VerifyKey};
189/// use openssl::{hash::MessageDigest, pkey::{PKey, Public}, sign::Verifier};
190///
191/// pub struct OpenSSLPublicKey(PKey<Public>);
192///
193/// #[async_trait::async_trait(?Send)]
194/// impl VerifyKey for OpenSSLPublicKey {
195///     type Error = VerifyError;
196///
197///     async fn init(
198///         req: &HttpRequest,
199///         key_id: &str,
200///         algorithm: Option<&Algorithm>,
201///     ) -> Result<Self, Self::Error> {
202///         match algorithm {
203///             Some(Algorithm::Hs2019 | Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => (),
204///             _ => return Err(VerifyError::Algorithm),
205///         };
206///
207///         if key_id != "my-key-id" {
208///             return Err(VerifyError::Key);
209///         }
210///
211///         let key = req.app_data::<Data<PKey<Public>>>().expect("Key loaded").as_ref().clone();
212///
213///         Ok(OpenSSLPublicKey(key))
214///     }
215///
216///     fn verify(&mut self, signature: &str, signing_string: &str) -> Result<bool, Self::Error> {
217///         let decoded = openssl::base64::decode_block(&signature).map_err(|_| VerifyError::Decode)?;
218///
219///         let verifier = Verifier::new(MessageDigest::sha256(), &self.0).map_err(|_| VerifyError::Verifier)?;
220///
221///         verifier.verify(&decoded).map_err(|_| VerifyError::Verify)
222///     }
223/// }
224///
225/// #[derive(Debug, thiserror::Error)]
226/// pub enum VerifyError {
227///     #[error("Unsupported algorithm")]
228///     Algorithm,
229///
230///     #[error("Couldn't decode signature")]
231///     Decode,
232///
233///     #[error("Invalid key")]
234///     Key,
235///
236///     #[error("Failed to create Verifier from key")]
237///     Verifier,
238///
239///     #[error("Failed to verify signature")]
240///     Verify,
241/// }
242///
243/// impl ResponseError for VerifyError {
244///     fn status_code(&self) -> StatusCode {
245///         StatusCode::BAD_REQUEST
246///     }
247///
248///     fn error_response(&self) -> HttpResponse {
249///         HttpResponse::BadRequest().finish()
250///     }
251/// }
252/// ```
253pub trait VerifyKey: Sized + Send {
254    /// Errors that can happen when extracting keys or verifying the signature
255    type Error: Into<actix_web::Error>;
256
257    /// Extract the key from the request, given the key_id and algorithm
258    async fn init(
259        req: &actix_web::HttpRequest,
260        key_id: &str,
261        algorithm: Option<&Algorithm>,
262    ) -> Result<Self, Self::Error>;
263
264    /// Verify the signature with the given signing string
265    fn verify(&mut self, signature: &str, signing_string: &str) -> Result<bool, Self::Error>;
266}
267
268/// Verifies the Digest header from the request
269///
270/// For endpoints that do not accept request bodies, `()` can be used as the verifier
271///
272/// ### Example:
273/// ```rust
274/// use http_signature_normalization_actix_extractor::{DigestPart, VerifyDigest};
275/// use openssl::sha::Sha256;
276///
277/// struct OpenSSLSha256(Option<Sha256>);
278///
279/// impl Default for OpenSSLSha256 {
280///     fn default() -> Self {
281///         Self::new()
282///     }
283/// }
284///
285/// impl OpenSSLSha256 {
286///     fn new() -> Self {
287///         Self(Some(Sha256::new()))
288///     }
289/// }
290///
291/// impl VerifyDigest for OpenSSLSha256 {
292///     fn update(&mut self, bytes: &[u8]) {
293///         self.0.as_mut().expect("Update called after verify").update(bytes);
294///     }
295///
296///     fn verify(&mut self, parts: &[DigestPart]) -> bool {
297///         if let Some(decoded) = parts.iter().find_map(|p| {
298///             if p.algorithm.to_lowercase() == "sha-256" {
299///                 openssl::base64::decode_block(&p.digest).ok()
300///             } else {
301///                 None
302///             }
303///         }) {
304///             return openssl::memcmp::eq(
305///                 &self.0.take().expect("verify called more than once").finish(),
306///                 &decoded,
307///             );
308///         }
309///
310///         false
311///     }
312/// }
313/// ```
314pub trait VerifyDigest: Default + Send + 'static {
315    /// Whether to run this verifier
316    const REQUIRED: bool = true;
317
318    /// Update the verifier with th eprovided bytes
319    fn update(&mut self, bytes: &[u8]);
320
321    /// Given a slice of parts, verify that the one matching the current verifier is valid
322    fn verify(&mut self, parts: &[DigestPart]) -> bool;
323}
324
325trait DigestName {
326    const NAME: &'static str;
327}
328
329#[async_trait::async_trait(?Send)]
330impl<C, D, K> actix_web_lab::extract::RequestSignatureScheme for SignedRequest<C, D, K>
331where
332    C: ConfigGenerator,
333    D: VerifyDigest,
334    K: VerifyKey,
335{
336    type Signature = Signature<D, K>;
337
338    type Error = actix_web::Error;
339
340    async fn init(req: &actix_web::HttpRequest) -> Result<Self, Self::Error> {
341        let config = C::config();
342
343        let unverified = begin_verify(
344            &config,
345            req.method(),
346            req.uri().path_and_query(),
347            req.headers(),
348        )?;
349
350        Ok(SignedRequest {
351            config_generator: std::marker::PhantomData,
352            key: std::marker::PhantomData,
353            digest_verifier: Some(D::default()),
354            unverified,
355        })
356    }
357
358    async fn consume_chunk(
359        &mut self,
360        _: &actix_web::HttpRequest,
361        chunk: actix_web::web::Bytes,
362    ) -> Result<(), Self::Error> {
363        if D::REQUIRED {
364            let mut verifier = self
365                .digest_verifier
366                .take()
367                .expect("consume_chunk polled concurrently");
368
369            self.digest_verifier = Some(
370                actix_web::web::block(move || {
371                    verifier.update(&chunk);
372                    verifier
373                })
374                .await
375                .expect("Panic in verifier update"),
376            );
377        }
378
379        Ok(())
380    }
381
382    async fn finalize(
383        mut self,
384        req: &actix_web::HttpRequest,
385    ) -> Result<Self::Signature, Self::Error> {
386        let key = K::init(req, self.unverified.key_id(), self.unverified.algorithm())
387            .await
388            .map_err(Into::into)?;
389
390        let digest_parts = if D::REQUIRED {
391            req.headers().get("digest").and_then(parse_digest)
392        } else {
393            None
394        };
395
396        Ok(Signature {
397            key,
398            unverified: self.unverified,
399            digest_verifier: self
400                .digest_verifier
401                .take()
402                .expect("finalize called more than once"),
403            digest_parts,
404        })
405    }
406
407    fn verify(
408        signature: Self::Signature,
409        _: &actix_web::HttpRequest,
410    ) -> Result<Self::Signature, Self::Error> {
411        let Signature {
412            mut key,
413            unverified,
414            mut digest_verifier,
415            digest_parts,
416        } = signature;
417
418        if !unverified
419            .verify(|sig, signing_string| key.verify(sig, signing_string))
420            .map_err(Into::into)?
421        {
422            return Err(Error::Signature.into());
423        }
424
425        if D::REQUIRED && !digest_verifier.verify(digest_parts.as_deref().unwrap_or(&[])) {
426            return Err(Error::Digest.into());
427        }
428
429        let signature = Signature {
430            key,
431            unverified,
432            digest_verifier,
433            digest_parts,
434        };
435
436        Ok(signature)
437    }
438}
439
440fn parse_digest(h: &actix_web::http::header::HeaderValue) -> Option<Vec<DigestPart>> {
441    let h = h.to_str().ok()?.split(';').next()?;
442    let v: Vec<_> = h
443        .split(',')
444        .filter_map(|p| {
445            let mut iter = p.splitn(2, '=');
446            iter.next()
447                .and_then(|alg| iter.next().map(|value| (alg, value)))
448        })
449        .map(|(alg, value)| DigestPart {
450            algorithm: alg.to_owned(),
451            digest: value.to_owned(),
452        })
453        .collect();
454
455    if v.is_empty() {
456        None
457    } else {
458        Some(v)
459    }
460}
461
462fn begin_verify(
463    config: &Config,
464    method: &actix_web::http::Method,
465    path_and_query: Option<&actix_web::http::uri::PathAndQuery>,
466    headers: &actix_web::http::header::HeaderMap,
467) -> Result<http_signature_normalization::verify::Unverified, Error> {
468    let headers = headers
469        .iter()
470        .map(|(k, v)| v.to_str().map(|v| (k.to_string(), v.to_string())))
471        .collect::<Result<std::collections::BTreeMap<_, _>, actix_web::http::header::ToStrError>>(
472        )?;
473
474    let path_and_query = path_and_query
475        .map(|p| p.to_string())
476        .unwrap_or_else(|| "/".to_string());
477
478    let unverified = config.begin_verify(method.as_ref(), &path_and_query, headers)?;
479
480    Ok(unverified)
481}
482
483impl ConfigGenerator for () {
484    fn config() -> Config {
485        Config::new()
486    }
487}
488
489impl VerifyDigest for () {
490    const REQUIRED: bool = false;
491    fn update(&mut self, _: &[u8]) {}
492    fn verify(&mut self, _: &[DigestPart]) -> bool {
493        true
494    }
495}
496
497impl std::fmt::Display for Error {
498    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
499        match self {
500            Self::Digest => write!(f, "Digest is inavlid"),
501            Self::Signature => write!(f, "Signature is invalid"),
502            Self::InvalidHeaderValue => write!(f, "Invalid header value"),
503            Self::PrepareVerify(_) => write!(f, "Error preparint verification"),
504        }
505    }
506}
507
508impl std::error::Error for Error {
509    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
510        match self {
511            Self::PrepareVerify(ref e) => Some(e),
512            Self::Digest => None,
513            Self::Signature => None,
514            Self::InvalidHeaderValue => None,
515        }
516    }
517}
518
519impl actix_web::ResponseError for Error {
520    fn status_code(&self) -> actix_web::http::StatusCode {
521        actix_web::http::StatusCode::BAD_REQUEST
522    }
523
524    fn error_response(&self) -> actix_web::HttpResponse<actix_web::body::BoxBody> {
525        actix_web::HttpResponse::build(self.status_code()).finish()
526    }
527}
528
529impl From<PrepareVerifyError> for Error {
530    fn from(e: PrepareVerifyError) -> Self {
531        Error::PrepareVerify(e)
532    }
533}
534
535impl From<actix_web::http::header::ToStrError> for Error {
536    fn from(_: actix_web::http::header::ToStrError) -> Self {
537        Error::InvalidHeaderValue
538    }
539}