Skip to content

Version 2.5.0 Release #17

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/cmake-multi-platform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ jobs:
- name: Install Dependencies
if: matrix.os == 'macos-13'
run: |
export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=TRUE
brew update
brew install llvm cmake ninja gcc libgcrypt openssl@3 readline libsodium
echo 'export PATH="/usr/local/opt/llvm/bin:$PATH"' >> ~/.bash_profile
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
cmake_minimum_required(VERSION 3.28)

project(privacyShield
VERSION 2.0.0
VERSION 2.5.0
DESCRIPTION "A suite of tools for privacy and security"
LANGUAGES CXX)

Expand Down
2 changes: 1 addition & 1 deletion CMakeModules/Packing.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
set(CPACK_PACKAGE_NAME "PrivacyShield")
set(CPACK_PACKAGE_VENDOR "Ian Duncan")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "A suite of tools for privacy and security")
set(CPACK_PACKAGE_VERSION "2.0.0")
set(CPACK_PACKAGE_VERSION "2.5.0")
set(CPACK_PACKAGE_CONTACT "[email protected]")

SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/Packages")
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,15 +421,15 @@ Internet connection might be required to install the dependencies.
For instance, on Ubuntu, you can install the .deb file using the following command:

```bash
sudo dpkg -i privacyshield_2.0.0_amd64.deb # Replace with the actual file path
sudo dpkg -i privacyshield_2.5.0_amd64.deb # Replace with the actual file path
# You can also use apt to install it:
sudo apt install ./privacyshield_2.0.0_amd64.deb # Replace with the actual file path
sudo apt install ./privacyshield_2.5.0_amd64.deb # Replace with the actual file path
```

On RPM-based distributions like Fedora, you can install the .rpm file using the following command:

```bash
sudo rpm -i privacyshield-2.0.0-1.x86_64.rpm # Replace with the actual file path
sudo rpm -i privacyshield-2.5.0-1.x86_64.rpm # Replace with the actual file path
```

