From 0b59c0d310c22244bbc6c7d26a78c2779103511d Mon Sep 17 00:00:00 2001 From: Vincent Ollivier Date: Fri, 3 Jan 2020 22:01:48 +0100 Subject: [PATCH] Add password hashing --- Cargo.lock | 106 ++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 ++ dsk/cfg/passwords.csv | 1 + src/kernel/mod.rs | 1 + src/kernel/random.rs | 8 ++++ src/main.rs | 1 + src/user/base64.rs | 30 ++++++++++++ src/user/login.rs | 102 ++++++++++++++++++++++++++++++++++------ src/user/mod.rs | 1 + src/user/shell.rs | 1 + x86_64-moros.json | 2 +- 11 files changed, 243 insertions(+), 14 deletions(-) create mode 100644 dsk/cfg/passwords.csv create mode 100644 src/kernel/random.rs create mode 100644 src/user/base64.rs diff --git a/Cargo.lock b/Cargo.lock index e6d1ec93..4ae75172 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,12 @@ dependencies = [ "stable_deref_trait", ] +[[package]] +name = "base64" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" + [[package]] name = "bit_field" version = "0.9.0" @@ -32,12 +38,39 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array 0.12.3", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + [[package]] name = "bootloader" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d596849a47f28abdea62d7a6a25c4f6e69c3d9b09b0a2877db6e9cda004ca993" +[[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + [[package]] name = "byteorder" version = "1.3.2" @@ -59,6 +92,31 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22b8e308ccfc5acf3b82f79c0eac444cf6114cb2ac67a230ca6c177210068daa" +[[package]] +name = "crypto-mac" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" +dependencies = [ + "generic-array 0.12.3", + "subtle", +] + +[[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array 0.12.3", +] + +[[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + [[package]] name = "generic-array" version = "0.12.3" @@ -97,6 +155,16 @@ dependencies = [ "hash32", ] +[[package]] +name = "hmac" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" +dependencies = [ + "crypto-mac", + "digest", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -110,11 +178,15 @@ dependencies = [ name = "moros" version = "0.1.0" dependencies = [ + "base64", "bootloader", "heapless", + "hmac", "lazy_static", + "pbkdf2", "pc-keyboard", "pic8259_simple", + "sha2", "spin", "volatile", "x86_64", @@ -126,6 +198,22 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" +[[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] +name = "pbkdf2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" +dependencies = [ + "byteorder", + "crypto-mac", +] + [[package]] name = "pc-keyboard" version = "0.5.0" @@ -164,6 +252,18 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "sha2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + [[package]] name = "spin" version = "0.5.2" @@ -176,6 +276,12 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +[[package]] +name = "subtle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" + [[package]] name = "typenum" version = "1.11.2" diff --git a/Cargo.toml b/Cargo.toml index c65cffe7..294270ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,7 @@ pic8259_simple = "0.1.1" pc-keyboard = { version = "0.5.0", git = "https://github.com/vinc/pc-keyboard", branch = "feature/add-dvorak-layout" } heapless = "0.5.1" lazy_static = { version = "1.0", features = ["spin_no_std"] } +base64 = { version = "0.11", default-features = false } +pbkdf2 = { version = "0.3", default-features = false } +sha2 = { version = "0.8", default-features = false } +hmac = { version = "0.7", default-features = false } diff --git a/dsk/cfg/passwords.csv b/dsk/cfg/passwords.csv new file mode 100644 index 00000000..587faa6f --- /dev/null +++ b/dsk/cfg/passwords.csv @@ -0,0 +1 @@ +root,1$AAAQAA$nvQAFYGhN1a4NmHS8ooWrA$7zOWQLxpmRF4AC3G3+F2OVJ9IejzdH52TXQFlbRAmLg diff --git a/src/kernel/mod.rs b/src/kernel/mod.rs index 9c1e9c70..4884b41e 100644 --- a/src/kernel/mod.rs +++ b/src/kernel/mod.rs @@ -4,5 +4,6 @@ pub mod console; pub mod fs; pub mod gdt; pub mod interrupts; +pub mod random; pub mod sleep; pub mod vga; diff --git a/src/kernel/random.rs b/src/kernel/random.rs new file mode 100644 index 00000000..7eb4166c --- /dev/null +++ b/src/kernel/random.rs @@ -0,0 +1,8 @@ +use x86_64::instructions::random::RdRand; + +pub fn rand64() -> Option { + match RdRand::new() { + Some(rand) => rand.get_u64(), + None => None + } +} diff --git a/src/main.rs b/src/main.rs index 22aba551..6a9ac73b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ fn main(_boot_info: &'static BootInfo) -> ! { moros::init(); include_file("/cfg/boot.sh", include_str!("../dsk/cfg/boot.sh")); include_file("/cfg/banner.txt", include_str!("../dsk/cfg/banner.txt")); + include_file("/cfg/passwords.csv", include_str!("../dsk/cfg/passwords.csv")); loop { user::shell::main(&["shell", "/cfg/boot.sh"]); } diff --git a/src/user/base64.rs b/src/user/base64.rs new file mode 100644 index 00000000..b2aafee8 --- /dev/null +++ b/src/user/base64.rs @@ -0,0 +1,30 @@ +use crate::{print, user}; +use heapless::{String, Vec}; +use heapless::consts::*; + +pub fn main(args: &[&str]) -> user::shell::ExitCode { + if args.len() != 2 { + user::shell::ExitCode::CommandError + } else { + let buf = encode(args[1].as_bytes()); + let encoded = String::from_utf8(buf).unwrap(); + print!("{}\n", encoded); + user::shell::ExitCode::CommandSuccessful + } +} + +pub fn encode(s: &[u8]) -> Vec { + let mut buf = Vec::::new(); + buf.resize(s.len() * 4 / 3 + 4, 0).unwrap(); // Resize to base64 + padding + let bytes_written = base64::encode_config_slice(s, base64::STANDARD_NO_PAD, &mut buf); + buf.resize(bytes_written, 0).unwrap(); // Resize back to actual size + buf +} + +pub fn decode(s: &[u8]) -> Vec { + let mut buf = Vec::::new(); + buf.resize(s.len(), 0).unwrap(); + let bytes_written = base64::decode_config_slice(s, base64::STANDARD_NO_PAD, &mut buf).unwrap(); + buf.resize(bytes_written, 0).unwrap(); + buf +} diff --git a/src/user/login.rs b/src/user/login.rs index 35db991c..dd9a62a7 100644 --- a/src/user/login.rs +++ b/src/user/login.rs @@ -1,4 +1,10 @@ use crate::{print, kernel, user}; +use heapless::{String, FnvIndexMap, Vec}; +use heapless::consts::*; +use core::convert::TryInto; +use core::str; +use hmac::Hmac; +use sha2::Sha256; pub fn main(_args: &[&str]) -> user::shell::ExitCode { login() @@ -6,21 +12,91 @@ pub fn main(_args: &[&str]) -> user::shell::ExitCode { // TODO: Add max number of attempts pub fn login() -> user::shell::ExitCode { - print!("\nUsername: "); - let username = kernel::console::get_line(); - if username != "root\n" { - kernel::sleep::sleep(1.0); - return login(); + let mut hashed_passwords: FnvIndexMap, String, U256> = FnvIndexMap::new(); + if let Some(file) = kernel::fs::File::open("/cfg/passwords.csv") { + for line in file.read().split("\n") { + let mut rows = line.split(","); + if let Some(username) = rows.next() { + if let Some(hashed_password) = rows.next() { + hashed_passwords.insert(username.into(), hashed_password.into()).unwrap(); + } + } + } } - print!("Password: "); - kernel::console::disable_echo(); - let password = kernel::console::get_line(); - kernel::console::enable_echo(); - print!("\n"); - if password != "root\n" { - kernel::sleep::sleep(1.0); - return login(); + print!("\nUsername: "); + let mut username = kernel::console::get_line(); + username.pop(); // Trim end of string + match hashed_passwords.get(&username) { + None => { + kernel::sleep::sleep(1.0); + return login(); + }, + Some(hashed_password) => { + print!("Password: "); + kernel::console::disable_echo(); + let mut password = kernel::console::get_line(); + kernel::console::enable_echo(); + print!("\n"); + password.pop(); + if !check(&password, hashed_password) { + kernel::sleep::sleep(1.0); + return login(); + } + } } user::shell::ExitCode::CommandSuccessful } + +pub fn check(password: &str, hashed_password: &str) -> bool { + let fields: Vec<_, U4> = hashed_password.split('$').collect(); + if fields.len() != 4 || fields[0] != "1" { + return false; + } + + let decoded_field = user::base64::decode(&fields[1].as_bytes()); + let c = u32::from_be_bytes(decoded_field[0..4].try_into().unwrap()); + + let decoded_field = user::base64::decode(&fields[2].as_bytes()); + let salt: [u8; 16] = decoded_field[0..16].try_into().unwrap(); + + let mut hash = [0u8; 32]; + pbkdf2::pbkdf2::>(password.as_bytes(), &salt, c as usize, &mut hash); + let encoded_hash = String::from_utf8(user::base64::encode(&hash)).unwrap(); + + encoded_hash == fields[3] +} + +// Password hashing version 1 => PBKDF2-HMAC-SHA256 + BASE64 +// Fields: "$$$" +// Example: "1$AAAQAA$PDkXP0I8O7SxNOxvUKmHHQ$BwIUWBxKs50BTpH6i4ImF3SZOxADv7dh4xtu3IKc3o8" +pub fn hash(password: &str) -> String { + let v = "1"; // Password hashing version + let c = 4096u32; // Number of iterations + let mut salt = [0u8; 16]; + let mut hash = [0u8; 32]; + + // Generating salt + for i in 0..2 { + let num = kernel::random::rand64().unwrap(); + let buf = num.to_be_bytes(); + let n = buf.len(); + for j in 0..n { + salt[i * n + j] = buf[j]; + } + } + + // Hashing password with PBKDF2-HMAC-SHA256 + pbkdf2::pbkdf2::>(password.as_bytes(), &salt, c as usize, &mut hash); + + // Encoding in Base64 standard without padding + let c = c.to_be_bytes(); + let mut res: String = String::from(v); + res.push('$').unwrap(); + res.push_str(&String::from_utf8(user::base64::encode(&c)).unwrap()).unwrap(); + res.push('$').unwrap(); + res.push_str(&String::from_utf8(user::base64::encode(&salt)).unwrap()).unwrap(); + res.push('$').unwrap(); + res.push_str(&String::from_utf8(user::base64::encode(&hash)).unwrap()).unwrap(); + res +} diff --git a/src/user/mod.rs b/src/user/mod.rs index 48449e14..8fd13257 100644 --- a/src/user/mod.rs +++ b/src/user/mod.rs @@ -1,3 +1,4 @@ +pub mod base64; pub mod clear; pub mod date; pub mod editor; diff --git a/src/user/shell.rs b/src/user/shell.rs index 5abb315f..a260b36c 100644 --- a/src/user/shell.rs +++ b/src/user/shell.rs @@ -141,6 +141,7 @@ impl Shell { "sleep" => user::sleep::main(&args), "clear" => user::clear::main(&args), "login" => user::login::main(&args), + "base64" => user::base64::main(&args), _ => ExitCode::CommandUnknown, } } diff --git a/x86_64-moros.json b/x86_64-moros.json index c1c29f9e..9afe809f 100644 --- a/x86_64-moros.json +++ b/x86_64-moros.json @@ -12,4 +12,4 @@ "panic-strategy": "abort", "disable-redzone": true, "features": "-mmx,-sse,+soft-float" - } \ No newline at end of file + }