diff --git a/Cargo.lock b/Cargo.lock index ca3dd57..7ec898c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -315,6 +315,15 @@ dependencies = [ ] [[package]] +name = "encoding_rs" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1" +dependencies = [ + "cfg-if", +] + +[[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -543,6 +552,9 @@ dependencies = [ "git2", "jsonwebtoken", "log", + "rand", + "reqwest", + "rsa", "serde", "serde_json", "sqlx", @@ -555,6 +567,31 @@ dependencies = [ ] [[package]] +name = "h2" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap 1.9.3", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] name = "hashbrown" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -570,7 +607,7 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "312f66718a2d7789ffef4f4b7b213138ed9f1eb3aa1d0d82fc99f88fb3ffd26f" dependencies = [ - "hashbrown", + "hashbrown 0.14.0", ] [[package]] @@ -633,12 +670,66 @@ dependencies = [ ] [[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "0.14.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2 0.4.9", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] name = "iana-time-zone" version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -673,15 +764,31 @@ dependencies = [ [[package]] name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.0", ] [[package]] +name = "ipnet" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28b29a3cd74f0f4598934efe3aeba42bae0eb4680554128851ebbecb02af14e6" + +[[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -838,6 +945,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1211,6 +1324,43 @@ dependencies = [ ] [[package]] +name = "reqwest" +version = "0.11.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1" +dependencies = [ + "base64 0.21.3", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-tls", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1241,6 +1391,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core", + "sha2", "signature", "spki", "subtle", @@ -1351,6 +1502,18 @@ dependencies = [ ] [[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] name = "sha1" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1429,6 +1592,16 @@ checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "socket2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2538b18701741680e0322a2302176d3253a35388e2e62f172f64f4f16605f877" @@ -1509,7 +1682,7 @@ dependencies = [ "futures-util", "hashlink", "hex", - "indexmap", + "indexmap 2.0.0", "log", "memchr", "native-tls", @@ -1822,7 +1995,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.5.3", "tokio-macros", "windows-sys", ] @@ -1839,6 +2012,16 @@ dependencies = [ ] [[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] name = "tokio-stream" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1862,6 +2045,20 @@ dependencies = [ ] [[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] name = "toml" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1888,7 +2085,7 @@ version = "0.19.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a" dependencies = [ - "indexmap", + "indexmap 2.0.0", "serde", "serde_spanned", "toml_datetime", @@ -1896,6 +2093,12 @@ dependencies = [ ] [[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1955,6 +2158,12 @@ dependencies = [ ] [[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] name = "tungstenite" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2054,6 +2263,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2091,6 +2309,18 @@ dependencies = [ ] [[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2242,6 +2472,16 @@ dependencies = [ ] [[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys", +] + +[[package]] name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml index 0899af3..79bd87f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,9 @@ serde_json = "1.0" tracing-subscriber = "0.3" jsonwebtoken = { version = "*", features = ["use_pem"]} log = "*" +rand = "*" +rsa = {version = "*", features = ["sha2"]} +reqwest = "*" toml = { version = "0.7" } diff --git a/src/authentication.rs b/src/authentication.rs index 76f9f89..7c44cf4 100644 --- a/src/authentication.rs +++ b/src/authentication.rs @@ -1,6 +1,5 @@ use std::{error::Error, time::SystemTime}; -use chrono::Duration; use jsonwebtoken::{encode, Algorithm, EncodingKey}; use serde::{Deserialize, Serialize}; use tokio::{fs::File, io::AsyncReadExt}; diff --git a/src/backend/git.rs b/src/backend/git.rs index f9a6a43..01b2397 100644 --- a/src/backend/git.rs +++ b/src/backend/git.rs @@ -5,6 +5,8 @@ use std::error::Error; use std::path::{Path, PathBuf}; use thiserror::Error; +use crate::messages::Authenticated; +use crate::model::instance::Instance; use crate::model::repository::{ Commit, RepositoryObjectType, RepositoryTreeEntry, RepositoryVisibility, }; @@ -212,8 +214,19 @@ impl GitBackend { impl RepositoryBackend for GitBackend { async fn create_repository( &mut self, - request: &CreateRepositoryRequest, + raw_request: &Authenticated, ) -> Result> { + let request = raw_request.inner().await; + + let public_key = public_key(&Instance { + url: String::from("giterated.dev"), + }) + .await + .unwrap(); + + assert!(matches!(raw_request.validate(public_key).await, Ok(()))); + info!("Request was valid!"); + // Check if repository already exists in the database if let Ok(_repository) = self .find_by_instance_username_name( @@ -465,3 +478,12 @@ impl IssuesBackend for GitBackend { todo!() } } + +async fn public_key(instance: &Instance) -> Result> { + let key = reqwest::get(format!("https://{}/.giterated/pubkey.pem", instance.url)) + .await? + .text() + .await?; + + Ok(key) +} diff --git a/src/backend/mod.rs b/src/backend/mod.rs index c082568..b3729ad 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -16,6 +16,7 @@ use crate::{ UserBioRequest, UserBioResponse, UserDisplayImageRequest, UserDisplayImageResponse, UserDisplayNameRequest, UserDisplayNameResponse, }, + Authenticated, }, model::repository::RepositoryView, }; @@ -24,7 +25,7 @@ use crate::{ pub trait RepositoryBackend: IssuesBackend { async fn create_repository( &mut self, - request: &CreateRepositoryRequest, + request: &Authenticated, ) -> Result>; async fn repository_info( &mut self, diff --git a/src/connection.rs b/src/connection.rs index 0458981..a9cc3c6 100644 --- a/src/connection.rs +++ b/src/connection.rs @@ -17,6 +17,7 @@ use crate::{ handshake::{HandshakeFinalize, HandshakeMessage, HandshakeResponse}, listener::Listeners, messages::{ + authentication::{AuthenticationMessage, AuthenticationRequest}, repository::{ RepositoryMessage, RepositoryMessageKind, RepositoryRequest, RepositoryResponse, }, @@ -179,6 +180,7 @@ pub async fn connection_worker( .unwrap(); } } + continue; } else { // This message is targeting this instance match &repository.command { @@ -210,6 +212,8 @@ pub async fn connection_worker( )) .await .unwrap(); + + continue; } RepositoryRequest::RepositoryFileInspect(request) => { let mut backend = backend.lock().await; @@ -240,6 +244,7 @@ pub async fn connection_worker( )) .await .unwrap(); + continue; } RepositoryRequest::RepositoryInfo(request) => { let mut backend = backend.lock().await; @@ -268,6 +273,7 @@ pub async fn connection_worker( )) .await .unwrap(); + continue; } RepositoryRequest::IssuesCount(request) => { let mut backend = backend.lock().await; @@ -296,6 +302,7 @@ pub async fn connection_worker( )) .await .unwrap(); + continue; } RepositoryRequest::IssueLabels(request) => { let mut backend = backend.lock().await; @@ -323,6 +330,7 @@ pub async fn connection_worker( )) .await .unwrap(); + continue; } RepositoryRequest::Issues(request) => { let mut backend = backend.lock().await; @@ -351,6 +359,7 @@ pub async fn connection_worker( )) .await .unwrap(); + continue; } }, RepositoryMessageKind::Response(_response) => { @@ -359,6 +368,31 @@ pub async fn connection_worker( } } } + + if let MessageKind::Authentication(authentication) = &message { + match authentication { + AuthenticationMessage::Request(request) => match request { + AuthenticationRequest::AuthenticationToken(token) => { + let mut granter = auth_granter.lock().await; + + let response = granter.token_request(token.clone()).await.unwrap(); + drop(granter); + + socket + .send(Message::Binary( + serde_json::to_vec(&MessageKind::Authentication( + AuthenticationMessage::Response(crate::messages::authentication::AuthenticationResponse::AuthenticationToken(response)) + )) + .unwrap(), + )) + .await + .unwrap(); + continue; + } + }, + AuthenticationMessage::Response(_) => unreachable!(), + } + } } info!("Connection closed"); diff --git a/src/main.rs b/src/main.rs index e82e6fc..8f4ecea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -38,9 +38,9 @@ async fn main() -> Result<(), Box> { .database(config["postgres"]["database"].as_str().unwrap()) .username(config["postgres"]["user"].as_str().unwrap()) .password(config["postgres"]["password"].as_str().unwrap()) - .log_statements(log::LevelFilter::Off) - .to_owned(); + .log_statements(log::LevelFilter::Off); let db_pool = PgPool::connect_with(db_conn_options).await?; + info!("Connected"); let repository_backend: Arc> = Arc::new(Mutex::new({ let foo: GitBackend = GitBackend { @@ -54,8 +54,11 @@ async fn main() -> Result<(), Box> { config: config.clone(), })); + info!("Connected"); + loop { let stream = accept_stream(&mut listener).await; + info!("Connected"); let (stream, address) = match stream { Ok(stream) => stream, diff --git a/src/messages/mod.rs b/src/messages/mod.rs index 1e003fd..5261597 100644 --- a/src/messages/mod.rs +++ b/src/messages/mod.rs @@ -1,3 +1,12 @@ +use std::{error::Error, fmt::Debug}; + +use rsa::{ + pkcs8::{DecodePrivateKey, DecodePublicKey}, + pss::{Signature, SigningKey, VerifyingKey}, + sha2::Sha256, + signature::{RandomizedSigner, SignatureEncoding, Verifier}, + RsaPrivateKey, RsaPublicKey, +}; use serde::{Deserialize, Serialize}; use crate::handshake::HandshakeMessage; @@ -15,3 +24,76 @@ pub enum MessageKind { Repository(RepositoryMessage), Authentication(AuthenticationMessage), } + +/// An authenticated message. +/// +/// Includes the message, with a digest generated with +/// our private key. +#[derive(Serialize, Deserialize)] +pub struct Authenticated { + #[serde(flatten)] + message: T, + token: String, + digest: Vec, +} + +impl Clone for Authenticated +where + T: Clone + Serialize, +{ + fn clone(&self) -> Self { + Self { + message: self.message.clone(), + token: self.token.clone(), + digest: self.digest.clone(), + } + } +} + +impl Debug for Authenticated +where + T: Debug + Serialize, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Authenticated") + .field("message", &self.message) + .field("token", &self.token) + .field("digest", &self.digest) + .finish() + } +} + +impl Authenticated { + pub fn new(message: T, token: String, private_key: String) -> Result> { + let mut rng = rand::thread_rng(); + + let private_key = RsaPrivateKey::from_pkcs8_pem(&private_key)?; + let signing_key = SigningKey::::new(private_key); + + let message_json = serde_json::to_vec(&message)?; + + let signature = signing_key.sign_with_rng(&mut rng, &message_json); + + Ok(Self { + message, + token, + digest: signature.to_vec(), + }) + } + + pub async fn inner(&self) -> &T { + &self.message + } + + pub async fn validate(&self, key: String) -> Result<(), Box> { + let public_key = RsaPublicKey::from_public_key_pem(&key)?; + + let verifying_key: VerifyingKey = VerifyingKey::new(public_key); + + let message_json = serde_json::to_vec(&self.message)?; + + verifying_key.verify(&message_json, &Signature::try_from(self.digest.as_ref())?)?; + + Ok(()) + } +} diff --git a/src/messages/repository.rs b/src/messages/repository.rs index 6a14064..3e067c8 100644 --- a/src/messages/repository.rs +++ b/src/messages/repository.rs @@ -6,6 +6,8 @@ use crate::model::{ user::User, }; +use super::Authenticated; + #[derive(Clone, Serialize, Deserialize)] pub struct RepositoryMessage { pub target: Repository, @@ -20,7 +22,7 @@ pub enum RepositoryMessageKind { #[derive(Clone, Serialize, Deserialize)] pub enum RepositoryRequest { - CreateRepository(CreateRepositoryRequest), + CreateRepository(Authenticated), RepositoryFileInspect(RepositoryFileInspectRequest), RepositoryInfo(RepositoryInfoRequest), IssuesCount(RepositoryIssuesCountRequest),