diff --git a/Cargo.lock b/Cargo.lock index 1f85cb3..4e5fdac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -674,6 +674,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + [[package]] name = "chacha20" version = "0.10.1" @@ -685,6 +696,19 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20 0.9.1", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "cipher" version = "0.4.4" @@ -693,6 +717,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common 0.1.7", "inout", + "zeroize", ] [[package]] @@ -704,6 +729,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "combine" version = "4.6.7" @@ -861,7 +892,9 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" dependencies = [ + "getrandom 0.4.3", "hybrid-array", + "rand_core 0.10.1", ] [[package]] @@ -873,6 +906,42 @@ dependencies = [ "cipher", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dashmap" version = "6.2.1" @@ -910,6 +979,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fd89660b2dc699704064e59e9dba0147b903e85319429e131620d022be411b" +dependencies = [ + "const-oid 0.10.2", + "zeroize", +] + [[package]] name = "der-parser" version = "10.0.0" @@ -985,6 +1064,7 @@ dependencies = [ "block-buffer 0.12.1", "const-oid 0.10.2", "crypto-common 0.2.2", + "ctutils", ] [[package]] @@ -1016,13 +1096,37 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "signature 2.2.0", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "ed448-goldilocks" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" dependencies = [ - "fiat-crypto", + "fiat-crypto 0.1.20", "hex", "subtle", ] @@ -1120,6 +1224,12 @@ version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1451,7 +1561,16 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", +] + +[[package]] +name = "hkdf" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa26c720c68b866f2c96ef5c1264b3e6f473fe5d4ce61cd44bbe913e553018" +dependencies = [ + "hmac 0.13.0", ] [[package]] @@ -1463,6 +1582,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "home" version = "0.5.12" @@ -1540,6 +1668,7 @@ version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" dependencies = [ + "ctutils", "typenum", ] @@ -1846,6 +1975,25 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "keccak" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e24a010dd405bd7ed803e5253182815b41bf2e6a80cc3bfc066658e03a198aa" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", +] + [[package]] name = "kv-log-macro" version = "1.0.7" @@ -2029,16 +2177,84 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ml-dsa" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "add6b9d92e496f16f4526d68ff29da1483aba4b119baeab8bed3b9e3544a6f3d" +dependencies = [ + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", + "hybrid-array", + "module-lattice", + "pkcs8 0.11.0", + "shake", + "signature 3.0.0", +] + +[[package]] +name = "mlkem-rs" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b0965b8b081668ff0398dc5e9dc3f2ebb9e833393f4ab5b9f725ddce11acef8" +dependencies = [ + "rand_core 0.6.4", + "serde", + "sha3", + "subtle", + "zeroize", +] + +[[package]] +name = "mlkem-tls" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77b205d031298adf904d88efd6a57862d8650a4ab754aade19a9b5e87040bf4e" +dependencies = [ + "mlkem-rs", + "rand_core 0.6.4", + "subtle", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "module-lattice" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c61b87c9683ab7cb1c6871d261ad5479b6b10ceb52c4352aaca3b5d35a8febe" +dependencies = [ + "ctutils", + "hybrid-array", + "num-traits", +] + [[package]] name = "mtp" version = "0.1.0" source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "mtp-client", + "mtp-codec", + "mtp-common", + "mtp-crypto", + "mtp-host", + "mtp-transport", + "mtp-type-map", +] + +[[package]] +name = "mtp-client" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" dependencies = [ "mtp-codec", "mtp-common", - "mtp-host", + "mtp-crypto", "mtp-transport", - "mtp-type-map", + "rand 0.8.6", + "tokio", ] [[package]] @@ -2049,6 +2265,7 @@ dependencies = [ "base64", "byteorder", "mtp-common", + "mtp-crypto", "mtp-type-map", "rand 0.8.6", ] @@ -2064,6 +2281,23 @@ dependencies = [ "wtransport", ] +[[package]] +name = "mtp-crypto" +version = "0.1.0" +source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a437d863a7a35426c" +dependencies = [ + "chacha20poly1305", + "ed25519-dalek", + "getrandom 0.4.3", + "hkdf 0.13.0", + "ml-dsa", + "mlkem-tls", + "rand_core 0.6.4", + "serde", + "sha2 0.11.0", + "zeroize", +] + [[package]] name = "mtp-host" version = "0.1.0" @@ -2071,6 +2305,7 @@ source = "git+https://git.methanium.net/methanium/mtp#41528ef7178982e70cb5789a43 dependencies = [ "mtp-codec", "mtp-common", + "mtp-crypto", "mtp-transport", "rand 0.8.6", "tokio", @@ -2198,9 +2433,10 @@ dependencies = [ "dashmap", "dotenv", "hex", - "hkdf", + "hkdf 0.12.4", "json", "mtp", + "mtp-crypto", "once_cell", "rand 0.8.6", "rand_core 0.6.4", @@ -2272,7 +2508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -2329,9 +2565,9 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ - "der", - "pkcs8", - "spki", + "der 0.7.10", + "pkcs8 0.10.2", + "spki 0.7.3", ] [[package]] @@ -2340,8 +2576,18 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ - "der", - "spki", + "der 0.7.10", + "spki 0.7.3", +] + +[[package]] +name = "pkcs8" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451913da69c775a56034ea8d9003d27ee8948e12443eae7c038ba100a4f21cb7" +dependencies = [ + "der 0.8.0", + "spki 0.8.0", ] [[package]] @@ -2386,6 +2632,17 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "polyval" version = "0.6.2" @@ -2541,7 +2798,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" dependencies = [ - "chacha20", + "chacha20 0.10.1", "getrandom 0.4.3", "rand_core 0.10.1", ] @@ -2726,10 +2983,10 @@ dependencies = [ "num-integer", "num-traits", "pkcs1", - "pkcs8", + "pkcs8 0.10.2", "rand_core 0.6.4", - "signature", - "spki", + "signature 2.2.0", + "spki 0.7.3", "subtle", "zeroize", ] @@ -3048,6 +3305,27 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", +] + +[[package]] +name = "shake" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09057cb2149ad4cbd2da1e26b351f9a4c354219421229c69c3063e6f61947c4a" +dependencies = [ + "digest 0.11.3", + "keccak 0.2.0", + "sponge-cursor", +] + [[package]] name = "shlex" version = "2.0.1" @@ -3074,6 +3352,16 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "signature" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d567dcbaf0049cb8ac2608a76cd95ff9e4412e1899d389ee400918ca7537f5" +dependencies = [ + "digest 0.11.3", + "rand_core 0.10.1", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -3157,9 +3445,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", - "der", + "der 0.7.10", ] +[[package]] +name = "spki" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9efca8738c78ee9484207732f728b1ef517bbb1833d6fc0879ca898a522f6f" +dependencies = [ + "base64ct", + "der 0.8.0", +] + +[[package]] +name = "sponge-cursor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a0219bd7d979d58245a4f41f695e1ac9f8befdffadd7f61f1bae9e39abc6620" + [[package]] name = "sqlx" version = "0.8.6" @@ -3266,8 +3570,8 @@ dependencies = [ "futures-util", "generic-array", "hex", - "hkdf", - "hmac", + "hkdf 0.12.4", + "hmac 0.12.1", "itoa", "log", "md-5", @@ -3304,8 +3608,8 @@ dependencies = [ "futures-core", "futures-util", "hex", - "hkdf", - "hmac", + "hkdf 0.12.4", + "hmac 0.12.1", "home", "itoa", "log", @@ -4246,6 +4550,18 @@ dependencies = [ "url", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "zeroize", +] + [[package]] name = "x448" version = "0.6.0" @@ -4416,7 +4732,7 @@ dependencies = [ "deflate64", "flate2", "getrandom 0.3.4", - "hmac", + "hmac 0.12.1", "indexmap", "lzma-rust2", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 5d4e97e..f227b9e 100755 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,10 @@ version = "0.1.0" edition = "2024" [dependencies] -mtp = { git = "https://git.methanium.net/methanium/mtp", features = ["host"] } +mtp = { git = "https://git.methanium.net/methanium/mtp", features = [ + "host", + "crypto", +] } actix-web = { version = "4.12.1", features = ["rustls-0_23"] } aes-gcm = "*" @@ -36,3 +39,4 @@ uuid = { version = "1.19.0", features = ["v4"] } x448 = "0.6.0" zip = "6.0.0" thiserror = "2.0.18" +mtp-crypto = { git = "https://git.methanium.net/methanium/mtp", version = "0.1.0", features = ["pqc"] } diff --git a/src/main.rs b/src/main.rs index 40bf10a..9429e59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,24 +8,30 @@ use crate::notifications::tauri; use crate::sql::sql::initialize_db; use crate::sql::sql::print_users; use crate::transport::omikron_connection; -use crate::util::crypto_helper::load_public_key; -use crate::util::crypto_helper::load_secret_key; use crate::util::file_util::get_directory; use crate::util::logger::PrintType; use crate::util::logger::startup; +use base64::Engine as _; use dotenv::from_path; +use mtp_crypto::{Keyring, PublicKeyBundle}; use once_cell::sync::Lazy; use rustls::crypto::aws_lc_rs::default_provider; use std::env; use std::path::Path; -static PRIVATE_KEY: Lazy = Lazy::new(|| env::var("PRIVATE_KEY").unwrap()); -pub fn get_private_key() -> x448::Secret { - load_secret_key(&*PRIVATE_KEY).unwrap() +static KEYRING_ENV: Lazy = Lazy::new(|| env::var("KEYRING").unwrap()); +fn load_keyring() -> Keyring { + let bytes = base64::engine::general_purpose::STANDARD + .decode(&*KEYRING_ENV) + .expect("Invalid KEYRING env var: not valid base64"); + Keyring::from_bytes(&bytes).expect("Invalid KEYRING env var: failed to deserialize") } -static PUBLIC_KEY: Lazy = Lazy::new(|| env::var("PUBLIC_KEY").unwrap()); -pub fn get_public_key() -> x448::PublicKey { - load_public_key(&*PUBLIC_KEY).unwrap() +pub fn get_keyring() -> &'static Keyring { + static KEYRING: Lazy = Lazy::new(load_keyring); + &KEYRING +} +pub fn get_public_key_bundle() -> PublicKeyBundle { + get_keyring().public_key_bundle() } #[tokio::main] diff --git a/src/server/api.rs b/src/server/api.rs index eb6f9cf..c9a1c59 100644 --- a/src/server/api.rs +++ b/src/server/api.rs @@ -1,16 +1,16 @@ -use crate::get_public_key; +use crate::get_public_key_bundle; use crate::sql::sql; use crate::sql::user_online_tracker::get_iota_primary_omikron_connection; use crate::transport::omikron_manager::get_random_omikron; use crate::util::file_util::get_directory; use crate::{ sql::sql::{get_by_user_id, get_omikron_by_id}, - util::crypto_helper::public_key_to_base64, }; use actix_web::HttpResponse; use actix_web::http::{StatusCode, header}; use base64::Engine as _; use json::JsonValue; +use mtp_crypto::PublicKeyBundle; pub async fn handle(path: &str, body_string: Option) -> HttpResponse { if path == "OPTIONS" { @@ -195,7 +195,8 @@ pub async fn handle(path: &str, body_string: Option) -> HttpResponse { ["api", "get", "public_key"] => { let mut res = JsonValue::new_object(); res["status"] = "success".into(); - res["public_key"] = public_key_to_base64(&get_public_key()).into(); + let bundle = get_public_key_bundle(); + res["public_key"] = base64::engine::general_purpose::STANDARD.encode(bundle.to_bytes()).into(); (StatusCode::OK, res.dump()) } diff --git a/src/util/crypto_helper.rs b/src/util/crypto_helper.rs deleted file mode 100644 index 68e3855..0000000 --- a/src/util/crypto_helper.rs +++ /dev/null @@ -1,137 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, OsRng}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD}; -use rand_core::RngCore; -use sha2::{Digest, Sha256}; -use x448::{PublicKey, Secret, SharedSecret}; - -/// Errors for crypto operations -#[derive(Debug)] -pub enum CryptoError { - Base64Decode, - AgreementError, - EncryptionError(aes_gcm::Error), - DecryptionError(aes_gcm::Error), -} - -impl From for CryptoError { - fn from(_: base64::DecodeError) -> Self { - CryptoError::Base64Decode - } -} - -pub fn generate_keypair() -> (Secret, PublicKey) { - let mut buf = [0u8; 56]; - let mut rng = OsRng; - rng.fill_bytes(&mut buf); - let secret = Secret::from_bytes(&buf).unwrap(); - let public = PublicKey::from(&secret); - (secret, public) -} - -pub fn public_key_to_base64(pubkey: &PublicKey) -> String { - STANDARD.encode(pubkey.as_bytes().as_ref()) -} - -pub fn secret_key_to_base64(secret: &Secret) -> String { - STANDARD.encode(secret.as_bytes().as_ref()) -} - -pub fn load_public_key(base64_pub: &str) -> Option { - let bytes = STANDARD.decode(base64_pub).unwrap(); - PublicKey::from_bytes(&bytes) -} - -pub fn load_secret_key(base64_secret: &str) -> Option { - let bytes = STANDARD.decode(base64_secret).unwrap(); - Secret::from_bytes(&bytes) -} - -fn derive_aes_key(shared: &SharedSecret) -> [u8; 32] { - let mut hasher = Sha256::new(); - hasher.update(shared.as_bytes()); - let result = hasher.finalize(); - let mut key = [0u8; 32]; - key.copy_from_slice(&result[..32]); - key -} - -pub fn encrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - plaintext: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - encrypt(secret, peer_pub, plaintext) -} -pub fn encrypt( - secret: Secret, - peer_pub: PublicKey, - plaintext: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - let mut nonce_bytes = [0u8; 12]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = Nonce::from_slice(&nonce_bytes); - let ciphertext = cipher - .encrypt(nonce, plaintext.as_bytes()) - .map_err(CryptoError::EncryptionError)?; - // prefix nonce to ciphertext - let mut out = Vec::with_capacity(nonce_bytes.len() + ciphertext.len()); - out.extend_from_slice(&nonce_bytes); - out.extend_from_slice(&ciphertext); - Ok(STANDARD.encode(&out)) -} - -pub fn decrypt_b64( - base64_secret: &str, - base64_peer_pub: &str, - encrypted_base64: &str, -) -> Result { - let secret = load_secret_key(base64_secret).unwrap(); - let peer_pub = load_public_key(base64_peer_pub).unwrap(); - decrypt(secret, peer_pub, encrypted_base64) -} -pub fn decrypt( - secret: Secret, - peer_pub: PublicKey, - encrypted_base64: &str, -) -> Result { - let shared = secret - .to_diffie_hellman(&peer_pub) - .ok_or(CryptoError::AgreementError)?; - let key_bytes = derive_aes_key(&shared); - let cipher = Aes256Gcm::new_from_slice(&key_bytes).expect("Key length should be correct"); - - let encrypted = STANDARD.decode(encrypted_base64)?; - if encrypted.len() < 12 { - return Err(CryptoError::DecryptionError(aes_gcm::Error)); - } - let nonce_bytes = &encrypted[..12]; - let ciphertext = &encrypted[12..]; - let nonce = Nonce::from_slice(nonce_bytes); - let plaintext_bytes = cipher - .decrypt(nonce, ciphertext) - .map_err(CryptoError::DecryptionError)?; - let plaintext = String::from_utf8(plaintext_bytes) - .map_err(|_| CryptoError::DecryptionError(aes_gcm::Error))?; - Ok(plaintext) -} - -pub fn hash_it(input: &str) -> Vec { - let mut hasher = Sha256::new(); - hasher.update(input.as_bytes()); - hasher.finalize().to_vec() -} - -pub fn hex_hash(input: &str) -> String { - let digest = hash_it(input); - digest.iter().map(|b| format!("{:02x}", b)).collect() -} diff --git a/src/util/crypto_util.rs b/src/util/crypto_util.rs deleted file mode 100644 index 97acab4..0000000 --- a/src/util/crypto_util.rs +++ /dev/null @@ -1,201 +0,0 @@ -use aes_gcm::{ - Aes256Gcm, Nonce, - aead::{Aead, KeyInit, Payload}, -}; -use base64::{Engine as _, engine::general_purpose::STANDARD as BASE64_STD}; -use hkdf::Hkdf; -use sha2::{Digest, Sha256}; -use x448::{PublicKey, Secret}; - -// --- Custom Errors --- -#[derive(Debug)] -pub enum SecurePayloadError { - InvalidBase64, - InvalidHex, - EncryptionError, - DecryptionError, - InvalidKeyLength, -} - -// --- Data Format Enum --- -#[derive(Clone, Copy, Debug)] -pub enum DataFormat { - Raw, - Base64, - Hex, -} - -// --- Main Class Structure --- -pub struct SecurePayload { - /// The internal canonical representation is always raw bytes. - inner_data: Vec, - /// The private key of the user associated with this payload instance. - private_key: Secret, -} - -impl Clone for SecurePayload { - fn clone(&self) -> Self { - Self { - inner_data: self.inner_data.clone(), - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - } - } -} - -impl SecurePayload { - /// Clear Constructor: Takes data in any format and the user's private key. - pub fn new>( - data: T, - format: DataFormat, - private_key: S, - ) -> Result - where - S: Into, - { - let raw_data = match format { - DataFormat::Raw => data.as_ref().to_vec(), - DataFormat::Base64 => BASE64_STD - .decode(data.as_ref()) - .map_err(|_| SecurePayloadError::InvalidBase64)?, - DataFormat::Hex => { - hex::decode(data.as_ref()).map_err(|_| SecurePayloadError::InvalidHex)? - } - }; - - Ok(Self { - inner_data: raw_data, - private_key: private_key.into(), - }) - } - - /// Helper to get the public key associated with this instance's private key. - pub fn get_public_key(&self) -> [u8; 56] { - *PublicKey::from(&self.private_key).as_bytes() - } - - /// Exports the internal data to the requested format - pub fn export(&self, format: DataFormat) -> String { - match format.into() { - DataFormat::Raw => String::from_utf8_lossy(&self.inner_data).to_string(), - DataFormat::Base64 => BASE64_STD.encode(&self.inner_data), - DataFormat::Hex => hex::encode(&self.inner_data), - } - } - - /// Access raw bytes directly - pub fn get_bytes(&self) -> &[u8] { - &self.inner_data - } - - /// Returns the SHA-256 Hash of the data in the requested format - pub fn get_hash(&self, format: DataFormat) -> String { - let mut hasher = Sha256::new(); - hasher.update(&self.inner_data); - let result = hasher.finalize(); - - match format { - DataFormat::Raw => String::from_utf8_lossy(&result).to_string(), - DataFormat::Base64 => BASE64_STD.encode(result), - DataFormat::Hex => hex::encode(result), - } - } - - /// Encrypts the held data for a specific recipient using AES-256-GCM. - /// The message will contain ONLY the ciphertext. - pub fn encrypt_x448(&self, public_key: S) -> Result - where - S: Into, - { - let peer_pub = public_key.into(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - println!( - "Encryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); - - // 3. Key & Nonce Derivation (HKDF) - // We derive 32 bytes for the key and 12 bytes for a deterministic nonce. - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; // 32 (Key) + 12 (Nonce) - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - // 4. Encrypt with AES-256-GCM - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let ciphertext = cipher - .encrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::EncryptionError)?; - - // 5. Result is ONLY the ciphertext. No key or nonce is packed. - Ok(SecurePayload { - inner_data: ciphertext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } - - /// Decrypts the held data providing the sender's public key manually. - pub fn decrypt_to_format( - &self, - peer_public_key_bytes: &[u8; 56], - output_format: DataFormat, - ) -> Result { - let decrypted_instance = self.decrypt_x448(peer_public_key_bytes)?; - Ok(decrypted_instance.export(output_format)) - } - - /// Decrypts the held data using the internal Private Key and the provided Peer Public Key. - pub fn decrypt_x448( - &self, - peer_public_key_bytes: &[u8; 56], - ) -> Result { - // 1. Perform Exchange - let peer_pub = PublicKey::from_bytes(peer_public_key_bytes).unwrap(); - let shared_secret = self.private_key.as_diffie_hellman(&peer_pub).unwrap(); - - // LOGGING: Shared Secret - println!( - "Decryption Shared Secret (Hex): {}", - hex::encode(shared_secret.as_bytes()) - ); - - // 2. Key & Nonce Derivation (Must match encryption exactly) - let hkdf = Hkdf::::new(None, shared_secret.as_bytes()); - let mut okm = [0u8; 44]; - hkdf.expand(b"x448-aes-gcm-no-overhead", &mut okm) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - let key = &okm[..32]; - let nonce_bytes = &okm[32..]; - - // 3. Decrypt with AES-256-GCM - let cipher = Aes256Gcm::new(key.into()); - let nonce = Nonce::from_slice(nonce_bytes); - - let plaintext = cipher - .decrypt( - nonce, - Payload { - msg: &self.inner_data, - aad: &[], - }, - ) - .map_err(|_| SecurePayloadError::DecryptionError)?; - - Ok(SecurePayload { - inner_data: plaintext, - private_key: Secret::from_bytes(self.private_key.as_bytes()).unwrap(), - }) - } -} diff --git a/src/util/mod.rs b/src/util/mod.rs index 1897223..d0cc1dd 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,4 +1,2 @@ -pub mod crypto_helper; -pub mod crypto_util; pub mod file_util; pub mod logger;