21cb0ef437
To prevent a bug with Mastodon
143 lines
4.5 KiB
Rust
143 lines
4.5 KiB
Rust
use base64;
|
|
use chrono::{offset::Utc, DateTime};
|
|
use openssl::hash::{Hasher, MessageDigest};
|
|
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE, DATE, USER_AGENT};
|
|
use std::ops::Deref;
|
|
use std::time::SystemTime;
|
|
|
|
use activity_pub::sign::Signer;
|
|
use activity_pub::{ap_accept_header, AP_CONTENT_TYPE};
|
|
|
|
const PLUME_USER_AGENT: &str = concat!("Plume/", env!("CARGO_PKG_VERSION"));
|
|
|
|
pub struct Digest(String);
|
|
|
|
impl Digest {
|
|
pub fn digest(body: &str) -> HeaderValue {
|
|
let mut hasher =
|
|
Hasher::new(MessageDigest::sha256()).expect("Digest::digest: initialization error");
|
|
hasher
|
|
.update(body.as_bytes())
|
|
.expect("Digest::digest: content insertion error");
|
|
let res = base64::encode(&hasher.finish().expect("Digest::digest: finalizing error"));
|
|
HeaderValue::from_str(&format!("SHA-256={}", res))
|
|
.expect("Digest::digest: header creation error")
|
|
}
|
|
|
|
pub fn verify(&self, body: &str) -> bool {
|
|
if self.algorithm() == "SHA-256" {
|
|
let mut hasher =
|
|
Hasher::new(MessageDigest::sha256()).expect("Digest::digest: initialization error");
|
|
hasher
|
|
.update(body.as_bytes())
|
|
.expect("Digest::digest: content insertion error");
|
|
self.value().deref()
|
|
== hasher
|
|
.finish()
|
|
.expect("Digest::digest: finalizing error")
|
|
.deref()
|
|
} else {
|
|
false //algorithm not supported
|
|
}
|
|
}
|
|
|
|
pub fn verify_header(&self, other: &Digest) -> bool {
|
|
self.value() == other.value()
|
|
}
|
|
|
|
pub fn algorithm(&self) -> &str {
|
|
let pos = self
|
|
.0
|
|
.find('=')
|
|
.expect("Digest::algorithm: invalid header error");
|
|
&self.0[..pos]
|
|
}
|
|
|
|
pub fn value(&self) -> Vec<u8> {
|
|
let pos = self
|
|
.0
|
|
.find('=')
|
|
.expect("Digest::value: invalid header error")
|
|
+ 1;
|
|
base64::decode(&self.0[pos..]).expect("Digest::value: invalid encoding error")
|
|
}
|
|
|
|
pub fn from_header(dig: &str) -> Result<Self, ()> {
|
|
if let Some(pos) = dig.find('=') {
|
|
let pos = pos + 1;
|
|
if base64::decode(&dig[pos..]).is_ok() {
|
|
Ok(Digest(dig.to_owned()))
|
|
} else {
|
|
Err(())
|
|
}
|
|
} else {
|
|
Err(())
|
|
}
|
|
}
|
|
|
|
pub fn from_body(body: &str) -> Self {
|
|
let mut hasher =
|
|
Hasher::new(MessageDigest::sha256()).expect("Digest::digest: initialization error");
|
|
hasher
|
|
.update(body.as_bytes())
|
|
.expect("Digest::digest: content insertion error");
|
|
let res = base64::encode(&hasher.finish().expect("Digest::digest: finalizing error"));
|
|
Digest(format!("SHA-256={}", res))
|
|
}
|
|
}
|
|
|
|
pub fn headers() -> HeaderMap {
|
|
let date: DateTime<Utc> = SystemTime::now().into();
|
|
let date = format!("{}", date.format("%a, %d %b %Y %T GMT"));
|
|
|
|
let mut headers = HeaderMap::new();
|
|
headers.insert(USER_AGENT, HeaderValue::from_static(PLUME_USER_AGENT));
|
|
headers.insert(
|
|
DATE,
|
|
HeaderValue::from_str(&date).expect("request::headers: date error"),
|
|
);
|
|
headers.insert(
|
|
ACCEPT,
|
|
HeaderValue::from_str(
|
|
&ap_accept_header()
|
|
.into_iter()
|
|
.collect::<Vec<_>>()
|
|
.join(", "),
|
|
)
|
|
.expect("request::headers: accept error"),
|
|
);
|
|
headers.insert(CONTENT_TYPE, HeaderValue::from_static(AP_CONTENT_TYPE));
|
|
headers
|
|
}
|
|
|
|
pub fn signature<S: Signer>(signer: &S, headers: &HeaderMap) -> Result<HeaderValue, ()> {
|
|
let signed_string = headers
|
|
.iter()
|
|
.map(|(h, v)| {
|
|
format!(
|
|
"{}: {}",
|
|
h.as_str().to_lowercase(),
|
|
v.to_str()
|
|
.expect("request::signature: invalid header error")
|
|
)
|
|
})
|
|
.collect::<Vec<String>>()
|
|
.join("\n");
|
|
let signed_headers = headers
|
|
.iter()
|
|
.map(|(h, _)| h.as_str())
|
|
.collect::<Vec<&str>>()
|
|
.join(" ")
|
|
.to_lowercase();
|
|
|
|
let data = signer.sign(&signed_string).map_err(|_| ())?;
|
|
let sign = base64::encode(&data);
|
|
|
|
HeaderValue::from_str(&format!(
|
|
"keyId=\"{key_id}\",algorithm=\"rsa-sha256\",headers=\"{signed_headers}\",signature=\"{signature}\"",
|
|
key_id = signer.get_key_id(),
|
|
signed_headers = signed_headers,
|
|
signature = sign
|
|
)).map_err(|_| ())
|
|
}
|