Enable creation of HTTP signatures for all types of requests
This commit is contained in:
parent
d2c70453e2
commit
d94e3d610d
3 changed files with 76 additions and 27 deletions
|
@ -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!(
|
||||
|
|
|
@ -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!(
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue