From a93b3b3fe58464dc85fe4b07b97ff1c3f6d05d59 Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Tue, 31 May 2022 21:40:13 +0200 Subject: [PATCH] Sophisticated and bug-fixed DataOutput/InputLayer, and added ModuleDecryption --- GCryptCLI/include/DataIngestionLayer.h | 8 ++ GCryptCLI/include/DataOutputLayer.h | 4 + GCryptCLI/include/ModuleDecryption.h | 18 +++++ GCryptCLI/src/DataFormatter.cpp | 6 +- GCryptCLI/src/DataIngestionLayer.cpp | 108 ++++++++++++++++++++----- GCryptCLI/src/DataOutputLayer.cpp | 37 ++++++--- GCryptCLI/src/ModuleDecryption.cpp | 56 +++++++++++++ GCryptCLI/src/ModuleEncryption.cpp | 11 ++- GCryptCLI/src/main.cpp | 5 ++ 9 files changed, 214 insertions(+), 39 deletions(-) create mode 100644 GCryptCLI/include/ModuleDecryption.h create mode 100644 GCryptCLI/src/ModuleDecryption.cpp diff --git a/GCryptCLI/include/DataIngestionLayer.h b/GCryptCLI/include/DataIngestionLayer.h index d0510a7..871688f 100644 --- a/GCryptCLI/include/DataIngestionLayer.h +++ b/GCryptCLI/include/DataIngestionLayer.h @@ -38,6 +38,11 @@ namespace IO { private: static std::istream* in; + // Will read n bytes from the input. + // If EOF is reached, it will return a string of length <= 5 + // and will set the approriate flags. + static std::string ReadBytes(const std::size_t n, std::size_t& out_bytes_read); + // We have to hold on to a reference to a filestream, // even if we're always just reading from in. // We still have to CLOSE the file handle afterwards! @@ -49,6 +54,9 @@ namespace IO { // Indicates whether this class has been initialized static bool initialized; + // Are we reading ciphertext or regular text? + static bool isReadingCiphertext; + // All read blocks, that haven't been given out yet static std::queue blocks; diff --git a/GCryptCLI/include/DataOutputLayer.h b/GCryptCLI/include/DataOutputLayer.h index 4e6e6c8..9b80679 100644 --- a/GCryptCLI/include/DataOutputLayer.h +++ b/GCryptCLI/include/DataOutputLayer.h @@ -32,6 +32,10 @@ namespace IO { static bool IsFinished(); private: + //! If we are finished, and are outputting to stdout, + //! and the user didn't specifically opt out, print a newline + static void AddTrailingLinebreakIfRequired(); + static std::ostream* out; // We have to hold on to a reference to a filestream, diff --git a/GCryptCLI/include/ModuleDecryption.h b/GCryptCLI/include/ModuleDecryption.h new file mode 100644 index 0000000..79d909f --- /dev/null +++ b/GCryptCLI/include/ModuleDecryption.h @@ -0,0 +1,18 @@ +#ifndef GCRYPTCLI_MODULE_DECRYPTION_H +#define GCRYPTCLI_MODULE_DECRYPTION_H + +namespace Module { + //! This module will decrypt supplied input + class Decryption { + public: + //! Will run the module + static void Run(); + + private: + // No instanciation! >:( + Decryption() {}; + }; +} + +#endif + diff --git a/GCryptCLI/src/DataFormatter.cpp b/GCryptCLI/src/DataFormatter.cpp index 3164a89..d7be9d1 100644 --- a/GCryptCLI/src/DataFormatter.cpp +++ b/GCryptCLI/src/DataFormatter.cpp @@ -171,16 +171,14 @@ std::vector DataFormatter::DecodeFormatMultiblock( // A block is this many digits wide, in encoding const std::size_t blockWidth = blockLengthByBase[base]; - //std::cout << "blockWidth is: " << blockWidth << std::endl; // How many blocks are we dealing with here? const std::size_t n_blocks = (str.length() / blockWidth) + 1; blocks.reserve(n_blocks); - //std::cout << "n_blocks is: " << n_blocks << std::endl; // Iterate over the string, and parse all blocks - // We now have to differentiate between single-char digit sets (hex), - // and multi-char digit sets (uwu): + // We now have to differentiate between single-char digit sets (like hex), + // and multi-char digit sets (like uwu): switch (base) { case Configuration::IOBASE_FORMAT::BASE_BYTES: case Configuration::IOBASE_FORMAT::BASE_2: diff --git a/GCryptCLI/src/DataIngestionLayer.cpp b/GCryptCLI/src/DataIngestionLayer.cpp index af53406..a2ebaa9 100644 --- a/GCryptCLI/src/DataIngestionLayer.cpp +++ b/GCryptCLI/src/DataIngestionLayer.cpp @@ -1,5 +1,6 @@ #include "DataIngestionLayer.h" #include "CommandlineInterface.h" +#include "DataFormatter.h" #include "Bases.h" #include #include @@ -53,6 +54,16 @@ void DataIngestionLayer::Init() { break; } + // Derive from our the current module if we're reading ciphertext or not + if ( + (Configuration::activeModule == Configuration::MODULE::DECRYPTION) + ) { + isReadingCiphertext = true; + } + else { + isReadingCiphertext = false; + } + initialized = true; reachedEof = false; @@ -74,35 +85,95 @@ void DataIngestionLayer::ReadBlock() { } if (!reachedEof) { - // This should really account for iobase recoding! + // A block is this many digits wide, in encoding + const std::size_t blockWidth = blockLengthByBase[Configuration::formatIn]; - // Create buffer to read into - char buf[Block::BLOCK_SIZE]; - memset(buf, 0, sizeof(buf)); + // Iterate over the string, and parse all blocks + // We now have to differentiate between single-char digit sets (like hex), + // and multi-char digit sets (like uwu): + switch (Configuration::formatIn) { + case Configuration::IOBASE_FORMAT::BASE_BYTES: + case Configuration::IOBASE_FORMAT::BASE_2: + case Configuration::IOBASE_FORMAT::BASE_8: + case Configuration::IOBASE_FORMAT::BASE_10: + case Configuration::IOBASE_FORMAT::BASE_16: + case Configuration::IOBASE_FORMAT::BASE_64: + // Easy case: Each digit is exactly one char in size. + // We can just calculate how many bytes we have to read. - // Read - in->read(buf, sizeof(buf)); + // bytesRead is always of the correct length, 0-padded. + std::size_t n_bytes_read; + const std::string dataRead = ReadBytes(blockWidth, n_bytes_read); - // Fetch how much we've read - const std::size_t n_bytes_read = in->gcount(); + // If we've read 0 bytes, this was the last block + // and it's completely empty. We can abort without doing anything. + // The ReadBytes function takes care of setting the reachedEof flag. + if (n_bytes_read == 0) { + return; + } - // Is this fewer bytes than we requested? - if (n_bytes_read < sizeof(buf)) { - // Yes: EOF reached. - reachedEof = true; - } + // If we are reading ciphertext + // make sure we've read enough bytes to compose a block. + if ( + (isReadingCiphertext) && + (n_bytes_read < blockWidth) + ) { + throw std::runtime_error("DataIngestionLayer::ReadBlock() read an input-data fragment that is smaller than a data block should be. Is your cipher text incomplete?"); + } - // Construct a Block from this buf - Block block; - block.FromByteString(std::string(buf, sizeof(buf))); + // This should decode to a block just like this. + Block newBlock; + + // Special-case: We are reading cleartext (no ciphertext) + // cleartext is always base_bytes + if (!isReadingCiphertext) { + // When just reading cleartext-bytes, we also allow shorter strings + // than BLOCK_SIZE. These will just get zero-padded. + newBlock.FromTextString(dataRead); + } + else { + // Else: recode to a block. + newBlock = DataFormatter::DecodeFormat( + dataRead, + Configuration::formatIn + ); + } + + blocks.emplace(newBlock); + break; + } - // Enqueue it - blocks.emplace(block); } return; } +std::string DataIngestionLayer::ReadBytes(const std::size_t n, std::size_t& out_bytes_read) { + + // Prepare a buffer to read to + char* buf = new char[n+1]; + memset(buf, 0, (n+1) * sizeof(buf[0])); + + // Read + in->read(buf, n * sizeof(buf[0])); + + // Fetch how much we've read + out_bytes_read = in->gcount(); + + // Is this fewer bytes than got requested? + if (out_bytes_read < n) { + // Yes: EOF reached. + reachedEof = true; + } + + // Translate buffer to a standard string + const std::string sbuf(buf, n); + delete[] buf; + + // Return our buffer + return sbuf; +} + bool DataIngestionLayer::ReachedEOF() { return reachedEof; } @@ -142,5 +213,6 @@ std::ifstream DataIngestionLayer::ifs; std::istringstream DataIngestionLayer::iss; bool DataIngestionLayer::reachedEof = false; bool DataIngestionLayer::initialized = false; +bool DataIngestionLayer::isReadingCiphertext; std::queue DataIngestionLayer::blocks; diff --git a/GCryptCLI/src/DataOutputLayer.cpp b/GCryptCLI/src/DataOutputLayer.cpp index 2959af0..e285c54 100644 --- a/GCryptCLI/src/DataOutputLayer.cpp +++ b/GCryptCLI/src/DataOutputLayer.cpp @@ -75,7 +75,6 @@ void DataOutputLayer::WriteBlock() { (reachedEof) ) ) { - // Fetch the block to write const Block block = blocks.front(); blocks.pop(); @@ -88,7 +87,8 @@ void DataOutputLayer::WriteBlock() { ); // Dump it - *out << formattedBlock; + // This way we avoid zerobytes getting trimmed off... + out->write(formattedBlock.data(), formattedBlock.length()); // If this is not the last block, and the used iobase set // requires it, append a seperator @@ -102,15 +102,8 @@ void DataOutputLayer::WriteBlock() { *out << " "; } - // If we are finished, and are outputting to stdout, - // and the user didn't specifically opt out, print a newline - if ( - (IsFinished()) && - (Configuration::outputTo == Configuration::OUTPUT_TO::STDOUT) && - (!CommandlineInterface::Get().HasParam("--no-newline")) - ) { - *out << std::endl; - } + AddTrailingLinebreakIfRequired(); + out->flush(); } @@ -119,6 +112,11 @@ void DataOutputLayer::WriteBlock() { void DataOutputLayer::ReachedEOF() { reachedEof = true; + + // Add the trailing linebreak here aswell, as, if input is ciphertext, + // ReachedEOF() may only be called after all blocks are already written + AddTrailingLinebreakIfRequired(); + return; } @@ -126,6 +124,23 @@ bool DataOutputLayer::IsFinished() { return (reachedEof) && (blocks.size() == 0); } +void DataOutputLayer::AddTrailingLinebreakIfRequired() { + // If we are finished, and are outputting to stdout, + // and input format is not bytes, + // and the user didn't specifically opt out, print a newline + if ( + (IsFinished()) && + (Configuration::outputTo == Configuration::OUTPUT_TO::STDOUT) && + (Configuration::formatIn != Configuration::IOBASE_FORMAT::BASE_BYTES) && + (!CommandlineInterface::Get().HasParam("--no-newline")) + ) { + *out << std::endl; + out->flush(); + } + + return; +} + std::ostream* DataOutputLayer::out; std::ofstream DataOutputLayer::ofs; bool DataOutputLayer::reachedEof = false; diff --git a/GCryptCLI/src/ModuleDecryption.cpp b/GCryptCLI/src/ModuleDecryption.cpp new file mode 100644 index 0000000..8d0d963 --- /dev/null +++ b/GCryptCLI/src/ModuleDecryption.cpp @@ -0,0 +1,56 @@ +#include "ModuleDecryption.h" +#include "DataIngestionLayer.h" +#include "DataOutputLayer.h" +#include "KeyManager.h" +#include + +using namespace Module; +using namespace Leonetienne::GCrypt; + +void Decryption::Run() { + + // Initialize the data ingestion layer + IO::DataIngestionLayer::Init(); + + // Initialize the data output layer + IO::DataOutputLayer::Init(); + + // Initialize a cipher + GCipher cipher( + KeyManager::GetKey(), + GCipher::DIRECTION::DECIPHER + ); + + while (!IO::DataOutputLayer::IsFinished()) { + // Read in new blocks, if not reached eof + if (!IO::DataIngestionLayer::ReachedEOF()) { + IO::DataIngestionLayer::ReadBlock(); + } + + // Process a block, if one is ready + if (IO::DataIngestionLayer::IsBlockReady()) { + const Block cleartext = IO::DataIngestionLayer::GetNextBlock(); + const Block ciphertext = cipher.Digest(cleartext); + + // Enqueue the block for output + IO::DataOutputLayer::Enqueue(ciphertext); + } + + // Tell the data output layer that it received the + // last block, if it did + if (IO::DataIngestionLayer::IsFinished()) { + IO::DataOutputLayer::ReachedEOF(); + } + + // Attempt to write a block + IO::DataOutputLayer::WriteBlock(); + } + + // Destruct the data ingestion layer + IO::DataIngestionLayer::Destruct(); + + // Destruct the data output layer + IO::DataOutputLayer::Destruct(); + + return; +} diff --git a/GCryptCLI/src/ModuleEncryption.cpp b/GCryptCLI/src/ModuleEncryption.cpp index 9cec537..c0fe11d 100644 --- a/GCryptCLI/src/ModuleEncryption.cpp +++ b/GCryptCLI/src/ModuleEncryption.cpp @@ -2,7 +2,6 @@ #include "DataIngestionLayer.h" #include "DataOutputLayer.h" #include "KeyManager.h" -#include #include using namespace Module; @@ -35,12 +34,12 @@ void Encryption::Run() { // Enqueue the block for output IO::DataOutputLayer::Enqueue(ciphertext); + } - // Tell the data output layer that it just received the - // last block, if it did - if (IO::DataIngestionLayer::IsFinished()) { - IO::DataOutputLayer::ReachedEOF(); - } + // Tell the data output layer that it just received the + // last block, if it did + if (IO::DataIngestionLayer::IsFinished()) { + IO::DataOutputLayer::ReachedEOF(); } // Attempt to write a block diff --git a/GCryptCLI/src/main.cpp b/GCryptCLI/src/main.cpp index f477576..1fb080a 100644 --- a/GCryptCLI/src/main.cpp +++ b/GCryptCLI/src/main.cpp @@ -3,6 +3,7 @@ #include "KeyManager.h" #include "ModuleGenerateKey.h" #include "ModuleEncryption.h" +#include "ModuleDecryption.h" int main(int argc, char* const* argv) { @@ -24,6 +25,10 @@ int main(int argc, char* const* argv) { case Configuration::MODULE::ENCRYPTION: Module::Encryption::Run(); break; + + case Configuration::MODULE::DECRYPTION: + Module::Decryption::Run(); + break; } return 0;