Verifyt http signatures

This commit is contained in:
Felix 2020-04-19 19:35:40 +02:00
parent 5557f38436
commit 5e7a60630c
4 changed files with 113 additions and 21 deletions

View file

@ -139,7 +139,7 @@ pub fn follow_community(
let to = format!("{}/inbox", community.actor_id); let to = format!("{}/inbox", community.actor_id);
send_activity( send_activity(
&follow, &follow,
&community.private_key.as_ref().unwrap(), &user.private_key.as_ref().unwrap(),
&community.actor_id, &community.actor_id,
vec![to], vec![to],
)?; )?;
@ -150,7 +150,7 @@ pub fn follow_community(
pub fn accept_follow(follow: &Follow, conn: &PgConnection) -> Result<(), Error> { pub fn accept_follow(follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
let community_uri = follow let community_uri = follow
.follow_props .follow_props
.get_actor_xsd_any_uri() .get_object_xsd_any_uri()
.unwrap() .unwrap()
.to_string(); .to_string();
let community = Community::read_from_actor_id(conn, &community_uri)?; let community = Community::read_from_actor_id(conn, &community_uri)?;

View file

@ -1,9 +1,10 @@
use crate::apub::activities::accept_follow; use crate::apub::activities::accept_follow;
use crate::apub::fetcher::fetch_remote_user; use crate::apub::fetcher::fetch_remote_user;
use crate::apub::signatures::verify;
use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm}; use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm};
use crate::db::Followable; use crate::db::Followable;
use activitystreams::activity::Follow; use activitystreams::activity::Follow;
use actix_web::{web, HttpResponse}; use actix_web::{web, HttpRequest, HttpResponse};
use diesel::r2d2::{ConnectionManager, Pool}; use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error; use failure::Error;
@ -19,6 +20,7 @@ pub enum CommunityAcceptedObjects {
/// Handler for all incoming activities to community inboxes. /// Handler for all incoming activities to community inboxes.
pub async fn community_inbox( pub async fn community_inbox(
request: HttpRequest,
input: web::Json<CommunityAcceptedObjects>, input: web::Json<CommunityAcceptedObjects>,
path: web::Path<String>, path: web::Path<String>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>, db: web::Data<Pool<ConnectionManager<PgConnection>>>,
@ -31,13 +33,25 @@ pub async fn community_inbox(
&input &input
); );
match 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 /// Handle a follow request from a remote user, adding it to the local database and returning an
/// Accept activity. /// 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 // TODO: make sure this is a local community
let community_uri = follow let community_uri = follow
.follow_props .follow_props
@ -45,12 +59,6 @@ fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result<HttpResponse, E
.unwrap() .unwrap()
.to_string(); .to_string();
let community = Community::read_from_actor_id(conn, &community_uri)?; 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 { let community_follower_form = CommunityFollowerForm {
community_id: community.id, community_id: community.id,
user_id: user.id, user_id: user.id,

View file

@ -1,13 +1,19 @@
use activitystreams::{actor::Actor, ext::Extension}; use activitystreams::{actor::Actor, ext::Extension};
use actix_web::HttpRequest;
use failure::Error; use failure::Error;
use http::request::Builder; use http::request::Builder;
use http_signature_normalization::Config; use http_signature_normalization::Config;
use log::debug;
use openssl::hash::MessageDigest; use openssl::hash::MessageDigest;
use openssl::sign::Signer; use openssl::sign::{Signer, Verifier};
use openssl::{pkey::PKey, rsa::Rsa}; use openssl::{pkey::PKey, rsa::Rsa};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
lazy_static! {
static ref HTTP_SIG_CONFIG: Config = Config::new();
}
pub struct Keypair { pub struct Keypair {
pub private_key: String, pub private_key: String,
pub public_key: String, pub public_key: String,
@ -29,7 +35,6 @@ pub fn generate_actor_keypair() -> Result<Keypair, Error> {
/// TODO: would be nice to pass the sending actor in, instead of raw privatekey/id strings /// 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> { pub fn sign(request: &Builder, private_key: &str, sender_id: &str) -> Result<String, Error> {
let signing_key_id = format!("{}#main-key", sender_id); let signing_key_id = format!("{}#main-key", sender_id);
let config = Config::new();
let headers = request let headers = request
.headers_ref() .headers_ref()
@ -40,7 +45,7 @@ pub fn sign(request: &Builder, private_key: &str, sender_id: &str) -> Result<Str
}) })
.collect::<Result<BTreeMap<String, String>, Error>>()?; .collect::<Result<BTreeMap<String, String>, Error>>()?;
let signature_header_value = config let signature_header_value = HTTP_SIG_CONFIG
.begin_sign( .begin_sign(
request.method_ref().unwrap().as_str(), request.method_ref().unwrap().as_str(),
request request
@ -62,6 +67,43 @@ pub fn sign(request: &Builder, private_key: &str, sender_id: &str) -> Result<Str
Ok(signature_header_value) 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: // The following is taken from here:
// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html // https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html

View file

@ -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::post::{Post, PostForm};
use crate::db::Crud; use crate::db::Crud;
use activitystreams::activity::{Accept, Create, Update}; use activitystreams::activity::{Accept, Create, Update};
use activitystreams::object::Page; use activitystreams::object::Page;
use actix_web::{web, HttpResponse}; use actix_web::{web, HttpRequest, HttpResponse};
use diesel::r2d2::{ConnectionManager, Pool}; use diesel::r2d2::{ConnectionManager, Pool};
use diesel::PgConnection; use diesel::PgConnection;
use failure::Error; use failure::Error;
use log::debug; use log::debug;
use serde::Deserialize; use serde::Deserialize;
use url::Url;
#[serde(untagged)] #[serde(untagged)]
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@ -19,10 +22,12 @@ pub enum UserAcceptedObjects {
/// Handler for all incoming activities to user inboxes. /// Handler for all incoming activities to user inboxes.
pub async fn user_inbox( pub async fn user_inbox(
request: HttpRequest,
input: web::Json<UserAcceptedObjects>, input: web::Json<UserAcceptedObjects>,
path: web::Path<String>, path: web::Path<String>,
db: web::Data<Pool<ConnectionManager<PgConnection>>>, db: web::Data<Pool<ConnectionManager<PgConnection>>>,
) -> Result<HttpResponse, Error> { ) -> 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 input = input.into_inner();
let conn = &db.get().unwrap(); let conn = &db.get().unwrap();
debug!( debug!(
@ -32,14 +37,27 @@ pub async fn user_inbox(
); );
match input { match input {
UserAcceptedObjects::Create(c) => handle_create(&c, conn), UserAcceptedObjects::Create(c) => handle_create(&c, &request, conn),
UserAcceptedObjects::Update(u) => handle_update(&u, conn), UserAcceptedObjects::Update(u) => handle_update(&u, &request, conn),
UserAcceptedObjects::Accept(a) => handle_accept(&a, conn), UserAcceptedObjects::Accept(a) => handle_accept(&a, &request, conn),
} }
} }
/// Handle create activities and insert them in the database. /// 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 let page = create
.create_props .create_props
.get_object_base_box() .get_object_base_box()
@ -54,7 +72,19 @@ fn handle_create(create: &Create, conn: &PgConnection) -> Result<HttpResponse, E
} }
/// Handle update activities and insert them in the database. /// 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 let page = update
.update_props .update_props
.get_object_base_box() .get_object_base_box()
@ -70,7 +100,19 @@ fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, E
} }
/// Handle accepted follows. /// 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: make sure that we actually requested a follow
// TODO: at this point, indicate to the user that they are following the community // TODO: at this point, indicate to the user that they are following the community
Ok(HttpResponse::Ok().finish()) Ok(HttpResponse::Ok().finish())