Sign GET request to external instances

This commit is contained in:
Kitaiti Makoto 2021-09-24 04:27:22 +09:00
parent 6e4def4cc5
commit 3a448e9e17
17 changed files with 321 additions and 97 deletions

View File

@ -1,6 +1,10 @@
use std::fmt::Debug; use std::fmt::Debug;
use super::request; use super::{request, sign::Signer};
use reqwest::{
header::{HeaderValue, HOST},
Url,
};
/// Represents an ActivityPub inbox. /// Represents an ActivityPub inbox.
/// ///
@ -11,7 +15,8 @@ use super::request;
/// ```rust /// ```rust
/// # extern crate activitypub; /// # extern crate activitypub;
/// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note}; /// # use activitypub::{actor::Person, activity::{Announce, Create}, object::Note};
/// # use plume_common::activity_pub::inbox::*; /// # use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
/// # use plume_common::activity_pub::{inbox::*, sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer}};
/// # struct User; /// # struct User;
/// # impl FromId<()> for User { /// # impl FromId<()> for User {
/// # type Error = (); /// # type Error = ();
@ -60,6 +65,42 @@ use super::request;
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// # } /// # }
/// # struct MySigner {
/// # public_key: String,
/// # private_key: String,
/// # }
/// #
/// # impl MySigner {
/// # fn new() -> Self {
/// # let (pub_key, priv_key) = gen_keypair();
/// # Self {
/// # public_key: String::from_utf8(pub_key).unwrap(),
/// # private_key: String::from_utf8(priv_key).unwrap(),
/// # }
/// # }
/// # }
/// #
/// # impl Signer for MySigner {
/// # fn get_key_id(&self) -> String {
/// # "mysigner".into()
/// # }
/// #
/// # fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
/// # let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
/// # .unwrap();
/// # let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
/// # signer.update(to_sign.as_bytes()).unwrap();
/// # signer.sign_to_vec().map_err(|_| SignatureError())
/// # }
/// #
/// # fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
/// # let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
/// # .unwrap();
/// # let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
/// # verifier.update(data.as_bytes()).unwrap();
/// # verifier.verify(&signature).map_err(|_| SignatureError())
/// # }
/// # }
/// # /// #
/// # let mut act = Create::default(); /// # let mut act = Create::default();
/// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap(); /// # act.object_props.set_id_string(String::from("https://test.ap/activity")).unwrap();
@ -70,8 +111,9 @@ use super::request;
/// # let activity_json = serde_json::to_value(act).unwrap(); /// # let activity_json = serde_json::to_value(act).unwrap();
/// # /// #
/// # let conn = (); /// # let conn = ();
/// # let sender = MySigner::new();
/// # /// #
/// let result: Result<(), ()> = Inbox::handle(&conn, activity_json) /// let result: Result<(), ()> = Inbox::handle(&conn, &sender, activity_json)
/// .with::<User, Announce, Message>(None) /// .with::<User, Announce, Message>(None)
/// .with::<User, Create, Message>(None) /// .with::<User, Create, Message>(None)
/// .done(); /// .done();
@ -85,9 +127,10 @@ where
/// # Structure /// # Structure
/// ///
/// - the context to be passed to each handler. /// - the context to be passed to each handler.
/// - the sender actor to sign request
/// - the activity /// - the activity
/// - the reason it has not been handled yet /// - the reason it has not been handled yet
NotHandled(&'a C, serde_json::Value, InboxError<E>), NotHandled(&'a C, &'a dyn Signer, serde_json::Value, InboxError<E>),
/// A matching handler have been found but failed /// A matching handler have been found but failed
/// ///
@ -140,8 +183,12 @@ where
/// ///
/// - `ctx`: the context to pass to each handler /// - `ctx`: the context to pass to each handler
/// - `json`: the JSON representation of the incoming activity /// - `json`: the JSON representation of the incoming activity
pub fn handle(ctx: &'a C, json: serde_json::Value) -> Inbox<'a, C, E, R> { pub fn handle(
Inbox::NotHandled(ctx, json, InboxError::NoMatch) ctx: &'a C,
sender: &'a dyn Signer,
json: serde_json::Value,
) -> Inbox<'a, C, E, R> {
Inbox::NotHandled(ctx, sender, json, InboxError::NoMatch)
} }
/// Registers an handler on this Inbox. /// Registers an handler on this Inbox.
@ -152,27 +199,30 @@ where
M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>, M: AsObject<A, V, &'a C, Error = E> + FromId<C, Error = E>,
M::Output: Into<R>, M::Output: Into<R>,
{ {
if let Inbox::NotHandled(ctx, mut act, e) = self { if let Inbox::NotHandled(ctx, sender, mut act, e) = self {
if serde_json::from_value::<V>(act.clone()).is_ok() { if serde_json::from_value::<V>(act.clone()).is_ok() {
let act_clone = act.clone(); let act_clone = act.clone();
let act_id = match act_clone["id"].as_str() { let act_id = match act_clone["id"].as_str() {
Some(x) => x, Some(x) => x,
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidID), None => return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidID),
}; };
// Get the actor ID // Get the actor ID
let actor_id = match get_id(act["actor"].clone()) { let actor_id = match get_id(act["actor"].clone()) {
Some(x) => x, Some(x) => x,
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(None)), None => {
return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidActor(None))
}
}; };
if Self::is_spoofed_activity(&actor_id, &act) { if Self::is_spoofed_activity(&actor_id, &act) {
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None)); return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidObject(None));
} }
// Transform this actor to a model (see FromId for details about the from_id function) // Transform this actor to a model (see FromId for details about the from_id function)
let actor = match A::from_id( let actor = match A::from_id(
ctx, ctx,
sender,
&actor_id, &actor_id,
serde_json::from_value(act["actor"].clone()).ok(), serde_json::from_value(act["actor"].clone()).ok(),
proxy, proxy,
@ -183,17 +233,25 @@ where
if let Some(json) = json { if let Some(json) = json {
act["actor"] = json; act["actor"] = json;
} }
return Inbox::NotHandled(ctx, act, InboxError::InvalidActor(Some(e))); return Inbox::NotHandled(
ctx,
sender,
act,
InboxError::InvalidActor(Some(e)),
);
} }
}; };
// Same logic for "object" // Same logic for "object"
let obj_id = match get_id(act["object"].clone()) { let obj_id = match get_id(act["object"].clone()) {
Some(x) => x, Some(x) => x,
None => return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(None)), None => {
return Inbox::NotHandled(ctx, sender, act, InboxError::InvalidObject(None))
}
}; };
let obj = match M::from_id( let obj = match M::from_id(
ctx, ctx,
sender,
&obj_id, &obj_id,
serde_json::from_value(act["object"].clone()).ok(), serde_json::from_value(act["object"].clone()).ok(),
proxy, proxy,
@ -203,7 +261,12 @@ where
if let Some(json) = json { if let Some(json) = json {
act["object"] = json; act["object"] = json;
} }
return Inbox::NotHandled(ctx, act, InboxError::InvalidObject(Some(e))); return Inbox::NotHandled(
ctx,
sender,
act,
InboxError::InvalidObject(Some(e)),
);
} }
}; };
@ -215,7 +278,7 @@ where
} else { } else {
// If the Activity type is not matching the expected one for // If the Activity type is not matching the expected one for
// this handler, try with the next one. // this handler, try with the next one.
Inbox::NotHandled(ctx, act, e) Inbox::NotHandled(ctx, sender, act, e)
} }
} else { } else {
self self
@ -226,7 +289,7 @@ where
pub fn done(self) -> Result<R, E> { pub fn done(self) -> Result<R, E> {
match self { match self {
Inbox::Handled(res) => Ok(res), Inbox::Handled(res) => Ok(res),
Inbox::NotHandled(_, _, err) => Err(E::from(err)), Inbox::NotHandled(_, _, _, err) => Err(E::from(err)),
Inbox::Failed(err) => Err(err), Inbox::Failed(err) => Err(err),
} }
} }
@ -293,6 +356,7 @@ pub trait FromId<C>: Sized {
/// If absent, the ID will be dereferenced. /// If absent, the ID will be dereferenced.
fn from_id( fn from_id(
ctx: &C, ctx: &C,
sender: &dyn Signer,
id: &str, id: &str,
object: Option<Self::Object>, object: Option<Self::Object>,
proxy: Option<&reqwest::Proxy>, proxy: Option<&reqwest::Proxy>,
@ -301,7 +365,7 @@ pub trait FromId<C>: Sized {
Ok(x) => Ok(x), Ok(x) => Ok(x),
_ => match object { _ => match object {
Some(o) => Self::from_activity(ctx, o).map_err(|e| (None, e)), Some(o) => Self::from_activity(ctx, o).map_err(|e| (None, e)),
None => Self::from_activity(ctx, Self::deref(id, proxy.cloned())?) None => Self::from_activity(ctx, Self::deref(id, sender, proxy.cloned())?)
.map_err(|e| (None, e)), .map_err(|e| (None, e)),
}, },
} }
@ -310,9 +374,17 @@ pub trait FromId<C>: Sized {
/// Dereferences an ID /// Dereferences an ID
fn deref( fn deref(
id: &str, id: &str,
sender: &dyn Signer,
proxy: Option<reqwest::Proxy>, proxy: Option<reqwest::Proxy>,
) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> { ) -> Result<Self::Object, (Option<serde_json::Value>, Self::Error)> {
let headers = request::headers(); let mut headers = request::headers();
let url = Url::parse(&id).map_err(|_| (None, InboxError::InvalidID.into()))?;
if !url.has_host() {
return Err((None, InboxError::InvalidID.into()));
}
let host_header_value = HeaderValue::from_str(&url.host_str().expect("Unreachable"))
.map_err(|_| (None, InboxError::DerefError.into()))?;
headers.insert(HOST, host_header_value);
if let Some(proxy) = proxy { if let Some(proxy) = proxy {
reqwest::ClientBuilder::new().proxy(proxy) reqwest::ClientBuilder::new().proxy(proxy)
} else { } else {
@ -322,7 +394,12 @@ pub trait FromId<C>: Sized {
.build() .build()
.map_err(|_| (None, InboxError::DerefError.into()))? .map_err(|_| (None, InboxError::DerefError.into()))?
.get(id) .get(id)
.headers(headers) .headers(headers.clone())
.header(
"Signature",
request::signature(sender, &headers, ("get", url.path(), url.query()))
.map_err(|_| (None, InboxError::DerefError.into()))?,
)
.send() .send()
.map_err(|_| (None, InboxError::DerefError)) .map_err(|_| (None, InboxError::DerefError))
.and_then(|mut r| { .and_then(|mut r| {
@ -451,8 +528,10 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::super::sign::{gen_keypair, Error as SignatureError, Result as SignatureResult};
use super::*; use super::*;
use activitypub::{activity::*, actor::Person, object::Note}; use activitypub::{activity::*, actor::Person, object::Note};
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
struct MyActor; struct MyActor;
impl FromId<()> for MyActor { impl FromId<()> for MyActor {
@ -550,10 +629,47 @@ mod tests {
act act
} }
struct MySigner {
public_key: String,
private_key: String,
}
impl MySigner {
fn new() -> Self {
let (pub_key, priv_key) = gen_keypair();
Self {
public_key: String::from_utf8(pub_key).unwrap(),
private_key: String::from_utf8(priv_key).unwrap(),
}
}
}
impl Signer for MySigner {
fn get_key_id(&self) -> String {
"mysigner".into()
}
fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
.unwrap();
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
signer.update(to_sign.as_bytes()).unwrap();
signer.sign_to_vec().map_err(|_| SignatureError())
}
fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
.unwrap();
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
verifier.update(data.as_bytes()).unwrap();
verifier.verify(&signature).map_err(|_| SignatureError())
}
}
#[test] #[test]
fn test_inbox_basic() { fn test_inbox_basic() {
let act = serde_json::to_value(build_create()).unwrap(); let act = serde_json::to_value(build_create()).unwrap();
let res: Result<(), ()> = Inbox::handle(&(), act) let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
.with::<MyActor, Create, MyObject>(None) .with::<MyActor, Create, MyObject>(None)
.done(); .done();
assert!(res.is_ok()); assert!(res.is_ok());
@ -562,7 +678,7 @@ mod tests {
#[test] #[test]
fn test_inbox_multi_handlers() { fn test_inbox_multi_handlers() {
let act = serde_json::to_value(build_create()).unwrap(); let act = serde_json::to_value(build_create()).unwrap();
let res: Result<(), ()> = Inbox::handle(&(), act) let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
.with::<MyActor, Announce, MyObject>(None) .with::<MyActor, Announce, MyObject>(None)
.with::<MyActor, Delete, MyObject>(None) .with::<MyActor, Delete, MyObject>(None)
.with::<MyActor, Create, MyObject>(None) .with::<MyActor, Create, MyObject>(None)
@ -575,7 +691,7 @@ mod tests {
fn test_inbox_failure() { fn test_inbox_failure() {
let act = serde_json::to_value(build_create()).unwrap(); let act = serde_json::to_value(build_create()).unwrap();
// Create is not handled by this inbox // Create is not handled by this inbox
let res: Result<(), ()> = Inbox::handle(&(), act) let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act)
.with::<MyActor, Announce, MyObject>(None) .with::<MyActor, Announce, MyObject>(None)
.with::<MyActor, Like, MyObject>(None) .with::<MyActor, Like, MyObject>(None)
.done(); .done();
@ -624,12 +740,12 @@ mod tests {
fn test_inbox_actor_failure() { fn test_inbox_actor_failure() {
let act = serde_json::to_value(build_create()).unwrap(); let act = serde_json::to_value(build_create()).unwrap();
let res: Result<(), ()> = Inbox::handle(&(), act.clone()) let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act.clone())
.with::<FailingActor, Create, MyObject>(None) .with::<FailingActor, Create, MyObject>(None)
.done(); .done();
assert!(res.is_err()); assert!(res.is_err());
let res: Result<(), ()> = Inbox::handle(&(), act.clone()) let res: Result<(), ()> = Inbox::handle(&(), &MySigner::new(), act.clone())
.with::<FailingActor, Create, MyObject>(None) .with::<FailingActor, Create, MyObject>(None)
.with::<MyActor, Create, MyObject>(None) .with::<MyActor, Create, MyObject>(None)
.done(); .done();

View File

@ -118,8 +118,8 @@ type Path<'a> = &'a str;
type Query<'a> = &'a str; type Query<'a> = &'a str;
type RequestTarget<'a> = (Method<'a>, Path<'a>, Option<Query<'a>>); type RequestTarget<'a> = (Method<'a>, Path<'a>, Option<Query<'a>>);
pub fn signature<S: Signer>( pub fn signature(
signer: &S, signer: &dyn Signer,
headers: &HeaderMap, headers: &HeaderMap,
request_target: RequestTarget, request_target: RequestTarget,
) -> Result<HeaderValue, Error> { ) -> Result<HeaderValue, Error> {
@ -166,8 +166,10 @@ pub fn signature<S: Signer>(
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::{signature, Error}; use super::signature;
use crate::activity_pub::sign::{gen_keypair, Signer}; use crate::activity_pub::sign::{
gen_keypair, Error as SignatureError, Result as SignatureResult, Signer,
};
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa}; use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa};
use reqwest::header::HeaderMap; use reqwest::header::HeaderMap;
@ -187,26 +189,24 @@ mod tests {
} }
impl Signer for MySigner { impl Signer for MySigner {
type Error = Error;
fn get_key_id(&self) -> String { fn get_key_id(&self) -> String {
"mysigner".into() "mysigner".into()
} }
fn sign(&self, to_sign: &str) -> Result<Vec<u8>, Self::Error> { fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap()) let key = PKey::from_rsa(Rsa::private_key_from_pem(self.private_key.as_ref()).unwrap())
.unwrap(); .unwrap();
let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap(); let mut signer = openssl::sign::Signer::new(MessageDigest::sha256(), &key).unwrap();
signer.update(to_sign.as_bytes()).unwrap(); signer.update(to_sign.as_bytes()).unwrap();
signer.sign_to_vec().map_err(|_| Error()) signer.sign_to_vec().map_err(|_| SignatureError())
} }
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error> { fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap()) let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref()).unwrap())
.unwrap(); .unwrap();
let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap(); let mut verifier = openssl::sign::Verifier::new(MessageDigest::sha256(), &key).unwrap();
verifier.update(data.as_bytes()).unwrap(); verifier.update(data.as_bytes()).unwrap();
verifier.verify(&signature).map_err(|_| Error()) verifier.verify(&signature).map_err(|_| SignatureError())
} }
} }

View File

@ -19,20 +19,25 @@ pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
#[derive(Debug)] #[derive(Debug)]
pub struct Error(); pub struct Error();
pub type Result<T> = std::result::Result<T, Error>;
impl From<openssl::error::ErrorStack> for Error {
fn from(_: openssl::error::ErrorStack) -> Self {
Self()
}
}
pub trait Signer { pub trait Signer {
type Error;
fn get_key_id(&self) -> String; fn get_key_id(&self) -> String;
/// Sign some data with the signer keypair /// Sign some data with the signer keypair
fn sign(&self, to_sign: &str) -> Result<Vec<u8>, Self::Error>; fn sign(&self, to_sign: &str) -> Result<Vec<u8>>;
/// Verify if the signature is valid /// Verify if the signature is valid
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool, Self::Error>; fn verify(&self, data: &str, signature: &[u8]) -> Result<bool>;
} }
pub trait Signable { pub trait Signable {
fn sign<T>(&mut self, creator: &T) -> Result<&mut Self, Error> fn sign<T>(&mut self, creator: &T) -> Result<&mut Self>
where where
T: Signer; T: Signer;
fn verify<T>(self, creator: &T) -> bool fn verify<T>(self, creator: &T) -> bool
@ -46,7 +51,7 @@ pub trait Signable {
} }
impl Signable for serde_json::Value { impl Signable for serde_json::Value {
fn sign<T: Signer>(&mut self, creator: &T) -> Result<&mut serde_json::Value, Error> { fn sign<T: Signer>(&mut self, creator: &T) -> Result<&mut serde_json::Value> {
let creation_date = Utc::now().to_rfc3339(); let creation_date = Utc::now().to_rfc3339();
let mut options = json!({ let mut options = json!({
"type": "RsaSignature2017", "type": "RsaSignature2017",

View File

@ -18,7 +18,8 @@ use openssl::{
}; };
use plume_common::activity_pub::{ use plume_common::activity_pub::{
inbox::{AsActor, FromId}, inbox::{AsActor, FromId},
sign, ActivityStream, ApSignature, Id, IntoId, PublicKey, Source, sign::{self, Error as SignatureError, Result as SignatureResult},
ActivityStream, ApSignature, Id, IntoId, PublicKey, Source,
}; };
use url::Url; use url::Url;
use webfinger::*; use webfinger::*;
@ -149,7 +150,16 @@ impl Blog {
.into_iter() .into_iter()
.find(|l| l.mime_type == Some(String::from("application/activity+json"))) .find(|l| l.mime_type == Some(String::from("application/activity+json")))
.ok_or(Error::Webfinger) .ok_or(Error::Webfinger)
.and_then(|l| Blog::from_id(conn, &l.href?, None, CONFIG.proxy()).map_err(|(_, e)| e)) .and_then(|l| {
Blog::from_id(
conn,
&Instance::get_local().expect("Failed to get local instance"),
&l.href?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)
})
} }
pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> { pub fn to_activity(&self, conn: &Connection) -> Result<CustomGroup> {
@ -374,7 +384,14 @@ impl FromId<DbConn> for Blog {
Media::save_remote( Media::save_remote(
conn, conn,
icon.object_props.url_string().ok()?, icon.object_props.url_string().ok()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?, &User::from_id(
conn,
&Instance::get_local().expect("Failed to get local instance"),
&owner,
None,
CONFIG.proxy(),
)
.ok()?,
) )
.ok() .ok()
}) })
@ -390,7 +407,14 @@ impl FromId<DbConn> for Blog {
Media::save_remote( Media::save_remote(
conn, conn,
banner.object_props.url_string().ok()?, banner.object_props.url_string().ok()?,
&User::from_id(conn, &owner, None, CONFIG.proxy()).ok()?, &User::from_id(
conn,
&Instance::get_local().expect("Failed to get local instance"),
&owner,
None,
CONFIG.proxy(),
)
.ok()?,
) )
.ok() .ok()
}) })
@ -453,24 +477,22 @@ impl AsActor<&PlumeRocket> for Blog {
} }
impl sign::Signer for Blog { impl sign::Signer for Blog {
type Error = Error;
fn get_key_id(&self) -> String { fn get_key_id(&self) -> String {
format!("{}#main-key", self.ap_url) format!("{}#main-key", self.ap_url)
} }
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> { fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
let key = self.get_keypair()?; let key = self.get_keypair()?;
let mut signer = Signer::new(MessageDigest::sha256(), &key)?; let mut signer = Signer::new(MessageDigest::sha256(), &key)?;
signer.update(to_sign.as_bytes())?; signer.update(to_sign.as_bytes())?;
signer.sign_to_vec().map_err(Error::from) signer.sign_to_vec().map_err(SignatureError::from)
} }
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> { fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?; let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?; let mut verifier = Verifier::new(MessageDigest::sha256(), &key)?;
verifier.update(data.as_bytes())?; verifier.update(data.as_bytes())?;
verifier.verify(&signature).map_err(Error::from) verifier.verify(&signature).map_err(SignatureError::from)
} }
} }

View File

@ -236,6 +236,7 @@ impl FromId<DbConn> for Comment {
})?, })?,
author_id: User::from_id( author_id: User::from_id(
conn, conn,
&Instance::get_local().expect("Failed to get local instance"),
&note.object_props.attributed_to_link::<Id>()?, &note.object_props.attributed_to_link::<Id>()?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),
@ -294,7 +295,13 @@ impl FromId<DbConn> for Comment {
.collect::<HashSet<_>>() // remove duplicates (don't do a query more than once) .collect::<HashSet<_>>() // remove duplicates (don't do a query more than once)
.into_iter() .into_iter()
.map(|v| { .map(|v| {
if let Ok(user) = User::from_id(conn, &v, None, CONFIG.proxy()) { if let Ok(user) = User::from_id(
conn,
&Instance::get_local().expect("Failed to get local instance"),
&v,
None,
CONFIG.proxy(),
) {
vec![user] vec![user]
} else { } else {
vec![] // TODO try to fetch collection vec![] // TODO try to fetch collection

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
ap_url, db_conn::DbConn, notifications::*, schema::follows, users::User, Connection, Error, ap_url, db_conn::DbConn, instance::Instance, notifications::*, schema::follows, users::User,
Result, CONFIG, Connection, Error, Result, CONFIG,
}; };
use activitypub::activity::{Accept, Follow as FollowAct, Undo}; use activitypub::activity::{Accept, Follow as FollowAct, Undo};
use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl}; use diesel::{self, ExpressionMethods, QueryDsl, RunQueryDsl, SaveChangesDsl};
@ -168,6 +168,7 @@ impl FromId<DbConn> for Follow {
fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> { fn from_activity(conn: &DbConn, follow: FollowAct) -> Result<Self> {
let actor = User::from_id( let actor = User::from_id(
conn, conn,
&Instance::get_local().expect("Failed to get local instance"),
&follow.follow_props.actor_link::<Id>()?, &follow.follow_props.actor_link::<Id>()?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),
@ -176,6 +177,7 @@ impl FromId<DbConn> for Follow {
let target = User::from_id( let target = User::from_id(
conn, conn,
&Instance::get_local().expect("Failed to get local instance"),
&follow.follow_props.object_link::<Id>()?, &follow.follow_props.object_link::<Id>()?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),

View File

@ -3,7 +3,9 @@ use activitypub::activity::*;
use crate::{ use crate::{
comments::Comment, comments::Comment,
db_conn::DbConn, db_conn::DbConn,
follows, likes, follows,
instance::Instance,
likes,
posts::{Post, PostUpdate}, posts::{Post, PostUpdate},
reshares::Reshare, reshares::Reshare,
users::User, users::User,
@ -47,20 +49,24 @@ impl_into_inbox_result! {
} }
pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> { pub fn inbox(conn: &DbConn, act: serde_json::Value) -> Result<InboxResult, Error> {
Inbox::handle(conn, act) Inbox::handle(
.with::<User, Announce, Post>(CONFIG.proxy()) conn,
.with::<User, Create, Comment>(CONFIG.proxy()) &Instance::get_local().expect("Failed to get local instance"),
.with::<User, Create, Post>(CONFIG.proxy()) act,
.with::<User, Delete, Comment>(CONFIG.proxy()) )
.with::<User, Delete, Post>(CONFIG.proxy()) .with::<User, Announce, Post>(CONFIG.proxy())
.with::<User, Delete, User>(CONFIG.proxy()) .with::<User, Create, Comment>(CONFIG.proxy())
.with::<User, Follow, User>(CONFIG.proxy()) .with::<User, Create, Post>(CONFIG.proxy())
.with::<User, Like, Post>(CONFIG.proxy()) .with::<User, Delete, Comment>(CONFIG.proxy())
.with::<User, Undo, Reshare>(CONFIG.proxy()) .with::<User, Delete, Post>(CONFIG.proxy())
.with::<User, Undo, follows::Follow>(CONFIG.proxy()) .with::<User, Delete, User>(CONFIG.proxy())
.with::<User, Undo, likes::Like>(CONFIG.proxy()) .with::<User, Follow, User>(CONFIG.proxy())
.with::<User, Update, PostUpdate>(CONFIG.proxy()) .with::<User, Like, Post>(CONFIG.proxy())
.done() .with::<User, Undo, Reshare>(CONFIG.proxy())
.with::<User, Undo, follows::Follow>(CONFIG.proxy())
.with::<User, Undo, likes::Like>(CONFIG.proxy())
.with::<User, Update, PostUpdate>(CONFIG.proxy())
.done()
} }
#[cfg(test)] #[cfg(test)]

View File

@ -17,7 +17,7 @@ use openssl::{
}; };
use plume_common::{ use plume_common::{
activity_pub::{ activity_pub::{
sign::{gen_keypair, Signer}, sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer},
ApSignature, PublicKey, ApSignature, PublicKey,
}, },
utils::md_to_html, utils::md_to_html,
@ -348,30 +348,28 @@ impl NewInstance {
} }
impl Signer for Instance { impl Signer for Instance {
type Error = Error;
fn get_key_id(&self) -> String { fn get_key_id(&self) -> String {
format!("{}#main-key", self.ap_url()) format!("{}#main-key", self.ap_url())
} }
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> { fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
let key = self.get_keypair()?; let key = self.get_keypair()?;
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?; let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
signer.update(to_sign.as_bytes())?; signer.update(to_sign.as_bytes())?;
signer.sign_to_vec().map_err(Error::from) signer.sign_to_vec().map_err(SignatureError::from)
} }
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> { fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
if self.public_key.is_none() { if self.public_key.is_none() {
warn!("missing public key for {}", self.public_domain); warn!("missing public key for {}", self.public_domain);
return Err(Error::Signature); return Err(SignatureError());
} }
let key = PKey::from_rsa(Rsa::public_key_from_pem( let key = PKey::from_rsa(Rsa::public_key_from_pem(
self.public_key.clone().unwrap().as_ref(), self.public_key.clone().unwrap().as_ref(),
)?)?; )?)?;
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?; let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
verifier.update(data.as_bytes())?; verifier.update(data.as_bytes())?;
verifier.verify(&signature).map_err(Error::from) verifier.verify(&signature).map_err(SignatureError::from)
} }
} }

View File

@ -20,7 +20,7 @@ extern crate tantivy;
use db_conn::DbPool; use db_conn::DbPool;
use instance::Instance; use instance::Instance;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use plume_common::activity_pub::inbox::InboxError; use plume_common::activity_pub::{inbox::InboxError, sign};
use posts::PostEvent; use posts::PostEvent;
use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder}; use riker::actors::{channel, ActorSystem, ChannelRef, SystemBuilder};
use users::UserEvent; use users::UserEvent;
@ -82,6 +82,12 @@ impl From<openssl::error::ErrorStack> for Error {
} }
} }
impl From<sign::Error> for Error {
fn from(_: sign::Error) -> Self {
Error::Signature
}
}
impl From<diesel::result::Error> for Error { impl From<diesel::result::Error> for Error {
fn from(err: diesel::result::Error) -> Self { fn from(err: diesel::result::Error) -> Self {
Error::Db(err) Error::Db(err)
@ -162,6 +168,12 @@ impl From<InboxError<Error>> for Error {
pub type Result<T> = std::result::Result<T, Error>; pub type Result<T> = std::result::Result<T, Error>;
impl From<Error> for sign::Error {
fn from(_: Error) -> Self {
Self()
}
}
/// Adds a function to a model, that returns the first /// Adds a function to a model, that returns the first
/// matching row for a given list of fields. /// matching row for a given list of fields.
/// ///

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
db_conn::DbConn, notifications::*, posts::Post, schema::likes, timeline::*, users::User, db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::likes, timeline::*,
Connection, Error, Result, CONFIG, users::User, Connection, Error, Result, CONFIG,
}; };
use activitypub::activity; use activitypub::activity;
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
@ -117,6 +117,7 @@ impl FromId<DbConn> for Like {
NewLike { NewLike {
post_id: Post::from_id( post_id: Post::from_id(
conn, conn,
&Instance::get_local().expect("Failed to get local instance"),
&act.like_props.object_link::<Id>()?, &act.like_props.object_link::<Id>()?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),
@ -125,6 +126,7 @@ impl FromId<DbConn> for Like {
.id, .id,
user_id: User::from_id( user_id: User::from_id(
conn, conn,
&Instance::get_local().expect("Failed to get local instance"),
&act.like_props.actor_link::<Id>()?, &act.like_props.actor_link::<Id>()?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),

View File

@ -272,6 +272,7 @@ impl Media {
content_warning: image.object_props.summary_string().ok(), content_warning: image.object_props.summary_string().ok(),
owner_id: User::from_id( owner_id: User::from_id(
conn, conn,
&Instance::get_local().expect("Failed to get local instance"),
image image
.object_props .object_props
.attributed_to_link_vec::<Id>() .attributed_to_link_vec::<Id>()

View File

@ -630,13 +630,28 @@ impl FromId<DbConn> for Post {
.into_iter() .into_iter()
.fold((None, vec![]), |(blog, mut authors), link| { .fold((None, vec![]), |(blog, mut authors), link| {
let url = link; let url = link;
match User::from_id(conn, &url, None, CONFIG.proxy()) { match User::from_id(
conn,
&Instance::get_local().expect("Failed to get local instance"),
&url,
None,
CONFIG.proxy(),
) {
Ok(u) => { Ok(u) => {
authors.push(u); authors.push(u);
(blog, authors) (blog, authors)
} }
Err(_) => ( Err(_) => (
blog.or_else(|| Blog::from_id(conn, &url, None, CONFIG.proxy()).ok()), blog.or_else(|| {
Blog::from_id(
conn,
&Instance::get_local().expect("Failed to get local instance"),
&url,
None,
CONFIG.proxy(),
)
.ok()
}),
authors, authors,
), ),
} }
@ -837,8 +852,14 @@ impl AsObject<User, Update, &DbConn> for PostUpdate {
type Output = (); type Output = ();
fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> { fn activity(self, conn: &DbConn, actor: User, _id: &str) -> Result<()> {
let mut post = let mut post = Post::from_id(
Post::from_id(conn, &self.ap_url, None, CONFIG.proxy()).map_err(|(_, e)| e)?; conn,
&Instance::get_local().expect("Failed to get local instance"),
&self.ap_url,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)?;
if !post.is_author(conn, actor.id)? { if !post.is_author(conn, actor.id)? {
// TODO: maybe the author was added in the meantime // TODO: maybe the author was added in the meantime

View File

@ -1,6 +1,7 @@
use crate::{ use crate::{
db_conn::{DbConn, DbPool}, db_conn::{DbConn, DbPool},
follows, follows,
instance::Instance,
posts::{LicensedArticle, Post}, posts::{LicensedArticle, Post},
users::{User, UserEvent}, users::{User, UserEvent},
ACTOR_SYS, CONFIG, USER_CHAN, ACTOR_SYS, CONFIG, USER_CHAN,
@ -89,7 +90,13 @@ fn fetch_and_cache_followers(user: &Arc<User>, conn: &DbConn) {
match follower_ids { match follower_ids {
Ok(user_ids) => { Ok(user_ids) => {
for user_id in user_ids { for user_id in user_ids {
let follower = User::from_id(conn, &user_id, None, CONFIG.proxy()); let follower = User::from_id(
conn,
&Instance::get_local().expect("Failed to get local instance"),
&user_id,
None,
CONFIG.proxy(),
);
match follower { match follower {
Ok(follower) => { Ok(follower) => {
let inserted = follows::Follow::insert( let inserted = follows::Follow::insert(

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
db_conn::DbConn, notifications::*, posts::Post, schema::reshares, timeline::*, users::User, db_conn::DbConn, instance::Instance, notifications::*, posts::Post, schema::reshares,
Connection, Error, Result, CONFIG, timeline::*, users::User, Connection, Error, Result, CONFIG,
}; };
use activitypub::activity::{Announce, Undo}; use activitypub::activity::{Announce, Undo};
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
@ -142,6 +142,7 @@ impl FromId<DbConn> for Reshare {
NewReshare { NewReshare {
post_id: Post::from_id( post_id: Post::from_id(
conn, conn,
&Instance::get_local().expect("Failed to get local instance"),
&act.announce_props.object_link::<Id>()?, &act.announce_props.object_link::<Id>()?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),
@ -150,6 +151,7 @@ impl FromId<DbConn> for Reshare {
.id, .id,
user_id: User::from_id( user_id: User::from_id(
conn, conn,
&Instance::get_local().expect("Failed to get local instance"),
&act.announce_props.actor_link::<Id>()?, &act.announce_props.actor_link::<Id>()?,
None, None,
CONFIG.proxy(), CONFIG.proxy(),

View File

@ -24,7 +24,7 @@ use plume_common::{
activity_pub::{ activity_pub::{
ap_accept_header, ap_accept_header,
inbox::{AsActor, AsObject, FromId}, inbox::{AsActor, AsObject, FromId},
sign::{gen_keypair, Signer}, sign::{gen_keypair, Error as SignatureError, Result as SignatureResult, Signer},
ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY, ActivityStream, ApSignature, Id, IntoId, PublicKey, PUBLIC_VISIBILITY,
}, },
utils, utils,
@ -210,7 +210,14 @@ impl User {
.into_iter() .into_iter()
.find(|l| l.mime_type == Some(String::from("application/activity+json"))) .find(|l| l.mime_type == Some(String::from("application/activity+json")))
.ok_or(Error::Webfinger)?; .ok_or(Error::Webfinger)?;
User::from_id(conn, link.href.as_ref()?, None, CONFIG.proxy()).map_err(|(_, e)| e) User::from_id(
conn,
&Instance::get_local().expect("Failed to get local instance"),
link.href.as_ref()?,
None,
CONFIG.proxy(),
)
.map_err(|(_, e)| e)
} }
pub fn fetch_remote_interact_uri(acct: &str) -> Result<String> { pub fn fetch_remote_interact_uri(acct: &str) -> Result<String> {
@ -1065,24 +1072,22 @@ impl AsObject<User, Delete, &DbConn> for User {
} }
impl Signer for User { impl Signer for User {
type Error = Error;
fn get_key_id(&self) -> String { fn get_key_id(&self) -> String {
format!("{}#main-key", self.ap_url) format!("{}#main-key", self.ap_url)
} }
fn sign(&self, to_sign: &str) -> Result<Vec<u8>> { fn sign(&self, to_sign: &str) -> SignatureResult<Vec<u8>> {
let key = self.get_keypair()?; let key = self.get_keypair()?;
let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?; let mut signer = sign::Signer::new(MessageDigest::sha256(), &key)?;
signer.update(to_sign.as_bytes())?; signer.update(to_sign.as_bytes())?;
signer.sign_to_vec().map_err(Error::from) signer.sign_to_vec().map_err(SignatureError::from)
} }
fn verify(&self, data: &str, signature: &[u8]) -> Result<bool> { fn verify(&self, data: &str, signature: &[u8]) -> SignatureResult<bool> {
let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?; let key = PKey::from_rsa(Rsa::public_key_from_pem(self.public_key.as_ref())?)?;
let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?; let mut verifier = sign::Verifier::new(MessageDigest::sha256(), &key)?;
verifier.update(data.as_bytes())?; verifier.update(data.as_bytes())?;
verifier.verify(&signature).map_err(Error::from) verifier.verify(&signature).map_err(SignatureError::from)
} }
} }

View File

@ -26,8 +26,14 @@ pub fn handle_incoming(
.or_else(|| activity["actor"]["id"].as_str()) .or_else(|| activity["actor"]["id"].as_str())
.ok_or(status::BadRequest(Some("Missing actor id for activity")))?; .ok_or(status::BadRequest(Some("Missing actor id for activity")))?;
let actor = User::from_id(&conn, actor_id, None, CONFIG.proxy()) let actor = User::from_id(
.expect("instance::shared_inbox: user error"); &conn,
&Instance::get_local().expect("Failed to get local instance"),
actor_id,
None,
CONFIG.proxy(),
)
.expect("instance::shared_inbox: user error");
if !verify_http_headers(&actor, &headers.0, &sig).is_secure() && !act.clone().verify(&actor) { if !verify_http_headers(&actor, &headers.0, &sig).is_secure() && !act.clone().verify(&actor) {
// maybe we just know an old key? // maybe we just know an old key?
actor actor

View File

@ -434,7 +434,13 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi
return Some(Redirect::to(uri!(super::user::details: name = target))); return Some(Redirect::to(uri!(super::user::details: name = target)));
} }
if let Ok(post) = Post::from_id(&conn, &target, None, CONFIG.proxy()) { if let Ok(post) = Post::from_id(
&conn,
&Instance::get_local().expect("Failed to get local instance"),
&target,
None,
CONFIG.proxy(),
) {
return Some(Redirect::to(uri!( return Some(Redirect::to(uri!(
super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn, super::posts::details: blog = post.get_blog(&conn).expect("Can't retrieve blog").fqn,
slug = &post.slug, slug = &post.slug,
@ -442,7 +448,13 @@ pub fn interact(conn: DbConn, user: Option<User>, target: String) -> Option<Redi
))); )));
} }
if let Ok(comment) = Comment::from_id(&conn, &target, None, CONFIG.proxy()) { if let Ok(comment) = Comment::from_id(
&conn,
&Instance::get_local().expect("Failed to get local instance"),
&target,
None,
CONFIG.proxy(),
) {
if comment.can_see(&conn, user.as_ref()) { if comment.can_see(&conn, user.as_ref()) {
let post = comment.get_post(&conn).expect("Can't retrieve post"); let post = comment.get_post(&conn).expect("Can't retrieve post");
return Some(Redirect::to(uri!( return Some(Redirect::to(uri!(