From 446cd6bdd6234ffba76a0e440c606686053f0ba0 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Sun, 25 Apr 2021 22:23:58 +0300 Subject: Went over error messages --- src/handlers.rs | 188 ++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 127 insertions(+), 61 deletions(-) (limited to 'src') diff --git a/src/handlers.rs b/src/handlers.rs index 123f70e..7204aa7 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -16,13 +16,21 @@ use sha2::Sha256; use std::collections::{HashMap, HashSet}; use std::convert::Infallible; use std::fs; +use std::hash::Hash; use warp::{http::StatusCode, reply}; use crate::PRIVATE_KEY; -const BLOCK_TRANSACTION_COUNT: u8 = 1; + +// Valid blocks should have this many transactions +const BLOCK_TRANSACTION_COUNT: u8 = 5; +// Inital registration bonus const REGISTER_BONUS: u16 = 40; +// Coinbase reward const BLOCK_REWARD: u16 = 3; +// Transaction amount limit const TX_UPPER_LIMIT: u16 = 2; +// Transaction traffic reward +const TX_TRAFFIC_REWARD: u16 = 1; // Encryption primitive type Aes128Cbc = Cbc; @@ -108,42 +116,68 @@ pub async fn authenticate_user( let padding = PaddingScheme::new_oaep::(); + // Peel away the base64 layer from "key" field let key_ciphertext = match base64::decode(&request.key) { Ok(c) => c, Err(err) => { debug!( - "The ciphertext of the key was not base64 encoded {}, {}", + "\"key\" field of initial auth request was not base64 encoded: {}, {}", &request.key, err ); let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "The ciphertext of the key was not base64 encoded {}, {}".to_owned(), + message: format!( + "\"key\" field of initial auth request was not base64 encoded: {}, {}", + &request.key, err + ), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } }; + // Decrypt the "key" field using Gradecoin's private key let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) { Ok(k) => k, Err(err) => { debug!( - "Failed to decrypt ciphertext {:?}, {}", - &key_ciphertext, err + "Failed to decrypt ciphertext of the key with Gradecoin's public key: {}. Key was {:?}", + err, &key_ciphertext ); let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "Failed to decrypt the ciphertext of the temporary key".to_owned(), + message: "Failed to decrypt the 'key_ciphertext' field of the auth request" + .to_owned(), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } }; - let byte_iv = base64::decode(&request.iv).unwrap(); + // Peel away the base64 from the iv field as well + let byte_iv = match base64::decode(&request.iv) { + Ok(iv) => iv, + Err(err) => { + debug!( + "\"iv\" field of initial auth request was not base64 encoded: {}, {}", + &request.iv, err + ); + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: format!( + "\"iv\" field of initial auth request was not base64 encoded: {}, {}", + &request.iv, err + ), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; + + // we have key and iv, time to decrypt the "c" field, first prepare the decryptor let cipher = match Aes128Cbc::new_var(&temp_key, &byte_iv) { Ok(c) => c, Err(err) => { @@ -161,42 +195,49 @@ pub async fn authenticate_user( } }; + // peel away the base64 from the auth packet let auth_packet = match base64::decode(&request.c) { Ok(a) => a, - Err(err) => { debug!( - "The auth_packet (c field) did not base64 decode {} {}", + "\"c\" field of initial auth request was not base64 encoded: {}, {}", &request.c, err ); let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "The c field was not correctly base64 encoded".to_owned(), + message: format!( + "\"c\" field of initial auth request was not base64 encoded: {}, {}", + &request.c, err + ), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } }; + // c field was properly base64 encoded, now available in auth_packet + // decryptor was setup properly, with the correct lenght key let mut buf = auth_packet.to_vec(); let auth_plaintext = match cipher.decrypt(&mut buf) { Ok(p) => p, Err(err) => { - debug!( - "Base64 decoded auth request did not decrypt correctly {:?} {}", + println!( + "auth request (c) did not decrypt correctly {:?} {}", &auth_packet, err ); let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "The Base64 decoded auth request did not decrypt correctly".to_owned(), + message: "Failed to decrypt the 'c' field of the auth request, 'iv' and 'k_temp' were valid so far though" + .to_owned(), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } }; + // we have a decrypted c field, create a string from the bytes mess let utf8_auth_plaintext = match String::from_utf8(auth_plaintext.to_vec()) { Ok(text) => text, Err(err) => { @@ -207,13 +248,15 @@ pub async fn authenticate_user( let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "Auth plaintext couldn't get converted to UTF-8".to_owned(), + message: "P_AR couldn't get converted to UTF-8, please check your encoding" + .to_owned(), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } }; + // finally create an AuthRequest object from the plaintext let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) { Ok(req) => req, Err(err) => { @@ -224,24 +267,32 @@ pub async fn authenticate_user( let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "The auth request JSON did not serialize correctly".to_owned(), + message: "The P_AR JSON did not serialize correctly, did it include all 3 fields 'student_id', 'passwd' and 'public_key'?".to_owned(), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } }; - let privileged_student_id = match MetuId::new(request.student_id, request.passwd) { - Some(id) => id, - None => { - let res_json = warp::reply::json(&GradeCoinResponse { + // is the student in AuthRequest privileged? + let privileged_student_id = + match MetuId::new(request.student_id.clone(), request.passwd.clone()) { + Some(id) => id, + None => { + debug!( + "Someone tried to auth with invalid credentials: {} {}", + &request.student_id, &request.passwd + ); + let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "This user cannot have a gradecoin account".to_owned(), + message: + "The credentials given ('student_id', 'passwd') cannot hold a Gradecoin account" + .to_owned(), }); - return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); - } - }; + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; // Students should be able to authenticate once { @@ -264,7 +315,7 @@ pub async fn authenticate_user( if DecodingKey::from_rsa_pem(request.public_key.as_bytes()).is_err() { let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "The supplied RSA public key is not in valid PEM format".to_owned(), + message: "The RSA 'public_key' in 'P_AR' is not in valid PEM format".to_owned(), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); @@ -279,23 +330,23 @@ pub async fn authenticate_user( is_bot: false, }; - debug!("New user authenticated themselves! {:?}", &new_user); + debug!("NEW USER: {:?}", &new_user); + // save the user to disk let user_at_rest_json = serde_json::to_string(&UserAtRest { + fingerprint: fingerprint.clone(), user: User { user_id: new_user.user_id.clone(), public_key: new_user.public_key.clone(), - balance: 0, + balance: new_user.balance, is_bot: false, }, - fingerprint: fingerprint.clone(), }) .unwrap(); fs::write(format!("users/{}.guy", new_user.user_id), user_at_rest_json).unwrap(); let mut userlist = db.users.write(); - userlist.insert(fingerprint.clone(), new_user); let res_json = warp::reply::json(&GradeCoinResponse { @@ -312,7 +363,6 @@ pub async fn authenticate_user( /// GET /transaction /// Returns JSON array of transactions pub async fn list_transactions(db: Db) -> Result { - debug!("GET /transaction, list_transactions() is handling"); let mut result = HashMap::new(); let transactions = db.pending_transactions.read(); @@ -338,10 +388,9 @@ pub async fn propose_block( token: String, db: Db, ) -> Result { - debug!("POST /block, propose_block() is handling"); - warn!("New block proposal: {:?}", &new_block); + // Check if there are enough transactions in the block if new_block.transaction_list.len() < BLOCK_TRANSACTION_COUNT as usize { debug!( "{} transactions offered, needed {}", @@ -360,21 +409,19 @@ pub async fn propose_block( } // proposer (first transaction fingerprint) checks - let pending_transactions = db.pending_transactions.upgradable_read(); let internal_user_fingerprint = match pending_transactions.get(&new_block.transaction_list[0]) { Some(coinbase) => &coinbase.source, None => { debug!( - "User with public key signature {:?} is not found in the database", + "Block proposer with public key signature {:?} is not found in the database", new_block.transaction_list[0] ); let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "User with that public key signature is not found in the database" - .to_owned(), + message: "Proposer of the first transaction is not found in the system".to_owned(), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); @@ -383,6 +430,7 @@ pub async fn propose_block( let users_store = db.users.upgradable_read(); + // this probably cannot fail, if the transaction is valid then it must've been checked already let internal_user = match users_store.get(internal_user_fingerprint) { Some(existing_user) => existing_user, None => { @@ -407,7 +455,7 @@ pub async fn propose_block( let token_payload = match authorize_proposer(token, &proposer_public_key) { Ok(data) => data, Err(below) => { - debug!("Something went wrong below {:?}", below); + debug!("Something went wrong with the JWT {:?}", below); let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, @@ -426,46 +474,36 @@ pub async fn propose_block( ); let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "The hash of the block did not match the hash given in JWT".to_owned(), + message: "The hash of the block did not match the hash given in JWT tha field" + .to_owned(), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } - // scope the HashSet - { - let mut proposed_transactions = HashSet::new(); - for tx in new_block.transaction_list.iter() { - proposed_transactions.insert(tx); - } - - if proposed_transactions.len() < BLOCK_TRANSACTION_COUNT as usize { - let res_json = warp::reply::json(&GradeCoinResponse { - res: ResponseType::Error, - message: format!( - "Block cannot contain less than {} unique transaction(s).", - BLOCK_TRANSACTION_COUNT - ), - }); + if !has_unique_elements(&new_block.transaction_list) { + debug!("Block contains duplicate transactions!"); + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "Block cannot contain duplicate transactions".to_owned(), + }); - return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); - } + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } - // Scope the RwLocks, there are hashing stuff below - // Are transactions in the block valid? for transaction_hash in new_block.transaction_list.iter() { if !pending_transactions.contains_key(transaction_hash) { let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, - message: "Block contains unknown transaction".to_owned(), + message: "Block contains an unknown transaction".to_owned(), }); return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); } } + // hash the block ourselves to double check let naked_block = NakedBlock { transaction_list: new_block.transaction_list.clone(), nonce: new_block.nonce, @@ -509,6 +547,7 @@ pub async fn propose_block( let mut pending_transactions = RwLockUpgradableReadGuard::upgrade(pending_transactions); let mut users_store = RwLockUpgradableReadGuard::upgrade(users_store); + // Play out the transactions for fingerprint in new_block.transaction_list.iter() { if let Some(transaction) = pending_transactions.remove(fingerprint) { let source = &transaction.source; @@ -519,7 +558,7 @@ pub async fn propose_block( } if let Some(to) = users_store.get_mut(target) { - to.balance += transaction.amount + 1; + to.balance += transaction.amount + TX_TRAFFIC_REWARD; } // if the receiver is a bot, they will reciprocate @@ -539,6 +578,7 @@ pub async fn propose_block( } } + // Reward the block proposer let coinbase_fingerprint = new_block.transaction_list.get(0).unwrap(); if let Some(coinbase_user) = users_store.get_mut(coinbase_fingerprint) { @@ -562,7 +602,7 @@ pub async fn propose_block( Ok(warp::reply::with_status( warp::reply::json(&GradeCoinResponse { res: ResponseType::Success, - message: "Block accepted coinbase reward awarded".to_owned(), + message: "Block accepted, coinbase reward awarded".to_owned(), }), StatusCode::CREATED, )) @@ -582,8 +622,6 @@ pub async fn propose_transaction( token: String, db: Db, ) -> Result { - debug!("POST /transaction, propose_transaction() is handling"); - warn!("New transaction proposal: {:?}", &new_transaction); let users_store = db.users.read(); @@ -628,6 +666,25 @@ pub async fn propose_transaction( } }; + // is the target of the transaction in the system? + if !users_store.contains_key(&new_transaction.target) { + debug!( + "Target of the transaction is not in the system {}", + new_transaction.target + ); + + return Ok(warp::reply::with_status( + warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: format!( + "Target of the transaction {} is not found in the system", + new_transaction.target + ), + }), + StatusCode::BAD_REQUEST, + )); + } + let transaction_id = calculate_transaction_id(&new_transaction.source, &new_transaction.target); // OLD: Does this user have a pending transaction? @@ -826,3 +883,12 @@ pub async fn user_list_handler(db: Db) -> Result(iter: T) -> bool +where + T: IntoIterator, + T::Item: Eq + Hash, +{ + let mut uniq = HashSet::new(); + iter.into_iter().all(move |x| uniq.insert(x)) +} -- cgit v1.2.3-70-g09d2