aboutsummaryrefslogtreecommitdiffstats
path: root/src/handlers.rs
diff options
context:
space:
mode:
authorYigit Sever2022-04-11 19:01:42 +0300
committerYigit Sever2022-04-11 19:01:42 +0300
commit426e83ec6fba028692bed334803ae9d3a645cb18 (patch)
treea92c2d62f54e28493d1d3b3ddfc185c1ed5cf4a5 /src/handlers.rs
parent04c3b4597a995d73e6925c5391bfa27d27f298b7 (diff)
downloadgradecoin-426e83ec6fba028692bed334803ae9d3a645cb18.tar.gz
gradecoin-426e83ec6fba028692bed334803ae9d3a645cb18.tar.bz2
gradecoin-426e83ec6fba028692bed334803ae9d3a645cb18.zip
[WIP] Spring cleaning
Diffstat (limited to 'src/handlers.rs')
-rw-r--r--src/handlers.rs158
1 files changed, 75 insertions, 83 deletions
diff --git a/src/handlers.rs b/src/handlers.rs
index 2e9964c..8d8b62f 100644
--- a/src/handlers.rs
+++ b/src/handlers.rs
@@ -1,24 +1,27 @@
1/// API handlers, the ends of each filter chain 1/// API handlers, the ends of each filter chain
2use crate::block::{AuthRequest, Block, Claims, InitialAuthRequest, NakedBlock, Transaction};
3use crate::student::{MetuId, User, UserAtRest};
4use crate::Db;
2use aes::Aes128; 5use aes::Aes128;
3use askama::Template; 6use askama::Template;
4use blake2::{Blake2s, Digest}; 7use blake2::{Blake2s, Digest};
5use block_modes::block_padding::Pkcs7; 8use block_modes::{block_padding::Pkcs7, BlockMode, Cbc};
6use block_modes::{BlockMode, Cbc};
7use chrono::Utc; 9use chrono::Utc;
8use jsonwebtoken::errors::ErrorKind; 10use jsonwebtoken::errors::ErrorKind;
9use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; 11use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation};
10use lazy_static::lazy_static; 12use lazy_static::lazy_static;
11use log::{debug, warn}; 13use log::{debug, warn};
12use math;
13use md5::Md5; 14use md5::Md5;
14use parking_lot::RwLockUpgradableReadGuard; 15use parking_lot::RwLockUpgradableReadGuard;
15use rsa::{PaddingScheme, RSAPrivateKey}; 16use rsa::{PaddingScheme, RSAPrivateKey};
16use serde::Serialize; 17use serde::Serialize;
17use sha2::Sha256; 18use sha2::Sha256;
18use std::collections::{HashMap, HashSet}; 19use std::{
19use std::convert::Infallible; 20 collections::{HashMap, HashSet},
20use std::fs; 21 convert::Infallible,
21use std::hash::Hash; 22 fs,
23 hash::Hash,
24};
22use warp::{http::StatusCode, reply}; 25use warp::{http::StatusCode, reply};
23 26
24use crate::PRIVATE_KEY; 27use crate::PRIVATE_KEY;
@@ -52,11 +55,6 @@ enum ResponseType {
52 Error, 55 Error,
53} 56}
54 57
55use crate::schema::{
56 AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User,
57 UserAtRest,
58};
59
60const BEARER: &str = "Bearer "; 58const BEARER: &str = "Bearer ";
61 59
62lazy_static! { 60lazy_static! {
@@ -64,7 +62,7 @@ lazy_static! {
64 .lines() 62 .lines()
65 .filter(|line| !line.starts_with('-')) 63 .filter(|line| !line.starts_with('-'))
66 .fold(String::new(), |mut data, line| { 64 .fold(String::new(), |mut data, line| {
67 data.push_str(&line); 65 data.push_str(line);
68 data 66 data
69 }); 67 });
70 68
@@ -87,9 +85,9 @@ lazy_static! {
87/// - Student picks a short temporary key (`k_temp`) 85/// - Student picks a short temporary key (`k_temp`)
88/// - Creates a JSON object (`auth_plaintext`) with their `metu_id` and `public key` in base64 (PEM) format (`S_PK`): 86/// - Creates a JSON object (`auth_plaintext`) with their `metu_id` and `public key` in base64 (PEM) format (`S_PK`):
89/// { 87/// {
90/// student_id: "e12345", 88/// `student_id`: "e12345",
91/// passwd: "15 char secret" 89/// `passwd`: "15 char secret"
92/// public_key: "---BEGIN PUBLIC KEY..." 90/// `public_key`: "---BEGIN PUBLIC KEY..."
93/// } 91/// }
94/// 92///
95/// - 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` 93/// - 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`
@@ -97,18 +95,17 @@ lazy_static! {
97/// using sha256 with `gradecoin_public_key`, giving us `key_ciphertext` 95/// using sha256 with `gradecoin_public_key`, giving us `key_ciphertext`
98/// - The payload JSON object (`auth_request`) can be JSON serialized now: 96/// - The payload JSON object (`auth_request`) can be JSON serialized now:
99/// { 97/// {
100/// c: "auth_ciphertext" 98/// c: "`auth_ciphertext`"
101/// key: "key_ciphertext" 99/// key: "`key_ciphertext`"
102/// } 100/// }
103/// 101///
104/// ## Gradecoin Side 102/// ## Gradecoin Side
105/// 103///
106/// - 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) 104/// - 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)
107/// - With `temp_key`, we can AES 128 Cbc Pkcs7 decrypt the `auth_request.c`, giving us 105/// - With `temp_key`, we can AES 128 Cbc Pkcs7 decrypt the `auth_request.c`, giving us `auth_plaintext`
108/// auth_plaintext
109/// - The `auth_plaintext` String can be deserialized to [`AuthRequest`] 106/// - The `auth_plaintext` String can be deserialized to [`AuthRequest`]
110/// - We then verify the payload and calculate the User fingerprint 107/// - We then verify the payload and calculate the User fingerprint
111/// - Finally, create the new [`User`] object, insert to users HashMap `<fingerprint, User>` 108/// - Finally, create the new [`User`] object, insert to users `HashMap` `<fingerprint, User>`
112/// 109///
113pub async fn authenticate_user( 110pub async fn authenticate_user(
114 request: InitialAuthRequest, 111 request: InitialAuthRequest,
@@ -226,7 +223,7 @@ pub async fn authenticate_user(
226 223
227 // c field was properly base64 encoded, now available in auth_packet 224 // c field was properly base64 encoded, now available in auth_packet
228 // decryptor was setup properly, with the correct lenght key 225 // decryptor was setup properly, with the correct lenght key
229 let mut buf = auth_packet.to_vec(); 226 let mut buf = auth_packet;
230 let auth_plaintext = match cipher.decrypt(&mut buf) { 227 let auth_plaintext = match cipher.decrypt(&mut buf) {
231 Ok(p) => p, 228 Ok(p) => p,
232 Err(err) => { 229 Err(err) => {
@@ -284,22 +281,21 @@ pub async fn authenticate_user(
284 281
285 // is the student in AuthRequest privileged? 282 // is the student in AuthRequest privileged?
286 let privileged_student_id = 283 let privileged_student_id =
287 match MetuId::new(request.student_id.clone(), request.passwd.clone()) { 284 if let Some(id) = MetuId::new(request.student_id.clone(), request.passwd.clone()) {
288 Some(id) => id, 285 id
289 None => { 286 } else {
290 debug!( 287 debug!(
291 "Someone tried to auth with invalid credentials: {} {}", 288 "Someone tried to auth with invalid credentials: {} {}",
292 &request.student_id, &request.passwd 289 &request.student_id, &request.passwd
293 ); 290 );
294 let res_json = warp::reply::json(&GradeCoinResponse { 291 let res_json = warp::reply::json(&GradeCoinResponse {
295 res: ResponseType::Error, 292 res: ResponseType::Error,
296 message: 293 message:
297 "The credentials given ('student_id', 'passwd') cannot hold a Gradecoin account" 294 "The credentials given ('student_id', 'passwd') cannot hold a Gradecoin account"
298 .to_owned(), 295 .to_owned(),
299 }); 296 });
300 297
301 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 298 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
302 }
303 }; 299 };
304 300
305 // Students should be able to authenticate once 301 // Students should be able to authenticate once
@@ -329,7 +325,7 @@ pub async fn authenticate_user(
329 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 325 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
330 } 326 }
331 327
332 let fingerprint = format!("{:x}", Sha256::digest(&request.public_key.as_bytes())); 328 let fingerprint = format!("{:x}", Sha256::digest(request.public_key.as_bytes()));
333 329
334 let new_user = User { 330 let new_user = User {
335 user_id: privileged_student_id, 331 user_id: privileged_student_id,
@@ -387,7 +383,7 @@ pub async fn list_transactions(db: Db) -> Result<impl warp::Reply, Infallible> {
387/// Proposes a new block for the next round. 383/// Proposes a new block for the next round.
388/// Can reject the block 384/// Can reject the block
389/// 385///
390/// The proposer has to put their transaction as the first transaction of the [`transaction_list`]. 386/// The proposer has to put their transaction as the first transaction of the transaction_list.
391/// This is the analogue of `coinbase` in Bitcoin works 387/// This is the analogue of `coinbase` in Bitcoin works
392/// 388///
393/// The `coinbase` transaction also gets something for their efforts. 389/// The `coinbase` transaction also gets something for their efforts.
@@ -420,9 +416,10 @@ pub async fn propose_block(
420 let pending_transactions = db.pending_transactions.upgradable_read(); 416 let pending_transactions = db.pending_transactions.upgradable_read();
421 417
422 // we get the proposers fingerprint by finding the transaction (id) then extracting the source 418 // we get the proposers fingerprint by finding the transaction (id) then extracting the source
423 let internal_user_fingerprint = match pending_transactions.get(&new_block.transaction_list[0]) { 419 let internal_user_fingerprint =
424 Some(coinbase) => &coinbase.source, 420 if let Some(coinbase) = pending_transactions.get(&new_block.transaction_list[0]) {
425 None => { 421 &coinbase.source
422 } else {
426 debug!( 423 debug!(
427 "Transaction with id {} is not found in the pending_transactions", 424 "Transaction with id {} is not found in the pending_transactions",
428 new_block.transaction_list[0] 425 new_block.transaction_list[0]
@@ -434,34 +431,31 @@ pub async fn propose_block(
434 }); 431 });
435 432
436 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 433 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
437 } 434 };
438 };
439 435
440 let users_store = db.users.upgradable_read(); 436 let users_store = db.users.upgradable_read();
441 437
442 // this probably cannot fail, if the transaction is valid then it must've been checked already 438 // this probably cannot fail, if the transaction is valid then it must've been checked already
443 let internal_user = match users_store.get(internal_user_fingerprint) { 439 let internal_user = if let Some(existing_user) = users_store.get(internal_user_fingerprint) {
444 Some(existing_user) => existing_user, 440 existing_user
445 None => { 441 } else {
446 debug!( 442 debug!(
447 "User with public key signature {:?} is not found in the database", 443 "User with public key signature {:?} is not found in the database",
448 new_block.transaction_list[0] 444 new_block.transaction_list[0]
449 ); 445 );
450 446
451 let res_json = warp::reply::json(&GradeCoinResponse { 447 let res_json = warp::reply::json(&GradeCoinResponse {
452 res: ResponseType::Error, 448 res: ResponseType::Error,
453 message: "User with that public key signature is not found in the database" 449 message: "User with that public key signature is not found in the database".to_owned(),
454 .to_owned(), 450 });
455 });
456 451
457 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); 452 return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST));
458 }
459 }; 453 };
460 454
461 let proposer_public_key = &internal_user.public_key; 455 let proposer_public_key = &internal_user.public_key;
462 456
463 // JWT Check 457 // JWT Check
464 let token_payload = match authorize_proposer(token, &proposer_public_key) { 458 let token_payload = match authorize_proposer(&token, proposer_public_key) {
465 Ok(data) => data, 459 Ok(data) => data,
466 Err(below) => { 460 Err(below) => {
467 debug!("Something went wrong with the JWT {:?}", below); 461 debug!("Something went wrong with the JWT {:?}", below);
@@ -501,7 +495,7 @@ pub async fn propose_block(
501 } 495 }
502 496
503 // Are transactions in the block valid? 497 // Are transactions in the block valid?
504 for transaction_hash in new_block.transaction_list.iter() { 498 for transaction_hash in &new_block.transaction_list {
505 if !pending_transactions.contains_key(transaction_hash) { 499 if !pending_transactions.contains_key(transaction_hash) {
506 let res_json = warp::reply::json(&GradeCoinResponse { 500 let res_json = warp::reply::json(&GradeCoinResponse {
507 res: ResponseType::Error, 501 res: ResponseType::Error,
@@ -536,7 +530,7 @@ pub async fn propose_block(
536 } 530 }
537 531
538 // Are the 6 leftmost characters (=24 bits) zero? 532 // Are the 6 leftmost characters (=24 bits) zero?
539 let should_zero = hashvalue[0] as i32 + hashvalue[1] as i32 + hashvalue[2] as i32; 533 let should_zero = i32::from(hashvalue[0]) + i32::from(hashvalue[1]) + i32::from(hashvalue[2]);
540 534
541 if should_zero != 0 { 535 if should_zero != 0 {
542 debug!("the hash does not have 6 rightmost zero bits"); 536 debug!("the hash does not have 6 rightmost zero bits");
@@ -566,7 +560,7 @@ pub async fn propose_block(
566 let holding: HashMap<String, Transaction> = HashMap::new(); 560 let holding: HashMap<String, Transaction> = HashMap::new();
567 561
568 // Play out the transactions 562 // Play out the transactions
569 for fingerprint in new_block.transaction_list.iter() { 563 for fingerprint in &new_block.transaction_list {
570 if let Some(transaction) = pending_transactions.remove(fingerprint) { 564 if let Some(transaction) = pending_transactions.remove(fingerprint) {
571 let source = &transaction.source; 565 let source = &transaction.source;
572 let target = &transaction.target; 566 let target = &transaction.target;
@@ -581,7 +575,7 @@ pub async fn propose_block(
581 if is_source_bot { 575 if is_source_bot {
582 // Add staking reward 576 // Add staking reward
583 to.balance += 577 to.balance +=
584 math::round::ceil((transaction.amount as f64) * STAKING_REWARD, 0) 578 math::round::ceil((f64::from(transaction.amount)) * STAKING_REWARD, 0)
585 as u16; 579 as u16;
586 } 580 }
587 } 581 }
@@ -592,8 +586,8 @@ pub async fn propose_block(
592 pending_transactions.insert( 586 pending_transactions.insert(
593 transaction_id, 587 transaction_id,
594 Transaction { 588 Transaction {
595 source: target.to_owned(), 589 source: target.clone(),
596 target: source.to_owned(), 590 target: source.clone(),
597 amount: transaction.amount, 591 amount: transaction.amount,
598 timestamp: Utc::now().naive_local(), 592 timestamp: Utc::now().naive_local(),
599 }, 593 },
@@ -602,8 +596,8 @@ pub async fn propose_block(
602 } 596 }
603 } 597 }
604 598
605 for (fp, tx) in holding.iter() { 599 for (fp, tx) in &holding {
606 pending_transactions.insert(fp.to_owned(), tx.to_owned()); 600 pending_transactions.insert(fp.clone(), tx.clone());
607 } 601 }
608 602
609 // just update everyone's .guy file 603 // just update everyone's .guy file
@@ -665,23 +659,21 @@ pub async fn propose_transaction(
665 let users_store = db.users.read(); 659 let users_store = db.users.read();
666 660
667 // Is this transaction from an authorized source? 661 // Is this transaction from an authorized source?
668 let internal_user = match users_store.get(&new_transaction.source) { 662 let internal_user = if let Some(existing_user) = users_store.get(&new_transaction.source) {
669 Some(existing_user) => existing_user, 663 existing_user
670 None => { 664 } else {
671 debug!( 665 debug!(
672 "User with public key signature {:?} is not found in the database", 666 "User with public key signature {:?} is not found in the database",
673 new_transaction.source 667 new_transaction.source
674 ); 668 );
675 669
676 return Ok(warp::reply::with_status( 670 return Ok(warp::reply::with_status(
677 warp::reply::json(&GradeCoinResponse { 671 warp::reply::json(&GradeCoinResponse {
678 res: ResponseType::Error, 672 res: ResponseType::Error,
679 message: "User with the given public key signature is not authorized" 673 message: "User with the given public key signature is not authorized".to_owned(),
680 .to_owned(), 674 }),
681 }), 675 StatusCode::BAD_REQUEST,
682 StatusCode::BAD_REQUEST, 676 ));
683 ));
684 }
685 }; 677 };
686 678
687 if internal_user.is_bot { 679 if internal_user.is_bot {
@@ -702,7 +694,7 @@ pub async fn propose_transaction(
702 // *this* point 694 // *this* point
703 let proposer_public_key = &internal_user.public_key; 695 let proposer_public_key = &internal_user.public_key;
704 696
705 let token_payload = match authorize_proposer(token, &proposer_public_key) { 697 let token_payload = match authorize_proposer(&token, proposer_public_key) {
706 Ok(data) => data, 698 Ok(data) => data,
707 Err(below) => { 699 Err(below) => {
708 debug!("JWT Error: {:?}", below); 700 debug!("JWT Error: {:?}", below);
@@ -815,7 +807,7 @@ pub async fn propose_transaction(
815 807
816 debug!("Taking the hash of {}", serd_tx); 808 debug!("Taking the hash of {}", serd_tx);
817 809
818 let hashed_transaction = Md5::digest(&serd_tx.as_bytes()); 810 let hashed_transaction = Md5::digest(serd_tx.as_bytes());
819 if token_payload.claims.tha != format!("{:x}", hashed_transaction) { 811 if token_payload.claims.tha != format!("{:x}", hashed_transaction) {
820 return Ok(warp::reply::with_status( 812 return Ok(warp::reply::with_status(
821 warp::reply::json(&GradeCoinResponse { 813 warp::reply::json(&GradeCoinResponse {
@@ -859,7 +851,7 @@ pub async fn list_blocks(db: Db) -> Result<impl warp::Reply, Infallible> {
859/// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" 851/// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc"
860/// *[`user_pem`]: User Public Key, "BEGIN RSA" 852/// *[`user_pem`]: User Public Key, "BEGIN RSA"
861/// NOT async, might look into it if this becomes a bottleneck 853/// NOT async, might look into it if this becomes a bottleneck
862fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result<TokenData<Claims>, String> { 854fn authorize_proposer(jwt_token: &str, user_pem: &str) -> Result<TokenData<Claims>, String> {
863 // Throw away the "Bearer " part 855 // Throw away the "Bearer " part
864 let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); 856 let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned();
865 857
@@ -929,7 +921,7 @@ pub async fn user_list_handler(db: Db) -> Result<impl warp::Reply, warp::Rejecti
929 921
930 for (fingerprint, user) in users.iter() { 922 for (fingerprint, user) in users.iter() {
931 sane_users.push(DisplayUsers { 923 sane_users.push(DisplayUsers {
932 fingerprint: fingerprint.to_owned(), 924 fingerprint: fingerprint.clone(),
933 balance: user.balance, 925 balance: user.balance,
934 is_bot: user.is_bot, 926 is_bot: user.is_bot,
935 }); 927 });