Move verify_signed_request function to activitypub::authentication module
This commit is contained in:
parent
256e225742
commit
01d3f54939
4 changed files with 86 additions and 79 deletions
78
src/activitypub/authentication.rs
Normal file
78
src/activitypub/authentication.rs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
use actix_web::HttpRequest;
|
||||||
|
use tokio_postgres::GenericClient;
|
||||||
|
|
||||||
|
use crate::config::Config;
|
||||||
|
use crate::http_signatures::verify::{
|
||||||
|
parse_http_signature,
|
||||||
|
verify_http_signature,
|
||||||
|
VerificationError,
|
||||||
|
};
|
||||||
|
use crate::models::profiles::queries::get_profile_by_remote_actor_id;
|
||||||
|
use crate::models::profiles::types::DbActorProfile;
|
||||||
|
use crate::utils::crypto::deserialize_public_key;
|
||||||
|
use super::fetcher::helpers::get_or_import_profile_by_actor_id;
|
||||||
|
use super::receiver::HandlerError;
|
||||||
|
|
||||||
|
fn key_id_to_actor_id(key_id: &str) -> Result<String, VerificationError> {
|
||||||
|
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
|
||||||
|
pub async fn verify_signed_request(
|
||||||
|
config: &Config,
|
||||||
|
db_client: &impl GenericClient,
|
||||||
|
request: &HttpRequest,
|
||||||
|
no_fetch: bool,
|
||||||
|
) -> Result<DbActorProfile, VerificationError> {
|
||||||
|
let signature_data = parse_http_signature(
|
||||||
|
request.method(),
|
||||||
|
request.uri(),
|
||||||
|
request.headers(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let actor_id = key_id_to_actor_id(&signature_data.key_id)?;
|
||||||
|
let actor_profile = if no_fetch {
|
||||||
|
get_profile_by_remote_actor_id(db_client, &actor_id).await?
|
||||||
|
} else {
|
||||||
|
match get_or_import_profile_by_actor_id(
|
||||||
|
db_client,
|
||||||
|
&config.instance(),
|
||||||
|
&config.media_dir(),
|
||||||
|
&actor_id,
|
||||||
|
).await {
|
||||||
|
Ok(profile) => profile,
|
||||||
|
Err(HandlerError::DatabaseError(error)) => return Err(error.into()),
|
||||||
|
Err(other_error) => {
|
||||||
|
return Err(VerificationError::ActorError(other_error.to_string()));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let actor = actor_profile.actor_json.as_ref()
|
||||||
|
.ok_or(VerificationError::ActorError("invalid profile".to_string()))?;
|
||||||
|
let public_key = deserialize_public_key(&actor.public_key.public_key_pem)?;
|
||||||
|
|
||||||
|
verify_http_signature(&signature_data, &public_key)?;
|
||||||
|
|
||||||
|
Ok(actor_profile)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
mod activity;
|
mod activity;
|
||||||
pub mod actors;
|
pub mod actors;
|
||||||
|
mod authentication;
|
||||||
pub mod builders;
|
pub mod builders;
|
||||||
mod collections;
|
mod collections;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
|
|
|
@ -10,11 +10,9 @@ use crate::errors::{
|
||||||
HttpError,
|
HttpError,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
};
|
};
|
||||||
use crate::http_signatures::verify::{
|
use crate::http_signatures::verify::VerificationError;
|
||||||
verify_signed_request,
|
|
||||||
VerificationError,
|
|
||||||
};
|
|
||||||
use super::activity::{Activity, Object};
|
use super::activity::{Activity, Object};
|
||||||
|
use super::authentication::verify_signed_request;
|
||||||
use super::fetcher::{
|
use super::fetcher::{
|
||||||
fetchers::FetchError,
|
fetchers::FetchError,
|
||||||
helpers::import_post,
|
helpers::import_post,
|
||||||
|
|
|
@ -1,23 +1,12 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use actix_web::{
|
use actix_web::http::{Method, Uri, header::HeaderMap};
|
||||||
HttpRequest,
|
|
||||||
http::{Method, Uri, header::HeaderMap},
|
|
||||||
};
|
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rsa::RsaPublicKey;
|
use rsa::RsaPublicKey;
|
||||||
use tokio_postgres::GenericClient;
|
|
||||||
|
|
||||||
use crate::activitypub::{
|
|
||||||
fetcher::helpers::get_or_import_profile_by_actor_id,
|
|
||||||
handlers::HandlerError,
|
|
||||||
};
|
|
||||||
use crate::config::Config;
|
|
||||||
use crate::errors::DatabaseError;
|
use crate::errors::DatabaseError;
|
||||||
use crate::models::profiles::queries::get_profile_by_remote_actor_id;
|
use crate::utils::crypto::verify_signature;
|
||||||
use crate::models::profiles::types::DbActorProfile;
|
|
||||||
use crate::utils::crypto::{deserialize_public_key, verify_signature};
|
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum VerificationError {
|
pub enum VerificationError {
|
||||||
|
@ -49,7 +38,7 @@ pub enum VerificationError {
|
||||||
InvalidSigner,
|
InvalidSigner,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct HttpSignatureData {
|
pub struct HttpSignatureData {
|
||||||
pub key_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
|
||||||
|
@ -58,7 +47,7 @@ struct HttpSignatureData {
|
||||||
|
|
||||||
const SIGNATURE_PARAMETER_RE: &str = r#"^(?P<key>[a-zA-Z]+)="(?P<value>.+)"$"#;
|
const SIGNATURE_PARAMETER_RE: &str = r#"^(?P<key>[a-zA-Z]+)="(?P<value>.+)"$"#;
|
||||||
|
|
||||||
fn parse_http_signature(
|
pub fn parse_http_signature(
|
||||||
request_method: &Method,
|
request_method: &Method,
|
||||||
request_uri: &Uri,
|
request_uri: &Uri,
|
||||||
request_headers: &HeaderMap,
|
request_headers: &HeaderMap,
|
||||||
|
@ -132,7 +121,7 @@ fn parse_http_signature(
|
||||||
Ok(signature_data)
|
Ok(signature_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_http_signature(
|
pub fn verify_http_signature(
|
||||||
signature_data: &HttpSignatureData,
|
signature_data: &HttpSignatureData,
|
||||||
signer_key: &RsaPublicKey,
|
signer_key: &RsaPublicKey,
|
||||||
) -> Result<(), VerificationError> {
|
) -> Result<(), VerificationError> {
|
||||||
|
@ -150,54 +139,6 @@ fn verify_http_signature(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
pub async fn verify_signed_request(
|
|
||||||
config: &Config,
|
|
||||||
db_client: &impl GenericClient,
|
|
||||||
request: &HttpRequest,
|
|
||||||
no_fetch: bool,
|
|
||||||
) -> Result<DbActorProfile, VerificationError> {
|
|
||||||
let signature_data = parse_http_signature(
|
|
||||||
request.method(),
|
|
||||||
request.uri(),
|
|
||||||
request.headers(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let actor_id = key_id_to_actor_id(&signature_data.key_id)?;
|
|
||||||
let actor_profile = if no_fetch {
|
|
||||||
get_profile_by_remote_actor_id(db_client, &actor_id).await?
|
|
||||||
} else {
|
|
||||||
match get_or_import_profile_by_actor_id(
|
|
||||||
db_client,
|
|
||||||
&config.instance(),
|
|
||||||
&config.media_dir(),
|
|
||||||
&actor_id,
|
|
||||||
).await {
|
|
||||||
Ok(profile) => profile,
|
|
||||||
Err(HandlerError::DatabaseError(error)) => return Err(error.into()),
|
|
||||||
Err(other_error) => {
|
|
||||||
return Err(VerificationError::ActorError(other_error.to_string()));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let actor = actor_profile.actor_json.as_ref()
|
|
||||||
.ok_or(VerificationError::ActorError("invalid profile".to_string()))?;
|
|
||||||
let public_key = deserialize_public_key(&actor.public_key.public_key_pem)?;
|
|
||||||
|
|
||||||
verify_http_signature(&signature_data, &public_key)?;
|
|
||||||
|
|
||||||
Ok(actor_profile)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use actix_web::http::{
|
use actix_web::http::{
|
||||||
|
@ -239,15 +180,4 @@ mod tests {
|
||||||
assert_eq!(signature_data.signature, "test");
|
assert_eq!(signature_data.signature, "test");
|
||||||
assert_eq!(signature_data.created_at.is_some(), false);
|
assert_eq!(signature_data.created_at.is_some(), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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