diff --git a/core/Cargo.toml b/core/Cargo.toml index 9d32d30..24feda7 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -26,6 +26,7 @@ infer = "^0.9" # crypto rand = "^0.8" +aes-gcm = { version = "^0.10", features = ["std"] } argon2 = { version = "^0.4", features = ["std"] } age = { version = "^0.9", features = ["async"] } secrecy = { version = "^0.8" } diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 9c90d90..4717922 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -1,3 +1,5 @@ +use aes_gcm::aead::Aead; +use aes_gcm::{KeyInit}; use argon2::{ Argon2, PasswordHasher as ArgonPasswordHasher, PasswordVerifier as ArgonPasswordVerifier, }; @@ -74,7 +76,56 @@ pub async fn verify_password<'a>( Ok(result?) } +fn convert_key_aes256(key: &str) -> Vec { + const REQUIRED_KEY_BYTES: usize = 32; + //const REQUIRED_NONCE_BYTES: usize = 32; + + let key_len = key.as_bytes().len(); + let multiplied_key: String = key.repeat((REQUIRED_KEY_BYTES / key_len) + 1); + + let key = multiplied_key.as_bytes().to_vec(); + + key +} + pub async fn encrypt_password( + password_plain_text: String, + key: String, +) -> Result> { + let hash = tokio::task::spawn_blocking(move || { + let aes_key_nonce = convert_key_aes256(&key); + + let nonce = aes_gcm::Nonce::from_slice(&aes_key_nonce[..12]); + + let cipher = aes_gcm::Aes256Gcm::new_from_slice(&aes_key_nonce[..32]).unwrap(); + + let res = cipher.encrypt(nonce, password_plain_text.as_bytes()); + res + }) + .await??; + + Ok(base64::encode(hash)) +} + +pub async fn decrypt_password(password_cipher_text: String, key: String) -> Result> { + let input = base64::decode(password_cipher_text).unwrap(); + let plain = tokio::task::spawn_blocking(move || { + let aes_key_nonce = convert_key_aes256(&key); + + let nonce = aes_gcm::Nonce::from_slice(&aes_key_nonce[..12]); + let cipher = aes_gcm::Aes256Gcm::new_from_slice(&aes_key_nonce[..32]).unwrap(); + + let res = cipher.decrypt(nonce, &*input); + + res + }).await??; + + Ok(String::from_utf8(plain).unwrap()) + +} + +#[deprecated(note = "Too slow, use AES instead")] +pub async fn encrypt_password_age( password_plain_text: &str, key: &str, ) -> Result { @@ -94,7 +145,8 @@ pub async fn encrypt_password( Ok(base64::encode(encrypt_buffer)) } -pub async fn decrypt_password( +#[deprecated(note = "Too slow, use AES instead")] +pub async fn decrypt_password_age( password_encrypted: &str, key: &str, ) -> Result> { @@ -297,12 +349,21 @@ mod tests { assert!(result); } + #[test] + fn test_convert_key_aes256() { + const KEY: &str = "test"; + + let key = super::convert_key_aes256(KEY); + + assert!(key.len() >= 32); + } + #[tokio::test] async fn test_encrypt_password_is_valid_base64() { const PASSWORD: &str = "test"; - const KEY: &str = "test"; + const KEY: &str = "testtesttesttesttesttest"; - let encrypted = super::encrypt_password(PASSWORD, KEY).await.unwrap(); + let encrypted = super::encrypt_password(PASSWORD.to_string(), KEY.to_string()).await.unwrap(); assert!(base64::decode(encrypted).is_ok()); } @@ -312,9 +373,33 @@ mod tests { const PASSWORD: &str = "test"; const KEY: &str = "test"; - let encrypted = super::encrypt_password(PASSWORD, KEY).await.unwrap(); + let encrypted = super::encrypt_password(PASSWORD.to_string(), KEY.to_string()).await.unwrap(); - let decrypted = super::decrypt_password(&encrypted, KEY).await.unwrap(); + let decrypted = super::decrypt_password(encrypted, KEY.to_string()).await.unwrap(); + + assert_eq!(PASSWORD, decrypted); + } + + #[tokio::test] + async fn test_encrypt_password_age_is_valid_base64() { + const PASSWORD: &str = "test"; + const KEY: &str = "testtesttesttesttesttest"; + + #[allow(deprecated)] + let encrypted = super::encrypt_password_age(PASSWORD, KEY).await.unwrap(); + + assert!(base64::decode(encrypted).is_ok()); + } + + #[tokio::test] + async fn test_encrypt_decrypt_age_password() { + const PASSWORD: &str = "test"; + const KEY: &str = "test"; + + #[allow(deprecated)] + let encrypted = super::encrypt_password_age(PASSWORD, KEY).await.unwrap(); + #[allow(deprecated)] + let decrypted = super::decrypt_password_age(&encrypted, KEY).await.unwrap(); assert_eq!(PASSWORD, decrypted); } @@ -404,30 +489,52 @@ mod tests { let mut plain_file = async_tempfile::TempFile::new().await.unwrap(); let mut encrypted_file = async_tempfile::TempFile::new().await.unwrap(); - tokio::io::AsyncWriteExt::write_all(&mut plain_file, PASSWORD.as_bytes()).await.unwrap(); - encrypted_file.sync_all().await.unwrap(); - - assert_eq!(tokio::fs::read_to_string(&plain_file.file_path()).await.unwrap(), PASSWORD); - - super::encrypt_file_with_recipients(&plain_file.file_path(), &encrypted_file.file_path(), vec![PUBLIC_KEY]) + tokio::io::AsyncWriteExt::write_all(&mut plain_file, PASSWORD.as_bytes()) .await .unwrap(); encrypted_file.sync_all().await.unwrap(); + assert_eq!( + tokio::fs::read_to_string(&plain_file.file_path()) + .await + .unwrap(), + PASSWORD + ); + + super::encrypt_file_with_recipients( + &plain_file.file_path(), + &encrypted_file.file_path(), + vec![PUBLIC_KEY], + ) + .await + .unwrap(); + encrypted_file.sync_all().await.unwrap(); + let mut buffer = [0; 21]; - tokio::io::AsyncReadExt::read(&mut encrypted_file, &mut buffer).await.unwrap(); + tokio::io::AsyncReadExt::read(&mut encrypted_file, &mut buffer) + .await + .unwrap(); assert_eq!(&buffer, b"age-encryption.org/v1"); let decrypted_file = async_tempfile::TempFile::new().await.unwrap(); - super::decrypt_file_with_private_key(&encrypted_file.file_path(), &decrypted_file.file_path(), PRIVATE_KEY) - .await - .unwrap(); + super::decrypt_file_with_private_key( + &encrypted_file.file_path(), + &decrypted_file.file_path(), + PRIVATE_KEY, + ) + .await + .unwrap(); decrypted_file.sync_all().await.unwrap(); - assert_eq!(tokio::fs::read_to_string(&decrypted_file.file_path()).await.unwrap(), PASSWORD); + assert_eq!( + tokio::fs::read_to_string(&decrypted_file.file_path()) + .await + .unwrap(), + PASSWORD + ); } #[tokio::test] @@ -441,15 +548,21 @@ mod tests { let mut plain_file = async_tempfile::TempFile::new().await.unwrap(); let encrypted_file = async_tempfile::TempFile::new().await.unwrap(); - tokio::io::AsyncWriteExt::write_all(&mut plain_file, PASSWORD.as_bytes()).await.unwrap(); + tokio::io::AsyncWriteExt::write_all(&mut plain_file, PASSWORD.as_bytes()) + .await + .unwrap(); let plain_buffer = tokio::fs::read(&plain_file.file_path()).await.unwrap(); assert_eq!(String::from_utf8(plain_buffer.clone()).unwrap(), PASSWORD); - super::encrypt_file_with_recipients(&plain_file.file_path(), &encrypted_file.file_path(), vec![PUBLIC_KEY]) - .await - .unwrap(); + super::encrypt_file_with_recipients( + &plain_file.file_path(), + &encrypted_file.file_path(), + vec![PUBLIC_KEY], + ) + .await + .unwrap(); let decrypted_buffer = super::decrypt_file_with_private_key_as_buffer(encrypted_file.file_path(), PRIVATE_KEY) @@ -463,4 +576,4 @@ mod tests { PASSWORD ); } -} \ No newline at end of file +} diff --git a/core/src/mutation.rs b/core/src/mutation.rs index 7e88f47..d127342 100644 --- a/core/src/mutation.rs +++ b/core/src/mutation.rs @@ -14,7 +14,7 @@ impl Mutation { // TODO: unwrap pro testing.. let hashed_password = hash_password(plain_text_password.to_string()).await.unwrap(); let (pubkey, priv_key_plain_text) = crypto::create_identity(); - let encrypted_priv_key = crypto::encrypt_password(&priv_key_plain_text, &plain_text_password.to_string()).await.unwrap(); + let encrypted_priv_key = crypto::encrypt_password_age(&priv_key_plain_text, &plain_text_password.to_string()).await.unwrap(); candidate::ActiveModel { @@ -101,7 +101,7 @@ mod tests { let encrypted_message = crypto::encrypt_password_with_recipients(&secret_message, vec![&candidate.public_key]).await.unwrap(); - let private_key_plain_text = crypto::decrypt_password(&candidate.private_key, &plain_text_password).await.unwrap(); + let private_key_plain_text = crypto::decrypt_password_age(&candidate.private_key, &plain_text_password).await.unwrap(); let decrypted_message = crypto::decrypt_password_with_private_key(&encrypted_message, &private_key_plain_text).await.unwrap();