#![deny(missing_docs)]
use actix_web::{
error::BlockingError,
http::Uri,
web::{self, ServiceConfig},
FromRequest, HttpRequest, HttpResponse, ResponseError,
};
use apub_core::{
digest::{Digest, DigestBuilder, DigestFactory},
ingest::{is_local, Authority, Ingest, IngestFactory},
repo::{Dereference, Repo, RepoFactory},
session::SessionFactory,
signature::{PrivateKeyBuilder, Verify, VerifyBuilder, VerifyFactory},
};
use apub_privatekey::{PrivateKeyRepo, PrivateKeyRepoFactory};
use apub_publickey::{PublicKeyClient, PublicKeyError, PublicKeyRepo, PublicKeyRepoFactory};
use http_signature_normalization_actix::{
digest::DigestName,
prelude::{
Algorithm, DeprecatedAlgorithm, DigestPart, DigestVerify, SignatureVerified,
SignatureVerify, VerifyDigest, VerifySignature,
},
};
use std::{future::Future, marker::PhantomData, pin::Pin};
use url::{Host, Url};
#[derive(Clone, Debug)]
pub struct Config {
pub local_host: Host<String>,
pub local_port: Option<u16>,
pub scheme: String,
pub server_actor_id: Url,
}
impl Config {
fn build_uri(&self, uri: &Uri) -> String {
if let Some(port) = self.local_port {
format!("{}{}:{}{}", self.scheme, self.local_host, port, uri)
} else {
format!("{}{}{}", self.scheme, self.local_host, uri)
}
}
pub fn is_local(&self, url: &Url) -> bool {
is_local(&self.local_host, self.local_port, url)
}
}
pub use http_signature_normalization_actix::Config as SignatureConfig;
#[derive(Debug, thiserror::Error)]
pub enum VerifyError {
#[error("Unsupported algorithm: {0}")]
Algorithm(String),
#[error("Invalid Key ID: {0}")]
KeyId(String),
#[error("Actor {0} is not public key's owner")]
InvalidOwner(Url),
#[error("Public Key {0} is not the expected key")]
InvalidKey(Url),
#[error("No key associated with Key ID")]
KeyNotFound,
#[error("Key verification panicked")]
Canceled,
}
type RFactErr<R> = <<R as RepoFactory>::Repo as Repo>::Error;
type PubKeyRepoErr<P> = <<P as PublicKeyRepoFactory>::PublicKeyRepo as PublicKeyRepo>::Error;
type VFactErr<V> = <<V as VerifyFactory>::Verify as Verify>::Error;
type PrivKeyRepoErr<P> = <<P as PrivateKeyRepoFactory>::PrivateKeyRepo as PrivateKeyRepo>::Error;
type PrivKeyError<R> = <<R as RepoFactory>::Crypto as PrivateKeyBuilder>::Error;
pub fn inbox<A, I, V, E>(
config: Config,
ingest_factory: I,
verifier: V,
signature_config: SignatureConfig,
require_signature: bool,
) -> impl FnOnce(&mut ServiceConfig)
where
A: for<'de> serde::de::Deserialize<'de> + 'static,
I: IngestFactory<A> + PrivateKeyRepoFactory + RepoFactory + SessionFactory + 'static,
I::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = I::Crypto>,
<I::Ingest as Ingest<A>>::ActorId: FromRequest + AsRef<Url>,
<I::Ingest as Ingest<A>>::Error: From<<I::Repo as Repo>::Error>,
V: RepoFactory
+ SessionFactory
+ PublicKeyRepoFactory
+ PrivateKeyRepoFactory
+ VerifyFactory
+ DigestFactory
+ Clone
+ 'static,
V::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = V::Crypto>,
V::Crypto: PrivateKeyBuilder,
<V as DigestFactory>::Digest: Clone,
E: ResponseError
+ From<VerifyError>
+ From<VFactErr<V>>
+ From<PublicKeyError<PubKeyRepoErr<V>, RFactErr<V>>>
+ From<PrivKeyRepoErr<V>>
+ From<PrivKeyError<V>>
+ 'static,
{
move |service_config: &mut ServiceConfig| {
let signature = VerifySignature::new(
VerifyMiddleware::<V, E>::new(verifier, config),
signature_config,
);
let digest = VerifyDigest::new(DigestWrapper(V::Digest::build()));
let (signature, digest) = if require_signature {
(signature, digest)
} else {
(signature.optional(), digest.optional())
};
service_config.service(
web::scope("")
.app_data(web::Data::new(ingest_factory))
.wrap(digest)
.wrap(signature)
.route("", web::post().to(inbox_handler::<A, I>)),
);
}
}
pub fn serve_objects<D, R, V, E>(
config: Config,
repo: R,
verifier: V,
signature_config: SignatureConfig,
require_signature: bool,
) -> impl FnOnce(&mut ServiceConfig)
where
D: Dereference + From<Url> + 'static,
<D as Dereference>::Output: serde::ser::Serialize,
R: Repo + 'static,
V: RepoFactory
+ SessionFactory
+ PublicKeyRepoFactory
+ PrivateKeyRepoFactory
+ VerifyFactory
+ Clone
+ 'static,
V::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = V::Crypto>,
V::Crypto: PrivateKeyBuilder,
E: ResponseError
+ From<VerifyError>
+ From<VFactErr<V>>
+ From<PublicKeyError<PubKeyRepoErr<V>, RFactErr<V>>>
+ From<PrivKeyRepoErr<V>>
+ From<PrivKeyError<V>>
+ 'static,
{
move |service_config: &mut ServiceConfig| {
let verifier = VerifySignature::new(
VerifyMiddleware::<V, E>::new(verifier, config.clone()),
signature_config,
);
let verifier = if require_signature {
verifier
} else {
verifier.optional()
};
service_config.service(
web::scope("/{object}")
.app_data(web::Data::new(repo))
.app_data(web::Data::new(config))
.wrap(verifier)
.route("", web::get().to(serve_object_handler::<D, R>)),
);
}
}
async fn inbox_handler<A, I>(
ingest_factory: web::Data<I>,
authority: Option<SignatureVerified>,
activity: web::Json<A>,
actor_id: <I::Ingest as Ingest<A>>::ActorId,
) -> HttpResponse
where
A: for<'de> serde::de::Deserialize<'de> + 'static,
I: IngestFactory<A> + PrivateKeyRepoFactory + RepoFactory + SessionFactory,
I::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = I::Crypto>,
<I::Ingest as Ingest<A>>::ActorId: FromRequest + AsRef<Url>,
<I::Ingest as Ingest<A>>::Error: From<<I::Repo as Repo>::Error>,
{
let url = actor_id.as_ref();
let private_key_repo = ingest_factory.build_private_key_repo();
let private_key = match private_key_repo.fetch(url).await {
Ok(private_key) => private_key,
Err(_) => return HttpResponse::BadRequest().finish(),
};
let activity = activity.into_inner();
let remote_repo = ingest_factory.build_repo(private_key);
let mut session = ingest_factory.build_session();
let ingest = ingest_factory.build_ingest();
if let Some(auth) = authority {
if let Ok(url) = auth.key_id().parse() {
if ingest
.ingest(
Authority::Actor(url),
actor_id,
&activity,
&remote_repo,
&mut session,
)
.await
.is_ok()
{
return HttpResponse::Accepted().finish();
}
}
} else if ingest
.ingest(
Authority::None,
actor_id,
&activity,
&remote_repo,
&mut session,
)
.await
.is_ok()
{
return HttpResponse::Accepted().finish();
}
HttpResponse::BadRequest().finish()
}
async fn serve_object_handler<D, R>(
req: HttpRequest,
repo: web::Data<R>,
config: web::Data<Config>,
) -> HttpResponse
where
D: Dereference + From<Url> + 'static,
<D as Dereference>::Output: serde::ser::Serialize,
R: Repo + 'static,
{
let url: Url = match config.build_uri(req.uri()).parse() {
Ok(url) => url,
Err(_) => return HttpResponse::BadRequest().finish(),
};
let res = repo.fetch(D::from(url), ()).await;
match res {
Ok(Some(object)) => HttpResponse::Ok()
.content_type("application/activity+json")
.json(object),
Ok(None) => HttpResponse::NotFound().finish(),
Err(_) => HttpResponse::InternalServerError().finish(),
}
}
#[derive(Clone)]
struct DigestWrapper<D>(D);
impl<D> DigestName for DigestWrapper<D>
where
D: Digest,
{
const NAME: &'static str = D::NAME;
}
impl<D> DigestVerify for DigestWrapper<D>
where
D: Digest + Clone,
{
fn update(&mut self, part: &[u8]) {
self.0.update(part);
}
fn verify(&mut self, digests: &[DigestPart]) -> bool {
if let Some(part) = digests.iter().find(|part| part.algorithm == D::NAME) {
return self.0.clone().verify(&part.digest);
}
false
}
}
struct VerifyMiddleware<V, E> {
verifier: V,
config: Config,
_error: PhantomData<fn() -> E>,
}
impl<V, E> Clone for VerifyMiddleware<V, E>
where
V: Clone,
{
fn clone(&self) -> Self {
Self {
verifier: self.verifier.clone(),
config: self.config.clone(),
_error: PhantomData,
}
}
}
impl<V, E> VerifyMiddleware<V, E>
where
V: VerifyFactory,
{
fn new(verifier: V, config: Config) -> Self {
Self {
verifier,
config,
_error: PhantomData,
}
}
}
async fn verify<V, E>(
verifier: &V,
config: Config,
algorithm: Option<Algorithm>,
key_id: String,
signature: String,
signing_string: String,
) -> Result<bool, E>
where
V: RepoFactory + SessionFactory + VerifyFactory + PublicKeyRepoFactory + PrivateKeyRepoFactory,
V::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = V::Crypto>,
V::Crypto: PrivateKeyBuilder,
<<V as VerifyFactory>::Verify as Verify>::Error: 'static,
E: ResponseError
+ From<VerifyError>
+ From<VFactErr<V>>
+ From<PublicKeyError<PubKeyRepoErr<V>, RFactErr<V>>>
+ From<PrivKeyRepoErr<V>>
+ From<PrivKeyError<V>>
+ 'static,
{
match algorithm {
None | Some(Algorithm::Hs2019 | Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => {}
Some(other) => return Err(VerifyError::Algorithm(other.to_string()).into()),
};
let key_id = key_id.parse().map_err(|_| VerifyError::KeyId(key_id))?;
let private_key_repo = verifier.build_private_key_repo();
let crypto = private_key_repo.fetch(&config.server_actor_id).await?;
let session = verifier.build_session();
let http_repo = verifier.build_repo(crypto);
let public_key_repo = verifier.public_key_repo();
let public_key_client = PublicKeyClient::new(
public_key_repo,
http_repo,
config.local_host,
config.local_port,
);
let public_key = public_key_client
.find(&key_id, session)
.await
.map_err(E::from)?
.ok_or(VerifyError::KeyNotFound)?;
let verified = web::block(move || {
let verified =
<<V as VerifyFactory>::Verify as VerifyBuilder>::build(&public_key.public_key_pem)?
.verify(&signing_string, &signature)?;
Ok(verified) as Result<bool, VFactErr<V>>
})
.await
.map_err(VerifyError::from)??;
Ok(verified)
}
impl<V, E> SignatureVerify for VerifyMiddleware<V, E>
where
V: RepoFactory
+ SessionFactory
+ PublicKeyRepoFactory
+ PrivateKeyRepoFactory
+ VerifyFactory
+ Clone
+ 'static,
V::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = V::Crypto>,
V::Crypto: PrivateKeyBuilder,
E: ResponseError
+ From<VerifyError>
+ From<VFactErr<V>>
+ From<PublicKeyError<PubKeyRepoErr<V>, RFactErr<V>>>
+ From<PrivKeyRepoErr<V>>
+ From<PrivKeyError<V>>
+ 'static,
{
type Error = E;
type Future = Pin<Box<dyn Future<Output = Result<bool, Self::Error>>>>;
fn signature_verify(
&mut self,
algorithm: Option<Algorithm>,
key_id: String,
signature: String,
signing_string: String,
) -> Self::Future {
let verifier = self.verifier.clone();
let config = self.config.clone();
Box::pin(async move {
verify::<V, E>(
&verifier,
config,
algorithm,
key_id,
signature,
signing_string,
)
.await
})
}
}
impl From<BlockingError> for VerifyError {
fn from(_: BlockingError) -> Self {
VerifyError::Canceled
}
}