mirror of
https://github.com/LemmyNet/activitypub-federation-rust.git
synced 2024-06-11 09:49:24 +00:00
(WIP)feat: add axum feature
This commit is contained in:
parent
4d8caf9a85
commit
e51cd50113
162
Cargo.lock
generated
162
Cargo.lock
generated
|
@ -11,6 +11,7 @@ dependencies = [
|
|||
"actix-web",
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"axum",
|
||||
"background-jobs",
|
||||
"base64",
|
||||
"chrono",
|
||||
|
@ -270,6 +271,68 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.6.0-rc.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b6b56b1bdef08d4d50e63683d79c797dbf2ae467586c7502b283241b5e2b8a"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"headers",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-http",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.3.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d1aa274f0599e5100cbc24e1f184d437d8086ea0bba0b5f68326e2ad5a48567"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"mime",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.3.0-rc.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2185fff4d6f14de84dcc01b0ff8eee2ac5331a962cf85e60e080ce7db724cc9"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "background-jobs"
|
||||
version = "0.13.0"
|
||||
|
@ -753,6 +816,37 @@ version = "0.12.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
version = "0.3.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"headers-core",
|
||||
"http",
|
||||
"httpdate",
|
||||
"mime",
|
||||
"sha1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers-core"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429"
|
||||
dependencies = [
|
||||
"http",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
|
@ -784,6 +878,12 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29"
|
||||
|
||||
[[package]]
|
||||
name = "http-signature-normalization"
|
||||
version = "0.6.0"
|
||||
|
@ -1031,6 +1131,12 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3dfc802da7b1cf80aefffa0c7b2f77247c8b32206cc83c270b61264f5b360a80"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
|
@ -1476,6 +1582,15 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "184c643044780f7ceb59104cef98a5a6f12cb2288a7bc701ab93a362b49fd47d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
|
@ -1570,6 +1685,12 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8"
|
||||
|
||||
[[package]]
|
||||
name = "task-local-extensions"
|
||||
version = "0.1.3"
|
||||
|
@ -1727,6 +1848,47 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c530c8675c1dbf98facee631536fa116b5fb6382d7dd6dc1b118d970eafe3ba"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-range-header",
|
||||
"pin-project-lite",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.2"
|
||||
|
|
|
@ -33,9 +33,11 @@ http-signature-normalization-reqwest = { version = "0.7.1", default-features = f
|
|||
|
||||
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-rc.5", features = ["json", "headers", "macros"], optional = true }
|
||||
|
||||
[features]
|
||||
actix = ["actix-web", "http-signature-normalization-actix"]
|
||||
actix = ["dep:actix-web", "dep:http-signature-normalization-actix"]
|
||||
axum = ["dep:axum"]
|
||||
|
||||
[dev-dependencies]
|
||||
activitystreams-kinds = "0.2.1"
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
use actix_web::ResponseError;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// Necessary because of this issue: https://github.com/actix/actix-web/issues/1711
|
||||
#[derive(Debug)]
|
||||
pub struct Error(anyhow::Error);
|
||||
|
||||
impl ResponseError for Error {}
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0, f)
|
||||
|
@ -21,3 +19,23 @@ where
|
|||
Error(t.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
mod actix {
|
||||
use crate::error::Error;
|
||||
use actix_web::ResponseError;
|
||||
|
||||
impl ResponseError for Error {}
|
||||
}
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
mod axum {
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use http::StatusCode;
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("{}", self.0)).into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::{
|
|||
person::{MyUser, PersonAcceptedActivities},
|
||||
},
|
||||
};
|
||||
|
||||
use activitypub_federation::{
|
||||
core::{inbox::receive_activity, object_id::ObjectId, signatures::generate_actor_keypair},
|
||||
data::Data,
|
||||
|
@ -16,8 +17,10 @@ use activitypub_federation::{
|
|||
UrlVerifier,
|
||||
APUB_JSON_CONTENT_TYPE,
|
||||
};
|
||||
|
||||
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer};
|
||||
use async_trait::async_trait;
|
||||
/// FIXME: actix
|
||||
use http_signature_normalization_actix::prelude::VerifyDigest;
|
||||
use reqwest::Client;
|
||||
use sha2::{Digest, Sha256};
|
||||
|
|
|
@ -137,7 +137,7 @@ async fn do_send(
|
|||
) -> Result<(), anyhow::Error> {
|
||||
debug!("Sending {} to {}", task.activity_id, task.inbox);
|
||||
let request_builder = client
|
||||
.post(&task.inbox.to_string())
|
||||
.post(task.inbox.to_string())
|
||||
.timeout(timeout)
|
||||
.headers(generate_request_headers(&task.inbox));
|
||||
let request = sign_request(
|
||||
|
|
|
@ -1,53 +1,124 @@
|
|||
use crate::{
|
||||
core::{object_id::ObjectId, signatures::verify_signature},
|
||||
data::Data,
|
||||
traits::{ActivityHandler, Actor, ApubObject},
|
||||
utils::{verify_domains_match, verify_url_valid},
|
||||
Error,
|
||||
LocalInstance,
|
||||
};
|
||||
use actix_web::{dev::Payload, FromRequest, HttpRequest, HttpResponse};
|
||||
use http_signature_normalization_actix::prelude::DigestVerified;
|
||||
use serde::de::DeserializeOwned;
|
||||
use tracing::debug;
|
||||
#[cfg(feature = "actix")]
|
||||
pub use actix_imp::receive_activity;
|
||||
|
||||
/// Receive an activity and perform some basic checks, including HTTP signature verification.
|
||||
pub async fn receive_activity<Activity, ActorT, Datatype>(
|
||||
request: HttpRequest,
|
||||
activity: Activity,
|
||||
local_instance: &LocalInstance,
|
||||
data: &Data<Datatype>,
|
||||
) -> Result<HttpResponse, <Activity as ActivityHandler>::Error>
|
||||
where
|
||||
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
|
||||
ActorT: ApubObject<DataType = Datatype> + Actor + Send + 'static,
|
||||
for<'de2> <ActorT as ApubObject>::ApubType: serde::Deserialize<'de2>,
|
||||
<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>,
|
||||
<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?;
|
||||
#[cfg(feature = "axum")]
|
||||
pub use axum_imp::receive_activity;
|
||||
|
||||
verify_domains_match(activity.id(), activity.actor())?;
|
||||
verify_url_valid(activity.id(), &local_instance.settings).await?;
|
||||
if local_instance.is_local_url(activity.id()) {
|
||||
return Err(Error::UrlVerificationError("Activity was sent from local instance").into());
|
||||
#[cfg(feature = "actix")]
|
||||
mod actix_imp {
|
||||
use crate::{
|
||||
core::{object_id::ObjectId, signatures::actix::verify_signature},
|
||||
data::Data,
|
||||
traits::{ActivityHandler, Actor, ApubObject},
|
||||
utils::{verify_domains_match, verify_url_valid},
|
||||
Error,
|
||||
LocalInstance,
|
||||
};
|
||||
|
||||
use actix_web::{dev::Payload, FromRequest, HttpRequest, HttpResponse};
|
||||
use http_signature_normalization_actix::prelude::DigestVerified;
|
||||
use serde::de::DeserializeOwned;
|
||||
use tracing::debug;
|
||||
|
||||
/// Receive an activity and perform some basic checks, including HTTP signature verification.
|
||||
pub async fn receive_activity<Activity, ActorT, Datatype>(
|
||||
request: HttpRequest,
|
||||
activity: Activity,
|
||||
local_instance: &LocalInstance,
|
||||
data: &Data<Datatype>,
|
||||
) -> Result<HttpResponse, <Activity as ActivityHandler>::Error>
|
||||
where
|
||||
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
|
||||
ActorT: ApubObject<DataType = Datatype> + Actor + Send + 'static,
|
||||
for<'de2> <ActorT as ApubObject>::ApubType: serde::Deserialize<'de2>,
|
||||
<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>,
|
||||
<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?;
|
||||
|
||||
verify_domains_match(activity.id(), activity.actor())?;
|
||||
verify_url_valid(activity.id(), &local_instance.settings).await?;
|
||||
if local_instance.is_local_url(activity.id()) {
|
||||
return Err(
|
||||
Error::UrlVerificationError("Activity was sent from local instance").into(),
|
||||
);
|
||||
}
|
||||
|
||||
let request_counter = &mut 0;
|
||||
let actor = ObjectId::<ActorT>::new(activity.actor().clone())
|
||||
.dereference(data, local_instance, request_counter)
|
||||
.await?;
|
||||
verify_signature(&request, actor.public_key())?;
|
||||
|
||||
debug!("Verifying activity {}", activity.id().to_string());
|
||||
activity.verify(data, request_counter).await?;
|
||||
|
||||
debug!("Receiving activity {}", activity.id().to_string());
|
||||
activity.receive(data, request_counter).await?;
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
mod axum_imp {
|
||||
use crate::{
|
||||
core::{object_id::ObjectId, signatures::axum::verify_signature},
|
||||
data::Data,
|
||||
traits::{ActivityHandler, Actor, ApubObject},
|
||||
utils::{verify_domains_match, verify_url_valid},
|
||||
Error,
|
||||
LocalInstance,
|
||||
};
|
||||
use axum::{http::Request, Extension, Json};
|
||||
use serde::de::DeserializeOwned;
|
||||
use tracing::debug;
|
||||
|
||||
/// Receive an activity and perform some basic checks, including HTTP signature verification.
|
||||
pub async fn receive_activity<Activity, ActorT, Datatype>(
|
||||
request: Request<Activity>,
|
||||
Json(activity): Json<Activity>,
|
||||
Extension(local_instance): Extension<&LocalInstance>,
|
||||
Extension(data): Extension<&Data<Datatype>>,
|
||||
) -> Result<(), <Activity as ActivityHandler>::Error>
|
||||
where
|
||||
Activity: ActivityHandler<DataType = Datatype> + DeserializeOwned + Send + 'static,
|
||||
ActorT: ApubObject<DataType = Datatype> + Actor + Send + 'static,
|
||||
for<'de2> <ActorT as ApubObject>::ApubType: serde::Deserialize<'de2>,
|
||||
<Activity as ActivityHandler>::Error: From<anyhow::Error>
|
||||
+ From<Error>
|
||||
+ From<<ActorT as ApubObject>::Error>
|
||||
+ From<serde_json::Error>,
|
||||
// FIXME
|
||||
// + From<http_signature_normalization_actix::digest::middleware::VerifyError>,
|
||||
<ActorT as ApubObject>::Error: From<Error> + From<anyhow::Error>,
|
||||
{
|
||||
// ensure that payload hash was checked against digest header by middleware
|
||||
/// FIXME
|
||||
// DigestVerified::from_request(&request, &mut Payload::None).await?;
|
||||
verify_domains_match(activity.id(), activity.actor())?;
|
||||
verify_url_valid(activity.id(), &local_instance.settings).await?;
|
||||
if local_instance.is_local_url(activity.id()) {
|
||||
return Err(
|
||||
Error::UrlVerificationError("Activity was sent from local instance").into(),
|
||||
);
|
||||
}
|
||||
|
||||
let request_counter = &mut 0;
|
||||
let actor = ObjectId::<ActorT>::new(activity.actor().clone())
|
||||
.dereference(data, local_instance, request_counter)
|
||||
.await?;
|
||||
verify_signature(&request, actor.public_key())?;
|
||||
|
||||
debug!("Verifying activity {}", activity.id().to_string());
|
||||
activity.verify(data, request_counter).await?;
|
||||
|
||||
debug!("Receiving activity {}", activity.id().to_string());
|
||||
activity.receive(data, request_counter).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let request_counter = &mut 0;
|
||||
let actor = ObjectId::<ActorT>::new(activity.actor().clone())
|
||||
.dereference(data, local_instance, request_counter)
|
||||
.await?;
|
||||
verify_signature(&request, actor.public_key())?;
|
||||
|
||||
debug!("Verifying activity {}", activity.id().to_string());
|
||||
activity.verify(data, request_counter).await?;
|
||||
|
||||
debug!("Receiving activity {}", activity.id().to_string());
|
||||
activity.receive(data, request_counter).await?;
|
||||
Ok(HttpResponse::Ok().finish())
|
||||
}
|
||||
|
|
|
@ -1,23 +1,13 @@
|
|||
use actix_web::HttpRequest;
|
||||
use anyhow::anyhow;
|
||||
use http_signature_normalization_actix::Config as ConfigActix;
|
||||
use http_signature_normalization_reqwest::prelude::{Config, SignExt};
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use openssl::{
|
||||
hash::MessageDigest,
|
||||
pkey::PKey,
|
||||
rsa::Rsa,
|
||||
sign::{Signer, Verifier},
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
use openssl::{hash::MessageDigest, pkey::PKey, rsa::Rsa, sign::Signer};
|
||||
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 CONFIG2: Lazy<ConfigActix> = Lazy::new(ConfigActix::new);
|
||||
static HTTP_SIG_CONFIG: OnceCell<Config> = OnceCell::new();
|
||||
|
||||
/// A private/public key pair used for HTTP signatures
|
||||
|
@ -80,33 +70,6 @@ pub(crate) async fn sign_request(
|
|||
.await
|
||||
}
|
||||
|
||||
/// 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| -> Result<bool, anyhow::Error> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct PublicKey {
|
||||
|
@ -131,3 +94,55 @@ impl PublicKey {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "actix")]
|
||||
pub mod actix {
|
||||
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| -> Result<bool, anyhow::Error> {
|
||||
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()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "axum")]
|
||||
pub mod axum {
|
||||
use axum::http::Request;
|
||||
|
||||
/// Verifies the HTTP signature on an incoming inbox request.
|
||||
pub fn verify_signature<B>(
|
||||
request: &Request<B>,
|
||||
public_key: &str,
|
||||
) -> Result<(), anyhow::Error> {
|
||||
todo!("Tower Http does not have this feature yet")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue