http-signature-normalization/actix/README.md

168 lines
5.2 KiB
Markdown
Raw Permalink Normal View History

# HTTP Signature Normalization Actix
2019-09-21 16:26:11 +00:00
_An HTTP Signatures library that leaves the signing to you_
- [crates.io](https://crates.io/crates/http-signature-normalization-actix)
- [docs.rs](https://docs.rs/http-signature-normalization-actix)
- [Hit me up on Mastodon](https://asonix.dog/@asonix)
2019-09-21 16:26:11 +00:00
Http Signature Normalization is a minimal-dependency crate for producing HTTP Signatures with user-provided signing and verification. The API is simple; there's a series of steps for creation and verification with types that ensure reasonable usage.
## Usage
This crate provides extensions the ClientRequest type from Actix Web, and provides middlewares for verifying HTTP Signatures, and optionally, Digest headers
#### First, add this crate to your dependencies
```toml
2022-03-08 18:00:17 +00:00
actix-rt = "2.6.0"
actix-web = "4.0.0"
thiserror = "0.1"
http-signature-normalization-actix = { version = "0.8.0", default-features = false, features = ["sha-2"] }
2020-09-30 00:08:34 +00:00
sha2 = "0.9"
2019-09-21 16:26:11 +00:00
```
#### Then, use it in your client
```rust
2022-03-08 18:00:17 +00:00
async fn request(config: Config) -> Result<(), Box<dyn std::error::Error>> {
let digest = Sha256::new();
let mut response = Client::default()
.post("http://127.0.0.1:8010/")
2022-03-08 18:00:17 +00:00
.append_header(("User-Agent", "Actix Web"))
.append_header(("Accept", "text/plain"))
.insert_header(actix_web::http::header::Date(SystemTime::now().into()))
.signature_with_digest(config, "my-key-id", digest, "Hewwo-owo", |s| {
info!("Signing String\n{}", s);
Ok(base64::encode(s)) as Result<_, MyError>
2022-03-08 18:00:17 +00:00
})
2020-04-21 18:04:25 +00:00
.await?
.send()
.await
.map_err(|e| {
2022-03-08 18:00:17 +00:00
error!("Error, {}", e);
MyError::SendRequest
})?;
let body = response.body().await.map_err(|e| {
2022-03-08 18:00:17 +00:00
error!("Error, {}", e);
MyError::Body
})?;
2022-03-08 18:00:17 +00:00
info!("{:?}", body);
Ok(())
2019-09-21 16:26:11 +00:00
}
```
#### Or, use it in your server
```rust
#[derive(Clone, Debug)]
struct MyVerify;
impl SignatureVerify for MyVerify {
type Error = MyError;
type Future = Ready<Result<bool, Self::Error>>;
2019-09-21 16:26:11 +00:00
fn signature_verify(
&mut self,
algorithm: Option<Algorithm>,
key_id: String,
signature: String,
signing_string: String,
2019-09-21 16:26:11 +00:00
) -> Self::Future {
match algorithm {
Some(Algorithm::Hs2019) => (),
2022-03-08 18:00:17 +00:00
_ => return ready(Err(MyError::Algorithm)),
2019-09-21 16:26:11 +00:00
};
if key_id != "my-key-id" {
2022-03-08 18:00:17 +00:00
return ready(Err(MyError::Key));
2019-09-21 16:26:11 +00:00
}
let decoded = match base64::decode(&signature) {
Ok(decoded) => decoded,
2022-03-08 18:00:17 +00:00
Err(_) => return ready(Err(MyError::Decode)),
};
2019-09-21 16:26:11 +00:00
2022-03-08 18:00:17 +00:00
info!("Signing String\n{}", signing_string);
ready(Ok(decoded == signing_string.as_bytes()))
2019-09-21 16:26:11 +00:00
}
}
2022-03-08 18:00:17 +00:00
async fn index(
_: DigestVerified,
sig_verified: SignatureVerified,
2022-03-08 18:00:17 +00:00
req: HttpRequest,
_body: web::Bytes,
) -> &'static str {
info!("Verified request for {}", sig_verified.key_id());
info!("{:?}", req);
2019-09-21 16:26:11 +00:00
"Eyyyyup"
}
#[actix_rt::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
2022-03-08 18:00:17 +00:00
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
let subscriber = tracing_subscriber::Registry::default()
.with(env_filter)
.with(ErrorLayer::default())
.with(tracing_subscriber::fmt::layer());
tracing::subscriber::set_global_default(subscriber)?;
let config = Config::default().require_header("accept").require_digest();
2019-09-21 16:26:11 +00:00
HttpServer::new(move || {
App::new()
.wrap(VerifyDigest::new(Sha256::new()).optional())
.wrap(VerifySignature::new(MyVerify, config.clone()))
2022-03-08 18:00:17 +00:00
.wrap(TracingLogger::default())
2019-09-21 16:26:11 +00:00
.route("/", web::post().to(index))
})
.bind("127.0.0.1:8010")?
.run()
.await?;
2019-09-21 16:26:11 +00:00
Ok(())
}
#[derive(Debug, thiserror::Error)]
2019-09-21 16:26:11 +00:00
enum MyError {
2022-03-08 18:00:17 +00:00
#[error("Failed to verify, {0}")]
Verify(#[from] PrepareVerifyError),
2019-09-21 16:26:11 +00:00
#[error("Unsupported algorithm")]
2019-09-21 16:26:11 +00:00
Algorithm,
#[error("Couldn't decode signature")]
2019-09-21 16:26:11 +00:00
Decode,
#[error("Invalid key")]
2019-09-21 16:26:11 +00:00
Key,
}
impl ResponseError for MyError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST
2019-09-21 16:26:11 +00:00
}
fn error_response(&self) -> HttpResponse {
HttpResponse::BadRequest().finish()
2019-09-21 16:26:11 +00:00
}
}
```
### Contributing
Feel free to open issues for anything you find an issue with. Please note that any contributed code will be licensed under the AGPLv3.
2019-09-21 16:26:11 +00:00
### License
Copyright © 2022 Riley Trautman
HTTP Signature Normalization Actix is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
HTTP Signature Normalization Actix is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. This file is part of HTTP Signature Normalization Actix.
You should have received a copy of the GNU General Public License along with HTTP Signature Normalization Actix. If not, see [http://www.gnu.org/licenses/](http://www.gnu.org/licenses/).