diff options
Diffstat (limited to 'src/handlers.rs')
-rw-r--r-- | src/handlers.rs | 223 |
1 files changed, 167 insertions, 56 deletions
diff --git a/src/handlers.rs b/src/handlers.rs index a8c9947..fe60ded 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
@@ -1,6 +1,6 @@ | |||
1 | /// API handlers, the ends of each filter chain | ||
2 | use aes::Aes128; | 1 | use aes::Aes128; |
3 | use base64; | 2 | /// API handlers, the ends of each filter chain |
3 | use askama::Template; | ||
4 | use blake2::{Blake2s, Digest}; | 4 | use blake2::{Blake2s, Digest}; |
5 | use block_modes::block_padding::Pkcs7; | 5 | use block_modes::block_padding::Pkcs7; |
6 | use block_modes::{BlockMode, Cbc}; | 6 | use block_modes::{BlockMode, Cbc}; |
@@ -8,10 +8,8 @@ use jsonwebtoken::errors::ErrorKind; | |||
8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | 8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; |
9 | use log::{debug, warn}; | 9 | use log::{debug, warn}; |
10 | use md5::Md5; | 10 | use md5::Md5; |
11 | use parking_lot::RwLockUpgradableReadGuard; | ||
12 | use rsa::{PaddingScheme, RSAPrivateKey}; | 11 | use rsa::{PaddingScheme, RSAPrivateKey}; |
13 | use serde::Serialize; | 12 | use serde::Serialize; |
14 | use serde_json; | ||
15 | use sha2::Sha256; | 13 | use sha2::Sha256; |
16 | use std::collections::HashMap; | 14 | use std::collections::HashMap; |
17 | use std::convert::Infallible; | 15 | use std::convert::Infallible; |
@@ -39,6 +37,7 @@ enum ResponseType { | |||
39 | 37 | ||
40 | use crate::schema::{ | 38 | use crate::schema::{ |
41 | AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User, | 39 | AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User, |
40 | UserAtRest, | ||
42 | }; | 41 | }; |
43 | 42 | ||
44 | const BEARER: &str = "Bearer "; | 43 | const BEARER: &str = "Bearer "; |
@@ -61,9 +60,9 @@ const BEARER: &str = "Bearer "; | |||
61 | /// public_key: "---BEGIN PUBLIC KEY..." | 60 | /// public_key: "---BEGIN PUBLIC KEY..." |
62 | /// } | 61 | /// } |
63 | /// | 62 | /// |
64 | /// - 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? | 63 | /// - 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` |
65 | /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme | 64 | /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme |
66 | /// using sha256 with `gradecoin_public_key` (TODO base64? same as above), giving us `key_ciphertext` | 65 | /// using sha256 with `gradecoin_public_key`, giving us `key_ciphertext` |
67 | /// - The payload JSON object (`auth_request`) can be JSON serialized now: | 66 | /// - The payload JSON object (`auth_request`) can be JSON serialized now: |
68 | /// { | 67 | /// { |
69 | /// c: "auth_ciphertext" | 68 | /// c: "auth_ciphertext" |
@@ -92,7 +91,7 @@ pub async fn authenticate_user( | |||
92 | // Load our RSA Private Key as DER | 91 | // Load our RSA Private Key as DER |
93 | let der_encoded = PRIVATE_KEY | 92 | let der_encoded = PRIVATE_KEY |
94 | .lines() | 93 | .lines() |
95 | .filter(|line| !line.starts_with("-")) | 94 | .filter(|line| !line.starts_with('-')) |
96 | .fold(String::new(), |mut data, line| { | 95 | .fold(String::new(), |mut data, line| { |
97 | data.push_str(&line); | 96 | data.push_str(&line); |
98 | data | 97 | data |
@@ -104,18 +103,126 @@ pub async fn authenticate_user( | |||
104 | let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); | 103 | let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); |
105 | 104 | ||
106 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); | 105 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); |
107 | let temp_key = gradecoin_private_key | ||
108 | .decrypt(padding, &request.key.as_bytes()) | ||
109 | .expect("failed to decrypt"); | ||
110 | 106 | ||
111 | // decrypt c using key dec_key | 107 | let key_ciphertext = match base64::decode(&request.key) { |
112 | let cipher = Aes128Cbc::new_var(&temp_key, &request.iv).unwrap(); | 108 | Ok(c) => c, |
113 | let auth_plaintext = cipher | 109 | Err(err) => { |
114 | .decrypt_vec(&base64::decode(request.c).unwrap()) | 110 | debug!( |
115 | .unwrap(); | 111 | "The ciphertext of the key was not base64 encoded {}, {}", |
112 | &request.key, err | ||
113 | ); | ||
114 | |||
115 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
116 | res: ResponseType::Error, | ||
117 | message: "The ciphertext of the key was not base64 encoded {}, {}".to_owned(), | ||
118 | }); | ||
119 | |||
120 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
121 | } | ||
122 | }; | ||
123 | |||
124 | let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) { | ||
125 | Ok(k) => k, | ||
126 | Err(err) => { | ||
127 | debug!( | ||
128 | "Failed to decrypt ciphertext {:?}, {}", | ||
129 | &key_ciphertext, err | ||
130 | ); | ||
131 | |||
132 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
133 | res: ResponseType::Error, | ||
134 | message: "Failed to decrypt the ciphertext of the temporary key".to_owned(), | ||
135 | }); | ||
136 | |||
137 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
138 | } | ||
139 | }; | ||
140 | |||
141 | let cipher = match Aes128Cbc::new_var(&temp_key, &request.iv.as_bytes()) { | ||
142 | Ok(c) => c, | ||
143 | Err(err) => { | ||
144 | debug!( | ||
145 | "Could not create a cipher from temp_key and request.iv {:?}, {}, {}", | ||
146 | &temp_key, &request.iv, err | ||
147 | ); | ||
148 | |||
149 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
150 | res: ResponseType::Error, | ||
151 | message: "Given IV has invalid length".to_owned(), | ||
152 | }); | ||
153 | |||
154 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
155 | } | ||
156 | }; | ||
116 | 157 | ||
117 | let request: AuthRequest = | 158 | let auth_packet = match base64::decode(&request.c) { |
118 | serde_json::from_str(&String::from_utf8(auth_plaintext).unwrap()).unwrap(); | 159 | Ok(a) => a, |
160 | |||
161 | Err(err) => { | ||
162 | debug!( | ||
163 | "The auth_packet (c field) did not base64 decode {} {}", | ||
164 | &request.c, err | ||
165 | ); | ||
166 | |||
167 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
168 | res: ResponseType::Error, | ||
169 | message: "The c field was not correctly base64 encoded".to_owned(), | ||
170 | }); | ||
171 | |||
172 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
173 | } | ||
174 | }; | ||
175 | |||
176 | let auth_plaintext = match cipher.decrypt_vec(&auth_packet) { | ||
177 | Ok(p) => p, | ||
178 | Err(err) => { | ||
179 | debug!( | ||
180 | "Base64 decoded auth request did not decrypt correctly {:?} {}", | ||
181 | &auth_packet, err | ||
182 | ); | ||
183 | |||
184 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
185 | res: ResponseType::Error, | ||
186 | message: "The Bas64 decoded auth request did not decrypt correctly".to_owned(), | ||
187 | }); | ||
188 | |||
189 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
190 | } | ||
191 | }; | ||
192 | |||
193 | let utf8_auth_plaintext = match String::from_utf8(auth_plaintext.clone()) { | ||
194 | Ok(text) => text, | ||
195 | Err(err) => { | ||
196 | debug!( | ||
197 | "Auth plaintext did not convert into utf8 {:?} {}", | ||
198 | &auth_plaintext, err | ||
199 | ); | ||
200 | |||
201 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
202 | res: ResponseType::Error, | ||
203 | message: "Auth plaintext couldn't get converted to UTF-8".to_owned(), | ||
204 | }); | ||
205 | |||
206 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
207 | } | ||
208 | }; | ||
209 | |||
210 | let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) { | ||
211 | Ok(req) => req, | ||
212 | Err(err) => { | ||
213 | debug!( | ||
214 | "Auth plaintext did not serialize correctly {:?} {}", | ||
215 | &utf8_auth_plaintext, err | ||
216 | ); | ||
217 | |||
218 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
219 | res: ResponseType::Error, | ||
220 | message: "The auth request JSON did not serialize correctly".to_owned(), | ||
221 | }); | ||
222 | |||
223 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
224 | } | ||
225 | }; | ||
119 | 226 | ||
120 | let provided_id = request.student_id.clone(); | 227 | let provided_id = request.student_id.clone(); |
121 | 228 | ||
@@ -131,33 +238,24 @@ pub async fn authenticate_user( | |||
131 | } | 238 | } |
132 | }; | 239 | }; |
133 | 240 | ||
134 | let userlist = db.users.upgradable_read(); | 241 | { |
135 | 242 | let userlist = db.users.read(); | |
136 | if userlist.contains_key(&provided_id) { | ||
137 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
138 | res: ResponseType::Error, | ||
139 | message: | ||
140 | "This user is already authenticated, do you think this is a mistake? Contact me" | ||
141 | .to_owned(), | ||
142 | }); | ||
143 | |||
144 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
145 | } | ||
146 | 243 | ||
147 | // We're using this as the validator | 244 | if userlist.contains_key(&provided_id) { |
148 | // I hate myself | 245 | let res_json = warp::reply::json(&GradeCoinResponse { |
149 | if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { | 246 | res: ResponseType::Error, |
150 | let res_json = warp::reply::json(&GradeCoinResponse { | 247 | message: |
151 | res: ResponseType::Error, | 248 | "This user is already authenticated, do you think this is a mistake? Contact me" |
152 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | 249 | .to_owned(), |
153 | }); | 250 | }); |
154 | 251 | ||
155 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | 252 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); |
253 | } | ||
156 | } | 254 | } |
157 | 255 | ||
158 | // We're using this as the validator | 256 | // We're using this as the validator |
159 | // I hate myself | 257 | // I hate myself |
160 | if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { | 258 | if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { |
161 | let res_json = warp::reply::json(&GradeCoinResponse { | 259 | let res_json = warp::reply::json(&GradeCoinResponse { |
162 | res: ResponseType::Error, | 260 | res: ResponseType::Error, |
163 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | 261 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), |
@@ -174,11 +272,19 @@ pub async fn authenticate_user( | |||
174 | balance: 0, | 272 | balance: 0, |
175 | }; | 273 | }; |
176 | 274 | ||
177 | let user_json = serde_json::to_string(&new_user).unwrap(); | 275 | let user_at_rest_json = serde_json::to_string(&UserAtRest { |
276 | user: User { | ||
277 | user_id: new_user.user_id.clone(), | ||
278 | public_key: new_user.public_key.clone(), | ||
279 | balance: 0, | ||
280 | }, | ||
281 | fingerprint: fingerprint.clone(), | ||
282 | }) | ||
283 | .unwrap(); | ||
178 | 284 | ||
179 | fs::write(format!("users/{}.guy", new_user.user_id), user_json).unwrap(); | 285 | fs::write(format!("users/{}.guy", new_user.user_id), user_at_rest_json).unwrap(); |
180 | 286 | ||
181 | let mut userlist = RwLockUpgradableReadGuard::upgrade(userlist); | 287 | let mut userlist = db.users.write(); |
182 | 288 | ||
183 | userlist.insert(fingerprint.clone(), new_user); | 289 | userlist.insert(fingerprint.clone(), new_user); |
184 | 290 | ||
@@ -193,17 +299,6 @@ pub async fn authenticate_user( | |||
193 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) | 299 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) |
194 | } | 300 | } |
195 | 301 | ||
196 | // fn shed_pem_header_footer(maybe_key: String) -> Result<Vec<u8>, String> { | ||
197 | // let der_encoded = maybe_key | ||
198 | // .lines() | ||
199 | // .filter(|line| !line.starts_with("-")) | ||
200 | // .fold(String::new(), |mut data, line| { | ||
201 | // data.push_str(&line); | ||
202 | // data | ||
203 | // }); | ||
204 | // Ok(base64::decode(&der_encoded).expect("failed to decode base64 content")) | ||
205 | // } | ||
206 | |||
207 | /// GET /transaction | 302 | /// GET /transaction |
208 | /// Returns JSON array of transactions | 303 | /// Returns JSON array of transactions |
209 | /// Cannot fail | 304 | /// Cannot fail |
@@ -241,7 +336,7 @@ pub async fn authorized_propose_block( | |||
241 | 336 | ||
242 | println!("{:?}", &new_block); | 337 | println!("{:?}", &new_block); |
243 | 338 | ||
244 | if new_block.transaction_list.len() < 1 { | 339 | if new_block.transaction_list.is_empty() { |
245 | let res_json = warp::reply::json(&GradeCoinResponse { | 340 | let res_json = warp::reply::json(&GradeCoinResponse { |
246 | res: ResponseType::Error, | 341 | res: ResponseType::Error, |
247 | message: format!( | 342 | message: format!( |
@@ -322,8 +417,8 @@ pub async fn authorized_propose_block( | |||
322 | 417 | ||
323 | let naked_block = NakedBlock { | 418 | let naked_block = NakedBlock { |
324 | transaction_list: new_block.transaction_list.clone(), | 419 | transaction_list: new_block.transaction_list.clone(), |
325 | nonce: new_block.nonce.clone(), | 420 | nonce: new_block.nonce, |
326 | timestamp: new_block.timestamp.clone(), | 421 | timestamp: new_block.timestamp, |
327 | }; | 422 | }; |
328 | 423 | ||
329 | let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); | 424 | let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); |
@@ -556,7 +651,7 @@ pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> { | |||
556 | /// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" | 651 | /// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" |
557 | /// *[`user_pem`]: User Public Key, "BEGIN RSA" | 652 | /// *[`user_pem`]: User Public Key, "BEGIN RSA" |
558 | /// NOT async, might look into it if this becomes a bottleneck | 653 | /// NOT async, might look into it if this becomes a bottleneck |
559 | fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData<Claims>, String> { | 654 | fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result<TokenData<Claims>, String> { |
560 | // Throw away the "Bearer " part | 655 | // Throw away the "Bearer " part |
561 | let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); | 656 | let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); |
562 | debug!("raw_jwt: {:?}", raw_jwt); | 657 | debug!("raw_jwt: {:?}", raw_jwt); |
@@ -599,3 +694,19 @@ fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData< | |||
599 | 694 | ||
600 | Ok(token_payload) | 695 | Ok(token_payload) |
601 | } | 696 | } |
697 | |||
698 | #[derive(Template)] | ||
699 | #[template(path = "welcome.html")] | ||
700 | struct WelcomeTemplate<'a> { | ||
701 | title: &'a str, | ||
702 | body: &'a str, | ||
703 | } | ||
704 | |||
705 | pub async fn welcome_handler() -> Result<impl warp::Reply, warp::Rejection> { | ||
706 | let template = WelcomeTemplate { | ||
707 | title: "Welcome", | ||
708 | body: "To The Bookstore!", | ||
709 | }; | ||
710 | let res = template.render().unwrap(); | ||
711 | Ok(warp::reply::html(res)) | ||
712 | } | ||