diff --git a/src/authentication.rs b/src/authentication.rs index 47a0a11..7bcef25 100644 --- a/src/authentication.rs +++ b/src/authentication.rs @@ -17,9 +17,9 @@ use crate::{ }; #[derive(Debug, Serialize, Deserialize)] -struct UserTokenMetadata { - user: User, - generated_for: Instance, +pub struct UserTokenMetadata { + pub user: User, + pub generated_for: Instance, exp: u64, } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 0a06575..0800cde 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -16,7 +16,7 @@ use crate::{ UserBioRequest, UserBioResponse, UserDisplayImageRequest, UserDisplayImageResponse, UserDisplayNameRequest, UserDisplayNameResponse, }, - UserAuthenticated, + ValidatedUserAuthenticated, }, model::repository::RepositoryView, }; @@ -25,30 +25,30 @@ use crate::{ pub trait RepositoryBackend: IssuesBackend { async fn create_repository( &mut self, - request: &UserAuthenticated, + request: &ValidatedUserAuthenticated, ) -> Result>; async fn repository_info( &mut self, - request: &UserAuthenticated, + request: &ValidatedUserAuthenticated, ) -> Result>; fn repository_file_inspect( &mut self, - request: &UserAuthenticated, + request: &ValidatedUserAuthenticated, ) -> Result>; } pub trait IssuesBackend { fn issues_count( &mut self, - request: &UserAuthenticated, + request: &ValidatedUserAuthenticated, ) -> Result>; fn issue_labels( &mut self, - request: &UserAuthenticated, + request: &ValidatedUserAuthenticated, ) -> Result>; fn issues( &mut self, - request: &UserAuthenticated, + request: &ValidatedUserAuthenticated, ) -> Result>; } diff --git a/src/connection.rs b/src/connection.rs index 96a09ca..2886d4f 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -184,10 +184,11 @@ pub async fn connection_worker( } else { // This message is targeting this instance match &repository.command { - RepositoryMessageKind::Request(request) => match request { + RepositoryMessageKind::Request(request) => match request.clone() { RepositoryRequest::CreateRepository(request) => { let mut backend = backend.lock().await; - let response = backend.create_repository(request).await; + let request = request.validate().await.unwrap(); + let response = backend.create_repository(&request).await; let response = match response { Ok(response) => response, @@ -217,7 +218,8 @@ pub async fn connection_worker( } RepositoryRequest::RepositoryFileInspect(request) => { let mut backend = backend.lock().await; - let response = backend.repository_file_inspect(request); + let request = request.validate().await.unwrap(); + let response = backend.repository_file_inspect(&request); let response = match response { Ok(response) => response, @@ -248,7 +250,8 @@ pub async fn connection_worker( } RepositoryRequest::RepositoryInfo(request) => { let mut backend = backend.lock().await; - let response = backend.repository_info(request).await; + let request = request.validate().await.unwrap(); + let response = backend.repository_info(&request).await; let response = match response { Ok(response) => response, @@ -276,6 +279,8 @@ pub async fn connection_worker( continue; } RepositoryRequest::IssuesCount(request) => { + let request = &request.validate().await.unwrap(); + let mut backend = backend.lock().await; let response = backend.issues_count(request); @@ -305,8 +310,10 @@ pub async fn connection_worker( continue; } RepositoryRequest::IssueLabels(request) => { + let request = &request.validate().await.unwrap(); + let mut backend = backend.lock().await; - let response = backend.issue_labels(request); + let response = backend.issue_labels(&request); let response = match response { Ok(response) => response, @@ -333,8 +340,10 @@ pub async fn connection_worker( continue; } RepositoryRequest::Issues(request) => { + let request = request.validate().await.unwrap(); + let mut backend = backend.lock().await; - let response = backend.issues(request); + let response = backend.issues(&request); let response = match response { Ok(response) => response, diff --git a/src/messages/mod.rs b/src/messages/mod.rs index 712614c..ca9ffa2 100644 --- a/src/messages/mod.rs +++ b/src/messages/mod.rs @@ -1,5 +1,6 @@ use std::{error::Error, fmt::Debug}; +use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; use rsa::{ pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey}, pss::{Signature, SigningKey, VerifyingKey}, @@ -9,7 +10,11 @@ use rsa::{ }; use serde::{Deserialize, Serialize}; -use crate::{handshake::HandshakeMessage, model::instance::Instance}; +use crate::{ + authentication::UserTokenMetadata, + handshake::HandshakeMessage, + model::{instance::Instance, user::User}, +}; use self::{authentication::AuthenticationMessage, repository::RepositoryMessage}; @@ -110,14 +115,59 @@ impl InstanceAuthenticated { /// Includes the message, with a digest generated with /// our private key. #[derive(Serialize, Deserialize)] -pub struct UserAuthenticated { +pub struct ValidatedUserAuthenticated { + #[serde(flatten)] + message: T, + user: User, +} + +impl Clone for ValidatedUserAuthenticated +where + T: Clone + Serialize, +{ + fn clone(&self) -> Self { + Self { + message: self.message.clone(), + user: self.user.clone(), + } + } +} + +impl Debug for ValidatedUserAuthenticated +where + T: Debug + Serialize, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Authenticated") + .field("message", &self.message) + .field("user", &self.user) + .finish() + } +} + +impl ValidatedUserAuthenticated { + pub async fn inner(&self) -> &T { + &self.message + } + + pub async fn user(&self) -> &User { + &self.user + } +} + +/// An unvalidated authenticated message. +/// +/// Includes the message, with a digest generated with +/// our private key. +#[derive(Serialize, Deserialize)] +pub struct UnvalidatedUserAuthenticated { #[serde(flatten)] message: T, token: String, digest: Vec, } -impl Clone for UserAuthenticated +impl Clone for UnvalidatedUserAuthenticated where T: Clone + Serialize, { @@ -130,7 +180,7 @@ where } } -impl Debug for UserAuthenticated +impl Debug for UnvalidatedUserAuthenticated where T: Debug + Serialize, { @@ -143,7 +193,7 @@ where } } -impl UserAuthenticated { +impl UnvalidatedUserAuthenticated { pub fn new(message: T, token: String, private_key: String) -> Result> { let mut rng = rand::thread_rng(); @@ -165,8 +215,19 @@ impl UserAuthenticated { &self.message } - pub async fn validate(&self, key: String) -> Result<(), Box> { - let public_key = RsaPublicKey::from_pkcs1_pem(&key).unwrap(); + pub async fn validate(self) -> Result, Box> { + let instance = { + let mut validation = Validation::new(Algorithm::RS256); + validation.insecure_disable_signature_validation(); + + let value: TokenData = + decode(&self.token, &DecodingKey::from_secret(b"test"), &validation).unwrap(); + + value.claims.generated_for.clone() + }; + + let public_key_raw = public_key(&instance).await?; + let public_key = RsaPublicKey::from_pkcs1_pem(&public_key_raw).unwrap(); let verifying_key: VerifyingKey = VerifyingKey::new(public_key); @@ -179,7 +240,21 @@ impl UserAuthenticated { ) .unwrap(); - Ok(()) + let verification_key = DecodingKey::from_rsa_pem(public_key_raw.as_bytes()).unwrap(); + + let data: TokenData = decode( + &self.token, + &verification_key, + &Validation::new(Algorithm::RS256), + ) + .unwrap(); + + assert_eq!(data.claims.generated_for, instance); + + Ok(ValidatedUserAuthenticated { + message: self.message, + user: data.claims.user, + }) } } diff --git a/src/messages/repository.rs b/src/messages/repository.rs index 63c4226..05d6aeb 100644 --- a/src/messages/repository.rs +++ b/src/messages/repository.rs @@ -6,7 +6,7 @@ use crate::model::{ user::User, }; -use super::UserAuthenticated; +use super::UnvalidatedUserAuthenticated; #[derive(Clone, Serialize, Deserialize)] pub struct RepositoryMessage { @@ -22,12 +22,12 @@ pub enum RepositoryMessageKind { #[derive(Clone, Serialize, Deserialize)] pub enum RepositoryRequest { - CreateRepository(UserAuthenticated), - RepositoryFileInspect(UserAuthenticated), - RepositoryInfo(UserAuthenticated), - IssuesCount(UserAuthenticated), - IssueLabels(UserAuthenticated), - Issues(UserAuthenticated), + CreateRepository(UnvalidatedUserAuthenticated), + RepositoryFileInspect(UnvalidatedUserAuthenticated), + RepositoryInfo(UnvalidatedUserAuthenticated), + IssuesCount(UnvalidatedUserAuthenticated), + IssueLabels(UnvalidatedUserAuthenticated), + Issues(UnvalidatedUserAuthenticated), } #[derive(Clone, Serialize, Deserialize)] diff --git a/src/model/repository.rs b/src/model/repository.rs index 824693a..4e5bed7 100644 --- a/src/model/repository.rs +++ b/src/model/repository.rs @@ -5,7 +5,7 @@ use super::{instance::Instance, user::User}; #[derive(Hash, Clone, Serialize, Deserialize)] pub struct Repository { /// Name of the repository. - /// + /// /// This must be treated as an opaque path to a repository. Do not /// modify or parse the path. pub name: String, @@ -25,7 +25,7 @@ pub enum RepositoryVisibility { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct RepositoryView { /// Name of the repository - /// + /// /// This is different than the [`Repository`] name, /// which may be a path. pub name: String, @@ -134,6 +134,6 @@ impl From> for CommitSignature { impl ToString for Repository { fn to_string(&self) -> String { - format!("{}@{}",self.name, self.instance.url) + format!("{}@{}", self.name, self.instance.url) } -} \ No newline at end of file +} diff --git a/src/model/user.rs b/src/model/user.rs index d8e1ffd..aa93bc0 100644 --- a/src/model/user.rs +++ b/src/model/user.rs @@ -12,4 +12,4 @@ impl ToString for User { fn to_string(&self) -> String { format!("{}:{}", self.username, self.instance.url) } -} \ No newline at end of file +}