91 lines
3.0 KiB
Rust
91 lines
3.0 KiB
Rust
use super::{actor, keys, types::ActorExt, CLIENT};
|
|
use activitystreams::activity::ActorAndObject;
|
|
use anyhow::anyhow;
|
|
use chrono::Utc;
|
|
use http_signature_normalization::Config;
|
|
use hyper::header::CONTENT_TYPE;
|
|
use log::{debug, error, info};
|
|
use openssl::{hash::MessageDigest, pkey::PKey, sha::Sha256, sign::Signer};
|
|
use reqwest::header::{ACCEPT, DATE, HOST};
|
|
use std::collections::BTreeMap;
|
|
use url::Url;
|
|
|
|
pub async fn fetch_actor(id: &str) -> anyhow::Result<ActorExt> {
|
|
let actor = CLIENT
|
|
.get(id)
|
|
.header("Accept", "application/activity+json, application/json")
|
|
.send()
|
|
.await?
|
|
.json::<ActorExt>()
|
|
.await?;
|
|
Ok(actor)
|
|
}
|
|
|
|
pub async fn sign_and_send<Kind: serde::Serialize>(
|
|
activity: &ActorAndObject<Kind>,
|
|
inbox: &str,
|
|
) -> anyhow::Result<()> {
|
|
if std::env::var("SKIP_FEDERATE").is_ok() {
|
|
info!("Skipping federation");
|
|
return Ok(());
|
|
}
|
|
|
|
let inbox_url = Url::parse(inbox)?;
|
|
|
|
let body = serde_json::to_string(activity)?;
|
|
|
|
let mut headers = BTreeMap::new();
|
|
|
|
let host = inbox_url
|
|
.host_str()
|
|
.ok_or(anyhow!("missing inbox host"))?
|
|
.to_owned();
|
|
headers.insert("host".into(), host.clone());
|
|
// mastodon wants rfc 2616 dates, which are always in GMT
|
|
// and GMT is the same as UTC so we just hardcode it in the format
|
|
let date = Utc::now().format("%a, %d %b %Y %H:%M:%S GMT").to_string();
|
|
headers.insert("date".into(), date.clone());
|
|
let mut sha256 = Sha256::new();
|
|
sha256.update(body.as_bytes());
|
|
let digest = base64::encode(sha256.finish());
|
|
let digest_header = format!("SHA-256={}", digest);
|
|
headers.insert("digest".into(), digest_header.clone());
|
|
|
|
let config = Config::new().mastodon_compat();
|
|
let sig_header = config
|
|
.begin_sign("POST", inbox_url.path(), headers)?
|
|
.sign::<_, anyhow::Error>(actor::KEY_ID.to_string(), |signing_str| {
|
|
debug!("Signing string: {}", signing_str);
|
|
let keypair = PKey::private_key_from_pem(keys::PRIV_KEY_PEM.as_ref())?;
|
|
let mut signer = Signer::new(MessageDigest::sha256(), &keypair)?;
|
|
signer.update(signing_str.as_bytes())?;
|
|
let sig = signer.sign_to_vec()?;
|
|
Ok(base64::encode(&sig))
|
|
})?
|
|
.signature_header();
|
|
|
|
debug!("Sending: {}\nSignature: {}", body, sig_header);
|
|
|
|
let response = CLIENT
|
|
.post(inbox_url)
|
|
.body(body.into_bytes())
|
|
.header(HOST, host)
|
|
.header(DATE, date)
|
|
.header("digest", digest_header)
|
|
.header("signature", sig_header)
|
|
.header(ACCEPT, "application/activity+json, application/json")
|
|
.header(CONTENT_TYPE, "application/activity+json")
|
|
.send()
|
|
.await?;
|
|
|
|
if (200..299).contains(&response.status().as_u16()) {
|
|
Ok(())
|
|
} else {
|
|
let status = response.status();
|
|
if let Ok(s) = response.text().await {
|
|
error!("Failing response body: {}", s);
|
|
}
|
|
Err(anyhow!("Unexpected status code: {}", status))
|
|
}
|
|
}
|