http-signature-normalization/http-signature-normalization-actix/README.md

173 lines
5.4 KiB
Markdown
Raw Normal View History

2019-09-21 16:26:11 +00:00
# HTTP Signature Normaliztion Actix
_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"
2022-03-08 18:00:17 +00:00
http-signature-normalization-actix = { version = "0.6.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(
(_, sig_verified): (DigestVerified, SignatureVerified),
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())
2022-03-08 18:00:17 +00:00
.wrap(VerifySignature::new(MyVerify, config.clone()).optional())
.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
Unless otherwise stated, all contributions to this project will be licensed under the CSL with
the exceptions listed in the License section of this file.
### License
This work is licensed under the Cooperative Software License. This is not a Free Software
License, but may be considered a "source-available License." For most hobbyists, self-employed
developers, worker-owned companies, and cooperatives, this software can be used in most
projects so long as this software is distributed under the terms of the CSL. For more
information, see the provided LICENSE file. If none exists, the license can be found online
[here](https://lynnesbian.space/csl/). If you are a free software project and wish to use this
software under the terms of the GNU Affero General Public License, please contact me at
[asonix@asonix.dog](mailto:asonix@asonix.dog) and we can sort that out. If you wish to use this
project under any other license, especially in proprietary software, the answer is likely no.
2020-04-18 19:36:53 +00:00
Http Signature Normalization Actix is currently licensed under the AGPL to the Lemmy project, found
at [github.com/LemmyNet/lemmy](https://github.com/LemmyNet/lemmy)