From 01d3f54939292ea3e1726a608986168e8064f906 Mon Sep 17 00:00:00 2001 From: silverpill Date: Sun, 23 Oct 2022 19:13:05 +0000 Subject: [PATCH] Move verify_signed_request function to activitypub::authentication module --- src/activitypub/authentication.rs | 78 ++++++++++++++++++++++++++++++ src/activitypub/mod.rs | 1 + src/activitypub/receiver.rs | 6 +-- src/http_signatures/verify.rs | 80 ++----------------------------- 4 files changed, 86 insertions(+), 79 deletions(-) create mode 100644 src/activitypub/authentication.rs diff --git a/src/activitypub/authentication.rs b/src/activitypub/authentication.rs new file mode 100644 index 0000000..f74c5de --- /dev/null +++ b/src/activitypub/authentication.rs @@ -0,0 +1,78 @@ +use actix_web::HttpRequest; +use tokio_postgres::GenericClient; + +use crate::config::Config; +use crate::http_signatures::verify::{ + parse_http_signature, + verify_http_signature, + VerificationError, +}; +use crate::models::profiles::queries::get_profile_by_remote_actor_id; +use crate::models::profiles::types::DbActorProfile; +use crate::utils::crypto::deserialize_public_key; +use super::fetcher::helpers::get_or_import_profile_by_actor_id; +use super::receiver::HandlerError; + +fn key_id_to_actor_id(key_id: &str) -> Result { + let key_url = url::Url::parse(key_id)?; + // Strip #main-key (works with most AP servers) + let actor_id = &key_url[..url::Position::BeforeQuery]; + // GoToSocial compat + let actor_id = actor_id.trim_end_matches("/main-key"); + Ok(actor_id.to_string()) +} + +/// Verifies HTTP signature and returns signer +pub async fn verify_signed_request( + config: &Config, + db_client: &impl GenericClient, + request: &HttpRequest, + no_fetch: bool, +) -> Result { + let signature_data = parse_http_signature( + request.method(), + request.uri(), + request.headers(), + )?; + + let actor_id = key_id_to_actor_id(&signature_data.key_id)?; + let actor_profile = if no_fetch { + get_profile_by_remote_actor_id(db_client, &actor_id).await? + } else { + match get_or_import_profile_by_actor_id( + db_client, + &config.instance(), + &config.media_dir(), + &actor_id, + ).await { + Ok(profile) => profile, + Err(HandlerError::DatabaseError(error)) => return Err(error.into()), + Err(other_error) => { + return Err(VerificationError::ActorError(other_error.to_string())); + }, + } + }; + let actor = actor_profile.actor_json.as_ref() + .ok_or(VerificationError::ActorError("invalid profile".to_string()))?; + let public_key = deserialize_public_key(&actor.public_key.public_key_pem)?; + + verify_http_signature(&signature_data, &public_key)?; + + Ok(actor_profile) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_key_id_to_actor_id() { + let key_id = "https://myserver.org/actor#main-key"; + let actor_id = key_id_to_actor_id(key_id).unwrap(); + assert_eq!(actor_id, "https://myserver.org/actor"); + + let key_id = "https://myserver.org/actor/main-key"; + let actor_id = key_id_to_actor_id(key_id).unwrap(); + assert_eq!(actor_id, "https://myserver.org/actor"); + } +} diff --git a/src/activitypub/mod.rs b/src/activitypub/mod.rs index d474181..83a72ce 100644 --- a/src/activitypub/mod.rs +++ b/src/activitypub/mod.rs @@ -1,5 +1,6 @@ mod activity; pub mod actors; +mod authentication; pub mod builders; mod collections; pub mod constants; diff --git a/src/activitypub/receiver.rs b/src/activitypub/receiver.rs index 1b8a281..8fa751c 100644 --- a/src/activitypub/receiver.rs +++ b/src/activitypub/receiver.rs @@ -10,11 +10,9 @@ use crate::errors::{ HttpError, ValidationError, }; -use crate::http_signatures::verify::{ - verify_signed_request, - VerificationError, -}; +use crate::http_signatures::verify::VerificationError; use super::activity::{Activity, Object}; +use super::authentication::verify_signed_request; use super::fetcher::{ fetchers::FetchError, helpers::import_post, diff --git a/src/http_signatures/verify.rs b/src/http_signatures/verify.rs index e8737e1..235dbcb 100644 --- a/src/http_signatures/verify.rs +++ b/src/http_signatures/verify.rs @@ -1,23 +1,12 @@ use std::collections::HashMap; -use actix_web::{ - HttpRequest, - http::{Method, Uri, header::HeaderMap}, -}; +use actix_web::http::{Method, Uri, header::HeaderMap}; use chrono::{DateTime, TimeZone, Utc}; use regex::Regex; use rsa::RsaPublicKey; -use tokio_postgres::GenericClient; -use crate::activitypub::{ - fetcher::helpers::get_or_import_profile_by_actor_id, - handlers::HandlerError, -}; -use crate::config::Config; use crate::errors::DatabaseError; -use crate::models::profiles::queries::get_profile_by_remote_actor_id; -use crate::models::profiles::types::DbActorProfile; -use crate::utils::crypto::{deserialize_public_key, verify_signature}; +use crate::utils::crypto::verify_signature; #[derive(thiserror::Error, Debug)] pub enum VerificationError { @@ -49,7 +38,7 @@ pub enum VerificationError { InvalidSigner, } -struct HttpSignatureData { +pub struct HttpSignatureData { pub key_id: String, pub message: String, // reconstructed message pub signature: String, // base64-encoded signature @@ -58,7 +47,7 @@ struct HttpSignatureData { const SIGNATURE_PARAMETER_RE: &str = r#"^(?P[a-zA-Z]+)="(?P.+)"$"#; -fn parse_http_signature( +pub fn parse_http_signature( request_method: &Method, request_uri: &Uri, request_headers: &HeaderMap, @@ -132,7 +121,7 @@ fn parse_http_signature( Ok(signature_data) } -fn verify_http_signature( +pub fn verify_http_signature( signature_data: &HttpSignatureData, signer_key: &RsaPublicKey, ) -> Result<(), VerificationError> { @@ -150,54 +139,6 @@ fn verify_http_signature( Ok(()) } -fn key_id_to_actor_id(key_id: &str) -> Result { - let key_url = url::Url::parse(key_id)?; - // Strip #main-key (works with most AP servers) - let actor_id = &key_url[..url::Position::BeforeQuery]; - // GoToSocial compat - let actor_id = actor_id.trim_end_matches("/main-key"); - Ok(actor_id.to_string()) -} - -/// Verifies HTTP signature and returns signer -pub async fn verify_signed_request( - config: &Config, - db_client: &impl GenericClient, - request: &HttpRequest, - no_fetch: bool, -) -> Result { - let signature_data = parse_http_signature( - request.method(), - request.uri(), - request.headers(), - )?; - - let actor_id = key_id_to_actor_id(&signature_data.key_id)?; - let actor_profile = if no_fetch { - get_profile_by_remote_actor_id(db_client, &actor_id).await? - } else { - match get_or_import_profile_by_actor_id( - db_client, - &config.instance(), - &config.media_dir(), - &actor_id, - ).await { - Ok(profile) => profile, - Err(HandlerError::DatabaseError(error)) => return Err(error.into()), - Err(other_error) => { - return Err(VerificationError::ActorError(other_error.to_string())); - }, - } - }; - let actor = actor_profile.actor_json.as_ref() - .ok_or(VerificationError::ActorError("invalid profile".to_string()))?; - let public_key = deserialize_public_key(&actor.public_key.public_key_pem)?; - - verify_http_signature(&signature_data, &public_key)?; - - Ok(actor_profile) -} - #[cfg(test)] mod tests { use actix_web::http::{ @@ -239,15 +180,4 @@ mod tests { assert_eq!(signature_data.signature, "test"); assert_eq!(signature_data.created_at.is_some(), false); } - - #[test] - fn test_key_id_to_actor_id() { - let key_id = "https://myserver.org/actor#main-key"; - let actor_id = key_id_to_actor_id(key_id).unwrap(); - assert_eq!(actor_id, "https://myserver.org/actor"); - - let key_id = "https://myserver.org/actor/main-key"; - let actor_id = key_id_to_actor_id(key_id).unwrap(); - assert_eq!(actor_id, "https://myserver.org/actor"); - } }