diff options
-rw-r--r-- | Cargo.lock | 58 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | src/handlers.rs | 82 | ||||
-rw-r--r-- | src/schema.rs | 1 |
4 files changed, 121 insertions, 22 deletions
@@ -1,6 +1,37 @@ | |||
1 | # This file is automatically @generated by Cargo. | 1 | # This file is automatically @generated by Cargo. |
2 | # It is not intended for manual editing. | 2 | # It is not intended for manual editing. |
3 | [[package]] | 3 | [[package]] |
4 | name = "aes" | ||
5 | version = "0.6.0" | ||
6 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
7 | checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" | ||
8 | dependencies = [ | ||
9 | "aes-soft", | ||
10 | "aesni", | ||
11 | "cipher", | ||
12 | ] | ||
13 | |||
14 | [[package]] | ||
15 | name = "aes-soft" | ||
16 | version = "0.6.4" | ||
17 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
18 | checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" | ||
19 | dependencies = [ | ||
20 | "cipher", | ||
21 | "opaque-debug", | ||
22 | ] | ||
23 | |||
24 | [[package]] | ||
25 | name = "aesni" | ||
26 | version = "0.10.0" | ||
27 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
28 | checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" | ||
29 | dependencies = [ | ||
30 | "cipher", | ||
31 | "opaque-debug", | ||
32 | ] | ||
33 | |||
34 | [[package]] | ||
4 | name = "aho-corasick" | 35 | name = "aho-corasick" |
5 | version = "0.7.15" | 36 | version = "0.7.15" |
6 | source = "registry+https://github.com/rust-lang/crates.io-index" | 37 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -71,6 +102,22 @@ dependencies = [ | |||
71 | ] | 102 | ] |
72 | 103 | ||
73 | [[package]] | 104 | [[package]] |
105 | name = "block-modes" | ||
106 | version = "0.7.0" | ||
107 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
108 | checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" | ||
109 | dependencies = [ | ||
110 | "block-padding", | ||
111 | "cipher", | ||
112 | ] | ||
113 | |||
114 | [[package]] | ||
115 | name = "block-padding" | ||
116 | version = "0.2.1" | ||
117 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
118 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" | ||
119 | |||
120 | [[package]] | ||
74 | name = "buf_redux" | 121 | name = "buf_redux" |
75 | version = "0.8.4" | 122 | version = "0.8.4" |
76 | source = "registry+https://github.com/rust-lang/crates.io-index" | 123 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -137,6 +184,15 @@ dependencies = [ | |||
137 | ] | 184 | ] |
138 | 185 | ||
139 | [[package]] | 186 | [[package]] |
187 | name = "cipher" | ||
188 | version = "0.2.5" | ||
189 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
190 | checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" | ||
191 | dependencies = [ | ||
192 | "generic-array", | ||
193 | ] | ||
194 | |||
195 | [[package]] | ||
140 | name = "cloudabi" | 196 | name = "cloudabi" |
141 | version = "0.0.3" | 197 | version = "0.0.3" |
142 | source = "registry+https://github.com/rust-lang/crates.io-index" | 198 | source = "registry+https://github.com/rust-lang/crates.io-index" |
@@ -319,8 +375,10 @@ dependencies = [ | |||
319 | name = "gradecoin" | 375 | name = "gradecoin" |
320 | version = "0.1.0" | 376 | version = "0.1.0" |
321 | dependencies = [ | 377 | dependencies = [ |
378 | "aes", | ||
322 | "base64 0.13.0", | 379 | "base64 0.13.0", |
323 | "blake2", | 380 | "blake2", |
381 | "block-modes", | ||
324 | "chrono", | 382 | "chrono", |
325 | "hex-literal", | 383 | "hex-literal", |
326 | "jsonwebtoken", | 384 | "jsonwebtoken", |
@@ -23,6 +23,8 @@ md-5 = "0.9.1" | |||
23 | rsa = "0.4.0" | 23 | rsa = "0.4.0" |
24 | base64 = "0.13.0" | 24 | base64 = "0.13.0" |
25 | sha2 = "0.9.3" | 25 | sha2 = "0.9.3" |
26 | block-modes = "0.7.0" | ||
27 | aes = "0.6.0" | ||
26 | 28 | ||
27 | [dev-dependencies] | 29 | [dev-dependencies] |
28 | serde_test = "1.0.117" | 30 | serde_test = "1.0.117" |
diff --git a/src/handlers.rs b/src/handlers.rs index 55d3ab4..21911e1 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
@@ -1,12 +1,15 @@ | |||
1 | use base64; | ||
2 | /// API handlers, the ends of each filter chain | 1 | /// API handlers, the ends of each filter chain |
2 | use aes::Aes128; | ||
3 | use base64; | ||
3 | use blake2::{Blake2s, Digest}; | 4 | use blake2::{Blake2s, Digest}; |
5 | use block_modes::block_padding::Pkcs7; | ||
6 | use block_modes::{BlockMode, Cbc}; | ||
4 | use jsonwebtoken::errors::ErrorKind; | 7 | use jsonwebtoken::errors::ErrorKind; |
5 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | 8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; |
6 | use log::{debug, warn}; | 9 | use log::{debug, warn}; |
7 | use md5::Md5; | 10 | use md5::Md5; |
8 | use parking_lot::RwLockUpgradableReadGuard; | 11 | use parking_lot::RwLockUpgradableReadGuard; |
9 | use rsa::{PaddingScheme, RSAPrivateKey}; | 12 | use rsa::{PaddingScheme, RSAPrivateKey, RSAPublicKey}; |
10 | use serde::Serialize; | 13 | use serde::Serialize; |
11 | use serde_json; | 14 | use serde_json; |
12 | use sha2; | 15 | use sha2; |
@@ -16,6 +19,9 @@ use warp::{http::StatusCode, reply}; | |||
16 | 19 | ||
17 | use crate::PRIVATE_KEY; | 20 | use crate::PRIVATE_KEY; |
18 | 21 | ||
22 | // Encryption primitive | ||
23 | type Aes128Cbc = Cbc<Aes128, Pkcs7>; | ||
24 | |||
19 | #[derive(Serialize, Debug)] | 25 | #[derive(Serialize, Debug)] |
20 | struct GradeCoinResponse { | 26 | struct GradeCoinResponse { |
21 | res: ResponseType, | 27 | res: ResponseType, |
@@ -41,21 +47,21 @@ const BEARER: &str = "Bearer "; | |||
41 | /// the [`AuthRequest.user_id`] of the `request` is not in the list of users that can hold a Gradecoin account | 47 | /// the [`AuthRequest.user_id`] of the `request` is not in the list of users that can hold a Gradecoin account |
42 | /// | 48 | /// |
43 | /// # Authentication Process | 49 | /// # Authentication Process |
44 | /// - Gradecoin's Public Key (`G_PK`) is listed on moodle. | 50 | /// - Gradecoin's Public Key (`gradecoin_public_key`) is listed on moodle. |
45 | /// - Gradecoin's Private Key (`G_PR`) is loaded here | 51 | /// - Gradecoin's Private Key (`gradecoin_private_key`) is loaded here |
46 | /// | 52 | /// |
47 | /// - Student picks a short temporary key (`k_temp`) | 53 | /// - Student picks a short temporary key (`k_temp`) |
48 | /// - Creates a JSON object (`auth_plaintext`) with their `metu_id` and `public key` in base64 (PEM) format (`S_PK`): | 54 | /// - Creates a JSON object (`auth_plaintext`) with their `metu_id` and `public key` in base64 (PEM) format (`S_PK`): |
49 | /// { | 55 | /// { |
50 | /// student_id: "e12345", | 56 | /// student_id: "e12345", |
57 | /// passwd: "15 char secret" | ||
51 | /// public_key: "---BEGIN PUBLIC KEY..." | 58 | /// public_key: "---BEGIN PUBLIC KEY..." |
52 | /// } | 59 | /// } |
53 | /// | 60 | /// |
54 | /// - Encrypts the serialized string of `auth_plaintext` with AES in TODO format using the temporary key | 61 | /// - Encrypts the serialized string of `auth_plaintext` with 128 bit block AES in CBC mode with Pkcs7 padding using the temporary key (`k_temp`), the result is `auth_ciphertext` TODO should this be base64'd? |
55 | /// (`k_temp`), the result is `auth_ciphertext`, (TODO base64?) | 62 | /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme |
56 | /// - The temporary key student has picked `k_temp` is encrypted (TODO details) with `G_PK` (TODO | 63 | /// using sha256 with `gradecoin_public_key` (TODO base64? same as above), giving us `key_ciphertext` |
57 | /// base64?) = `key_ciphertext` | 64 | /// - The payload JSON object (`auth_request`) can be JSON serialized now: |
58 | /// - The payload JSON object (`auth_request`) can be prepared now: | ||
59 | /// { | 65 | /// { |
60 | /// c: "auth_ciphertext" | 66 | /// c: "auth_ciphertext" |
61 | /// key: "key_ciphertext" | 67 | /// key: "key_ciphertext" |
@@ -63,8 +69,10 @@ const BEARER: &str = "Bearer "; | |||
63 | /// | 69 | /// |
64 | /// ## Gradecoin Side | 70 | /// ## Gradecoin Side |
65 | /// | 71 | /// |
66 | /// - Upon receiving, we first extract the temporary key by decrypting `key`, receiving `temp_key` | 72 | /// - Upon receiving, we first RSA decrypt with OAEP padding scheme using SHA256 with `gradecoin_private_key` as the key and auth_request.key `key` as the ciphertext, receiving `temp_key` (this is the temporary key chosen by student) |
67 | /// - With this key, we can decrypt c TODO with aes? | 73 | /// - With `temp_key`, we can AES 128 Cbc Pkcs7 decrypt the `auth_request.c`, giving us |
74 | /// auth_plaintext | ||
75 | /// - The `auth_plaintext` String can be deserialized to [`AuthRequest`] | ||
68 | /// - We then verify the payload and calculate the User fingerprint | 76 | /// - We then verify the payload and calculate the User fingerprint |
69 | /// - Finally, create the new [`User`] object, insert to users HashMap `<fingerprint, User>` | 77 | /// - Finally, create the new [`User`] object, insert to users HashMap `<fingerprint, User>` |
70 | /// | 78 | /// |
@@ -74,8 +82,11 @@ pub async fn authenticate_user( | |||
74 | ) -> Result<impl warp::Reply, warp::Rejection> { | 82 | ) -> Result<impl warp::Reply, warp::Rejection> { |
75 | debug!("POST request to /register, authenticate_user"); | 83 | debug!("POST request to /register, authenticate_user"); |
76 | 84 | ||
85 | // In essence PEM files are just base64 encoded versions of the DER encoded data. | ||
86 | // ~tls.mbed.org | ||
87 | |||
77 | // TODO: lazyload or something <14-04-21, yigit> // | 88 | // TODO: lazyload or something <14-04-21, yigit> // |
78 | // This is our key, used to first decrypt the users temporal key | 89 | // Load our RSA Private Key as DER |
79 | let der_encoded = PRIVATE_KEY | 90 | let der_encoded = PRIVATE_KEY |
80 | .lines() | 91 | .lines() |
81 | .filter(|line| !line.starts_with("-")) | 92 | .filter(|line| !line.starts_with("-")) |
@@ -84,23 +95,28 @@ pub async fn authenticate_user( | |||
84 | data | 95 | data |
85 | }); | 96 | }); |
86 | 97 | ||
98 | // base64(der(pem)) | ||
87 | // Our private key is saved in PEM (base64) format | 99 | // Our private key is saved in PEM (base64) format |
88 | let der_bytes = base64::decode(&der_encoded).expect("failed to decode base64 content"); | 100 | let der_bytes = base64::decode(&der_encoded).expect("failed to decode base64 content"); |
89 | let private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); | 101 | let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); |
90 | 102 | ||
91 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); | 103 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); |
92 | let dec_key = private_key | 104 | let temp_key = gradecoin_private_key |
93 | .decrypt(padding, &request.key.as_bytes()) | 105 | .decrypt(padding, &request.key.as_bytes()) |
94 | .expect("failed to decrypt"); | 106 | .expect("failed to decrypt"); |
95 | 107 | ||
96 | // then decrypt c using key dec_key | 108 | // decrypt c using key dec_key |
109 | let cipher = Aes128Cbc::new_var(&temp_key, &request.iv).unwrap(); | ||
110 | let auth_plaintext = cipher | ||
111 | .decrypt_vec(&base64::decode(request.c).unwrap()) | ||
112 | .unwrap(); | ||
97 | 113 | ||
98 | // let request: AuthRequest = serde_json::from_str(&String::from_utf8(dec_data).unwrap()).unwrap(); | 114 | let request: AuthRequest = |
99 | let request; | 115 | serde_json::from_str(&String::from_utf8(auth_plaintext).unwrap()).unwrap(); |
100 | 116 | ||
101 | let provided_id = request.student_id.clone(); | 117 | let provided_id = request.student_id.clone(); |
102 | 118 | ||
103 | let priv_student_id = match MetuId::new(request.student_id, request.passwd) { | 119 | let privileged_student_id = match MetuId::new(request.student_id) { |
104 | Some(id) => id, | 120 | Some(id) => id, |
105 | None => { | 121 | None => { |
106 | let res_json = warp::reply::json(&GradeCoinResponse { | 122 | let res_json = warp::reply::json(&GradeCoinResponse { |
@@ -117,15 +133,27 @@ pub async fn authenticate_user( | |||
117 | if userlist.contains_key(&provided_id) { | 133 | if userlist.contains_key(&provided_id) { |
118 | let res_json = warp::reply::json(&GradeCoinResponse { | 134 | let res_json = warp::reply::json(&GradeCoinResponse { |
119 | res: ResponseType::Error, | 135 | res: ResponseType::Error, |
120 | message: "This user is already authenticated".to_owned(), | 136 | message: |
137 | "This user is already authenticated, do you think this is a mistake? Contact me" | ||
138 | .to_owned(), | ||
139 | }); | ||
140 | |||
141 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
142 | } | ||
143 | |||
144 | // We're using this as the validator | ||
145 | // I hate myself | ||
146 | if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { | ||
147 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
148 | res: ResponseType::Error, | ||
149 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | ||
121 | }); | 150 | }); |
122 | 151 | ||
123 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | 152 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); |
124 | } | 153 | } |
125 | 154 | ||
126 | // TODO: audit public key, is it valid? <13-04-21, yigit> // | ||
127 | let new_user = User { | 155 | let new_user = User { |
128 | user_id: priv_student_id, | 156 | user_id: privileged_student_id, |
129 | public_key: request.public_key, | 157 | public_key: request.public_key, |
130 | balance: 0, | 158 | balance: 0, |
131 | }; | 159 | }; |
@@ -146,6 +174,17 @@ pub async fn authenticate_user( | |||
146 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) | 174 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) |
147 | } | 175 | } |
148 | 176 | ||
177 | // fn shed_pem_header_footer(maybe_key: String) -> Result<Vec<u8>, String> { | ||
178 | // let der_encoded = maybe_key | ||
179 | // .lines() | ||
180 | // .filter(|line| !line.starts_with("-")) | ||
181 | // .fold(String::new(), |mut data, line| { | ||
182 | // data.push_str(&line); | ||
183 | // data | ||
184 | // }); | ||
185 | // Ok(base64::decode(&der_encoded).expect("failed to decode base64 content")) | ||
186 | // } | ||
187 | |||
149 | /// GET /transaction | 188 | /// GET /transaction |
150 | /// Returns JSON array of transactions | 189 | /// Returns JSON array of transactions |
151 | /// Cannot fail | 190 | /// Cannot fail |
@@ -447,7 +486,6 @@ fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData< | |||
447 | debug!("raw_jwt: {:?}", raw_jwt); | 486 | debug!("raw_jwt: {:?}", raw_jwt); |
448 | 487 | ||
449 | // Extract a jsonwebtoken compatible decoding_key from user's public key | 488 | // Extract a jsonwebtoken compatible decoding_key from user's public key |
450 | // TODO: just use this for reading users pem key <13-04-21, yigit> // | ||
451 | let decoding_key = match DecodingKey::from_rsa_pem(user_pem.as_bytes()) { | 489 | let decoding_key = match DecodingKey::from_rsa_pem(user_pem.as_bytes()) { |
452 | Ok(key) => key, | 490 | Ok(key) => key, |
453 | Err(j) => { | 491 | Err(j) => { |
diff --git a/src/schema.rs b/src/schema.rs index f159d83..fb88640 100644 --- a/src/schema.rs +++ b/src/schema.rs | |||
@@ -190,6 +190,7 @@ pub struct AuthRequest { | |||
190 | #[derive(Serialize, Deserialize, Debug)] | 190 | #[derive(Serialize, Deserialize, Debug)] |
191 | pub struct InitialAuthRequest { | 191 | pub struct InitialAuthRequest { |
192 | pub c: String, | 192 | pub c: String, |
193 | pub iv: [u8; 32], | ||
193 | pub key: String, | 194 | pub key: String, |
194 | } | 195 | } |
195 | 196 | ||