From 9e9dbcc038a5f66ed6d7e7c38e19a8fca19f5685 Mon Sep 17 00:00:00 2001 From: EETagent Date: Fri, 28 Oct 2022 23:02:32 +0200 Subject: [PATCH] refactor: massive refactoring of age key encryption/decryption functions --- core/src/crypto.rs | 163 ++++++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 77 deletions(-) diff --git a/core/src/crypto.rs b/core/src/crypto.rs index 31faab4..ee15b00 100644 --- a/core/src/crypto.rs +++ b/core/src/crypto.rs @@ -2,9 +2,9 @@ use argon2::{ Argon2, PasswordHasher as ArgonPasswordHasher, PasswordVerifier as ArgonPasswordVerifier, }; use async_compat::CompatExt; -use secrecy::ExposeSecret; use futures::io::{AsyncReadExt, AsyncWriteExt}; use rand::Rng; +use secrecy::ExposeSecret; use std::iter; use std::path::Path; use std::str::FromStr; @@ -32,7 +32,7 @@ pub fn random_8_char_string() -> String { pub async fn hash_password( password_plain_text: String, -) -> Result> { +) -> Result> { let argon_config = Argon2::default(); let hash = tokio::task::spawn_blocking(move || { @@ -41,7 +41,9 @@ pub async fn hash_password( let salt_str = argon2::password_hash::SaltString::generate(rand::thread_rng()); let salt = salt_str.as_salt(); - return argon_config.hash_password(password, &salt).map(|x| x.serialize().to_string()); + return argon_config + .hash_password(password, &salt) + .map(|x| x.serialize().to_string()); }); let hash_string = hash.await??; @@ -112,71 +114,19 @@ pub async fn decrypt_password( Ok(String::from_utf8(decrypt_buffer)?) } -pub fn create_identity() -> (String, String){ +pub fn create_identity() -> (String, String) { let identity = age::x25519::Identity::generate(); // Public Key & Private Key - (identity.to_public().to_string(), identity.to_string().expose_secret().to_string()) + ( + identity.to_public().to_string(), + identity.to_string().expose_secret().to_string(), + ) } -pub async fn encrypt_password_with_recipients( - password_plain_text: &str, - recipients: Vec<&str>, -) -> Result { - let public_keys = recipients - .into_iter() - .map(|recipient| { - //TODO: No unwrap - Box::new(age::x25519::Recipient::from_str(recipient).unwrap()) as _ - }) - .collect(); - - let encryptor_option = age::Encryptor::with_recipients(public_keys); - - if let Some(encryptor) = encryptor_option { - let mut encrypt_buffer = Vec::new(); - let mut encrypt_writer = encryptor.wrap_async_output(&mut encrypt_buffer).await?; - - encrypt_writer - .write_all(password_plain_text.as_bytes()) - .await?; - - encrypt_writer.flush().await?; - - encrypt_writer.close().await?; - - Ok(base64::encode(encrypt_buffer)) - } else { - // TODO: Error handling - unreachable!("No recipients provided"); - } -} - -pub async fn decrypt_password_with_private_key( - password_encrypted: &str, - key: &str, -) -> Result> { - let encrypted = base64::decode(password_encrypted)?; - - let decryptor = match age::Decryptor::new_async(&encrypted[..]).await? { - age::Decryptor::Recipients(d) => d, - _ => unreachable!(), - }; - - let mut decrypt_buffer = Vec::new(); - let mut decrypt_writer = decryptor.decrypt_async(iter::once( - &age::x25519::Identity::from_str(key)? as &dyn age::Identity, - ))?; - - decrypt_writer.read_to_end(&mut decrypt_buffer).await?; - - Ok(String::from_utf8(decrypt_buffer)?) -} - -// TODO: Massive refactor of encrypt_file_with_recipients required -pub async fn encrypt_file_with_recipients>( - plain_file_path: P, - cipher_file_path: P, +async fn age_encrypt_with_recipients( + input_buffer: &[u8], + output_buffer: &mut W, recipients: Vec<&str>, ) -> Result<(), age::EncryptError> { let public_keys = recipients @@ -190,16 +140,11 @@ pub async fn encrypt_file_with_recipients>( let encryptor_option = age::Encryptor::with_recipients(public_keys); if let Some(encryptor) = encryptor_option { - let mut cipher_file = tokio::fs::File::create(cipher_file_path).await?; - let mut plain_file = tokio::fs::File::open(plain_file_path).await?; + let mut encrypt_writer = encryptor + .wrap_async_output(output_buffer.compat_mut()) + .await?; - let mut plain_file_contents = Vec::new(); - - tokio::io::AsyncReadExt::read_to_end(&mut plain_file, &mut plain_file_contents).await?; - - let mut encrypt_writer = encryptor.wrap_async_output(cipher_file.compat_mut()).await?; - - encrypt_writer.write_all(&plain_file_contents).await?; + encrypt_writer.write_all(input_buffer).await?; encrypt_writer.flush().await?; @@ -212,6 +157,69 @@ pub async fn encrypt_file_with_recipients>( } } +async fn age_decrypt_with_private_key( + input_buffer: R, + output_buffer: &mut Vec, + key: &str, +) -> Result<(), Box> { + let decryptor = match age::Decryptor::new_async(input_buffer.compat()).await? { + age::Decryptor::Recipients(d) => d, + _ => unreachable!(), + }; + + let mut decrypt_writer = decryptor.decrypt_async(iter::once( + &age::x25519::Identity::from_str(key)? as &dyn age::Identity, + ))?; + + decrypt_writer.read_to_end(output_buffer).await?; + + Ok(()) +} + +pub async fn encrypt_password_with_recipients( + password_plain_text: &str, + recipients: Vec<&str>, +) -> Result { + let mut encrypt_buffer = Vec::new(); + + age_encrypt_with_recipients( + password_plain_text.as_bytes(), + &mut encrypt_buffer, + recipients, + ) + .await?; + + Ok(base64::encode(encrypt_buffer)) +} + +pub async fn decrypt_password_with_private_key( + password_encrypted: &str, + key: &str, +) -> Result> { + let encrypted = base64::decode(password_encrypted)?; + + let mut decrypt_buffer = Vec::new(); + + age_decrypt_with_private_key(encrypted.as_slice(), &mut decrypt_buffer, key).await?; + + Ok(String::from_utf8(decrypt_buffer)?) +} + +pub async fn encrypt_file_with_recipients>( + plain_file_path: P, + cipher_file_path: P, + recipients: Vec<&str>, +) -> Result<(), age::EncryptError> { + let mut cipher_file = tokio::fs::File::create(cipher_file_path).await?; + let mut plain_file = tokio::fs::File::open(plain_file_path).await?; + + let mut plain_file_contents = Vec::new(); + + tokio::io::AsyncReadExt::read_to_end(&mut plain_file, &mut plain_file_contents).await?; + + age_encrypt_with_recipients(plain_file_contents.as_slice(), &mut cipher_file, recipients).await +} + #[cfg(test)] mod tests { #[test] @@ -278,7 +286,7 @@ mod tests { assert_eq!(PASSWORD, decrypted); } - + #[test] fn test_create_identity() { let identity = super::create_identity(); @@ -358,7 +366,7 @@ mod tests { async fn test_encrypt_file_with_recipients() { const PUBLIC_KEY: &str = "age1t220v5c8ye0pjx99kw8nr57y7a5qlw4ke0wchjuxnr2gcvfzt3hq7fufz0"; const PRIVATE_KEY: &str = - "AGE-SECRET-KEY-1WPDHL2FLJ23T6RK5KCX8KS8DNLX0CGXMNZG0XNUAH4QP5C8ZZ46QGD3STV"; + "AGE-SECRET-KEY-1WPDHL2FLJ23T6RK5KCX8KS8DNLX0CGXMNZG0XNUAH4QP5C8ZZ46QGD3STV"; const PASSWORD: &str = "test"; @@ -369,13 +377,14 @@ mod tests { assert_eq!(std::fs::read_to_string(&plain_file).unwrap(), PASSWORD); - super::encrypt_file_with_recipients(&plain_file, &encrypted_file, vec![PUBLIC_KEY]).await.unwrap(); - + super::encrypt_file_with_recipients(&plain_file, &encrypted_file, vec![PUBLIC_KEY]) + .await + .unwrap(); + let mut buffer = [0; 21]; std::io::Read::read(&mut encrypted_file, &mut buffer).unwrap(); assert_eq!(&buffer, b"age-encryption.org/v1"); } - }