Add http signature verification

This commit is contained in:
asonix 2020-03-15 23:15:50 -05:00
parent eea0577686
commit 9fdd3bec18
5 changed files with 92 additions and 13 deletions

View file

@ -81,6 +81,9 @@ pub struct AcceptedActors {
pub inbox: XsdAnyUri, pub inbox: XsdAnyUri,
pub endpoints: Endpoints, pub endpoints: Endpoints,
#[serde(skip_serializing_if = "Option::is_none")]
pub public_key: Option<PublicKey>,
} }
#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)]

View file

@ -27,6 +27,12 @@ pub enum MyError {
#[error("Couldn't parse the signature header")] #[error("Couldn't parse the signature header")]
HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue), HeaderValidation(#[from] actix_web::http::header::InvalidHeaderValue),
#[error("Couldn't decode base64")]
Base64(#[from] base64::DecodeError),
#[error("Invalid algorithm provided to verifier")]
Algorithm,
#[error("Wrong ActivityPub kind")] #[error("Wrong ActivityPub kind")]
Kind, Kind,
@ -50,6 +56,9 @@ pub enum MyError {
#[error("URI is missing domain field")] #[error("URI is missing domain field")]
Domain, Domain,
#[error("Public key is missing")]
MissingKey,
} }
impl ResponseError for MyError { impl ResponseError for MyError {

View file

@ -1,3 +1,9 @@
use crate::{
apub::{AcceptedActors, AcceptedObjects, ValidTypes},
db_actor::{DbActor, DbQuery, Pool},
error::MyError,
state::{State, UrlKind},
};
use activitystreams::{ use activitystreams::{
activity::apub::{Accept, Announce, Follow, Undo}, activity::apub::{Accept, Announce, Follow, Undo},
context, context,
@ -8,13 +14,6 @@ use actix_web::{client::Client, web, HttpResponse};
use futures::join; use futures::join;
use log::error; use log::error;
use crate::{
apub::{AcceptedActors, AcceptedObjects, ValidTypes},
db_actor::{DbActor, DbQuery, Pool},
error::MyError,
state::{State, UrlKind},
};
pub async fn inbox( pub async fn inbox(
db_actor: web::Data<Addr<DbActor>>, db_actor: web::Data<Addr<DbActor>>,
state: web::Data<State>, state: web::Data<State>,
@ -23,7 +22,12 @@ pub async fn inbox(
) -> Result<HttpResponse, MyError> { ) -> Result<HttpResponse, MyError> {
let input = input.into_inner(); let input = input.into_inner();
let actor = fetch_actor(state.clone(), &client, &input.actor).await?; let actor = fetch_actor(
state.clone().into_inner(),
client.clone().into_inner(),
&input.actor,
)
.await?;
match input.kind { match input.kind {
ValidTypes::Announce | ValidTypes::Create => { ValidTypes::Announce | ValidTypes::Create => {
@ -217,15 +221,15 @@ async fn handle_follow(
let client = client.into_inner(); let client = client.into_inner();
let accept2 = accept.clone(); let accept2 = accept.clone();
actix::Arbiter::spawn(async move { actix::Arbiter::spawn(async move {
let _ = deliver(&state.into_inner(), &client, actor_inbox, &accept2).await; let _ = deliver(&state.into_inner(), &client.clone(), actor_inbox, &accept2).await;
}); });
Ok(response(accept)) Ok(response(accept))
} }
async fn fetch_actor( pub async fn fetch_actor(
state: web::Data<State>, state: std::sync::Arc<State>,
client: &web::Data<Client>, client: std::sync::Arc<Client>,
actor_id: &XsdAnyUri, actor_id: &XsdAnyUri,
) -> Result<AcceptedActors, MyError> { ) -> Result<AcceptedActors, MyError> {
if let Some(actor) = state.get_actor(actor_id).await { if let Some(actor) = state.get_actor(actor_id).await {

View file

@ -2,7 +2,9 @@
use activitystreams::{actor::apub::Application, context, endpoint::EndpointProperties}; use activitystreams::{actor::apub::Application, context, endpoint::EndpointProperties};
use actix_web::{client::Client, middleware::Logger, web, App, HttpServer, Responder}; use actix_web::{client::Client, middleware::Logger, web, App, HttpServer, Responder};
use bb8_postgres::tokio_postgres; use bb8_postgres::tokio_postgres;
use http_signature_normalization_actix::prelude::{VerifyDigest, VerifySignature};
use rsa_pem::KeyExt; use rsa_pem::KeyExt;
use sha2::{Digest, Sha256};
mod apub; mod apub;
mod db_actor; mod db_actor;
@ -10,6 +12,7 @@ mod error;
mod inbox; mod inbox;
mod label; mod label;
mod state; mod state;
mod verifier;
mod webfinger; mod webfinger;
use self::{ use self::{
@ -18,6 +21,8 @@ use self::{
error::MyError, error::MyError,
label::ArbiterLabelFactory, label::ArbiterLabelFactory,
state::{State, UrlKind}, state::{State, UrlKind},
verifier::MyVerify,
webfinger::RelayResolver,
}; };
async fn index() -> impl Responder { async fn index() -> impl Responder {
@ -84,6 +89,11 @@ async fn main() -> Result<(), anyhow::Error> {
let client = Client::default(); let client = Client::default();
App::new() App::new()
.wrap(VerifyDigest::new(Sha256::new()))
.wrap(VerifySignature::new(
MyVerify(state.clone(), client.clone()),
Default::default(),
))
.wrap(Logger::default()) .wrap(Logger::default())
.data(actor) .data(actor)
.data(state.clone()) .data(state.clone())
@ -91,7 +101,7 @@ async fn main() -> Result<(), anyhow::Error> {
.service(web::resource("/").route(web::get().to(index))) .service(web::resource("/").route(web::get().to(index)))
.service(web::resource("/inbox").route(web::post().to(inbox::inbox))) .service(web::resource("/inbox").route(web::post().to(inbox::inbox)))
.service(web::resource("/actor").route(web::get().to(actor_route))) .service(web::resource("/actor").route(web::get().to(actor_route)))
.service(actix_webfinger::resource::<_, webfinger::RelayResolver>()) .service(actix_webfinger::resource::<_, RelayResolver>())
}) })
.bind("127.0.0.1:8080")? .bind("127.0.0.1:8080")?
.run() .run()

53
src/verifier.rs Normal file
View file

@ -0,0 +1,53 @@
use crate::{error::MyError, state::State};
use actix_web::client::Client;
use http_signature_normalization_actix::prelude::*;
use rsa::{hash::Hashes, padding::PaddingScheme, PublicKey, RSAPublicKey};
use rsa_pem::KeyExt;
use std::{future::Future, pin::Pin, sync::Arc};
#[derive(Clone)]
pub struct MyVerify(pub State, pub Client);
impl SignatureVerify for MyVerify {
type Error = MyError;
type Future = Pin<Box<dyn Future<Output = Result<bool, Self::Error>>>>;
fn signature_verify(
&mut self,
algorithm: Option<Algorithm>,
key_id: &str,
signature: &str,
signing_string: &str,
) -> Self::Future {
let key_id = key_id.to_owned();
let signature = signature.to_owned();
let signing_string = signing_string.to_owned();
let state = Arc::new(self.0.clone());
let client = Arc::new(self.1.clone());
Box::pin(async move {
let actor = crate::inbox::fetch_actor(state, client, &key_id.parse()?).await?;
let public_key = actor.public_key.ok_or(MyError::MissingKey)?;
let public_key = RSAPublicKey::from_pem_pkcs8(&public_key.public_key_pem)?;
match algorithm {
Some(Algorithm::Hs2019) => (),
_ => return Err(MyError::Algorithm),
};
let decoded = base64::decode(signature)?;
public_key.verify(
PaddingScheme::PKCS1v15,
Some(&Hashes::SHA2_256),
&decoded,
signing_string.as_bytes(),
)?;
Ok(true)
})
}
}