From 9fdd3bec1811981a6ceec91e97354fc9b84b5b03 Mon Sep 17 00:00:00 2001 From: asonix Date: Sun, 15 Mar 2020 23:15:50 -0500 Subject: [PATCH] Add http signature verification --- src/apub.rs | 3 +++ src/error.rs | 9 +++++++++ src/inbox.rs | 28 +++++++++++++++----------- src/main.rs | 12 ++++++++++- src/verifier.rs | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 92 insertions(+), 13 deletions(-) create mode 100644 src/verifier.rs diff --git a/src/apub.rs b/src/apub.rs index 269ca4d..786006c 100644 --- a/src/apub.rs +++ b/src/apub.rs @@ -81,6 +81,9 @@ pub struct AcceptedActors { pub inbox: XsdAnyUri, pub endpoints: Endpoints, + + #[serde(skip_serializing_if = "Option::is_none")] + pub public_key: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] diff --git a/src/error.rs b/src/error.rs index f6f8eee..36b8144 100644 --- a/src/error.rs +++ b/src/error.rs @@ -27,6 +27,12 @@ pub enum MyError { #[error("Couldn't parse the signature header")] HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue), + #[error("Couldn't decode base64")] + Base64(#[from] base64::DecodeError), + + #[error("Invalid algorithm provided to verifier")] + Algorithm, + #[error("Wrong ActivityPub kind")] Kind, @@ -50,6 +56,9 @@ pub enum MyError { #[error("URI is missing domain field")] Domain, + + #[error("Public key is missing")] + MissingKey, } impl ResponseError for MyError { diff --git a/src/inbox.rs b/src/inbox.rs index 30c7065..f796993 100644 --- a/src/inbox.rs +++ b/src/inbox.rs @@ -1,3 +1,9 @@ +use crate::{ + apub::{AcceptedActors, AcceptedObjects, ValidTypes}, + db_actor::{DbActor, DbQuery, Pool}, + error::MyError, + state::{State, UrlKind}, +}; use activitystreams::{ activity::apub::{Accept, Announce, Follow, Undo}, context, @@ -8,13 +14,6 @@ use actix_web::{client::Client, web, HttpResponse}; use futures::join; use log::error; -use crate::{ - apub::{AcceptedActors, AcceptedObjects, ValidTypes}, - db_actor::{DbActor, DbQuery, Pool}, - error::MyError, - state::{State, UrlKind}, -}; - pub async fn inbox( db_actor: web::Data>, state: web::Data, @@ -23,7 +22,12 @@ pub async fn inbox( ) -> Result { let input = input.into_inner(); - let actor = fetch_actor(state.clone(), &client, &input.actor).await?; + let actor = fetch_actor( + state.clone().into_inner(), + client.clone().into_inner(), + &input.actor, + ) + .await?; match input.kind { ValidTypes::Announce | ValidTypes::Create => { @@ -217,15 +221,15 @@ async fn handle_follow( let client = client.into_inner(); let accept2 = accept.clone(); actix::Arbiter::spawn(async move { - let _ = deliver(&state.into_inner(), &client, actor_inbox, &accept2).await; + let _ = deliver(&state.into_inner(), &client.clone(), actor_inbox, &accept2).await; }); Ok(response(accept)) } -async fn fetch_actor( - state: web::Data, - client: &web::Data, +pub async fn fetch_actor( + state: std::sync::Arc, + client: std::sync::Arc, actor_id: &XsdAnyUri, ) -> Result { if let Some(actor) = state.get_actor(actor_id).await { diff --git a/src/main.rs b/src/main.rs index 36199cb..ebfb090 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,9 @@ use activitystreams::{actor::apub::Application, context, endpoint::EndpointProperties}; use actix_web::{client::Client, middleware::Logger, web, App, HttpServer, Responder}; use bb8_postgres::tokio_postgres; +use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature}; use rsa_pem::KeyExt; +use sha2::{Digest, Sha256}; mod apub; mod db_actor; @@ -10,6 +12,7 @@ mod error; mod inbox; mod label; mod state; +mod verifier; mod webfinger; use self::{ @@ -18,6 +21,8 @@ use self::{ error::MyError, label::ArbiterLabelFactory, state::{State, UrlKind}, + verifier::MyVerify, + webfinger::RelayResolver, }; async fn index() -> impl Responder { @@ -84,6 +89,11 @@ async fn main() -> Result<(), anyhow::Error> { let client = Client::default(); App::new() + .wrap(VerifyDigest::new(Sha256::new())) + .wrap(VerifySignature::new( + MyVerify(state.clone(), client.clone()), + Default::default(), + )) .wrap(Logger::default()) .data(actor) .data(state.clone()) @@ -91,7 +101,7 @@ async fn main() -> Result<(), anyhow::Error> { .service(web::resource("/").route(web::get().to(index))) .service(web::resource("/inbox").route(web::post().to(inbox::inbox))) .service(web::resource("/actor").route(web::get().to(actor_route))) - .service(actix_webfinger::resource::<_, webfinger::RelayResolver>()) + .service(actix_webfinger::resource::<_, RelayResolver>()) }) .bind("127.0.0.1:8080")? .run() diff --git a/src/verifier.rs b/src/verifier.rs new file mode 100644 index 0000000..5c01ede --- /dev/null +++ b/src/verifier.rs @@ -0,0 +1,53 @@ +use crate::{error::MyError, state::State}; +use actix_web::client::Client; +use http_signature_normalization_actix::prelude::*; +use rsa::{hash::Hashes, padding::PaddingScheme, PublicKey, RSAPublicKey}; +use rsa_pem::KeyExt; +use std::{future::Future, pin::Pin, sync::Arc}; + +#[derive(Clone)] +pub struct MyVerify(pub State, pub Client); + +impl SignatureVerify for MyVerify { + type Error = MyError; + type Future = Pin>>>; + + fn signature_verify( + &mut self, + algorithm: Option, + key_id: &str, + signature: &str, + signing_string: &str, + ) -> Self::Future { + let key_id = key_id.to_owned(); + let signature = signature.to_owned(); + let signing_string = signing_string.to_owned(); + + let state = Arc::new(self.0.clone()); + let client = Arc::new(self.1.clone()); + + Box::pin(async move { + let actor = crate::inbox::fetch_actor(state, client, &key_id.parse()?).await?; + + let public_key = actor.public_key.ok_or(MyError::MissingKey)?; + + let public_key = RSAPublicKey::from_pem_pkcs8(&public_key.public_key_pem)?; + + match algorithm { + Some(Algorithm::Hs2019) => (), + _ => return Err(MyError::Algorithm), + }; + + let decoded = base64::decode(signature)?; + + public_key.verify( + PaddingScheme::PKCS1v15, + Some(&Hashes::SHA2_256), + &decoded, + signing_string.as_bytes(), + )?; + + Ok(true) + }) + } +}