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 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::utils::crypto::deserialize_private_key;
use crate::utils::urls::get_hostname;
@ -22,7 +25,7 @@ pub enum DelivererError {
KeyDeserializationError(#[from] rsa::pkcs8::Error),
#[error(transparent)]
SignatureError(#[from] SignatureError),
HttpSignatureError(#[from] HttpSignatureError),
#[error("activity serialization 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::constants::AP_MEDIA_TYPE;
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::urls::guess_protocol;
use crate::webfinger::types::JsonResourceDescriptor;
@ -18,7 +21,7 @@ const FETCHER_CONNECTION_TIMEOUT: u64 = 30;
#[derive(thiserror::Error, Debug)]
pub enum FetchError {
#[error(transparent)]
SignatureError(#[from] SignatureError),
SignatureError(#[from] HttpSignatureError),
#[error(transparent)]
RequestError(#[from] reqwest::Error),

View file

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

View file

@ -6,6 +6,7 @@ use actix_web::{
};
use chrono::{DateTime, TimeZone, Utc};
use regex::Regex;
use rsa::RsaPublicKey;
use tokio_postgres::GenericClient;
use crate::activitypub::fetcher::helpers::{
@ -45,7 +46,7 @@ pub enum VerificationError {
InvalidSignature,
}
struct SignatureData {
struct HttpSignatureData {
pub key_id: String,
pub message: String, // reconstructed message
pub signature: String, // base64-encoded signature
@ -58,7 +59,7 @@ fn parse_http_signature(
request_method: &Method,
request_uri: &Uri,
request_headers: &HeaderMap,
) -> Result<SignatureData, VerificationError> {
) -> Result<HttpSignatureData, VerificationError> {
let signature_header = request_headers.get("signature")
.ok_or(VerificationError::HeaderError("missing signature header"))?
.to_str()
@ -119,7 +120,7 @@ fn parse_http_signature(
};
let message = message_parts.join("\n");
let signature_data = SignatureData {
let signature_data = HttpSignatureData {
key_id,
message,
signature,
@ -128,6 +129,24 @@ fn parse_http_signature(
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> {
let key_url = url::Url::parse(key_id)?;
// 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
pub async fn verify_http_signature(
pub async fn verify_signed_request(
config: &Config,
db_client: &impl GenericClient,
request: &HttpRequest,
@ -149,9 +168,6 @@ pub async fn verify_http_signature(
request.uri(),
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_profile = if no_fetch {
@ -172,16 +188,10 @@ pub async fn verify_http_signature(
};
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)?;
let is_valid_signature = verify_signature(
&public_key,
&signature_data.message,
&signature_data.signature,
)?;
if !is_valid_signature {
return Err(VerificationError::InvalidSignature);
};
verify_http_signature(&signature_data, &public_key)?;
Ok(actor_profile)
}

View file

@ -147,10 +147,8 @@ async fn create_status(
linked.push(post);
};
};
if post_data.links.len() > 0 {
if post_data.visibility != Visibility::Public {
return Err(ValidationError("can't add links to non-public posts").into());
};
if post_data.links.len() > 0 && post_data.visibility != Visibility::Public {
return Err(ValidationError("can't add links to non-public posts").into());
};
if post_data.links.len() > 3 {
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 {
let digest = Sha256::digest(message.as_bytes());
let digest_b64 = base64::encode(digest);
format!("SHA-256={}", digest_b64)
digest_b64
}
pub fn verify_signature(