From 379116605faee304277561f1d3ca671c0958ebe0 Mon Sep 17 00:00:00 2001 From: silverpill Date: Mon, 2 May 2022 23:29:04 +0000 Subject: [PATCH] Make HTTP signature verification compatible with GoToSocial --- src/activitypub/actor.rs | 4 ++-- src/activitypub/constants.rs | 2 ++ src/activitypub/deliverer.rs | 5 +++-- src/activitypub/fetcher/helpers.rs | 2 +- src/config.rs | 3 ++- src/http_signatures/verify.rs | 32 +++++++++++++++++++++++------- 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/activitypub/actor.rs b/src/activitypub/actor.rs index e5ab9fd..837a131 100644 --- a/src/activitypub/actor.rs +++ b/src/activitypub/actor.rs @@ -12,7 +12,7 @@ use crate::models::profiles::types::{ExtraField, IdentityProof}; use crate::models::users::types::User; use crate::utils::crypto::{deserialize_private_key, get_public_key_pem}; use crate::utils::files::get_file_url; -use super::constants::AP_CONTEXT; +use super::constants::{ACTOR_KEY_SUFFIX, AP_CONTEXT}; use super::views::{ get_actor_url, get_inbox_url, @@ -241,7 +241,7 @@ pub fn get_local_actor( let private_key = deserialize_private_key(&user.private_key)?; let public_key_pem = get_public_key_pem(&private_key)?; let public_key = PublicKey { - id: format!("{}#main-key", actor_id), + id: format!("{}{}", actor_id, ACTOR_KEY_SUFFIX), owner: actor_id.clone(), public_key_pem: public_key_pem, }; diff --git a/src/activitypub/constants.rs b/src/activitypub/constants.rs index c7bf2f4..1027eb8 100644 --- a/src/activitypub/constants.rs +++ b/src/activitypub/constants.rs @@ -3,3 +3,5 @@ pub const ACTIVITY_CONTENT_TYPE: &str = r#"application/ld+json; profile="https:/ pub const AP_CONTEXT: &str = "https://www.w3.org/ns/activitystreams"; pub const AP_PUBLIC: &str = "https://www.w3.org/ns/activitystreams#Public"; + +pub const ACTOR_KEY_SUFFIX: &str = "#main-key"; diff --git a/src/activitypub/deliverer.rs b/src/activitypub/deliverer.rs index 151a1c1..0f66077 100644 --- a/src/activitypub/deliverer.rs +++ b/src/activitypub/deliverer.rs @@ -7,7 +7,7 @@ use crate::models::users::types::User; use crate::utils::crypto::deserialize_private_key; use super::activity::Activity; use super::actor::Actor; -use super::constants::ACTIVITY_CONTENT_TYPE; +use super::constants::{ACTIVITY_CONTENT_TYPE, ACTOR_KEY_SUFFIX}; use super::views::get_actor_url; #[derive(thiserror::Error, Debug)] @@ -83,11 +83,12 @@ async fn deliver_activity_worker( ) -> Result<(), DelivererError> { let actor_key = deserialize_private_key(&sender.private_key)?; let actor_key_id = format!( - "{}#main-key", + "{}{}", get_actor_url( &instance.url(), &sender.profile.username, ), + ACTOR_KEY_SUFFIX, ); let activity_json = serde_json::to_string(&activity)?; let mut inboxes: Vec = recipients.into_iter() diff --git a/src/activitypub/fetcher/helpers.rs b/src/activitypub/fetcher/helpers.rs index 02fe1ee..41b1754 100644 --- a/src/activitypub/fetcher/helpers.rs +++ b/src/activitypub/fetcher/helpers.rs @@ -69,7 +69,7 @@ pub async fn get_or_import_profile_by_actor_id( ) .await .map_err(|err| { - log::warn!("failed to fetch {}: {}", actor_id, err); + log::warn!("failed to fetch {} ({})", actor_id, err); err })?; log::info!("fetched profile {}", profile_data.acct); diff --git a/src/config.rs b/src/config.rs index 77ca93b..900c6d2 100644 --- a/src/config.rs +++ b/src/config.rs @@ -6,6 +6,7 @@ use rsa::RsaPrivateKey; use serde::{de, Deserialize, Deserializer}; use url::Url; +use crate::activitypub::constants::ACTOR_KEY_SUFFIX; use crate::activitypub::views::get_instance_actor_url; use crate::errors::ConversionError; use crate::ethereum::utils::{parse_caip2_chain_id, ChainIdError}; @@ -203,7 +204,7 @@ impl Instance { } pub fn actor_key_id(&self) -> String { - format!("{}#main-key", self.actor_id()) + format!("{}{}", self.actor_id(), ACTOR_KEY_SUFFIX) } pub fn agent(&self) -> String { diff --git a/src/http_signatures/verify.rs b/src/http_signatures/verify.rs index 7b55c84..8340c24 100644 --- a/src/http_signatures/verify.rs +++ b/src/http_signatures/verify.rs @@ -44,7 +44,7 @@ pub enum VerificationError { } struct SignatureData { - pub actor_id: String, + pub key_id: String, pub message: String, // reconstructed message pub signature: String, // base64-encoded signature } @@ -102,17 +102,23 @@ fn parse_http_signature( message.push_str(&message_part); } - let key_url = url::Url::parse(&key_id)?; - let actor_id = &key_url[..url::Position::BeforeQuery]; - let signature_data = SignatureData { - actor_id: actor_id.to_string(), + key_id, message, signature, }; Ok(signature_data) } +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 ID pub async fn verify_http_signature( config: &Config, @@ -125,11 +131,12 @@ pub async fn verify_http_signature( request.headers(), )?; + let actor_id = key_id_to_actor_id(&signature_data.key_id)?; let actor_profile = match get_or_import_profile_by_actor_id( db_client, &config.instance(), &config.media_dir(), - &signature_data.actor_id, + &actor_id, ).await { Ok(profile) => profile, Err(ImportError::DatabaseError(error)) => return Err(error.into()), @@ -185,11 +192,22 @@ mod tests { &request_uri, &request_headers, ).unwrap(); - assert_eq!(signature_data.actor_id, "https://myserver.org/actor"); + assert_eq!(signature_data.key_id, "https://myserver.org/actor#main-key"); assert_eq!( signature_data.message, "(request-target): post /user/123/inbox\nhost: example.com", ); assert_eq!(signature_data.signature, "test"); } + + #[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"); + } }