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::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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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";
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue