mirror of
https://git.asonix.dog/asonix/http-signature-normalization.git
synced 2024-11-23 18:00:59 +00:00
Signature extractor is now required for ensuring verification
This commit is contained in:
parent
b53c88fc2e
commit
85bbcb0bae
4 changed files with 27 additions and 55 deletions
|
@ -1,7 +1,7 @@
|
|||
[package]
|
||||
name = "http-signature-normalization-actix"
|
||||
description = "An HTTP Signatures library that leaves the signing to you"
|
||||
version = "0.7.2"
|
||||
version = "0.8.0"
|
||||
authors = ["asonix <asonix@asonix.dog>"]
|
||||
license = "AGPL-3.0"
|
||||
readme = "README.md"
|
||||
|
|
|
@ -16,7 +16,7 @@ This crate provides extensions the ClientRequest type from Actix Web, and provid
|
|||
actix-rt = "2.6.0"
|
||||
actix-web = "4.0.0"
|
||||
thiserror = "0.1"
|
||||
http-signature-normalization-actix = { version = "0.6.0", default-features = false, features = ["sha-2"] }
|
||||
http-signature-normalization-actix = { version = "0.8.0", default-features = false, features = ["sha-2"] }
|
||||
sha2 = "0.9"
|
||||
```
|
||||
|
||||
|
@ -91,7 +91,8 @@ impl SignatureVerify for MyVerify {
|
|||
}
|
||||
|
||||
async fn index(
|
||||
(_, sig_verified): (DigestVerified, SignatureVerified),
|
||||
_: DigestVerified,
|
||||
sig_verified: SignatureVerified,
|
||||
req: HttpRequest,
|
||||
_body: web::Bytes,
|
||||
) -> &'static str {
|
||||
|
@ -116,7 +117,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(VerifyDigest::new(Sha256::new()).optional())
|
||||
.wrap(VerifySignature::new(MyVerify, config.clone()).optional())
|
||||
.wrap(VerifySignature::new(MyVerify, config.clone()))
|
||||
.wrap(TracingLogger::default())
|
||||
.route("/", web::post().to(index))
|
||||
})
|
||||
|
|
|
@ -42,7 +42,8 @@ impl SignatureVerify for MyVerify {
|
|||
}
|
||||
|
||||
async fn index(
|
||||
(_, sig_verified): (DigestVerified, SignatureVerified),
|
||||
_: DigestVerified,
|
||||
sig_verified: SignatureVerified,
|
||||
req: HttpRequest,
|
||||
_body: web::Bytes,
|
||||
) -> &'static str {
|
||||
|
@ -67,7 +68,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
HttpServer::new(move || {
|
||||
App::new()
|
||||
.wrap(VerifyDigest::new(Sha256::new()).optional())
|
||||
.wrap(VerifySignature::new(MyVerify, config.clone()).optional())
|
||||
.wrap(VerifySignature::new(MyVerify, config.clone()))
|
||||
.wrap(TracingLogger::default())
|
||||
.route("/", web::post().to(index))
|
||||
})
|
||||
|
|
|
@ -11,6 +11,7 @@ use futures_util::future::LocalBoxFuture;
|
|||
use std::{
|
||||
collections::HashSet,
|
||||
future::{ready, Ready},
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
use tracing::{debug, Span};
|
||||
|
@ -18,8 +19,7 @@ use tracing_error::SpanTrace;
|
|||
use tracing_futures::Instrument;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
/// A marker type that can be used to guard routes when the signature middleware is set to
|
||||
/// 'optional'
|
||||
/// A marker type that is used to guard routes
|
||||
pub struct SignatureVerified(String);
|
||||
|
||||
impl SignatureVerified {
|
||||
|
@ -36,9 +36,7 @@ impl SignatureVerified {
|
|||
/// The Verify signature middleware
|
||||
///
|
||||
/// ```rust,ignore
|
||||
/// let middleware = VerifySignature::new(MyVerifier::new(), Config::default())
|
||||
/// .authorization()
|
||||
/// .optional();
|
||||
/// let middleware = VerifySignature::new(MyVerifier::new(), Config::default()).authorization();
|
||||
///
|
||||
/// HttpServer::new(move || {
|
||||
/// App::new()
|
||||
|
@ -47,11 +45,11 @@ impl SignatureVerified {
|
|||
/// .route("/unprotected", web::post().to(|| "No verification required"))
|
||||
/// })
|
||||
/// ```
|
||||
pub struct VerifySignature<T>(T, Config, HeaderKind, bool);
|
||||
pub struct VerifySignature<T>(T, Config, HeaderKind);
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Debug)]
|
||||
#[doc(hidden)]
|
||||
pub struct VerifyMiddleware<T, S>(S, Config, HeaderKind, bool, T);
|
||||
pub struct VerifyMiddleware<T, S>(Rc<S>, Config, HeaderKind, T);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
|
||||
enum HeaderKind {
|
||||
|
@ -112,9 +110,6 @@ enum VerifyErrorKind {
|
|||
#[error("Signature is not a valid string")]
|
||||
ParseSignature,
|
||||
|
||||
#[error("Signature is invalid")]
|
||||
Validate,
|
||||
|
||||
#[error("Request extension not present")]
|
||||
Extension,
|
||||
|
||||
|
@ -141,21 +136,12 @@ where
|
|||
/// By default, this middleware expects to verify Signature headers, and requires the presence
|
||||
/// of the header
|
||||
pub fn new(verify_signature: T, config: Config) -> Self {
|
||||
VerifySignature(verify_signature, config, HeaderKind::Signature, false)
|
||||
VerifySignature(verify_signature, config, HeaderKind::Signature)
|
||||
}
|
||||
|
||||
/// Verify Authorization headers instead of Signature headers
|
||||
pub fn authorization(self) -> Self {
|
||||
VerifySignature(self.0, self.1, HeaderKind::Authorization, self.3)
|
||||
}
|
||||
|
||||
/// Mark the presence of a Signature or Authorization header as optional
|
||||
///
|
||||
/// If a header is present, it will be verified, but if there is not one present, the request
|
||||
/// is passed through. This can be used to set a global middleware, and then guard each route
|
||||
/// handler with the [`SignatureVerified`] type.
|
||||
pub fn optional(self) -> Self {
|
||||
VerifySignature(self.0, self.1, self.2, true)
|
||||
VerifySignature(self.0, self.1, HeaderKind::Authorization)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -219,31 +205,25 @@ where
|
|||
let algorithm = unverified.algorithm().cloned();
|
||||
let key_id = unverified.key_id().to_owned();
|
||||
|
||||
let f1 = unverified.verify(|signature, signing_string| {
|
||||
let fut = span.in_scope(|| {
|
||||
self.4.clone().signature_verify(
|
||||
let verify_fut = unverified.verify(|signature, signing_string| {
|
||||
span.in_scope(|| {
|
||||
self.3.clone().signature_verify(
|
||||
algorithm,
|
||||
key_id.clone(),
|
||||
signature.to_string(),
|
||||
signing_string.to_string(),
|
||||
)
|
||||
});
|
||||
|
||||
fut.instrument(span.clone())
|
||||
})
|
||||
});
|
||||
|
||||
req.extensions_mut().insert(SignatureVerified(key_id));
|
||||
|
||||
let f2 = self.0.call(req);
|
||||
let service = Rc::clone(&self.0);
|
||||
|
||||
Box::pin(async move {
|
||||
let span = span;
|
||||
|
||||
if f1.await? {
|
||||
f2.await
|
||||
} else {
|
||||
Err(VerifyError::new(&span, VerifyErrorKind::Validate).into())
|
||||
if verify_fut.instrument(span).await? {
|
||||
req.extensions_mut().insert(SignatureVerified(key_id));
|
||||
}
|
||||
|
||||
service.call(req).await
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -292,10 +272,9 @@ where
|
|||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ready(Ok(VerifyMiddleware(
|
||||
service,
|
||||
Rc::new(service),
|
||||
self.1.clone(),
|
||||
self.2,
|
||||
self.3,
|
||||
self.0.clone(),
|
||||
)))
|
||||
}
|
||||
|
@ -321,7 +300,6 @@ where
|
|||
"Signature Verification",
|
||||
signature.kind = tracing::field::Empty,
|
||||
signature.expected_kind = tracing::field::display(&self.2),
|
||||
signature.optional = tracing::field::display(&self.3),
|
||||
);
|
||||
let authorization = req.headers().get("Authorization").is_some();
|
||||
let signature = req.headers().get("Signature").is_some();
|
||||
|
@ -340,17 +318,9 @@ where
|
|||
}
|
||||
} else {
|
||||
span.record("signature.kind", &tracing::field::display("None"));
|
||||
|
||||
if self.3 {
|
||||
return Box::pin(self.0.call(req));
|
||||
}
|
||||
}
|
||||
|
||||
Box::pin(ready(Err(VerifyError::new(
|
||||
&span,
|
||||
VerifyErrorKind::MissingSignature,
|
||||
)
|
||||
.into())))
|
||||
Box::pin(self.0.call(req))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue