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}