diff options
Diffstat (limited to 'src/handlers.rs')
-rw-r--r-- | src/handlers.rs | 158 |
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 |
2 | use crate::block::{AuthRequest, Block, Claims, InitialAuthRequest, NakedBlock, Transaction}; | ||
3 | use crate::student::{MetuId, User, UserAtRest}; | ||
4 | use crate::Db; | ||
2 | use aes::Aes128; | 5 | use aes::Aes128; |
3 | use askama::Template; | 6 | use askama::Template; |
4 | use blake2::{Blake2s, Digest}; | 7 | use blake2::{Blake2s, Digest}; |
5 | use block_modes::block_padding::Pkcs7; | 8 | use block_modes::{block_padding::Pkcs7, BlockMode, Cbc}; |
6 | use block_modes::{BlockMode, Cbc}; | ||
7 | use chrono::Utc; | 9 | use chrono::Utc; |
8 | use jsonwebtoken::errors::ErrorKind; | 10 | use jsonwebtoken::errors::ErrorKind; |
9 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | 11 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; |
10 | use lazy_static::lazy_static; | 12 | use lazy_static::lazy_static; |
11 | use log::{debug, warn}; | 13 | use log::{debug, warn}; |
12 | use math; | ||
13 | use md5::Md5; | 14 | use md5::Md5; |
14 | use parking_lot::RwLockUpgradableReadGuard; | 15 | use parking_lot::RwLockUpgradableReadGuard; |
15 | use rsa::{PaddingScheme, RSAPrivateKey}; | 16 | use rsa::{PaddingScheme, RSAPrivateKey}; |
16 | use serde::Serialize; | 17 | use serde::Serialize; |
17 | use sha2::Sha256; | 18 | use sha2::Sha256; |
18 | use std::collections::{HashMap, HashSet}; | 19 | use std::{ |
19 | use std::convert::Infallible; | 20 | collections::{HashMap, HashSet}, |
20 | use std::fs; | 21 | convert::Infallible, |
21 | use std::hash::Hash; | 22 | fs, |
23 | hash::Hash, | ||
24 | }; | ||
22 | use warp::{http::StatusCode, reply}; | 25 | use warp::{http::StatusCode, reply}; |
23 | 26 | ||
24 | use crate::PRIVATE_KEY; | 27 | use crate::PRIVATE_KEY; |
@@ -52,11 +55,6 @@ enum ResponseType { | |||
52 | Error, | 55 | Error, |
53 | } | 56 | } |
54 | 57 | ||
55 | use crate::schema::{ | ||
56 | AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User, | ||
57 | UserAtRest, | ||
58 | }; | ||
59 | |||
60 | const BEARER: &str = "Bearer "; | 58 | const BEARER: &str = "Bearer "; |
61 | 59 | ||
62 | lazy_static! { | 60 | lazy_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 | /// |
113 | pub async fn authenticate_user( | 110 | pub 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 |
862 | fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result<TokenData<Claims>, String> { | 854 | fn 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 | }); |