Move verify_signed_request function to activitypub::authentication module

This commit is contained in:
silverpill 2022-10-23 19:13:05 +00:00
parent 256e225742
commit 01d3f54939
4 changed files with 86 additions and 79 deletions

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

View file

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

View file

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

View file

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