From 77b99f7d3a8747f562f2b8f1e8df551aafea1b28 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Thu, 15 Apr 2021 13:35:06 +0300 Subject: Remove lorems and inpsumses --- src/schema.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/schema.rs') diff --git a/src/schema.rs b/src/schema.rs index af10b4d..33dc301 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -195,7 +195,7 @@ pub struct AuthRequest { #[derive(Serialize, Deserialize, Debug)] pub struct InitialAuthRequest { pub c: String, - pub iv: [u8; 32], + pub iv: String, pub key: String, } -- cgit v1.2.3-70-g09d2 From bff5cd1eb6a67456426f02d6730e0bf945fc67e3 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Thu, 15 Apr 2021 13:48:22 +0300 Subject: Listen to clippy --- src/handlers.rs | 25 ++++++------------------- src/lib.rs | 4 ++-- src/routes.rs | 2 +- src/schema.rs | 15 +++++++++++---- 4 files changed, 20 insertions(+), 26 deletions(-) (limited to 'src/schema.rs') diff --git a/src/handlers.rs b/src/handlers.rs index 5110bd5..848cb75 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -1,7 +1,6 @@ use aes::Aes128; /// API handlers, the ends of each filter chain use askama::Template; -use base64; use blake2::{Blake2s, Digest}; use block_modes::block_padding::Pkcs7; use block_modes::{BlockMode, Cbc}; @@ -12,7 +11,6 @@ use md5::Md5; use parking_lot::RwLockUpgradableReadGuard; use rsa::{PaddingScheme, RSAPrivateKey}; use serde::Serialize; -use serde_json; use sha2::Sha256; use std::collections::HashMap; use std::convert::Infallible; @@ -93,7 +91,7 @@ pub async fn authenticate_user( // Load our RSA Private Key as DER let der_encoded = PRIVATE_KEY .lines() - .filter(|line| !line.starts_with("-")) + .filter(|line| !line.starts_with('-')) .fold(String::new(), |mut data, line| { data.push_str(&line); data @@ -147,18 +145,7 @@ pub async fn authenticate_user( // We're using this as the validator // I hate myself - if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { - let res_json = warp::reply::json(&GradeCoinResponse { - res: ResponseType::Error, - message: "The supplied RSA public key is not in valid PEM format".to_owned(), - }); - - return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); - } - - // We're using this as the validator - // I hate myself - if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { + 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(), @@ -231,7 +218,7 @@ pub async fn authorized_propose_block( println!("{:?}", &new_block); - if new_block.transaction_list.len() < 1 { + if new_block.transaction_list.is_empty() { let res_json = warp::reply::json(&GradeCoinResponse { res: ResponseType::Error, message: format!( @@ -312,8 +299,8 @@ pub async fn authorized_propose_block( let naked_block = NakedBlock { transaction_list: new_block.transaction_list.clone(), - nonce: new_block.nonce.clone(), - timestamp: new_block.timestamp.clone(), + nonce: new_block.nonce, + timestamp: new_block.timestamp, }; let naked_block_flat = serde_json::to_vec(&naked_block).unwrap(); @@ -546,7 +533,7 @@ pub async fn list_blocks(db: Db) -> Result { /// *[`jwt_token`]: The raw JWT token, "Bearer aaa.bbb.ccc" /// *[`user_pem`]: User Public Key, "BEGIN RSA" /// NOT async, might look into it if this becomes a bottleneck -fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result, String> { +fn authorize_proposer(jwt_token: String, user_pem: &str) -> Result, String> { // Throw away the "Bearer " part let raw_jwt = jwt_token.trim_start_matches(BEARER).to_owned(); debug!("raw_jwt: {:?}", raw_jwt); diff --git a/src/lib.rs b/src/lib.rs index 82fb51f..5442c6b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -26,7 +26,7 @@ pub mod handlers; pub mod routes; pub mod schema; -pub const PRIVATE_KEY: &'static str = "-----BEGIN RSA PRIVATE KEY----- +pub const PRIVATE_KEY: &str = "-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAyGuqiCPGcguy+Y9TH7Bl7XlEsalyqb9bYlzpbV0dnqZ3lPkE PkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO/jbN8jfcxVwBu0JxjF3v1YRBxbOH hz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDvQiSW5NdrX/lEkvqfGtdEX1m2+Hdc @@ -54,7 +54,7 @@ PDYHM9dfQ8xn51U0fTeaXjy/8Km8fyX2Jtxntlm6puyhSTJ8AX+FEgJkC4ajNEvA mJ1Gsy2fXKUyyZdI2b74MLqOpzr9cvS60tmTIScuiHFzg/SJgiA= -----END RSA PRIVATE KEY-----"; -pub const PUB_KEY: &'static str = "-----BEGIN PUBLIC KEY----- +pub const PUB_KEY: &str = "-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyGuqiCPGcguy+Y9TH7Bl 7XlEsalyqb9bYlzpbV0dnqZ3lPkEPkuOhkN+GcuiV6iXtSwyh7nB+xTRXKJFRUBO /jbN8jfcxVwBu0JxjF3v1YRBxbOHhz2A295mbKD9xHQCKxkfYBNkUXxj8gd+GaDv diff --git a/src/routes.rs b/src/routes.rs index 59342bb..52d357a 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -18,7 +18,7 @@ pub fn consensus_routes(db: Db) -> impl Filter Db { let block: Block = serde_json::from_str(json).unwrap(); let db = Db::new(); *db.blockchain.write() = block; - return db; + + db } /// Creates a new database, uses the previous last block if one exists @@ -59,9 +60,9 @@ pub fn create_database() -> Db { fs::create_dir_all("users").unwrap(); let (res, path) = last_block_exists(); if res { - return create_db_with_last_block(path); + create_db_with_last_block(path) } else { - return Db::new(); + Db::new() } } @@ -168,6 +169,12 @@ impl Block { } } +impl Default for Block { + fn default() -> Self { + Self::new() + } +} + /// Simply a Student #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct User { @@ -247,7 +254,7 @@ impl MetuId { pub fn new(id: String, pwd: String) -> Option { if OUR_STUDENTS.contains(&(&*id, &*pwd)) { Some(MetuId { - id: id, + id, passwd: pwd, }) } else { -- cgit v1.2.3-70-g09d2 From 237fa34e91d8c1833c2dbd0244929669c271ab5e Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Thu, 15 Apr 2021 14:30:41 +0300 Subject: Cleaned up user authentication Removed TODOs, unrolled unwraps --- src/handlers.rs | 136 ++++++++++++++++++++++++++++++++++++++++++++++++++------ src/schema.rs | 2 +- 2 files changed, 123 insertions(+), 15 deletions(-) (limited to 'src/schema.rs') diff --git a/src/handlers.rs b/src/handlers.rs index 848cb75..515caa5 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -60,9 +60,9 @@ const BEARER: &str = "Bearer "; /// public_key: "---BEGIN PUBLIC KEY..." /// } /// -/// - 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? +/// - 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` /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme -/// using sha256 with `gradecoin_public_key` (TODO base64? same as above), giving us `key_ciphertext` +/// using sha256 with `gradecoin_public_key`, giving us `key_ciphertext` /// - The payload JSON object (`auth_request`) can be JSON serialized now: /// { /// c: "auth_ciphertext" @@ -103,18 +103,126 @@ pub async fn authenticate_user( let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); let padding = PaddingScheme::new_oaep::(); - let temp_key = gradecoin_private_key - .decrypt(padding, &request.key.as_bytes()) - .expect("failed to decrypt"); - - // decrypt c using key dec_key - let cipher = Aes128Cbc::new_var(&temp_key, &request.iv.as_bytes()).unwrap(); - let auth_plaintext = cipher - .decrypt_vec(&base64::decode(request.c).unwrap()) - .unwrap(); - - let request: AuthRequest = - serde_json::from_str(&String::from_utf8(auth_plaintext).unwrap()).unwrap(); + + let key_ciphertext = match base64::decode(&request.key) { + Ok(c) => c, + Err(err) => { + debug!( + "The ciphertext of the key 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(), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; + + let temp_key = match gradecoin_private_key.decrypt(padding, &key_ciphertext) { + Ok(k) => k, + Err(err) => { + debug!( + "Failed to decrypt ciphertext {:?}, {}", + &key_ciphertext, err + ); + + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "Failed to decrypt the ciphertext of the temporary key".to_owned(), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; + + let cipher = match Aes128Cbc::new_var(&temp_key, &request.iv.as_bytes()) { + Ok(c) => c, + Err(err) => { + debug!( + "Could not create a cipher from temp_key and request.iv {:?}, {}, {}", + &temp_key, &request.iv, err + ); + + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "Given IV has invalid length".to_owned(), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; + + let auth_packet = match base64::decode(&request.c) { + Ok(a) => a, + + Err(err) => { + debug!( + "The auth_packet (c field) did not base64 decode {} {}", + &request.c, err + ); + + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "The c field was not correctly base64 encoded".to_owned(), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; + + let auth_plaintext = match cipher.decrypt_vec(&auth_packet) { + Ok(p) => p, + Err(err) => { + debug!( + "Base64 decoded auth request did not decrypt correctly {:?} {}", + &auth_packet, err + ); + + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "The Bas64 decoded auth request did not decrypt correctly".to_owned(), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; + + let utf8_auth_plaintext = match String::from_utf8(auth_plaintext.clone()) { + Ok(text) => text, + Err(err) => { + debug!( + "Auth plaintext did not convert into utf8 {:?} {}", + &auth_plaintext, err + ); + + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "Auth plaintext couldn't get converted to UTF-8".to_owned(), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; + + let request: AuthRequest = match serde_json::from_str(&utf8_auth_plaintext) { + Ok(req) => req, + Err(err) => { + debug!( + "Auth plaintext did not serialize correctly {:?} {}", + &utf8_auth_plaintext, err + ); + + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: "The auth request JSON did not serialize correctly".to_owned(), + }); + + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } + }; let provided_id = request.student_id.clone(); diff --git a/src/schema.rs b/src/schema.rs index 6f2f1f3..dca0eef 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -163,7 +163,7 @@ impl Block { Block { transaction_list: vec!["gradecoin_bank".to_owned()], nonce: 0, - timestamp: NaiveDate::from_ymd(2021, 04, 11).and_hms(20, 45, 00), + timestamp: NaiveDate::from_ymd(2021, 4, 11).and_hms(20, 45, 00), hash: String::from("not_actually_mined"), } } -- cgit v1.2.3-70-g09d2 From 7d8b9272f885c2a240867f45e7abccc98ebf18f7 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Thu, 15 Apr 2021 23:25:21 +0300 Subject: Add Doc for User --- src/schema.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) (limited to 'src/schema.rs') diff --git a/src/schema.rs b/src/schema.rs index dca0eef..79fee2d 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -175,7 +175,11 @@ impl Default for Block { } } -/// Simply a Student +/// A Student +/// +/// * [`user_id`]: Can only be one of the repopulated +/// * [`public_key`]: A PEM format public key "---- BEGIN" and all +/// * [`balance`]: User's current Gradecoin amount #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct User { pub user_id: MetuId, @@ -253,10 +257,7 @@ impl fmt::Display for MetuId { impl MetuId { pub fn new(id: String, pwd: String) -> Option { if OUR_STUDENTS.contains(&(&*id, &*pwd)) { - Some(MetuId { - id, - passwd: pwd, - }) + Some(MetuId { id, passwd: pwd }) } else { None } -- cgit v1.2.3-70-g09d2 From 72f8ae422eeb03ed87c7819af5d5e25758267b03 Mon Sep 17 00:00:00 2001 From: Yigit Sever Date: Fri, 16 Apr 2021 00:53:03 +0300 Subject: Load users from disk at start --- src/handlers.rs | 36 ++++++++++++++++++++------------ src/schema.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 74 insertions(+), 27 deletions(-) (limited to 'src/schema.rs') diff --git a/src/handlers.rs b/src/handlers.rs index 515caa5..fe60ded 100644 --- a/src/handlers.rs +++ b/src/handlers.rs @@ -8,7 +8,6 @@ use jsonwebtoken::errors::ErrorKind; use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; use log::{debug, warn}; use md5::Md5; -use parking_lot::RwLockUpgradableReadGuard; use rsa::{PaddingScheme, RSAPrivateKey}; use serde::Serialize; use sha2::Sha256; @@ -38,6 +37,7 @@ enum ResponseType { use crate::schema::{ AuthRequest, Block, Claims, Db, InitialAuthRequest, MetuId, NakedBlock, Transaction, User, + UserAtRest, }; const BEARER: &str = "Bearer "; @@ -238,17 +238,19 @@ pub async fn authenticate_user( } }; - let userlist = db.users.upgradable_read(); + { + let userlist = db.users.read(); - if userlist.contains_key(&provided_id) { - let res_json = warp::reply::json(&GradeCoinResponse { - res: ResponseType::Error, - message: - "This user is already authenticated, do you think this is a mistake? Contact me" - .to_owned(), - }); + if userlist.contains_key(&provided_id) { + let res_json = warp::reply::json(&GradeCoinResponse { + res: ResponseType::Error, + message: + "This user is already authenticated, do you think this is a mistake? Contact me" + .to_owned(), + }); - return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); + } } // We're using this as the validator @@ -270,11 +272,19 @@ pub async fn authenticate_user( balance: 0, }; - let user_json = serde_json::to_string(&new_user).unwrap(); + let user_at_rest_json = serde_json::to_string(&UserAtRest { + user: User { + user_id: new_user.user_id.clone(), + public_key: new_user.public_key.clone(), + balance: 0, + }, + fingerprint: fingerprint.clone(), + }) + .unwrap(); - fs::write(format!("users/{}.guy", new_user.user_id), user_json).unwrap(); + fs::write(format!("users/{}.guy", new_user.user_id), user_at_rest_json).unwrap(); - let mut userlist = RwLockUpgradableReadGuard::upgrade(userlist); + let mut userlist = db.users.write(); userlist.insert(fingerprint.clone(), new_user); diff --git a/src/schema.rs b/src/schema.rs index 79fee2d..80cf73f 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -9,6 +9,7 @@ //! Users are held in memory and they're also backed up to text files use chrono::{NaiveDate, NaiveDateTime}; use lazy_static::lazy_static; +use log::debug; use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use std::collections::{HashMap, HashSet}; @@ -23,17 +24,15 @@ use std::vec::Vec; pub type Fingerprint = String; -// TODO: start by reading users from file too <14-04-21, yigit> // - -fn last_block_exists() -> (bool, String) { +fn last_block_exists() -> Option { let blocks = read_block_name().unwrap(); for block in blocks { let block = block.to_str().unwrap(); if block.contains("last.block") { - return (true, block.to_string()); + return Some(block.to_string()); } } - (false, "".to_string()) + None } fn read_block_name() -> io::Result> { @@ -44,26 +43,64 @@ fn read_block_name() -> io::Result> { Ok(entries) } -fn create_db_with_last_block(path: String) -> Db { +fn read_users() -> io::Result> { + let entries = fs::read_dir("./users")? + .map(|res| res.map(|e| e.path())) + .collect::, io::Error>>()?; + + Ok(entries) +} + +fn populate_db_with_last_block(db: &mut Db, path: String) -> &mut Db { + debug!("Populating db with last block {}", path); let file = fs::read(path).unwrap(); let json = std::str::from_utf8(&file).unwrap(); let block: Block = serde_json::from_str(json).unwrap(); - let db = Db::new(); *db.blockchain.write() = block; db } -/// Creates a new database, uses the previous last block if one exists +#[derive(Debug, Serialize, Deserialize, PartialEq)] +pub struct UserAtRest { + pub fingerprint: Fingerprint, + pub user: User, +} + +fn populate_db_with_users(db: &mut Db, files: Vec) -> &mut Db { + for fs in files { + if let Ok(file_content) = fs::read(fs) { + let json = + String::from_utf8(file_content).expect("we have written a malformed user file"); + let user_at_rest: UserAtRest = serde_json::from_str(&json).unwrap(); + + debug!("Populating db with user: {:?}", user_at_rest); + db.users + .write() + .insert(user_at_rest.fingerprint, user_at_rest.user); + } + } + + db +} + +/// Creates a new database, uses the previous last block if one exists and attempts the populate +/// the users pub fn create_database() -> Db { fs::create_dir_all("blocks").unwrap(); fs::create_dir_all("users").unwrap(); - let (res, path) = last_block_exists(); - if res { - create_db_with_last_block(path) - } else { - Db::new() + + let mut db = Db::new(); + + if let Some(blocks_path) = last_block_exists() { + populate_db_with_last_block(&mut db, blocks_path); + } + + if let Ok(users_path) = read_users() { + populate_db_with_users(&mut db, users_path); } + + db } /// A JWT Payload/Claims representation @@ -188,7 +225,7 @@ pub struct User { } /// The values are hard coded in [`OUR_STUDENTS`] so MetuId::new() can accept/reject values based on that -#[derive(Serialize, Deserialize, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] pub struct MetuId { id: String, passwd: String, -- cgit v1.2.3-70-g09d2