Refactor http_signatures module
This commit is contained in:
parent
9d671344e6
commit
862415d49b
7 changed files with 70 additions and 53 deletions
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in a new issue