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::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,
};

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_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 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<String> = recipients.into_iter()

View file

@ -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);

View file

@ -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 {

View file

@ -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<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
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");
}
}