let to = format!("{}/inbox", community.actor_id);
send_activity(
&follow,
- &community.private_key.as_ref().unwrap(),
+ &user.private_key.as_ref().unwrap(),
&community.actor_id,
vec![to],
)?;
pub fn accept_follow(follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
let community_uri = follow
.follow_props
- .get_actor_xsd_any_uri()
+ .get_object_xsd_any_uri()
.unwrap()
.to_string();
let community = Community::read_from_actor_id(conn, &community_uri)?;
use crate::apub::activities::accept_follow;
use crate::apub::fetcher::fetch_remote_user;
+use crate::apub::signatures::verify;
use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm};
use crate::db::Followable;
use activitystreams::activity::Follow;
-use actix_web::{web, HttpResponse};
+use actix_web::{web, HttpRequest, HttpResponse};
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection;
use failure::Error;
/// Handler for all incoming activities to community inboxes.
pub async fn community_inbox(
+ request: HttpRequest,
input: web::Json<CommunityAcceptedObjects>,
path: web::Path<String>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
&input
);
match input {
- CommunityAcceptedObjects::Follow(f) => handle_follow(&f, conn),
+ CommunityAcceptedObjects::Follow(f) => handle_follow(&f, &request, conn),
}
}
/// Handle a follow request from a remote user, adding it to the local database and returning an
/// Accept activity.
-fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result<HttpResponse, Error> {
+fn handle_follow(
+ follow: &Follow,
+ request: &HttpRequest,
+ conn: &PgConnection,
+) -> Result<HttpResponse, Error> {
+ let user_uri = follow
+ .follow_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+ let user = fetch_remote_user(&Url::parse(&user_uri)?, conn)?;
+ verify(&request, &user.public_key.unwrap())?;
+
// TODO: make sure this is a local community
let community_uri = follow
.follow_props
.unwrap()
.to_string();
let community = Community::read_from_actor_id(conn, &community_uri)?;
- let user_uri = follow
- .follow_props
- .get_actor_xsd_any_uri()
- .unwrap()
- .to_string();
- let user = fetch_remote_user(&Url::parse(&user_uri)?, conn)?;
let community_follower_form = CommunityFollowerForm {
community_id: community.id,
user_id: user.id,
use activitystreams::{actor::Actor, ext::Extension};
+use actix_web::HttpRequest;
use failure::Error;
use http::request::Builder;
use http_signature_normalization::Config;
+use log::debug;
use openssl::hash::MessageDigest;
-use openssl::sign::Signer;
+use openssl::sign::{Signer, Verifier};
use openssl::{pkey::PKey, rsa::Rsa};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
+lazy_static! {
+ static ref HTTP_SIG_CONFIG: Config = Config::new();
+}
+
pub struct Keypair {
pub private_key: String,
pub public_key: String,
/// TODO: would be nice to pass the sending actor in, instead of raw privatekey/id strings
pub fn sign(request: &Builder, private_key: &str, sender_id: &str) -> Result<String, Error> {
let signing_key_id = format!("{}#main-key", sender_id);
- let config = Config::new();
let headers = request
.headers_ref()
})
.collect::<Result<BTreeMap<String, String>, Error>>()?;
- let signature_header_value = config
+ let signature_header_value = HTTP_SIG_CONFIG
.begin_sign(
request.method_ref().unwrap().as_str(),
request
Ok(signature_header_value)
}
+pub fn verify(request: &HttpRequest, public_key: &str) -> Result<(), Error> {
+ 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 {}",
+ &public_key, &signing_string
+ );
+ let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
+ 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()
+ ))
+ }
+}
+
// The following is taken from here:
// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
+use crate::apub::fetcher::{fetch_remote_community, fetch_remote_user};
+use crate::apub::signatures::verify;
use crate::db::post::{Post, PostForm};
use crate::db::Crud;
use activitystreams::activity::{Accept, Create, Update};
use activitystreams::object::Page;
-use actix_web::{web, HttpResponse};
+use actix_web::{web, HttpRequest, HttpResponse};
use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection;
use failure::Error;
use log::debug;
use serde::Deserialize;
+use url::Url;
#[serde(untagged)]
#[derive(Deserialize, Debug)]
/// Handler for all incoming activities to user inboxes.
pub async fn user_inbox(
+ request: HttpRequest,
input: web::Json<UserAcceptedObjects>,
path: web::Path<String>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse, Error> {
+ // TODO: would be nice if we could do the signature check here, but we cant access the actor property
let input = input.into_inner();
let conn = &db.get().unwrap();
debug!(
);
match input {
- UserAcceptedObjects::Create(c) => handle_create(&c, conn),
- UserAcceptedObjects::Update(u) => handle_update(&u, conn),
- UserAcceptedObjects::Accept(a) => handle_accept(&a, conn),
+ UserAcceptedObjects::Create(c) => handle_create(&c, &request, conn),
+ UserAcceptedObjects::Update(u) => handle_update(&u, &request, conn),
+ UserAcceptedObjects::Accept(a) => handle_accept(&a, &request, conn),
}
}
/// Handle create activities and insert them in the database.
-fn handle_create(create: &Create, conn: &PgConnection) -> Result<HttpResponse, Error> {
+fn handle_create(
+ create: &Create,
+ request: &HttpRequest,
+ conn: &PgConnection,
+) -> Result<HttpResponse, Error> {
+ let community_uri = create
+ .create_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+ // TODO: should do this in a generic way so we dont need to know if its a user or a community
+ let user = fetch_remote_user(&Url::parse(&community_uri)?, conn)?;
+ verify(request, &user.public_key.unwrap())?;
+
let page = create
.create_props
.get_object_base_box()
}
/// Handle update activities and insert them in the database.
-fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, Error> {
+fn handle_update(
+ update: &Update,
+ request: &HttpRequest,
+ conn: &PgConnection,
+) -> Result<HttpResponse, Error> {
+ let community_uri = update
+ .update_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+ let user = fetch_remote_user(&Url::parse(&community_uri)?, conn)?;
+ verify(request, &user.public_key.unwrap())?;
+
let page = update
.update_props
.get_object_base_box()
}
/// Handle accepted follows.
-fn handle_accept(_accept: &Accept, _conn: &PgConnection) -> Result<HttpResponse, Error> {
+fn handle_accept(
+ accept: &Accept,
+ request: &HttpRequest,
+ conn: &PgConnection,
+) -> Result<HttpResponse, Error> {
+ let community_uri = accept
+ .accept_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+ let community = fetch_remote_community(&Url::parse(&community_uri)?, conn)?;
+ verify(request, &community.public_key.unwrap())?;
+
// TODO: make sure that we actually requested a follow
// TODO: at this point, indicate to the user that they are following the community
Ok(HttpResponse::Ok().finish())