JavaScript is disabled, refresh for a better experience. ambee/giterated

ambee/giterated

Git repository hosting, collaboration, and discovery for the Fediverse.

Add token extension

Amber - ⁨2⁩ years ago

parent: tbd commit: ⁨86d028f

Showing ⁨⁨10⁩ changed files⁩ with ⁨⁨309⁩ insertions⁩ and ⁨⁨22⁩ deletions⁩

.vscode/launch.json

View file
@@ -0,0 +1,64 @@
1 {
2 // Use IntelliSense to learn about possible attributes.
3 // Hover to view descriptions of existing attributes.
4 // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 "version": "0.2.0",
6 "configurations": [
7 {
8 "type": "lldb",
9 "request": "launch",
10 "name": "Debug unit tests in library 'giterated-daemon'",
11 "cargo": {
12 "args": [
13 "test",
14 "--no-run",
15 "--lib",
16 "--package=giterated-daemon"
17 ],
18 "filter": {
19 "name": "giterated-daemon",
20 "kind": "lib"
21 }
22 },
23 "args": [],
24 "cwd": "${workspaceFolder}"
25 },
26 {
27 "type": "lldb",
28 "request": "launch",
29 "name": "Debug executable 'giterated-daemon'",
30 "cargo": {
31 "args": [
32 "build",
33 "--bin=giterated-daemon",
34 "--package=giterated-daemon"
35 ],
36 "filter": {
37 "name": "giterated-daemon",
38 "kind": "bin"
39 }
40 },
41 "args": [],
42 "cwd": "${workspaceFolder}"
43 },
44 {
45 "type": "lldb",
46 "request": "launch",
47 "name": "Debug unit tests in executable 'giterated-daemon'",
48 "cargo": {
49 "args": [
50 "test",
51 "--no-run",
52 "--bin=giterated-daemon",
53 "--package=giterated-daemon"
54 ],
55 "filter": {
56 "name": "giterated-daemon",
57 "kind": "bin"
58 }
59 },
60 "args": [],
61 "cwd": "${workspaceFolder}"
62 }
63 ]
64 }
64 \ No newline at end of file

Cargo.toml

View file
@@ -16,7 +16,7 @@ tracing-subscriber = "0.3"
16 16 jsonwebtoken = { version = "*", features = ["use_pem"]}
17 17 log = "*"
18 18 rand = "*"
19 rsa = {version = "*", features = ["sha2"]}
19 rsa = {version = "0.9", features = ["sha2"]}
20 20 reqwest = "*"
21 21
22 22 toml = { version = "0.7" }

src/authentication.rs

View file
@@ -1,12 +1,18 @@
1 use std::{error::Error, time::SystemTime};
1 use std::{error::Error, os::raw, time::SystemTime};
2 2
3 use jsonwebtoken::{encode, Algorithm, EncodingKey};
3 use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, TokenData, Validation};
4 4 use serde::{Deserialize, Serialize};
5 5 use tokio::{fs::File, io::AsyncReadExt};
6 6 use toml::Table;
7 7
8 8 use crate::{
9 messages::authentication::{AuthenticationTokenRequest, AuthenticationTokenResponse},
9 messages::{
10 authentication::{
11 AuthenticationTokenRequest, AuthenticationTokenResponse, TokenExtensionRequest,
12 TokenExtensionResponse,
13 },
14 InstanceAuthenticated,
15 },
10 16 model::{instance::Instance, user::User},
11 17 };
12 18
@@ -72,4 +78,91 @@ impl AuthenticationTokenGranter {
72 78
73 79 Ok(AuthenticationTokenResponse { token })
74 80 }
81
82 pub async fn extension_request(
83 &mut self,
84 raw_request: InstanceAuthenticated<TokenExtensionRequest>,
85 ) -> Result<TokenExtensionResponse, Box<dyn Error + Send>> {
86 let request = raw_request.inner().await;
87
88 let server_public_key = {
89 let mut file = File::open(self.config["keys"]["public"].as_str().unwrap())
90 .await
91 .unwrap();
92
93 let mut key = vec![];
94 file.read_to_end(&mut key).await.unwrap();
95
96 key
97 };
98
99 let verification_key = DecodingKey::from_rsa_pem(&server_public_key).unwrap();
100
101 let data: TokenData<UserTokenMetadata> = decode(
102 &request.token,
103 &verification_key,
104 &Validation::new(Algorithm::RS256),
105 )
106 .unwrap();
107
108 info!("Token Extension Request Token validated");
109
110 let secret_key = self.config["authentication"]["secret_key"]
111 .as_str()
112 .unwrap();
113
114 if request.secret_key != secret_key {
115 error!("Incorrect secret key!");
116
117 panic!()
118 }
119
120 let requester_public_key = public_key(&data.claims.generated_for).await.unwrap();
121
122 // Validate request
123 raw_request.validate(requester_public_key).await.unwrap();
124 info!("Validated request for key extension");
125
126 let private_key = {
127 let mut file = File::open(self.config["keys"]["private"].as_str().unwrap())
128 .await
129 .unwrap();
130
131 let mut key = vec![];
132 file.read_to_end(&mut key).await.unwrap();
133
134 key
135 };
136
137 let encoding_key = EncodingKey::from_rsa_pem(&private_key).unwrap();
138
139 let claims = UserTokenMetadata {
140 // TODO: Probably exploitable
141 user: data.claims.user,
142 generated_for: data.claims.generated_for,
143 exp: (SystemTime::UNIX_EPOCH.elapsed().unwrap()
144 + std::time::Duration::from_secs(24 * 60 * 60))
145 .as_secs(),
146 };
147
148 let token = encode(
149 &jsonwebtoken::Header::new(Algorithm::RS256),
150 &claims,
151 &encoding_key,
152 )
153 .unwrap();
154
155 Ok(TokenExtensionResponse {
156 new_token: Some(token),
157 })
158 }
159 }
160
161 async fn public_key(instance: &Instance) -> Result<String, Box<dyn Error>> {
162 let key = reqwest::get(format!("https://{}/.giterated/pubkey.pem", instance.url))
163 .await?
164 .text()
165 .await?;
166
167 Ok(key)
75 168 }

