Skip to main content

apub_actix_web/
lib.rs

1//! Actix Web integration for Apub
2//!
3//! Currently, this only includes verifying HTTP Signatures and Digests for authorized fetch and
4//! inbox delivery, but it doesn't do any further verification
5
6#![deny(missing_docs)]
7
8use actix_web::{
9    error::BlockingError,
10    http::Uri,
11    web::{self, ServiceConfig},
12    FromRequest, HttpRequest, HttpResponse, ResponseError,
13};
14use apub_core::{
15    digest::{Digest, DigestBuilder, DigestFactory},
16    ingest::{is_local, Authority, Ingest, IngestFactory},
17    repo::{Dereference, Repo, RepoFactory},
18    session::SessionFactory,
19    signature::{PrivateKeyBuilder, Verify, VerifyBuilder, VerifyFactory},
20};
21use apub_privatekey::{PrivateKeyRepo, PrivateKeyRepoFactory};
22use apub_publickey::{PublicKeyClient, PublicKeyError, PublicKeyRepo, PublicKeyRepoFactory};
23use http_signature_normalization_actix::{
24    digest::DigestName,
25    prelude::{
26        Algorithm, DeprecatedAlgorithm, DigestPart, DigestVerify, SignatureVerified,
27        SignatureVerify, VerifyDigest, VerifySignature,
28    },
29};
30use std::{future::Future, marker::PhantomData, pin::Pin};
31use url::{Host, Url};
32
33/// Server configuration
34#[derive(Clone, Debug)]
35pub struct Config {
36    /// the publicly visible the server is located at
37    ///
38    /// e.g. `hello.com` or `70.123.123.20`
39    pub local_host: Host<String>,
40
41    /// the publicly visible port the server is located at
42    ///
43    /// e.g. `None` or `Some(8080)`
44    pub local_port: Option<u16>,
45
46    /// the public scheme the server is accessible over
47    ///
48    /// e.g. `http://` or `https://`
49    pub scheme: String,
50
51    /// Used to look up the server actor's key
52    pub server_actor_id: Url,
53}
54
55impl Config {
56    fn build_uri(&self, uri: &Uri) -> String {
57        if let Some(port) = self.local_port {
58            format!("{}{}:{}{}", self.scheme, self.local_host, port, uri)
59        } else {
60            format!("{}{}{}", self.scheme, self.local_host, uri)
61        }
62    }
63
64    /// Determine if a given URL is local
65    pub fn is_local(&self, url: &Url) -> bool {
66        is_local(&self.local_host, self.local_port, url)
67    }
68}
69
70/// The HTTP Signature config for Actix Web
71pub use http_signature_normalization_actix::Config as SignatureConfig;
72
73/// Errors that can happen when verifying signatures
74#[derive(Debug, thiserror::Error)]
75pub enum VerifyError {
76    /// An unsupported signature algorithm was provided
77    #[error("Unsupported algorithm: {0}")]
78    Algorithm(String),
79
80    /// The given KeyId is malformed
81    #[error("Invalid Key ID: {0}")]
82    KeyId(String),
83
84    /// The owner of the provided key is doesn't match the fetched owner's ID
85    #[error("Actor {0} is not public key's owner")]
86    InvalidOwner(Url),
87
88    /// They fetched key's ID is different from the ID we expected
89    #[error("Public Key {0} is not the expected key")]
90    InvalidKey(Url),
91
92    /// The key was unable to be found
93    #[error("No key associated with Key ID")]
94    KeyNotFound,
95
96    /// The verification task panicked
97    #[error("Key verification panicked")]
98    Canceled,
99}
100
101type RFactErr<R> = <<R as RepoFactory>::Repo as Repo>::Error;
102type PubKeyRepoErr<P> = <<P as PublicKeyRepoFactory>::PublicKeyRepo as PublicKeyRepo>::Error;
103type VFactErr<V> = <<V as VerifyFactory>::Verify as Verify>::Error;
104type PrivKeyRepoErr<P> = <<P as PrivateKeyRepoFactory>::PrivateKeyRepo as PrivateKeyRepo>::Error;
105type PrivKeyError<R> = <<R as RepoFactory>::Crypto as PrivateKeyBuilder>::Error;
106
107/// Ingest activitypub objects at a given endpoint
108///
109/// ```rust,ignore
110/// use actix_web::App;
111/// use apub_actix_web::{inbox, SignatureConfig};
112/// use url::Host;
113///
114/// // Ingest implements Ingest
115/// let ingest = Ingest::new();
116///
117/// // verifier implements Repo, VerifyFactory, and DigestFactory
118/// let verifier = DatabaseVerifier::new();
119///
120/// let config = Config {
121///     local_host: Host::Domain(String::from("example.com")),
122///     local_port: None,
123///     scheme: String::from("https://"),
124/// };
125///
126/// App::new()
127///     .service(
128///         web::scope("/shared_inbox")
129///             .configure(inbox::<A, (), _, _, MyError>(
130///                 config,
131///                 ingest,
132///                 verifier,
133///                 SignatureConfig::default(),
134///                 true,
135///             ))
136///     );
137/// ```
138pub fn inbox<A, I, V, E>(
139    config: Config,
140    ingest_factory: I,
141    verifier: V,
142    signature_config: SignatureConfig,
143    require_signature: bool,
144) -> impl FnOnce(&mut ServiceConfig)
145where
146    A: for<'de> serde::de::Deserialize<'de> + 'static,
147    I: IngestFactory<A> + PrivateKeyRepoFactory + RepoFactory + SessionFactory + 'static,
148    I::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = I::Crypto>,
149    <I::Ingest as Ingest<A>>::ActorId: FromRequest + AsRef<Url>,
150    <I::Ingest as Ingest<A>>::Error: From<<I::Repo as Repo>::Error>,
151    V: RepoFactory
152        + SessionFactory
153        + PublicKeyRepoFactory
154        + PrivateKeyRepoFactory
155        + VerifyFactory
156        + DigestFactory
157        + Clone
158        + 'static,
159    V::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = V::Crypto>,
160    V::Crypto: PrivateKeyBuilder,
161    <V as DigestFactory>::Digest: Clone,
162    E: ResponseError
163        + From<VerifyError>
164        + From<VFactErr<V>>
165        + From<PublicKeyError<PubKeyRepoErr<V>, RFactErr<V>>>
166        + From<PrivKeyRepoErr<V>>
167        + From<PrivKeyError<V>>
168        + 'static,
169{
170    move |service_config: &mut ServiceConfig| {
171        let signature = VerifySignature::new(
172            VerifyMiddleware::<V, E>::new(verifier, config),
173            signature_config,
174        );
175
176        let digest = VerifyDigest::new(DigestWrapper(V::Digest::build()));
177
178        let (signature, digest) = if require_signature {
179            (signature, digest)
180        } else {
181            (signature.optional(), digest.optional())
182        };
183
184        service_config.service(
185            web::scope("")
186                .app_data(web::Data::new(ingest_factory))
187                .wrap(digest)
188                .wrap(signature)
189                .route("", web::post().to(inbox_handler::<A, I>)),
190        );
191    }
192}
193
194/// Serve activitypub objects from a given endpoint
195///
196/// ```rust,ignore
197/// use actix_web::App;
198/// use apub_actix_web::{serve_objects, SignatureConfig};
199/// use url::Host;
200///
201/// // repository implements Repo
202/// let repository = DatabaseRepo::new();
203///
204/// // verifier implements Repo and VerifyFactory
205/// let verifier = DatabaseVerifier::new();
206///
207/// let config = Config {
208///     local_host: Host::Domain(String::from("example.com")),
209///     local_port: None,
210///     scheme: String::from("https://"),
211/// };
212///
213/// App::new()
214///     .service(
215///         web::scope("/activites")
216///             .configure(serve_objects::<ObjectId<A>, _, _, MyError>(
217///                 config,
218///                 repository,
219///                 verifier,
220///                 SignatureConfig::default(),
221///                 true,
222///             ))
223///     );
224/// ```
225pub fn serve_objects<D, R, V, E>(
226    config: Config,
227    repo: R,
228    verifier: V,
229    signature_config: SignatureConfig,
230    require_signature: bool,
231) -> impl FnOnce(&mut ServiceConfig)
232where
233    D: Dereference + From<Url> + 'static,
234    <D as Dereference>::Output: serde::ser::Serialize,
235    R: Repo + 'static,
236    V: RepoFactory
237        + SessionFactory
238        + PublicKeyRepoFactory
239        + PrivateKeyRepoFactory
240        + VerifyFactory
241        + Clone
242        + 'static,
243    V::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = V::Crypto>,
244    V::Crypto: PrivateKeyBuilder,
245    E: ResponseError
246        + From<VerifyError>
247        + From<VFactErr<V>>
248        + From<PublicKeyError<PubKeyRepoErr<V>, RFactErr<V>>>
249        + From<PrivKeyRepoErr<V>>
250        + From<PrivKeyError<V>>
251        + 'static,
252{
253    move |service_config: &mut ServiceConfig| {
254        let verifier = VerifySignature::new(
255            VerifyMiddleware::<V, E>::new(verifier, config.clone()),
256            signature_config,
257        );
258
259        let verifier = if require_signature {
260            verifier
261        } else {
262            verifier.optional()
263        };
264
265        service_config.service(
266            web::scope("/{object}")
267                .app_data(web::Data::new(repo))
268                .app_data(web::Data::new(config))
269                .wrap(verifier)
270                .route("", web::get().to(serve_object_handler::<D, R>)),
271        );
272    }
273}
274
275async fn inbox_handler<A, I>(
276    ingest_factory: web::Data<I>,
277    authority: Option<SignatureVerified>,
278    activity: web::Json<A>,
279    actor_id: <I::Ingest as Ingest<A>>::ActorId,
280) -> HttpResponse
281where
282    A: for<'de> serde::de::Deserialize<'de> + 'static,
283    I: IngestFactory<A> + PrivateKeyRepoFactory + RepoFactory + SessionFactory,
284    I::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = I::Crypto>,
285    <I::Ingest as Ingest<A>>::ActorId: FromRequest + AsRef<Url>,
286    <I::Ingest as Ingest<A>>::Error: From<<I::Repo as Repo>::Error>,
287{
288    let url = actor_id.as_ref();
289
290    let private_key_repo = ingest_factory.build_private_key_repo();
291    let private_key = match private_key_repo.fetch(url).await {
292        Ok(private_key) => private_key,
293        Err(_) => return HttpResponse::BadRequest().finish(),
294    };
295
296    let activity = activity.into_inner();
297    let remote_repo = ingest_factory.build_repo(private_key);
298    let mut session = ingest_factory.build_session();
299    let ingest = ingest_factory.build_ingest();
300
301    if let Some(auth) = authority {
302        if let Ok(url) = auth.key_id().parse() {
303            if ingest
304                .ingest(
305                    Authority::Actor(url),
306                    actor_id,
307                    &activity,
308                    &remote_repo,
309                    &mut session,
310                )
311                .await
312                .is_ok()
313            {
314                return HttpResponse::Accepted().finish();
315            }
316        }
317    } else if ingest
318        .ingest(
319            Authority::None,
320            actor_id,
321            &activity,
322            &remote_repo,
323            &mut session,
324        )
325        .await
326        .is_ok()
327    {
328        return HttpResponse::Accepted().finish();
329    }
330
331    HttpResponse::BadRequest().finish()
332}
333
334async fn serve_object_handler<D, R>(
335    req: HttpRequest,
336    repo: web::Data<R>,
337    config: web::Data<Config>,
338) -> HttpResponse
339where
340    D: Dereference + From<Url> + 'static,
341    <D as Dereference>::Output: serde::ser::Serialize,
342    R: Repo + 'static,
343{
344    let url: Url = match config.build_uri(req.uri()).parse() {
345        Ok(url) => url,
346        Err(_) => return HttpResponse::BadRequest().finish(),
347    };
348
349    let res = repo.fetch(D::from(url), ()).await;
350
351    match res {
352        Ok(Some(object)) => HttpResponse::Ok()
353            .content_type("application/activity+json")
354            .json(object),
355        Ok(None) => HttpResponse::NotFound().finish(),
356        Err(_) => HttpResponse::InternalServerError().finish(),
357    }
358}
359
360#[derive(Clone)]
361struct DigestWrapper<D>(D);
362
363impl<D> DigestName for DigestWrapper<D>
364where
365    D: Digest,
366{
367    const NAME: &'static str = D::NAME;
368}
369
370impl<D> DigestVerify for DigestWrapper<D>
371where
372    D: Digest + Clone,
373{
374    fn update(&mut self, part: &[u8]) {
375        self.0.update(part);
376    }
377
378    fn verify(&mut self, digests: &[DigestPart]) -> bool {
379        if let Some(part) = digests.iter().find(|part| part.algorithm == D::NAME) {
380            return self.0.clone().verify(&part.digest);
381        }
382
383        false
384    }
385}
386
387struct VerifyMiddleware<V, E> {
388    verifier: V,
389    config: Config,
390    _error: PhantomData<fn() -> E>,
391}
392
393impl<V, E> Clone for VerifyMiddleware<V, E>
394where
395    V: Clone,
396{
397    fn clone(&self) -> Self {
398        Self {
399            verifier: self.verifier.clone(),
400            config: self.config.clone(),
401            _error: PhantomData,
402        }
403    }
404}
405
406impl<V, E> VerifyMiddleware<V, E>
407where
408    V: VerifyFactory,
409{
410    fn new(verifier: V, config: Config) -> Self {
411        Self {
412            verifier,
413            config,
414            _error: PhantomData,
415        }
416    }
417}
418
419async fn verify<V, E>(
420    verifier: &V,
421    config: Config,
422    algorithm: Option<Algorithm>,
423    key_id: String,
424    signature: String,
425    signing_string: String,
426) -> Result<bool, E>
427where
428    V: RepoFactory + SessionFactory + VerifyFactory + PublicKeyRepoFactory + PrivateKeyRepoFactory,
429    V::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = V::Crypto>,
430    V::Crypto: PrivateKeyBuilder,
431    <<V as VerifyFactory>::Verify as Verify>::Error: 'static,
432    E: ResponseError
433        + From<VerifyError>
434        + From<VFactErr<V>>
435        + From<PublicKeyError<PubKeyRepoErr<V>, RFactErr<V>>>
436        + From<PrivKeyRepoErr<V>>
437        + From<PrivKeyError<V>>
438        + 'static,
439{
440    match algorithm {
441        None | Some(Algorithm::Hs2019 | Algorithm::Deprecated(DeprecatedAlgorithm::RsaSha256)) => {}
442        Some(other) => return Err(VerifyError::Algorithm(other.to_string()).into()),
443    };
444
445    let key_id = key_id.parse().map_err(|_| VerifyError::KeyId(key_id))?;
446
447    let private_key_repo = verifier.build_private_key_repo();
448
449    let crypto = private_key_repo.fetch(&config.server_actor_id).await?;
450
451    let session = verifier.build_session();
452    let http_repo = verifier.build_repo(crypto);
453    let public_key_repo = verifier.public_key_repo();
454
455    let public_key_client = PublicKeyClient::new(
456        public_key_repo,
457        http_repo,
458        config.local_host,
459        config.local_port,
460    );
461
462    let public_key = public_key_client
463        .find(&key_id, session)
464        .await
465        .map_err(E::from)?
466        .ok_or(VerifyError::KeyNotFound)?;
467
468    let verified = web::block(move || {
469        let verified =
470            <<V as VerifyFactory>::Verify as VerifyBuilder>::build(&public_key.public_key_pem)?
471                .verify(&signing_string, &signature)?;
472
473        Ok(verified) as Result<bool, VFactErr<V>>
474    })
475    .await
476    .map_err(VerifyError::from)??;
477
478    Ok(verified)
479}
480
481impl<V, E> SignatureVerify for VerifyMiddleware<V, E>
482where
483    V: RepoFactory
484        + SessionFactory
485        + PublicKeyRepoFactory
486        + PrivateKeyRepoFactory
487        + VerifyFactory
488        + Clone
489        + 'static,
490    V::PrivateKeyRepo: PrivateKeyRepo<PrivateKey = V::Crypto>,
491    V::Crypto: PrivateKeyBuilder,
492    E: ResponseError
493        + From<VerifyError>
494        + From<VFactErr<V>>
495        + From<PublicKeyError<PubKeyRepoErr<V>, RFactErr<V>>>
496        + From<PrivKeyRepoErr<V>>
497        + From<PrivKeyError<V>>
498        + 'static,
499{
500    type Error = E;
501    type Future = Pin<Box<dyn Future<Output = Result<bool, Self::Error>>>>;
502
503    fn signature_verify(
504        &mut self,
505        algorithm: Option<Algorithm>,
506        key_id: String,
507        signature: String,
508        signing_string: String,
509    ) -> Self::Future {
510        let verifier = self.verifier.clone();
511        let config = self.config.clone();
512
513        Box::pin(async move {
514            verify::<V, E>(
515                &verifier,
516                config,
517                algorithm,
518                key_id,
519                signature,
520                signing_string,
521            )
522            .await
523        })
524    }
525}
526
527impl From<BlockingError> for VerifyError {
528    fn from(_: BlockingError) -> Self {
529        VerifyError::Canceled
530    }
531}