Enable creation of HTTP signatures for all types of requests
This commit is contained in:
parent
d2c70453e2
commit
d94e3d610d
|
@ -1,3 +1,4 @@
|
||||||
|
use actix_web::http::Method;
|
||||||
use rsa::RsaPrivateKey;
|
use rsa::RsaPrivateKey;
|
||||||
|
|
||||||
use crate::config::{Environment, Config};
|
use crate::config::{Environment, Config};
|
||||||
|
@ -36,12 +37,22 @@ async fn send_activity(
|
||||||
) -> Result<(), DelivererError> {
|
) -> Result<(), DelivererError> {
|
||||||
log::info!("sending activity to {}: {}", inbox_url, activity_json);
|
log::info!("sending activity to {}: {}", inbox_url, activity_json);
|
||||||
let headers = create_http_signature(
|
let headers = create_http_signature(
|
||||||
|
Method::POST,
|
||||||
inbox_url,
|
inbox_url,
|
||||||
activity_json,
|
activity_json,
|
||||||
actor_key,
|
actor_key,
|
||||||
actor_key_id,
|
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 {
|
match config.environment {
|
||||||
Environment::Development => {
|
Environment::Development => {
|
||||||
log::info!(
|
log::info!(
|
||||||
|
@ -50,17 +61,8 @@ async fn send_activity(
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
Environment::Production => {
|
Environment::Production => {
|
||||||
let client = reqwest::Client::new();
|
|
||||||
// Default timeout is 30s
|
// Default timeout is 30s
|
||||||
let response = client.post(inbox_url)
|
let response = request.send().await?;
|
||||||
.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_status = response.status();
|
let response_status = response.status();
|
||||||
let response_text = response.text().await?;
|
let response_text = response.text().await?;
|
||||||
log::info!(
|
log::info!(
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use actix_web::http::Method;
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use rsa::RsaPrivateKey;
|
use rsa::RsaPrivateKey;
|
||||||
|
|
||||||
|
@ -6,7 +7,7 @@ use crate::utils::crypto::{sign_message, get_message_digest};
|
||||||
pub struct SignatureHeaders {
|
pub struct SignatureHeaders {
|
||||||
pub host: String,
|
pub host: String,
|
||||||
pub date: String,
|
pub date: String,
|
||||||
pub digest: String,
|
pub digest: Option<String>,
|
||||||
pub signature: String,
|
pub signature: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +23,7 @@ pub enum SignatureError {
|
||||||
/// Creates HTTP signature according to the old HTTP Signatures Spec:
|
/// Creates HTTP signature according to the old HTTP Signatures Spec:
|
||||||
/// https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures.
|
/// https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures.
|
||||||
pub fn create_http_signature(
|
pub fn create_http_signature(
|
||||||
|
request_method: Method,
|
||||||
request_url: &str,
|
request_url: &str,
|
||||||
request_body: &str,
|
request_body: &str,
|
||||||
actor_key: &RsaPrivateKey,
|
actor_key: &RsaPrivateKey,
|
||||||
|
@ -29,27 +31,45 @@ pub fn create_http_signature(
|
||||||
) -> Result<SignatureHeaders, SignatureError> {
|
) -> Result<SignatureHeaders, SignatureError> {
|
||||||
let request_url_object = url::Url::parse(request_url)
|
let request_url_object = url::Url::parse(request_url)
|
||||||
.map_err(|_| SignatureError::UrlError)?;
|
.map_err(|_| SignatureError::UrlError)?;
|
||||||
let host = request_url_object.host_str()
|
let request_target = format!(
|
||||||
.ok_or(SignatureError::UrlError)?;
|
"{} {}",
|
||||||
let date = Utc::now().format("%a, %d %b %Y %T GMT").to_string();
|
request_method.as_str().to_lowercase(),
|
||||||
let digest = get_message_digest(request_body);
|
|
||||||
let message = format!(
|
|
||||||
"(request-target): post {}\nhost: {}\ndate: {}\ndigest: {}",
|
|
||||||
request_url_object.path(),
|
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_parameter = sign_message(actor_key, &message)?;
|
||||||
let signature_header = format!(
|
let signature_header = format!(
|
||||||
r#"keyId="{}",headers="{}",signature="{}""#,
|
r#"keyId="{}",headers="{}",signature="{}""#,
|
||||||
actor_key_id,
|
actor_key_id,
|
||||||
headers_parameter.join(" "),
|
headers_parameter,
|
||||||
signature_parameter,
|
signature_parameter,
|
||||||
);
|
);
|
||||||
let headers = SignatureHeaders {
|
let headers = SignatureHeaders {
|
||||||
host: host.to_string(),
|
host,
|
||||||
date,
|
date,
|
||||||
digest,
|
digest,
|
||||||
signature: signature_header,
|
signature: signature_header,
|
||||||
|
@ -63,13 +83,41 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[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_url = "https://example.org/inbox";
|
||||||
let request_body = "{}";
|
let request_body = "{}";
|
||||||
let actor_key = RsaPrivateKey::new(&mut OsRng, 512).unwrap();
|
let actor_key = RsaPrivateKey::new(&mut OsRng, 512).unwrap();
|
||||||
let actor_key_id = "https://myserver.org/actor#main-key";
|
let actor_key_id = "https://myserver.org/actor#main-key";
|
||||||
|
|
||||||
let result = create_http_signature(
|
let result = create_http_signature(
|
||||||
|
Method::POST,
|
||||||
request_url,
|
request_url,
|
||||||
request_body,
|
request_body,
|
||||||
&actor_key,
|
&actor_key,
|
||||||
|
@ -80,7 +128,7 @@ mod tests {
|
||||||
let headers = result.unwrap();
|
let headers = result.unwrap();
|
||||||
assert_eq!(headers.host, "example.org");
|
assert_eq!(headers.host, "example.org");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
headers.digest,
|
headers.digest.unwrap(),
|
||||||
"SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=",
|
"SHA-256=RBNvo1WzZ4oRRq0W9+hknpT7T8If536DEMBg9hyq/4o=",
|
||||||
);
|
);
|
||||||
let expected_signature_header = concat!(
|
let expected_signature_header = concat!(
|
||||||
|
|
|
@ -157,13 +157,12 @@ pub async fn verify_http_signature(
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::str::FromStr;
|
|
||||||
use actix_web::http::{header, HeaderMap, HeaderName, HeaderValue, Uri};
|
use actix_web::http::{header, HeaderMap, HeaderName, HeaderValue, Uri};
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_signature() {
|
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 request_uri = "/user/123/inbox".parse::<Uri>().unwrap();
|
||||||
let mut request_headers = HeaderMap::new();
|
let mut request_headers = HeaderMap::new();
|
||||||
request_headers.insert(
|
request_headers.insert(
|
||||||
|
|
Loading…
Reference in a new issue