src/backend/git.rs

View file
@@ -5,7 +5,7 @@ use std::error::Error;
5 5 use std::path::{Path, PathBuf};
6 6 use thiserror::Error;
7 7
8 use crate::messages::Authenticated;
8 use crate::messages::UserAuthenticated;
9 9 use crate::model::instance::Instance;
10 10 use crate::model::repository::{
11 11 Commit, RepositoryObjectType, RepositoryTreeEntry, RepositoryVisibility,
@@ -214,7 +214,7 @@ impl GitBackend {
214 214 impl RepositoryBackend for GitBackend {
215 215 async fn create_repository(
216 216 &mut self,
217 raw_request: &Authenticated<CreateRepositoryRequest>,
217 raw_request: &UserAuthenticated<CreateRepositoryRequest>,
218 218 ) -> Result<CreateRepositoryResponse, Box<dyn Error + Send>> {
219 219 let request = raw_request.inner().await;
220 220
@@ -224,7 +224,14 @@ impl RepositoryBackend for GitBackend {
224 224 .await
225 225 .unwrap();
226 226
227 assert!(matches!(raw_request.validate(public_key).await, Ok(())));
227 match raw_request.validate(public_key).await {
228 Ok(_) => info!("Request was validated"),
229 Err(err) => {
230 error!("Failed to validate request: {:?}", err);
231 panic!();
232 }
233 }
234
228 235 info!("Request was valid!");
229 236
230 237 // Check if repository already exists in the database

src/backend/mod.rs

View file
@@ -16,7 +16,7 @@ use crate::{
16 16 UserBioRequest, UserBioResponse, UserDisplayImageRequest, UserDisplayImageResponse,
17 17 UserDisplayNameRequest, UserDisplayNameResponse,
18 18 },
19 Authenticated,
19 UserAuthenticated,
20 20 },
21 21 model::repository::RepositoryView,
22 22 };
@@ -25,7 +25,7 @@ use crate::{
25 25 pub trait RepositoryBackend: IssuesBackend {
26 26 async fn create_repository(
27 27 &mut self,
28 request: &Authenticated<CreateRepositoryRequest>,
28 request: &UserAuthenticated<CreateRepositoryRequest>,
29 29 ) -> Result<CreateRepositoryResponse, Box<dyn Error + Send>>;
30 30 async fn repository_info(
31 31 &mut self,

src/connection.rs

View file
@@ -17,7 +17,7 @@ use crate::{
17 17 handshake::{HandshakeFinalize, HandshakeMessage, HandshakeResponse},
18 18 listener::Listeners,
19 19 messages::{
20 authentication::{AuthenticationMessage, AuthenticationRequest},
20 authentication::{AuthenticationMessage, AuthenticationRequest, TokenExtensionResponse},
21 21 repository::{
22 22 RepositoryMessage, RepositoryMessageKind, RepositoryRequest, RepositoryResponse,
23 23 },
@@ -61,7 +61,7 @@ pub async fn connection_worker(
61 61 ) {
62 62 let mut handshaked = false;
63 63 let this_instance = Instance {
64 url: String::from("127.0.0.1:8080"),
64 url: String::from("giterated.dev"),
65 65 };
66 66
67 67 while let Some(message) = socket.next().await {
@@ -389,6 +389,26 @@ pub async fn connection_worker(
389 389 .unwrap();
390 390 continue;
391 391 }
392 AuthenticationRequest::TokenExtension(request) => {
393 let mut granter = auth_granter.lock().await;
394
395 let response = granter
396 .extension_request(request.clone())
397 .await
398 .unwrap_or_else(|_| TokenExtensionResponse { new_token: None });
399 drop(granter);
400
401 socket
402 .send(Message::Binary(
403 serde_json::to_vec(&MessageKind::Authentication(
404 AuthenticationMessage::Response(crate::messages::authentication::AuthenticationResponse::TokenExtension(response))
405 ))
406 .unwrap(),
407 ))
408 .await
409 .unwrap();
410 continue;
411 }
392 412 },
393 413 AuthenticationMessage::Response(_) => unreachable!(),
394 414 }

src/main.rs

View file
@@ -40,6 +40,9 @@ async fn main() -> Result<(), Box<dyn Error>> {
40 40 .password(config["postgres"]["password"].as_str().unwrap())
41 41 .log_statements(log::LevelFilter::Off);
42 42 let db_pool = PgPool::connect_with(db_conn_options).await?;
43
44 debug!("Running database migrations...");
45 sqlx::migrate!().run(&db_pool).await?;
43 46 info!("Connected");
44 47
45 48 let repository_backend: Arc<Mutex<dyn RepositoryBackend + Send>> = Arc::new(Mutex::new({

src/messages/authentication.rs

View file
@@ -1,5 +1,7 @@
1 1 use serde::{Deserialize, Serialize};
2 2
3 use super::InstanceAuthenticated;
4
3 5 #[derive(Clone, Serialize, Deserialize)]
4 6 pub enum AuthenticationMessage {
5 7 Request(AuthenticationRequest),
@@ -9,11 +11,13 @@ pub enum AuthenticationMessage {
9 11 #[derive(Clone, Serialize, Deserialize)]
10 12 pub enum AuthenticationRequest {
11 13 AuthenticationToken(AuthenticationTokenRequest),
14 TokenExtension(InstanceAuthenticated<TokenExtensionRequest>),
12 15 }
13 16
14 17 #[derive(Clone, Serialize, Deserialize)]
15 18 pub enum AuthenticationResponse {
16 19 AuthenticationToken(AuthenticationTokenResponse),
20 TokenExtension(TokenExtensionResponse),
17 21 }
18 22
19 23 #[derive(Clone, Serialize, Deserialize)]
@@ -27,3 +31,14 @@ pub struct AuthenticationTokenRequest {
27 31 pub struct AuthenticationTokenResponse {
28 32 pub token: String,
29 33 }
34
35 #[derive(Clone, Serialize, Deserialize)]
36 pub struct TokenExtensionRequest {
37 pub secret_key: String,
38 pub token: String,
39 }
40
41 #[derive(Clone, Serialize, Deserialize)]
42 pub struct TokenExtensionResponse {
43 pub new_token: Option<String>,
44 }

src/messages/mod.rs

View file
@@ -1,15 +1,16 @@
1 1 use std::{error::Error, fmt::Debug};
2 2
3 3 use rsa::{
4 pkcs1::{DecodeRsaPrivateKey, DecodeRsaPublicKey},
4 5 pkcs8::{DecodePrivateKey, DecodePublicKey},
5 6 pss::{Signature, SigningKey, VerifyingKey},
6 7 sha2::Sha256,
7 8 signature::{RandomizedSigner, SignatureEncoding, Verifier},
8 RsaPrivateKey, RsaPublicKey, pkcs1::DecodeRsaPrivateKey,
9 RsaPrivateKey, RsaPublicKey,
9 10 };
10 11 use serde::{Deserialize, Serialize};
11 12
12 use crate::handshake::HandshakeMessage;
13 use crate::{handshake::HandshakeMessage, model::instance::Instance};
13 14
14 15 use self::{authentication::AuthenticationMessage, repository::RepositoryMessage};
15 16
@@ -25,19 +26,98 @@ pub enum MessageKind {
25 26 Authentication(AuthenticationMessage),
26 27 }
27 28
29 /// An authenticated message, where the instance is authenticating
30 /// a request it is making for itself.
31 #[derive(Serialize, Deserialize)]
32 pub struct InstanceAuthenticated<T: Serialize> {
33 message: T,
34 instance: Instance,
35 signature: Vec<u8>,
36 }
37
38 impl<T> Clone for InstanceAuthenticated<T>
39 where
40 T: Clone + Serialize,
41 {
42 fn clone(&self) -> Self {
43 Self {
44 message: self.message.clone(),
45 instance: self.instance.clone(),
46 signature: self.signature.clone(),
47 }
48 }
49 }
50
51 impl<T> Debug for InstanceAuthenticated<T>
52 where
53 T: Debug + Serialize,
54 {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 f.debug_struct("Authenticated")
57 .field("message", &self.message)
58 .field("instance", &self.instance)
59 .field("signature", &self.signature)
60 .finish()
61 }
62 }
63
64 impl<T: Serialize> InstanceAuthenticated<T> {
65 pub fn new(
66 message: T,
67 instance: Instance,
68 private_key: String,
69 ) -> Result<Self, Box<dyn Error>> {
70 let mut rng = rand::thread_rng();
71
72 let private_key = RsaPrivateKey::from_pkcs1_pem(&private_key)?;
73 let signing_key = SigningKey::<Sha256>::new(private_key);
74
75 let message_json = serde_json::to_vec(&message)?;
76
77 let signature = signing_key.sign_with_rng(&mut rng, &message_json);
78
79 Ok(Self {
80 message,
81 instance,
82 signature: signature.to_vec(),
83 })
84 }
85
86 pub async fn inner(&self) -> &T {
87 &self.message
88 }
89
90 pub async fn validate(&self, key: String) -> Result<(), Box<dyn Error>> {
91 let public_key = RsaPublicKey::from_pkcs1_pem(&key).unwrap();
92
93 let verifying_key: VerifyingKey<Sha256> = VerifyingKey::new(public_key);
94
95 let message_json = serde_json::to_vec(&self.message).unwrap();
96
97 verifying_key
98 .verify(
99 &message_json,
100 &Signature::try_from(self.signature.as_ref()).unwrap(),
101 )
102 .unwrap();
103
104 Ok(())
105 }
106 }
107
28 108 /// An authenticated message.
29 109 ///
30 110 /// Includes the message, with a digest generated with
31 111 /// our private key.
32 112 #[derive(Serialize, Deserialize)]
33 pub struct Authenticated<T: Serialize> {
113 pub struct UserAuthenticated<T: Serialize> {
34 114 #[serde(flatten)]
35 115 message: T,
36 116 token: String,
37 117 digest: Vec<u8>,
38 118 }
39 119
40 impl<T> Clone for Authenticated<T>
120 impl<T> Clone for UserAuthenticated<T>
41 121 where
42 122 T: Clone + Serialize,
43 123 {
@@ -50,7 +130,7 @@ where
50 130 }
51 131 }
52 132
53 impl<T> Debug for Authenticated<T>
133 impl<T> Debug for UserAuthenticated<T>
54 134 where
55 135 T: Debug + Serialize,
56 136 {
@@ -63,7 +143,7 @@ where
63 143 }
64 144 }
65 145
66 impl<T: Serialize> Authenticated<T> {
146 impl<T: Serialize> UserAuthenticated<T> {
67 147 pub fn new(message: T, token: String, private_key: String) -> Result<Self, Box<dyn Error>> {
68 148 let mut rng = rand::thread_rng();
69 149
@@ -86,13 +166,18 @@ impl<T: Serialize> Authenticated<T> {
86 166 }
87 167
88 168 pub async fn validate(&self, key: String) -> Result<(), Box<dyn Error>> {
89 let public_key = RsaPublicKey::from_public_key_pem(&key)?;
169 let public_key = RsaPublicKey::from_pkcs1_pem(&key).unwrap();
90 170
91 171 let verifying_key: VerifyingKey<Sha256> = VerifyingKey::new(public_key);
92 172
93 let message_json = serde_json::to_vec(&self.message)?;
173 let message_json = serde_json::to_vec(&self.message).unwrap();
94 174
95 verifying_key.verify(&message_json, &Signature::try_from(self.digest.as_ref())?)?;
175 verifying_key
176 .verify(
177 &message_json,
178 &Signature::try_from(self.digest.as_ref()).unwrap(),
179 )
180 .unwrap();
96 181
97 182 Ok(())
98 183 }

src/messages/repository.rs

View file
@@ -6,7 +6,7 @@ use crate::model::{
6 6 user::User,
7 7 };
8 8
9 use super::Authenticated;
9 use super::UserAuthenticated;
10 10
11 11 #[derive(Clone, Serialize, Deserialize)]
12 12 pub struct RepositoryMessage {
@@ -22,7 +22,7 @@ pub enum RepositoryMessageKind {
22 22
23 23 #[derive(Clone, Serialize, Deserialize)]
24 24 pub enum RepositoryRequest {
25 CreateRepository(Authenticated<CreateRepositoryRequest>),
25 CreateRepository(UserAuthenticated<CreateRepositoryRequest>),
26 26 RepositoryFileInspect(RepositoryFileInspectRequest),
27 27 RepositoryInfo(RepositoryInfoRequest),
28 28 IssuesCount(RepositoryIssuesCountRequest),