# HTTP Signature Normalization 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) 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 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"] } sha2 = "0.9" ``` #### Then, use it in your client ```rust async fn request(config: Config) -> Result<(), Box> { let digest = Sha256::new(); let mut response = Client::default() .post("http://127.0.0.1:8010/") .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> }) .await? .send() .await .map_err(|e| { error!("Error, {}", e); MyError::SendRequest })?; let body = response.body().await.map_err(|e| { error!("Error, {}", e); MyError::Body })?; info!("{:?}", body); Ok(()) } ``` #### Or, use it in your server ```rust #[derive(Clone, Debug)] struct MyVerify; impl SignatureVerify for MyVerify { type Error = MyError; type Future = Ready>; fn signature_verify( &mut self, algorithm: Option, key_id: String, signature: String, signing_string: String, ) -> Self::Future { match algorithm { Some(Algorithm::Hs2019) => (), _ => return ready(Err(MyError::Algorithm)), }; if key_id != "my-key-id" { return ready(Err(MyError::Key)); } let decoded = match base64::decode(&signature) { Ok(decoded) => decoded, Err(_) => return ready(Err(MyError::Decode)), }; info!("Signing String\n{}", signing_string); ready(Ok(decoded == signing_string.as_bytes())) } } async fn index( _: DigestVerified, sig_verified: SignatureVerified, req: HttpRequest, _body: web::Bytes, ) -> &'static str { info!("Verified request for {}", sig_verified.key_id()); info!("{:?}", req); "Eyyyyup" } #[actix_rt::main] async fn main() -> Result<(), Box> { 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(); HttpServer::new(move || { App::new() .wrap(VerifyDigest::new(Sha256::new()).optional()) .wrap(VerifySignature::new(MyVerify, config.clone())) .wrap(TracingLogger::default()) .route("/", web::post().to(index)) }) .bind("127.0.0.1:8010")? .run() .await?; Ok(()) } #[derive(Debug, thiserror::Error)] enum MyError { #[error("Failed to verify, {0}")] Verify(#[from] PrepareVerifyError), #[error("Unsupported algorithm")] Algorithm, #[error("Couldn't decode signature")] Decode, #[error("Invalid key")] Key, } impl ResponseError for MyError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } fn error_response(&self) -> HttpResponse { HttpResponse::BadRequest().finish() } } ``` ### 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. ### 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/).