]> Untitled Git - lemmy.git/commitdiff
Verifyt http signatures
authorFelix <me@nutomic.com>
Sun, 19 Apr 2020 17:35:40 +0000 (19:35 +0200)
committerFelix <me@nutomic.com>
Sun, 19 Apr 2020 17:35:40 +0000 (19:35 +0200)
server/src/apub/activities.rs
server/src/apub/community_inbox.rs
server/src/apub/signatures.rs
server/src/apub/user_inbox.rs

index 9d2a0668034d8e844e79e1102833f8da1ec8c862..e5980e293f8e0ef4dbf29b56a607ad651578c88f 100644 (file)
@@ -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)?;
index a60d8c68391431e91b5051a54343beb39794abd6..e7fc856e856d2f356efba9dba4a6a53b51011f4b 100644 (file)
@@ -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,
index 4181e11f73d714a2bb80af4a4007957cb0df2f55..40b3c738eff2417b536f7e9556ed7ac370b8c09b 100644 (file)
@@ -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
 
index 75cd4e4790e77a12fb4596e89ac1cdc9b3360dfd..f9faa0f095e5b70e21cf7e61ee9e1466bf50183d 100644 (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::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())