diff options
| author | Yigit Sever | 2021-04-14 18:06:06 +0300 |
|---|---|---|
| committer | Yigit Sever | 2021-04-14 19:12:49 +0300 |
| commit | 71e692ba2a3aa271f7ee1e20d16ac69de70acc5b (patch) | |
| tree | 2e660d26a3c5445842365951e6eeb5fd4e751434 | |
| parent | 3e333c952a54453bd877c556a09f2e8e0c434c87 (diff) | |
| download | gradecoin-71e692ba2a3aa271f7ee1e20d16ac69de70acc5b.tar.gz gradecoin-71e692ba2a3aa271f7ee1e20d16ac69de70acc5b.tar.bz2 gradecoin-71e692ba2a3aa271f7ee1e20d16ac69de70acc5b.zip | |
[WIP] Rough authentication process done
| -rw-r--r-- | Cargo.lock | 58 | ||||
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/handlers.rs | 82 | ||||
| -rw-r--r-- | src/schema.rs | 1 |
4 files changed, 121 insertions, 22 deletions
| @@ -1,6 +1,37 @@ | |||
| 1 | # This file is automatically @generated by Cargo. | 1 | # This file is automatically @generated by Cargo. |
| 2 | # It is not intended for manual editing. | 2 | # It is not intended for manual editing. |
| 3 | [[package]] | 3 | [[package]] |
| 4 | name = "aes" | ||
| 5 | version = "0.6.0" | ||
| 6 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 7 | checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" | ||
| 8 | dependencies = [ | ||
| 9 | "aes-soft", | ||
| 10 | "aesni", | ||
| 11 | "cipher", | ||
| 12 | ] | ||
| 13 | |||
| 14 | [[package]] | ||
| 15 | name = "aes-soft" | ||
| 16 | version = "0.6.4" | ||
| 17 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 18 | checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" | ||
| 19 | dependencies = [ | ||
| 20 | "cipher", | ||
| 21 | "opaque-debug", | ||
| 22 | ] | ||
| 23 | |||
| 24 | [[package]] | ||
| 25 | name = "aesni" | ||
| 26 | version = "0.10.0" | ||
| 27 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 28 | checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" | ||
| 29 | dependencies = [ | ||
| 30 | "cipher", | ||
| 31 | "opaque-debug", | ||
| 32 | ] | ||
| 33 | |||
| 34 | [[package]] | ||
| 4 | name = "aho-corasick" | 35 | name = "aho-corasick" |
| 5 | version = "0.7.15" | 36 | version = "0.7.15" |
| 6 | source = "registry+https://github.com/rust-lang/crates.io-index" | 37 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -71,6 +102,22 @@ dependencies = [ | |||
| 71 | ] | 102 | ] |
| 72 | 103 | ||
| 73 | [[package]] | 104 | [[package]] |
| 105 | name = "block-modes" | ||
| 106 | version = "0.7.0" | ||
| 107 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 108 | checksum = "57a0e8073e8baa88212fb5823574c02ebccb395136ba9a164ab89379ec6072f0" | ||
| 109 | dependencies = [ | ||
| 110 | "block-padding", | ||
| 111 | "cipher", | ||
| 112 | ] | ||
| 113 | |||
| 114 | [[package]] | ||
| 115 | name = "block-padding" | ||
| 116 | version = "0.2.1" | ||
| 117 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 118 | checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" | ||
| 119 | |||
| 120 | [[package]] | ||
| 74 | name = "buf_redux" | 121 | name = "buf_redux" |
| 75 | version = "0.8.4" | 122 | version = "0.8.4" |
| 76 | source = "registry+https://github.com/rust-lang/crates.io-index" | 123 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -137,6 +184,15 @@ dependencies = [ | |||
| 137 | ] | 184 | ] |
| 138 | 185 | ||
| 139 | [[package]] | 186 | [[package]] |
| 187 | name = "cipher" | ||
| 188 | version = "0.2.5" | ||
| 189 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 190 | checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" | ||
| 191 | dependencies = [ | ||
| 192 | "generic-array", | ||
| 193 | ] | ||
| 194 | |||
| 195 | [[package]] | ||
| 140 | name = "cloudabi" | 196 | name = "cloudabi" |
| 141 | version = "0.0.3" | 197 | version = "0.0.3" |
| 142 | source = "registry+https://github.com/rust-lang/crates.io-index" | 198 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| @@ -319,8 +375,10 @@ dependencies = [ | |||
| 319 | name = "gradecoin" | 375 | name = "gradecoin" |
| 320 | version = "0.1.0" | 376 | version = "0.1.0" |
| 321 | dependencies = [ | 377 | dependencies = [ |
| 378 | "aes", | ||
| 322 | "base64 0.13.0", | 379 | "base64 0.13.0", |
| 323 | "blake2", | 380 | "blake2", |
| 381 | "block-modes", | ||
| 324 | "chrono", | 382 | "chrono", |
| 325 | "hex-literal", | 383 | "hex-literal", |
| 326 | "jsonwebtoken", | 384 | "jsonwebtoken", |
| @@ -23,6 +23,8 @@ md-5 = "0.9.1" | |||
| 23 | rsa = "0.4.0" | 23 | rsa = "0.4.0" |
| 24 | base64 = "0.13.0" | 24 | base64 = "0.13.0" |
| 25 | sha2 = "0.9.3" | 25 | sha2 = "0.9.3" |
| 26 | block-modes = "0.7.0" | ||
| 27 | aes = "0.6.0" | ||
| 26 | 28 | ||
| 27 | [dev-dependencies] | 29 | [dev-dependencies] |
| 28 | serde_test = "1.0.117" | 30 | serde_test = "1.0.117" |
diff --git a/src/handlers.rs b/src/handlers.rs index 55d3ab4..21911e1 100644 --- a/src/handlers.rs +++ b/src/handlers.rs | |||
| @@ -1,12 +1,15 @@ | |||
| 1 | use base64; | ||
| 2 | /// API handlers, the ends of each filter chain | 1 | /// API handlers, the ends of each filter chain |
| 2 | use aes::Aes128; | ||
| 3 | use base64; | ||
| 3 | use blake2::{Blake2s, Digest}; | 4 | use blake2::{Blake2s, Digest}; |
| 5 | use block_modes::block_padding::Pkcs7; | ||
| 6 | use block_modes::{BlockMode, Cbc}; | ||
| 4 | use jsonwebtoken::errors::ErrorKind; | 7 | use jsonwebtoken::errors::ErrorKind; |
| 5 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; | 8 | use jsonwebtoken::{decode, Algorithm, DecodingKey, TokenData, Validation}; |
| 6 | use log::{debug, warn}; | 9 | use log::{debug, warn}; |
| 7 | use md5::Md5; | 10 | use md5::Md5; |
| 8 | use parking_lot::RwLockUpgradableReadGuard; | 11 | use parking_lot::RwLockUpgradableReadGuard; |
| 9 | use rsa::{PaddingScheme, RSAPrivateKey}; | 12 | use rsa::{PaddingScheme, RSAPrivateKey, RSAPublicKey}; |
| 10 | use serde::Serialize; | 13 | use serde::Serialize; |
| 11 | use serde_json; | 14 | use serde_json; |
| 12 | use sha2; | 15 | use sha2; |
| @@ -16,6 +19,9 @@ use warp::{http::StatusCode, reply}; | |||
| 16 | 19 | ||
| 17 | use crate::PRIVATE_KEY; | 20 | use crate::PRIVATE_KEY; |
| 18 | 21 | ||
| 22 | // Encryption primitive | ||
| 23 | type Aes128Cbc = Cbc<Aes128, Pkcs7>; | ||
| 24 | |||
| 19 | #[derive(Serialize, Debug)] | 25 | #[derive(Serialize, Debug)] |
| 20 | struct GradeCoinResponse { | 26 | struct GradeCoinResponse { |
| 21 | res: ResponseType, | 27 | res: ResponseType, |
| @@ -41,21 +47,21 @@ const BEARER: &str = "Bearer "; | |||
| 41 | /// the [`AuthRequest.user_id`] of the `request` is not in the list of users that can hold a Gradecoin account | 47 | /// the [`AuthRequest.user_id`] of the `request` is not in the list of users that can hold a Gradecoin account |
| 42 | /// | 48 | /// |
| 43 | /// # Authentication Process | 49 | /// # Authentication Process |
| 44 | /// - Gradecoin's Public Key (`G_PK`) is listed on moodle. | 50 | /// - Gradecoin's Public Key (`gradecoin_public_key`) is listed on moodle. |
| 45 | /// - Gradecoin's Private Key (`G_PR`) is loaded here | 51 | /// - Gradecoin's Private Key (`gradecoin_private_key`) is loaded here |
| 46 | /// | 52 | /// |
| 47 | /// - Student picks a short temporary key (`k_temp`) | 53 | /// - Student picks a short temporary key (`k_temp`) |
| 48 | /// - Creates a JSON object (`auth_plaintext`) with their `metu_id` and `public key` in base64 (PEM) format (`S_PK`): | 54 | /// - Creates a JSON object (`auth_plaintext`) with their `metu_id` and `public key` in base64 (PEM) format (`S_PK`): |
| 49 | /// { | 55 | /// { |
| 50 | /// student_id: "e12345", | 56 | /// student_id: "e12345", |
| 57 | /// passwd: "15 char secret" | ||
| 51 | /// public_key: "---BEGIN PUBLIC KEY..." | 58 | /// public_key: "---BEGIN PUBLIC KEY..." |
| 52 | /// } | 59 | /// } |
| 53 | /// | 60 | /// |
| 54 | /// - Encrypts the serialized string of `auth_plaintext` with AES in TODO format using the temporary key | 61 | /// - 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? |
| 55 | /// (`k_temp`), the result is `auth_ciphertext`, (TODO base64?) | 62 | /// - The temporary key student has picked `k_temp` is encrypted using RSA with OAEP padding scheme |
| 56 | /// - The temporary key student has picked `k_temp` is encrypted (TODO details) with `G_PK` (TODO | 63 | /// using sha256 with `gradecoin_public_key` (TODO base64? same as above), giving us `key_ciphertext` |
| 57 | /// base64?) = `key_ciphertext` | 64 | /// - The payload JSON object (`auth_request`) can be JSON serialized now: |
| 58 | /// - The payload JSON object (`auth_request`) can be prepared now: | ||
| 59 | /// { | 65 | /// { |
| 60 | /// c: "auth_ciphertext" | 66 | /// c: "auth_ciphertext" |
| 61 | /// key: "key_ciphertext" | 67 | /// key: "key_ciphertext" |
| @@ -63,8 +69,10 @@ const BEARER: &str = "Bearer "; | |||
| 63 | /// | 69 | /// |
| 64 | /// ## Gradecoin Side | 70 | /// ## Gradecoin Side |
| 65 | /// | 71 | /// |
| 66 | /// - Upon receiving, we first extract the temporary key by decrypting `key`, receiving `temp_key` | 72 | /// - 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) |
| 67 | /// - With this key, we can decrypt c TODO with aes? | 73 | /// - With `temp_key`, we can AES 128 Cbc Pkcs7 decrypt the `auth_request.c`, giving us |
| 74 | /// auth_plaintext | ||
| 75 | /// - The `auth_plaintext` String can be deserialized to [`AuthRequest`] | ||
| 68 | /// - We then verify the payload and calculate the User fingerprint | 76 | /// - We then verify the payload and calculate the User fingerprint |
| 69 | /// - Finally, create the new [`User`] object, insert to users HashMap `<fingerprint, User>` | 77 | /// - Finally, create the new [`User`] object, insert to users HashMap `<fingerprint, User>` |
| 70 | /// | 78 | /// |
| @@ -74,8 +82,11 @@ pub async fn authenticate_user( | |||
| 74 | ) -> Result<impl warp::Reply, warp::Rejection> { | 82 | ) -> Result<impl warp::Reply, warp::Rejection> { |
| 75 | debug!("POST request to /register, authenticate_user"); | 83 | debug!("POST request to /register, authenticate_user"); |
| 76 | 84 | ||
| 85 | // In essence PEM files are just base64 encoded versions of the DER encoded data. | ||
| 86 | // ~tls.mbed.org | ||
| 87 | |||
| 77 | // TODO: lazyload or something <14-04-21, yigit> // | 88 | // TODO: lazyload or something <14-04-21, yigit> // |
| 78 | // This is our key, used to first decrypt the users temporal key | 89 | // Load our RSA Private Key as DER |
| 79 | let der_encoded = PRIVATE_KEY | 90 | let der_encoded = PRIVATE_KEY |
| 80 | .lines() | 91 | .lines() |
| 81 | .filter(|line| !line.starts_with("-")) | 92 | .filter(|line| !line.starts_with("-")) |
| @@ -84,23 +95,28 @@ pub async fn authenticate_user( | |||
| 84 | data | 95 | data |
| 85 | }); | 96 | }); |
| 86 | 97 | ||
| 98 | // base64(der(pem)) | ||
| 87 | // Our private key is saved in PEM (base64) format | 99 | // Our private key is saved in PEM (base64) format |
| 88 | let der_bytes = base64::decode(&der_encoded).expect("failed to decode base64 content"); | 100 | let der_bytes = base64::decode(&der_encoded).expect("failed to decode base64 content"); |
| 89 | let private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); | 101 | let gradecoin_private_key = RSAPrivateKey::from_pkcs1(&der_bytes).expect("failed to parse key"); |
| 90 | 102 | ||
| 91 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); | 103 | let padding = PaddingScheme::new_oaep::<sha2::Sha256>(); |
| 92 | let dec_key = private_key | 104 | let temp_key = gradecoin_private_key |
| 93 | .decrypt(padding, &request.key.as_bytes()) | 105 | .decrypt(padding, &request.key.as_bytes()) |
| 94 | .expect("failed to decrypt"); | 106 | .expect("failed to decrypt"); |
| 95 | 107 | ||
| 96 | // then decrypt c using key dec_key | 108 | // decrypt c using key dec_key |
| 109 | let cipher = Aes128Cbc::new_var(&temp_key, &request.iv).unwrap(); | ||
| 110 | let auth_plaintext = cipher | ||
| 111 | .decrypt_vec(&base64::decode(request.c).unwrap()) | ||
| 112 | .unwrap(); | ||
| 97 | 113 | ||
| 98 | // let request: AuthRequest = serde_json::from_str(&String::from_utf8(dec_data).unwrap()).unwrap(); | 114 | let request: AuthRequest = |
| 99 | let request; | 115 | serde_json::from_str(&String::from_utf8(auth_plaintext).unwrap()).unwrap(); |
| 100 | 116 | ||
| 101 | let provided_id = request.student_id.clone(); | 117 | let provided_id = request.student_id.clone(); |
| 102 | 118 | ||
| 103 | let priv_student_id = match MetuId::new(request.student_id, request.passwd) { | 119 | let privileged_student_id = match MetuId::new(request.student_id) { |
| 104 | Some(id) => id, | 120 | Some(id) => id, |
| 105 | None => { | 121 | None => { |
| 106 | let res_json = warp::reply::json(&GradeCoinResponse { | 122 | let res_json = warp::reply::json(&GradeCoinResponse { |
| @@ -117,15 +133,27 @@ pub async fn authenticate_user( | |||
| 117 | if userlist.contains_key(&provided_id) { | 133 | if userlist.contains_key(&provided_id) { |
| 118 | let res_json = warp::reply::json(&GradeCoinResponse { | 134 | let res_json = warp::reply::json(&GradeCoinResponse { |
| 119 | res: ResponseType::Error, | 135 | res: ResponseType::Error, |
| 120 | message: "This user is already authenticated".to_owned(), | 136 | message: |
| 137 | "This user is already authenticated, do you think this is a mistake? Contact me" | ||
| 138 | .to_owned(), | ||
| 139 | }); | ||
| 140 | |||
| 141 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | ||
| 142 | } | ||
| 143 | |||
| 144 | // We're using this as the validator | ||
| 145 | // I hate myself | ||
| 146 | if let Err(_) = DecodingKey::from_rsa_pem(request.public_key.as_bytes()) { | ||
| 147 | let res_json = warp::reply::json(&GradeCoinResponse { | ||
| 148 | res: ResponseType::Error, | ||
| 149 | message: "The supplied RSA public key is not in valid PEM format".to_owned(), | ||
| 121 | }); | 150 | }); |
| 122 | 151 | ||
| 123 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); | 152 | return Ok(warp::reply::with_status(res_json, StatusCode::BAD_REQUEST)); |
| 124 | } | 153 | } |
| 125 | 154 | ||
| 126 | // TODO: audit public key, is it valid? <13-04-21, yigit> // | ||
| 127 | let new_user = User { | 155 | let new_user = User { |
| 128 | user_id: priv_student_id, | 156 | user_id: privileged_student_id, |
| 129 | public_key: request.public_key, | 157 | public_key: request.public_key, |
| 130 | balance: 0, | 158 | balance: 0, |
| 131 | }; | 159 | }; |
| @@ -146,6 +174,17 @@ pub async fn authenticate_user( | |||
| 146 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) | 174 | Ok(warp::reply::with_status(res_json, StatusCode::CREATED)) |
| 147 | } | 175 | } |
| 148 | 176 | ||
| 177 | // fn shed_pem_header_footer(maybe_key: String) -> Result<Vec<u8>, String> { | ||
| 178 | // let der_encoded = maybe_key | ||
| 179 | // .lines() | ||
| 180 | // .filter(|line| !line.starts_with("-")) | ||
| 181 | // .fold(String::new(), |mut data, line| { | ||
| 182 | // data.push_str(&line); | ||
| 183 | // data | ||
| 184 | // }); | ||
| 185 | // Ok(base64::decode(&der_encoded).expect("failed to decode base64 content")) | ||
| 186 | // } | ||
| 187 | |||
| 149 | /// GET /transaction | 188 | /// GET /transaction |
| 150 | /// Returns JSON array of transactions | 189 | /// Returns JSON array of transactions |
| 151 | /// Cannot fail | 190 | /// Cannot fail |
| @@ -447,7 +486,6 @@ fn authorize_proposer(jwt_token: String, user_pem: &String) -> Result<TokenData< | |||
| 447 | debug!("raw_jwt: {:?}", raw_jwt); | 486 | debug!("raw_jwt: {:?}", raw_jwt); |
| 448 | 487 | ||
| 449 | // Extract a jsonwebtoken compatible decoding_key from user's public key | 488 | // Extract a jsonwebtoken compatible decoding_key from user's public key |
| 450 | // TODO: just use this for reading users pem key <13-04-21, yigit> // | ||
| 451 | let decoding_key = match DecodingKey::from_rsa_pem(user_pem.as_bytes()) { | 489 | let decoding_key = match DecodingKey::from_rsa_pem(user_pem.as_bytes()) { |
| 452 | Ok(key) => key, | 490 | Ok(key) => key, |
| 453 | Err(j) => { | 491 | Err(j) => { |
diff --git a/src/schema.rs b/src/schema.rs index f159d83..fb88640 100644 --- a/src/schema.rs +++ b/src/schema.rs | |||
| @@ -190,6 +190,7 @@ pub struct AuthRequest { | |||
| 190 | #[derive(Serialize, Deserialize, Debug)] | 190 | #[derive(Serialize, Deserialize, Debug)] |
| 191 | pub struct InitialAuthRequest { | 191 | pub struct InitialAuthRequest { |
| 192 | pub c: String, | 192 | pub c: String, |
| 193 | pub iv: [u8; 32], | ||
| 193 | pub key: String, | 194 | pub key: String, |
| 194 | } | 195 | } |
| 195 | 196 | ||
