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