The packages can be verified using the [GnuPG](https://gnupg.org/) signature files provided.
Expand Down
19 changes: 7 additions & 12 deletions src/duplicateFinder/duplicateFinder.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ struct FileInfo {
/// \brief Calculates the 256-bit BLAKE3 hash of a file.
/// \param filePath path to the file.
/// \return Base64-encoded hash of the file.
/// \throws std::runtime_error if the file cannot be opened.
std::string calculateBlake3(const std::string &filePath) {
// Open the file
std::ifstream file(filePath, std::ios::binary);
Expand Down Expand Up @@ -73,7 +74,7 @@ std::string calculateBlake3(const std::string &filePath) {

/// \brief handles file i/o errors during low-level file operations.
/// \param filename path to the file on which an error occurred.
inline void handleAccessError(const std::string &filename) {
inline void handleAccessError(const std::string_view filename) {
std::string errMsg;
errMsg.reserve(50);

Expand Down Expand Up @@ -109,7 +110,7 @@ inline void handleAccessError(const std::string &filename) {
/// \brief recursively traverses a directory and collects file information.
/// \param directoryPath the directory to process.
/// \param files a vector to store the information from the files found in the directory.
void traverseDirectory(const std::string &directoryPath, std::vector<FileInfo> &files) {
void traverseDirectory(const std::string_view directoryPath, std::vector<FileInfo> &files) {
std::error_code ec;

for (const auto &entry: fs::recursive_directory_iterator(directoryPath,
Expand Down Expand Up @@ -158,11 +159,7 @@ void calculateHashes(std::vector<FileInfo> &files, const std::size_t start, cons
/// \brief finds duplicate files (by content) in a directory.
/// \param directoryPath the directory to process.
/// \return True if duplicates are found, else False.
std::size_t findDuplicates(const std::string &directoryPath) {
// Initialize libsodium if not already initialized
if (sodium_init() == -1)
throw std::runtime_error("Failed to initialize libsodium.");

std::size_t findDuplicates(const std::string_view directoryPath) {
// Collect file information
std::vector<FileInfo> files;
traverseDirectory(directoryPath, files);
Expand All @@ -187,16 +184,14 @@ std::size_t findDuplicates(const std::string &directoryPath) {
threads.emplace_back(calculateHashes, std::ref(files), start, files.size());

// Wait for all threads to finish execution
for (auto &thread: threads) {
thread.join();
}
for (auto &thread: threads) thread.join();

// A hash map to map the files to their corresponding hashes
std::unordered_map<std::string, std::vector<std::string> > hashMap;

// Iterate over files and identify duplicates
for (const auto &[filePath, hash]: files) {
for (const auto &[filePath, hash]: files)
hashMap[hash].push_back(filePath);
}

std::size_t duplicatesSet{0}, numDuplicates{0};

Expand Down
140 changes: 81 additions & 59 deletions src/encryption/encryptDecrypt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,32 +68,35 @@ class FormatFileSize {
};

/// \brief Available encryption/decryption ciphers.
enum class Algorithms : unsigned int {
AES = 1 << 0,
Camellia = 1 << 1,
Aria = 1 << 2,
Serpent = 1 << 3,
Twofish = 1 << 4
enum class Algorithms : std::uint_fast8_t {
AES = 1 << 0,
Camellia = 1 << 1,
Aria = 1 << 2,
Serpent = 1 << 3,
Twofish = 1 << 4
};

/// \brief Operation modes: encryption or decryption.
enum class OperationMode : unsigned int {
Encryption = 1,
Decryption = 2
enum class OperationMode : std::uint_fast8_t {
Encryption = 1,
Decryption = 2
};

/// \brief An anonymous struct to aid algorithm selection.
constexpr struct {
const char *const AES = "AES-256-CBC";
const char *const AES = "AES-256-CBC";
const char *const Camellia = "CAMELLIA-256-CBC";
const char *const Aria = "ARIA-256-CBC";
const char *const Aria = "ARIA-256-CBC";
const gcry_cipher_algos Serpent = GCRY_CIPHER_SERPENT256;
const gcry_cipher_algos Twofish = GCRY_CIPHER_TWOFISH;
} AlgoSelection;

/// \brief Checks for issues with the input file, that may hinder encryption/decryption.
/// \param inFile the input file, to be encrypted/decrypted.
/// \param mode the mode of operation: encryption or decryption.
/// \throws std::invalid_argument if \p mode is invalid.
/// \throws std::runtime_error if the input file does not exist, is a directory,
/// is not a regular file, or is not readable.
inline void checkInputFile(const fs::path &inFile, const OperationMode &mode) {
if (mode != OperationMode::Encryption && mode != OperationMode::Decryption)
throw std::invalid_argument("Invalid mode of operation.");
Expand All @@ -120,10 +123,12 @@ inline void checkInputFile(const fs::path &inFile, const OperationMode &mode) {
throw std::runtime_error(std::format("{} is not readable.", file));
}

/// \brief Creates the directory path for a given file path if it does not exist.
/// \brief Creates non-existing parent directories for a file.
/// \param filePath The file path for which the directory path needs to be created.
/// \return True if the directory path is created successfully or already exists, false otherwise.
inline bool createPath(const fs::path &filePath) noexcept {
if (filePath.string().empty()) return false; // Can't create empty paths

std::error_code ec;

auto absolutePath = weakly_canonical(filePath, ec);
Expand All @@ -148,49 +153,55 @@ inline bool createPath(const fs::path &filePath) noexcept {
/// \param inFile the input file, to be encrypted/decrypted.
/// \param outFile the output file, to be saved.
/// \param mode the mode of operation: encryption or decryption.
/// \throws std::invalid_argument if \p mode is invalid.
/// \throws std::runtime_error if the output file is not writable, readable, or there is not enough space to save it.
inline void checkOutputFile(const fs::path &inFile, fs::path &outFile, const OperationMode &mode) {
if (mode != OperationMode::Encryption && mode != OperationMode::Decryption)
throw std::invalid_argument("Invalid mode of operation.");

// Create parent directories, if necessary.
if (!createPath(outFile))
throw std::runtime_error("Unable to create destination directory.");
throw std::runtime_error("Unable to create destination directory. Check the path");

// Check if the output file is a directory, and rename it appropriately if so
if (is_directory(outFile)) {
if (mode == OperationMode::Encryption) {
outFile /= inFile.filename();
outFile += ".enc";
} else outFile /= inFile.extension() == ".enc" ? inFile.stem() : inFile.filename();
}
if (std::error_code ec; exists(outFile, ec)) {
// If the output file is not specified, name it appropriately
if (equivalent(fs::current_path(), outFile)) {
outFile = inFile;
if (inFile.extension() == ".enc") {
outFile.replace_extension("");
} else if (mode == OperationMode::Encryption) {
outFile += ".enc";
} else {
outFile.replace_extension("");
outFile += "_decrypted";
outFile += inFile.extension();
}
} else if (is_directory(outFile)) {
// If the output file is a directory, rename it appropriately.
if (mode == OperationMode::Encryption) {
outFile /= inFile.filename();
outFile += ".enc";
} else outFile /= inFile.extension() == ".enc" ? inFile.stem() : inFile.filename();
}
// If the output file exists, ask for confirmation for overwriting
if (exists(outFile, ec)) {
printColor(canonical(outFile).string(), 'b', false, std::cerr);
printColor(" already exists. \nDo you want to overwrite it? (y/n): ", 'r', false, std::cerr);
if (!validateYesNo())
throw std::runtime_error("Operation aborted.");

// If the output file is not specified, name it appropriately
if (outFile.string().empty()) {
outFile = inFile;
if (inFile.extension() == ".enc") {
outFile.replace_extension("");
} else if (mode == OperationMode::Encryption) {
outFile += ".enc";
} else {
outFile.replace_extension("");
outFile += "_decrypted";
outFile += inFile.extension();
// Determine if the output file can be written if it exists
if (auto file = weakly_canonical(outFile).string(); !(isWritable(file) && isReadable(file)))
throw std::runtime_error(std::format("{} is not writable/readable.", file));
}
}

// If the output file exists, ask for confirmation for overwriting
if (std::error_code ec; exists(outFile, ec)) {
printColor(canonical(outFile).string(), 'b', false, std::cerr);
printColor(" already exists. \nDo you want to overwrite it? (y/n): ", 'r', false, std::cerr);
if (!validateYesNo())
throw std::runtime_error("Operation aborted.");

// Determine if the output file can be written if it exists
if (auto file = weakly_canonical(outFile).string(); !(isWritable(file) && isReadable(file)))
throw std::runtime_error(std::format("{} is not writable/readable.", file));
}
// Check if the input and output files are the same
if (equivalent(inFile, outFile))
throw std::runtime_error("The input and output files are the same.");

// Check if there is enough space on the disk to save the output file.
const auto availableSpace = getAvailableSpace(outFile);
const auto availableSpace = getAvailableSpace(weakly_canonical(outFile));
if (const auto fileSize = file_size(inFile); std::cmp_less(availableSpace, fileSize)) {
printColor("Not enough space to save ", 'r', false, std::cerr);
printColor(weakly_canonical(outFile).string(), 'c', true, std::cerr);
Expand All @@ -210,7 +221,7 @@ inline void checkOutputFile(const fs::path &inFile, fs::path &outFile, const Ope
/// \brief Copies the last write time of a file to another.
/// \param srcFile the source file.
/// \param destFile the destination file.
inline void copyLastWrite(const std::string &srcFile, const std::string &destFile) noexcept {
inline void copyLastWrite(const std::string_view srcFile, const std::string_view destFile) noexcept {
std::error_code ec;
last_write_time(destFile, fs::last_write_time(srcFile, ec), ec);
}
Expand All @@ -222,7 +233,7 @@ inline void copyLastWrite(const std::string &srcFile, const std::string &destFil
/// \param algo the algorithm to use for encryption/decryption.
/// \param mode the mode of operation: encryption or decryption.
void fileEncryptionDecryption(const std::string &inputFileName, const std::string &outputFileName,
const privacy::string &password, const unsigned int &algo, const OperationMode &mode) {
const privacy::string &password, const Algorithms &algo, const OperationMode &mode) {
// The mode must be valid: must be either encryption or decryption
if (mode != OperationMode::Encryption && mode != OperationMode::Decryption) [[unlikely]] {
printColor("Invalid mode of operation.", 'r', true, std::cerr);
Expand All @@ -247,16 +258,23 @@ void fileEncryptionDecryption(const std::string &inputFileName, const std::strin
};

// Encrypt/decrypt with the specified algorithm
if (algo & static_cast<unsigned int>(Algorithms::AES))
encryptDecrypt(AlgoSelection.AES);
else if (algo & static_cast<unsigned int>(Algorithms::Camellia))
encryptDecrypt(AlgoSelection.Camellia);
else if (algo & static_cast<unsigned int>(Algorithms::Aria))
encryptDecrypt(AlgoSelection.Aria);
else if (algo & static_cast<unsigned int>(Algorithms::Serpent))
encryptDecryptMoreRounds(AlgoSelection.Serpent);
else if (algo & static_cast<unsigned int>(Algorithms::Twofish))
encryptDecryptMoreRounds(AlgoSelection.Twofish);
switch (algo) {
case Algorithms::Camellia:
encryptDecrypt(AlgoSelection.Camellia);
break;
case Algorithms::Aria:
encryptDecrypt(AlgoSelection.Aria);
break;
case Algorithms::Serpent:
encryptDecryptMoreRounds(AlgoSelection.Serpent);
break;
case Algorithms::Twofish:
encryptDecryptMoreRounds(AlgoSelection.Twofish);
break;
case Algorithms::AES: [[fallthrough]];
default:
encryptDecrypt(AlgoSelection.AES);
}

// If we reach here, the operation was successful
auto pre = mode == OperationMode::Encryption ? "En" : "De";
Expand Down Expand Up @@ -287,7 +305,7 @@ void encryptDecrypt() {
{5, Algorithms::Twofish}
};

const std::unordered_map<Algorithms, std::string> algoDescription = {
const std::unordered_map<Algorithms, std::string_view> algoDescription = {
{Algorithms::AES, "256-bit AES in CBC mode"},
{Algorithms::Camellia, "256-bit Camellia in CBC mode"},
{Algorithms::Aria, "256-bit Aria in CBC mode"},
Expand Down Expand Up @@ -324,13 +342,17 @@ void encryptDecrypt() {
inputFile.erase(inputFile.size() - 1);

fs::path inputPath(inputFile);
if (!inputPath.is_absolute()) // The path should be absolute
inputPath = fs::current_path() / inputPath;
checkInputFile(inputPath, static_cast<OperationMode>(choice));

printColor(std::format("Enter the path to save the {}crypted file "
"\n(or leave it blank to save it in the same directory):",
pre_l), 'c', true);

fs::path outputPath(getResponseStr());
fs::path outputPath{getResponseStr()};
if (!outputPath.is_absolute()) // If the path is not absolute
outputPath = fs::current_path() / outputPath;
checkOutputFile(inputPath, outputPath, static_cast<OperationMode>(choice));

std::cout << "Choose a cipher (All are 256-bit):\n";
Expand All @@ -349,7 +371,7 @@ void encryptDecrypt() {
continue;
}

auto it = algoChoice.find(algo);
const auto it = algoChoice.find(algo);
auto cipher = it != algoChoice.end() ? it->second : Algorithms::AES;

privacy::string password{getSensitiveInfo("Enter the password: ")};
Expand Down Expand Up @@ -380,7 +402,7 @@ void encryptDecrypt() {
printColor("...", 'g', true);

fileEncryptionDecryption(canonical(inputPath).string(), weakly_canonical(outputPath).string(),
password, static_cast<int>(cipher), static_cast<OperationMode>(choice));
password, cipher, static_cast<OperationMode>(choice));
std::cout << std::endl;
} catch (const std::exception &ex) {
printColor("Error: ", 'y', false, std::cerr);
Expand Down
Loading