From 5e7a60630c33f75cb1be41ddd0936f1d1074d463 Mon Sep 17 00:00:00 2001 From: Felix Date: Sun, 19 Apr 2020 19:35:40 +0200 Subject: [PATCH] Verifyt http signatures --- server/src/apub/activities.rs | 4 +-- server/src/apub/community_inbox.rs | 26 +++++++++----- server/src/apub/signatures.rs | 48 +++++++++++++++++++++++-- server/src/apub/user_inbox.rs | 56 ++++++++++++++++++++++++++---- 4 files changed, 113 insertions(+), 21 deletions(-) diff --git a/server/src/apub/activities.rs b/server/src/apub/activities.rs index 9d2a06680..e5980e293 100644 --- a/server/src/apub/activities.rs +++ b/server/src/apub/activities.rs @@ -139,7 +139,7 @@ pub fn follow_community( 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], )?; @@ -150,7 +150,7 @@ pub fn follow_community( 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)?; diff --git a/server/src/apub/community_inbox.rs b/server/src/apub/community_inbox.rs index a60d8c683..e7fc856e8 100644 --- a/server/src/apub/community_inbox.rs +++ b/server/src/apub/community_inbox.rs @@ -1,9 +1,10 @@ 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; @@ -19,6 +20,7 @@ pub enum CommunityAcceptedObjects { /// Handler for all incoming activities to community inboxes. pub async fn community_inbox( + request: HttpRequest, input: web::Json, path: web::Path, db: web::Data>>, @@ -31,13 +33,25 @@ pub async fn community_inbox( &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 { +fn handle_follow( + follow: &Follow, + request: &HttpRequest, + conn: &PgConnection, +) -> Result { + 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 @@ -45,12 +59,6 @@ fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result Result { /// 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 { let signing_key_id = format!("{}#main-key", sender_id); - let config = Config::new(); let headers = request .headers_ref() @@ -40,7 +45,7 @@ pub fn sign(request: &Builder, private_key: &str, sender_id: &str) -> Result, Error>>()?; - let signature_header_value = config + let signature_header_value = HTTP_SIG_CONFIG .begin_sign( request.method_ref().unwrap().as_str(), request @@ -62,6 +67,43 @@ pub fn sign(request: &Builder, private_key: &str, sender_id: &str) -> Result 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::, 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 { + 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 diff --git a/server/src/apub/user_inbox.rs b/server/src/apub/user_inbox.rs index 75cd4e479..f9faa0f09 100644 --- a/server/src/apub/user_inbox.rs +++ b/server/src/apub/user_inbox.rs @@ -1,13 +1,16 @@ +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)] @@ -19,10 +22,12 @@ pub enum UserAcceptedObjects { /// Handler for all incoming activities to user inboxes. pub async fn user_inbox( + request: HttpRequest, input: web::Json, path: web::Path, db: web::Data>>, ) -> Result { + // 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!( @@ -32,14 +37,27 @@ pub async fn user_inbox( ); 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 { +fn handle_create( + create: &Create, + request: &HttpRequest, + conn: &PgConnection, +) -> Result { + 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() @@ -54,7 +72,19 @@ fn handle_create(create: &Create, conn: &PgConnection) -> Result Result { +fn handle_update( + update: &Update, + request: &HttpRequest, + conn: &PgConnection, +) -> Result { + 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() @@ -70,7 +100,19 @@ fn handle_update(update: &Update, conn: &PgConnection) -> Result Result { +fn handle_accept( + accept: &Accept, + request: &HttpRequest, + conn: &PgConnection, +) -> Result { + 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())