diff --git a/Cargo.lock b/Cargo.lock index 42d7ab0..05c86f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -222,8 +222,11 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" name = "auto-decrypt" version = "0.1.0" dependencies = [ + "argon2", "async-ssh2-tokio", "async-trait", + "base64", + "chacha20poly1305", "clap", "custom_error", "diesel", @@ -411,6 +414,19 @@ dependencies = [ "cpufeatures", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "chrono" version = "0.4.41" @@ -433,6 +449,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 4248ab2..6350b1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,4 +17,7 @@ url = "2" custom_error = "1.9.2" enum_dispatch = "0.3.13" rocket_dyn_templates = { version = "0.2.0", features = ["minijinja"]} -diesel-derive-enum = { version = "3.0.0-beta.1", features = ["sqlite"] } \ No newline at end of file +diesel-derive-enum = { version = "3.0.0-beta.1", features = ["sqlite"] } +argon2 = "0.5.3" +base64 = "0.22.1" +chacha20poly1305 = "0.10.1" diff --git a/src/app_config/general_config.rs b/src/app_config/general_config.rs index 2f8cbb9..53a5375 100644 --- a/src/app_config/general_config.rs +++ b/src/app_config/general_config.rs @@ -7,6 +7,7 @@ pub (super) struct TomlAppConfig { pub (super) port: Option, pub (super) addresses: Option>, pub (super) platform: Option, + pub (super) crypto_key: Option, pub (super) ssh_known_host_file: Option, pub (super) user_confirmation_expiration: Option, pub (super) db_file: Option, diff --git a/src/app_config/mod.rs b/src/app_config/mod.rs index fe62e25..d36a4a6 100644 --- a/src/app_config/mod.rs +++ b/src/app_config/mod.rs @@ -24,6 +24,7 @@ pub(crate) struct AppConfig { pub(crate) ssh_known_host_file: String, pub(crate) user_confirmation_expiration: i64, pub(crate) db_file: String, + pub(crate) crypto_key: String, pub(crate) beggars: HashMap, // We request to unlock owned services pub(crate) providers: HashMap, // We offer to unlock these services @@ -35,6 +36,7 @@ impl Default for AppConfig { port: 8080, addresses: vec!["*".to_string()], beggars: HashMap::new(), + crypto_key: "default_key_change_me".to_string(), providers: HashMap::new(), user_confirmation_expiration: 60 * 60 * 24, // 24 hours platform: Platform::OMV, @@ -51,6 +53,7 @@ impl AppConfig { return AppConfig { port: toml_config.port.unwrap_or(default.port), addresses: toml_config.addresses.unwrap_or(default.addresses), + crypto_key: toml_config.crypto_key.unwrap_or(default.crypto_key), platform: toml_config.platform.unwrap_or(default.platform), user_confirmation_expiration: toml_config.user_confirmation_expiration.unwrap_or(default.user_confirmation_expiration), ssh_known_host_file: toml_config.ssh_known_host_file.unwrap_or(default.ssh_known_host_file), diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs new file mode 100644 index 0000000..d402ded --- /dev/null +++ b/src/crypto/mod.rs @@ -0,0 +1,54 @@ +use argon2::{ + password_hash::{ + rand_core::OsRng, + PasswordHash, PasswordHasher, PasswordVerifier, SaltString + }, + Argon2 +}; +use crate::errors::AutoDecryptError; +use base64::prelude::*; +use chacha20poly1305::{ + aead::{Aead, AeadCore, KeyInit, OsRng as AeadOsRng}, + ChaCha20Poly1305, Nonce +}; + +use crate::app_config::CONFIG; + +fn hash_hey(input: &str) -> Result<(String), AutoDecryptError> { + let argon2 = Argon2::default(); + let salt = SaltString::generate(&mut OsRng); + Ok(argon2.hash_password(input.as_bytes(), &salt).map_err( + |e| AutoDecryptError::APIError { comment: (e.to_string()) })? + .to_string()) +} + +fn derive_key(password: &str, salt: &str) -> Result<[u8; 32], AutoDecryptError> { + let mut output = [0u8; 32]; + Argon2::default().hash_password_into(password.as_bytes(), salt.as_bytes(), &mut output).map_err( + |e| AutoDecryptError::CryptoError { comment: (e.to_string()) })?; + Ok(output) +} + +fn encrypt_data(data: &str, key: &[u8; 32], salt: Option<[u8; 96]>) -> Result<(String, [u8; 96]), AutoDecryptError> { + let cipher = ChaCha20Poly1305::new(key.into()); + + let salt = match salt { + Some(s) => Nonce::clone_from_slice(&s), + None => ChaCha20Poly1305::generate_nonce(&mut AeadOsRng), + }; + let salt_arr: [u8; 96] = salt.as_slice().try_into().expect("Nonce should be 96 bytes"); + let ciphertext = cipher.encrypt(&salt, data.as_bytes()).map_err( + |e| AutoDecryptError::APIError { comment: (e.to_string()) })?; + Ok((BASE64_STANDARD.encode(ciphertext), salt_arr)) +} + +fn decrypt_data(encrypted_data: &str, key: &[u8; 32], salt: &[u8; 96]) -> Result { + let cipher = ChaCha20Poly1305::new(key.into()); + let nonce = Nonce::from_slice(salt); + let ciphertext = BASE64_STANDARD.decode(encrypted_data).map_err( + |e| AutoDecryptError::APIError { comment: (e.to_string()) })?; + let plaintext = cipher.decrypt(nonce, ciphertext.as_ref()).map_err( + |e| AutoDecryptError::CryptoError { comment: (e.to_string()) })?; + Ok(String::from_utf8(plaintext).map_err( + |e| AutoDecryptError::CryptoError { comment: (e.to_string()) })?) +} \ No newline at end of file diff --git a/src/errors.rs b/src/errors.rs index 58fb3ab..6a3afc3 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -4,4 +4,5 @@ custom_error!{pub AutoDecryptError ConfigurationError{comment:String} = "{comment}", APIError{comment:String} = "{comment}", ORMError{comment:String} = "{comment}", + CryptoError{comment:String} = "{comment}", } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 2648c71..2d8c92e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ mod services; mod app_config; mod api; +mod crypto; use clap::Parser; use crate::app_config::{init_config, CONFIG};