Enable creation of HTTP signatures for all types of requests

This commit is contained in:
silverpill 2021-11-17 21:35:17 +00:00
parent d2c70453e2
commit d94e3d610d
3 changed files with 76 additions and 27 deletions

View file

@ -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!(

View file

@ -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<String>,
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<SignatureHeaders, SignatureError> {
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::<Vec<String>>().join("\n");
let headers_parameter = headers.iter()
.map(|(name, _)| name.to_string())
.collect::<Vec<String>>().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!(

View file

@ -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::<Uri>().unwrap();
let mut request_headers = HeaderMap::new();
request_headers.insert(