mod keychain;
mod decrypt;
mod encrypt;
mod file_remover;
#[cfg(feature = "default")]
mod sign_falcon;
#[cfg(feature = "dilithium")]
mod sign_dilithium;
use crate::{
keychain::*,
encrypt::*,
decrypt::*,
file_remover::*,
};
pub use crate::sign_falcon::Sign;
use std::{
error::Error,
fmt::{self, Display, Formatter},
fs,
path::{Path, PathBuf},
};
use tokio::io;
use pqcrypto_traits::sign::{SignedMessage as SignedMessageSign, SecretKey as SecretKeySign, PublicKey as PublicKeySign, DetachedSignature as DetachedSignatureSign};
use hex;
use pqcrypto_kyber::kyber1024;
use indicatif::{ProgressBar, ProgressStyle};
#[cfg(feature="dilithium")]
pub use crate::sign_dilithium::SignDilithium;
#[cfg(feature="dilithium")]
pub use pqcrypto_dilithium;
#[derive(Debug)]
pub enum SigningErr {
SecretKeyMissing,
PublicKeyMissing,
SignatureVerificationFailed,
SigningMessageFailed,
IOError(io::Error),
}
impl Display for SigningErr {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
SigningErr::SecretKeyMissing => write!(f, "Secret key is missing"),
SigningErr::PublicKeyMissing => write!(f, "Public key is missing"),
SigningErr::SignatureVerificationFailed => write!(f, "Signature verification failed"),
SigningErr::SigningMessageFailed => write!(f, "Failed to sign message"),
SigningErr::IOError(err) => write!(f, "IOError occurred: {}", err),
}
}
}
impl PartialEq for SigningErr {
fn eq(&self, other: &Self) -> bool {
use SigningErr::*;
match (self, other) {
(SecretKeyMissing, SecretKeyMissing)
| (PublicKeyMissing, PublicKeyMissing)
| (SignatureVerificationFailed, SignatureVerificationFailed)
| (SigningMessageFailed, SigningMessageFailed) => true,
(IOError(_), IOError(_)) => false,
_ => false,
}
}
}
impl From<pqcrypto_traits::Error> for SigningErr {
fn from(_: pqcrypto_traits::Error) -> Self {
SigningErr::SignatureVerificationFailed
}
}
impl Error for SigningErr {}
impl From<io::Error> for SigningErr {
fn from(err: io::Error) -> Self {
SigningErr::IOError(err)
}
}
pub struct Encrypt;
pub struct Decrypt;
pub struct Keychain {
pub public_key: Option<kyber1024::PublicKey>,
pub secret_key: Option<kyber1024::SecretKey>,
pub shared_secret: Option<kyber1024::SharedSecret>,
pub ciphertext: Option<kyber1024::Ciphertext>,
}
pub struct FileRemover {
pub overwrite_times: u32,
pub file_path: PathBuf,
pub recursive: bool,
pub progress_bar: ProgressBar,
}
pub struct File {
pub path: String,
pub data: Vec<u8>,
}
enum ActionType {
FileAction,
MessageAction,
}
#[cfg(test)]
mod tests {
extern crate tempfile;
use super::*;
use crate::{
keychain::*,
encrypt::*,
decrypt::*,
file_remover::*,
SigningErr::{*, self},
Encrypt,
Decrypt,
Keychain,
FileRemover,
sign_falcon::*,
ActionType,
};
use std::{
path::{PathBuf, Path},
fs,
env::current_dir,
io::Write,
ffi::OsStr
};
use pqcrypto_kyber::kyber1024::*;
use pqcrypto_falcon::falcon1024;
use pqcrypto_traits::kem::{SharedSecret as SharedSecretTrait, SecretKey as SecretKeyTrait};
use hex;
use tempfile::{NamedTempFile, tempdir};
use pqcrypto_traits::sign::{SignedMessage as SignedMessageSign, SecretKey as SecretKeySign, PublicKey as PublicKeySign, DetachedSignature as DetachedSignatureSign};
#[cfg(feature = "dilithium")]
use crate::sign_dilithium;
#[cfg(feature = "dilithium")]
use crate::sign_dilithium::SignDilithium;
#[tokio::test]
async fn keychain_new_works() {
let keychain = Keychain::new().unwrap();
assert!(keychain.public_key.is_some());
assert!(keychain.secret_key.is_some());
assert!(keychain.shared_secret.is_some());
assert!(keychain.ciphertext.is_some());
if let (Some(ct), Some(sk)) = (&keychain.ciphertext, &keychain.secret_key) {
let ss = decapsulate(ct, sk);
assert_eq!(keychain.shared_secret.as_ref().unwrap().as_bytes(), ss.as_bytes());
}
}
#[tokio::test]
async fn generate_unique_filename_works() {
let base_path = "test_file";
let extension = "txt";
let unique_path = Keychain::generate_unique_filename(base_path, extension);
fs::write(&unique_path, "Test content").unwrap();
assert!(Path::new(&unique_path).is_file());
assert_eq!(Path::new(&unique_path).extension().unwrap().to_str().unwrap(), extension);
fs::remove_file(unique_path).unwrap();
}
#[tokio::test]
async fn generate_original_filename_works() {
let encrypt_filename1 = "./test_file.txt_1.enc";
let encrypt_filename2 = "./test_file.txt.enc";
let decrypt: Decrypt = Decrypt::new();
let encrypt: Encrypt = Encrypt::new();
let original1 = decrypt.generate_original_filename(encrypt_filename1).await;
let original2 = decrypt.generate_original_filename(encrypt_filename2).await;
assert_eq!("./test_file.txt", original1);
assert_eq!("./test_file.txt", original2);
}
#[tokio::test]
async fn generate_and_verify_hmac() {
let decrypt: Decrypt = Decrypt::new();
let encrypt: Encrypt = Encrypt::new();
let key = b"Hello, how are you?";
let data = b"Ax23526";
let hmac = Encrypt::generate_hmac(key, data);
let data_with_hmac = [data.as_ref(), hmac.as_ref()].concat();
let hmac_len = 64; let verification_result = decrypt.verify_hmac(key, &data_with_hmac, hmac_len);
assert!(verification_result.is_ok(), "HMAC verification failed");
let verified_data = verification_result.unwrap();
assert_eq!(verified_data, data, "Verified data does not match original data");
}
#[tokio::test]
async fn test_encrypt_decrypt_file() {
let decrypt: Decrypt = Decrypt::new();
let encrypt: Encrypt = Encrypt::new();
let keychain = Keychain::new().unwrap();
let message = "This is a test message.";
let message_bytes = message.as_bytes();
let dir = tempdir().unwrap();
let file_path = dir.path().join("test_message.txt");
let encrypted_file_path = dir.path().join("test_message_encrypted.txt");
let decrypted_file_path = dir.path().join("test_message_decrypted.txt");
fs::write(&file_path, message_bytes).expect("Failed to write test file");
let encrypted_data = encrypt.encrypt_file(
file_path.clone(),
keychain.shared_secret.as_ref().unwrap(),
b"hmackey"
).await.expect("Encryption failed");
assert_ne!(message_bytes, encrypted_data);
fs::write(&encrypted_file_path, &encrypted_data).expect("Failed to write encrypted file");
let decrypted_data = decrypt.decrypt_file(
&encrypted_file_path,
keychain.shared_secret.as_ref().unwrap(),
b"hmackey"
).await.expect("Decryption failed");
fs::write(&decrypted_file_path, &decrypted_data).expect("Failed to write decrypted file");
assert_eq!(message_bytes, decrypted_data);
dir.close().unwrap();
}
#[tokio::test]
async fn test_encrypt_decrypt_message() {
let decrypt: Decrypt = Decrypt::new();
let encrypt: Encrypt = Encrypt::new();
let message = "This is a secret message!";
let hmac_key = b"encryption_test_key";
let keychain = Keychain::new().unwrap();
let encrypted_message = encrypt.encrypt_msg(message, keychain.shared_secret.as_ref().unwrap(), hmac_key)
.await
.expect("Failed to encrypt message");
let decrypted_message_result = decrypt.decrypt_msg(&encrypted_message, keychain.shared_secret.as_ref().unwrap(), hmac_key, false)
.await;
assert_eq!(decrypted_message_result.unwrap(), message, "Decrypted message does not match the original message");
}
#[tokio::test]
async fn test_encrypt_decrypt() {
let keychain = Keychain::new().unwrap();
let decrypt: Decrypt = Decrypt::new();
let encrypt: Encrypt = Encrypt::new();
let pubkey = PathBuf::from("./keychain/key/key.pub");
let secret_key = PathBuf::from("./keychain/key/key.sec");
let ciphertext = PathBuf::from("./keychain/cipher/cipher.ct");
let dir = tempdir().unwrap();
let original_file_path = dir.path().join("test.txt");
let encrypted_file_path = dir.path().join("test.txt.enc");
let original_file_contents = "this is a test file";
fs::write(&original_file_path, original_file_contents).expect("Failed to write original file");
let _ = encrypt.encrypt(pubkey, &original_file_path.as_os_str().to_str().unwrap(), ActionType::FileAction, b"secret", None).await;
let _ = decrypt.decrypt(secret_key, ciphertext, encrypted_file_path.as_os_str().to_str().unwrap(), ActionType::FileAction, b"secret", None).await;
let decrypted_file_contents = fs::read_to_string(&original_file_path).expect("Failed to read decrypted file");
assert_eq!(decrypted_file_contents, original_file_contents);
dir.close().unwrap();
fs::remove_file("./keychain/cipher/cipher.ct");
}
#[tokio::test]
async fn test_file_removal() -> Result<(), Box<dyn std::error::Error>> {
let keychain = Keychain::new().unwrap();
let keychain_path = PathBuf::from("./keychain/sign");
let duplicated_path = PathBuf::from("./key_duplicate");
fs::create_dir_all(&duplicated_path)?; for entry in fs::read_dir(&keychain_path)? {
let entry = entry?;
let dest_path = duplicated_path.join(entry.file_name());
fs::copy(entry.path(), dest_path)?;
}
match FileRemover::new(5, duplicated_path.clone(), true) {
Ok(mut file_remover) => {
if let Err(e) = file_remover.delete() {
eprintln!("Error while deleting file: {}", e);
} else {
println!("File successfully deleted.");
}
}
Err(e) => eprintln!("Failed to initialize file remover: {}", e),
}
assert!(!duplicated_path.exists(), "The duplicated folder still exists after deletion");
Ok(())
}
#[tokio::test]
async fn test_sign_msg() {
let mut sign = Sign::new().unwrap();
let message = b"Test message";
let result = sign.sign_msg(message).await;
println!("{:?}", result);
assert!(result.is_ok());
assert!(!result.unwrap().is_empty());
}
#[tokio::test]
async fn test_signing_detached() {
let mut sign = Sign::new().unwrap();
let message = b"Test message";
let result = sign.signing_detached(message).await;
assert!(result.is_ok());
}
#[tokio::test]
async fn test_verify_msg() {
let mut sign = Sign::new().unwrap();
let message = b"Test message";
sign.sign_msg(message).await.unwrap();
let result = sign.verify_msg(message).await;
println!("{:?}", result);
assert!(result.is_ok());
}
#[tokio::test]
async fn test_verify_detached() {
let mut sign = Sign::new().unwrap();
let message = b"Test message";
let detached_signature = sign.signing_detached(message).await.unwrap();
let result = sign.verify_detached(message).await;
println!("{:?}", result);
assert_eq!(result, Ok(true));
}
#[tokio::test]
async fn test_sign_file() {
let mut sign = Sign::new().unwrap();
let _ = sign.save_keys("keychain", "sign").await;
let file_path = PathBuf::from("./README.md");
let sign_result = sign.sign_file(file_path.clone()).await;
assert!(sign_result.is_ok(), "Signing the file failed");
let file_content = fs::read(&file_path).expect("Failed to read the file");
let verify_result = sign.verify_detached(&file_content).await;
assert!(verify_result.is_ok(), "Signature verification failed");
assert_eq!(verify_result.unwrap(), true, "The file signature verification failed");
}
#[tokio::test]
async fn test_key_validation() {
let mut sign = Sign::new().unwrap();
let message = b"Test message for key validation";
let signature = sign.signing_detached(message).await.expect("Signing failed");
let verification_result = sign.verify_detached(message).await.expect("Verification failed");
assert!(verification_result, "Signature verification failed with the original key pair");
}
#[tokio::test]
#[cfg(feature = "dilithium")]
async fn test_sign_msg_dilithium() {
let mut sign = SignDilithium::new().unwrap();
let message = b"Test message";
let result = sign.sign_msg(message).await;
println!("{:?}", result);
assert!(result.is_ok());
assert!(!result.unwrap().is_empty());
}
#[tokio::test]
#[cfg(feature = "dilithium")]
async fn test_signing_detached_dilithium() {
let mut sign = SignDilithium::new().unwrap();
let message = b"Test message";
let result = sign.signing_detached(message).await;
assert!(result.is_ok());
}
#[tokio::test]
#[cfg(feature = "dilithium")]
async fn test_verify_msg_dilithium() {
let mut sign = SignDilithium::new().unwrap();
let message = b"Test message";
sign.sign_msg(message).await.unwrap();
let result = sign.verify_msg(message).await;
println!("{:?}", result);
assert!(result.is_ok());
}
#[tokio::test]
#[cfg(feature = "dilithium")]
async fn test_verify_detached_dilithium() {
let mut sign = SignDilithium::new().unwrap();
let message = b"Test message";
let detached_signature = sign.signing_detached(message).await.unwrap();
let result = sign.verify_detached(message).await;
println!("{:?}", result);
assert_eq!(result, Ok(true));
}
#[tokio::test]
#[cfg(feature = "dilithium")]
async fn test_sign_file_dilithium() {
let mut sign = SignDilithium::new().unwrap();
let _ = sign.save_keys("keychain", "sign");
let file_path = PathBuf::from("./README.md");
let sign_result = sign.sign_file(file_path.clone()).await;
assert!(sign_result.is_ok(), "Signing the file failed");
let file_content = fs::read(&file_path).expect("Failed to read the file");
let verify_result = sign.verify_detached(&file_content).await;
assert!(verify_result.is_ok(), "Signature verification failed");
assert_eq!(verify_result.unwrap(), true, "The file signature verification failed");
}
#[tokio::test]
#[cfg(feature = "dilithium")]
async fn test_key_validation_dilithium() {
let mut sign = SignDilithium::new().unwrap();
let message = b"Test message for key validation";
let signature = sign.signing_detached(message).await.expect("Signing failed");
let verification_result = sign.verify_detached(message).await.expect("Verification failed");
assert!(verification_result, "Signature verification failed with the original key pair");
}
#[tokio::test]
async fn test_append_extract_verify_signature() {
let keychain = Keychain::new().expect("Failed to create keychain");
let sign = Sign::new().unwrap();
let encrypt = Encrypt::new();
let decrypt = Decrypt::new();
let message = "Test message";
let secret_key = sign.secret_key().await.unwrap();
let public_key = sign.public_key().await;
let encrypted_message = encrypt.encrypt_msg(&message, &keychain.get_shared_secret().await.unwrap(), b"hmackey")
.await
.expect("Encryption failed");
let signature = Encrypt::generate_signature(&encrypted_message, *secret_key);
let signed_data = Encrypt::append_signature(&encrypted_message, signature.clone())
.expect("Failed to append signature");
let (extracted_data, extracted_signature) = Decrypt::extract_signature(&signed_data).unwrap();
let is_signature_valid = decrypt.verify_signature(extracted_signature, encrypted_message.as_slice(), &public_key.unwrap()).expect("Verification failed");
assert!(is_signature_valid, "Signature verification failed");
let decrypted_data = decrypt.decrypt_msg(&extracted_data, keychain.shared_secret.as_ref().unwrap(), b"hmackey", false).await.unwrap();
assert_eq!(message, decrypted_data, "Original message does not match decrypted message!");
assert_eq!(encrypted_message, extracted_data, "Original data does not match extracted data");
assert_eq!(&signature, DetachedSignatureSign::as_bytes(&extracted_signature), "Original signature does not match extracted signature");
}
#[tokio::test]
#[cfg(feature = "xchacha20")]
async fn test_encrypt_decrypt_message_xchacha() {
let keychain = Keychain::new().expect("Failed to create keychain");
let encrypt = Encrypt::new();
let decrypt = Decrypt::new();
let shared_secret = keychain.get_shared_secret().await.expect("Failed to get shared secret");
let nonce = generate_nonce(); let message = "This is a secret message!";
let encrypted_message = encrypt.encrypt_msg_xchacha20(message, &shared_secret, &nonce, "encryption_test_key".as_bytes())
.await
.expect("Failed to encrypt message");
let decrypted_message = decrypt.decrypt_msg_xchacha20(&encrypted_message, &shared_secret, &nonce, "encryption_test_key".as_bytes(), false)
.await
.expect("Failed to decrypt message");
assert_eq!(message, decrypted_message);
}
#[tokio::test]
#[cfg(feature = "xchacha20")]
async fn test_encrypt_decrypt_data_xchacha20() {
let keychain = Keychain::new().expect("Failed to create keychain");
let encrypt = Encrypt::new();
let decrypt = Decrypt::new();
let key = keychain.get_shared_secret().await.expect("Failed to get shared secret").as_bytes().to_owned(); let hmac_key = "encryption_test_key".as_bytes();
let data = b"Example plaintext data";
let nonce = generate_nonce(); println!("Original Data: {:?}", data);
let encrypted_data = encrypt.encrypt_data_xchacha20(data.as_ref(), &key, &nonce, &hmac_key)
.await
.expect("Encryption failed");
println!("Encrypted Data: {:?}", encrypted_data);
let hmac_len = 64; match decrypt.verify_hmac(&hmac_key, &encrypted_data, hmac_len) {
Ok(data_with_hmac) => {
let decrypted_data = decrypt.decrypt_data_xchacha20(&data_with_hmac, &nonce, &key)
.await
.expect("Decryption failed");
println!("Decrypted Data: {:?}", decrypted_data);
assert_eq!(data, &decrypted_data[..]);
},
Err(e) => panic!("HMAC verification failed: {}", e),
}
}
#[tokio::test]
#[cfg(feature = "xchacha20")]
async fn test_encrypt_decrypt_file_xchacha20() {
let decrypt: Decrypt = Decrypt::new();
let encrypt: Encrypt = Encrypt::new();
let keychain = Keychain::new().unwrap();
let nonce = generate_nonce(); let message = "This is a test message.";
let message_bytes = message.as_bytes();
let dir = tempdir().unwrap();
let file_path = dir.path().join("test_message.txt");
let encrypted_file_path = dir.path().join("test_message_encrypted.txt");
let decrypted_file_path = dir.path().join("test_message_decrypted.txt");
fs::write(&file_path, message_bytes).expect("Failed to write test file");
let encrypted_data = encrypt.encrypt_file_xchacha20(file_path.clone(), &keychain.get_shared_secret().await.unwrap(), &nonce, b"hmackeyaergfdgrfgswgs<edgsf")
.await
.expect("Encryption failed");
assert_ne!(message_bytes, encrypted_data);
fs::write(&encrypted_file_path, &encrypted_data).expect("Failed to write encrypted file");
let decrypted_data = decrypt.decrypt_file_xchacha20(&encrypted_file_path, &keychain.get_shared_secret().await.unwrap(), &nonce, b"hmackeyaergfdgrfgswgs<edgsf")
.await
.expect("Decryption failed");
assert_eq!(message.as_bytes().to_vec(), decrypted_data, "Decrypted data does not match original content");
}
#[tokio::test]
#[cfg(feature = "xchacha20")]
async fn test_encrypt_decrypt_msg_xchacha20() {
let decrypt: Decrypt = Decrypt::new();
let encrypt: Encrypt = Encrypt::new();
let keychain = Keychain::new().unwrap();
let nonce = generate_nonce(); let message = "This is a test message.";
let encrypted_data = encrypt.encrypt_msg_xchacha20(message.as_ref(), &keychain.get_shared_secret().await.unwrap(), &nonce, b"hmackeyaergfdgrfgswgs<edgsf")
.await
.expect("Encryption failed");
assert_ne!(message.as_bytes(), encrypted_data);
let decrypted_data = decrypt.decrypt_msg_xchacha20(&encrypted_data, &keychain.get_shared_secret().await.unwrap(), &nonce, b"hmackeyaergfdgrfgswgs<edgsf", false)
.await
.expect("Decryption failed");
assert_eq!(message, decrypted_data, "Decrypted data does not match original content");
}
#[tokio::test]
#[cfg(feature = "xchacha20")]
async fn test_encrypt_decrypt_xchacha20() {
let nonce = generate_nonce(); let keychain = Keychain::new().unwrap();
let decrypt: Decrypt = Decrypt::new();
let encrypt: Encrypt = Encrypt::new();
let pubkey = PathBuf::from("./keychain/key/key.pub");
let secret_key = PathBuf::from("./keychain/key/key.sec");
let ciphertext = PathBuf::from("./keychain/cipher/cipher.ct");
let dir = tempdir().unwrap();
let original_file_path = dir.path().join("test.txt");
let encrypted_file_path = dir.path().join("test.txt.enc");
let original_file_contents = "this is a test file";
fs::write(&original_file_path, original_file_contents).expect("Failed to write original file");
let _ = encrypt.encrypt(pubkey, &original_file_path.as_os_str().to_str().unwrap(), ActionType::FileAction, b"secret", Some(&nonce)).await;
let _ = decrypt.decrypt(secret_key, ciphertext, encrypted_file_path.as_os_str().to_str().unwrap(), ActionType::FileAction, b"secret", Some(&nonce)).await;
let decrypted_file_contents = fs::read_to_string(&original_file_path).expect("Failed to read decrypted file");
assert_eq!(decrypted_file_contents, original_file_contents);
dir.close().unwrap();
fs::remove_file("./keychain/cipher/cipher.ct");
}
}