Make HTTP signature verification compatible with GoToSocial
This commit is contained in:
parent
5c0e6b0b0c
commit
379116605f
6 changed files with 35 additions and 13 deletions
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue