forked from mirrors/relay
Add http signature verification
This commit is contained in:
parent
eea0577686
commit
9fdd3bec18
5 changed files with 92 additions and 13 deletions
|
@ -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)]
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
28
src/inbox.rs
28
src/inbox.rs
|
@ -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 {
|
||||||
|
|
12
src/main.rs
12
src/main.rs
|
@ -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
53
src/verifier.rs
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue