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…
	
	Add table
		
		Reference in a new issue