mirror of
https://github.com/LemmyNet/activitypub-federation-rust.git
synced 2024-06-10 17:29:34 +00:00
refactor: update according to PR comment and factorize 'verify_digest'
This commit is contained in:
parent
605d8cc60b
commit
d01b55f5d2
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -21,7 +21,6 @@ dependencies = [
|
|||
"env_logger",
|
||||
"http",
|
||||
"http-signature-normalization",
|
||||
"http-signature-normalization-actix",
|
||||
"http-signature-normalization-reqwest",
|
||||
"httpdate",
|
||||
"hyper",
|
||||
|
@ -900,26 +899,6 @@ dependencies = [
|
|||
"httpdate",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-signature-normalization-actix"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86dfd54a1764ad79376b8dbf29e5bf918a463eb5ec66c90cd0388508289af6f0"
|
||||
dependencies = [
|
||||
"actix-http",
|
||||
"actix-rt",
|
||||
"actix-web",
|
||||
"base64",
|
||||
"futures-util",
|
||||
"http-signature-normalization",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-error",
|
||||
"tracing-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-signature-normalization-reqwest"
|
||||
version = "0.7.1"
|
||||
|
@ -1978,16 +1957,6 @@ dependencies = [
|
|||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-futures"
|
||||
version = "0.2.5"
|
||||
|
|
|
@ -34,7 +34,6 @@ http-signature-normalization = "0.6.0"
|
|||
actix-rt = { version = "2.7.0" }
|
||||
|
||||
actix-web = { version = "4.2.1", default-features = false, optional = true }
|
||||
http-signature-normalization-actix = { version = "0.6.1", default-features = false, features = ["server", "sha-2"], optional = true }
|
||||
axum = { version = "0.6.0", features = ["json", "headers", "macros", "original-uri"], optional = true }
|
||||
|
||||
# Axum
|
||||
|
@ -45,7 +44,7 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = tr
|
|||
|
||||
[features]
|
||||
default = ["actix"]
|
||||
actix = ["dep:actix-web", "dep:http-signature-normalization-actix"]
|
||||
actix = ["dep:actix-web"]
|
||||
axum = [
|
||||
"dep:axum",
|
||||
"dep:tower-http",
|
||||
|
|
|
@ -24,9 +24,7 @@ use activitypub_federation::{
|
|||
|
||||
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
|
||||
use async_trait::async_trait;
|
||||
use http_signature_normalization_actix::prelude::VerifyDigest;
|
||||
use reqwest::Client;
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::{
|
||||
ops::Deref,
|
||||
sync::{Arc, Mutex},
|
||||
|
@ -94,9 +92,6 @@ impl Instance {
|
|||
.route("/objects/{user_name}", web::get().to(http_get_user))
|
||||
.service(
|
||||
web::scope("")
|
||||
// Important: this ensures that the activity json matches the hashsum in signed
|
||||
// HTTP header
|
||||
.wrap(VerifyDigest::new(Sha256::new()))
|
||||
// Just a single, global inbox for simplicity
|
||||
.route("/inbox", web::post().to(http_post_user_inbox)),
|
||||
)
|
||||
|
|
|
@ -6,9 +6,8 @@ use crate::{
|
|||
LocalInstance,
|
||||
};
|
||||
|
||||
use crate::core::actix::signature::verify_signature;
|
||||
use actix_web::{dev::Payload, FromRequest, HttpRequest, HttpResponse};
|
||||
use http_signature_normalization_actix::prelude::DigestVerified;
|
||||
use crate::core::signatures::verify_signature;
|
||||
use actix_web::{HttpRequest, HttpResponse};
|
||||
use serde::de::DeserializeOwned;
|
||||
use tracing::debug;
|
||||
|
||||
|
@ -26,12 +25,9 @@ where
|
|||
<Activity as ActivityHandler>::Error: From<anyhow::Error>
|
||||
+ From<Error>
|
||||
+ From<<ActorT as ApubObject>::Error>
|
||||
+ From<serde_json::Error>
|
||||
+ From<http_signature_normalization_actix::digest::middleware::VerifyError>,
|
||||
+ From<serde_json::Error>,
|
||||
<ActorT as ApubObject>::Error: From<Error> + From<anyhow::Error>,
|
||||
{
|
||||
// ensure that payload hash was checked against digest header by middleware
|
||||
DigestVerified::from_request(&request, &mut Payload::None).await?;
|
||||
local_instance.verify_url_and_domain(&activity).await?;
|
||||
|
||||
let request_counter = &mut 0;
|
||||
|
@ -39,7 +35,12 @@ where
|
|||
.dereference(data, local_instance, request_counter)
|
||||
.await?;
|
||||
|
||||
verify_signature(&request, actor.public_key())?;
|
||||
verify_signature(
|
||||
request.headers(),
|
||||
request.method(),
|
||||
request.uri(),
|
||||
actor.public_key(),
|
||||
)?;
|
||||
|
||||
debug!("Verifying activity {}", activity.id().to_string());
|
||||
activity.verify(data, request_counter).await?;
|
||||
|
|
|
@ -1,2 +1 @@
|
|||
pub mod inbox;
|
||||
mod signature;
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
use actix_web::HttpRequest;
|
||||
use anyhow::anyhow;
|
||||
use http_signature_normalization_actix::Config as ConfigActix;
|
||||
use once_cell::sync::Lazy;
|
||||
use openssl::{hash::MessageDigest, pkey::PKey, sign::Verifier};
|
||||
use tracing::debug;
|
||||
|
||||
static CONFIG2: Lazy<ConfigActix> = Lazy::new(ConfigActix::new);
|
||||
|
||||
/// Verifies the HTTP signature on an incoming inbox request.
|
||||
pub fn verify_signature(request: &HttpRequest, public_key: &str) -> Result<(), anyhow::Error> {
|
||||
let verified = CONFIG2
|
||||
.begin_verify(
|
||||
request.method(),
|
||||
request.uri().path_and_query(),
|
||||
request.headers().clone(),
|
||||
)?
|
||||
.verify(|signature, signing_string| -> anyhow::Result<bool> {
|
||||
debug!(
|
||||
"Verifying with key {}, message {}",
|
||||
&public_key, &signing_string
|
||||
);
|
||||
let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
|
||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?;
|
||||
verifier.update(signing_string.as_bytes())?;
|
||||
Ok(verifier.verify(&base64::decode(signature)?)?)
|
||||
})?;
|
||||
|
||||
if verified {
|
||||
debug!("verified signature for {}", &request.uri());
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("Invalid signature on request: {}", &request.uri()))
|
||||
}
|
||||
}
|
|
@ -1,8 +1,5 @@
|
|||
use crate::{
|
||||
core::{
|
||||
axum::{signature::verify_signature, DigestVerified},
|
||||
object_id::ObjectId,
|
||||
},
|
||||
core::{axum::DigestVerified, object_id::ObjectId, signatures::verify_signature},
|
||||
data::Data,
|
||||
traits::{ActivityHandler, Actor, ApubObject},
|
||||
Error,
|
||||
|
@ -39,7 +36,7 @@ where
|
|||
.dereference(data, local_instance, request_counter)
|
||||
.await?;
|
||||
|
||||
verify_signature(&headers, method, uri, actor.public_key())?;
|
||||
verify_signature(&headers, &method, &uri, actor.public_key())?;
|
||||
|
||||
debug!("Verifying activity {}", activity.id().to_string());
|
||||
activity.verify(data, request_counter).await?;
|
||||
|
|
|
@ -11,7 +11,6 @@ use digest::{verify_sha256, DigestPart};
|
|||
mod digest;
|
||||
pub mod inbox;
|
||||
pub mod json;
|
||||
mod signature;
|
||||
|
||||
/// A request guard to ensure digest has been verified request has been
|
||||
/// see [`receive_activity`]
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
use anyhow::anyhow;
|
||||
use http::{uri::PathAndQuery, HeaderMap, Method, Uri};
|
||||
use http_signature_normalization::Config;
|
||||
use openssl::{hash::MessageDigest, pkey::PKey, sign::Verifier};
|
||||
use std::collections::BTreeMap;
|
||||
use tracing::debug;
|
||||
|
||||
/// Verifies the HTTP signature on an incoming inbox request.
|
||||
pub fn verify_signature(
|
||||
headers: &HeaderMap,
|
||||
method: Method,
|
||||
uri: Uri,
|
||||
public_key: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
let config = Config::default();
|
||||
let mut header_map = BTreeMap::new();
|
||||
for (name, value) in headers {
|
||||
if let Ok(value) = value.to_str() {
|
||||
header_map.insert(name.to_string(), value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
let path_and_query = uri.path_and_query().map(PathAndQuery::as_str).unwrap_or("");
|
||||
|
||||
let verified = config
|
||||
.begin_verify(method.as_str(), path_and_query, header_map)?
|
||||
.verify(|signature, signing_string| -> anyhow::Result<bool> {
|
||||
debug!(
|
||||
"Verifying with key {}, message {}",
|
||||
&public_key, &signing_string
|
||||
);
|
||||
let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
|
||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?;
|
||||
verifier.update(signing_string.as_bytes())?;
|
||||
Ok(verifier.verify(&base64::decode(signature)?)?)
|
||||
})?;
|
||||
|
||||
if verified {
|
||||
debug!("verified signature for {}", uri);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("Invalid signature on request: {}", uri))
|
||||
}
|
||||
}
|
|
@ -1,11 +1,20 @@
|
|||
use crate::utils::header_to_map;
|
||||
use anyhow::anyhow;
|
||||
use http::{header::HeaderName, uri::PathAndQuery, HeaderValue, Method, Uri};
|
||||
use http_signature_normalization_reqwest::prelude::{Config, SignExt};
|
||||
use once_cell::sync::OnceCell;
|
||||
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa, sign::Signer};
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
pkey::PKey,
|
||||
rsa::Rsa,
|
||||
sign::{Signer, Verifier},
|
||||
};
|
||||
use reqwest::Request;
|
||||
use reqwest_middleware::RequestBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::io::{Error, ErrorKind};
|
||||
use tracing::debug;
|
||||
use url::Url;
|
||||
|
||||
static HTTP_SIG_CONFIG: OnceCell<Config> = OnceCell::new();
|
||||
|
@ -94,3 +103,40 @@ impl PublicKey {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
static CONFIG2: Lazy<http_signature_normalization::Config> =
|
||||
Lazy::new(http_signature_normalization::Config::new);
|
||||
|
||||
/// Verifies the HTTP signature on an incoming inbox request.
|
||||
pub fn verify_signature<'a, H>(
|
||||
headers: H,
|
||||
method: &Method,
|
||||
uri: &Uri,
|
||||
public_key: &str,
|
||||
) -> Result<(), anyhow::Error>
|
||||
where
|
||||
H: IntoIterator<Item = (&'a HeaderName, &'a HeaderValue)>,
|
||||
{
|
||||
let headers = header_to_map(headers);
|
||||
let path_and_query = uri.path_and_query().map(PathAndQuery::as_str).unwrap_or("");
|
||||
|
||||
let verified = CONFIG2
|
||||
.begin_verify(method.as_str(), path_and_query, headers)?
|
||||
.verify(|signature, signing_string| -> anyhow::Result<bool> {
|
||||
debug!(
|
||||
"Verifying with key {}, message {}",
|
||||
&public_key, &signing_string
|
||||
);
|
||||
let public_key = PKey::public_key_from_pem(public_key.as_bytes())?;
|
||||
let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key)?;
|
||||
verifier.update(signing_string.as_bytes())?;
|
||||
Ok(verifier.verify(&base64::decode(signature)?)?)
|
||||
})?;
|
||||
|
||||
if verified {
|
||||
debug!("verified signature for {}", uri);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(anyhow!("Invalid signature on request: {}", uri))
|
||||
}
|
||||
}
|
||||
|
|
0
src/signature.rs
Normal file
0
src/signature.rs
Normal file
19
src/utils.rs
19
src/utils.rs
|
@ -1,6 +1,7 @@
|
|||
use crate::{Error, LocalInstance, APUB_JSON_CONTENT_TYPE};
|
||||
use http::StatusCode;
|
||||
use http::{header::HeaderName, HeaderValue, StatusCode};
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::collections::BTreeMap;
|
||||
use tracing::info;
|
||||
use url::Url;
|
||||
|
||||
|
@ -50,3 +51,19 @@ pub fn verify_urls_match(a: &Url, b: &Url) -> Result<(), Error> {
|
|||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Utility to converts either actix or axum headermap to a BTreeMap
|
||||
pub fn header_to_map<'a, H>(headers: H) -> BTreeMap<String, String>
|
||||
where
|
||||
H: IntoIterator<Item = (&'a HeaderName, &'a HeaderValue)>,
|
||||
{
|
||||
let mut header_map = BTreeMap::new();
|
||||
|
||||
for (name, value) in headers {
|
||||
if let Ok(value) = value.to_str() {
|
||||
header_map.insert(name.to_string(), value.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
header_map
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue