Make HTTP signature verification compatible with GoToSocial

This commit is contained in:
silverpill 2022-05-02 23:29:04 +00:00
parent 5c0e6b0b0c
commit 379116605f
6 changed files with 35 additions and 13 deletions

View file

@ -12,7 +12,7 @@ use crate::models::profiles::types::{ExtraField, IdentityProof};
use crate::models::users::types::User; use crate::models::users::types::User;
use crate::utils::crypto::{deserialize_private_key, get_public_key_pem}; use crate::utils::crypto::{deserialize_private_key, get_public_key_pem};
use crate::utils::files::get_file_url; use crate::utils::files::get_file_url;
use super::constants::AP_CONTEXT; use super::constants::{ACTOR_KEY_SUFFIX, AP_CONTEXT};
use super::views::{ use super::views::{
get_actor_url, get_actor_url,
get_inbox_url, get_inbox_url,
@ -241,7 +241,7 @@ pub fn get_local_actor(
let private_key = deserialize_private_key(&user.private_key)?; let private_key = deserialize_private_key(&user.private_key)?;
let public_key_pem = get_public_key_pem(&private_key)?; let public_key_pem = get_public_key_pem(&private_key)?;
let public_key = PublicKey { let public_key = PublicKey {
id: format!("{}#main-key", actor_id), id: format!("{}{}", actor_id, ACTOR_KEY_SUFFIX),
owner: actor_id.clone(), owner: actor_id.clone(),
public_key_pem: public_key_pem, public_key_pem: public_key_pem,
}; };

View file

@ -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_CONTEXT: &str = "https://www.w3.org/ns/activitystreams";
pub const AP_PUBLIC: &str = "https://www.w3.org/ns/activitystreams#Public"; pub const AP_PUBLIC: &str = "https://www.w3.org/ns/activitystreams#Public";
pub const ACTOR_KEY_SUFFIX: &str = "#main-key";

View file

@ -7,7 +7,7 @@ use crate::models::users::types::User;
use crate::utils::crypto::deserialize_private_key; use crate::utils::crypto::deserialize_private_key;
use super::activity::Activity; use super::activity::Activity;
use super::actor::Actor; 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; use super::views::get_actor_url;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
@ -83,11 +83,12 @@ async fn deliver_activity_worker(
) -> Result<(), DelivererError> { ) -> Result<(), DelivererError> {
let actor_key = deserialize_private_key(&sender.private_key)?; let actor_key = deserialize_private_key(&sender.private_key)?;
let actor_key_id = format!( let actor_key_id = format!(
"{}#main-key", "{}{}",
get_actor_url( get_actor_url(
&instance.url(), &instance.url(),
&sender.profile.username, &sender.profile.username,
), ),
ACTOR_KEY_SUFFIX,
); );
let activity_json = serde_json::to_string(&activity)?; let activity_json = serde_json::to_string(&activity)?;
let mut inboxes: Vec<String> = recipients.into_iter() let mut inboxes: Vec<String> = recipients.into_iter()

View file

@ -69,7 +69,7 @@ pub async fn get_or_import_profile_by_actor_id(
) )
.await .await
.map_err(|err| { .map_err(|err| {
log::warn!("failed to fetch {}: {}", actor_id, err); log::warn!("failed to fetch {} ({})", actor_id, err);
err err
})?; })?;
log::info!("fetched profile {}", profile_data.acct); log::info!("fetched profile {}", profile_data.acct);

View file

@ -6,6 +6,7 @@ use rsa::RsaPrivateKey;
use serde::{de, Deserialize, Deserializer}; use serde::{de, Deserialize, Deserializer};
use url::Url; use url::Url;
use crate::activitypub::constants::ACTOR_KEY_SUFFIX;
use crate::activitypub::views::get_instance_actor_url; use crate::activitypub::views::get_instance_actor_url;
use crate::errors::ConversionError; use crate::errors::ConversionError;
use crate::ethereum::utils::{parse_caip2_chain_id, ChainIdError}; use crate::ethereum::utils::{parse_caip2_chain_id, ChainIdError};
@ -203,7 +204,7 @@ impl Instance {
} }
pub fn actor_key_id(&self) -> String { 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 { pub fn agent(&self) -> String {

View file

@ -44,7 +44,7 @@ pub enum VerificationError {
} }
struct SignatureData { struct SignatureData {
pub actor_id: String, pub key_id: String,
pub message: String, // reconstructed message pub message: String, // reconstructed message
pub signature: String, // base64-encoded signature pub signature: String, // base64-encoded signature
} }
@ -102,17 +102,23 @@ fn parse_http_signature(
message.push_str(&message_part); 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 { let signature_data = SignatureData {
actor_id: actor_id.to_string(), key_id,
message, message,
signature, signature,
}; };
Ok(signature_data) Ok(signature_data)
} }
fn key_id_to_actor_id(key_id: &str) -> Result<String, url::ParseError> {
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 /// Verifies HTTP signature and returns signer ID
pub async fn verify_http_signature( pub async fn verify_http_signature(
config: &Config, config: &Config,
@ -125,11 +131,12 @@ pub async fn verify_http_signature(
request.headers(), 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( let actor_profile = match get_or_import_profile_by_actor_id(
db_client, db_client,
&config.instance(), &config.instance(),
&config.media_dir(), &config.media_dir(),
&signature_data.actor_id, &actor_id,
).await { ).await {
Ok(profile) => profile, Ok(profile) => profile,
Err(ImportError::DatabaseError(error)) => return Err(error.into()), Err(ImportError::DatabaseError(error)) => return Err(error.into()),
@ -185,11 +192,22 @@ mod tests {
&request_uri, &request_uri,
&request_headers, &request_headers,
).unwrap(); ).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!( assert_eq!(
signature_data.message, signature_data.message,
"(request-target): post /user/123/inbox\nhost: example.com", "(request-target): post /user/123/inbox\nhost: example.com",
); );
assert_eq!(signature_data.signature, "test"); 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");
}
} }