Verifyt http signatures
This commit is contained in:
parent
5284dc0c52
commit
7117b5ce32
4 changed files with 113 additions and 21 deletions
|
@ -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)?;
|
||||
|
|
|
@ -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<CommunityAcceptedObjects>,
|
||||
path: web::Path<String>,
|
||||
db: web::Data<Pool<ConnectionManager<PgConnection>>>,
|
||||
|
@ -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<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
|
||||
|
@ -45,12 +59,6 @@ fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result<HttpResponse, E
|
|||
.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,
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
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,
|
||||
|
@ -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
|
||||
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()
|
||||
|
@ -40,7 +45,7 @@ pub fn sign(request: &Builder, private_key: &str, sender_id: &str) -> Result<Str
|
|||
})
|
||||
.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
|
||||
|
@ -62,6 +67,43 @@ pub fn sign(request: &Builder, private_key: &str, sender_id: &str) -> Result<Str
|
|||
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
|
||||
|
||||
|
|
|
@ -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<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!(
|
||||
|
@ -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<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()
|
||||
|
@ -54,7 +72,19 @@ fn handle_create(create: &Create, conn: &PgConnection) -> Result<HttpResponse, E
|
|||
}
|
||||
|
||||
/// 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()
|
||||
|
@ -70,7 +100,19 @@ fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, E
|
|||
}
|
||||
|
||||
/// 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())
|
||||
|
|
Loading…
Reference in a new issue