From d94e3d610d7690c1e464b914257003560db291fb Mon Sep 17 00:00:00 2001 From: silverpill Date: Wed, 17 Nov 2021 21:35:17 +0000 Subject: [PATCH] Enable creation of HTTP signatures for all types of requests --- src/activitypub/deliverer.rs | 22 +++++----- src/http_signatures/create.rs | 78 ++++++++++++++++++++++++++++------- src/http_signatures/verify.rs | 3 +- 3 files changed, 76 insertions(+), 27 deletions(-) diff --git a/src/activitypub/deliverer.rs b/src/activitypub/deliverer.rs index 093e520..5468d19 100644 --- a/src/activitypub/deliverer.rs +++ b/src/activitypub/deliverer.rs @@ -1,3 +1,4 @@ +use actix_web::http::Method; use rsa::RsaPrivateKey; use crate::config::{Environment, Config}; @@ -36,12 +37,22 @@ async fn send_activity( ) -> Result<(), DelivererError> { log::info!("sending activity to {}: {}", inbox_url, activity_json); let headers = create_http_signature( + Method::POST, inbox_url, activity_json, actor_key, actor_key_id, )?; + let client = reqwest::Client::new(); + let request = client.post(inbox_url) + .header("Host", headers.host) + .header("Date", headers.date) + .header("Digest", headers.digest.unwrap()) + .header("Signature", headers.signature) + .header("Content-Type", ACTIVITY_CONTENT_TYPE) + .body(activity_json.to_owned()); + match config.environment { Environment::Development => { log::info!( @@ -50,17 +61,8 @@ async fn send_activity( ); }, Environment::Production => { - let client = reqwest::Client::new(); // Default timeout is 30s - let response = client.post(inbox_url) - .header("Host", headers.host) - .header("Date", headers.date) - .header("Digest", headers.digest) - .header("Signature", headers.signature) - .header("Content-Type", ACTIVITY_CONTENT_TYPE) - .body(activity_json.to_owned()) - .send() - .await?; + let response = request.send().await?; let response_status = response.status(); let response_text = response.text().await?; log::info!( diff --git a/src/http_signatures/create.rs b/src/http_signatures/create.rs index b76ca3b..30ec62a 100644 --- a/src/http_signatures/create.rs +++ b/src/http_signatures/create.rs @@ -1,3 +1,4 @@ +use actix_web::http::Method; use chrono::Utc; use rsa::RsaPrivateKey; @@ -6,7 +7,7 @@ use crate::utils::crypto::{sign_message, get_message_digest}; pub struct SignatureHeaders { pub host: String, pub date: String, - pub digest: String, + pub digest: Option, pub signature: String, } @@ -22,6 +23,7 @@ pub enum SignatureError { /// Creates HTTP signature according to the old HTTP Signatures Spec: /// https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures. pub fn create_http_signature( + request_method: Method, request_url: &str, request_body: &str, actor_key: &RsaPrivateKey, @@ -29,27 +31,45 @@ pub fn create_http_signature( ) -> Result { let request_url_object = url::Url::parse(request_url) .map_err(|_| SignatureError::UrlError)?; - let host = request_url_object.host_str() - .ok_or(SignatureError::UrlError)?; - let date = Utc::now().format("%a, %d %b %Y %T GMT").to_string(); - let digest = get_message_digest(request_body); - let message = format!( - "(request-target): post {}\nhost: {}\ndate: {}\ndigest: {}", + let request_target = format!( + "{} {}", + request_method.as_str().to_lowercase(), request_url_object.path(), - host, - date, - digest, ); - let headers_parameter = &["(request-target)", "host", "date", "digest"]; + let host = request_url_object.host_str() + .ok_or(SignatureError::UrlError)? + .to_string(); + let date = Utc::now().format("%a, %d %b %Y %T GMT").to_string(); + let digest = if request_body.is_empty() { + None + } else { + Some(get_message_digest(request_body)) + }; + + let mut headers = vec![ + ("(request-target)", &request_target), + ("host", &host), + ("date", &date), + ]; + if let Some(ref digest) = digest { + headers.push(("digest", digest)); + }; + + let message = headers.iter() + .map(|(name, value)| format!("{}: {}", name, value)) + .collect::>().join("\n"); + let headers_parameter = headers.iter() + .map(|(name, _)| name.to_string()) + .collect::>().join(" "); let signature_parameter = sign_message(actor_key, &message)?; let signature_header = format!( r#"keyId="{}",headers="{}",signature="{}""#, actor_key_id, - headers_parameter.join(" "), + headers_parameter, signature_parameter, ); let headers = SignatureHeaders { - host: host.to_string(), + host, date, digest, signature: signature_header, @@ -63,13 +83,41 @@ mod tests { use super::*; #[test] - fn test_create_signature() { + fn test_create_signature_get() { + let request_url = "https://example.org/inbox"; + let actor_key = RsaPrivateKey::new(&mut OsRng, 512).unwrap(); + let actor_key_id = "https://myserver.org/actor#main-key"; + + let headers = create_http_signature( + Method::GET, + request_url, + "", + &actor_key, + actor_key_id, + ).unwrap(); + + assert_eq!(headers.host, "example.org"); + assert_eq!(headers.digest, None); + let expected_signature_header = concat!( + r#"keyId="https://myserver.org/actor#main-key","#, + r#"headers="(request-target) host date","#, + r#"signature=""#, + ); + assert_eq!( + headers.signature.starts_with(expected_signature_header), + true, + ); + } + + #[test] + fn test_create_signature_post() { let request_url = "https://example.org/inbox"; let request_body = "{}"; let actor_key = RsaPrivateKey::new(&mut OsRng, 512).unwrap(); let actor_key_id = "https://myserver.org/actor#main-key"; let result = create_http_signature( + Method::POST, request_url, request_body, &actor_key, @@ -80,7 +128,7 @@ mod tests { let headers = result.unwrap(); assert_eq!(headers.host, "example.org"); assert_eq!( - headers.digest, + headers.digest.unwrap(), "SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=", ); let expected_signature_header = concat!( diff --git a/src/http_signatures/verify.rs b/src/http_signatures/verify.rs index 9077606..638f5fe 100644 --- a/src/http_signatures/verify.rs +++ b/src/http_signatures/verify.rs @@ -157,13 +157,12 @@ pub async fn verify_http_signature( #[cfg(test)] mod tests { - use std::str::FromStr; use actix_web::http::{header, HeaderMap, HeaderName, HeaderValue, Uri}; use super::*; #[test] fn test_parse_signature() { - let request_method = Method::from_str("POST").unwrap(); + let request_method = Method::POST; let request_uri = "/user/123/inbox".parse::().unwrap(); let mut request_headers = HeaderMap::new(); request_headers.insert(