lemmy/server/src/apub/extensions/signatures.rs

137 lines
3.8 KiB
Rust
Raw Normal View History

use crate::apub::ActorType;
2020-05-05 14:30:13 +00:00
use activitystreams::ext::Extension;
use actix_web::HttpRequest;
use failure::Error;
2020-06-25 19:36:03 +00:00
use http::request::Builder;
2020-05-05 14:30:13 +00:00
use http_signature_normalization::Config;
use log::debug;
2020-05-16 14:04:08 +00:00
use openssl::{
hash::MessageDigest,
pkey::PKey,
rsa::Rsa,
sign::{Signer, Verifier},
};
2020-05-05 14:30:13 +00:00
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
2020-04-19 17:35:40 +00:00
lazy_static! {
static ref HTTP_SIG_CONFIG: Config = Config::new();
}
pub struct Keypair {
pub private_key: String,
pub public_key: String,
}
/// Generate the asymmetric keypair for ActivityPub HTTP signatures.
2020-04-19 11:44:44 +00:00
pub fn generate_actor_keypair() -> Result<Keypair, Error> {
let rsa = Rsa::generate(2048)?;
let pkey = PKey::from_rsa(rsa)?;
let public_key = pkey.public_key_to_pem()?;
let private_key = pkey.private_key_to_pem_pkcs8()?;
Ok(Keypair {
private_key: String::from_utf8(private_key)?,
public_key: String::from_utf8(public_key)?,
})
}
/// Signs request headers with the given keypair.
2020-06-25 19:36:03 +00:00
pub fn sign(request: &Builder, actor: &dyn ActorType) -> Result<String, Error> {
let signing_key_id = format!("{}#main-key", actor.actor_id());
let headers = request
2020-06-25 19:36:03 +00:00
.headers_ref()
.unwrap()
.iter()
.map(|h| -> Result<(String, String), Error> {
Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned()))
})
.collect::<Result<BTreeMap<String, String>, Error>>()?;
2020-04-19 17:35:40 +00:00
let signature_header_value = HTTP_SIG_CONFIG
.begin_sign(
2020-06-25 19:36:03 +00:00
request.method_ref().unwrap().as_str(),
request
.uri_ref()
.unwrap()
.path_and_query()
.unwrap()
.as_str(),
headers,
)?
.sign(signing_key_id, |signing_string| {
let private_key = PKey::private_key_from_pem(actor.private_key().as_bytes())?;
let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap();
signer.update(signing_string.as_bytes()).unwrap();
Ok(base64::encode(signer.sign_to_vec()?)) as Result<_, Error>
})?
.signature_header();
Ok(signature_header_value)
}
2020-04-10 13:50:40 +00:00
pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), Error> {
2020-04-19 17:35:40 +00:00
let headers = request
.headers()
.iter()
.map(|h| -> Result<(String, String), Error> {
Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned()))
})
.collect::<Result<BTreeMap<String, String>, Error>>()?;
let verified = HTTP_SIG_CONFIG
.begin_verify(
request.method().as_str(),
request.uri().path_and_query().unwrap().as_str(),
headers,
)?
.verify(|signature, signing_string| -> Result<bool, Error> {
debug!(
"Verifying with key {}, message {}",
&actor.public_key(),
&signing_string
2020-04-19 17:35:40 +00:00
);
let public_key = PKey::public_key_from_pem(actor.public_key().as_bytes())?;
2020-04-19 17:35:40 +00:00
let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key).unwrap();
verifier.update(&signing_string.as_bytes()).unwrap();
Ok(verifier.verify(&base64::decode(signature)?)?)
})?;
if verified {
debug!("verified signature for {}", &request.uri());
Ok(())
} else {
Err(format_err!(
"Invalid signature on request: {}",
&request.uri()
))
}
}
2020-04-10 13:50:40 +00:00
// The following is taken from here:
// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
2020-04-17 15:33:55 +00:00
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
2020-04-10 13:50:40 +00:00
#[serde(rename_all = "camelCase")]
pub struct PublicKey {
pub id: String,
pub owner: String,
pub public_key_pem: String,
}
2020-04-17 15:33:55 +00:00
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
2020-04-10 13:50:40 +00:00
#[serde(rename_all = "camelCase")]
pub struct PublicKeyExtension {
pub public_key: PublicKey,
}
impl PublicKey {
pub fn to_ext(&self) -> PublicKeyExtension {
PublicKeyExtension {
public_key: self.to_owned(),
}
}
}
impl<T> Extension<T> for PublicKeyExtension where T: activitystreams::Actor {}