From b7b6860983db503119345f9c31f436ac3705ba92 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sat, 9 Sep 2023 23:15:17 +0100 Subject: [PATCH 01/22] feat(hsh): v0.0.5 --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- README.md | 4 ++-- TEMPLATE.md | 4 ++-- src/lib.rs | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bf2baa..962fc57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -618,7 +618,7 @@ dependencies = [ [[package]] name = "hsh" -version = "0.0.4" +version = "0.0.5" dependencies = [ "argon2rs", "assert_cmd", @@ -1214,7 +1214,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vrd" -version = "0.0.4" +version = "0.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a81b8b5b404f3d7afa1b8142a6bc980c20cd68556c634c3db517871aa0402521" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index 92941ea..94307ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ license = "MIT OR Apache-2.0" name = "hsh" repository = "https://github.com/sebastienrousseau/hsh/" rust-version = "1.69.0" -version = "0.0.4" +version = "0.0.5" include = [ "/CONTRIBUTING.md", "/LICENSE-APACHE", @@ -55,7 +55,7 @@ bcrypt = "0.14.0" scrypt = "0.11.0" serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" -vrd = "0.0.4" +vrd = "0.0.5" [dev-dependencies] assert_cmd = "2.0.11" diff --git a/README.md b/README.md index 3abf8d1..a8729bb 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ file: ```toml [dependencies] -hsh = "0.0.4" +hsh = "0.0.5" ``` Add the following to your `main.rs` file: @@ -239,6 +239,6 @@ lot of useful suggestions on how to improve this project. [crates-badge]: https://img.shields.io/crates/v/hsh.svg?style=for-the-badge 'Crates.io' [divider]: https://kura.pro/common/images/elements/divider.svg "divider" [docs-badge]: https://img.shields.io/docsrs/hsh.svg?style=for-the-badge 'Docs.rs' -[libs-badge]: https://img.shields.io/badge/lib.rs-v0.0.4-orange.svg?style=for-the-badge 'Lib.rs' +[libs-badge]: https://img.shields.io/badge/lib.rs-v0.0.5-orange.svg?style=for-the-badge 'Lib.rs' [license-badge]: https://img.shields.io/crates/l/hsh.svg?style=for-the-badge 'License' [made-with-rust]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust 'Made With Rust' diff --git a/TEMPLATE.md b/TEMPLATE.md index b7cec15..9469b95 100644 --- a/TEMPLATE.md +++ b/TEMPLATE.md @@ -4,7 +4,7 @@ alt="Hash (HSH) logo" width="261" align="right" /> -# Hash (HSH) v0.0.4 +# Hash (HSH) v0.0.5 Quantum-Resistant Cryptographic Hash Library for Password Hashing and Verification @@ -82,7 +82,7 @@ Hash (HSH) is a lightweight library that can easily integrate into any Rust proj [crates-badge]: https://img.shields.io/crates/v/hsh.svg?style=for-the-badge 'Crates.io' [divider]: https://kura.pro/common/images/elements/divider.svg "divider" [docs-badge]: https://img.shields.io/docsrs/hsh.svg?style=for-the-badge 'Docs.rs' -[libs-badge]: https://img.shields.io/badge/lib.rs-v0.0.4-orange.svg?style=for-the-badge 'Lib.rs' +[libs-badge]: https://img.shields.io/badge/lib.rs-v0.0.5-orange.svg?style=for-the-badge 'Lib.rs' [license-badge]: https://img.shields.io/crates/l/hsh.svg?style=for-the-badge 'License' [made-with-rust]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust 'Made With Rust' diff --git a/src/lib.rs b/src/lib.rs index b91ed8a..147e5b0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,7 +139,7 @@ //! [banner]: https://kura.pro/hsh/images/banners/banner-hsh.svg "The Hash (HSH) Banner" //! [crate-shield]: https://img.shields.io/crates/v/hsh.svg?style=for-the-badge&color=success&labelColor=27A006 "Crates.io" //! [github-shield]: https://img.shields.io/badge/github-555555?style=for-the-badge&labelColor=000000&logo=github "GitHub" -//! [lib-rs-shield]: https://img.shields.io/badge/lib.rs-v0.0.4-success.svg?style=for-the-badge&color=8A48FF&labelColor=6F36E4 "Lib.rs" +//! [lib-rs-shield]: https://img.shields.io/badge/lib.rs-v0.0.5-success.svg?style=for-the-badge&color=8A48FF&labelColor=6F36E4 "Lib.rs" //! [license-shield]: https://img.shields.io/crates/l/hsh.svg?style=for-the-badge&color=007EC6&labelColor=03589B "License" //! [rust-shield]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust "Rust" //! From 1c8d5bf6482fcf1faf4afa7ebc27d039a6b6ce68 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sat, 9 Sep 2023 23:34:15 +0100 Subject: [PATCH 02/22] deps(hsh): updating dependencies --- Cargo.lock | 40 ++++++++++++++++++++-------------------- Cargo.toml | 12 ++++++------ 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 962fc57..d8adda7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,9 +41,9 @@ dependencies = [ [[package]] name = "assert_cmd" -version = "2.0.11" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d6b683edf8d1119fe420a94f8a7e389239666aa72e65495d91c00462510151" +checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" dependencies = [ "anstyle", "bstr", @@ -73,9 +73,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.21.2" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" [[package]] name = "base64ct" @@ -85,9 +85,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bcrypt" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df288bec72232f78c1ec5fe4e8f1d108aa0265476e93097593c803c8c02062a" +checksum = "28d1c9c15093eb224f0baa400f38fcd713fc1391a6f1c389d886beef146d60a3" dependencies = [ "base64", "blowfish", @@ -892,18 +892,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1054,29 +1054,29 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.163" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2113ab51b87a539ae008b5c6c02dc020ffa39afd2d83cffcb3f4eb2722cebec2" +checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.163" +version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c805777e3930c8883389c602315a24224bcc738b63905ef87cd1420353ea93e" +checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.31", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "2cc66a619ed80bf7a0f6b17dd063a84b88f6dea1813737cf469aef1d081142c2" dependencies = [ "itoa", "ryu", @@ -1135,9 +1135,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" dependencies = [ "proc-macro2", "quote", @@ -1214,7 +1214,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "vrd" -version = "0.0.5" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a81b8b5b404f3d7afa1b8142a6bc980c20cd68556c634c3db517871aa0402521" dependencies = [ diff --git a/Cargo.toml b/Cargo.toml index 94307ec..eddbfc1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,15 +50,15 @@ path = "examples/hsh.rs" [dependencies] argon2rs = "0.2.5" -base64 = "0.21.2" -bcrypt = "0.14.0" +base64 = "0.21.3" +bcrypt = "0.15.0" scrypt = "0.11.0" -serde = { version = "1.0.163", features = ["derive"] } -serde_json = "1.0.96" -vrd = "0.0.5" +serde = { version = "1.0.188", features = ["derive"] } +serde_json = "1.0.106" +vrd = "0.0.4" [dev-dependencies] -assert_cmd = "2.0.11" +assert_cmd = "2.0.12" criterion = "0.5.1" [lib] From 52b287cff735f17ddd5e4ffeacb1c140aaf82537 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sat, 9 Sep 2023 23:41:18 +0100 Subject: [PATCH 03/22] fix(hsh): Clippy warnings in hsh_in_range! test --- tests/test_macros.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_macros.rs b/tests/test_macros.rs index 74f33d8..9a94a76 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -81,9 +81,13 @@ mod tests { #[test] fn macro_hsh_in_range() { - // Test that hsh_in_range! macro correctly checks if a number is within a range - assert!(hsh_in_range!(10, 0, 100)); - assert!(!hsh_in_range!(-10, 0, 100)); + let lower_bound = 0; + let upper_bound = 100; + let test_val1 = 10; + let test_val2 = -10; + + assert!(hsh_in_range!(test_val1, lower_bound, upper_bound)); + assert!(!hsh_in_range!(test_val2, lower_bound, upper_bound)); } #[test] From 71c63a872d583be9821efd9e940997cfcd8542e8 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sat, 9 Sep 2023 23:44:10 +0100 Subject: [PATCH 04/22] fix(hsh): Clippy warnings in hsh_in_range! test --- tests/test_macros.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_macros.rs b/tests/test_macros.rs index 74f33d8..9a94a76 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -81,9 +81,13 @@ mod tests { #[test] fn macro_hsh_in_range() { - // Test that hsh_in_range! macro correctly checks if a number is within a range - assert!(hsh_in_range!(10, 0, 100)); - assert!(!hsh_in_range!(-10, 0, 100)); + let lower_bound = 0; + let upper_bound = 100; + let test_val1 = 10; + let test_val2 = -10; + + assert!(hsh_in_range!(test_val1, lower_bound, upper_bound)); + assert!(!hsh_in_range!(test_val2, lower_bound, upper_bound)); } #[test] From 0cfe40e038e0ab34ea6a284fbb1ce09ececd46d5 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sat, 9 Sep 2023 23:47:36 +0100 Subject: [PATCH 05/22] fix(hsh): Clippy warnings in hsh_in_range! test --- tests/test_macros.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_macros.rs b/tests/test_macros.rs index 9a94a76..7defe39 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -79,6 +79,7 @@ mod tests { assert!(!hsh_contains!("Hello", "x")); } + #[test] fn macro_hsh_in_range() { let lower_bound = 0; @@ -90,6 +91,7 @@ mod tests { assert!(!hsh_in_range!(test_val2, lower_bound, upper_bound)); } + #[test] fn macro_hsh_parse() { let input: Result = hsh_parse!("42"); From 0d037bf18de40be7ece7006c1df5226bc46058dc Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 10:15:16 +0100 Subject: [PATCH 06/22] feat(hsh): decoupling models --- Cargo.lock | 4 +- Cargo.toml | 2 +- benches/criterion.rs | 2 +- examples/hsh.rs | 3 +- src/constants.rs | 0 src/lib.rs | 55 ++++++----- src/loggers.rs | 228 +++++++++++++++++++++++++++++++++++++++++++ src/macros.rs | 6 +- src/models/data.rs | 30 ++++++ src/models/mod.rs | 5 + tests/test_lib.rs | 1 + tests/test_macros.rs | 3 +- 12 files changed, 304 insertions(+), 35 deletions(-) create mode 100644 src/constants.rs create mode 100644 src/loggers.rs create mode 100644 src/models/data.rs create mode 100644 src/models/mod.rs diff --git a/Cargo.lock b/Cargo.lock index d8adda7..5070395 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,9 +73,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" -version = "0.21.3" +version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414dcefbc63d77c526a76b3afcf6fbb9b5e2791c19c3aa2297733208750c6e53" +checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "base64ct" diff --git a/Cargo.toml b/Cargo.toml index eddbfc1..8183ff4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ path = "examples/hsh.rs" [dependencies] argon2rs = "0.2.5" -base64 = "0.21.3" +base64 = "0.21.4" bcrypt = "0.15.0" scrypt = "0.11.0" serde = { version = "1.0.188", features = ["derive"] } diff --git a/benches/criterion.rs b/benches/criterion.rs index ce8f822..530a4d1 100644 --- a/benches/criterion.rs +++ b/benches/criterion.rs @@ -10,7 +10,7 @@ use criterion::{ }; extern crate hsh; -use self::hsh::Hash; +use hsh::models::data::Hash; fn generate_hash_benchmark(c: &mut Criterion) { c.bench_function("generate_hash", |b| { diff --git a/examples/hsh.rs b/examples/hsh.rs index fa241f4..388ae4d 100644 --- a/examples/hsh.rs +++ b/examples/hsh.rs @@ -2,7 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Using the Hash (HSH) library -use hsh::{new_hash, Hash, HashAlgorithm}; +use hsh::{new_hash, HashAlgorithm}; +use hsh::models::data::*; use std::str::FromStr; // Creating and verifying hashes diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/lib.rs b/src/lib.rs index 147e5b0..d4747d9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,7 @@ //! ```rust //! // Import the Hash struct //! extern crate hsh; -//! use hsh::Hash; +//! use hsh::models::data::Hash; //! //! // Main function //! fn main() { @@ -169,38 +169,41 @@ extern crate base64; extern crate bcrypt; extern crate scrypt; extern crate vrd; +use crate::models::data::*; use argon2rs::argon2i_simple; use base64::{engine::general_purpose, Engine as _}; - use scrypt::scrypt; use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; use vrd::Random; -/// A type alias for a salt. -pub type Salt = Vec; - -/// A struct for storing and verifying hashed passwords based on the argon2rs crate -#[non_exhaustive] -#[derive( - Clone, - Debug, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, - Deserialize, -)] -pub struct Hash { - /// The password hash. - pub hash: Vec, - /// The salt used for hashing - pub salt: Salt, - /// The hash algorithm used - pub algorithm: HashAlgorithm, -} +/// The `models` module contains the data models for the library. +pub mod models; + +// /// A type alias for a salt. +// pub type Salt = Vec; + +// /// A struct for storing and verifying hashed passwords based on the argon2rs crate +// #[non_exhaustive] +// #[derive( +// Clone, +// Debug, +// Eq, +// Hash, +// Ord, +// PartialEq, +// PartialOrd, +// Serialize, +// Deserialize, +// )] +// pub struct Hash { +// /// The password hash. +// pub hash: Vec, +// /// The salt used for hashing +// pub salt: Salt, +// /// The hash algorithm used +// pub algorithm: HashAlgorithm, +// } /// The supported hash algorithms #[non_exhaustive] diff --git a/src/loggers.rs b/src/loggers.rs new file mode 100644 index 0000000..7f62b33 --- /dev/null +++ b/src/loggers.rs @@ -0,0 +1,228 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// Standard library imports for formatting and I/O operations. +use std::{ + fmt, + io::{self, Write as IoWrite}, + fs::OpenOptions, +}; + + +/// Enum representing the different log formats that can be used. +/// +/// This enum allows the developer to specify the format in which log messages should be displayed. +/// +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub enum LogFormat { + /// The log format is set to the Common Log Format (CLF) + CLF, + /// The log format is set to the JSON format + JSON, + /// The log format is set to the Common Event Format (CEF) + CEF, + /// The log format is set to the Extended Log Format (ELF) + ELF, + /// The log format is set to the W3C Extended Log File Format + W3C, + /// The log format is set to the Graylog Extended Log Format (GELF) + GELF, +} + +/// Implements Display trait for LogFormat enum. +/// +/// This allows easy conversion of the log format enums to strings. +impl fmt::Display for LogFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + writeln!(f, "{:?}", self) + } +} + +/// An enumeration of the different levels that a log message can have. +/// Each variant of the enumeration represents a different level of +/// importance. +/// +/// # Arguments +/// +/// * `ALL` - The log level is set to all. +/// * `DEBUG` - The log level is set to debug. +/// * `DISABLED` - The log level is set to disabled. +/// * `ERROR` - The log level is set to error. +/// * `FATAL` - The log level is set to fatal. +/// * `INFO` - The log level is set to info. +/// * `NONE` - The log level is set to none. +/// * `TRACE` - The log level is set to trace. +/// * `VERBOSE` - The log level is set to verbose. +/// * `WARNING` - The log level is set to warning. +/// +#[derive(Debug, PartialEq, Eq, Hash, Clone,PartialOrd)] +pub enum LogLevel { + /// The log level is set to all. + ALL, + /// The log level is set to debug. + DEBUG, + /// The log level is set to disabled. + DISABLED, + /// The log level is set to error. + ERROR, + /// The log level is set to fatal. + FATAL, + /// The log level is set to info. + INFO, + /// The log level is set to none. + NONE, + /// The log level is set to trace. + TRACE, + /// The log level is set to verbose. + VERBOSE, + /// The log level is set to warning. + WARNING, +} +/// Display trait implementation for `LogLevel`. +/// +/// This converts the enum to a string representation. +impl fmt::Display for LogLevel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +/// Struct representing a log message. +/// +/// Contains all the elements that make up a complete log message. +#[derive(Debug, PartialEq, Eq, Hash, Clone)] +pub struct Log { + /// A string that holds a session ID. The session ID is a unique + /// identifier for the current session. A random GUID (Globally + /// Unique Identifier) is generated by default. + pub session_id: String, + /// A string that holds the timestamp in ISO 8601 format. + pub time: String, + /// A string that holds the level (INFO, WARN, ERROR, etc.). + pub level: LogLevel, + /// A string that holds the component name. + pub component: String, + /// A string that holds the description of the log message. + pub description: String, + /// A string that holds the log format. + pub format: LogFormat, +} + +impl Log { + /// Logs a message to the console using a pre-allocated buffer to + /// reduce memory allocation and flush the output buffer to ensure + /// that the message is written immediately. + /// + /// # Errors + /// + /// This function will panic if an error occurs when writing to the + /// pre-allocated buffer or flushing the output buffer. + pub fn log(&self) -> io::Result<()> { + // Open the file in append mode. If the file does not exist, create it. + let mut file = OpenOptions::new() + .write(true) + .truncate(true) + .open("shokunin.log")?; + match self.format { + LogFormat::CLF => { + writeln!( + file, + "SessionID={}\tTimestamp={}\tDescription={}\tLevel={}\tComponent={}\tFormat={}", + self.session_id, self.time, self.description, self.level, self.component, self.format + ) + }, + LogFormat::JSON => { + writeln!( + file, + r#"{{"session_id": "{}", "timestamp": "{}", "description": "{}", "level": "{}", "component": "{}", "format": "{}"}}"#, + self.session_id, self.time, self.description, self.level, self.component, self.format + ) + }, + LogFormat::CEF => { + writeln!( + file, + r#"[CEF] + + 1 + shokunin + Application + {} + Log + {} + {} + {} + localhost + localhost + - + - + - + - + - + - + + "#, + self.time, self.level, self.description, self.session_id + ) + }, + _ => Err(io::Error::new(io::ErrorKind::InvalidInput, "Unsupported log format")), + }?; + file.flush()?; + Ok(()) + } + + /// Creates a new `Log` instance. + /// + /// Initializes a new `Log` struct with the provided details. + /// + /// # Returns + /// + /// Returns a new instance of the `Log` struct. + pub fn new( + session_id: &str, + time: &str, + level: LogLevel, + component: &str, + description: &str, + format: LogFormat, + ) -> Self { + Self { + session_id: session_id.to_string(), + time: time.to_string(), + level, + component: component.to_string(), + description: description.to_string(), + format, + } + } +} + +/// Provides default values for `Log`. +/// +/// This implementation provides a quick way to generate a `Log` instance with default values. +impl Default for Log { + fn default() -> Self { + Self { + session_id: String::default(), + time: String::default(), + level: LogLevel::INFO, // Default log level + component: String::default(), + description: String::default(), + format: LogFormat::CLF, // Default log format + } + } +} +#[cfg(test)] +/// Tests for the `log_info!` macro. +mod tests { + use crate::macro_log_info; + + #[test] + fn test_log_info() { + macro_log_info!( + LogLevel::INFO, + "component", + "description", + LogFormat::CLF + ); + } +} \ No newline at end of file diff --git a/src/macros.rs b/src/macros.rs index 44926e5..a63a748 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -366,7 +366,7 @@ macro_rules! match_algo { /// /// ``` /// extern crate hsh; -/// use hsh::Hash; +/// use hsh::models::data::Hash; /// use hsh::{ generate_hash, HashAlgorithm }; /// /// let password = "password"; @@ -391,7 +391,7 @@ macro_rules! generate_hash { /// /// ``` /// extern crate hsh; -/// use hsh::Hash; +/// use hsh::models::data::Hash; /// use hsh::{ new_hash, HashAlgorithm }; /// /// let password = "password"; @@ -415,7 +415,7 @@ macro_rules! new_hash { /// /// ``` /// extern crate hsh; -/// use hsh::Hash; +/// use hsh::models::data::Hash; /// use hsh::{ hash_length }; /// use hsh::{ new_hash, HashAlgorithm }; /// diff --git a/src/models/data.rs b/src/models/data.rs new file mode 100644 index 0000000..d8adac5 --- /dev/null +++ b/src/models/data.rs @@ -0,0 +1,30 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::HashAlgorithm; +use serde::{Deserialize, Serialize}; + +/// A type alias for a salt. +pub type Salt = Vec; + +/// A struct for storing and verifying hashed passwords based on the argon2rs crate +#[non_exhaustive] +#[derive( + Clone, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, + Deserialize, +)] +pub struct Hash { + /// The password hash. + pub hash: Vec, + /// The salt used for hashing + pub salt: Salt, + /// The hash algorithm used + pub algorithm: HashAlgorithm, +} \ No newline at end of file diff --git a/src/models/mod.rs b/src/models/mod.rs new file mode 100644 index 0000000..66d37e3 --- /dev/null +++ b/src/models/mod.rs @@ -0,0 +1,5 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +/// The `data` module contains the structs. +pub mod data; \ No newline at end of file diff --git a/tests/test_lib.rs b/tests/test_lib.rs index caca6ea..2014b90 100644 --- a/tests/test_lib.rs +++ b/tests/test_lib.rs @@ -4,6 +4,7 @@ #[cfg(test)] mod tests { use hsh::*; + use hsh::models::data::Hash; #[test] fn test_new() { diff --git a/tests/test_macros.rs b/tests/test_macros.rs index 7defe39..efb902b 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -6,7 +6,8 @@ mod tests { // Importing hsh crate and all of its macros extern crate hsh; - use hsh::{generate_hash, hash_length, new_hash, Hash}; + use hsh::{generate_hash, hash_length, new_hash}; + use hsh::models::data::*; use hsh::{ hsh_assert, hsh_contains, hsh_in_range, hsh_join, hsh_max, hsh_min, hsh_parse, hsh_print, hsh_print_vec, hsh_split, From 5db2d058c24b62fcbcf354d3999ab9ae9c512e3a Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 11:02:50 +0100 Subject: [PATCH 07/22] feat(hsh): decoupling hash_algorithm --- benches/criterion.rs | 2 +- examples/hsh.rs | 4 +- src/lib.rs | 77 +++------------------------------ src/macros.rs | 16 +++---- src/models/{data.rs => hash.rs} | 2 +- src/models/hash_algorithm.rs | 39 +++++++++++++++++ src/models/mod.rs | 5 ++- tests/test_lib.rs | 4 +- tests/test_macros.rs | 4 +- 9 files changed, 64 insertions(+), 89 deletions(-) rename src/models/{data.rs => hash.rs} (93%) create mode 100644 src/models/hash_algorithm.rs diff --git a/benches/criterion.rs b/benches/criterion.rs index 530a4d1..a46a339 100644 --- a/benches/criterion.rs +++ b/benches/criterion.rs @@ -10,7 +10,7 @@ use criterion::{ }; extern crate hsh; -use hsh::models::data::Hash; +use hsh::models::hash::Hash; fn generate_hash_benchmark(c: &mut Criterion) { c.bench_function("generate_hash", |b| { diff --git a/examples/hsh.rs b/examples/hsh.rs index 388ae4d..11ffa63 100644 --- a/examples/hsh.rs +++ b/examples/hsh.rs @@ -2,9 +2,9 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Using the Hash (HSH) library -use hsh::{new_hash, HashAlgorithm}; -use hsh::models::data::*; + use std::str::FromStr; +use hsh::{models::{hash_algorithm::HashAlgorithm, hash::Hash}, new_hash}; // Creating and verifying hashes fn create_and_verify_hash() { diff --git a/src/lib.rs b/src/lib.rs index d4747d9..9912f41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -106,7 +106,7 @@ //! ```rust //! // Import the Hash struct //! extern crate hsh; -//! use hsh::models::data::Hash; +//! use hsh::models::hash::Hash; //! //! // Main function //! fn main() { @@ -164,88 +164,21 @@ /// The `macros` module contains functions for generating macros. pub mod macros; +/// The `models` module contains the data models for the library. +pub mod models; + extern crate argon2rs; extern crate base64; extern crate bcrypt; extern crate scrypt; extern crate vrd; -use crate::models::data::*; +use crate::models::{hash::*, hash_algorithm::*}; use argon2rs::argon2i_simple; use base64::{engine::general_purpose, Engine as _}; use scrypt::scrypt; -use serde::{Deserialize, Serialize}; use std::{fmt, str::FromStr}; use vrd::Random; -/// The `models` module contains the data models for the library. -pub mod models; - -// /// A type alias for a salt. -// pub type Salt = Vec; - -// /// A struct for storing and verifying hashed passwords based on the argon2rs crate -// #[non_exhaustive] -// #[derive( -// Clone, -// Debug, -// Eq, -// Hash, -// Ord, -// PartialEq, -// PartialOrd, -// Serialize, -// Deserialize, -// )] -// pub struct Hash { -// /// The password hash. -// pub hash: Vec, -// /// The salt used for hashing -// pub salt: Salt, -// /// The hash algorithm used -// pub algorithm: HashAlgorithm, -// } - -/// The supported hash algorithms -#[non_exhaustive] -#[derive( - Clone, - Copy, - Debug, - Eq, - Hash, - Ord, - PartialEq, - PartialOrd, - Serialize, - Deserialize, -)] - -/// Enum representing different hash algorithms for password hashing. -pub enum HashAlgorithm { - /// Argon2i: A memory-hard password hashing algorithm. - /// - /// Argon2i is designed to be resistant against various types of attacks, - /// including GPU-based attacks and side-channel attacks. It incorporates - /// multiple parameters, such as memory usage, parallelism, and time cost, - /// to make it difficult for attackers to crack hashed passwords efficiently. - Argon2i, - - /// Bcrypt: A widely used password hashing algorithm. - /// - /// Bcrypt is based on the Blowfish encryption cipher and is designed to be - /// slow and computationally expensive. It uses a technique called key - /// stretching, where the password is repeatedly hashed with a random salt - /// and a specified number of iterations. This approach makes it time-consuming - /// and resource-intensive for attackers to perform password cracking. - Bcrypt, - - /// Scrypt: A memory-hard password hashing algorithm. - /// - /// Scrypt is designed to be memory-hard and resistant to brute-force attacks. - /// It uses a large amount of memory, making it more difficult and costly for - /// attackers to perform parallelized attacks using specialized hardware. - Scrypt, -} impl Hash { /// A function that returns the hash algorithm used by the hash map. diff --git a/src/macros.rs b/src/macros.rs index a63a748..4fd744f 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -339,7 +339,7 @@ macro_rules! random_string { /// /// ``` /// extern crate hsh; -/// use hsh::{ match_algo, HashAlgorithm }; +/// use hsh::{ match_algo, models::hash_algorithm::HashAlgorithm }; /// /// let algo = match_algo!("bcrypt"); /// ``` @@ -366,8 +366,8 @@ macro_rules! match_algo { /// /// ``` /// extern crate hsh; -/// use hsh::models::data::Hash; -/// use hsh::{ generate_hash, HashAlgorithm }; +/// use hsh::models::hash::Hash; +/// use hsh::{generate_hash, models::hash_algorithm::{HashAlgorithm}}; /// /// let password = "password"; /// let salt = "salt"; @@ -391,8 +391,8 @@ macro_rules! generate_hash { /// /// ``` /// extern crate hsh; -/// use hsh::models::data::Hash; -/// use hsh::{ new_hash, HashAlgorithm }; +/// use hsh::{new_hash, models::{hash::Hash, hash_algorithm::{HashAlgorithm}}}; +/// /// /// let password = "password"; /// let salt = "salt"; @@ -415,9 +415,9 @@ macro_rules! new_hash { /// /// ``` /// extern crate hsh; -/// use hsh::models::data::Hash; -/// use hsh::{ hash_length }; -/// use hsh::{ new_hash, HashAlgorithm }; +/// use hsh::models::{hash::Hash, hash_algorithm::{HashAlgorithm}}; +/// use hsh::{ hash_length, new_hash }; +/// /// /// let password = "password"; /// let salt = "salt"; diff --git a/src/models/data.rs b/src/models/hash.rs similarity index 93% rename from src/models/data.rs rename to src/models/hash.rs index d8adac5..d7c4ef9 100644 --- a/src/models/data.rs +++ b/src/models/hash.rs @@ -1,8 +1,8 @@ // Copyright © 2023 Hash (HSH) library. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT -use crate::HashAlgorithm; use serde::{Deserialize, Serialize}; +use super::hash_algorithm::HashAlgorithm; /// A type alias for a salt. pub type Salt = Vec; diff --git a/src/models/hash_algorithm.rs b/src/models/hash_algorithm.rs new file mode 100644 index 0000000..ca3c37c --- /dev/null +++ b/src/models/hash_algorithm.rs @@ -0,0 +1,39 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use serde::{Deserialize, Serialize}; + +/// Represents the different algorithms available for password hashing. +/// +/// This enum is used to specify which hashing algorithm should be used +/// when creating a new hashed password. +/// +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +pub enum HashAlgorithm { + /// Argon2i - A memory-hard password hashing algorithm. + /// + /// Resistant against various types of attacks, including: + /// - GPU-based attacks + /// - Side-channel attacks + /// + /// Incorporates multiple parameters to deter attackers: + /// - Memory usage + /// - Parallelism + /// - Time cost + Argon2i, + + /// Bcrypt - A widely used, computationally intensive password hashing algorithm. + /// + /// Features: + /// - Based on the Blowfish encryption cipher + /// - Uses key stretching technique + /// - Time-consuming and resource-intensive, which makes it resistant to cracking + Bcrypt, + + /// Scrypt - A memory-hard password hashing algorithm designed for resistance to brute-force attacks. + /// + /// Features: + /// - Consumes a large amount of memory + /// - Makes parallelized attacks difficult and costly + Scrypt, +} diff --git a/src/models/mod.rs b/src/models/mod.rs index 66d37e3..ced1a95 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -2,4 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT /// The `data` module contains the structs. -pub mod data; \ No newline at end of file +pub mod hash; + +/// The `hash_algorithm` module contains the `HashAlgorithm` enum. +pub mod hash_algorithm; \ No newline at end of file diff --git a/tests/test_lib.rs b/tests/test_lib.rs index 2014b90..20c659c 100644 --- a/tests/test_lib.rs +++ b/tests/test_lib.rs @@ -3,8 +3,8 @@ #[cfg(test)] mod tests { - use hsh::*; - use hsh::models::data::Hash; + use hsh::models::hash::Hash; + use hsh::models::hash_algorithm::HashAlgorithm; #[test] fn test_new() { diff --git a/tests/test_macros.rs b/tests/test_macros.rs index efb902b..07c86e9 100644 --- a/tests/test_macros.rs +++ b/tests/test_macros.rs @@ -7,12 +7,12 @@ mod tests { // Importing hsh crate and all of its macros extern crate hsh; use hsh::{generate_hash, hash_length, new_hash}; - use hsh::models::data::*; + use hsh::models::hash::*; + use hsh::models::hash_algorithm::HashAlgorithm; use hsh::{ hsh_assert, hsh_contains, hsh_in_range, hsh_join, hsh_max, hsh_min, hsh_parse, hsh_print, hsh_print_vec, hsh_split, hsh_vec, match_algo, random_string, to_str_error, - HashAlgorithm, }; #[test] From 48f3b99fd7b3e0b788220dc8ddd28f385e19154f Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 13:12:33 +0100 Subject: [PATCH 08/22] feat(hsh): decoupling argon2i, bcrypt and scrypt --- examples/hsh.rs | 98 +++++++++++++++++++++++------------- src/algorithms/argon2i.rs | 37 ++++++++++++++ src/algorithms/bcrypt.rs | 55 ++++++++++++++++++++ src/algorithms/mod.rs | 11 ++++ src/algorithms/scrypt.rs | 47 +++++++++++++++++ src/lib.rs | 69 ++++++++++++++----------- src/models/hash_algorithm.rs | 28 +++++++++++ 7 files changed, 279 insertions(+), 66 deletions(-) create mode 100644 src/algorithms/argon2i.rs create mode 100644 src/algorithms/bcrypt.rs create mode 100644 src/algorithms/mod.rs create mode 100644 src/algorithms/scrypt.rs diff --git a/examples/hsh.rs b/examples/hsh.rs index 11ffa63..12c63c4 100644 --- a/examples/hsh.rs +++ b/examples/hsh.rs @@ -4,57 +4,83 @@ //! Using the Hash (HSH) library use std::str::FromStr; -use hsh::{models::{hash_algorithm::HashAlgorithm, hash::Hash}, new_hash}; +use hsh::{models::{hash::Hash, hash_algorithm::HashAlgorithm}, new_hash}; + +fn create_new_hash(password: &str, salt: &str, algorithm: &str) -> Result { + let hash = Hash::new(password, salt, algorithm)?; + println!("Debug: Salt used for new hash: {:?}", String::from_utf8_lossy(hash.salt())); + Ok(hash) +} -// Creating and verifying hashes fn create_and_verify_hash() { - // Creates a hash with a password and salt using `Hash::new`. - let mut hash = - Hash::new("password", "salt1234", "argon2i").unwrap(); + let hash_argon2i = create_new_hash("password", "salt1234", "argon2i").unwrap(); + let hash_bcrypt = create_new_hash("password", "salt1234", "bcrypt").unwrap(); + let hash_scrypt = create_new_hash("password", "salt1234", "scrypt").unwrap(); + + // Verify the newly created hashes + verify_password(&hash_argon2i, "password", "Argon2i"); + verify_password(&hash_bcrypt, "password", "BCrypt"); + verify_password(&hash_scrypt, "password", "Scrypt"); + + let mut new_hash_argon2i = hash_argon2i.clone(); + new_hash_argon2i.set_password("new_password", "salt1234", "argon2i").unwrap(); - // Sets a new password, salt, and algorithm for the hash using `Hash::set_password`. - hash.set_password("new_password", "new_salt1234", "argon2i") - .unwrap(); + let mut new_hash_bcrypt = hash_bcrypt.clone(); + new_hash_bcrypt.set_password("new_password", "salt1234", "bcrypt").unwrap(); - // Verifies a password against the stored hash using `Hash::verify`. - let is_valid = hash.verify("new_password"); + let mut new_hash_scrypt = hash_scrypt.clone(); + new_hash_scrypt.set_password("new_password", "salt1234", "scrypt").unwrap(); + + // Verify the updated hashes + verify_password(&new_hash_argon2i, "new_password", "Argon2i"); + verify_password(&new_hash_bcrypt, "new_password", "BCrypt"); + verify_password(&new_hash_scrypt, "new_password", "Scrypt"); +} + +fn verify_password(hash: &Hash, password: &str, algorithm: &str) { + let is_valid = hash.verify(password); match is_valid { Ok(valid) => { - println!("🦀 Password verification result: ✅ {:>5}", valid) - } + println!("🦀 Password verification result for {}: ✅ {:?}", algorithm, valid); + }, Err(e) => { - eprintln!("🦀 Error during password verification: ❌ {}", e) + eprintln!("🦀 Error during password verification for {}: ❌ {}", algorithm, e); } } } -// Parsing and displaying hashes fn parse_and_display_hash() { - // Parses a hash algorithm from a string using `HashAlgorithm::from_str`. - let parsed_hash_algorithm = - HashAlgorithm::from_str("argon2i").unwrap(); - println!( - "🦀 Parsed hash algorithm: ✅ {}", - parsed_hash_algorithm - ); - - // Creates a new hash using the `new_hash!` macro. - let hash = new_hash!("password", "salt12345", "argon2i"); - - // Converts the hash to a string manually. - let hash_string = match hash { - Ok(hash) => format!( - "🦀 Hash to a string: ✅ {}", - hash.to_string_representation() - ), - Err(err) => format!( - "🦀 Hash to a string: ❌ Error: {}", - err - ), + let parsed_argon2i = HashAlgorithm::from_str("argon2i").unwrap(); + let parsed_bcrypt = HashAlgorithm::from_str("bcrypt").unwrap(); + let parsed_scrypt = HashAlgorithm::from_str("scrypt").unwrap(); + + println!("🦀 Parsed Argon2i hash algorithm: {}", parsed_argon2i); + println!("🦀 Parsed Bcrypt hash algorithm: {}", parsed_bcrypt); + println!("🦀 Parsed Scrypt hash algorithm: {}", parsed_scrypt); + + let argon2i_hash = new_hash!("password", "salt12345", "argon2i"); + let bcrypt_hash = new_hash!("password", "salt12345", "bcrypt"); + let scrypt_hash = new_hash!("password", "salt12345", "scrypt"); + + let argon2i_hash_string = match argon2i_hash { + Ok(hash) => hash.to_string_representation(), + Err(e) => format!("Error: {}", e), + }; + let bcrypt_hash_string = match bcrypt_hash { + Ok(hash) => hash.to_string_representation(), + Err(e) => format!("Error: {}", e), }; - println!("{}", hash_string); + let scrypt_hash_string = match scrypt_hash { + Ok(hash) => hash.to_string_representation(), + Err(e) => format!("Error: {}", e), + }; + + println!("🦀 Argon2i Hash to a string: {}", argon2i_hash_string); + println!("🦀 Bcrypt Hash to a string: {}", bcrypt_hash_string); + println!("🦀 Scrypt Hash to a string: {}", scrypt_hash_string); } + fn main() { create_and_verify_hash(); parse_and_display_hash(); diff --git a/src/algorithms/argon2i.rs b/src/algorithms/argon2i.rs new file mode 100644 index 0000000..488addb --- /dev/null +++ b/src/algorithms/argon2i.rs @@ -0,0 +1,37 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::models::hash_algorithm::HashingAlgorithm; +use argon2rs::argon2i_simple; +use serde::{Serialize, Deserialize}; + +/// Implementation of the Argon2i hashing algorithm. +/// +/// `Argon2i` is a struct that represents the Argon2i hashing algorithm, +/// which is a memory-hard algorithm resistant to GPU-based attacks and side-channel attacks. +/// It is one of the multiple hashing algorithms that can be used for password hashing in this library. +/// +/// This struct implements the `HashingAlgorithm` trait, providing a concrete implementation +/// for hashing passwords using the Argon2i algorithm. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +pub struct Argon2i; + +impl HashingAlgorithm for Argon2i { + /// Hashes a given password using Argon2i algorithm. + /// + /// Given a plaintext `password` and a `salt`, this method returns a hashed representation + /// of the password using Argon2i algorithm. + /// + /// # Parameters + /// + /// - `password`: The plaintext password to be hashed. + /// - `salt`: A cryptographic salt to prevent rainbow table attacks. + /// + /// # Returns + /// + /// Returns a `Result` containing the hashed password as a vector of bytes. + /// If hashing fails for some reason, returns a `String` detailing the error. + fn hash_password(password: &str, salt: &str) -> Result, String> { + Ok(argon2i_simple(password, salt).into_iter().collect()) + } +} diff --git a/src/algorithms/bcrypt.rs b/src/algorithms/bcrypt.rs new file mode 100644 index 0000000..a28b5d4 --- /dev/null +++ b/src/algorithms/bcrypt.rs @@ -0,0 +1,55 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::models::hash_algorithm::HashingAlgorithm; +use bcrypt::{hash, DEFAULT_COST}; +use serde::{Serialize, Deserialize}; + +/// Implementation of the Bcrypt hashing algorithm. +/// +/// `Bcrypt` is a struct that represents the Bcrypt hashing algorithm, +/// which is based on the Blowfish cipher and is particularly effective against brute-force attacks. +/// +/// This struct implements the `HashingAlgorithm` trait, providing a concrete implementation +/// for hashing passwords using the Bcrypt algorithm. +/// +/// # Features +/// +/// - Computationally intensive, making brute-force attacks more difficult. +/// - Uses key stretching to make pre-computed attacks (like rainbow tables) less effective. +/// +/// # Examples +/// +/// ``` +/// use hsh::models::hash_algorithm::HashingAlgorithm; +/// use hsh::algorithms::bcrypt::Bcrypt; +/// +/// let password = "supersecret"; +/// let salt = "randomsalt"; +/// +/// let hashed_password = Bcrypt::hash_password(password, salt).unwrap(); +/// ``` +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +pub struct Bcrypt; + +impl HashingAlgorithm for Bcrypt { + /// Hashes a given password using the Bcrypt algorithm. + /// + /// Given a plaintext `password` and a `salt`, this method returns a hashed representation + /// of the password using the Bcrypt algorithm. + /// + /// # Parameters + /// + /// - `password`: The plaintext password to be hashed. + /// - `salt`: A cryptographic salt to prevent rainbow table attacks. + /// + /// # Returns + /// + /// Returns a `Result` containing the hashed password as a vector of bytes. + /// If hashing fails for some reason, returns a `String` detailing the error. + fn hash_password(password: &str, _salt: &str) -> Result, String> { + hash(password, DEFAULT_COST) + .map_err(|e| e.to_string()) + .map(|hash_parts| hash_parts.into_bytes()) + } +} diff --git a/src/algorithms/mod.rs b/src/algorithms/mod.rs new file mode 100644 index 0000000..425f832 --- /dev/null +++ b/src/algorithms/mod.rs @@ -0,0 +1,11 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +/// The `argon2i` module contains the Argon2i password hashing algorithm. +pub mod argon2i; + +/// The `bcrypt` module contains the Bcrypt password hashing algorithm. +pub mod bcrypt; + +/// The `scrypt` module contains the Scrypt password hashing algorithm. +pub mod scrypt; \ No newline at end of file diff --git a/src/algorithms/scrypt.rs b/src/algorithms/scrypt.rs new file mode 100644 index 0000000..ddf6ec1 --- /dev/null +++ b/src/algorithms/scrypt.rs @@ -0,0 +1,47 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +use crate::models::hash_algorithm::HashingAlgorithm; +use scrypt::scrypt; +use scrypt::Params; +use serde::{Serialize, Deserialize}; + +/// Implementation of the Scrypt hashing algorithm. +/// +/// `Scrypt` is a struct that represents the Scrypt hashing algorithm, +/// which is a memory-hard algorithm designed to be computationally intensive, +/// thereby making it difficult to perform large-scale custom hardware attacks. +/// +/// This struct implements the `HashingAlgorithm` trait, providing a concrete implementation +/// for hashing passwords using the Scrypt algorithm. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd, Serialize, Deserialize)] +pub struct Scrypt; + +impl HashingAlgorithm for Scrypt { + /// Hashes a given password using the Scrypt algorithm. + /// + /// Given a plaintext `password` and a `salt`, this method returns a hashed representation + /// of the password using the Scrypt algorithm. + /// + /// # Parameters + /// + /// - `password`: The plaintext password to be hashed. + /// - `salt`: A cryptographic salt to prevent rainbow table attacks. + /// + /// # Returns + /// + /// Returns a `Result` containing the hashed password as a vector of bytes. + /// If hashing fails for some reason, it returns a `String` detailing the error. + fn hash_password(password: &str, salt: &str) -> Result, String> { + let params = Params::new(14, 8, 1, 64).map_err(|e| e.to_string())?; + let mut output = [0u8; 64]; + scrypt( + password.as_bytes(), + salt.as_bytes(), + ¶ms, + &mut output, + ) + .map_err(|e| e.to_string()) + .map(|_| output.to_vec()) + } +} diff --git a/src/lib.rs b/src/lib.rs index 9912f41..b4df8b2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -161,6 +161,10 @@ #![crate_name = "hsh"] #![crate_type = "lib"] + +/// The `algorithms` module contains the password hashing algorithms. +pub mod algorithms; + /// The `macros` module contains functions for generating macros. pub mod macros; @@ -172,14 +176,15 @@ extern crate base64; extern crate bcrypt; extern crate scrypt; extern crate vrd; -use crate::models::{hash::*, hash_algorithm::*}; + +use algorithms::{argon2i::Argon2i, bcrypt::Bcrypt, scrypt::Scrypt}; use argon2rs::argon2i_simple; use base64::{engine::general_purpose, Engine as _}; +use models::{hash::*, hash_algorithm::*}; use scrypt::scrypt; use std::{fmt, str::FromStr}; use vrd::Random; - impl Hash { /// A function that returns the hash algorithm used by the hash map. pub fn algorithm(&self) -> HashAlgorithm { @@ -249,28 +254,9 @@ impl Hash { algo: &str, ) -> Result, String> { match algo { - "argon2i" => { - Ok(argon2i_simple(password, salt).into_iter().collect()) - } - "bcrypt" => { - let bcrypt_cost = 12; - bcrypt::hash(password, bcrypt_cost) - .map_err(|e| e.to_string()) - .map(|hash_parts| hash_parts.into_bytes()) - } - "scrypt" => { - let scrypt_params = scrypt::Params::new(14, 8, 1, 64) - .map_err(|e| e.to_string())?; - let mut output = [0u8; 64]; - scrypt( - password.as_bytes(), - salt.as_bytes(), - &scrypt_params, - &mut output, - ) - .map_err(|e| e.to_string()) - .map(|_| output.to_vec()) - } + "argon2i" => Argon2i::hash_password(password, salt), + "bcrypt" => Bcrypt::hash_password(password, salt), + "scrypt" => Scrypt::hash_password(password, salt), _ => Err(format!("Unsupported hash algorithm: {}", algo)), } } @@ -419,22 +405,40 @@ impl Hash { pub fn verify(&self, password: &str) -> Result { let salt = std::str::from_utf8(&self.salt) .map_err(|_| "Failed to convert salt to string")?; + match self.algorithm { HashAlgorithm::Argon2i => { - let hash = argon2i_simple(password, salt); - Ok(hash.to_vec() == self.hash) + // Hash the password once + let calculated_hash = argon2i_simple(password, salt).to_vec(); + + // Debugging information + println!("Algorithm: Argon2i"); + println!("Provided password for verification: {}", password); + println!("Salt used for verification: {}", salt); + println!("Calculated Hash: {:?}", calculated_hash); + println!("Stored Hash: {:?}", self.hash); + + // Perform the verification + Ok(calculated_hash == self.hash) } HashAlgorithm::Bcrypt => { + // Debugging information + println!("Algorithm: Bcrypt"); + println!("Provided password for verification: {}", password); + let hash_str = std::str::from_utf8(&self.hash) .map_err(|_| "Failed to convert hash to string")?; bcrypt::verify(password, hash_str) .map_err(|_| "Failed to verify Bcrypt password") } HashAlgorithm::Scrypt => { + // Debugging information + println!("Algorithm: Scrypt"); + println!("Provided password for verification: {}", password); + println!("Salt used for verification: {}", salt); + let scrypt_params = scrypt::Params::new(14, 8, 1, 64) - .map_err(|_| { - "Failed to create Scrypt params" - })?; + .map_err(|_| "Failed to create Scrypt params")?; let mut output = [0u8; 64]; match scrypt( password.as_bytes(), @@ -442,12 +446,17 @@ impl Hash { &scrypt_params, &mut output, ) { - Ok(_) => Ok(output.to_vec() == self.hash), + Ok(_) => { + println!("Calculated Hash: {:?}", output.to_vec()); + println!("Stored Hash: {:?}", self.hash); + Ok(output.to_vec() == self.hash) + } Err(_) => Err("Scrypt hashing failed"), } } } } + } impl fmt::Display for Hash { diff --git a/src/models/hash_algorithm.rs b/src/models/hash_algorithm.rs index ca3c37c..1bf7340 100644 --- a/src/models/hash_algorithm.rs +++ b/src/models/hash_algorithm.rs @@ -37,3 +37,31 @@ pub enum HashAlgorithm { /// - Makes parallelized attacks difficult and costly Scrypt, } + +/// Represents a generic hashing algorithm. +/// +/// The `HashingAlgorithm` trait defines a common interface for hashing algorithms. +/// Implementing this trait for different hashing algorithms ensures that they can be used +/// interchangeably for hashing passwords. +/// +/// The primary consumer of this trait is the `Hash` struct, which uses it to handle the hashing +/// logic in a decoupled and extendable manner. +pub trait HashingAlgorithm { + /// Hashes a given password using a specific salt. + /// + /// Given a plaintext `password` and a `salt`, this method returns a hashed representation + /// of the password. The hashing algorithm used is determined by the implementing type. + /// + /// # Parameters + /// + /// - `password`: The plaintext password to be hashed. + /// - `salt`: A cryptographic salt to prevent rainbow table attacks. + /// + /// # Returns + /// + /// Returns a `Result` containing the hashed password as a vector of bytes. + /// If hashing fails, returns a `String` detailing the error. + fn hash_password(password: &str, salt: &str) -> Result, String>; +} + + From 20c9dbc64e19022ec18b4393cdb83fef059a2cbb Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 16:06:16 +0100 Subject: [PATCH 09/22] feat(hsh): decoupling algorithms & improving docs --- examples/hsh.rs | 70 +++++++++++-- src/lib.rs | 252 ++++++++++++++++++++++++++------------------- src/models/hash.rs | 86 +++++++++++++++- 3 files changed, 286 insertions(+), 122 deletions(-) diff --git a/examples/hsh.rs b/examples/hsh.rs index 12c63c4..5790d50 100644 --- a/examples/hsh.rs +++ b/examples/hsh.rs @@ -6,22 +6,47 @@ use std::str::FromStr; use hsh::{models::{hash::Hash, hash_algorithm::HashAlgorithm}, new_hash}; -fn create_new_hash(password: &str, salt: &str, algorithm: &str) -> Result { - let hash = Hash::new(password, salt, algorithm)?; - println!("Debug: Salt used for new hash: {:?}", String::from_utf8_lossy(hash.salt())); - Ok(hash) -} - +/// This function demonstrates how to create and verify password hashes using Argon2i, Bcrypt, and Scrypt algorithms. +/// +/// # Example +/// +/// ```rust +/// use hsh::models::{hash::Hash, salt::Salt}; +/// +/// // Function to create and verify hash +/// fn create_and_verify_hash() { +/// // Create new hashes for Argon2i, Bcrypt, and Scrypt +/// let password = "password"; +/// let salt_argon2i: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; +/// let salt_scrypt: Salt = vec![10, 11, 12, 13, 14, 15, 16, 17, 18, 19]; +/// let cost_bcrypt = 16; +/// +/// let hash_argon2i = Hash::new_argon2i(password, salt_argon2i).unwrap(); +/// let hash_bcrypt = Hash::new_bcrypt(password, cost_bcrypt).unwrap(); +/// let hash_scrypt = Hash::new_scrypt(password, salt_scrypt).unwrap(); +/// +/// // Verify these hashes +/// verify_password(&hash_argon2i, "password", "Argon2i"); +/// verify_password(&hash_bcrypt, "password", "BCrypt"); +/// verify_password(&hash_scrypt, "password", "Scrypt"); +/// +/// // ... (the rest of the function) +/// } +/// ``` +/// +/// Note: This is a simplified example, and in a real-world application, you should handle errors and edge cases more carefully. fn create_and_verify_hash() { - let hash_argon2i = create_new_hash("password", "salt1234", "argon2i").unwrap(); - let hash_bcrypt = create_new_hash("password", "salt1234", "bcrypt").unwrap(); - let hash_scrypt = create_new_hash("password", "salt1234", "scrypt").unwrap(); + // Create new hashes for Argon2i, Bcrypt, and Scrypt + let hash_argon2i = Hash::new_argon2i("password", "salt1234".into()).unwrap(); + let hash_bcrypt = Hash::new_bcrypt("password", 16).unwrap(); + let hash_scrypt = Hash::new_scrypt("password", "salt1234".into()).unwrap(); - // Verify the newly created hashes + // Verify these hashes verify_password(&hash_argon2i, "password", "Argon2i"); verify_password(&hash_bcrypt, "password", "BCrypt"); verify_password(&hash_scrypt, "password", "Scrypt"); + // Update the hashes let mut new_hash_argon2i = hash_argon2i.clone(); new_hash_argon2i.set_password("new_password", "salt1234", "argon2i").unwrap(); @@ -37,19 +62,33 @@ fn create_and_verify_hash() { verify_password(&new_hash_scrypt, "new_password", "Scrypt"); } +// Function to verify the password fn verify_password(hash: &Hash, password: &str, algorithm: &str) { + // Print header + println!("\n===[ Verifying Password with {} Algorithm ]===\n", algorithm); + let is_valid = hash.verify(password); match is_valid { Ok(valid) => { + println!("Algorithm: {}", algorithm); + println!("Provided password for verification: {}", password); + println!("Salt used for verification: {}", String::from_utf8_lossy(hash.salt())); println!("🦀 Password verification result for {}: ✅ {:?}", algorithm, valid); }, Err(e) => { eprintln!("🦀 Error during password verification for {}: ❌ {}", algorithm, e); } } + + // Print footer + println!("\n==================================================\n"); } +// Function to parse and display hash algorithms and their string representations fn parse_and_display_hash() { + // Print header for parsing algorithms + println!("\n===[ Parsing Hash Algorithms ]===\n"); + let parsed_argon2i = HashAlgorithm::from_str("argon2i").unwrap(); let parsed_bcrypt = HashAlgorithm::from_str("bcrypt").unwrap(); let parsed_scrypt = HashAlgorithm::from_str("scrypt").unwrap(); @@ -58,6 +97,12 @@ fn parse_and_display_hash() { println!("🦀 Parsed Bcrypt hash algorithm: {}", parsed_bcrypt); println!("🦀 Parsed Scrypt hash algorithm: {}", parsed_scrypt); + // Print footer for parsing algorithms + println!("\n======================================\n"); + + // Print header for hash to string conversion + println!("\n===[ Hash to String Conversion ]===\n"); + let argon2i_hash = new_hash!("password", "salt12345", "argon2i"); let bcrypt_hash = new_hash!("password", "salt12345", "bcrypt"); let scrypt_hash = new_hash!("password", "salt12345", "scrypt"); @@ -78,9 +123,12 @@ fn parse_and_display_hash() { println!("🦀 Argon2i Hash to a string: {}", argon2i_hash_string); println!("🦀 Bcrypt Hash to a string: {}", bcrypt_hash_string); println!("🦀 Scrypt Hash to a string: {}", scrypt_hash_string); -} + // Print footer for hash to string conversion + println!("\n========================================\n"); +} +// Main function fn main() { create_and_verify_hash(); parse_and_display_hash(); diff --git a/src/lib.rs b/src/lib.rs index b4df8b2..d6cb8a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,150 +3,93 @@ //! # Quantum-Resistant Cryptographic Hash Library for Password Encryption and Verification //! -//! *Part of the [Mini Functions][0] family of libraries.* -//! -//!
+//! Part of the [Mini Functions][0] family of libraries. //! //! ![Hash (HSH) Banner][banner] //! -//! [![Crates.io][crate-shield]]() -//! [![GitHub][github-shield]]() -//! [![Lib.rs][lib-rs-shield]]() -//! [![License][license-shield]]() -//! [![Rust][rust-shield]]() -//! -//!
-//! -//! ## Overview 📖 -//! -//! The `Hash (HSH)` Rust library provides an interface for implementing -//! secure hash and digest algorithms, specifically designed for -//! password encryption and verification. -//! -//! The library provides a simple API that makes it easy to store and -//! verify hashed passwords. It enables robust security for passwords, -//! using the latest advancements in `Quantum-resistant cryptography`. -//! Quantum-resistant cryptography refers to cryptographic algorithms, -//! usually public-key algorithms, that are thought to be secure against -//! an attack by a quantum computer. As quantum computing continues to -//! advance, this feature of the library assures that the passwords -//! managed through this system remain secure even against cutting-edge -//! computational capabilities. +//! [![Crates.io][crate-shield]](https://crates.io/crates/hsh) +//! [![GitHub][github-shield]](https://github.com/sebastienrousseau/hsh) +//! [![Lib.rs][lib-rs-shield]](https://lib.rs/hsh) +//! [![License][license-shield]](http://opensource.org/licenses/MIT) +//! [![Rust][rust-shield]](https://www.rust-lang.org) //! -//! The library supports the following Password Hashing Schemes -//! (Password Based Key Derivation Functions): +//! ## Overview //! -//! - **Argon2i**: A cutting-edge and highly secure key derivation function designed to protect against both traditional brute-force attacks and rainbow table attacks. (Recommended) -//! - **Bcrypt**: A password hashing function designed to resist time-memory trade-off (TMTO) attacks, secure against brute-force attacks. -//! - **Scrypt**: A password hashing function designed to be secure against both brute-force attacks and rainbow table attacks. +//! The `Hash (HSH)` library in Rust offers a secure interface for hash and digest algorithms, focusing on password encryption and verification. Utilizing state-of-the-art quantum-resistant cryptography, it provides robust security against current and future computational threats. //! -//! The library is a valuable tool for developers who need to store and verify passwords in a secure manner. It is easy to use and can be integrated into a variety of applications. +//! ### Supported Password Hashing Schemes //! -//! ## Features ✨ +//! - **Argon2i**: Highly secure, resistant to both brute-force and rainbow table attacks. (Recommended) +//! - **Bcrypt**: Resistant to time-memory trade-off (TMTO) and brute-force attacks. +//! - **Scrypt**: Secure against both brute-force and rainbow table attacks. //! -//! - **Compliant with multiple Password Hashing Schemes (Password Based Key Derivation Functions) such as Argon2i, Bcrypt and Scrypt.** This makes the library more versatile and can be used in a variety of applications. -//! - **Quantum-resistant, making it secure against future attacks using quantum computers.** This is an important feature as quantum computers become more powerful. -//! - **Easy to use.** The library provides a simple API that makes it easy to store and verify hashed passwords. -//! - **Can be integrated into a variety of applications.** The library is written in Rust, which makes it easy to integrate into any Rust project and is fast, efficient, and secure. +//! ## Features //! +//! - **Versatility**: Supports multiple Password Hashing Schemes like Argon2i, Bcrypt, and Scrypt. +//! - **Future-Proof**: Quantum-resistant cryptography to secure against future technological advancements. +//! - **Ease of Use**: Simple API for storing and verifying hashed passwords. +//! - **Integrable**: Written in Rust, the library is fast, efficient, and easily integrable into other Rust projects. //! -//! ### Hash Struct +//! ## Core Components //! -//! The `Hash` struct is a data structure that stores the following information about a hashed password: +//! ### `Hash` Struct //! -//! - **algorithm**: An enum that stores the algorithm used for password hashing. The enum has three variants: Argon2i, Bcrypt, and Scrypt. -//! - **hash**: A vector of bytes that stores the hashed password. -//! - **salt**: A vector of bytes that stores the salt used for password hashing. +//! Contains: +//! - **algorithm**: Enum representing the hashing algorithm (Argon2i, Bcrypt, Scrypt). +//! - **hash**: Byte vector containing the hashed password. +//! - **salt**: Byte vector containing the salt used in hashing. //! -//! ### Hash Algorithms +//! ### `HashAlgorithm` Enum //! -//! The `HashAlgorithm` enum provides support for the following Password Hashing Schemes (Password Based Key Derivation Functions): +//! Provides variants for supported hashing algorithms: Argon2i, Bcrypt, and Scrypt. //! -//! - **Argon2i**: A cutting-edge and highly secure key derivation function designed to protect against both traditional brute-force attacks and rainbow table attacks. It is recommended for its strong security. -//! - **Bcrypt**: A password hashing function designed to resist time-memory trade-off (TMTO) attacks and provide security against brute-force attacks. -//! - **Scrypt**: A password hashing function designed to be secure against both brute-force attacks and rainbow table attacks. +//! ## Methods //! -//! ### Hash Methods +//! The `Hash` struct offers methods for password hashing and management, including but not limited to: //! -//! The `Hash` struct provides the following methods for working with hashed passwords: +//! - Creating new Hash objects. +//! - Generating and setting salts and hashes. +//! - Verifying passwords against stored hashes. //! -//! - `algorithm`: A function that returns the hash algorithm used by the hash map. -//! - `from_hash`: A function that creates a new hash object from a hash value and a hash algorithm. -//! - `from_string`: A function that creates a new hash object from a hash string in the format algorithm$salt$hash. -//! - `generate_hash`: A function that generates a hash value for a password using the specified hash algorithm. -//! - `generate_random_string`: A function that generates a random string of the specified length. -//! - `generate_salt`: A function that generates a random salt for a password using the specified hash algorithm. -//! - `hash`: A function that returns the hash value of a hash object. -//! - `hash_length`: A function that returns the length of the hash value of a hash object. -//! - `new`: A function that creates a new hash object from a password, salt, and hash algorithm. -//! - `parse`: A function that parses a JSON string into a hash object. -//! - `parse_algorithm`: A function that parses a hash string into a hash algorithm. -//! - `salt`: A function that returns the salt used to hash a password. -//! - `set_hash`: A function that sets the hash value of a hash object. -//! - `set_password`: A function that sets the password of a hash object. -//! - `set_salt`: A function that sets the salt of a hash object. -//! - `to_string_representation`: A function that converts a hash object to a string representation. -//! - `verify`: A function that verifies a password against a hash object. +//! ## Getting Started //! -//! ### Traits -//! -//! The Hash struct also implements the following traits: -//! -//! - `FromStr`: Allows the Hash struct to be converted from a string. -//! - `std::fmt::Display`: Allows the Hash struct to be printed as a string. -//! -//! ## Getting Started 🚀 -//! -//! To start using Hash (HSH), add it as a dependency in your Cargo.toml file and import it in your Rust file. You can then create a new Hash instance and call the appropriate methods for your needs. +//! Add `Hash (HSH)` as a dependency in your `Cargo.toml` and import it in your main Rust file. //! //! ### Example //! -//! This example demonstrates how to create a Hash object, retrieve the hashed password bytes, and test the hash() method for verifying the correctness of the hash. +//! Here's a simple example demonstrating basic usage: //! //! ```rust -//! // Import the Hash struct -//! extern crate hsh; -//! use hsh::models::hash::Hash; +//! use hsh::models::hash::Hash; // Import the Hash struct //! -//! // Main function //! fn main() { +//! let password = "password123"; +//! let salt = "somesalt"; +//! let algo = "argon2i"; //! -//! // Define the password, salt, and algorithm -//! let password = "password123"; // Must be at least 8 characters. -//! let salt = "somesalt"; // Must be at least 8 characters. -//! let algo = "argon2i"; // Must be either "argon2i", "bcrypt", or "scrypt". -//! -//! // Create a new Hash object -//! let original_hash = Hash::new(password, salt, algo).unwrap(); // Unwrap the Result -//! -//! // Get the hashed password bytes from the Hash object -//! let hashed_password = original_hash.hash.clone(); // Clone the hash vector -//! -//! // Test the `hash` method for verifying the correctness of the hash -//! assert_eq!(original_hash.hash(), &hashed_password); // Verify the hash +//! let original_hash = Hash::new(password, salt, algo).expect("Failed to create hash"); +//! let hashed_password = original_hash.hash.clone(); //! +//! assert_eq!(original_hash.hash(), &hashed_password); //! } //! ``` //! -//! ## License 📝 -//! -//! The project is licensed under the terms of both the MIT license and the -//! Apache License (Version 2.0). +//! ## License //! -//! - [Apache License, Version 2.0][1] -//! - [MIT license][2] +//! Licensed under the MIT and Apache License (Version 2.0). //! -//! [banner]: https://kura.pro/hsh/images/banners/banner-hsh.svg "The Hash (HSH) Banner" -//! [crate-shield]: https://img.shields.io/crates/v/hsh.svg?style=for-the-badge&color=success&labelColor=27A006 "Crates.io" -//! [github-shield]: https://img.shields.io/badge/github-555555?style=for-the-badge&labelColor=000000&logo=github "GitHub" -//! [lib-rs-shield]: https://img.shields.io/badge/lib.rs-v0.0.5-success.svg?style=for-the-badge&color=8A48FF&labelColor=6F36E4 "Lib.rs" -//! [license-shield]: https://img.shields.io/crates/l/hsh.svg?style=for-the-badge&color=007EC6&labelColor=03589B "License" -//! [rust-shield]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust "Rust" +//! [banner]: https://kura.pro/hsh/images/banners/banner-hsh.svg +//! [crate-shield]: https://img.shields.io/crates/v/hsh.svg?style=for-the-badge&color=success&labelColor=27A006 +//! [github-shield]: https://img.shields.io/badge/github-555555?style=for-the-badge&labelColor=000000&logo=github +//! [lib-rs-shield]: https://img.shields.io/badge/lib.rs-v0.0.5-success.svg?style=for-the-badge&color=8A48FF&labelColor=6F36E4 +//! [license-shield]: https://img.shields.io/crates/l/hsh.svg?style=for-the-badge&color=007EC6&labelColor=03589B +//! [rust-shield]: https://img.shields.io/badge/rust-f04041?style=for-the-badge&labelColor=c0282d&logo=rust //! -//! [0]: https://minifunctions.com/ "MiniFunctions" +//! [0]: https://minifunctions.com/ //! [1]: http://www.apache.org/licenses/LICENSE-2.0 //! [2]: http://opensource.org/licenses/MIT + #![cfg_attr(feature = "bench", feature(test))] #![deny(dead_code)] #![deny(missing_debug_implementations)] @@ -186,6 +129,101 @@ use std::{fmt, str::FromStr}; use vrd::Random; impl Hash { + /// Creates a new `Hash` instance using Argon2i algorithm for password hashing. + /// + /// # Example + /// + /// ``` + /// use hsh::models::hash::{Hash, Salt}; + /// + /// let password = "my_password"; + /// let salt: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + /// + /// let result = Hash::new_argon2i(password, salt); + /// match result { + /// Ok(hash) => println!("Successfully created Argon2i hash"), + /// Err(e) => println!("An error occurred: {}", e), + /// } + /// ``` + pub fn new_argon2i(password: &str, salt: Salt) -> Result { + // Convert the Vec salt to a &str + let salt_str = std::str::from_utf8(&salt) + .map_err(|_| "Failed to convert salt to string")?; + + // Perform Argon2i hashing + let calculated_hash = argon2i_simple(password, salt_str).to_vec(); + + HashBuilder::new() + .hash(calculated_hash) + .salt(salt) + .algorithm(HashAlgorithm::Argon2i) + .build() + } + + /// Creates a new `Hash` instance using Bcrypt algorithm for password hashing. + /// + /// # Example + /// + /// ``` + /// use hsh::models::hash::Hash; + /// + /// let password = "my_password"; + /// let cost: u32 = 16; + /// + /// let result = Hash::new_bcrypt(password, cost); + /// match result { + /// Ok(hash) => println!("Successfully created Bcrypt hash"), + /// Err(e) => println!("An error occurred: {}", e), + /// } + /// ``` + pub fn new_bcrypt(password: &str, cost: u32) -> Result { + // Perform Bcrypt hashing + let hashed_password = bcrypt::hash(password, cost) + .map_err(|e| format!("Failed to hash password with Bcrypt: {}", e))?; + + // In Bcrypt, the salt is embedded in the hashed password. + // So, you can just use an empty salt when building the Hash object. + let empty_salt = Vec::new(); + + HashBuilder::new() + .hash(hashed_password.as_bytes().to_vec()) + .salt(empty_salt) + .algorithm(HashAlgorithm::Bcrypt) + .build() + } + + /// Creates a new `Hash` instance using Scrypt algorithm for password hashing. + /// + /// # Example + /// + /// ``` + /// use hsh::models::hash::{Hash, Salt}; + /// + /// let password = "my_password"; + /// let salt: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + /// + /// let result = Hash::new_scrypt(password, salt); + /// match result { + /// Ok(hash) => println!("Successfully created Scrypt hash"), + /// Err(e) => println!("An error occurred: {}", e), + /// } + /// ``` + pub fn new_scrypt(password: &str, salt: Salt) -> Result { + // Convert the Vec salt to a &str for hashing + let salt_str = std::str::from_utf8(&salt) + .map_err(|_| "Failed to convert salt to string")?; + + // Perform Scrypt hashing using a wrapper function that sets the parameters + let calculated_hash = algorithms::scrypt::Scrypt::hash_password(password, salt_str)?; + + // Use the builder pattern to construct the Hash instance + HashBuilder::new() + .hash(calculated_hash) + .salt(salt) + .algorithm(HashAlgorithm::Scrypt) + .build() + } + /// A function that returns the hash algorithm used by the hash map. pub fn algorithm(&self) -> HashAlgorithm { self.algorithm diff --git a/src/models/hash.rs b/src/models/hash.rs index d7c4ef9..75a18c9 100644 --- a/src/models/hash.rs +++ b/src/models/hash.rs @@ -7,7 +7,8 @@ use super::hash_algorithm::HashAlgorithm; /// A type alias for a salt. pub type Salt = Vec; -/// A struct for storing and verifying hashed passwords based on the argon2rs crate +/// A struct for storing and verifying hashed passwords. +/// It uses `#[non_exhaustive]` and derive macros for common functionalities. #[non_exhaustive] #[derive( Clone, @@ -23,8 +24,85 @@ pub type Salt = Vec; pub struct Hash { /// The password hash. pub hash: Vec, - /// The salt used for hashing + /// The salt used for hashing. pub salt: Salt, - /// The hash algorithm used + /// The hash algorithm used. pub algorithm: HashAlgorithm, -} \ No newline at end of file +} + +/// A builder struct for the `Hash` struct. +/// It contains optional fields that correspond to the fields in `Hash`. +/// The `#[derive(Default)]` allows us to initialize all fields to `None`. +#[derive( + Clone, + Debug, + Eq, + Hash, + Ord, + PartialEq, + PartialOrd, + Serialize, + Deserialize, +)] +pub struct HashBuilder { + /// The password hash. + hash: Option>, + /// The salt used for hashing. + salt: Option, + /// The hash algorithm used. + algorithm: Option, +} + +impl HashBuilder { + /// Creates a new `HashBuilder` with all fields set to `None`. + pub fn new() -> Self { + HashBuilder { + hash: None, + salt: None, + algorithm: None, + } + } + + /// Sets the `hash` field in the builder. + /// The `self` parameter is consumed and returned to allow for method chaining. + pub fn hash(mut self, hash: Vec) -> Self { + self.hash = Some(hash); + self + } + + /// Sets the `salt` field in the builder. + /// The `self` parameter is consumed and returned to allow for method chaining. + pub fn salt(mut self, salt: Salt) -> Self { + self.salt = Some(salt); + self + } + + /// Sets the `algorithm` field in the builder. + /// The `self` parameter is consumed and returned to allow for method chaining. + pub fn algorithm(mut self, algorithm: HashAlgorithm) -> Self { + self.algorithm = Some(algorithm); + self + } + + /// Consumes the builder and returns a `Hash` if all fields are set. + /// Otherwise, it returns an error. + pub fn build(self) -> Result { + if let (Some(hash), Some(salt), Some(algorithm)) = (self.hash, self.salt, self.algorithm) { + Ok(Hash { + hash, + salt, + algorithm, + }) + } else { + Err("Missing fields".to_string()) + } + } +} + +/// Creates a new `HashBuilder` with all fields set to `None`. +impl Default for HashBuilder { + fn default() -> Self { + Self::new() + } +} + From 058f11284dbe01d9d3dba4635309f95bfb34f892 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 20:26:15 +0100 Subject: [PATCH 10/22] doc(hsh): updated library documentation --- src/lib.rs | 447 +++-------------------------------------------------- 1 file changed, 22 insertions(+), 425 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index d6cb8a6..e35e90b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,21 +1,23 @@ // Copyright © 2023 Hash (HSH) library. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT -//! # Quantum-Resistant Cryptographic Hash Library for Password Encryption and Verification +//! # HSH: Quantum-Resistant Cryptographic Hash Library for Robust Password Encryption & Verification //! -//! Part of the [Mini Functions][0] family of libraries. +//! A highly secure cryptographic hash library with a focus on password encryption and verification. Designed with quantum-resistant cryptography, this library provides a robust line of defence against current and emerging computational threats. //! //! ![Hash (HSH) Banner][banner] //! -//! [![Crates.io][crate-shield]](https://crates.io/crates/hsh) -//! [![GitHub][github-shield]](https://github.com/sebastienrousseau/hsh) -//! [![Lib.rs][lib-rs-shield]](https://lib.rs/hsh) -//! [![License][license-shield]](http://opensource.org/licenses/MIT) -//! [![Rust][rust-shield]](https://www.rust-lang.org) +//! Part of the [Mini Functions][0] family of libraries. +//! +//! [![Available on Crates.io][crate-shield]](https://crates.io/crates/hsh) +//! [![GitHub Repository][github-shield]](https://github.com/sebastienrousseau/hsh) +//! [![Available on Lib.rs][lib-rs-shield]](https://lib.rs/hsh) +//! [![MIT License][license-shield]](http://opensource.org/licenses/MIT) +//! [![Built with Rust][rust-shield]](https://www.rust-lang.org) //! //! ## Overview //! -//! The `Hash (HSH)` library in Rust offers a secure interface for hash and digest algorithms, focusing on password encryption and verification. Utilizing state-of-the-art quantum-resistant cryptography, it provides robust security against current and future computational threats. +//! The Hash (HSH) library is a secure and easy-to-use library for password encryption and verification in Rust. It supports multiple password hashing schemes, including Argon2i, Bcrypt, and Scrypt. It is specifically designed for password encryption and verification, and uses state-of-the-art quantum-resistant cryptography to ensure maximum security against both current and future computational threats. //! //! ### Supported Password Hashing Schemes //! @@ -25,16 +27,17 @@ //! //! ## Features //! -//! - **Versatility**: Supports multiple Password Hashing Schemes like Argon2i, Bcrypt, and Scrypt. -//! - **Future-Proof**: Quantum-resistant cryptography to secure against future technological advancements. //! - **Ease of Use**: Simple API for storing and verifying hashed passwords. +//! - **Future-Proof**: Quantum-resistant cryptography to secure against future technological advancements. //! - **Integrable**: Written in Rust, the library is fast, efficient, and easily integrable into other Rust projects. +//! - **Versatility**: Supports multiple Password Hashing Schemes like Argon2i, Bcrypt, and Scrypt. //! //! ## Core Components //! //! ### `Hash` Struct //! //! Contains: +//! //! - **algorithm**: Enum representing the hashing algorithm (Argon2i, Bcrypt, Scrypt). //! - **hash**: Byte vector containing the hashed password. //! - **salt**: Byte vector containing the salt used in hashing. @@ -89,13 +92,12 @@ //! [1]: http://www.apache.org/licenses/LICENSE-2.0 //! [2]: http://opensource.org/licenses/MIT - #![cfg_attr(feature = "bench", feature(test))] -#![deny(dead_code)] #![deny(missing_debug_implementations)] #![deny(missing_docs)] -#![forbid(unsafe_code)] -#![warn(unreachable_pub)] +#![deny(unsafe_code)] +#![warn(clippy::all)] +#![warn(rust_2018_idioms)] #![doc( html_favicon_url = "https://kura.pro/hsh/images/favicon.ico", html_logo_url = "https://kura.pro/hsh/images/logos/hsh.svg", @@ -104,432 +106,27 @@ #![crate_name = "hsh"] #![crate_type = "lib"] - /// The `algorithms` module contains the password hashing algorithms. pub mod algorithms; /// The `macros` module contains functions for generating macros. pub mod macros; +/// The `modules` module contains the library modules. +pub mod modules; + /// The `models` module contains the data models for the library. pub mod models; -extern crate argon2rs; -extern crate base64; -extern crate bcrypt; -extern crate scrypt; -extern crate vrd; - -use algorithms::{argon2i::Argon2i, bcrypt::Bcrypt, scrypt::Scrypt}; -use argon2rs::argon2i_simple; -use base64::{engine::general_purpose, Engine as _}; -use models::{hash::*, hash_algorithm::*}; -use scrypt::scrypt; -use std::{fmt, str::FromStr}; -use vrd::Random; - -impl Hash { - /// Creates a new `Hash` instance using Argon2i algorithm for password hashing. - /// - /// # Example - /// - /// ``` - /// use hsh::models::hash::{Hash, Salt}; - /// - /// let password = "my_password"; - /// let salt: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - /// - /// let result = Hash::new_argon2i(password, salt); - /// match result { - /// Ok(hash) => println!("Successfully created Argon2i hash"), - /// Err(e) => println!("An error occurred: {}", e), - /// } - /// ``` - pub fn new_argon2i(password: &str, salt: Salt) -> Result { - // Convert the Vec salt to a &str - let salt_str = std::str::from_utf8(&salt) - .map_err(|_| "Failed to convert salt to string")?; - - // Perform Argon2i hashing - let calculated_hash = argon2i_simple(password, salt_str).to_vec(); - - HashBuilder::new() - .hash(calculated_hash) - .salt(salt) - .algorithm(HashAlgorithm::Argon2i) - .build() - } - - /// Creates a new `Hash` instance using Bcrypt algorithm for password hashing. - /// - /// # Example - /// - /// ``` - /// use hsh::models::hash::Hash; - /// - /// let password = "my_password"; - /// let cost: u32 = 16; - /// - /// let result = Hash::new_bcrypt(password, cost); - /// match result { - /// Ok(hash) => println!("Successfully created Bcrypt hash"), - /// Err(e) => println!("An error occurred: {}", e), - /// } - /// ``` - pub fn new_bcrypt(password: &str, cost: u32) -> Result { - // Perform Bcrypt hashing - let hashed_password = bcrypt::hash(password, cost) - .map_err(|e| format!("Failed to hash password with Bcrypt: {}", e))?; - - // In Bcrypt, the salt is embedded in the hashed password. - // So, you can just use an empty salt when building the Hash object. - let empty_salt = Vec::new(); - - HashBuilder::new() - .hash(hashed_password.as_bytes().to_vec()) - .salt(empty_salt) - .algorithm(HashAlgorithm::Bcrypt) - .build() - } - - /// Creates a new `Hash` instance using Scrypt algorithm for password hashing. - /// - /// # Example - /// - /// ``` - /// use hsh::models::hash::{Hash, Salt}; - /// - /// let password = "my_password"; - /// let salt: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; - /// - /// let result = Hash::new_scrypt(password, salt); - /// match result { - /// Ok(hash) => println!("Successfully created Scrypt hash"), - /// Err(e) => println!("An error occurred: {}", e), - /// } - /// ``` - pub fn new_scrypt(password: &str, salt: Salt) -> Result { - // Convert the Vec salt to a &str for hashing - let salt_str = std::str::from_utf8(&salt) - .map_err(|_| "Failed to convert salt to string")?; - - // Perform Scrypt hashing using a wrapper function that sets the parameters - let calculated_hash = algorithms::scrypt::Scrypt::hash_password(password, salt_str)?; - - // Use the builder pattern to construct the Hash instance - HashBuilder::new() - .hash(calculated_hash) - .salt(salt) - .algorithm(HashAlgorithm::Scrypt) - .build() - } - - /// A function that returns the hash algorithm used by the hash map. - pub fn algorithm(&self) -> HashAlgorithm { - self.algorithm - } - - /// A function that creates a new hash object from a hash value and a hash algorithm. - pub fn from_hash(hash: &[u8], algo: &str) -> Result { - let algorithm = match algo { - "argon2i" => Ok(HashAlgorithm::Argon2i), - "bcrypt" => Ok(HashAlgorithm::Bcrypt), - "scrypt" => Ok(HashAlgorithm::Scrypt), - _ => Err(format!("Unsupported hash algorithm: {}", algo)), - }?; - - Ok(Hash { - salt: Vec::new(), - hash: hash.to_vec(), - algorithm, - }) - } - - /// A function that creates a new hash object from a hash string in the format algorithm$salt$hash. - pub fn from_string(hash_str: &str) -> Result { - // Split the hash string into six parts, using the `$` character as the delimiter. - let parts: Vec<&str> = hash_str.split('$').collect(); - - // If the hash string does not contain six parts, return an error. - if parts.len() != 6 { - return Err(String::from("Invalid hash string")); - } - - // Parse the algorithm from the first part of the hash string. - let algorithm = Self::parse_algorithm(hash_str)?; - - // Parse the salt from the second, third, fourth, and fifth parts of the hash string. - let salt = format!( - "${}${}${}${}", - parts[1], parts[2], parts[3], parts[4] - ); - - // Decode the hash bytes from the sixth part of the hash string. - let hash_bytes = - general_purpose::STANDARD.decode(parts[5]).map_err( - |_| format!("Failed to decode base64: {}", parts[5]), - )?; - - // Create the `Hash` object and return it. - Ok(Hash { - salt: salt.into_bytes(), - hash: hash_bytes, - algorithm, - }) - } - - /// A function that generates a hash value for a password using the specified hash algorithm. - /// The function takes three arguments: - /// - /// - password: The password to be hashed. - /// - salt: A random string used to make the hash value unique. - /// - algo: The name of the hash algorithm to use. - /// - /// The function returns a `Result` object containing the hash value if successful, or an error message if unsuccessful. - pub fn generate_hash( - password: &str, - salt: &str, - algo: &str, - ) -> Result, String> { - match algo { - "argon2i" => Argon2i::hash_password(password, salt), - "bcrypt" => Bcrypt::hash_password(password, salt), - "scrypt" => Scrypt::hash_password(password, salt), - _ => Err(format!("Unsupported hash algorithm: {}", algo)), - } - } - - /// A function that generates a random string of the specified length. - pub fn generate_random_string(len: usize) -> String { - let mut rng = Random::default(); - let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; - (0..len) - .map(|_| { - chars - .chars() - .nth(rng.random_range(0, chars.len() as u32) - as usize) - .unwrap() - }) - .collect() - } - - /// A function that generates a random salt for a password using the specified hash algorithm. - pub fn generate_salt(algo: &str) -> Result { - let mut rng = Random::default(); - match algo { - "argon2i" => Ok(Self::generate_random_string(16)), - "bcrypt" => { - let salt: Vec = rng.bytes(16); - let salt_array: [u8; 16] = - salt.try_into().map_err(|_| { - "Error: failed to convert salt to an array" - })?; - Ok(general_purpose::STANDARD.encode(&salt_array[..])) - } - "scrypt" => { - let salt: Vec = rng.bytes(32); - let salt_array: [u8; 32] = - salt.try_into().map_err(|_| { - "Error: failed to convert salt to an array" - })?; - Ok(general_purpose::STANDARD.encode(&salt_array[..])) - } - _ => Err(format!("Unsupported hash algorithm: {}", algo)), - } - } - - /// A function that returns the hash value of a hash object. - pub fn hash(&self) -> &[u8] { - &self.hash - } - - /// A function that returns the length of the hash value of a hash object. - pub fn hash_length(&self) -> usize { - self.hash.len() - } - - /// A function that creates a new hash object from a password, salt, and hash algorithm. - pub fn new( - password: &str, - salt: &str, - algo: &str, - ) -> Result { - // Enforce a minimum password length of 8 characters. - if password.len() < 8 { - return Err(String::from("Password is too short. It must be at least 8 characters.")); - } - let hash = Self::generate_hash(password, salt, algo)?; - - let algorithm = match algo { - "argon2i" => Ok(HashAlgorithm::Argon2i), - "bcrypt" => Ok(HashAlgorithm::Bcrypt), - "scrypt" => Ok(HashAlgorithm::Scrypt), - _ => Err(format!("Unsupported hash algorithm: {}", algo)), - }?; - - Ok(Self { - hash, - salt: salt.as_bytes().to_vec(), - algorithm, - }) - } - - /// A function that parses a JSON string into a hash object. - pub fn parse( - input: &str, - ) -> Result> { - let hash: Hash = serde_json::from_str(input)?; - Ok(hash) - } - - /// A function that parses a hash string into a hash algorithm. - pub fn parse_algorithm( - hash_str: &str, - ) -> Result { - let parts: Vec<&str> = hash_str.split('$').collect(); - if parts.len() < 2 { - return Err(String::from("Invalid hash string")); - } - match parts[1] { - "argon2i" => Ok(HashAlgorithm::Argon2i), - "bcrypt" => Ok(HashAlgorithm::Bcrypt), - "scrypt" => Ok(HashAlgorithm::Scrypt), - _ => { - Err(format!("Unsupported hash algorithm: {}", parts[1])) - } - } - } - - /// A function that returns the salt used to hash a password. - pub fn salt(&self) -> &[u8] { - &self.salt - } - - /// A function that sets the hash value of a hash object. - pub fn set_hash(&mut self, hash: &[u8]) { - self.hash = hash.to_vec(); - } - - /// A function that sets the password of a hash object. - pub fn set_password( - &mut self, - password: &str, - salt: &str, - algo: &str, - ) -> Result<(), String> { - self.hash = Self::generate_hash(password, salt, algo)?; - Ok(()) - } - - /// A function that sets the salt of a hash object. - pub fn set_salt(&mut self, salt: &[u8]) { - self.salt = salt.to_vec(); - } - - /// A function that converts a hash object to a string representation. - pub fn to_string_representation(&self) -> String { - let hash_str = self - .hash - .iter() - .map(|b| format!("{:02x}", b)) - .collect::>() - .join(""); - - format!("{}:{}", String::from_utf8_lossy(&self.salt), hash_str) - } - - /// A function that verifies a password against a hash object. - pub fn verify(&self, password: &str) -> Result { - let salt = std::str::from_utf8(&self.salt) - .map_err(|_| "Failed to convert salt to string")?; - - match self.algorithm { - HashAlgorithm::Argon2i => { - // Hash the password once - let calculated_hash = argon2i_simple(password, salt).to_vec(); - - // Debugging information - println!("Algorithm: Argon2i"); - println!("Provided password for verification: {}", password); - println!("Salt used for verification: {}", salt); - println!("Calculated Hash: {:?}", calculated_hash); - println!("Stored Hash: {:?}", self.hash); - - // Perform the verification - Ok(calculated_hash == self.hash) - } - HashAlgorithm::Bcrypt => { - // Debugging information - println!("Algorithm: Bcrypt"); - println!("Provided password for verification: {}", password); - - let hash_str = std::str::from_utf8(&self.hash) - .map_err(|_| "Failed to convert hash to string")?; - bcrypt::verify(password, hash_str) - .map_err(|_| "Failed to verify Bcrypt password") - } - HashAlgorithm::Scrypt => { - // Debugging information - println!("Algorithm: Scrypt"); - println!("Provided password for verification: {}", password); - println!("Salt used for verification: {}", salt); - - let scrypt_params = scrypt::Params::new(14, 8, 1, 64) - .map_err(|_| "Failed to create Scrypt params")?; - let mut output = [0u8; 64]; - match scrypt( - password.as_bytes(), - salt.as_bytes(), - &scrypt_params, - &mut output, - ) { - Ok(_) => { - println!("Calculated Hash: {:?}", output.to_vec()); - println!("Stored Hash: {:?}", self.hash); - Ok(output.to_vec() == self.hash) - } - Err(_) => Err("Scrypt hashing failed"), - } - } - } - } - -} - -impl fmt::Display for Hash { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Hash {{ hash: {:?} }}", self.hash) - } -} - -impl fmt::Display for HashAlgorithm { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{:?}", self) - } -} - -impl FromStr for HashAlgorithm { - type Err = String; - - fn from_str(s: &str) -> Result { - let algorithm = match s { - "argon2i" => HashAlgorithm::Argon2i, - "bcrypt" => HashAlgorithm::Bcrypt, - "scrypt" => HashAlgorithm::Scrypt, - _ => return Err(String::from("Invalid hash algorithm")), - }; - Ok(algorithm) - } -} - /// This is the main entry point for the `Hash (HSH)` library. pub fn run() -> Result<(), Box> { + // Example of conditional logic for test mode, if necessary. if std::env::var("HSH_TEST_MODE").unwrap_or_default() == "1" { return Err("Simulated error".into()); } + let name = "hsh"; - println!("Welcome to `{}` 👋!", { name }.to_uppercase()); + println!("Welcome to `{}` 👋!", name.to_uppercase()); println!( "Quantum-Resistant Cryptographic Hash Library for Password Encryption and Verification." ); From 75bcdc105a354ec8e04a718a93db7fc957591251 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 20:26:57 +0100 Subject: [PATCH 11/22] feat(hsh): decoupling of the implementation of the function --- src/models/hash.rs | 408 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 408 insertions(+) diff --git a/src/models/hash.rs b/src/models/hash.rs index 75a18c9..dd0e177 100644 --- a/src/models/hash.rs +++ b/src/models/hash.rs @@ -3,6 +3,19 @@ use serde::{Deserialize, Serialize}; use super::hash_algorithm::HashAlgorithm; +use crate::algorithms; +use crate::models::hash_algorithm::HashingAlgorithm; +use algorithms::{argon2i::Argon2i, bcrypt::Bcrypt, scrypt::Scrypt}; +use std::convert::TryInto; + +// use algorithms::{argon2i::Argon2i, bcrypt::Bcrypt, scrypt::Scrypt}; +use argon2rs::argon2i_simple; +use base64::{engine::general_purpose, Engine as _}; +// use models::{hash::*, hash_algorithm::*}; +use scrypt::scrypt; +use std::{fmt, str::FromStr}; +use vrd::Random; + /// A type alias for a salt. pub type Salt = Vec; @@ -30,6 +43,401 @@ pub struct Hash { pub algorithm: HashAlgorithm, } +impl Hash { + /// Creates a new `Hash` instance using Argon2i algorithm for password hashing. + /// + /// # Example + /// + /// ``` + /// use hsh::models::hash::{Hash, Salt}; + /// + /// let password = "my_password"; + /// let salt: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + /// + /// let result = Hash::new_argon2i(password, salt); + /// match result { + /// Ok(hash) => println!("Successfully created Argon2i hash"), + /// Err(e) => println!("An error occurred: {}", e), + /// } + /// ``` + pub fn new_argon2i(password: &str, salt: Salt) -> Result { + // Convert the Vec salt to a &str + let salt_str = std::str::from_utf8(&salt) + .map_err(|_| "Failed to convert salt to string")?; + + // Perform Argon2i hashing + let calculated_hash = argon2i_simple(password, salt_str).to_vec(); + + HashBuilder::new() + .hash(calculated_hash) + .salt(salt) + .algorithm(HashAlgorithm::Argon2i) + .build() + } + + /// Creates a new `Hash` instance using Bcrypt algorithm for password hashing. + /// + /// # Example + /// + /// ``` + /// use hsh::models::hash::Hash; + /// + /// let password = "my_password"; + /// let cost: u32 = 16; + /// + /// let result = Hash::new_bcrypt(password, cost); + /// match result { + /// Ok(hash) => println!("Successfully created Bcrypt hash"), + /// Err(e) => println!("An error occurred: {}", e), + /// } + /// ``` + pub fn new_bcrypt(password: &str, cost: u32) -> Result { + // Perform Bcrypt hashing + let hashed_password = bcrypt::hash(password, cost) + .map_err(|e| format!("Failed to hash password with Bcrypt: {}", e))?; + + // In Bcrypt, the salt is embedded in the hashed password. + // So, you can just use an empty salt when building the Hash object. + let empty_salt = Vec::new(); + + HashBuilder::new() + .hash(hashed_password.as_bytes().to_vec()) + .salt(empty_salt) + .algorithm(HashAlgorithm::Bcrypt) + .build() + } + + /// Creates a new `Hash` instance using Scrypt algorithm for password hashing. + /// + /// # Example + /// + /// ``` + /// use hsh::models::hash::{Hash, Salt}; + /// + /// let password = "my_password"; + /// let salt: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + /// + /// let result = Hash::new_scrypt(password, salt); + /// match result { + /// Ok(hash) => println!("Successfully created Scrypt hash"), + /// Err(e) => println!("An error occurred: {}", e), + /// } + /// ``` + pub fn new_scrypt(password: &str, salt: Salt) -> Result { + // Convert the Vec salt to a &str for hashing + let salt_str = std::str::from_utf8(&salt) + .map_err(|_| "Failed to convert salt to string")?; + + // Perform Scrypt hashing using a wrapper function that sets the parameters + let calculated_hash = algorithms::scrypt::Scrypt::hash_password(password, salt_str)?; + + // Use the builder pattern to construct the Hash instance + HashBuilder::new() + .hash(calculated_hash) + .salt(salt) + .algorithm(HashAlgorithm::Scrypt) + .build() + } + + /// A function that returns the hash algorithm used by the hash map. + pub fn algorithm(&self) -> HashAlgorithm { + self.algorithm + } + + /// A function that creates a new hash object from a hash value and a hash algorithm. + pub fn from_hash(hash: &[u8], algo: &str) -> Result { + let algorithm = match algo { + "argon2i" => Ok(HashAlgorithm::Argon2i), + "bcrypt" => Ok(HashAlgorithm::Bcrypt), + "scrypt" => Ok(HashAlgorithm::Scrypt), + _ => Err(format!("Unsupported hash algorithm: {}", algo)), + }?; + + Ok(Hash { + salt: Vec::new(), + hash: hash.to_vec(), + algorithm, + }) + } + + /// A function that creates a new hash object from a hash string in the format algorithm$salt$hash. + pub fn from_string(hash_str: &str) -> Result { + // Split the hash string into six parts, using the `$` character as the delimiter. + let parts: Vec<&str> = hash_str.split('$').collect(); + + // If the hash string does not contain six parts, return an error. + if parts.len() != 6 { + return Err(String::from("Invalid hash string")); + } + + // Parse the algorithm from the first part of the hash string. + let algorithm = Self::parse_algorithm(hash_str)?; + + // Parse the salt from the second, third, fourth, and fifth parts of the hash string. + let salt = format!( + "${}${}${}${}", + parts[1], parts[2], parts[3], parts[4] + ); + + // Decode the hash bytes from the sixth part of the hash string. + let hash_bytes = + general_purpose::STANDARD.decode(parts[5]).map_err( + |_| format!("Failed to decode base64: {}", parts[5]), + )?; + + // Create the `Hash` object and return it. + Ok(Hash { + salt: salt.into_bytes(), + hash: hash_bytes, + algorithm, + }) + } + + /// A function that generates a hash value for a password using the specified hash algorithm. + /// The function takes three arguments: + /// + /// - password: The password to be hashed. + /// - salt: A random string used to make the hash value unique. + /// - algo: The name of the hash algorithm to use. + /// + /// The function returns a `Result` object containing the hash value if successful, or an error message if unsuccessful. + pub fn generate_hash( + password: &str, + salt: &str, + algo: &str, + ) -> Result, String> { + match algo { + "argon2i" => Argon2i::hash_password(password, salt), + "bcrypt" => Bcrypt::hash_password(password, salt), + "scrypt" => Scrypt::hash_password(password, salt), + _ => Err(format!("Unsupported hash algorithm: {}", algo)), + } + } + + /// A function that generates a random string of the specified length. + pub fn generate_random_string(len: usize) -> String { + let mut rng = Random::default(); + let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + (0..len) + .map(|_| { + chars + .chars() + .nth(rng.random_range(0, chars.len() as u32) + as usize) + .unwrap() + }) + .collect() + } + + /// A function that generates a random salt for a password using the specified hash algorithm. + pub fn generate_salt(algo: &str) -> Result { + let mut rng = Random::default(); + match algo { + "argon2i" => Ok(Self::generate_random_string(16)), + "bcrypt" => { + let salt: Vec = rng.bytes(16); + let salt_array: [u8; 16] = + salt.try_into().map_err(|_| { + "Error: failed to convert salt to an array" + })?; + Ok(general_purpose::STANDARD.encode(&salt_array[..])) + } + "scrypt" => { + let salt: Vec = rng.bytes(32); + let salt_array: [u8; 32] = + salt.try_into().map_err(|_| { + "Error: failed to convert salt to an array" + })?; + Ok(general_purpose::STANDARD.encode(&salt_array[..])) + } + _ => Err(format!("Unsupported hash algorithm: {}", algo)), + } + } + + /// A function that returns the hash value of a hash object. + pub fn hash(&self) -> &[u8] { + &self.hash + } + + /// A function that returns the length of the hash value of a hash object. + pub fn hash_length(&self) -> usize { + self.hash.len() + } + + /// A function that creates a new hash object from a password, salt, and hash algorithm. + pub fn new( + password: &str, + salt: &str, + algo: &str, + ) -> Result { + // Enforce a minimum password length of 8 characters. + if password.len() < 8 { + return Err(String::from("Password is too short. It must be at least 8 characters.")); + } + let hash = Self::generate_hash(password, salt, algo)?; + + let algorithm = match algo { + "argon2i" => Ok(HashAlgorithm::Argon2i), + "bcrypt" => Ok(HashAlgorithm::Bcrypt), + "scrypt" => Ok(HashAlgorithm::Scrypt), + _ => Err(format!("Unsupported hash algorithm: {}", algo)), + }?; + + Ok(Self { + hash, + salt: salt.as_bytes().to_vec(), + algorithm, + }) + } + + /// A function that parses a JSON string into a hash object. + pub fn parse( + input: &str, + ) -> Result> { + let hash: Hash = serde_json::from_str(input)?; + Ok(hash) + } + + /// A function that parses a hash string into a hash algorithm. + pub fn parse_algorithm( + hash_str: &str, + ) -> Result { + let parts: Vec<&str> = hash_str.split('$').collect(); + if parts.len() < 2 { + return Err(String::from("Invalid hash string")); + } + match parts[1] { + "argon2i" => Ok(HashAlgorithm::Argon2i), + "bcrypt" => Ok(HashAlgorithm::Bcrypt), + "scrypt" => Ok(HashAlgorithm::Scrypt), + _ => { + Err(format!("Unsupported hash algorithm: {}", parts[1])) + } + } + } + + /// A function that returns the salt used to hash a password. + pub fn salt(&self) -> &[u8] { + &self.salt + } + + /// A function that sets the hash value of a hash object. + pub fn set_hash(&mut self, hash: &[u8]) { + self.hash = hash.to_vec(); + } + + /// A function that sets the password of a hash object. + pub fn set_password( + &mut self, + password: &str, + salt: &str, + algo: &str, + ) -> Result<(), String> { + self.hash = Self::generate_hash(password, salt, algo)?; + Ok(()) + } + + /// A function that sets the salt of a hash object. + pub fn set_salt(&mut self, salt: &[u8]) { + self.salt = salt.to_vec(); + } + + /// A function that converts a hash object to a string representation. + pub fn to_string_representation(&self) -> String { + let hash_str = self + .hash + .iter() + .map(|b| format!("{:02x}", b)) + .collect::>() + .join(""); + + format!("{}:{}", String::from_utf8_lossy(&self.salt), hash_str) + } + + /// A function that verifies a password against a hash object. + pub fn verify(&self, password: &str) -> Result { + let salt = std::str::from_utf8(&self.salt) + .map_err(|_| "Failed to convert salt to string")?; + + match self.algorithm { + HashAlgorithm::Argon2i => { + // Hash the password once + let calculated_hash = argon2i_simple(password, salt).to_vec(); + + // Debugging information + println!("Algorithm: Argon2i"); + println!("Provided password for verification: {}", password); + println!("Salt used for verification: {}", salt); + println!("Calculated Hash: {:?}", calculated_hash); + println!("Stored Hash: {:?}", self.hash); + + // Perform the verification + Ok(calculated_hash == self.hash) + } + HashAlgorithm::Bcrypt => { + // Debugging information + println!("Algorithm: Bcrypt"); + println!("Provided password for verification: {}", password); + + let hash_str = std::str::from_utf8(&self.hash) + .map_err(|_| "Failed to convert hash to string")?; + bcrypt::verify(password, hash_str) + .map_err(|_| "Failed to verify Bcrypt password") + } + HashAlgorithm::Scrypt => { + // Debugging information + println!("Algorithm: Scrypt"); + println!("Provided password for verification: {}", password); + println!("Salt used for verification: {}", salt); + + let scrypt_params = scrypt::Params::new(14, 8, 1, 64) + .map_err(|_| "Failed to create Scrypt params")?; + let mut output = [0u8; 64]; + match scrypt( + password.as_bytes(), + salt.as_bytes(), + &scrypt_params, + &mut output, + ) { + Ok(_) => { + println!("Calculated Hash: {:?}", output.to_vec()); + println!("Stored Hash: {:?}", self.hash); + Ok(output.to_vec() == self.hash) + } + Err(_) => Err("Scrypt hashing failed"), + } + } + } + } + +} + +impl fmt::Display for Hash { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Hash {{ hash: {:?} }}", self.hash) + } +} + +impl fmt::Display for HashAlgorithm { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}", self) + } +} + +impl FromStr for HashAlgorithm { + type Err = String; + + fn from_str(s: &str) -> Result { + let algorithm = match s { + "argon2i" => HashAlgorithm::Argon2i, + "bcrypt" => HashAlgorithm::Bcrypt, + "scrypt" => HashAlgorithm::Scrypt, + _ => return Err(String::from("Invalid hash algorithm")), + }; + Ok(algorithm) + } +} + /// A builder struct for the `Hash` struct. /// It contains optional fields that correspond to the fields in `Hash`. /// The `#[derive(Default)]` allows us to initialize all fields to `None`. From 59a16125985d0fed50439d97eb7471391ffe09f0 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 20:31:24 +0100 Subject: [PATCH 12/22] fix(hsh): temporary comment modules --- src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e35e90b..c5e249d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,8 +112,8 @@ pub mod algorithms; /// The `macros` module contains functions for generating macros. pub mod macros; -/// The `modules` module contains the library modules. -pub mod modules; +// The `modules` module contains the library modules. +// pub mod modules; /// The `models` module contains the data models for the library. pub mod models; From 462fd6a3772fe7bd985de6838de4e63e1e5e9047 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 21:10:42 +0100 Subject: [PATCH 13/22] fix(hsh): Refactor and Document hsh Macros --- src/macros.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 4fd744f..0b8aeff 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -14,7 +14,7 @@ //! //! | Macro | Description | //! |--------|------------| -//! | `hsh` | Calls the parse method on the Common struct from the hsh crate. | +//! | `hsh` | Calls the `parse` method on the `Hash` struct from the hsh crate. | //! | `hsh_assert` | Asserts that a given condition is true. If the condition is false, the macro will cause the program to panic with the message "Assertion failed!". | //! | `hsh_contains` | Checks if a given string contains a specified substring. | //! | `hsh_in_range` | Checks if a given value is within a specified range (inclusive). | @@ -41,12 +41,12 @@ /// This macro takes any number of arguments and parses them into a Rust /// value. The parsed value is returned wrapped in -/// `hsh::Common::parse()` function call. +/// `hsh::Hash::parse()` function call. /// #[macro_export] macro_rules! hsh { - ($($tt:tt)*) => { - hsh::Hash::parse($($tt)*) + ($($token:tt)*) => { + hsh::Hash::parse($($token)*) }; } @@ -106,11 +106,7 @@ macro_rules! hsh_contains { #[macro_export] macro_rules! hsh_in_range { ($value:expr, $min:expr, $max:expr) => { - if $value >= $min && $value <= $max { - true - } else { - false - } + $value >= $min && $value <= $max }; } From 9f2e386f98d2bfc67cef821c8f7a4940e6e88992 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 21:11:11 +0100 Subject: [PATCH 14/22] doc(hsh): corrected label --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index b2def9a..a51380a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ // Copyright © 2023 Hash (HSH) library. All rights reserved. // SPDX-License-Identifier: Apache-2.0 OR MIT -//! This is the main entry point for the cmn application. +//! This is the main entry point for the hsh application. fn main() { // Call the `run()` function from the `Hash (HSH)` module. if let Err(err) = hsh::run() { From 753086d082e325992c3baca47d1a522d8aa0ddaf Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 21:11:41 +0100 Subject: [PATCH 15/22] fix(hsh): cleaning up --- src/lib.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c5e249d..bbbce5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -112,15 +112,11 @@ pub mod algorithms; /// The `macros` module contains functions for generating macros. pub mod macros; -// The `modules` module contains the library modules. -// pub mod modules; - /// The `models` module contains the data models for the library. pub mod models; /// This is the main entry point for the `Hash (HSH)` library. pub fn run() -> Result<(), Box> { - // Example of conditional logic for test mode, if necessary. if std::env::var("HSH_TEST_MODE").unwrap_or_default() == "1" { return Err("Simulated error".into()); } From 7607e67556ece791c214d5005616b09d998a40af Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 22:06:09 +0100 Subject: [PATCH 16/22] test(hsh): new tests for hash algorithms --- tests/test_argon2i.rs | 29 ++++++++++++++++++++++++++ tests/test_bcrypt.rs | 38 ++++++++++++++++++++++++++++++++++ tests/test_scrypt.rs | 48 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 tests/test_argon2i.rs create mode 100644 tests/test_bcrypt.rs create mode 100644 tests/test_scrypt.rs diff --git a/tests/test_argon2i.rs b/tests/test_argon2i.rs new file mode 100644 index 0000000..bfecf29 --- /dev/null +++ b/tests/test_argon2i.rs @@ -0,0 +1,29 @@ + + +#[cfg(test)] +mod tests { + use hsh::models::hash_algorithm::HashingAlgorithm; + + #[test] + fn test_hash_differs_from_password() { + let password = "password123"; + let salt = "somesalt"; + let hashed_password = hsh::algorithms::argon2i::Argon2i::hash_password(password, salt).unwrap(); + + assert_ne!(hashed_password, password.as_bytes()); + } + + #[test] + fn test_different_salts_produce_different_hashes() { + let password = "password123"; + let salt1 = "salt123456789012345678901234567"; + let salt2 = "salt234567890123456789012345678"; + + let hash1 = hsh::algorithms::argon2i::Argon2i::hash_password(password, salt1).unwrap(); + let hash2 = hsh::algorithms::argon2i::Argon2i::hash_password(password, salt2).unwrap(); + + assert_ne!(hash1, hash2); + } + + +} diff --git a/tests/test_bcrypt.rs b/tests/test_bcrypt.rs new file mode 100644 index 0000000..c67d893 --- /dev/null +++ b/tests/test_bcrypt.rs @@ -0,0 +1,38 @@ + +#[cfg(test)] +mod tests { + use hsh::models::hash_algorithm::HashingAlgorithm; + + #[test] + fn test_hash_differs_from_password() { + let password = "password123"; + let salt = "somesalt"; + let hashed_password = hsh::algorithms::bcrypt::Bcrypt::hash_password(password, salt).unwrap(); + + assert_ne!(hashed_password, password.as_bytes()); + } + + #[test] + fn test_different_salts_produce_different_hashes() { + let password = "password123"; + let salt1 = "salt1"; + let salt2 = "salt2"; + + let hash1 = hsh::algorithms::bcrypt::Bcrypt::hash_password(password, salt1).unwrap(); + let hash2 = hsh::algorithms::bcrypt::Bcrypt::hash_password(password, salt2).unwrap(); + + assert_ne!(hash1, hash2); + } + + #[test] + fn test_hashing_error() { + // Setup conditions for hashing to fail + let password = "password123"; + + // Intentionally using an invalid cost to force an error + let invalid_cost = 1; + let result = bcrypt::hash(password, invalid_cost); + + assert!(result.is_err()); + } +} \ No newline at end of file diff --git a/tests/test_scrypt.rs b/tests/test_scrypt.rs new file mode 100644 index 0000000..4daa390 --- /dev/null +++ b/tests/test_scrypt.rs @@ -0,0 +1,48 @@ +#[cfg(test)] +mod tests { + use hsh::models::hash_algorithm::HashingAlgorithm; + + #[test] + fn test_hash_password_success() { + let password = "secure_password"; + let salt = "random_salt"; + + let result = hsh::algorithms::scrypt::Scrypt::hash_password(password, salt); + assert!(result.is_ok()); + } + + #[test] + fn test_same_salt_and_password_produce_same_hash() { + let password = "password123"; + let salt = "salt123"; + + let hash1_result = hsh::algorithms::scrypt::Scrypt::hash_password(password, salt).unwrap(); + let hash2_result = hsh::algorithms::scrypt::Scrypt::hash_password(password, salt).unwrap(); + + assert_eq!(hash1_result, hash2_result); + } + + #[test] + fn test_different_salts_produce_different_hashes() { + let password = "password123"; + let salt1 = "salt123"; + let salt2 = "another_salt123"; + + let hash1_result = hsh::algorithms::scrypt::Scrypt::hash_password(password, salt1).unwrap(); + let hash2_result = hsh::algorithms::scrypt::Scrypt::hash_password(password, salt2).unwrap(); + + assert_ne!(hash1_result, hash2_result); + } + + #[test] + fn test_different_passwords_produce_different_hashes() { + let password1 = "password123"; + let password2 = "other_password123"; + let salt = "salt123"; + + let hash1_result = hsh::algorithms::scrypt::Scrypt::hash_password(password1, salt).unwrap(); + let hash2_result = hsh::algorithms::scrypt::Scrypt::hash_password(password2, salt).unwrap(); + + assert_ne!(hash1_result, hash2_result); + } +} From cde3d9109f55886bb82c955d18b4f9e859e8a1fd Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 22:17:53 +0100 Subject: [PATCH 17/22] test(hsh): new tests for loggers --- Cargo.lock | 76 +++++++++++++++++++++++++++++++++++++------ Cargo.toml | 2 ++ src/lib.rs | 3 ++ src/macros.rs | 25 ++++++++++++++ tests/test_loggers.rs | 49 ++++++++++++++++++++++++++++ 5 files changed, 145 insertions(+), 10 deletions(-) create mode 100644 tests/test_loggers.rs diff --git a/Cargo.lock b/Cargo.lock index 5070395..1238ad1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783" +dependencies = [ + "memchr", +] + [[package]] name = "anes" version = "0.1.6" @@ -139,7 +148,7 @@ checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09" dependencies = [ "memchr", "once_cell", - "regex-automata", + "regex-automata 0.1.10", "serde", ] @@ -412,6 +421,12 @@ dependencies = [ "syn 1.0.107", ] +[[package]] +name = "deranged" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" + [[package]] name = "derive_builder" version = "0.12.0" @@ -478,6 +493,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "dtt" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e274740bf3a29185381f8d9e881737a336f01717ed8a7b8514226a536794dd16" +dependencies = [ + "regex", + "serde", + "time", +] + [[package]] name = "duct" version = "0.13.6" @@ -625,6 +651,8 @@ dependencies = [ "base64", "bcrypt", "criterion", + "dtt", + "log", "scrypt", "serde", "serde_json", @@ -732,12 +760,9 @@ checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" [[package]] name = "log" -version = "0.4.17" +version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" @@ -971,10 +996,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.1" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.3.7", "regex-syntax", ] @@ -984,11 +1012,22 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "rustix" @@ -1178,6 +1217,23 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" +[[package]] +name = "time" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" +dependencies = [ + "deranged", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" + [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index 8183ff4..fd43559 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -52,6 +52,8 @@ path = "examples/hsh.rs" argon2rs = "0.2.5" base64 = "0.21.4" bcrypt = "0.15.0" +dtt = "0.0.4" +log = {version="0.4.20", features = ["std"] } scrypt = "0.11.0" serde = { version = "1.0.188", features = ["derive"] } serde_json = "1.0.106" diff --git a/src/lib.rs b/src/lib.rs index bbbce5b..4573f70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,6 +109,9 @@ /// The `algorithms` module contains the password hashing algorithms. pub mod algorithms; +/// The `loggers` module contains the loggers for the library. +pub mod loggers; + /// The `macros` module contains functions for generating macros. pub mod macros; diff --git a/src/macros.rs b/src/macros.rs index 0b8aeff..773e46e 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -433,3 +433,28 @@ macro_rules! hash_length { $hash.hash_length() }; } + +#[macro_export] +/// # `macro_log_info` Macro +macro_rules! macro_log_info { + ($level:expr, $component:expr, $description:expr, $format:expr) => { + { + use $crate::loggers::{Log, LogLevel, LogFormat}; + + extern crate dtt; + use dtt::DateTime; + // Get the current date and time in ISO 8601 format. + let date = DateTime::new(); + let iso = date.iso_8601; + + extern crate vrd; + use vrd::Random; + // Create a new random number generator + let mut rng = Random::default(); + let session_id = rng.rand().to_string(); + + let log = Log::new(&session_id, &iso, $level, $component, $description, $format); + let _ = log.log(); + } + }; +} diff --git a/tests/test_loggers.rs b/tests/test_loggers.rs new file mode 100644 index 0000000..eb9fb94 --- /dev/null +++ b/tests/test_loggers.rs @@ -0,0 +1,49 @@ +#[cfg(test)] +mod tests { + use hsh::loggers::{Log, LogLevel, LogFormat}; + + #[test] + fn test_log_default_values() { + let log = Log::default(); + assert_eq!(log.session_id, String::default()); + assert_eq!(log.time, String::default()); + assert_eq!(log.level, LogLevel::INFO); + assert_eq!(log.component, String::default()); + assert_eq!(log.description, String::default()); + assert_eq!(log.format, LogFormat::CLF); + } + + #[test] + fn test_log_custom_values() { + let session_id = "session_id_123"; + let time = "2021-12-01T12:34:56Z"; + let level = LogLevel::ERROR; + let component = "test_component"; + let description = "test_description"; + let format = LogFormat::JSON; + + let log = Log::new(session_id, time, level.clone(), component, description, format.clone()); + + assert_eq!(log.session_id, session_id); + assert_eq!(log.time, time); + assert_eq!(log.level, level); + assert_eq!(log.component, component); + assert_eq!(log.description, description); + assert_eq!(log.format, format); + } + + #[test] + fn test_log_level_to_string() { + assert_eq!(format!("{}", LogLevel::INFO), "INFO"); + assert_eq!(format!("{}", LogLevel::ERROR), "ERROR"); + assert_eq!(format!("{}", LogLevel::DEBUG), "DEBUG"); + } + + #[test] + fn test_log_format_to_string() { + assert_eq!(format!("{}", LogFormat::CLF), "CLF\n"); + assert_eq!(format!("{}", LogFormat::JSON), "JSON\n"); + assert_eq!(format!("{}", LogFormat::CEF), "CEF\n"); + } + +} From 8876ec77d18b44efc4618311a5462abc1cc176f3 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 22:48:50 +0100 Subject: [PATCH 18/22] test(hsh): new tests for models --- src/constants.rs | 0 tests/test_hash.rs | 65 ++++++++++++++++++++++++++++++++++++ tests/test_hash_algorithm.rs | 32 ++++++++++++++++++ 3 files changed, 97 insertions(+) delete mode 100644 src/constants.rs create mode 100644 tests/test_hash.rs create mode 100644 tests/test_hash_algorithm.rs diff --git a/src/constants.rs b/src/constants.rs deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_hash.rs b/tests/test_hash.rs new file mode 100644 index 0000000..2a8d59c --- /dev/null +++ b/tests/test_hash.rs @@ -0,0 +1,65 @@ +#[cfg(test)] +mod tests { + use hsh::models::hash_algorithm::HashAlgorithm; + use hsh::models::hash::{Hash, HashBuilder, Salt}; + use std::str::FromStr; + + #[test] + fn test_new_argon2i() { + let password = "password123"; + let salt: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let hash = Hash::new_argon2i(password, salt.clone()).unwrap(); + assert_eq!(hash.salt, salt); + assert_eq!(hash.algorithm, HashAlgorithm::Argon2i); + } + + #[test] + fn test_new_bcrypt() { + let password = "password123"; + let cost = 4; + let hash = Hash::new_bcrypt(password, cost).unwrap(); + assert_eq!(hash.algorithm, HashAlgorithm::Bcrypt); + } + + #[test] + fn test_new_scrypt() { + let password = "password123"; + let salt: Salt = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let hash = Hash::new_scrypt(password, salt.clone()).unwrap(); + assert_eq!(hash.salt, salt); + assert_eq!(hash.algorithm, HashAlgorithm::Scrypt); + } + + #[test] + fn test_from_hash() { + let hash_bytes = vec![1, 2, 3, 4]; + let hash = Hash::from_hash(&hash_bytes, "argon2i").unwrap(); + assert_eq!(hash.hash, hash_bytes); + assert_eq!(hash.algorithm, HashAlgorithm::Argon2i); + } + + #[test] + fn test_hash_algorithm_from_str() { + let algorithm = HashAlgorithm::from_str("argon2i").unwrap(); + assert_eq!(algorithm, HashAlgorithm::Argon2i); + } + + #[test] + fn test_hash_builder() { + let hash = vec![1, 2, 3, 4]; + let salt: Salt = vec![0, 1, 2, 3]; + let algorithm = HashAlgorithm::Argon2i; + let built_hash = HashBuilder::new() + .hash(hash.clone()) + .salt(salt.clone()) + .algorithm(algorithm) + .build() + .unwrap(); + + assert_eq!(built_hash.hash, hash); + assert_eq!(built_hash.salt, salt); + assert_eq!(built_hash.algorithm, algorithm); + } + + // Add more tests such as verification, string representation, etc. +} diff --git a/tests/test_hash_algorithm.rs b/tests/test_hash_algorithm.rs new file mode 100644 index 0000000..493c4c3 --- /dev/null +++ b/tests/test_hash_algorithm.rs @@ -0,0 +1,32 @@ +#[cfg(test)] +mod tests { + use hsh::models::hash_algorithm::{HashAlgorithm, HashingAlgorithm}; + + // Dummy struct to implement HashingAlgorithm for testing + struct DummyAlgorithm; + + impl HashingAlgorithm for DummyAlgorithm { + fn hash_password(_password: &str, _salt: &str) -> Result, String> { + Ok(vec![1, 2, 3, 4]) // Dummy logic + } + } + + #[test] + fn test_hash_algorithm_enum() { + let argon2i = HashAlgorithm::Argon2i; + let bcrypt = HashAlgorithm::Bcrypt; + let scrypt = HashAlgorithm::Scrypt; + + assert_eq!(argon2i as i32, 0); + assert_eq!(bcrypt as i32, 1); + assert_eq!(scrypt as i32, 2); + } + + #[test] + fn test_hashing_algorithm_trait() { + let password = "password123"; + let salt = "salt123"; + let hashed = DummyAlgorithm::hash_password(password, salt).unwrap(); + assert_eq!(hashed, vec![1, 2, 3, 4]); + } +} From eb3d09000545ed86ccb4db721beee355f0b0bfcd Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 22:54:42 +0100 Subject: [PATCH 19/22] doc(hsh): added copyrights --- tests/test_argon2i.rs | 3 ++- tests/test_bcrypt.rs | 4 ++++ tests/test_hash.rs | 3 +++ tests/test_hash_algorithm.rs | 3 +++ tests/test_loggers.rs | 3 +++ tests/test_scrypt.rs | 3 +++ 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tests/test_argon2i.rs b/tests/test_argon2i.rs index bfecf29..6574c73 100644 --- a/tests/test_argon2i.rs +++ b/tests/test_argon2i.rs @@ -1,4 +1,5 @@ - +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT #[cfg(test)] mod tests { diff --git a/tests/test_bcrypt.rs b/tests/test_bcrypt.rs index c67d893..c45cdb7 100644 --- a/tests/test_bcrypt.rs +++ b/tests/test_bcrypt.rs @@ -1,4 +1,8 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + + #[cfg(test)] mod tests { use hsh::models::hash_algorithm::HashingAlgorithm; diff --git a/tests/test_hash.rs b/tests/test_hash.rs index 2a8d59c..2dd836a 100644 --- a/tests/test_hash.rs +++ b/tests/test_hash.rs @@ -1,3 +1,6 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + #[cfg(test)] mod tests { use hsh::models::hash_algorithm::HashAlgorithm; diff --git a/tests/test_hash_algorithm.rs b/tests/test_hash_algorithm.rs index 493c4c3..28c7daf 100644 --- a/tests/test_hash_algorithm.rs +++ b/tests/test_hash_algorithm.rs @@ -1,3 +1,6 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + #[cfg(test)] mod tests { use hsh::models::hash_algorithm::{HashAlgorithm, HashingAlgorithm}; diff --git a/tests/test_loggers.rs b/tests/test_loggers.rs index eb9fb94..f60614f 100644 --- a/tests/test_loggers.rs +++ b/tests/test_loggers.rs @@ -1,3 +1,6 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + #[cfg(test)] mod tests { use hsh::loggers::{Log, LogLevel, LogFormat}; diff --git a/tests/test_scrypt.rs b/tests/test_scrypt.rs index 4daa390..6bddca0 100644 --- a/tests/test_scrypt.rs +++ b/tests/test_scrypt.rs @@ -1,3 +1,6 @@ +// Copyright © 2023 Hash (HSH) library. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 OR MIT + #[cfg(test)] mod tests { use hsh::models::hash_algorithm::HashingAlgorithm; From b8bb2374370171e028b2fb692f2d7201b82ae15c Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 22:56:35 +0100 Subject: [PATCH 20/22] deps(hsh): xtask updates --- Cargo.lock | 4 ++-- xtask/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1238ad1..9b3986b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,9 +25,9 @@ checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "argon2rs" diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 7d4cdef..841bf68 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -8,7 +8,7 @@ edition = "2021" [dependencies] xtaskops = "0.4.2" -anyhow = "1.0.71" +anyhow = "1.0.75" [[bin]] name = "xtask" From 7272990653ca3e9a2742456a759f98be4d3eb537 Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 23:11:37 +0100 Subject: [PATCH 21/22] fix(hsh): criterion benches --- benches/criterion.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benches/criterion.rs b/benches/criterion.rs index a46a339..5606caa 100644 --- a/benches/criterion.rs +++ b/benches/criterion.rs @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 OR MIT //! Benchmarking the Hash (HSH) library using Criterion.rs + +#![allow(missing_docs)] extern crate argon2rs; extern crate criterion; From 8a446d1db6a9a5a5e8879667064695400edc400a Mon Sep 17 00:00:00 2001 From: Sebastien Rousseau Date: Sun, 10 Sep 2023 23:32:03 +0100 Subject: [PATCH 22/22] fix(hsh): workflow updates --- .github/workflows/audit.yml | 24 ++++++--- .github/workflows/check.yml | 24 ++++++--- .github/workflows/document.yml | 93 +++++++++++++++++--------------- .github/workflows/lint.yml | 24 ++++++--- .github/workflows/release.yml | 98 ++++++++++++++++++++-------------- .github/workflows/test.yml | 97 ++++++++++++++++++++++----------- src/loggers.rs | 4 +- 7 files changed, 227 insertions(+), 137 deletions(-) diff --git a/.github/workflows/audit.yml b/.github/workflows/audit.yml index a478330..363cfd7 100644 --- a/.github/workflows/audit.yml +++ b/.github/workflows/audit.yml @@ -1,13 +1,23 @@ name: 🧪 Audit -on: [push, pull_request] + +on: + push: + branches: + - feat/hsh + pull_request: + branches: + - feat/hsh + release: + types: [created] + jobs: dependencies: name: Audit dependencies runs-on: ubuntu-latest steps: - - uses: hecrj/setup-rust-action@v1 - - name: Install cargo-audit - run: cargo install cargo-audit - - uses: actions/checkout@master - - name: Audit dependencies - run: cargo audit \ No newline at end of file + - uses: hecrj/setup-rust-action@v2 + - name: Install cargo-audit + run: cargo install cargo-audit + - uses: actions/checkout@master + - name: Audit dependencies + run: cargo audit \ No newline at end of file diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 0e14081..2311e0c 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -1,13 +1,23 @@ name: 🧪 Check -on: [push, pull_request] + +on: + push: + branches: + - feat/hsh + pull_request: + branches: + - feat/hsh + release: + types: [created] + jobs: all: name: Check runs-on: ubuntu-latest steps: - - uses: hecrj/setup-rust-action@v1 - with: - components: clippy - - uses: actions/checkout@master - - name: Check lints - run: cargo check --all-targets --workspace --all-features \ No newline at end of file + - uses: hecrj/setup-rust-action@v2 + with: + components: clippy + - uses: actions/checkout@master + - name: Check lints + run: cargo check --all-targets --workspace --all-features \ No newline at end of file diff --git a/.github/workflows/document.yml b/.github/workflows/document.yml index 6b405ea..2197651 100644 --- a/.github/workflows/document.yml +++ b/.github/workflows/document.yml @@ -1,51 +1,60 @@ name: 🧪 Document -on: [push, pull_request] + +on: + push: + branches: + - feat/hsh + pull_request: + branches: + - feat/hsh + release: + types: [created] + jobs: all: name: Document if: github.ref == 'refs/heads/main' && github.event_name == 'push' runs-on: ubuntu-latest concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }} steps: + - uses: hecrj/setup-rust-action@v2 + with: + rust-version: nightly + + - uses: actions/checkout@v4 + + - name: Update libssl + run: | + sudo apt-get update + sudo apt-get install -y libssl1.1 + + - name: Generate documentation for all features and publish it + run: | + RUSTDOCFLAGS="--cfg docsrs" \ + cargo doc --no-deps --all-features --workspace + # Write index.html with redirect + echo '' > ./target/doc/index.html + + - name: Deploy + uses: actions/upload-artifact@v3 + with: + name: documentation + path: target/doc + if-no-files-found: error + retention-days: 1 + + - name: Write CNAME file + run: echo 'doc.hshlib.one' > ./target/doc/CNAME - - uses: hecrj/setup-rust-action@v1 - with: - rust-version: nightly - - - uses: actions/checkout@v3 - - - name: Update libssl - run: | - sudo apt-get update - sudo apt-get install -y libssl1.1 - - - name: Generate documentation for all features and publish it - run: | - RUSTDOCFLAGS="--cfg docsrs" \ - cargo doc --no-deps --all-features --workspace - # Write index.html with redirect - echo '' > ./target/doc/index.html - - - name: Deploy - uses: actions/upload-artifact@v3 - with: - name: documentation - path: target/doc - if-no-files-found: error - retention-days: 1 - - - name: Write CNAME file - run: echo 'doc.hshlib.one' > ./target/doc/CNAME - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc - publish_branch: gh-pages - cname: true - clean: true - commit_message: Deploy documentation at ${{ github.sha }} - commit_user_name: github-actions - commit_user_email: actions@users.noreply.github.com \ No newline at end of file + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v3 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./target/doc + publish_branch: gh-pages + cname: true + clean: true + commit_message: Deploy documentation at ${{ github.sha }} + commit_user_name: github-actions + commit_user_email: actions@users.noreply.github.com \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2c9f3da..60e5f78 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,13 +1,23 @@ name: 🧪 Lint -on: [push, pull_request] + +on: + push: + branches: + - feat/hsh + pull_request: + branches: + - feat/hsh + release: + types: [created] + jobs: all: name: Lint runs-on: ubuntu-latest steps: - - uses: hecrj/setup-rust-action@v1 - with: - components: clippy - - uses: actions/checkout@master - - name: Check lints - run: cargo clippy --workspace --all-features --all-targets --no-deps -- -D warnings \ No newline at end of file + - uses: hecrj/setup-rust-action@v2 + with: + components: clippy + - uses: actions/checkout@master + - name: Check lints + run: cargo clippy --workspace --all-features --all-targets --no-deps -- -D warnings \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cba37ce..5f2993d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,13 +2,28 @@ name: 🧪 Release on: [push, pull_request] +concurrency: + group: ${{ github.ref }} + cancel-in-progress: true + jobs: # Build the project for all the targets and generate artifacts. build: # This job builds the project for all the targets and generates a # release artifact that contains the binaries for all the targets. name: ❯ Build 🛠 + + # Only run this job on the main branch when a commit is pushed. if: github.ref == 'refs/heads/main' && github.event_name == 'push' + + # Set up the job environment variables. + env: + BUILD_ID: ${{ github.run_id }} + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OS: ${{ matrix.os }} + TARGET: ${{ matrix.target }} + strategy: fail-fast: false matrix: @@ -33,17 +48,12 @@ jobs: - arm-unknown-linux-gnueabi # ARMv6 Linux (kernel 3.2, glibc 2.17) - arm-unknown-linux-gnueabihf # ARMv7 Linux, hardfloat (kernel 3.2, glibc 2.17) - armv7-unknown-linux-gnueabihf # ARMv7 Linux, hardfloat (kernel 3.2, glibc 2.17) - - mips-unknown-linux-gnu # MIPS Linux (kernel 3.2, glibc 2.17) - - mips64-unknown-linux-gnuabi64 # MIPS64 Linux (kernel 3.2, glibc 2.17) - - mips64el-unknown-linux-gnuabi64 # MIPS64el Linux (kernel 3.2, glibc 2.17) - - mipsel-unknown-linux-gnu # MIPSel Linux (kernel 3.2, glibc 2.17) - powerpc-unknown-linux-gnu # PowerPC Linux (kernel 3.2, glibc 2.17) - powerpc64-unknown-linux-gnu # PowerPC64 Linux (kernel 3.2, glibc 2.17) - powerpc64le-unknown-linux-gnu # PowerPC64le Linux (kernel 3.2, glibc 2.17) - riscv64gc-unknown-linux-gnu # RISC-V Linux (kernel 3.2, glibc 2.17) - s390x-unknown-linux-gnu # s390x Linux (kernel 3.2, glibc 2.17) - x86_64-unknown-freebsd # 64-bit FreeBSD on x86-64 - - x86_64-unknown-illumos # 64-bit Illumos on x86-64 - x86_64-unknown-linux-musl # 64-bit Linux (kernel 2.6.32+, musl libc) - x86_64-unknown-netbsd # 64-bit NetBSD on x86-64 @@ -93,18 +103,6 @@ jobs: - target: armv7-unknown-linux-gnueabihf os: ubuntu-latest cross: true - - target: mips-unknown-linux-gnu - os: ubuntu-latest - cross: true - - target: mips64-unknown-linux-gnuabi64 - os: ubuntu-latest - cross: true - - target: mips64el-unknown-linux-gnuabi64 - os: ubuntu-latest - cross: true - - target: mipsel-unknown-linux-gnu - os: ubuntu-latest - cross: true - target: powerpc-unknown-linux-gnu os: ubuntu-latest cross: true @@ -123,9 +121,6 @@ jobs: - target: x86_64-unknown-freebsd os: ubuntu-latest cross: true - - target: x86_64-unknown-illumos - os: ubuntu-latest - cross: true - target: x86_64-unknown-linux-musl os: ubuntu-latest cross: true @@ -139,7 +134,7 @@ jobs: # Check out the repository code - name: Checkout sources id: checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Install the stable Rust toolchain - name: Install stable toolchain @@ -167,6 +162,7 @@ jobs: run: | # Install cross cargo install cross + # Clean the build artifacts cargo clean --verbose shell: bash @@ -178,23 +174,23 @@ jobs: with: use-cross: true command: build - args: --verbose --workspace --release --target ${{ matrix.target }} + args: --verbose --workspace --release --target ${{ env.TARGET }} # Package the binary for each target - name: Package the binary id: package-binary run: | mkdir -p target/package - tar czf target/package/${{ matrix.target }}.tar.gz -C target/${{ matrix.target }}/release . - echo "${{ matrix.target }}.tar.gz=target/package/${{ matrix.target }}.tar.gz" >> $GITHUB_ENV + tar czf target/package/${{ env.TARGET }}.tar.gz -C target/${{ env.TARGET }}/release . + echo "${{ env.TARGET }}.tar.gz=target/package/${{ env.TARGET }}.tar.gz" >> $GITHUB_ENV # Upload the binary for each target - name: Upload the binary id: upload-binary uses: actions/upload-artifact@v3 with: - name: ${{ matrix.target }}.tar.gz - path: ${{ format('{0}.tar.gz', matrix.target) }} + name: ${{ env.TARGET }}.tar.gz + path: target/package/${{ env.TARGET }}.tar.gz # Release the binary to GitHub Releases release: @@ -205,7 +201,7 @@ jobs: steps: # Check out the repository code - name: Checkout sources - uses: actions/checkout@v3 + uses: actions/checkout@v4 # Install the stable Rust toolchain - name: Install stable toolchain @@ -232,7 +228,7 @@ jobs: # Download the artifacts from the build job - name: Download artifacts run: | - for target in ${{ matrix.target }}; do + for target in ${{ env.TARGET }}; do echo "Downloading $target artifact" name="${target}.tar.gz" echo "Artifact name: $name" @@ -241,20 +237,41 @@ jobs: done env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_ID: ${{ github.run_id }} + VERSION: ${{ env.VERSION }} + TARGET: ${{ env.TARGET }} + OS: ${{ env.OS }} - # Generate the changelog based on git log and template file + # Generate the changelog based on the git log - name: Generate Changelog + id: generate-changelog + env: + BUILD_ID: ${{ github.run_id }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_URL: https://github.com/sebastienrousseau/hsh/releases + TARGET: ${{ env.TARGET }} + run: | - # Append version information to CHANGELOG.md - echo "## Release v${{ env.VERSION }} - $(date +'%Y-%m-%d')" >> ${{ github.workspace }}/CHANGELOG.md - # Copy content of template file to CHANGELOG.md - cat TEMPLATE.md >> ${{ github.workspace }}/CHANGELOG.md - # Append git log to CHANGELOG.md - echo "$(git log --pretty=format:'%s' --reverse $(git describe --tags --abbrev=0)..HEAD)" >> ${{ github.workspace }}/CHANGELOG.md - # Append empty line to CHANGELOG.md - echo "" >> ${{ github.workspace }}/CHANGELOG.md + if [[ ! -f CHANGELOG.md ]]; then + # Set path to changelog file + changelog_file="${{ github.workspace }}/CHANGELOG.md" + + # Get version from Cargo.toml + version=$(grep version Cargo.toml | sed -n 2p | cut -d '"' -f 2) + + # Append version information to changelog + echo "## Release v${version} - $(date +'%Y-%m-%d')" >> "${changelog_file}" + + # Copy content of template file to changelog + cat TEMPLATE.md >> "${changelog_file}" + + # Append git log to changelog + echo "$(git log --pretty=format:'%s' --reverse HEAD)" >> "${changelog_file}" + + # Append empty line to changelog + echo "" >> "${changelog_file}" + + fi + shell: bash # Append the artifact links to the changelog - name: Append Artifact Links @@ -266,12 +283,13 @@ jobs: echo "* [${link}](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/download/v${{ env.VERSION }}/$link)" >> ${{ github.workspace }}/CHANGELOG.md done - # Create the release on GitHub Releases + # Create the release on GitHub releases - name: Create Release id: create-release uses: actions/create-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + VERSION: ${{ env.VERSION }} with: tag_name: v${{ env.VERSION }} release_name: Hash (HSH) 🦀 v${{ env.VERSION }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fa2a9c9..bd70621 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,36 +1,69 @@ name: 🧪 Test -on: [push, pull_request] + +on: [pull_request, push] jobs: - all: - # Run tests on Linux - name: Test + test-lib: + name: Test library runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest] + toolchain: [stable, nightly] + continue-on-error: true + steps: - - uses: hecrj/setup-rust-action@v1 - - uses: actions/checkout@master - - name: Run tests - run: cargo test --verbose --workspace - - name: Run tests with all features - run: cargo test --verbose --workspace --all-features - - # Install grcov - - name: Install grcov - run: | - mkdir -p "${HOME}/.local/bin" - curl -sL https://github.com/mozilla/grcov/releases/download/v0.8.18/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - -C "${HOME}/.local/bin" - echo "$HOME/.local/bin" >> $GITHUB_PATH - - # Use grcov to generate a coverage report - - name: Generate coverage report - id: coverage - uses: actions-rs/cargo@v1 - with: - command: xtask - args: coverage - - # Upload the coverage report to codecov - - name: Upload coverage report to codecov - id: codecov - uses: codecov/codecov-action@v3 - with: - files: coverage/*.lcov \ No newline at end of file + # Checkout the repository + - name: Checkout repository + uses: actions/checkout@v4 + + # Setup Rust + - name: Setup Rust + run: | + rustup toolchain add ${{ matrix.toolchain }} --component llvm-tools-preview + rustup override set ${{ matrix.toolchain }} + + # Configure cache + - name: Configure cache + uses: actions/cache@v3 + with: + path: | + ~/.cargo/bin/ + ~/.cargo/registry/index/ + ~/.cargo/registry/cache/ + ~/.cargo/git/db/ + target/ + key: test-${{ runner.os }}-cargo-${{ matrix.toolchain }}-${{ hashFiles('**/Cargo.lock') }} + + # Run tests with all features + - name: Run tests with all features + id: run-tests-all-features + run: cargo test --verbose --workspace --all-features + + # Install grcov + - name: Install grcov + # Only run this job on the main branch when a commit is pushed. + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + id: install-grcov + run: | + mkdir -p "${HOME}/.local/bin" + curl -sL https://github.com/mozilla/grcov/releases/download/v0.8.18/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - -C "${HOME}/.local/bin" + echo "$HOME/.local/bin" >> $GITHUB_PATH + + # Use grcov to generate a coverage report + - name: Generate coverage report + # Only run this job on the main branch when a commit is pushed. + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + id: generate-code-coverage + uses: actions-rs/cargo@v1.0.1 + with: + command: xtask + args: coverage + + # Upload the coverage report to codecov + - name: Upload coverage report to codecov + # Only run this job on the main branch when a commit is pushed. + if: github.ref == 'refs/heads/main' && github.event_name == 'push' + id: upload-report-codecov + uses: codecov/codecov-action@v3 + with: + files: coverage/*.lcov \ No newline at end of file diff --git a/src/loggers.rs b/src/loggers.rs index 7f62b33..2c2ad3e 100644 --- a/src/loggers.rs +++ b/src/loggers.rs @@ -122,7 +122,7 @@ impl Log { let mut file = OpenOptions::new() .write(true) .truncate(true) - .open("shokunin.log")?; + .open("hsh.log")?; match self.format { LogFormat::CLF => { writeln!( @@ -144,7 +144,7 @@ impl Log { r#"[CEF] 1 - shokunin + Hash (HSH) Application {} Log