diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 3ebe4e2f..7bad17c7 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -57,10 +57,16 @@ enum { NOT_SUPPORTED = -101, /** * @brief Conflicting or invalid arguments for a method + * + * This does not set CDocReader/CDocWriter into error state - so invoking subsequent methods + * with correct arguments will succeed */ WRONG_ARGUMENTS = -102, /** * @brief Components of multi-method workflow are called in wrong order + * + * This does not set CDocReader/CDocWriter into error state - so invoking subsequent methods + * in correct order will succeed */ WORKFLOW_ERROR = -103, /** @@ -85,6 +91,9 @@ enum { INPUT_STREAM_ERROR = -108, /** * @brief The supplied decryption key is wrong + * + * This does not set CDocReader/CDocWriter into error state - so invoking subsequent methods + * with correct key will succeed */ WRONG_KEY = -109, /** diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 0f301ce6..1389c749 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -87,6 +87,20 @@ struct CDoc2Reader::Private { std::unique_ptr dec; std::unique_ptr zsrc; std::unique_ptr tar; + + result_t decryptAllAndClose() { + std::array buf; + result_t rv = dec->read(buf.data(), buf.size()); + while (rv == buf.size()) { + rv = dec->read(buf.data(), buf.size()); + } + if (rv < 0) return rv; + zsrc.reset(); + tar.reset(); + rv = dec->close(); + dec.reset(); + return rv; + } }; CDoc2Reader::~CDoc2Reader() @@ -118,35 +132,44 @@ CDoc2Reader::getLockForCert(const std::vector& cert){ libcdoc::result_t CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) { + if (lock_idx >= priv->locks.size()) { + setLastError(t_("Invalid lock index")); + LOG_ERROR("{}", last_error); + return libcdoc::WRONG_ARGUMENTS; + } LOG_DBG("CDoc2Reader::getFMK: {}", lock_idx); LOG_DBG("CDoc2Reader::num locks: {}", priv->locks.size()); const Lock& lock = priv->locks.at(lock_idx); + LOG_DBG("Label: {}", lock.label); std::vector kek; if (lock.type == Lock::Type::PASSWORD) { // Password LOG_DBG("password"); std::string info_str = libcdoc::CDoc2::getSaltForExpand(lock.label); + LOG_DBG("info: {}", toHex(info_str)); std::vector kek_pm; - crypto->extractHKDF(kek_pm, lock.getBytes(Lock::SALT), lock.getBytes(Lock::PW_SALT), lock.getInt(Lock::KDF_ITER), lock_idx); - LOG_DBG("password2"); + if (auto rv = crypto->extractHKDF(kek_pm, lock.getBytes(Lock::SALT), lock.getBytes(Lock::PW_SALT), lock.getInt(Lock::KDF_ITER), lock_idx); rv != libcdoc::OK) { + setLastError(crypto->getLastErrorStr(rv)); + LOG_ERROR("{}", last_error); + return rv; + } + LOG_TRACE_KEY("salt: {}", lock.getBytes(Lock::SALT)); + LOG_TRACE_KEY("kek_pm: {}", kek_pm); kek = libcdoc::Crypto::expand(kek_pm, info_str, 32); - if (kek.empty()) return libcdoc::CRYPTO_ERROR; - LOG_DBG("password3"); } else if (lock.type == Lock::Type::SYMMETRIC_KEY) { // Symmetric key LOG_DBG("symmetric"); std::string info_str = libcdoc::CDoc2::getSaltForExpand(lock.label); - std::vector kek_pm; - crypto->extractHKDF(kek_pm, lock.getBytes(Lock::SALT), {}, 0, lock_idx); - kek = libcdoc::Crypto::expand(kek_pm, info_str, 32); - - LOG_DBG("Label: {}", lock.label); LOG_DBG("info: {}", toHex(info_str)); + std::vector kek_pm; + if (auto rv = crypto->extractHKDF(kek_pm, lock.getBytes(Lock::SALT), {}, 0, lock_idx); rv != libcdoc::OK) { + setLastError(crypto->getLastErrorStr(rv)); + LOG_ERROR("{}", last_error); + return rv; + } LOG_TRACE_KEY("salt: {}", lock.getBytes(Lock::SALT)); LOG_TRACE_KEY("kek_pm: {}", kek_pm); - LOG_TRACE_KEY("kek: {}", kek); - - if (kek.empty()) return libcdoc::CRYPTO_ERROR; + kek = libcdoc::Crypto::expand(kek_pm, info_str, 32); } else if ((lock.type == Lock::Type::PUBLIC_KEY) || (lock.type == Lock::Type::SERVER)) { // Public/private key std::vector key_material; @@ -196,13 +219,9 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) LOG_ERROR("{}", last_error); return result; } - LOG_TRACE_KEY("Key kekPm: {}", kek_pm); - std::string info_str = libcdoc::CDoc2::getSaltForExpand(key_material, lock.getBytes(Lock::Params::RCPT_KEY)); - LOG_DBG("info: {}", toHex(info_str)); - kek = libcdoc::Crypto::expand(kek_pm, info_str, libcdoc::CDoc2::KEY_LEN); } } else if (lock.type == Lock::Type::SHARE_SERVER) { @@ -312,7 +331,6 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) LOG_TRACE_KEY("KEK: {}", kek); - if(kek.empty()) { setLastError(t_("Failed to derive KEK")); LOG_ERROR("{}", last_error); @@ -394,10 +412,10 @@ CDoc2Reader::beginDecryption(const std::vector& fmk) std::vector aad(libcdoc::CDoc2::PAYLOAD.cbegin(), libcdoc::CDoc2::PAYLOAD.cend()); aad.insert(aad.end(), priv->header_data.cbegin(), priv->header_data.cend()); aad.insert(aad.end(), priv->headerHMAC.cbegin(), priv->headerHMAC.cend()); - if(priv->dec->updateAAD(aad) != OK) { - setLastError("Wrong decryption key (FMK)"); + if(auto rv = priv->dec->updateAAD(aad); rv != OK) { + setLastError(priv->dec->getLastErrorStr(rv)); LOG_ERROR("{}", last_error); - return libcdoc::WRONG_KEY; + return rv; } priv->zsrc = std::make_unique(priv->dec.get(), false); @@ -414,8 +432,13 @@ CDoc2Reader::nextFile(std::string& name, int64_t& size) LOG_ERROR("{}", last_error); return libcdoc::WORKFLOW_ERROR; } - result_t result = priv->tar->next(name, size); - if (result != OK) { + result_t result = priv->tar->next(name, size); + if (result < 0) { + result_t sr = priv->decryptAllAndClose(); + if (sr != OK) { + setLastError("Crypto payload integrity check failed"); + return sr; + } setLastError(priv->tar->getLastErrorStr(result)); } return result; @@ -430,7 +453,12 @@ CDoc2Reader::readData(uint8_t *dst, size_t size) return libcdoc::WORKFLOW_ERROR; } result_t result = priv->tar->read(dst, size); - if (result != OK) { + if (result < 0) { + result_t sr = priv->decryptAllAndClose(); + if (sr != OK) { + setLastError("Crypto payload integrity check failed"); + return sr; + } setLastError(priv->tar->getLastErrorStr(result)); } return result; @@ -439,11 +467,15 @@ CDoc2Reader::readData(uint8_t *dst, size_t size) libcdoc::result_t CDoc2Reader::finishDecryption() { + if (!priv->tar) { + setLastError("finishDecryption() called before beginDecryption()"); + LOG_ERROR("{}", last_error); + return libcdoc::WORKFLOW_ERROR; + } if (!priv->zsrc->isEof()) { setLastError(t_("CDoc contains additional payload data that is not part of content")); LOG_WARN("{}", last_error); } - setLastError({}); priv->zsrc.reset(); priv->tar.reset(); diff --git a/cdoc/CDoc2Writer.cpp b/cdoc/CDoc2Writer.cpp index 66942284..ba7f886e 100644 --- a/cdoc/CDoc2Writer.cpp +++ b/cdoc/CDoc2Writer.cpp @@ -457,6 +457,11 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector 8ULL * 1024 * 1024 * 1024) { + setLastError("Invalid file size"); + LOG_ERROR("{}", last_error); + return libcdoc::WRONG_ARGUMENTS; + } if(auto rv = tar->open(name, size); rv < 0) { setLastError(tar->getLastErrorStr(rv)); LOG_ERROR("{}", last_error); @@ -505,6 +529,11 @@ CDoc2Writer::addFile(const std::string& name, size_t size) libcdoc::result_t CDoc2Writer::writeData(const uint8_t *src, size_t size) { + if (finished) { + setLastError("Encryption finished"); + LOG_ERROR("{}", last_error); + return libcdoc::WORKFLOW_ERROR; + } if(!tar) { setLastError("No file added"); LOG_ERROR("{}", last_error); @@ -520,6 +549,11 @@ CDoc2Writer::writeData(const uint8_t *src, size_t size) libcdoc::result_t CDoc2Writer::finishEncryption() { + if (finished) { + setLastError("Encryption finished"); + LOG_ERROR("{}", last_error); + return libcdoc::WORKFLOW_ERROR; + } if(!tar) { setLastError("No file added"); LOG_ERROR("{}", last_error); @@ -531,12 +565,18 @@ CDoc2Writer::finishEncryption() tar.reset(); recipients.clear(); if (owned) dst->close(); + finished = true; return rv; } libcdoc::result_t CDoc2Writer::encrypt(libcdoc::MultiDataSource& src, const std::vector& keys) { + if (finished) { + setLastError("Encryption finished"); + LOG_ERROR("{}", last_error); + return libcdoc::WORKFLOW_ERROR; + } for (auto rcpt : keys) { if(auto rv = addRecipient(rcpt); rv != libcdoc::OK) return rv; diff --git a/cdoc/CDoc2Writer.h b/cdoc/CDoc2Writer.h index f68acea2..3bb5800a 100644 --- a/cdoc/CDoc2Writer.h +++ b/cdoc/CDoc2Writer.h @@ -46,6 +46,7 @@ class CDoc2Writer final: public libcdoc::CDocWriter { std::unique_ptr tar; std::vector recipients; + bool finished = false; }; } diff --git a/cdoc/CryptoBackend.cpp b/cdoc/CryptoBackend.cpp index b0c10459..8c0f1652 100644 --- a/cdoc/CryptoBackend.cpp +++ b/cdoc/CryptoBackend.cpp @@ -82,7 +82,7 @@ CryptoBackend::getKeyMaterial(std::vector& key_material, const std::vec if (pw_salt.empty()) return INVALID_PARAMS; std::vector secret; int result = getSecret(secret, idx); - if (result < 0) return result; + if (result) return result; LOG_DBG("Secret: {}", toHex(secret)); @@ -91,7 +91,7 @@ CryptoBackend::getKeyMaterial(std::vector& key_material, const std::vec if (key_material.empty()) return OPENSSL_ERROR; } else { int result = getSecret(key_material, idx); - if (result < 0) return result; + if (result) return result; LOG_DBG("Secret: {}", toHex(key_material)); if (key_material.size() != 32) { return INVALID_PARAMS; diff --git a/cdoc/Io.h b/cdoc/Io.h index cdf9fd5c..45902101 100644 --- a/cdoc/Io.h +++ b/cdoc/Io.h @@ -255,7 +255,7 @@ struct CDOC_EXPORT IStreamSource : public DataSource { if (_owned) delete _ifs; } - result_t seek(size_t pos) { + result_t seek(size_t pos) override { if(_ifs->bad()) return INPUT_STREAM_ERROR; _ifs->clear(); _ifs->seekg(pos); diff --git a/cdoc/Tar.cpp b/cdoc/Tar.cpp index e1f79417..c4775fdb 100644 --- a/cdoc/Tar.cpp +++ b/cdoc/Tar.cpp @@ -130,6 +130,10 @@ libcdoc::TarConsumer::~TarConsumer() libcdoc::result_t libcdoc::TarConsumer::write(const uint8_t *src, size_t size) noexcept { + if ((_current_size >= 0) && ((_current_written + size) > _current_size)) { + return WORKFLOW_ERROR; + } + _current_written += size; return _dst->write(src, size); } @@ -160,19 +164,25 @@ libcdoc::TarConsumer::writePadding(int64_t size) noexcept { libcdoc::result_t libcdoc::TarConsumer::close() noexcept { - if (_current_size > 0) { - if(auto rv = writePadding(_current_size); rv != OK) - return rv; - } - Header empty = {}; - if(auto rv = writeHeader(empty); rv != OK) - return rv; - if(auto rv = writeHeader(empty); rv != OK) - return rv; + result_t result = OK; + if ((_current_size >= 0) && (_current_written < _current_size)) { + result = DATA_FORMAT_ERROR; + } else { + if (_current_written > 0) { + if(auto rv = writePadding(_current_written); rv != OK) + return rv; + } + Header empty = {}; + if(auto rv = writeHeader(empty); rv != OK) + return rv; + if(auto rv = writeHeader(empty); rv != OK) + return rv; + } if (_owned) { - return _dst->close(); + if (auto rv = _dst->close(); rv != OK) + return rv; } - return OK; + return result; } bool @@ -184,12 +194,16 @@ libcdoc::TarConsumer::isError() noexcept libcdoc::result_t libcdoc::TarConsumer::open(const std::string& name, int64_t size) { - if (_current_size > 0) { - if(auto rv = writePadding(_current_size); rv != OK) + if ((_current_size >= 0) && (_current_written < _current_size)) { + return WORKFLOW_ERROR; + } + if (_current_written > 0) { + if(auto rv = writePadding(_current_written); rv != OK) return rv; } _current_size = size; + _current_written = 0; Header h {}; size_t len = std::min(name.size(), h.name.size()); std::copy_n(name.cbegin(), len, h.name.begin()); diff --git a/test/libcdoc_boost.cpp b/test/libcdoc_boost.cpp index 39035652..3b48536e 100644 --- a/test/libcdoc_boost.cpp +++ b/test/libcdoc_boost.cpp @@ -344,74 +344,173 @@ gen_random_filename() return utf16_to_utf8(u16); } -BOOST_AUTO_TEST_SUITE(LargeFiles) - -BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithPasswordAndLabel, FixtureBase, * utf::description("Testing weird and large files")) -{ - std::srand(1); +// CDoc2 password and label - std::vector data; - bool eof = false; - PipeConsumer pipec(data, eof); - PipeSource pipes(data, eof); - PipeCrypto pcrypto("password"); +struct TestCrypto : public libcdoc::CryptoBackend { + std::string_view password; - // Create writer - libcdoc::CDocWriter *writer = libcdoc::CDocWriter::createWriter(2, &pipec, false, nullptr, &pcrypto, nullptr); - BOOST_TEST(writer != nullptr); - libcdoc::Recipient rcpt = libcdoc::Recipient::makeSymmetric("test", 65536); - BOOST_TEST(writer->addRecipient(rcpt) == libcdoc::OK); - BOOST_TEST(writer->beginEncryption() == libcdoc::OK); + libcdoc::result_t getSecret(std::vector& dst, unsigned int idx) override final { + // Mark empty password with bogus error to detect it + if(password.empty()) return libcdoc::WRONG_ARGUMENTS; + dst.assign(password.cbegin(), password.cend()); + return libcdoc::OK; + }; +}; - // List of files: 0, 0, max_size...0 - std::vector files; - files.emplace_back(gen_random_filename(), 0); - files.emplace_back(gen_random_filename(), 0); - for (size_t size = max_filesize; size != 0; size = size / 100) { - files.emplace_back(gen_random_filename(), size); - } - files.emplace_back(gen_random_filename(), 0); +BOOST_AUTO_TEST_SUITE(CDoc2Errors) +BOOST_FIXTURE_TEST_CASE_WITH_DECOR(CDoc2EncryptErrors, EncryptFixture, + * utf::description("Cause various encryption errors")) +{ + std::string container = formTargetFile("CDoc2Errors.cdoc"); + uint8_t test_data[256]; - PipeWriter wrt(writer, files); + libcdoc::ToolConf conf; + TestCrypto crypto; - // Create reader - libcdoc::CDocReader *reader = libcdoc::CDocReader::createReader(&pipes, false, nullptr, &pcrypto, nullptr); - BOOST_TEST(reader != nullptr); + srand(0); + // Create writer + libcdoc::CDocWriter *wrt = libcdoc::CDocWriter::createWriter(2, container, &conf, &crypto, nullptr); + BOOST_TEST(wrt != nullptr, "Cannot create writer"); + // Nothing can be done until at least one recipient is added + BOOST_TEST(wrt->beginEncryption() == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->addFile("testfile", 1024) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->finishEncryption() == libcdoc::WORKFLOW_ERROR); + + // Add recipient + libcdoc::Recipient rcpt = libcdoc::Recipient::makeSymmetric("test-recipient", 65536); + BOOST_TEST(wrt->addRecipient(rcpt) == libcdoc::OK); + // Encryption cannot proceed before beginEncryption is called + BOOST_TEST(wrt->addFile("testfile", 1024) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->finishEncryption() == libcdoc::WORKFLOW_ERROR); + + // Begin encryption + BOOST_TEST(wrt->beginEncryption() == libcdoc::WRONG_ARGUMENTS); + crypto.password = "test-password"; + BOOST_TEST(wrt->beginEncryption() == libcdoc::OK); + // Cannot do anything else than add files + BOOST_TEST(wrt->addRecipient(rcpt) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->beginEncryption() == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::WORKFLOW_ERROR); + // Finish encryption will succeed with empty tar + + // Add file + BOOST_TEST(wrt->addFile("testfile", 1024) == libcdoc::OK); + // Errors + BOOST_TEST(wrt->addRecipient(rcpt) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->beginEncryption() == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->addFile("testfile", 1024) == libcdoc::WORKFLOW_ERROR); + + // Write data + for (int i = 0; i < 256; i++) test_data[i] = uint8_t(rand() & 0xff); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::OK); + BOOST_TEST(wrt->addRecipient(rcpt) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->beginEncryption() == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->addFile("testfile", 1024) == libcdoc::WORKFLOW_ERROR); + for (int i = 0; i < 256; i++) test_data[i] = uint8_t(rand() & 0xff); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::OK); + for (int i = 0; i < 256; i++) test_data[i] = uint8_t(rand() & 0xff); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::OK); + for (int i = 0; i < 256; i++) test_data[i] = uint8_t(rand() & 0xff); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::OK); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->addRecipient(rcpt) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->beginEncryption() == libcdoc::WORKFLOW_ERROR); + // Add file with unknown size + BOOST_TEST(wrt->addFile("testfile2", 10000000000ULL) == libcdoc::WRONG_ARGUMENTS); + BOOST_TEST(wrt->addFile("testfile2", 255) == libcdoc::OK); + for (int i = 0; i < 256; i++) test_data[i] = uint8_t(rand() & 0xff); + BOOST_TEST(wrt->writeData(test_data, 255) == libcdoc::OK); + BOOST_TEST(wrt->addRecipient(rcpt) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->beginEncryption() == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->finishEncryption() == libcdoc::OK); + + BOOST_TEST(wrt->addRecipient(rcpt) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->beginEncryption() == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->addFile("testfile", 1024) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->writeData(test_data, 256) == libcdoc::WORKFLOW_ERROR); + BOOST_TEST(wrt->finishEncryption() == libcdoc::WORKFLOW_ERROR); + + delete wrt; +} - // Fill buffer - while((data.size() < 2 * wrt.BUFSIZE) && !wrt.isEof()) { - BOOST_TEST(wrt.writeMore() == libcdoc::OK); - } - std::vector fmk; - BOOST_TEST(reader->getFMK(fmk, 0) == libcdoc::OK); - BOOST_TEST(reader->beginDecryption(fmk) == libcdoc::OK); +BOOST_FIXTURE_TEST_CASE_WITH_DECOR(CDoc2DecryptErrors, DecryptFixture, + * utf::depends_on("CDoc2Errors/CDoc2EncryptErrors") + * utf::description("Cause various decryption errors")) +{ + std::string container = checkTargetFile("CDoc2Errors.cdoc"); + libcdoc::ToolConf conf; + TestCrypto crypto; + uint8_t buf[1024]; + + libcdoc::CDocReader *rdr = libcdoc::CDocReader::createReader(container, &conf, &crypto, nullptr); + BOOST_TEST(rdr != nullptr, "Cannot create reader"); + std::vector fmk(32); + BOOST_TEST(rdr->getFMK(fmk, 10) == libcdoc::WRONG_ARGUMENTS); + // Decryption should start with random key + BOOST_TEST(rdr->beginDecryption(fmk) == libcdoc::OK); libcdoc::FileInfo fi; - for (int cfile = 0; cfile < files.size(); cfile++) { - // Fill buffer - while((data.size() < 2 * wrt.BUFSIZE) && !wrt.isEof()) { - BOOST_TEST(wrt.writeMore() == libcdoc::OK); - } - // Get file - BOOST_TEST(reader->nextFile(fi) == libcdoc::OK); - BOOST_TEST(fi.name == files[cfile].name); - BOOST_TEST(fi.size == files[cfile].size); - for (size_t pos = 0; pos < files[cfile].size; pos += wrt.BUFSIZE) { - // Fill buffer - while((data.size() < 2 * wrt.BUFSIZE) && !wrt.isEof()) { - BOOST_TEST(wrt.writeMore() == libcdoc::OK); - } - size_t toread = files[cfile].size - pos; - if (toread > wrt.BUFSIZE) toread = wrt.BUFSIZE; - uint8_t buf[wrt.BUFSIZE], cbuf[wrt.BUFSIZE]; - BOOST_TEST(reader->readData(buf, toread) == toread); - for (size_t i = 0; i < toread; i++) cbuf[i] = wrt.getChar(cfile, pos + i); - BOOST_TEST(std::memcmp(buf, cbuf, toread) == 0); - } + // But the first file should file + BOOST_TEST(rdr->nextFile(fi) != libcdoc::OK); + delete rdr; + + rdr = libcdoc::CDocReader::createReader(container, &conf, &crypto, nullptr); + BOOST_TEST(rdr != nullptr, "Cannot create reader"); + BOOST_TEST(rdr->getFMK(fmk, 0) == libcdoc::WRONG_ARGUMENTS); + crypto.password = "wrong-password"; + BOOST_TEST(rdr->getFMK(fmk, 0) == libcdoc::WRONG_KEY); + crypto.password = "test-password"; + BOOST_TEST(rdr->getFMK(fmk, 0) == libcdoc::OK); + BOOST_TEST(rdr->beginDecryption(fmk) == libcdoc::OK); + BOOST_TEST(rdr->nextFile(fi) == libcdoc::OK); + BOOST_TEST(fi.size == 1024); + BOOST_TEST(rdr->readData(buf, 256) == 256); + BOOST_TEST(rdr->readData(buf, 256) == 256); + BOOST_TEST(rdr->readData(buf, 256) == 256); + BOOST_TEST(rdr->readData(buf, 1024) == 256); + BOOST_TEST(rdr->nextFile(fi) == libcdoc::OK); + BOOST_TEST(fi.size == 255); + BOOST_TEST(rdr->readData(buf, 1024) == 255); + BOOST_TEST(rdr->finishDecryption() == libcdoc::OK); + delete rdr; + + // Write over the end of file + size_t fsize = std::filesystem::file_size(container); + std::fstream file(container, std::ios::out | std::ios::in); + BOOST_TEST(!file.bad()); + file.seekp(fsize - 16, std::ios::beg); + file.write((char *) buf, 16); + file.close(); + + rdr = libcdoc::CDocReader::createReader(container, &conf, &crypto, nullptr); + BOOST_TEST(rdr != nullptr, "Cannot create reader"); + BOOST_TEST(rdr->getFMK(fmk, 0) == libcdoc::OK); + BOOST_TEST(rdr->beginDecryption(fmk) == libcdoc::OK); + BOOST_TEST(rdr->nextFile(fi) == libcdoc::OK); + BOOST_TEST(rdr->nextFile(fi) == libcdoc::OK); + BOOST_TEST(rdr->finishDecryption() == libcdoc::CRYPTO_ERROR); + delete rdr; + + // Truncate file, should result zlib error + std::filesystem::resize_file(container, fsize - 32); + rdr = libcdoc::CDocReader::createReader(container, &conf, &crypto, nullptr); + BOOST_TEST(rdr != nullptr, "Cannot create reader"); + BOOST_TEST(rdr->getFMK(fmk, 0) == libcdoc::OK); + BOOST_TEST(rdr->beginDecryption(fmk) == libcdoc::OK); + libcdoc::result_t rv = rdr->nextFile(fi); + BOOST_TEST(((rv == libcdoc::OK) || (rv == libcdoc::CRYPTO_ERROR))); + for (int i = 0; i < 4; i++) { + rv = rdr->readData(buf, 256); + BOOST_TEST(((rv == 256) || (rv == libcdoc::CRYPTO_ERROR))); } - BOOST_TEST(reader->nextFile(fi) == libcdoc::END_OF_STREAM); - BOOST_TEST(reader->finishDecryption() == libcdoc::OK); + rv = rdr->nextFile(fi); + BOOST_TEST(((rv == libcdoc::OK) || (rv == libcdoc::CRYPTO_ERROR))); + rv = rdr->readData(buf, 256); + BOOST_TEST(((rv == 255) || (rv == libcdoc::CRYPTO_ERROR))); + BOOST_TEST(rdr->finishDecryption() == libcdoc::WORKFLOW_ERROR); + delete rdr; } - BOOST_AUTO_TEST_SUITE_END() // CDoc2 password and label @@ -553,6 +652,78 @@ BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithRSAKeyV1, DecryptFixture, } BOOST_AUTO_TEST_SUITE_END() +// Stream encryption/decryption of large files + +BOOST_AUTO_TEST_SUITE(LargeFiles) + +BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithPasswordAndLabel, FixtureBase, * utf::description("Testing weird and large files")) +{ + std::srand(1); + + std::vector data; + bool eof = false; + PipeConsumer pipec(data, eof); + PipeSource pipes(data, eof); + PipeCrypto pcrypto("password"); + + // Create writer + libcdoc::CDocWriter *writer = libcdoc::CDocWriter::createWriter(2, &pipec, false, nullptr, &pcrypto, nullptr); + BOOST_TEST(writer != nullptr); + libcdoc::Recipient rcpt = libcdoc::Recipient::makeSymmetric("test", 65536); + BOOST_TEST(writer->addRecipient(rcpt) == libcdoc::OK); + BOOST_TEST(writer->beginEncryption() == libcdoc::OK); + + // List of files: 0, 0, max_size...0 + std::vector files; + files.emplace_back(gen_random_filename(), 0); + files.emplace_back(gen_random_filename(), 0); + for (size_t size = max_filesize; size != 0; size = size / 100) { + files.emplace_back(gen_random_filename(), size); + } + files.emplace_back(gen_random_filename(), 0); + + PipeWriter wrt(writer, files); + + // Create reader + libcdoc::CDocReader *reader = libcdoc::CDocReader::createReader(&pipes, false, nullptr, &pcrypto, nullptr); + BOOST_TEST(reader != nullptr); + + // Fill buffer + while((data.size() < 2 * wrt.BUFSIZE) && !wrt.isEof()) { + BOOST_TEST(wrt.writeMore() == libcdoc::OK); + } + std::vector fmk; + BOOST_TEST(reader->getFMK(fmk, 0) == libcdoc::OK); + BOOST_TEST(reader->beginDecryption(fmk) == libcdoc::OK); + libcdoc::FileInfo fi; + for (int cfile = 0; cfile < files.size(); cfile++) { + // Fill buffer + while((data.size() < 2 * wrt.BUFSIZE) && !wrt.isEof()) { + BOOST_TEST(wrt.writeMore() == libcdoc::OK); + } + // Get file + BOOST_TEST(reader->nextFile(fi) == libcdoc::OK); + BOOST_TEST(fi.name == files[cfile].name); + BOOST_TEST(fi.size == files[cfile].size); + for (size_t pos = 0; pos < files[cfile].size; pos += wrt.BUFSIZE) { + // Fill buffer + while((data.size() < 2 * wrt.BUFSIZE) && !wrt.isEof()) { + BOOST_TEST(wrt.writeMore() == libcdoc::OK); + } + size_t toread = files[cfile].size - pos; + if (toread > wrt.BUFSIZE) toread = wrt.BUFSIZE; + uint8_t buf[wrt.BUFSIZE], cbuf[wrt.BUFSIZE]; + BOOST_TEST(reader->readData(buf, toread) == toread); + for (size_t i = 0; i < toread; i++) cbuf[i] = wrt.getChar(cfile, pos + i); + BOOST_TEST(std::memcmp(buf, cbuf, toread) == 0); + } + } + BOOST_TEST(reader->nextFile(fi) == libcdoc::END_OF_STREAM); + BOOST_TEST(reader->finishDecryption() == libcdoc::OK); +} + +BOOST_AUTO_TEST_SUITE_END() + // Label parsing BOOST_AUTO_TEST_SUITE(MachineLabelParsing)