Refactor http_signatures module

This commit is contained in:
silverpill 2022-10-23 16:04:02 +00:00
parent 9d671344e6
commit 862415d49b
7 changed files with 70 additions and 53 deletions

View file

@ -8,7 +8,10 @@ use serde::Serialize;
use tokio::time::sleep; use tokio::time::sleep;
use crate::config::Instance; use crate::config::Instance;
use crate::http_signatures::create::{create_http_signature, SignatureError}; use crate::http_signatures::create::{
create_http_signature,
HttpSignatureError,
};
use crate::models::users::types::User; use crate::models::users::types::User;
use crate::utils::crypto::deserialize_private_key; use crate::utils::crypto::deserialize_private_key;
use crate::utils::urls::get_hostname; use crate::utils::urls::get_hostname;
@ -22,7 +25,7 @@ pub enum DelivererError {
KeyDeserializationError(#[from] rsa::pkcs8::Error), KeyDeserializationError(#[from] rsa::pkcs8::Error),
#[error(transparent)] #[error(transparent)]
SignatureError(#[from] SignatureError), HttpSignatureError(#[from] HttpSignatureError),
#[error("activity serialization error")] #[error("activity serialization error")]
SerializationError(#[from] serde_json::Error), SerializationError(#[from] serde_json::Error),

View file

@ -8,7 +8,10 @@ use crate::activitypub::activity::Object;
use crate::activitypub::actors::types::{Actor, ActorAddress}; use crate::activitypub::actors::types::{Actor, ActorAddress};
use crate::activitypub::constants::AP_MEDIA_TYPE; use crate::activitypub::constants::AP_MEDIA_TYPE;
use crate::config::Instance; use crate::config::Instance;
use crate::http_signatures::create::{create_http_signature, SignatureError}; use crate::http_signatures::create::{
create_http_signature,
HttpSignatureError,
};
use crate::utils::files::save_file; use crate::utils::files::save_file;
use crate::utils::urls::guess_protocol; use crate::utils::urls::guess_protocol;
use crate::webfinger::types::JsonResourceDescriptor; use crate::webfinger::types::JsonResourceDescriptor;
@ -18,7 +21,7 @@ const FETCHER_CONNECTION_TIMEOUT: u64 = 30;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum FetchError { pub enum FetchError {
#[error(transparent)] #[error(transparent)]
SignatureError(#[from] SignatureError), SignatureError(#[from] HttpSignatureError),
#[error(transparent)] #[error(transparent)]
RequestError(#[from] reqwest::Error), RequestError(#[from] reqwest::Error),

View file

@ -5,7 +5,7 @@ use tokio_postgres::GenericClient;
use crate::config::Config; use crate::config::Config;
use crate::errors::{ConversionError, HttpError, ValidationError}; use crate::errors::{ConversionError, HttpError, ValidationError};
use crate::http_signatures::verify::verify_http_signature; use crate::http_signatures::verify::verify_signed_request;
use super::activity::{Activity, Object}; use super::activity::{Activity, Object};
use super::fetcher::helpers::import_post; use super::fetcher::helpers::import_post;
use super::handlers::{ use super::handlers::{
@ -119,7 +119,7 @@ pub async fn receive_activity(
activity.actor == object_id activity.actor == object_id
} else { false }; } else { false };
// Don't fetch signer if this is Delete(Person) activity // Don't fetch signer if this is Delete(Person) activity
let signer = match verify_http_signature(config, db_client, request, is_self_delete).await { let signer = match verify_signed_request(config, db_client, request, is_self_delete).await {
Ok(signer) => signer, Ok(signer) => signer,
Err(error) => { Err(error) => {
if is_self_delete { if is_self_delete {

View file

@ -6,7 +6,7 @@ use crate::utils::crypto::{sign_message, get_message_digest};
pub const SIGNATURE_ALGORITHM: &str = "rsa-sha256"; pub const SIGNATURE_ALGORITHM: &str = "rsa-sha256";
pub struct SignatureHeaders { pub struct HttpSignatureHeaders {
pub host: String, pub host: String,
pub date: String, pub date: String,
pub digest: Option<String>, pub digest: Option<String>,
@ -14,12 +14,12 @@ pub struct SignatureHeaders {
} }
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum SignatureError { pub enum HttpSignatureError {
#[error("invalid request url")] #[error("invalid request url")]
UrlError, UrlError(#[from] url::ParseError),
#[error("signature error")] #[error("signing error")]
SignatureError(#[from] rsa::errors::Error), SigningError(#[from] rsa::errors::Error),
} }
/// Creates HTTP signature according to the old HTTP Signatures Spec: /// Creates HTTP signature according to the old HTTP Signatures Spec:
@ -28,24 +28,27 @@ pub fn create_http_signature(
request_method: Method, request_method: Method,
request_url: &str, request_url: &str,
request_body: &str, request_body: &str,
actor_key: &RsaPrivateKey, signer_key: &RsaPrivateKey,
actor_key_id: &str, signer_key_id: &str,
) -> Result<SignatureHeaders, SignatureError> { ) -> Result<HttpSignatureHeaders, HttpSignatureError> {
let request_url_object = url::Url::parse(request_url) let request_url_object = url::Url::parse(request_url)?;
.map_err(|_| SignatureError::UrlError)?;
let request_target = format!( let request_target = format!(
"{} {}", "{} {}",
request_method.as_str().to_lowercase(), request_method.as_str().to_lowercase(),
request_url_object.path(), request_url_object.path(),
); );
let host = request_url_object.host_str() let host = request_url_object.host_str()
.ok_or(SignatureError::UrlError)? .ok_or(url::ParseError::EmptyHost)?
.to_string(); .to_string();
let date = Utc::now().format("%a, %d %b %Y %T GMT").to_string(); let date = Utc::now().format("%a, %d %b %Y %T GMT").to_string();
let digest = if request_body.is_empty() { let maybe_digest = if request_body.is_empty() {
None None
} else { } else {
Some(get_message_digest(request_body)) let digest = format!(
"SHA-256={}",
get_message_digest(request_body),
);
Some(digest)
}; };
let mut headers = vec![ let mut headers = vec![
@ -53,7 +56,7 @@ pub fn create_http_signature(
("host", &host), ("host", &host),
("date", &date), ("date", &date),
]; ];
if let Some(ref digest) = digest { if let Some(ref digest) = maybe_digest {
headers.push(("digest", digest)); headers.push(("digest", digest));
}; };
@ -63,18 +66,18 @@ pub fn create_http_signature(
let headers_parameter = headers.iter() let headers_parameter = headers.iter()
.map(|(name, _)| name.to_string()) .map(|(name, _)| name.to_string())
.collect::<Vec<String>>().join(" "); .collect::<Vec<String>>().join(" ");
let signature_parameter = sign_message(actor_key, &message)?; let signature_parameter = sign_message(signer_key, &message)?;
let signature_header = format!( let signature_header = format!(
r#"keyId="{}",algorithm="{}",headers="{}",signature="{}""#, r#"keyId="{}",algorithm="{}",headers="{}",signature="{}""#,
actor_key_id, signer_key_id,
SIGNATURE_ALGORITHM, SIGNATURE_ALGORITHM,
headers_parameter, headers_parameter,
signature_parameter, signature_parameter,
); );
let headers = SignatureHeaders { let headers = HttpSignatureHeaders {
host, host,
date, date,
digest, digest: maybe_digest,
signature: signature_header, signature: signature_header,
}; };
Ok(headers) Ok(headers)
@ -88,15 +91,15 @@ mod tests {
#[test] #[test]
fn test_create_signature_get() { fn test_create_signature_get() {
let request_url = "https://example.org/inbox"; let request_url = "https://example.org/inbox";
let actor_key = generate_weak_private_key().unwrap(); let signer_key = generate_weak_private_key().unwrap();
let actor_key_id = "https://myserver.org/actor#main-key"; let signer_key_id = "https://myserver.org/actor#main-key";
let headers = create_http_signature( let headers = create_http_signature(
Method::GET, Method::GET,
request_url, request_url,
"", "",
&actor_key, &signer_key,
actor_key_id, signer_key_id,
).unwrap(); ).unwrap();
assert_eq!(headers.host, "example.org"); assert_eq!(headers.host, "example.org");
@ -117,15 +120,15 @@ mod tests {
fn test_create_signature_post() { fn test_create_signature_post() {
let request_url = "https://example.org/inbox"; let request_url = "https://example.org/inbox";
let request_body = "{}"; let request_body = "{}";
let actor_key = generate_weak_private_key().unwrap(); let signer_key = generate_weak_private_key().unwrap();
let actor_key_id = "https://myserver.org/actor#main-key"; let signer_key_id = "https://myserver.org/actor#main-key";
let result = create_http_signature( let result = create_http_signature(
Method::POST, Method::POST,
request_url, request_url,
request_body, request_body,
&actor_key, &signer_key,
actor_key_id, signer_key_id,
); );
assert_eq!(result.is_ok(), true); assert_eq!(result.is_ok(), true);

View file

@ -6,6 +6,7 @@ use actix_web::{
}; };
use chrono::{DateTime, TimeZone, Utc}; use chrono::{DateTime, TimeZone, Utc};
use regex::Regex; use regex::Regex;
use rsa::RsaPublicKey;
use tokio_postgres::GenericClient; use tokio_postgres::GenericClient;
use crate::activitypub::fetcher::helpers::{ use crate::activitypub::fetcher::helpers::{
@ -45,7 +46,7 @@ pub enum VerificationError {
InvalidSignature, InvalidSignature,
} }
struct SignatureData { 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 +59,7 @@ fn parse_http_signature(
request_method: &Method, request_method: &Method,
request_uri: &Uri, request_uri: &Uri,
request_headers: &HeaderMap, request_headers: &HeaderMap,
) -> Result<SignatureData, VerificationError> { ) -> Result<HttpSignatureData, VerificationError> {
let signature_header = request_headers.get("signature") let signature_header = request_headers.get("signature")
.ok_or(VerificationError::HeaderError("missing signature header"))? .ok_or(VerificationError::HeaderError("missing signature header"))?
.to_str() .to_str()
@ -119,7 +120,7 @@ fn parse_http_signature(
}; };
let message = message_parts.join("\n"); let message = message_parts.join("\n");
let signature_data = SignatureData { let signature_data = HttpSignatureData {
key_id, key_id,
message, message,
signature, signature,
@ -128,6 +129,24 @@ fn parse_http_signature(
Ok(signature_data) Ok(signature_data)
} }
fn verify_http_signature(
signature_data: &HttpSignatureData,
signer_key: &RsaPublicKey,
) -> Result<(), VerificationError> {
if signature_data.created_at.is_none() {
log::warn!("signature creation time is missing");
};
let is_valid_signature = verify_signature(
signer_key,
&signature_data.message,
&signature_data.signature,
)?;
if !is_valid_signature {
return Err(VerificationError::InvalidSignature);
};
Ok(())
}
fn key_id_to_actor_id(key_id: &str) -> Result<String, url::ParseError> { fn key_id_to_actor_id(key_id: &str) -> Result<String, url::ParseError> {
let key_url = url::Url::parse(key_id)?; let key_url = url::Url::parse(key_id)?;
// Strip #main-key (works with most AP servers) // Strip #main-key (works with most AP servers)
@ -138,7 +157,7 @@ fn key_id_to_actor_id(key_id: &str) -> Result<String, url::ParseError> {
} }
/// Verifies HTTP signature and returns signer /// Verifies HTTP signature and returns signer
pub async fn verify_http_signature( pub async fn verify_signed_request(
config: &Config, config: &Config,
db_client: &impl GenericClient, db_client: &impl GenericClient,
request: &HttpRequest, request: &HttpRequest,
@ -149,9 +168,6 @@ pub async fn verify_http_signature(
request.uri(), request.uri(),
request.headers(), request.headers(),
)?; )?;
if signature_data.created_at.is_none() {
log::warn!("signature creation time is missing");
};
let actor_id = key_id_to_actor_id(&signature_data.key_id)?; let actor_id = key_id_to_actor_id(&signature_data.key_id)?;
let actor_profile = if no_fetch { let actor_profile = if no_fetch {
@ -172,16 +188,10 @@ pub async fn verify_http_signature(
}; };
let actor = actor_profile.actor_json.as_ref() let actor = actor_profile.actor_json.as_ref()
.ok_or(VerificationError::ActorError("invalid profile".to_string()))?; .ok_or(VerificationError::ActorError("invalid profile".to_string()))?;
let public_key = deserialize_public_key(&actor.public_key.public_key_pem)?; let public_key = deserialize_public_key(&actor.public_key.public_key_pem)?;
let is_valid_signature = verify_signature(
&public_key, verify_http_signature(&signature_data, &public_key)?;
&signature_data.message,
&signature_data.signature,
)?;
if !is_valid_signature {
return Err(VerificationError::InvalidSignature);
};
Ok(actor_profile) Ok(actor_profile)
} }

View file

@ -147,10 +147,8 @@ async fn create_status(
linked.push(post); linked.push(post);
}; };
}; };
if post_data.links.len() > 0 { if post_data.links.len() > 0 && post_data.visibility != Visibility::Public {
if post_data.visibility != Visibility::Public { return Err(ValidationError("can't add links to non-public posts").into());
return Err(ValidationError("can't add links to non-public posts").into());
};
}; };
if post_data.links.len() > 3 { if post_data.links.len() > 3 {
return Err(ValidationError("too many links").into()); return Err(ValidationError("too many links").into());

View file

@ -78,7 +78,7 @@ pub fn sign_message(
pub fn get_message_digest(message: &str) -> String { pub fn get_message_digest(message: &str) -> String {
let digest = Sha256::digest(message.as_bytes()); let digest = Sha256::digest(message.as_bytes());
let digest_b64 = base64::encode(digest); let digest_b64 = base64::encode(digest);
format!("SHA-256={}", digest_b64) digest_b64
} }
pub fn verify_signature( pub fn verify_signature(