mirror of
https://github.com/LemmyNet/lemmy.git
synced 2024-11-30 08:11:20 +00:00
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);
|
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)?;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Reference in a new issue