From 25ab00796435f45c492d7883f0ccf56f2fea7879 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 23 Jan 2026 16:42:05 +0200 Subject: [PATCH 01/50] Tar and workflow fixes and tests Signed-off-by: Lauris Kaplinski --- cdoc/CDoc.h | 9 ++ cdoc/CDoc2Reader.cpp | 80 ++++++++---- cdoc/CDoc2Writer.cpp | 42 +++++- cdoc/CDoc2Writer.h | 1 + cdoc/CryptoBackend.cpp | 4 +- cdoc/Io.h | 2 +- cdoc/Tar.cpp | 40 ++++-- test/libcdoc_boost.cpp | 289 ++++++++++++++++++++++++++++++++--------- 8 files changed, 367 insertions(+), 100 deletions(-) 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) From b4a173662af1bdc42060e61b66ef9f1dab004eb2 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 30 Jan 2026 14:57:46 +0200 Subject: [PATCH 02/50] Create locks even if capsule is not supported --- cdoc/CDoc2Reader.cpp | 272 ++++++++++++++++++++--------------------- cdoc/Crypto.cpp | 2 +- cdoc/Lock.h | 7 +- test/libcdoc_boost.cpp | 10 +- 4 files changed, 148 insertions(+), 143 deletions(-) diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 1389c749..91de38e7 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -101,6 +101,8 @@ struct CDoc2Reader::Private { dec.reset(); return rv; } + + static void buildLock(Lock& lock, const cdoc20::header::RecipientRecord& recipient); }; CDoc2Reader::~CDoc2Reader() @@ -487,11 +489,140 @@ CDoc2Reader::finishDecryption() return rv; } +void +CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecord& recipient) +{ + using namespace cdoc20::recipients; + using namespace cdoc20::header; + + lock.label = recipient.key_label()->str(); + lock.encrypted_fmk.assign(recipient.encrypted_fmk()->cbegin(), recipient.encrypted_fmk()->cend()); + + if(recipient.fmk_encryption_method() != cdoc20::header::FMKEncryptionMethod::XOR) + { + LOG_WARN("Unsupported FMK encryption method"); + return; + } + switch(recipient.capsule_type()) + { + case Capsule::recipients_ECCPublicKeyCapsule: + if(const auto *key = recipient.capsule_as_recipients_ECCPublicKeyCapsule()) { + if(key->curve() == EllipticCurve::secp384r1) { + lock.type = Lock::Type::PUBLIC_KEY; + lock.pk_type = Lock::PKType::ECC; + lock.setBytes(Lock::Params::RCPT_KEY, std::vector(key->recipient_public_key()->cbegin(), key->recipient_public_key()->cend())); + lock.setBytes(Lock::Params::KEY_MATERIAL, std::vector(key->sender_public_key()->cbegin(), key->sender_public_key()->cend())); + LOG_DBG("Load PK: {}", toHex(lock.getBytes(Lock::Params::RCPT_KEY))); + } else { + LOG_ERROR("Unsupported ECC curve: skipping"); + } + } + return; + case Capsule::recipients_RSAPublicKeyCapsule: + if(const auto *key = recipient.capsule_as_recipients_RSAPublicKeyCapsule()) + { + lock.type = Lock::Type::PUBLIC_KEY; + lock.pk_type = Lock::PKType::RSA; + lock.setBytes(Lock::Params::RCPT_KEY, std::vector(key->recipient_public_key()->cbegin(), key->recipient_public_key()->cend())); + lock.setBytes(Lock::Params::KEY_MATERIAL, std::vector(key->encrypted_kek()->cbegin(), key->encrypted_kek()->cend())); + } + return; + case Capsule::recipients_KeyServerCapsule: + if (const KeyServerCapsule *server = recipient.capsule_as_recipients_KeyServerCapsule()) { + KeyDetailsUnion details = server->recipient_key_details_type(); + switch (details) { + case KeyDetailsUnion::EccKeyDetails: + if(const EccKeyDetails *eccDetails = server->recipient_key_details_as_EccKeyDetails()) { + if(eccDetails->curve() != EllipticCurve::secp384r1) { + LOG_ERROR("Unsupported elliptic curve key type"); + return; + } + lock.pk_type = Lock::PKType::ECC; + lock.setBytes(Lock::Params::RCPT_KEY, std::vector(eccDetails->recipient_public_key()->cbegin(), eccDetails->recipient_public_key()->cend())); + } else { + LOG_ERROR("Invalid file format"); + return; + } + break; + case KeyDetailsUnion::RsaKeyDetails: + if(const RsaKeyDetails *rsaDetails = server->recipient_key_details_as_RsaKeyDetails()) { + lock.pk_type = Lock::PKType::RSA; + lock.setBytes(Lock::Params::RCPT_KEY, std::vector(rsaDetails->recipient_public_key()->cbegin(), rsaDetails->recipient_public_key()->cend())); + } else { + LOG_ERROR("Invalid file format"); + return; + } + break; + default: + LOG_ERROR("Unsupported Key Server Details: skipping"); + return; + } + lock.type = Lock::Type::SERVER; + lock.setString(Lock::Params::KEYSERVER_ID, server->keyserver_id()->str()); + lock.setString(Lock::Params::TRANSACTION_ID, server->transaction_id()->str()); + } else { + LOG_ERROR("Invalid file format"); + } + return; + case Capsule::recipients_SymmetricKeyCapsule: + if(const auto *capsule = recipient.capsule_as_recipients_SymmetricKeyCapsule()) + { + lock.type = Lock::SYMMETRIC_KEY; + lock.setBytes(Lock::SALT, std::vector(capsule->salt()->cbegin(), capsule->salt()->cend())); + } + return; + case Capsule::recipients_PBKDF2Capsule: + if(const auto *capsule = recipient.capsule_as_recipients_PBKDF2Capsule()) { + KDFAlgorithmIdentifier kdf_id = capsule->kdf_algorithm_identifier(); + if (kdf_id != KDFAlgorithmIdentifier::PBKDF2WithHmacSHA256) { + LOG_ERROR("Unsupported KDF algorithm: skipping"); + return; + } + lock.type = Lock::PASSWORD; + lock.setBytes(Lock::SALT, std::vector(capsule->salt()->cbegin(), capsule->salt()->cend())); + lock.setBytes(Lock::PW_SALT, std::vector(capsule->password_salt()->cbegin(), capsule->password_salt()->cend())); + lock.setInt(Lock::KDF_ITER, capsule->kdf_iterations()); + } + return; + case Capsule::recipients_KeySharesCapsule: + if (const auto *capsule = recipient.capsule_as_recipients_KeySharesCapsule()) { + if (capsule->recipient_type() != cdoc20::recipients::KeyShareRecipientType::SID_MID) { + LOG_ERROR("Invalid keyshare recipient type: {}", (int) capsule->recipient_type()); + return; + } + if (capsule->shares_scheme() != cdoc20::recipients::SharesScheme::N_OF_N) { + LOG_ERROR("Invalid keyshare scheme type: {}", (int) capsule->shares_scheme()); + return; + } + /* url,share_id;url,share_id... */ + std::vector strs; + for (auto cshare = capsule->shares()->cbegin(); cshare != capsule->shares()->cend(); ++cshare) { + std::string id = cshare->share_id()->str(); + std::string url = cshare->server_base_url()->str(); + std::string str = url + "," + id; + LOG_DBG("Keyshare: {}", str); + strs.push_back(std::move(str)); + } + std::string urls = join(strs, ";"); + LOG_DBG("Keyshare urls: {}", urls); + std::vector salt(capsule->salt()->cbegin(), capsule->salt()->cend()); + LOG_DBG("Keyshare salt: {}", toHex(salt)); + std::string recipient_id = capsule->recipient_id()->str(); + LOG_DBG("Keyshare recipient id: {}", recipient_id); + lock.type = Lock::SHARE_SERVER; + lock.setString(Lock::SHARE_URLS, urls); + lock.setBytes(Lock::SALT, salt); + lock.setString(Lock::RECIPIENT_ID, recipient_id); + } + return; + default: + LOG_ERROR("Unsupported Key Details: skipping"); + } +} + CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) : CDocReader(2), priv(std::make_unique(src, take_ownership)) { - - using namespace cdoc20::recipients; using namespace cdoc20::header; setLastError(t_("Invalid CDoc 2.0 header")); @@ -555,140 +686,9 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) setLastError({}); for(const auto *recipient: *recipients){ - if(recipient->fmk_encryption_method() != FMKEncryptionMethod::XOR) - { - LOG_WARN("Unsupported FMK encryption method: skipping"); - continue; - } - auto fillRecipientPK = [&recipient,&locks = priv->locks] (Lock::PKType pk_type, auto key) -> Lock& { - Lock &k = locks.emplace_back(Lock::Type::PUBLIC_KEY); - k.pk_type = pk_type; - k.setBytes(Lock::Params::RCPT_KEY, std::vector(key->recipient_public_key()->cbegin(), key->recipient_public_key()->cend())); - k.label = recipient->key_label()->str(); - k.encrypted_fmk.assign(recipient->encrypted_fmk()->cbegin(), recipient->encrypted_fmk()->cend()); - return k; - }; - switch(recipient->capsule_type()) - { - case Capsule::recipients_ECCPublicKeyCapsule: - if(const auto *key = recipient->capsule_as_recipients_ECCPublicKeyCapsule()) { - if(key->curve() != EllipticCurve::secp384r1) { - LOG_ERROR("Unsupported ECC curve: skipping"); - continue; - } - Lock &k = fillRecipientPK(Lock::PKType::ECC, key); - k.setBytes(Lock::Params::KEY_MATERIAL, std::vector(key->sender_public_key()->cbegin(), key->sender_public_key()->cend())); - LOG_DBG("Load PK: {}", toHex(k.getBytes(Lock::Params::RCPT_KEY))); - } - break; - case Capsule::recipients_RSAPublicKeyCapsule: - if(const auto *key = recipient->capsule_as_recipients_RSAPublicKeyCapsule()) - { - Lock &k = fillRecipientPK(Lock::PKType::RSA, key); - k.setBytes(Lock::Params::KEY_MATERIAL, std::vector(key->encrypted_kek()->cbegin(), key->encrypted_kek()->cend())); - } - break; - case Capsule::recipients_KeyServerCapsule: - if (const KeyServerCapsule *server = recipient->capsule_as_recipients_KeyServerCapsule()) { - KeyDetailsUnion details = server->recipient_key_details_type(); - Lock ckey; - switch (details) { - case KeyDetailsUnion::EccKeyDetails: - if(const EccKeyDetails *eccDetails = server->recipient_key_details_as_EccKeyDetails()) { - if(eccDetails->curve() == EllipticCurve::secp384r1) { - ckey.type = Lock::Type::SERVER; - ckey.pk_type = Lock::PKType::ECC; - ckey.setBytes(Lock::Params::RCPT_KEY, std::vector(eccDetails->recipient_public_key()->cbegin(), eccDetails->recipient_public_key()->cend())); - } else { - LOG_ERROR("Unsupported elliptic curve key type"); - } - } else { - LOG_ERROR("Invalid file format"); - } - break; - case KeyDetailsUnion::RsaKeyDetails: - if(const RsaKeyDetails *rsaDetails = server->recipient_key_details_as_RsaKeyDetails()) { - ckey.type = Lock::Type::SERVER; - ckey.pk_type = Lock::PKType::RSA; - ckey.setBytes(Lock::Params::RCPT_KEY, std::vector(rsaDetails->recipient_public_key()->cbegin(), rsaDetails->recipient_public_key()->cend())); - } else { - LOG_ERROR("Invalid file format"); - } - break; - default: - LOG_ERROR("Unsupported Key Server Details: skipping"); - } - if (ckey.type != Lock::Type::INVALID) { - ckey.label = recipient->key_label()->c_str(); - ckey.encrypted_fmk.assign(recipient->encrypted_fmk()->cbegin(), recipient->encrypted_fmk()->cend()); - ckey.setString(Lock::Params::KEYSERVER_ID, server->keyserver_id()->str()); - ckey.setString(Lock::Params::TRANSACTION_ID, server->transaction_id()->str()); - priv->locks.push_back(std::move(ckey)); - } - } else { - LOG_ERROR("Invalid file format"); - } - break; - case Capsule::recipients_SymmetricKeyCapsule: - if(const auto *capsule = recipient->capsule_as_recipients_SymmetricKeyCapsule()) - { - Lock &key = priv->locks.emplace_back(Lock::SYMMETRIC_KEY); - key.label = recipient->key_label()->str(); - key.encrypted_fmk.assign(recipient->encrypted_fmk()->cbegin(), recipient->encrypted_fmk()->cend()); - key.setBytes(Lock::SALT, std::vector(capsule->salt()->cbegin(), capsule->salt()->cend())); - } - break; - case Capsule::recipients_PBKDF2Capsule: - if(const auto *capsule = recipient->capsule_as_recipients_PBKDF2Capsule()) { - KDFAlgorithmIdentifier kdf_id = capsule->kdf_algorithm_identifier(); - if (kdf_id != KDFAlgorithmIdentifier::PBKDF2WithHmacSHA256) { - LOG_ERROR("Unsupported KDF algorithm: skipping"); - continue; - } - Lock &key = priv->locks.emplace_back(Lock::PASSWORD); - key.label = recipient->key_label()->str(); - key.encrypted_fmk.assign(recipient->encrypted_fmk()->cbegin(), recipient->encrypted_fmk()->cend()); - key.setBytes(Lock::SALT, std::vector(capsule->salt()->cbegin(), capsule->salt()->cend())); - key.setBytes(Lock::PW_SALT, std::vector(capsule->password_salt()->cbegin(), capsule->password_salt()->cend())); - key.setInt(Lock::KDF_ITER, capsule->kdf_iterations()); - } - break; - case Capsule::recipients_KeySharesCapsule: - if (const auto *capsule = recipient->capsule_as_recipients_KeySharesCapsule()) { - if (capsule->recipient_type() != cdoc20::recipients::KeyShareRecipientType::SID_MID) { - LOG_ERROR("Invalid keyshare recipient type: {}", (int) capsule->recipient_type()); - continue; - } - if (capsule->shares_scheme() != cdoc20::recipients::SharesScheme::N_OF_N) { - LOG_ERROR("Invalid keyshare scheme type: {}", (int) capsule->shares_scheme()); - continue; - } - /* url,share_id;url,share_id... */ - std::vector strs; - for (auto cshare = capsule->shares()->cbegin(); cshare != capsule->shares()->cend(); ++cshare) { - std::string id = cshare->share_id()->str(); - std::string url = cshare->server_base_url()->str(); - std::string str = url + "," + id; - LOG_DBG("Keyshare: {}", str); - strs.push_back(std::move(str)); - } - std::string urls = join(strs, ";"); - LOG_DBG("Keyshare urls: {}", urls); - std::vector salt(capsule->salt()->cbegin(), capsule->salt()->cend()); - LOG_DBG("Keyshare salt: {}", toHex(salt)); - std::string recipient_id = capsule->recipient_id()->str(); - LOG_DBG("Keyshare recipient id: {}", recipient_id); - Lock &lock = priv->locks.emplace_back(Lock::SHARE_SERVER); - lock.label = recipient->key_label()->str(); - lock.encrypted_fmk.assign(recipient->encrypted_fmk()->cbegin(), recipient->encrypted_fmk()->cend()); - lock.setString(Lock::SHARE_URLS, urls); - lock.setBytes(Lock::SALT, salt); - lock.setString(Lock::RECIPIENT_ID, recipient_id); - } - break; - default: - LOG_ERROR("Unsupported Key Details: skipping"); - } + Lock lock(Lock::Type::UNSUPPORTED); + Private::buildLock(lock, *recipient); + priv->locks.push_back(std::move(lock)); } } diff --git a/cdoc/Crypto.cpp b/cdoc/Crypto.cpp index 81ac0c3c..9f902ba5 100644 --- a/cdoc/Crypto.cpp +++ b/cdoc/Crypto.cpp @@ -621,6 +621,6 @@ result_t DecryptionSource::close() int len = 0; std::vector buffer(EVP_CIPHER_CTX_block_size(ctx.get()), 0); if (SSL_FAILED(EVP_CipherFinal_ex(ctx.get(), buffer.data(), &len), "EVP_CipherFinal_ex")) - return error = CRYPTO_ERROR; + return error = HASH_MISMATCH; return OK; } diff --git a/cdoc/Lock.h b/cdoc/Lock.h index 99d5f720..6c91e546 100644 --- a/cdoc/Lock.h +++ b/cdoc/Lock.h @@ -47,6 +47,11 @@ struct CDOC_EXPORT Lock * @brief Invalid value */ INVALID, + /** + * @brief Valid capsule but not supported by this library version + * + */ + UNSUPPORTED, /** * @brief Symmetric AES key */ @@ -194,7 +199,7 @@ struct CDOC_EXPORT Lock * @brief check whether lock is valid * @return true if valid */ - bool isValid() const noexcept { return (type != Type::INVALID) && !label.empty() && !encrypted_fmk.empty(); } + bool isValid() const noexcept { return (type != Type::INVALID) && (type != Type::UNSUPPORTED) && !label.empty() && !encrypted_fmk.empty(); } /** * @brief check whether lock is based on symmetric key * @return true if type is SYMMETRIC_KEY or PASSWORD diff --git a/test/libcdoc_boost.cpp b/test/libcdoc_boost.cpp index 3b48536e..1401dc39 100644 --- a/test/libcdoc_boost.cpp +++ b/test/libcdoc_boost.cpp @@ -489,7 +489,7 @@ BOOST_FIXTURE_TEST_CASE_WITH_DECOR(CDoc2DecryptErrors, DecryptFixture, 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); + BOOST_TEST(rdr->finishDecryption() == libcdoc::HASH_MISMATCH); delete rdr; // Truncate file, should result zlib error @@ -499,15 +499,15 @@ BOOST_FIXTURE_TEST_CASE_WITH_DECOR(CDoc2DecryptErrors, DecryptFixture, 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))); + BOOST_TEST(((rv == libcdoc::OK) || (rv == libcdoc::HASH_MISMATCH))); for (int i = 0; i < 4; i++) { rv = rdr->readData(buf, 256); - BOOST_TEST(((rv == 256) || (rv == libcdoc::CRYPTO_ERROR))); + BOOST_TEST(((rv == 256) || (rv == libcdoc::HASH_MISMATCH))); } rv = rdr->nextFile(fi); - BOOST_TEST(((rv == libcdoc::OK) || (rv == libcdoc::CRYPTO_ERROR))); + BOOST_TEST(((rv == libcdoc::OK) || (rv == libcdoc::HASH_MISMATCH))); rv = rdr->readData(buf, 256); - BOOST_TEST(((rv == 255) || (rv == libcdoc::CRYPTO_ERROR))); + BOOST_TEST(((rv == 255) || (rv == libcdoc::HASH_MISMATCH))); BOOST_TEST(rdr->finishDecryption() == libcdoc::WORKFLOW_ERROR); delete rdr; } From 414452e0c185903ec543fc82ee7c53b617175a25 Mon Sep 17 00:00:00 2001 From: lauris71 Date: Mon, 2 Feb 2026 12:25:56 +0200 Subject: [PATCH 03/50] Update cdoc/CDoc2Reader.cpp Co-authored-by: Raul Metsma --- cdoc/CDoc2Reader.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 91de38e7..41b3c3a2 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -686,9 +686,7 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) setLastError({}); for(const auto *recipient: *recipients){ - Lock lock(Lock::Type::UNSUPPORTED); - Private::buildLock(lock, *recipient); - priv->locks.push_back(std::move(lock)); + Private::buildLock(priv->locks.emplace_back(Lock::Type::UNSUPPORTED), *recipient); } } From 0854344b75d815a300eb41fd0cfd0f4176b5bb50 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Mon, 2 Feb 2026 13:00:09 +0200 Subject: [PATCH 04/50] Use toUint8Vector helper --- cdoc/CDoc2Reader.cpp | 26 +++++++++++++------------- cdoc/CryptoBackend.h | 4 ++-- cdoc/NetworkBackend.cpp | 5 ++--- cdoc/Utils.h | 10 ++++++++++ 4 files changed, 27 insertions(+), 18 deletions(-) diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 41b3c3a2..514ee251 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -215,7 +215,7 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) } } else { std::vector kek_pm; - int result = crypto->deriveHMACExtract(kek_pm, key_material, std::vector(libcdoc::CDoc2::KEKPREMASTER.cbegin(), libcdoc::CDoc2::KEKPREMASTER.cend()), lock_idx); + int result = crypto->deriveHMACExtract(kek_pm, key_material, toUint8Vector(libcdoc::CDoc2::KEKPREMASTER), lock_idx); if (result < 0) { setLastError(crypto->getLastErrorStr(result)); LOG_ERROR("{}", last_error); @@ -411,7 +411,7 @@ CDoc2Reader::beginDecryption(const std::vector& fmk) LOG_TRACE_KEY("cek: {}", cek); priv->dec = std::make_unique(*priv->_src, EVP_chacha20_poly1305(), cek, libcdoc::CDoc2::NONCE_LEN); - std::vector aad(libcdoc::CDoc2::PAYLOAD.cbegin(), libcdoc::CDoc2::PAYLOAD.cend()); + std::vector aad = toUint8Vector(libcdoc::CDoc2::PAYLOAD); aad.insert(aad.end(), priv->header_data.cbegin(), priv->header_data.cend()); aad.insert(aad.end(), priv->headerHMAC.cbegin(), priv->headerHMAC.cend()); if(auto rv = priv->dec->updateAAD(aad); rv != OK) { @@ -496,7 +496,7 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor using namespace cdoc20::header; lock.label = recipient.key_label()->str(); - lock.encrypted_fmk.assign(recipient.encrypted_fmk()->cbegin(), recipient.encrypted_fmk()->cend()); + lock.encrypted_fmk = toUint8Vector(recipient.encrypted_fmk()); if(recipient.fmk_encryption_method() != cdoc20::header::FMKEncryptionMethod::XOR) { @@ -510,8 +510,8 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor if(key->curve() == EllipticCurve::secp384r1) { lock.type = Lock::Type::PUBLIC_KEY; lock.pk_type = Lock::PKType::ECC; - lock.setBytes(Lock::Params::RCPT_KEY, std::vector(key->recipient_public_key()->cbegin(), key->recipient_public_key()->cend())); - lock.setBytes(Lock::Params::KEY_MATERIAL, std::vector(key->sender_public_key()->cbegin(), key->sender_public_key()->cend())); + lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(key->recipient_public_key())); + lock.setBytes(Lock::Params::KEY_MATERIAL, toUint8Vector(key->sender_public_key())); LOG_DBG("Load PK: {}", toHex(lock.getBytes(Lock::Params::RCPT_KEY))); } else { LOG_ERROR("Unsupported ECC curve: skipping"); @@ -523,8 +523,8 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor { lock.type = Lock::Type::PUBLIC_KEY; lock.pk_type = Lock::PKType::RSA; - lock.setBytes(Lock::Params::RCPT_KEY, std::vector(key->recipient_public_key()->cbegin(), key->recipient_public_key()->cend())); - lock.setBytes(Lock::Params::KEY_MATERIAL, std::vector(key->encrypted_kek()->cbegin(), key->encrypted_kek()->cend())); + lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(key->recipient_public_key())); + lock.setBytes(Lock::Params::KEY_MATERIAL, toUint8Vector(key->encrypted_kek())); } return; case Capsule::recipients_KeyServerCapsule: @@ -538,7 +538,7 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor return; } lock.pk_type = Lock::PKType::ECC; - lock.setBytes(Lock::Params::RCPT_KEY, std::vector(eccDetails->recipient_public_key()->cbegin(), eccDetails->recipient_public_key()->cend())); + lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(eccDetails->recipient_public_key())); } else { LOG_ERROR("Invalid file format"); return; @@ -547,7 +547,7 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor case KeyDetailsUnion::RsaKeyDetails: if(const RsaKeyDetails *rsaDetails = server->recipient_key_details_as_RsaKeyDetails()) { lock.pk_type = Lock::PKType::RSA; - lock.setBytes(Lock::Params::RCPT_KEY, std::vector(rsaDetails->recipient_public_key()->cbegin(), rsaDetails->recipient_public_key()->cend())); + lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(rsaDetails->recipient_public_key())); } else { LOG_ERROR("Invalid file format"); return; @@ -568,7 +568,7 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor if(const auto *capsule = recipient.capsule_as_recipients_SymmetricKeyCapsule()) { lock.type = Lock::SYMMETRIC_KEY; - lock.setBytes(Lock::SALT, std::vector(capsule->salt()->cbegin(), capsule->salt()->cend())); + lock.setBytes(Lock::SALT, toUint8Vector(capsule->salt())); } return; case Capsule::recipients_PBKDF2Capsule: @@ -579,8 +579,8 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor return; } lock.type = Lock::PASSWORD; - lock.setBytes(Lock::SALT, std::vector(capsule->salt()->cbegin(), capsule->salt()->cend())); - lock.setBytes(Lock::PW_SALT, std::vector(capsule->password_salt()->cbegin(), capsule->password_salt()->cend())); + lock.setBytes(Lock::SALT, toUint8Vector(capsule->salt())); + lock.setBytes(Lock::PW_SALT, toUint8Vector(capsule->password_salt())); lock.setInt(Lock::KDF_ITER, capsule->kdf_iterations()); } return; @@ -605,7 +605,7 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor } std::string urls = join(strs, ";"); LOG_DBG("Keyshare urls: {}", urls); - std::vector salt(capsule->salt()->cbegin(), capsule->salt()->cend()); + std::vector salt = toUint8Vector(capsule->salt()); LOG_DBG("Keyshare salt: {}", toHex(salt)); std::string recipient_id = capsule->recipient_id()->str(); LOG_DBG("Keyshare recipient id: {}", recipient_id); diff --git a/cdoc/CryptoBackend.h b/cdoc/CryptoBackend.h index c1013c0e..56e64475 100644 --- a/cdoc/CryptoBackend.h +++ b/cdoc/CryptoBackend.h @@ -36,8 +36,8 @@ struct Lock; * - decryptRSA for RSA keys * - getSecret for symmetric keys. * - * ECC and symmetric keys have also frontend methods; implementing these allows the program to perform certain cryptographic procedures in controlled - * environment and (in case of symmetric keys) avoid exposing secret keys/passwords. + * ECC and symmetric keys have also frontend methods; implementing these allows the program to perform certain cryptographic procedures in secure + * environment and (in case of symmetric keys) avoid exposing secret keys/passwords to library code. */ struct CDOC_EXPORT CryptoBackend { static constexpr int INVALID_PARAMS = -201; diff --git a/cdoc/NetworkBackend.cpp b/cdoc/NetworkBackend.cpp index 40163de2..3fb52d06 100644 --- a/cdoc/NetworkBackend.cpp +++ b/cdoc/NetworkBackend.cpp @@ -397,8 +397,7 @@ libcdoc::NetworkBackend::fetchKey (std::vector& dst, const std::string& } error = {}; std::string ks = v.get(); - std::vector key_material = fromBase64(ks); - dst.assign(key_material.cbegin(), key_material.cend()); + dst = fromBase64(ks); return libcdoc::OK; } @@ -434,7 +433,7 @@ libcdoc::NetworkBackend::fetchNonce(std::vector& dst, const std::string return NETWORK_ERROR; } std::string nonce_str = v.get(); - dst.assign(nonce_str.cbegin(), nonce_str.cend()); + dst = toUint8Vector(nonce_str); return OK; } diff --git a/cdoc/Utils.h b/cdoc/Utils.h index a0283894..a98e4c83 100644 --- a/cdoc/Utils.h +++ b/cdoc/Utils.h @@ -125,6 +125,16 @@ struct urlEncode { friend std::ostream& operator<<(std::ostream& escaped, urlEncode src); }; +std::vector toUint8Vector(const auto* data) +{ + return {data->cbegin(), data->cend()}; +} + +std::vector toUint8Vector(const auto& data) +{ + return {data.cbegin(), data.cend()}; +} + std::string urlDecode(const std::string &src); } // namespace libcdoc From 777700cc6b799ca2daa90296ec7716f31b4c5d56 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Mon, 2 Feb 2026 17:15:55 +0200 Subject: [PATCH 05/50] Removed some extra check from parsing --- cdoc/CDoc2Reader.cpp | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 514ee251..30cf6831 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -539,29 +539,21 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor } lock.pk_type = Lock::PKType::ECC; lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(eccDetails->recipient_public_key())); - } else { - LOG_ERROR("Invalid file format"); - return; } break; case KeyDetailsUnion::RsaKeyDetails: if(const RsaKeyDetails *rsaDetails = server->recipient_key_details_as_RsaKeyDetails()) { lock.pk_type = Lock::PKType::RSA; lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(rsaDetails->recipient_public_key())); - } else { - LOG_ERROR("Invalid file format"); - return; } break; default: - LOG_ERROR("Unsupported Key Server Details: skipping"); + LOG_ERROR("Unsupported Key Server Details"); return; } lock.type = Lock::Type::SERVER; lock.setString(Lock::Params::KEYSERVER_ID, server->keyserver_id()->str()); lock.setString(Lock::Params::TRANSACTION_ID, server->transaction_id()->str()); - } else { - LOG_ERROR("Invalid file format"); } return; case Capsule::recipients_SymmetricKeyCapsule: @@ -616,7 +608,7 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor } return; default: - LOG_ERROR("Unsupported Key Details: skipping"); + LOG_ERROR("Unsupported capsule type"); } } From 8cdc65201185d1aac1680f073fdd7be9202403d9 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Tue, 3 Feb 2026 15:01:48 +0200 Subject: [PATCH 06/50] Remove INVALID lock type and rename UNSUPPORTED to UNKNOWN --- cdoc/CDoc2Reader.cpp | 2 +- cdoc/Lock.h | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 30cf6831..913adca3 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -678,7 +678,7 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) setLastError({}); for(const auto *recipient: *recipients){ - Private::buildLock(priv->locks.emplace_back(Lock::Type::UNSUPPORTED), *recipient); + Private::buildLock(priv->locks.emplace_back(), *recipient); } } diff --git a/cdoc/Lock.h b/cdoc/Lock.h index 6c91e546..3011f871 100644 --- a/cdoc/Lock.h +++ b/cdoc/Lock.h @@ -43,15 +43,11 @@ struct CDOC_EXPORT Lock * @brief The lock type */ enum Type : unsigned char { - /** - * @brief Invalid value - */ - INVALID, /** * @brief Valid capsule but not supported by this library version * */ - UNSUPPORTED, + UNKNOWN, /** * @brief Symmetric AES key */ @@ -180,7 +176,7 @@ struct CDOC_EXPORT Lock /** * @brief The lock type */ - Type type = Type::INVALID; + Type type = Type::UNKNOWN; /** * @brief algorithm type for public key based locks */ @@ -199,7 +195,7 @@ struct CDOC_EXPORT Lock * @brief check whether lock is valid * @return true if valid */ - bool isValid() const noexcept { return (type != Type::INVALID) && (type != Type::UNSUPPORTED) && !label.empty() && !encrypted_fmk.empty(); } + bool isValid() const noexcept { return (type != Type::UNKNOWN) && !label.empty() && !encrypted_fmk.empty(); } /** * @brief check whether lock is based on symmetric key * @return true if type is SYMMETRIC_KEY or PASSWORD From 5ef36d7c11098c3cbeb4becbcc34c6360093e3f3 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 4 Feb 2026 13:11:42 +0200 Subject: [PATCH 07/50] Return NULL from frontend if CDoc constructor fails --- cdoc/CDoc.cpp | 51 +++++++++++--------------------------------- cdoc/CDoc2Reader.cpp | 2 +- cdoc/Io.h | 2 -- 3 files changed, 14 insertions(+), 41 deletions(-) diff --git a/cdoc/CDoc.cpp b/cdoc/CDoc.cpp index 97e393b0..0be43f8b 100644 --- a/cdoc/CDoc.cpp +++ b/cdoc/CDoc.cpp @@ -103,7 +103,7 @@ libcdoc::CDocReader::createReader(DataSource *src, bool take_ownership, Configur int version = getCDocFileVersion(src); LOG_DBG("CDocReader::createReader: version {}", version); if (src->seek(0) != libcdoc::OK) return nullptr; - CDocReader *reader; + CDocReader *reader = nullptr; if (version == 1) { reader = new CDoc1Reader(src, take_ownership); } else if (version == 2) { @@ -111,12 +111,16 @@ libcdoc::CDocReader::createReader(DataSource *src, bool take_ownership, Configur } else { if(take_ownership) delete src; - return nullptr; - } - reader->conf = conf; - reader->crypto = crypto; + return nullptr; + } + if (!reader->getLastErrorStr().empty()) { + delete reader; + return nullptr; + } + reader->conf = conf; + reader->crypto = crypto; reader->network = network; - return reader; + return reader; } libcdoc::CDocReader * @@ -125,43 +129,14 @@ libcdoc::CDocReader::createReader(const std::string& path, Configuration *conf, if(path.empty()) return nullptr; auto isrc = make_unique(path); - int version = getCDocFileVersion(isrc.get()); - LOG_DBG("CDocReader::createReader: version {}", version); - if (isrc->seek(0) != libcdoc::OK) - return nullptr; - CDocReader *reader; - if (version == 1) { - reader = new CDoc1Reader(isrc.release(), true); - } else if (version == 2) { - reader = new CDoc2Reader(isrc.release(), true); - } else { - return nullptr; - } - reader->conf = conf; - reader->crypto = crypto; - reader->network = network; - return reader; + return createReader(isrc.release(), true, conf, crypto, network); } libcdoc::CDocReader * libcdoc::CDocReader::createReader(std::istream& ifs, Configuration *conf, CryptoBackend *crypto, NetworkBackend *network) { - libcdoc::IStreamSource *isrc = new libcdoc::IStreamSource(&ifs, false); - int version = getCDocFileVersion(isrc); - LOG_DBG("CDocReader::createReader: version {}", version); - CDocReader *reader; - if (version == 1) { - reader = new CDoc1Reader(isrc, true); - } else if (version == 2) { - reader = new CDoc2Reader(isrc, true); - } else { - delete isrc; - return nullptr; - } - reader->conf = conf; - reader->crypto = crypto; - reader->network = network; - return reader; + auto isrc = make_unique(&ifs, false); + return createReader(isrc.release(), true, conf, crypto, network); } #if LIBCDOC_TESTING diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 913adca3..6b83dbc9 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -588,7 +588,7 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor } /* url,share_id;url,share_id... */ std::vector strs; - for (auto cshare = capsule->shares()->cbegin(); cshare != capsule->shares()->cend(); ++cshare) { + for (auto cshare : *capsule->shares()) { std::string id = cshare->share_id()->str(); std::string url = cshare->server_base_url()->str(); std::string str = url + "," + id; diff --git a/cdoc/Io.h b/cdoc/Io.h index 45902101..dfc04199 100644 --- a/cdoc/Io.h +++ b/cdoc/Io.h @@ -259,8 +259,6 @@ struct CDOC_EXPORT IStreamSource : public DataSource { if(_ifs->bad()) return INPUT_STREAM_ERROR; _ifs->clear(); _ifs->seekg(pos); - //std::cerr << "Stream bad:" << _ifs->bad() << " eof:" << _ifs->eof() << " fail:" << _ifs->fail() << std::endl; - //std::cerr << "tell:" << _ifs->tellg() << std::endl; return bool(_ifs->bad()) ? INPUT_STREAM_ERROR : OK; } From 625c76826fc4f0ca33affda006dfadda5656ea90 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 11 Feb 2026 10:48:03 +0200 Subject: [PATCH 08/50] Remove label creation from Java test --- examples/java/src/main/java/ee/ria/cdoc/CDocTool.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java index edfcd382..fabc9d02 100644 --- a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java +++ b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java @@ -363,8 +363,7 @@ static void locks(String path) { static void test() { System.err.println("Testing label generation"); - String label = Recipient.buildLabel(new String[] {"Alpha", "1", "Beta", "2", "Gamma", "3", "Delta"}); - System.err.format("Label: %s\n", label); + String label = "data:v=1&type=ID-card&serial_number=PNOEE-38001085718&cn=J%C3%95EORG%2CJAAK-KRISTJAN%2C38001085718"; java.util.Map map = Recipient.parseLabel(label); for (String key : map.keySet()) { System.err.format(" %s:%s\n", key, map.get(key)); From 372f7f7842982d8dc0ddf81b2ab256c5c162f0a4 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 12 Feb 2026 13:40:54 +0200 Subject: [PATCH 09/50] Keep Java references in CDocReader and CDocWriter to prevent premature GC --- libcdoc.i | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/libcdoc.i b/libcdoc.i index 3b65a7cb..7e800a76 100644 --- a/libcdoc.i +++ b/libcdoc.i @@ -103,7 +103,7 @@ // CDocReader // -// Custom wrapper do away with const qualifiers +// Custom wrapper to do away with const qualifiers %extend libcdoc::CDocReader { std::vector getLocks() { const std::vector &locks = $self->getLocks(); @@ -505,7 +505,14 @@ static std::vector SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *jen %typemap(javadirectorin) std::string_view "$jniinput" // No return of std::string_view so no javadirectorout +// CDocReader + %typemap(javacode) libcdoc::CDocReader %{ + // Keep Java references to prevent GC deleting these prematurely + private Configuration config; + private CryptoBackend crypto; + private NetworkBackend network; + public void readFile(java.io.OutputStream ofs) throws CDocException, java.io.IOException { byte[] buf = new byte[1024]; long result = readData(buf); @@ -516,6 +523,37 @@ static std::vector SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *jen } %} +%typemap(javaout) libcdoc::CDocReader * libcdoc::CDocReader::createReader { + long cPtr = $jnicall; + if (cPtr == 0) return null; + CDocReader rdr = new CDocReader(cPtr, true); + // Set Java references + rdr.config = conf; + rdr.crypto = crypto; + rdr.network = network; + return rdr; +} + +// CDocWriter + +%typemap(javacode) libcdoc::CDocWriter %{ + // Keep Java references to prevent GC deleting these prematurely + private Configuration config; + private CryptoBackend crypto; + private NetworkBackend network; +%} + +%typemap(javaout) libcdoc::CDocWriter * libcdoc::CDocWriter::createWriter { + long cPtr = $jnicall; + if (cPtr == 0) return null; + CDocWriter wrtr = new CDocWriter(cPtr, true); + // Set Java references + wrtr.config = conf; + wrtr.crypto = crypto; + wrtr.network = network; + return wrtr; +} + %typemap(javacode) libcdoc::Configuration %{ public static final String KEYSERVER_SEND_URL = "KEYSERVER_SEND_URL"; public static final String KEYSERVER_FETCH_URL = "KEYSERVER_FETCH_URL"; From 4dc229f73929d4f839c9218fa864ef204e027ac4 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 13 Feb 2026 13:21:16 +0200 Subject: [PATCH 10/50] Cleaned up logging --- cdoc/CDoc.cpp | 24 +++- cdoc/CDoc.h | 44 +++++++ cdoc/CMakeLists.txt | 1 - cdoc/ConsoleLogger.h | 9 +- cdoc/Crypto.cpp | 2 +- cdoc/ILogger.h | 119 +++++------------- cdoc/LogEngine.cpp | 110 ---------------- cdoc/cdoc-tool.cpp | 6 +- .../src/main/java/ee/ria/cdoc/CDocTool.java | 110 ++++++++++++++-- 9 files changed, 207 insertions(+), 218 deletions(-) delete mode 100644 cdoc/LogEngine.cpp diff --git a/cdoc/CDoc.cpp b/cdoc/CDoc.cpp index 0be43f8b..37cfb832 100644 --- a/cdoc/CDoc.cpp +++ b/cdoc/CDoc.cpp @@ -21,7 +21,7 @@ #include "CDoc2Writer.h" #include "CDoc2Reader.h" #include "Configuration.h" -#include "ILogger.h" +#include "ConsoleLogger.h" #include "Io.h" #include "NetworkBackend.h" @@ -71,6 +71,28 @@ getVersion() return VERSION_STR; } +static Logger * +getDefaultLogger() +{ + static ConsoleLogger clogger; + return &clogger; +} + +static Logger *sys_logger = nullptr; + +void +setLogger(Logger *logger) +{ + sys_logger = logger; +} + +void +log(LogLevel level, std::string_view file, int line, std::string_view msg) +{ + Logger *logger = (sys_logger) ? sys_logger : getDefaultLogger(); + logger->log(level, file, line, msg); +} + int libcdoc::CDocReader::getCDocFileVersion(DataSource *src) { diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 7bad17c7..4b048e36 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -134,6 +134,50 @@ CDOC_EXPORT std::string getErrorStr(int64_t code); CDOC_EXPORT std::string getVersion(); +// Logging interface + +class ILogger; +typedef ILogger Logger; + +/** + * @brief Log-level enumeration to indicate severity of the log message. + */ +enum LogLevel +{ + /** + * @brief Most critical level. Application is about to abort. + */ + LEVEL_FATAL, + + /** + * @brief Errors where functionality has failed or an exception have been caught. + */ + LEVEL_ERROR, + + /** + * @brief Warnings about validation issues or temporary failures that can be recovered. + */ + LEVEL_WARNING, + + /** + * @brief Information that highlights progress or application lifetime events. + */ + LEVEL_INFO, + + /** + * @brief Debugging the application behavior from internal events of interest. + */ + LEVEL_DEBUG, + + /** + * @brief Most verbose level. Used for development, NOP in production code. + */ + LEVEL_TRACE +}; + +CDOC_EXPORT void setLogger(Logger *logger); +CDOC_EXPORT void log(LogLevel level, std::string_view file, int line, std::string_view msg); + /** * @brief A simple container of file name and size * diff --git a/cdoc/CMakeLists.txt b/cdoc/CMakeLists.txt index 97319077..f55a826a 100644 --- a/cdoc/CMakeLists.txt +++ b/cdoc/CMakeLists.txt @@ -38,7 +38,6 @@ add_library(cdoc CryptoBackend.cpp NetworkBackend.cpp PKCS11Backend.cpp - LogEngine.cpp $<$:WinBackend.cpp> Certificate.cpp Certificate.h Crypto.cpp Crypto.h diff --git a/cdoc/ConsoleLogger.h b/cdoc/ConsoleLogger.h index 06061d7f..c1302b8f 100644 --- a/cdoc/ConsoleLogger.h +++ b/cdoc/ConsoleLogger.h @@ -35,14 +35,11 @@ namespace libcdoc class ConsoleLogger : public ILogger { public: - virtual void LogMessage(LogLevel level, std::string_view file, int line, std::string_view message) override + virtual void logMessage(LogLevel level, std::string_view file, int line, std::string_view message) override { // We ignore by default the file name and line number, and call LogMessage with the level and message. - if (level <= minLogLevel) - { - std::ostream& ofs = (level == LEVEL_INFO) ? std::cout : std::cerr; - ofs << message << '\n'; - } + std::ostream& ofs = (level == LEVEL_INFO) ? std::cout : std::cerr; + ofs << message << '\n'; } }; diff --git a/cdoc/Crypto.cpp b/cdoc/Crypto.cpp index 9f902ba5..bf796ef1 100644 --- a/cdoc/Crypto.cpp +++ b/cdoc/Crypto.cpp @@ -426,7 +426,7 @@ void Crypto::LogSslError(const char* funcName, const char* file, int line) while (errorCode != 0) { ERR_error_string_n(errorCode, sslErrorStr, errorStrBufLen); - ILogger::getLogger()->LogMessage(ILogger::LEVEL_ERROR, file, line, FORMAT("{} failed: {}", funcName, sslErrorStr)); + LOG_ERROR("{} failed: {}", funcName, sslErrorStr); // Get next error code errorCode = ERR_get_error(); diff --git a/cdoc/ILogger.h b/cdoc/ILogger.h index 3bf1c72c..e66acc81 100644 --- a/cdoc/ILogger.h +++ b/cdoc/ILogger.h @@ -31,6 +31,8 @@ namespace fmt = std; #include "fmt/format.h" #endif +#include + #define FORMAT fmt::format namespace libcdoc @@ -42,62 +44,19 @@ namespace libcdoc class CDOC_EXPORT ILogger { public: - /** - * @brief Log-level enumeration to indicate severity of the log message. - */ - enum LogLevel - { - /** - * @brief Most critical level. Application is about to abort. - */ - LEVEL_FATAL, - - /** - * @brief Errors where functionality has failed or an exception have been caught. - */ - LEVEL_ERROR, - - /** - * @brief Warnings about validation issues or temporary failures that can be recovered. - */ - LEVEL_WARNING, - - /** - * @brief Information that highlights progress or application lifetime events. - */ - LEVEL_INFO, - - /** - * @brief Debugging the application behavior from internal events of interest. - */ - LEVEL_DEBUG, - - /** - * @brief Most verbose level. Used for development, NOP in production code. - */ - LEVEL_TRACE - }; - - ILogger() : minLogLevel(LEVEL_WARNING) {} - virtual ~ILogger() {} - /** * @brief Logs given message with given severity, file name and line number. + * + * It tests the log level and if <= min_level invokes logMessage + * * @param level Severity of the log message. * @param file File name where the log message was recorded. * @param line Line number in the file where the log message was recorded. - * @param message The log message. - * - * Every class implementing the ILogger interface must implement the member function. - * Default implementation does nothing. + * @param msg The log message. */ - virtual void LogMessage(LogLevel level, std::string_view file, int line, std::string_view message) {} - - /** - * @brief Returns current minimum log level of the logger. - * @return Minimum log level. - */ - LogLevel GetMinLogLevel() const noexcept { return minLogLevel; } + void log(LogLevel level, std::string_view file, int line, std::string_view msg) { + if (level <= min_level) logMessage(level, file, line, msg); + } /** * @brief Sets minimum log level for the logger. @@ -107,68 +66,56 @@ class CDOC_EXPORT ILogger * to LogLevelInfo (default), then LogLevelFatal, LogLevelError, LogLevelWarning and LogLevelInfo * messages are logged, but not LogLevelDebug or LogLevelTrace messages. */ - void SetMinLogLevel(LogLevel level) noexcept { minLogLevel = level; } - + void setMinLogLevel(LogLevel level) noexcept { min_level = level; } +protected: /** - * @brief Adds ILogger implementation to logging queue. + * @brief Logs given message with given severity, file name and line number. * - * This function does not take ownership of the logger's instance. - * It is up to the caller to free the resources of the logger's instance and - * keep it alive until removed from the queue. + * Every class implementing the ILogger interface must implement this member function. + * The efault implementation does nothing. + * The level should be checked by caller, thus the implementation should expect that level <= min_level * - * @param logger Logger's instance to be added. - * @return Unique cookie identifying the logger's instance in the logging queue. - */ - static int addLogger(ILogger* logger); - - /** - * @brief Removes logger's instance from the logging queue. - * @param cookie Unique cookie returned by the add_logger function when the logger was added. - * @return Pointer to ILogger object that is removed. It's up to user to free the resources. - */ - static ILogger* removeLogger(int cookie); - - /** - * @brief Returns global logger's instance. - * @return Global logger's instance. + * @param level Severity of the log message. + * @param file File name where the log message was recorded. + * @param line Line number in the file where the log message was recorded. + * @param msg The log message. */ - static ILogger* getLogger(); + virtual void logMessage(LogLevel level, std::string_view file, int line, std::string_view msg) {} - static void setLogger(ILogger *logger); - -protected: /** * @brief Minimum level of log messages to log. */ - LogLevel minLogLevel; + LogLevel min_level = LEVEL_WARNING; }; +typedef ILogger Logger; + #ifndef SWIG template -static inline void LogFormat(ILogger::LogLevel level, std::string_view file, int line, fmt::format_string fmt, Args&&... args) +static inline void LogFormat(LogLevel level, std::string_view file, int line, fmt::format_string fmt, Args&&... args) { auto msg = fmt::format(fmt, std::forward(args)...); - ILogger::getLogger()->LogMessage(level, file, line, msg); + libcdoc::log(level, file, line, msg); } -static inline void LogFormat(ILogger::LogLevel level, std::string_view file, int line, std::string_view msg) +static inline void LogFormat(LogLevel level, std::string_view file, int line, std::string_view msg) { - ILogger::getLogger()->LogMessage(level, file, line, msg); + libcdoc::log(level, file, line, msg); } #endif #define LOG(l,...) LogFormat((l), __FILE__, __LINE__, __VA_ARGS__) -#define LOG_ERROR(...) LogFormat(libcdoc::ILogger::LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__) -#define LOG_WARN(...) LogFormat(libcdoc::ILogger::LEVEL_WARNING, __FILE__, __LINE__, __VA_ARGS__) -#define LOG_INFO(...) LogFormat(libcdoc::ILogger::LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__) -#define LOG_DBG(...) LogFormat(libcdoc::ILogger::LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_ERROR(...) LogFormat(libcdoc::LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_WARN(...) LogFormat(libcdoc::LEVEL_WARNING, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_INFO(...) LogFormat(libcdoc::LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_DBG(...) LogFormat(libcdoc::LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__) #ifdef NDEBUG #define LOG_TRACE(...) #define LOG_TRACE_KEY(MSG, KEY) #else -#define LOG_TRACE(...) LogFormat(libcdoc::ILogger::LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__) -#define LOG_TRACE_KEY(MSG, KEY) LogFormat(libcdoc::ILogger::LEVEL_TRACE, __FILE__, __LINE__, MSG, toHex(KEY)) +#define LOG_TRACE(...) LogFormat(libcdoc::LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_TRACE_KEY(MSG, KEY) LogFormat(libcdoc::LEVEL_TRACE, __FILE__, __LINE__, MSG, toHex(KEY)) #endif } diff --git a/cdoc/LogEngine.cpp b/cdoc/LogEngine.cpp deleted file mode 100644 index e6bc69cc..00000000 --- a/cdoc/LogEngine.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * libcdoc - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#include "ILogger.h" - -#include -#include - -using namespace std; - -namespace libcdoc -{ -/** - * @brief Logging Engine implementation. - * - * The Logging Engine holds all instances of registered loggers and - * logs a log message to all the instances. - */ -struct LogEngine final : public ILogger -{ - void LogMessage(libcdoc::ILogger::LogLevel level, std::string_view file, int line, std::string_view message) final - { - lock_guard guard(loggers_protector); - for (const auto &[_, logger] : loggers) - { - logger->LogMessage(level, file, line, message); - } - } - - int AddLogger(ILogger* logger) - { - lock_guard guard(loggers_protector); - loggers[++currentLoggerCookie] = logger; - return currentLoggerCookie; - } - - ILogger* RemoveLogger(int cookie) - { - lock_guard guard(loggers_protector); - ILogger* tmp = loggers[cookie]; - loggers.erase(cookie); - return tmp; - } - - void setLogger(ILogger *logger) { - lock_guard guard(loggers_protector); - while (!loggers.empty()) { - delete loggers.begin()->second; - loggers.erase(loggers.begin()->first); - } - loggers[0] = logger; - } - -private: - // Current Cookie value - int currentLoggerCookie = 0; - - // The map with registered loggers. - map loggers; - - // Loggers map concurrency protector - mutex loggers_protector; -}; - -// Default logger's instance - Logging Engine instance. -static LogEngine defaultLogEngine; - -// It is essential to define shared functions and variables with namespace. Otherwise, the linker won't find them. - -int -ILogger::addLogger(ILogger* logger) -{ - return defaultLogEngine.AddLogger(logger); -} - -ILogger* -ILogger::removeLogger(int cookie) -{ - return defaultLogEngine.RemoveLogger(cookie); -} - -ILogger* -ILogger::getLogger() -{ - return &defaultLogEngine; -} - -void -ILogger::setLogger(ILogger *logger) -{ - defaultLogEngine.setLogger(logger); -} - - -} diff --git a/cdoc/cdoc-tool.cpp b/cdoc/cdoc-tool.cpp index c00495fb..9cd8403c 100644 --- a/cdoc/cdoc-tool.cpp +++ b/cdoc/cdoc-tool.cpp @@ -653,8 +653,8 @@ int main(int argc, char *argv[]) // Add console logger by default ConsoleLogger console_logger; - console_logger.SetMinLogLevel(ILogger::LEVEL_TRACE); - int cookie = ILogger::addLogger(&console_logger); + console_logger.setMinLogLevel(LEVEL_TRACE); + libcdoc::setLogger(&console_logger); string_view command(argv[1]); LOG_INFO("Command: {}", command); @@ -678,6 +678,6 @@ int main(int argc, char *argv[]) print_usage(cout); } - ILogger::removeLogger(cookie); + setLogger(nullptr); return retVal; } diff --git a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java index fabc9d02..837d7ca9 100644 --- a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java +++ b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java @@ -6,6 +6,7 @@ import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.HashMap; import java.util.Collection; @@ -33,11 +34,12 @@ public static String getArg(int arg_idx, String[] args) { } // Make logger static to ensure that it is not garbage-collected as long as it is atached to library - private static JavaLogger logger; + private static ILogger logger; public static void main(String[] args) { System.out.println("Starting app..."); + LogLevel log_level = LogLevel.LEVEL_INFO; String library = "../../build/macos/cdoc/libcdoc_javad.jnilib"; Action action = Action.INVALID; ArrayList files = new ArrayList<>(); @@ -119,6 +121,13 @@ public static void main(String[] args) { System.exit(1); } servers = args[i].split(","); + } else if (args[i].equals("--log-level")) { + i += 1; + if (i >= args.length) { + System.err.println("Invalid arguments"); + System.exit(1); + } + log_level = LogLevel.valueOf(args[i]); } else if (!args[i].startsWith("--")) { files.add(args[i]); } @@ -128,11 +137,11 @@ public static void main(String[] args) { System.load(lib.getAbsolutePath()); System.out.println("Library loaded"); - logger = new JavaLogger(); - //ConsoleLogger logger = new ConsoleLogger(); - logger.SetMinLogLevel(ILogger.LogLevel.LEVEL_TRACE); - ILogger.addLogger(logger); - ILogger.getLogger().LogMessage(ILogger.LogLevel.LEVEL_DEBUG, "FILENAME", 0, "Starting CDocTool.java"); + //logger = new JavaLogger(); + logger = new ConsoleLogger(); + logger.setMinLogLevel(log_level); + CDoc.setLogger(logger); + CDoc.log(LogLevel.LEVEL_DEBUG, "FILENAME", 0, "Starting CDocTool.java"); switch (action) { case ENCRYPT: @@ -373,15 +382,19 @@ static void test() { System.err.println("Creating ToolConf..."); ToolConf conf = new ToolConf(); + + System.err.println("Creating ToolCrypto"); + ToolCrypto crypto = new ToolCrypto(); + + System.err.println("Creating ToolNetwork"); + ToolNetwork network = new ToolNetwork(); + System.err.println("Creating DataBuffer..."); DataBuffer buf = new DataBuffer(); byte[] bytes = {1, 2, 3}; buf.setData(bytes); System.err.format("Buffer: %s\n", hex.formatHex(buf.getData())); - System.err.println("Creating ToolNetwork"); - ToolNetwork network = new ToolNetwork(); - System.err.println("Creating reader: " + path); CDocReader rdr = CDocReader.createReader(path, conf, null, network); System.err.format("Reader created (version %d)\n", rdr.getVersion()); @@ -397,9 +410,77 @@ static void test() { byte[] cert = certs.getCertificate(i); System.err.format(" %s\n", hex.formatHex(cert)); } + + testRW(conf, crypto); + System.err.println("Success"); } + static void testRW(ToolConf conf, ToolCrypto crypto) { + try { + final String container = "testfile.cdoc"; + final String password = "TereTalv!"; + final String label = "Jaanus Jõhvikas"; + + crypto.setPassword(password); + DataBuffer rnd = new DataBuffer(); + crypto.random(rnd, 57); + System.gc(); + final byte[] datain = rnd.getData(); + System.out.format("Data IN: %s\n", hex.formatHex(datain)); + + CDocWriter wrtr = CDocWriter.createWriter(2, container, conf, crypto, null); + System.gc(); + Recipient rcpt = Recipient.makeSymmetric(label, 65535); + long result = wrtr.addRecipient(rcpt); + System.gc(); + System.out.format("addRecipient: %d\n", result); + result = wrtr.beginEncryption(); + System.gc(); + System.out.format("beginEncryption: %d\n", result); + final String name = "kala"; + System.out.format("Adding file %s\n", name); + result = wrtr.addFile(name, datain.length); + System.gc(); + System.out.format("addFile: %d\n", result); + result = wrtr.writeData(datain); + System.gc(); + System.out.format("writeData: %d\n", result); + result = wrtr.finishEncryption(); + System.gc(); + System.out.format("finishEncryption: %d\n", result); + + CDocReader rdr = CDocReader.createReader(container, conf, crypto, null); + System.gc(); + System.out.format("Reader created (version %d)\n", rdr.getVersion()); + LockVector locks = rdr.getLocks(); + System.gc(); + ee.ria.cdoc.Lock lock = locks.get(0); + System.out.format("Lock: %s\n", lock.getLabel()); + byte[] fmk = rdr.getFMK(0); + System.gc(); + System.out.format("FMK is: %s\n", hex.formatHex(fmk)); + rdr.beginDecryption(fmk); + System.gc(); + FileInfo fi = new FileInfo(); + result = rdr.nextFile(fi); + System.gc(); + System.out.format("nextFile result: %d\n", result); + while (result == CDoc.OK) { + System.out.format("File %s length %d\n", fi.getName(), fi.getSize()); + byte[] dataout = new byte[(int) fi.getSize()]; + rdr.readData(dataout); + System.gc(); + System.out.format("Data OUT: %s\n", hex.formatHex(dataout)); + result = rdr.nextFile(fi); + } + rdr.finishDecryption(); + System.gc(); + } catch (CDocException exc) { + System.err.format("CDoc Exception %d: %s\n", exc.code, exc.getMessage()); + } + } + private static class ToolConf extends Configuration { public final HashMap values = new HashMap<>(); @@ -457,6 +538,15 @@ void setPassword(String password) { this.secret = password.getBytes(); } + @Override + public long random(DataBuffer dst, int size) throws CDocException { + SecureRandom random = new SecureRandom(); + byte bytes[] = new byte[size]; + random.nextBytes(bytes); + dst.setData(bytes); + return CDoc.OK; + } + @Override public long getSecret(DataBuffer dst, int idx) { dst.setData(secret); @@ -502,7 +592,7 @@ public long test(CertificateList dst) { private static class JavaLogger extends ILogger { @Override - public void LogMessage(ILogger.LogLevel level, String file, int line, String message) { + public void logMessage(LogLevel level, String file, int line, String message) { System.out.format("%s:%s %s %s\n", file, line, level, message); } } From 209042f0c756258aa08f6c47a2b858fc34c176e0 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 13 Feb 2026 14:24:19 +0200 Subject: [PATCH 11/50] Renamed ILogger -> Logger --- cdoc/CDoc.cpp | 3 +- cdoc/CDoc.h | 10 +- cdoc/CDoc1Reader.cpp | 1 - cdoc/CDoc1Writer.cpp | 1 - cdoc/CDoc2Reader.cpp | 1 - cdoc/CDoc2Writer.cpp | 1 - cdoc/CDocCipher.cpp | 1 - cdoc/CMakeLists.txt | 3 +- cdoc/Configuration.cpp | 1 - cdoc/ConsoleLogger.h | 47 ------- cdoc/Crypto.cpp | 1 - cdoc/CryptoBackend.cpp | 1 - cdoc/ILogger.h | 123 ------------------ cdoc/KeyShares.cpp | 1 - cdoc/Lock.cpp | 1 - cdoc/NetworkBackend.cpp | 1 - cdoc/PKCS11Backend.cpp | 1 - cdoc/Recipient.cpp | 1 - cdoc/Utils.cpp | 2 - cdoc/Utils.h | 42 ++++++ cdoc/cdoc-tool.cpp | 3 +- .../src/main/java/ee/ria/cdoc/CDocTool.java | 21 --- libcdoc.i | 7 +- 23 files changed, 50 insertions(+), 224 deletions(-) delete mode 100644 cdoc/ConsoleLogger.h delete mode 100644 cdoc/ILogger.h diff --git a/cdoc/CDoc.cpp b/cdoc/CDoc.cpp index 37cfb832..f98ef8bb 100644 --- a/cdoc/CDoc.cpp +++ b/cdoc/CDoc.cpp @@ -21,9 +21,10 @@ #include "CDoc2Writer.h" #include "CDoc2Reader.h" #include "Configuration.h" -#include "ConsoleLogger.h" #include "Io.h" #include "NetworkBackend.h" +#include "Utils.h" +#include "Logger.h" namespace libcdoc { diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 4b048e36..7e1e2e8f 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -24,11 +24,6 @@ #include #include -#ifndef LIBCDOC_TESTING -// Remove this in production code -#define LIBCDOC_TESTING 1 -#endif - namespace libcdoc { /** @@ -136,9 +131,6 @@ CDOC_EXPORT std::string getVersion(); // Logging interface -class ILogger; -typedef ILogger Logger; - /** * @brief Log-level enumeration to indicate severity of the log message. */ @@ -175,6 +167,8 @@ enum LogLevel LEVEL_TRACE }; +class Logger; + CDOC_EXPORT void setLogger(Logger *logger); CDOC_EXPORT void log(LogLevel level, std::string_view file, int line, std::string_view msg); diff --git a/cdoc/CDoc1Reader.cpp b/cdoc/CDoc1Reader.cpp index ef3c3569..9d293b51 100644 --- a/cdoc/CDoc1Reader.cpp +++ b/cdoc/CDoc1Reader.cpp @@ -23,7 +23,6 @@ #include "Crypto.h" #include "CryptoBackend.h" #include "DDocReader.h" -#include "ILogger.h" #include "Lock.h" #include "Utils.h" #include "XmlReader.h" diff --git a/cdoc/CDoc1Writer.cpp b/cdoc/CDoc1Writer.cpp index 988a7a43..18847fc5 100644 --- a/cdoc/CDoc1Writer.cpp +++ b/cdoc/CDoc1Writer.cpp @@ -20,7 +20,6 @@ #include "Crypto.h" #include "DDocWriter.h" -#include "ILogger.h" #include "Recipient.h" #include "Utils.h" #include "XmlWriter.h" diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index d10e6ac3..23ab5781 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -22,7 +22,6 @@ #include "Configuration.h" #include "CryptoBackend.h" #include "CDoc2.h" -#include "ILogger.h" #include "KeyShares.h" #include "Lock.h" #include "NetworkBackend.h" diff --git a/cdoc/CDoc2Writer.cpp b/cdoc/CDoc2Writer.cpp index ba7f886e..a79271b6 100644 --- a/cdoc/CDoc2Writer.cpp +++ b/cdoc/CDoc2Writer.cpp @@ -21,7 +21,6 @@ #include "Configuration.h" #include "Crypto.h" #include "CDoc2.h" -#include "ILogger.h" #include "NetworkBackend.h" #include "Recipient.h" #include "Tar.h" diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index b1f3230d..c94efe9f 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -19,7 +19,6 @@ #include "CDocCipher.h" #include "CDocReader.h" #include "CDoc2.h" -#include "ILogger.h" #include "Lock.h" #include "NetworkBackend.h" #include "PKCS11Backend.h" diff --git a/cdoc/CMakeLists.txt b/cdoc/CMakeLists.txt index f55a826a..61e43697 100644 --- a/cdoc/CMakeLists.txt +++ b/cdoc/CMakeLists.txt @@ -15,8 +15,7 @@ set(PUBLIC_HEADERS CryptoBackend.h NetworkBackend.h PKCS11Backend.h - ILogger.h - ConsoleLogger.h + Logger.h ) add_library(cdoc_ver INTERFACE) diff --git a/cdoc/Configuration.cpp b/cdoc/Configuration.cpp index 0e4973c6..eee0586d 100644 --- a/cdoc/Configuration.cpp +++ b/cdoc/Configuration.cpp @@ -20,7 +20,6 @@ #include "Configuration.h" -#include "ILogger.h" #include "Utils.h" #include "json/picojson/picojson.h" diff --git a/cdoc/ConsoleLogger.h b/cdoc/ConsoleLogger.h deleted file mode 100644 index c1302b8f..00000000 --- a/cdoc/ConsoleLogger.h +++ /dev/null @@ -1,47 +0,0 @@ -/* - * libcdoc - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#pragma once - -#include "ILogger.h" - -#include - -namespace libcdoc -{ - -/** - * @brief Console logger - * - * An ILogger subclass that logs text to console. - * - * Info messages are logged to cout, all others to cerr. - */ -class ConsoleLogger : public ILogger -{ -public: - virtual void logMessage(LogLevel level, std::string_view file, int line, std::string_view message) override - { - // We ignore by default the file name and line number, and call LogMessage with the level and message. - std::ostream& ofs = (level == LEVEL_INFO) ? std::cout : std::cerr; - ofs << message << '\n'; - } -}; - - -} diff --git a/cdoc/Crypto.cpp b/cdoc/Crypto.cpp index bf796ef1..d8adecd1 100644 --- a/cdoc/Crypto.cpp +++ b/cdoc/Crypto.cpp @@ -18,7 +18,6 @@ #include "CDoc.h" #include "Crypto.h" -#include "ILogger.h" #include "Utils.h" #define OPENSSL_SUPPRESS_DEPRECATED diff --git a/cdoc/CryptoBackend.cpp b/cdoc/CryptoBackend.cpp index 8c0f1652..56bc5ccd 100644 --- a/cdoc/CryptoBackend.cpp +++ b/cdoc/CryptoBackend.cpp @@ -18,7 +18,6 @@ #include "Crypto.h" #include "CryptoBackend.h" -#include "ILogger.h" #include "Utils.h" #define OPENSSL_SUPPRESS_DEPRECATED diff --git a/cdoc/ILogger.h b/cdoc/ILogger.h deleted file mode 100644 index e66acc81..00000000 --- a/cdoc/ILogger.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * libcdoc - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - */ - -#ifndef __ILOGGER_H__INCLUDED__ -#define __ILOGGER_H__INCLUDED__ - -#include - -#include - -#ifdef __cpp_lib_format -#include -namespace fmt = std; -#else -#define FMT_HEADER_ONLY -#include "fmt/format.h" -#endif - -#include - -#define FORMAT fmt::format - -namespace libcdoc -{ - -/** - * @brief Generic interface to implement a logger. - */ -class CDOC_EXPORT ILogger -{ -public: - /** - * @brief Logs given message with given severity, file name and line number. - * - * It tests the log level and if <= min_level invokes logMessage - * - * @param level Severity of the log message. - * @param file File name where the log message was recorded. - * @param line Line number in the file where the log message was recorded. - * @param msg The log message. - */ - void log(LogLevel level, std::string_view file, int line, std::string_view msg) { - if (level <= min_level) logMessage(level, file, line, msg); - } - - /** - * @brief Sets minimum log level for the logger. - * @param level minimum level to log. - * - * Sets minimum level of log messages to log. For example, if the minimum log level is set - * to LogLevelInfo (default), then LogLevelFatal, LogLevelError, LogLevelWarning and LogLevelInfo - * messages are logged, but not LogLevelDebug or LogLevelTrace messages. - */ - void setMinLogLevel(LogLevel level) noexcept { min_level = level; } -protected: - /** - * @brief Logs given message with given severity, file name and line number. - * - * Every class implementing the ILogger interface must implement this member function. - * The efault implementation does nothing. - * The level should be checked by caller, thus the implementation should expect that level <= min_level - * - * @param level Severity of the log message. - * @param file File name where the log message was recorded. - * @param line Line number in the file where the log message was recorded. - * @param msg The log message. - */ - virtual void logMessage(LogLevel level, std::string_view file, int line, std::string_view msg) {} - - /** - * @brief Minimum level of log messages to log. - */ - LogLevel min_level = LEVEL_WARNING; -}; - -typedef ILogger Logger; - -#ifndef SWIG -template -static inline void LogFormat(LogLevel level, std::string_view file, int line, fmt::format_string fmt, Args&&... args) -{ - auto msg = fmt::format(fmt, std::forward(args)...); - libcdoc::log(level, file, line, msg); -} - -static inline void LogFormat(LogLevel level, std::string_view file, int line, std::string_view msg) -{ - libcdoc::log(level, file, line, msg); -} -#endif - -#define LOG(l,...) LogFormat((l), __FILE__, __LINE__, __VA_ARGS__) -#define LOG_ERROR(...) LogFormat(libcdoc::LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__) -#define LOG_WARN(...) LogFormat(libcdoc::LEVEL_WARNING, __FILE__, __LINE__, __VA_ARGS__) -#define LOG_INFO(...) LogFormat(libcdoc::LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__) -#define LOG_DBG(...) LogFormat(libcdoc::LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__) - -#ifdef NDEBUG -#define LOG_TRACE(...) -#define LOG_TRACE_KEY(MSG, KEY) -#else -#define LOG_TRACE(...) LogFormat(libcdoc::LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__) -#define LOG_TRACE_KEY(MSG, KEY) LogFormat(libcdoc::LEVEL_TRACE, __FILE__, __LINE__, MSG, toHex(KEY)) -#endif - -} - -#endif diff --git a/cdoc/KeyShares.cpp b/cdoc/KeyShares.cpp index d7996e6d..06d48875 100644 --- a/cdoc/KeyShares.cpp +++ b/cdoc/KeyShares.cpp @@ -23,7 +23,6 @@ #include "Crypto.h" #include "CryptoBackend.h" #include "NetworkBackend.h" -#include "ILogger.h" #include "Utils.h" #include "json/jwt.h" diff --git a/cdoc/Lock.cpp b/cdoc/Lock.cpp index 53d8affd..b5be546d 100644 --- a/cdoc/Lock.cpp +++ b/cdoc/Lock.cpp @@ -20,7 +20,6 @@ #include "Certificate.h" #include "Utils.h" -#include "ILogger.h" namespace libcdoc { diff --git a/cdoc/NetworkBackend.cpp b/cdoc/NetworkBackend.cpp index 3fb52d06..3382e3be 100644 --- a/cdoc/NetworkBackend.cpp +++ b/cdoc/NetworkBackend.cpp @@ -22,7 +22,6 @@ #include "CryptoBackend.h" #include "Utils.h" #include "utils/memory.h" -#include "ILogger.h" #define OPENSSL_SUPPRESS_DEPRECATED diff --git a/cdoc/PKCS11Backend.cpp b/cdoc/PKCS11Backend.cpp index 4d201769..914004c8 100644 --- a/cdoc/PKCS11Backend.cpp +++ b/cdoc/PKCS11Backend.cpp @@ -19,7 +19,6 @@ #include "PKCS11Backend.h" #include "Certificate.h" #include "Crypto.h" -#include "ILogger.h" #include "Utils.h" #include "pkcs11.h" diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 16ad32c4..0e699844 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -21,7 +21,6 @@ #include "CDoc2.h" #include "Certificate.h" #include "Crypto.h" -#include "ILogger.h" #include "Utils.h" #include diff --git a/cdoc/Utils.cpp b/cdoc/Utils.cpp index 886035cb..27a83807 100644 --- a/cdoc/Utils.cpp +++ b/cdoc/Utils.cpp @@ -18,8 +18,6 @@ #include "Utils.h" -#include "ILogger.h" - #include "json/jwt.h" #include "json/picojson/picojson.h" diff --git a/cdoc/Utils.h b/cdoc/Utils.h index a98e4c83..ff0d4452 100644 --- a/cdoc/Utils.h +++ b/cdoc/Utils.h @@ -25,6 +25,20 @@ #include #include +#include + +#ifdef __cpp_lib_format +#include +namespace fmt = std; +#else +#define FMT_HEADER_ONLY +#include "fmt/format.h" +#endif + +#include + +#define FORMAT fmt::format + namespace libcdoc { std::string toBase64(const uint8_t *data, size_t len); @@ -137,6 +151,34 @@ std::vector toUint8Vector(const auto& data) std::string urlDecode(const std::string &src); +#ifndef SWIG +template +static inline void LogFormat(LogLevel level, std::string_view file, int line, fmt::format_string fmt, Args&&... args) +{ + auto msg = fmt::format(fmt, std::forward(args)...); + libcdoc::log(level, file, line, msg); +} + +static inline void LogFormat(LogLevel level, std::string_view file, int line, std::string_view msg) +{ + libcdoc::log(level, file, line, msg); +} +#endif + +#define LOG(l,...) LogFormat((l), __FILE__, __LINE__, __VA_ARGS__) +#define LOG_ERROR(...) LogFormat(libcdoc::LEVEL_ERROR, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_WARN(...) LogFormat(libcdoc::LEVEL_WARNING, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_INFO(...) LogFormat(libcdoc::LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_DBG(...) LogFormat(libcdoc::LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__) + +#ifdef NDEBUG +#define LOG_TRACE(...) +#define LOG_TRACE_KEY(MSG, KEY) +#else +#define LOG_TRACE(...) LogFormat(libcdoc::LEVEL_TRACE, __FILE__, __LINE__, __VA_ARGS__) +#define LOG_TRACE_KEY(MSG, KEY) LogFormat(libcdoc::LEVEL_TRACE, __FILE__, __LINE__, MSG, toHex(KEY)) +#endif + } // namespace libcdoc #endif // UTILS_H diff --git a/cdoc/cdoc-tool.cpp b/cdoc/cdoc-tool.cpp index 9cd8403c..4ae763ca 100644 --- a/cdoc/cdoc-tool.cpp +++ b/cdoc/cdoc-tool.cpp @@ -23,9 +23,8 @@ #endif #include "CDocCipher.h" -#include "ConsoleLogger.h" -#include "ILogger.h" #include "Utils.h" +#include "Logger.h" #include "json/jwt.h" diff --git a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java index 837d7ca9..00050c01 100644 --- a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java +++ b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java @@ -399,18 +399,6 @@ static void test() { CDocReader rdr = CDocReader.createReader(path, conf, null, network); System.err.format("Reader created (version %d)\n", rdr.getVersion()); - rdr.testConfig(buf); - System.err.format("Buffer out: %s\n", hex.formatHex(buf.getData())); - System.err.println("Success"); - - CertificateList certs = new CertificateList(); - rdr.testNetwork(certs); - System.err.format("Num certs: %s\n", certs.size()); - for (int i = 0; i < certs.size(); i++) { - byte[] cert = certs.getCertificate(i); - System.err.format(" %s\n", hex.formatHex(cert)); - } - testRW(conf, crypto); System.err.println("Success"); @@ -579,15 +567,6 @@ public long getPeerTLSCertificates(CertificateList dst, String url) throws CDocE System.err.println("ToolNetwork.getPeerTLSCertificates: " + dst); return CDoc.OK; } - - @Override - public long test(CertificateList dst) { - System.err.println("ToolNetwork.test: Java subclass implementation"); - System.err.format("dst: %s\n", dst); - dst.addCertificate(new byte[] {1, 2, 3}); - dst.addCertificate(new byte[] {4, 5, 6, 7, 8}); - return CDoc.OK; - } } private static class JavaLogger extends ILogger { diff --git a/libcdoc.i b/libcdoc.i index 7e800a76..83e1bef2 100644 --- a/libcdoc.i +++ b/libcdoc.i @@ -25,7 +25,7 @@ #include "Configuration.h" #include "CDocWriter.h" #include "CDocReader.h" -#include "ConsoleLogger.h" +#include "Logger.h" #include "Lock.h" #include "NetworkBackend.h" #include "PKCS11Backend.h" @@ -581,8 +581,6 @@ static std::vector SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *jen // Swig does not like visibility/declspec attributes #define CDOC_EXPORT -// fixme: Remove this in production -#define LIBCDOC_TESTING 1 #define CDOC_DISABLE_MOVE(X) %include "CDoc.h" @@ -594,8 +592,7 @@ static std::vector SWIG_JavaArrayToVectorUnsignedChar(JNIEnv *jen %include "CryptoBackend.h" %include "NetworkBackend.h" %include "PKCS11Backend.h" -%include "ILogger.h" -%include "ConsoleLogger.h" +%include "Logger.h" // LockVector template must come after Lock.h is included so that // SWIG knows about the libcdoc::Lock class definition. From ee33b97b09c10d623b8de0030b99e6158ad27b16 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 13 Feb 2026 14:33:06 +0200 Subject: [PATCH 12/50] Added Logger.h --- cdoc/Logger.h | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 cdoc/Logger.h diff --git a/cdoc/Logger.h b/cdoc/Logger.h new file mode 100644 index 00000000..138ff044 --- /dev/null +++ b/cdoc/Logger.h @@ -0,0 +1,103 @@ +/* + * libcdoc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef __LOGGER_H__INCLUDED__ +#define __LOGGER_H__INCLUDED__ + +#include +#include + +#include +#include + +namespace libcdoc +{ + +/** + * @brief Generic interface to implement a logger. + */ +class CDOC_EXPORT Logger +{ +public: + /** + * @brief Logs given message with given severity, file name and line number. + * + * It tests the log level and if <= min_level invokes logMessage + * + * @param level Severity of the log message. + * @param file File name where the log message was recorded. + * @param line Line number in the file where the log message was recorded. + * @param msg The log message. + */ + void log(LogLevel level, std::string_view file, int line, std::string_view msg) { + if (level <= min_level) logMessage(level, file, line, msg); + } + + /** + * @brief Sets minimum log level for the logger. + * @param level minimum level to log. + * + * Sets minimum level of log messages to log. For example, if the minimum log level is set + * to LEVEL_INFO (default), then LEVEL_FATAL, LEVEL_ERROR, LEVEL_WARNING and LEVEL_INFO + * messages are logged, but not LEVEL_DEBUG or LEVEL_TRACE messages. + */ + void setMinLogLevel(LogLevel level) noexcept { min_level = level; } + +protected: + /** + * @brief Logs given message with given severity, file name and line number. + * + * Every class implementing the ILogger interface must implement this member function. + * The efault implementation does nothing. + * The level should be checked by caller, thus the implementation should expect that level <= min_level + * + * @param level Severity of the log message. + * @param file File name where the log message was recorded. + * @param line Line number in the file where the log message was recorded. + * @param msg The log message. + */ + virtual void logMessage(LogLevel level, std::string_view file, int line, std::string_view msg) {} + + /** + * @brief Minimum level of log messages to log. + */ + LogLevel min_level = LEVEL_WARNING; +}; + +/** + * @brief Console logger + * + * An ILogger subclass that logs text to console. + * + * Info messages are logged to cout, all others to cerr. + */ + +class ConsoleLogger : public Logger +{ +public: + virtual void logMessage(LogLevel level, std::string_view file, int line, std::string_view message) override + { + // We ignore by default the file name and line number, and call LogMessage with the level and message. + std::ostream& ofs = (level == LEVEL_INFO) ? std::cout : std::cerr; + ofs << file << ':' << line << " " << message << '\n'; + } +}; + +} + +#endif From 9930c35a450579caf63a8796e0b2b40ceaa15002 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 13 Feb 2026 15:23:32 +0200 Subject: [PATCH 13/50] Removed ConsoleLogger from public interface --- cdoc/CDoc.cpp | 58 +++++++++++-------- cdoc/CDoc.h | 1 + cdoc/CDocReader.h | 4 -- cdoc/Configuration.h | 4 -- cdoc/Logger.h | 20 ------- cdoc/NetworkBackend.cpp | 9 --- cdoc/NetworkBackend.h | 4 -- cdoc/cdoc-tool.cpp | 57 ++++++++++++------ .../src/main/java/ee/ria/cdoc/CDocTool.java | 12 ++-- libcdoc.i | 2 +- 10 files changed, 77 insertions(+), 94 deletions(-) diff --git a/cdoc/CDoc.cpp b/cdoc/CDoc.cpp index f98ef8bb..546df084 100644 --- a/cdoc/CDoc.cpp +++ b/cdoc/CDoc.cpp @@ -26,6 +26,9 @@ #include "Utils.h" #include "Logger.h" +#include +#include + namespace libcdoc { struct Result { @@ -72,6 +75,29 @@ getVersion() return VERSION_STR; } +/** + * @brief Console logger + * + * An ILogger subclass that logs text to console. + * + * Info messages are logged to cout, all others to cerr. + */ + +class ConsoleLogger : public Logger +{ +public: + virtual void logMessage(LogLevel level, std::string_view file, int line, std::string_view message) override + { + // We ignore by default the file name and line number, and call LogMessage with the level and message. + std::ostream& ofs = (level == LEVEL_INFO) ? std::cout : std::cerr; + if (!file.empty()) { + ofs << std::filesystem::path(file).filename().string() << ':' << line << " " << message << '\n'; + } else { + ofs << message << '\n'; + } + } +}; + static Logger * getDefaultLogger() { @@ -87,6 +113,13 @@ setLogger(Logger *logger) sys_logger = logger; } +void +setLogLevel(LogLevel level) +{ + Logger *logger = (sys_logger) ? sys_logger : getDefaultLogger(); + logger->setMinLogLevel(level); +} + void log(LogLevel level, std::string_view file, int line, std::string_view msg) { @@ -162,31 +195,6 @@ libcdoc::CDocReader::createReader(std::istream& ifs, Configuration *conf, Crypto return createReader(isrc.release(), true, conf, crypto, network); } -#if LIBCDOC_TESTING -int64_t -libcdoc::CDocReader::testConfig(std::vector& dst) -{ - LOG_TRACE("CDocReader::testConfig::Native superclass"); - if (conf) { - LOG_DBG("CDocReader::testConfig this={} conf={}", reinterpret_cast(this), reinterpret_cast(conf)); - } - LOG_ERROR("CDocReader::testConfig::conf is null"); - return WORKFLOW_ERROR; -} - -int64_t -libcdoc::CDocReader::testNetwork(std::vector>& dst) -{ - LOG_TRACE("CDocReader::testNetwork::Native superclass"); - if (network) { - LOG_DBG("CDocReader::testNetwork this={} network={}", reinterpret_cast(this), reinterpret_cast(network)); - return network->test(dst); - } - LOG_ERROR("CDocReader::testNetwork::network is null"); - return WORKFLOW_ERROR; -} -#endif - libcdoc::CDocWriter::CDocWriter(int _version, DataConsumer *_dst, bool take_ownership) : version(_version), dst(_dst), owned(take_ownership) { diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 7e1e2e8f..741b1e6c 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -170,6 +170,7 @@ enum LogLevel class Logger; CDOC_EXPORT void setLogger(Logger *logger); +CDOC_EXPORT void setLogLevel(LogLevel level); CDOC_EXPORT void log(LogLevel level, std::string_view file, int line, std::string_view msg); /** diff --git a/cdoc/CDocReader.h b/cdoc/CDocReader.h index 5c033aa1..c1fdf16b 100644 --- a/cdoc/CDocReader.h +++ b/cdoc/CDocReader.h @@ -200,10 +200,6 @@ class CDOC_EXPORT CDocReader { */ static CDocReader *createReader(std::istream& ifs, Configuration *conf, CryptoBackend *crypto, NetworkBackend *network); -#if LIBCDOC_TESTING - virtual int64_t testConfig(std::vector& dst); - virtual int64_t testNetwork(std::vector>& dst); -#endif protected: explicit CDocReader(int _version) : version(_version) {}; diff --git a/cdoc/Configuration.h b/cdoc/Configuration.h index 309c2e31..080e3fba 100644 --- a/cdoc/Configuration.h +++ b/cdoc/Configuration.h @@ -111,10 +111,6 @@ struct CDOC_EXPORT Configuration { * @return the key value */ int getInt(std::string_view param, int def_val = 0) const; - -#if LIBCDOC_TESTING - virtual int64_t test(std::vector& dst) { return OK; } -#endif }; /** diff --git a/cdoc/Logger.h b/cdoc/Logger.h index 138ff044..20f15ed3 100644 --- a/cdoc/Logger.h +++ b/cdoc/Logger.h @@ -22,7 +22,6 @@ #include #include -#include #include namespace libcdoc @@ -79,25 +78,6 @@ class CDOC_EXPORT Logger LogLevel min_level = LEVEL_WARNING; }; -/** - * @brief Console logger - * - * An ILogger subclass that logs text to console. - * - * Info messages are logged to cout, all others to cerr. - */ - -class ConsoleLogger : public Logger -{ -public: - virtual void logMessage(LogLevel level, std::string_view file, int line, std::string_view message) override - { - // We ignore by default the file name and line number, and call LogMessage with the level and message. - std::ostream& ofs = (level == LEVEL_INFO) ? std::cout : std::cerr; - ofs << file << ':' << line << " " << message << '\n'; - } -}; - } #endif diff --git a/cdoc/NetworkBackend.cpp b/cdoc/NetworkBackend.cpp index 3382e3be..eae5deab 100644 --- a/cdoc/NetworkBackend.cpp +++ b/cdoc/NetworkBackend.cpp @@ -155,15 +155,6 @@ libcdoc::NetworkBackend::getLastErrorStr(result_t code) const return libcdoc::getErrorStr(code); } -#if LIBCDOC_TESTING -int64_t -libcdoc::NetworkBackend::test(std::vector> &dst) -{ - LOG_TRACE("NetworkBackend::test::Native superclass"); - return OK; -} -#endif - // // Set peer certificate(s) for given server url // diff --git a/cdoc/NetworkBackend.h b/cdoc/NetworkBackend.h index 6c082240..446cff7d 100644 --- a/cdoc/NetworkBackend.h +++ b/cdoc/NetworkBackend.h @@ -277,10 +277,6 @@ struct CDOC_EXPORT NetworkBackend { result_t signMID(std::vector& dst, std::vector& cert, const std::string& url, const std::string& rp_uuid, const std::string& rp_name, const std::string& phone, const std::string& rcpt_id, const std::vector& digest, CryptoBackend::HashAlgorithm algo); - -#if LIBCDOC_TESTING - virtual int64_t test(std::vector> &dst); -#endif }; } // namespace libcdoc diff --git a/cdoc/cdoc-tool.cpp b/cdoc/cdoc-tool.cpp index 4ae763ca..30bd7364 100644 --- a/cdoc/cdoc-tool.cpp +++ b/cdoc/cdoc-tool.cpp @@ -31,6 +31,15 @@ using namespace std; using namespace libcdoc; +static std::map str2level = { + {"FATAL", libcdoc::LEVEL_FATAL}, + {"ERROR", libcdoc::LEVEL_ERROR}, + {"WARNING", libcdoc::LEVEL_WARNING}, + {"INFO", libcdoc::LEVEL_INFO}, + {"DEBUG", libcdoc::LEVEL_DEBUG}, + {"TRACE", libcdoc::LEVEL_TRACE} +}; + enum { RESULT_OK = 0, RESULT_ERROR, @@ -75,17 +84,8 @@ static void print_usage(ostream& ofs) ofs << " --library - path to the PKCS11 library to be used" << endl; ofs << " --server ID URL(S) - specifies a key or share server. The recipient key will be stored in server instead of in the document." << endl; ofs << " for key server the url is either fetch or send url. For share server it is comma-separated list of share server urls." << endl; - ofs << " --accept FILENAME - specifies an accepted server certificate (in der encoding)" << endl; - - //<< "cdoc-tool encrypt -r X509DerRecipientCert [-r X509DerRecipientCert [...]] InFile [InFile [...]] OutFile" << std::endl - // << "cdoc-tool encrypt --rcpt RECIPIENT [--rcpt RECIPIENT] [--file INFILE] [...] --out OUTFILE" << std::endl - // << " where RECIPIENT is in form label:TYPE:value" << std::endl - // << " where TYPE is 'cert', 'key' or 'pw'" << std::endl -#ifdef _WIN32 - // << "cdoc-tool decrypt win [ui|noui] pin InFile OutFolder" << endl -#endif - // << "cdoc-tool decrypt pkcs11 path/to/so pin InFile OutFolder" << std::endl - // << "cdoc-tool decrypt pkcs12 path/to/pkcs12 pin InFile OutFolder" << std::endl; + ofs << " --accept FILENAME - specifies an accepted server certificate (in der encoding)" << endl; + ofs << " --log-level LEVEL - set logging level (FATAL, ERROR, WARNING, INFO, DEBUG, TRACE)" << endl; } static std::vector @@ -134,6 +134,10 @@ parse_common(ToolConf& conf, int arg_idx, int argc, char *argv[]) } else if ((arg == "--conf") && ((arg_idx + 1) < argc)) { conf.parse(argv[arg_idx + 1]); return 2; + } else if ((arg == "--log-level") && ((arg_idx + 1) < argc)) { + if (!str2level.contains(argv[arg_idx + 1])) return 0; + libcdoc::setLogLevel(str2level[argv[arg_idx + 1]]); + return 2; } return 0; } @@ -635,11 +639,30 @@ static int ParseAndReEncrypt(int argc, char *argv[]) static int ParseAndGetLocks(int argc, char *argv[]) { - if (argc < 1) - return 2; + ToolConf conf; + std::string container; + + // + // Parse all arguments into ToolConf structure + // + int arg_idx = 0; + while (arg_idx < argc) { + int result = parse_common(conf, arg_idx, argc, argv); + if (result < 0) return result; + arg_idx += result; + if (result > 0) continue; + if (argv[arg_idx][0] == '-') { + return RESULT_USAGE; + } else { + if (!container.empty()) return RESULT_USAGE; + container = argv[arg_idx]; + } + arg_idx += 1; + } + if (container.empty()) return RESULT_USAGE; CDocCipher cipher; - cipher.Locks(argv[0]); + cipher.Locks(container.c_str()); return 0; } @@ -650,13 +673,9 @@ int main(int argc, char *argv[]) return 1; } - // Add console logger by default - ConsoleLogger console_logger; - console_logger.setMinLogLevel(LEVEL_TRACE); - libcdoc::setLogger(&console_logger); + libcdoc::setLogLevel(LEVEL_TRACE); string_view command(argv[1]); - LOG_INFO("Command: {}", command); CDocCipher cipher; int retVal = 2; // Output the help by default. diff --git a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java index 00050c01..13e932ee 100644 --- a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java +++ b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java @@ -33,8 +33,8 @@ public static String getArg(int arg_idx, String[] args) { return args[arg_idx]; } - // Make logger static to ensure that it is not garbage-collected as long as it is atached to library - private static ILogger logger; + // Make logger static to ensure that it is not garbage-collected as long as it is attached to library + private static Logger logger; public static void main(String[] args) { System.out.println("Starting app..."); @@ -137,8 +137,7 @@ public static void main(String[] args) { System.load(lib.getAbsolutePath()); System.out.println("Library loaded"); - //logger = new JavaLogger(); - logger = new ConsoleLogger(); + logger = new JavaLogger(); logger.setMinLogLevel(log_level); CDoc.setLogger(logger); CDoc.log(LogLevel.LEVEL_DEBUG, "FILENAME", 0, "Starting CDocTool.java"); @@ -215,9 +214,6 @@ static void decrypt(String file, String label, String password) { CDocReader rdr = CDocReader.createReader(src, false, conf, crypto, null); System.out.format("Reader created (version %d)\n", rdr.getVersion()); - //rdr.testConfig(buf); - System.err.format("Buffer out: %s\n", hex.formatHex(buf.getData())); - LockVector locks = rdr.getLocks(); for (int idx = 0; idx < locks.size(); idx++) { ee.ria.cdoc.Lock lock = locks.get(idx); @@ -569,7 +565,7 @@ public long getPeerTLSCertificates(CertificateList dst, String url) throws CDocE } } - private static class JavaLogger extends ILogger { + private static class JavaLogger extends Logger { @Override public void logMessage(LogLevel level, String file, int line, String message) { System.out.format("%s:%s %s %s\n", file, line, level, message); diff --git a/libcdoc.i b/libcdoc.i index 83e1bef2..7f32dc2d 100644 --- a/libcdoc.i +++ b/libcdoc.i @@ -218,7 +218,7 @@ %feature("director") libcdoc::PKCS11Backend; %feature("director") libcdoc::NetworkBackend; %feature("director") libcdoc::Configuration; -%feature("director") libcdoc::ILogger; +%feature("director") libcdoc::Logger; #ifdef SWIGJAVA %include "arrays_java.i" From ce13f363185c116f6a92a1812152339bade0d4bd Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 13 Feb 2026 15:45:10 +0200 Subject: [PATCH 14/50] Fixed WinBackend.cpp --- cdoc/WinBackend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdoc/WinBackend.cpp b/cdoc/WinBackend.cpp index 5abb5731..de61a20c 100644 --- a/cdoc/WinBackend.cpp +++ b/cdoc/WinBackend.cpp @@ -19,7 +19,7 @@ #include "WinBackend.h" #include "CDoc2.h" -#include "ILogger.h" +#include "Logger.h" #include #include From 4453784acb4a148fe5c6d2ac400c65f7f1595ecf Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 13 Feb 2026 16:06:37 +0200 Subject: [PATCH 15/50] Fixed WinBackend.cpp more --- cdoc/WinBackend.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cdoc/WinBackend.cpp b/cdoc/WinBackend.cpp index de61a20c..4c725459 100644 --- a/cdoc/WinBackend.cpp +++ b/cdoc/WinBackend.cpp @@ -18,6 +18,7 @@ #include "WinBackend.h" +#include "CDoc.h" #include "CDoc2.h" #include "Logger.h" From cb101793cba0fb777bbdfb849c82e2a00f04bd9a Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 13 Feb 2026 16:23:19 +0200 Subject: [PATCH 16/50] One more Winbackend fix --- cdoc/WinBackend.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdoc/WinBackend.cpp b/cdoc/WinBackend.cpp index 4c725459..fbc8b55a 100644 --- a/cdoc/WinBackend.cpp +++ b/cdoc/WinBackend.cpp @@ -18,9 +18,9 @@ #include "WinBackend.h" -#include "CDoc.h" #include "CDoc2.h" #include "Logger.h" +#include "Utils.h" #include #include From d9395048bcea12460043e3d1b903d7ce6eb6dabd Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Tue, 17 Feb 2026 14:13:25 +0200 Subject: [PATCH 17/50] Implement secp256r1 curve support --- cdoc/CDoc1Reader.cpp | 2 +- cdoc/CDoc2Reader.cpp | 44 ++++++----- cdoc/CDoc2Writer.cpp | 26 +++++-- cdoc/CDocCipher.cpp | 129 ++++++++++++-------------------- cdoc/Lock.h | 26 ++----- cdoc/Recipient.cpp | 43 ++++++++--- cdoc/Recipient.h | 51 ++++++++----- cdoc/schema/recipients.fbs | 25 ++++--- libcdoc.i | 5 +- test/data/ec-secp256r1-cert.der | Bin 0 -> 598 bytes test/data/ec-secp256r1-priv.der | Bin 0 -> 138 bytes test/data/ec-secp256r1-pub.der | Bin 0 -> 91 bytes test/libcdoc_boost.cpp | 3 + 13 files changed, 186 insertions(+), 168 deletions(-) create mode 100644 test/data/ec-secp256r1-cert.der create mode 100644 test/data/ec-secp256r1-priv.der create mode 100644 test/data/ec-secp256r1-pub.der diff --git a/cdoc/CDoc1Reader.cpp b/cdoc/CDoc1Reader.cpp index 9d293b51..3933f3d1 100644 --- a/cdoc/CDoc1Reader.cpp +++ b/cdoc/CDoc1Reader.cpp @@ -331,7 +331,7 @@ CDoc1Reader::CDoc1Reader(libcdoc::DataSource *src, bool delete_on_close) Certificate ssl(cert); key.setBytes(Lock::CERT, std::move(cert)); key.setBytes(Lock::RCPT_KEY, ssl.getPublicKey()); - key.pk_type = (ssl.getAlgorithm() == libcdoc::Certificate::RSA) ? Lock::RSA : Lock::ECC; + key.pk_type = (ssl.getAlgorithm() == libcdoc::Certificate::RSA) ? Algorithm::RSA : Algorithm::ECC; } // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/CipherData/CipherValue else if(reader.isElement("CipherValue")) diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 23ab5781..6ae10d37 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -507,44 +507,52 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor switch(recipient.capsule_type()) { case Capsule::recipients_ECCPublicKeyCapsule: - if(const auto *key = recipient.capsule_as_recipients_ECCPublicKeyCapsule()) { - if(key->curve() == EllipticCurve::secp384r1) { - lock.type = Lock::Type::PUBLIC_KEY; - lock.pk_type = Lock::PKType::ECC; - lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(key->recipient_public_key())); - lock.setBytes(Lock::Params::KEY_MATERIAL, toUint8Vector(key->sender_public_key())); - LOG_DBG("Load PK: {}", toHex(lock.getBytes(Lock::Params::RCPT_KEY))); + if(const auto *capsule = recipient.capsule_as_recipients_ECCPublicKeyCapsule()) { + if(capsule->curve() == EllipticCurve::secp384r1) { + lock.ec_type = Curve::SECP_384_R1; + } else if (capsule->curve() == EllipticCurve::secp256r1) { + lock.ec_type = Curve::SECP_256_R1; } else { LOG_ERROR("Unsupported ECC curve: skipping"); + return; } + lock.type = Lock::Type::PUBLIC_KEY; + lock.pk_type = Algorithm::ECC; + lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(capsule->recipient_public_key())); + lock.setBytes(Lock::Params::KEY_MATERIAL, toUint8Vector(capsule->sender_public_key())); + LOG_DBG("Load PK: {}", toHex(lock.getBytes(Lock::Params::RCPT_KEY))); } return; case Capsule::recipients_RSAPublicKeyCapsule: if(const auto *key = recipient.capsule_as_recipients_RSAPublicKeyCapsule()) { lock.type = Lock::Type::PUBLIC_KEY; - lock.pk_type = Lock::PKType::RSA; + lock.pk_type = Algorithm::RSA; lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(key->recipient_public_key())); lock.setBytes(Lock::Params::KEY_MATERIAL, toUint8Vector(key->encrypted_kek())); } return; case Capsule::recipients_KeyServerCapsule: - if (const KeyServerCapsule *server = recipient.capsule_as_recipients_KeyServerCapsule()) { - KeyDetailsUnion details = server->recipient_key_details_type(); + if (const KeyServerCapsule *capsule = recipient.capsule_as_recipients_KeyServerCapsule()) { + KeyDetailsUnion details = capsule->recipient_key_details_type(); switch (details) { case KeyDetailsUnion::EccKeyDetails: - if(const EccKeyDetails *eccDetails = server->recipient_key_details_as_EccKeyDetails()) { - if(eccDetails->curve() != EllipticCurve::secp384r1) { - LOG_ERROR("Unsupported elliptic curve key type"); + if(const EccKeyDetails *eccDetails = capsule->recipient_key_details_as_EccKeyDetails()) { + if(eccDetails->curve() == EllipticCurve::secp384r1) { + lock.ec_type = Curve::SECP_384_R1; + } else if (eccDetails->curve() == EllipticCurve::secp256r1) { + lock.ec_type = Curve::SECP_256_R1; + } else { + LOG_ERROR("Unsupported ECC curve: skipping"); return; } - lock.pk_type = Lock::PKType::ECC; + lock.pk_type = Algorithm::ECC; lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(eccDetails->recipient_public_key())); } break; case KeyDetailsUnion::RsaKeyDetails: - if(const RsaKeyDetails *rsaDetails = server->recipient_key_details_as_RsaKeyDetails()) { - lock.pk_type = Lock::PKType::RSA; + if(const RsaKeyDetails *rsaDetails = capsule->recipient_key_details_as_RsaKeyDetails()) { + lock.pk_type = Algorithm::RSA; lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(rsaDetails->recipient_public_key())); } break; @@ -553,8 +561,8 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor return; } lock.type = Lock::Type::SERVER; - lock.setString(Lock::Params::KEYSERVER_ID, server->keyserver_id()->str()); - lock.setString(Lock::Params::TRANSACTION_ID, server->transaction_id()->str()); + lock.setString(Lock::Params::KEYSERVER_ID, capsule->keyserver_id()->str()); + lock.setString(Lock::Params::TRANSACTION_ID, capsule->transaction_id()->str()); } return; case Capsule::recipients_SymmetricKeyCapsule: diff --git a/cdoc/CDoc2Writer.cpp b/cdoc/CDoc2Writer.cpp index a79271b6..4b299f0f 100644 --- a/cdoc/CDoc2Writer.cpp +++ b/cdoc/CDoc2Writer.cpp @@ -95,6 +95,17 @@ CDoc2Writer::writeHeader(const std::vector &recipients) return OK; } +struct ECData { + int ossl_nid; + cdoc20::recipients::EllipticCurve fb_type; + const char *server_name; +}; + +static std::map ecdata = { + {Curve::SECP_384_R1, {NID_secp384r1, cdoc20::recipients::EllipticCurve::secp384r1, "ecc_secp384r1"}}, + {Curve::SECP_256_R1, {NID_X9_62_prime256v1, cdoc20::recipients::EllipticCurve::secp256r1, "ecc_secp256r1"}} +}; + static flatbuffers::Offset createRSACapsule(flatbuffers::FlatBufferBuilder& builder, const libcdoc::Recipient& rcpt, const std::vector& encrypted_kek, const std::vector& xor_key) { @@ -131,7 +142,7 @@ static flatbuffers::Offset createECCCapsule(flatbuffers::FlatBufferBuilder& builder, const libcdoc::Recipient& rcpt, const std::vector& eph_public_key, const std::vector& xor_key) { auto capsule = cdoc20::recipients::CreateECCPublicKeyCapsule(builder, - cdoc20::recipients::EllipticCurve::secp384r1, + ecdata[rcpt.ec_type].fb_type, builder.CreateVector(rcpt.rcpt_key), builder.CreateVector(eph_public_key)); return cdoc20::header::CreateRecipientRecord(builder, @@ -146,7 +157,7 @@ static flatbuffers::Offset createECCServerCapsule(flatbuffers::FlatBufferBuilder& builder, const libcdoc::Recipient& rcpt, const std::string& transaction_id, uint64_t expiry_time, const std::vector& xor_key) { auto eccKeyServer = cdoc20::recipients::CreateEccKeyDetails(builder, - cdoc20::recipients::EllipticCurve::secp384r1, + ecdata[rcpt.ec_type].fb_type, builder.CreateVector(rcpt.rcpt_key)); auto capsule = cdoc20::recipients::CreateKeyServerCapsule(builder, cdoc20::recipients::KeyDetailsUnion::EccKeyDetails, @@ -220,7 +231,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vectorrandom(kek, libcdoc::CDoc2::KEY_LEN); if (libcdoc::Crypto::xor_data(xor_key, fmk, kek) != libcdoc::OK) { setLastError("Internal error"); @@ -256,7 +267,12 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector& header, const std::vectorsendKey(cinfo, send_url, rcpt.rcpt_key, key_material, "ecc_secp384r1", rcpt.expiry_ts); + auto result = network->sendKey(cinfo, send_url, rcpt.rcpt_key, key_material, ecdata[rcpt.ec_type].server_name, rcpt.expiry_ts); if (result < 0) { setLastError(network->getLastErrorStr(result)); LOG_ERROR("{}", last_error); diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index c94efe9f..f94ca413 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -39,16 +39,14 @@ using namespace std; using namespace libcdoc; -static string GenerateRandomSequence(); - struct ToolPKCS11 : public libcdoc::PKCS11Backend { - const std::map& rcpts; + const std::vector& rcpts; - ToolPKCS11(const std::string& library, const std::map& vec) : libcdoc::PKCS11Backend(library), rcpts(vec) {} + ToolPKCS11(const std::string& library, const std::vector& vec) : libcdoc::PKCS11Backend(library), rcpts(vec) {} libcdoc::result_t connectToKey(int idx, bool priv) override final { - if (!rcpts.contains(idx)) return libcdoc::CRYPTO_ERROR; - libcdoc::RcptInfo rcpt = rcpts.at(idx); + if (idx >= rcpts.size()) idx = 0; + const libcdoc::RcptInfo& rcpt = rcpts[idx]; if (!priv) { return useSecretKey(rcpt.p11.slot, rcpt.secret, rcpt.p11.key_id, rcpt.p11.key_label); } else { @@ -59,26 +57,26 @@ struct ToolPKCS11 : public libcdoc::PKCS11Backend { #ifdef _WIN32 struct ToolWin : public libcdoc::WinBackend { - const std::map& rcpts; + const std::vector& rcpts; - ToolWin(const std::string& provider, const std::map& vec) : libcdoc::WinBackend(provider), rcpts(vec) {} + ToolWin(const std::string& provider, const std::vector& vec) : libcdoc::WinBackend(provider), rcpts(vec) {} result_t connectToKey(int idx, bool priv) { - if (!rcpts.contains(idx)) return libcdoc::CRYPTO_ERROR; - const libcdoc::RcptInfo &rcpt = rcpts.at(idx); + if (idx >= rcpts.size()) idx = 0; + const libcdoc::RcptInfo& rcpt = rcpts[idx]; return useKey(rcpt.p11.key_label, std::string(rcpt.secret.cbegin(), rcpt.secret.cend())); } }; #endif struct ToolCrypto : public libcdoc::CryptoBackend { - const std::map& rcpts; + const std::vector& rcpts; std::unique_ptr p11; #ifdef _WIN32 std::unique_ptr ncrypt; #endif - ToolCrypto(const std::map& recipients) : rcpts(recipients) { + ToolCrypto(const std::vector& recipients) : rcpts(recipients) { } bool connectLibrary(const std::string& library) { @@ -97,8 +95,8 @@ struct ToolCrypto : public libcdoc::CryptoBackend { libcdoc::result_t decryptRSA(std::vector& dst, const std::vector &data, bool oaep, unsigned int idx) override final { if (p11) return p11->decryptRSA(dst, data, oaep, idx); - if (!rcpts.contains(idx)) return libcdoc::CRYPTO_ERROR; - const libcdoc::RcptInfo &rcpt = rcpts.at(idx); + if (idx >= rcpts.size()) idx = 0; + const libcdoc::RcptInfo& rcpt = rcpts[idx]; if (rcpt.secret.empty()) return libcdoc::CRYPTO_ERROR; const uint8_t *p = rcpt.secret.data(); @@ -129,8 +127,8 @@ struct ToolCrypto : public libcdoc::CryptoBackend { } libcdoc::result_t deriveECDH1(std::vector& dst, const std::vector &public_key, unsigned int idx) override final { - if (!rcpts.contains(idx)) return libcdoc::CRYPTO_ERROR; - const libcdoc::RcptInfo &rcpt = rcpts.at(idx); + if (idx >= rcpts.size()) idx = 0; + const libcdoc::RcptInfo& rcpt = rcpts[idx]; if (rcpt.secret.empty()) return libcdoc::CRYPTO_ERROR; const uint8_t *p = rcpt.secret.data(); @@ -182,8 +180,8 @@ struct ToolCrypto : public libcdoc::CryptoBackend { } libcdoc::result_t getSecret(std::vector& secret, unsigned int idx) override final { - if (!rcpts.contains(idx)) return libcdoc::CRYPTO_ERROR; - const libcdoc::RcptInfo &rcpt = rcpts.at(idx); + if (idx >= rcpts.size()) idx = 0; + const libcdoc::RcptInfo& rcpt = rcpts[idx]; secret = rcpt.secret; return secret.empty() ? INVALID_PARAMS : libcdoc::OK; } @@ -205,8 +203,8 @@ struct ToolNetwork : public libcdoc::NetworkBackend { } libcdoc::result_t getClientTLSCertificate(std::vector& dst) override final { - if (!crypto->rcpts.contains(rcpt_idx)) return libcdoc::CRYPTO_ERROR; - const RcptInfo& rcpt = crypto->rcpts.at(rcpt_idx); + if (rcpt_idx >= crypto->rcpts.size()) rcpt_idx = 0; + const libcdoc::RcptInfo& rcpt = crypto->rcpts[rcpt_idx]; bool rsa = false; return crypto->p11->getCertificate(dst, rsa, rcpt.p11.slot, rcpt.secret, rcpt.p11.key_id, rcpt.p11.key_label); } @@ -217,7 +215,8 @@ struct ToolNetwork : public libcdoc::NetworkBackend { } libcdoc::result_t signTLS(std::vector& dst, libcdoc::CryptoBackend::HashAlgorithm algorithm, const std::vector &digest) override final { - if (!crypto->rcpts.contains(rcpt_idx)) return libcdoc::CRYPTO_ERROR; + if (rcpt_idx >= crypto->rcpts.size()) rcpt_idx = 0; + const libcdoc::RcptInfo& rcpt = crypto->rcpts[rcpt_idx]; return crypto->p11->sign(dst, algorithm, digest, rcpt_idx); } @@ -258,7 +257,7 @@ int CDocCipher::writer_push(CDocWriter& writer, const vector& rcpts, #define PUSH true static bool -fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector& rcpts, std::map& crypto_rcpts, const std::vector& recipients) +fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector& rcpts, const std::vector& recipients) { int idx = 0; for (const auto& rcpt : recipients) { @@ -266,8 +265,6 @@ fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector& recipients) { - std::map crypto_rcpts; - ToolCrypto crypto(crypto_rcpts); + ToolCrypto crypto(recipients); ToolNetwork network(&crypto); network.certs = std::move(conf.accept_certs); @@ -342,7 +341,7 @@ int CDocCipher::Encrypt(ToolConf& conf, std::vector& recipien } vector rcpts; - fill_recipients_from_rcpt_info(conf, crypto, rcpts, crypto_rcpts, recipients); + fill_recipients_from_rcpt_info(conf, crypto, rcpts, recipients); if (rcpts.empty()) { LOG_ERROR("No key for encryption was found"); @@ -370,8 +369,8 @@ int CDocCipher::Encrypt(ToolConf& conf, std::vector& recipien int CDocCipher::Decrypt(ToolConf& conf, const RcptInfo& recipient) { - std::map rcpts; - ToolCrypto crypto(rcpts); + std::vector r = {recipient}; + ToolCrypto crypto(r); ToolNetwork network(&crypto); network.certs = std::move(conf.accept_certs); @@ -424,9 +423,8 @@ int CDocCipher::Decrypt(ToolConf& conf, const RcptInfo& recipient) return 1; } LOG_INFO("Found matching lock: {}", recipient.label); - rcpts[lock_idx] = recipient; - network.rcpt_idx = lock_idx; + return Decrypt(rdr, lock_idx, conf.out); } @@ -513,11 +511,11 @@ int CDocCipher::Decrypt(const unique_ptr& rdr, unsigned int lock_idx } int -CDocCipher::ReEncrypt(ToolConf& conf, const RcptInfo& lock_info, std::vector& recipients) +CDocCipher::ReEncrypt(ToolConf& conf, const RcptInfo& dec_info, std::vector& enc_info) { // Decryption part - std::map dec_info; - ToolCrypto crypto(dec_info); + std::vector rcpt_list = {dec_info}; + ToolCrypto crypto(rcpt_list); ToolNetwork network(&crypto); network.certs = std::move(conf.accept_certs); @@ -535,39 +533,36 @@ CDocCipher::ReEncrypt(ToolConf& conf, const RcptInfo& lock_info, std::vector& locks = rdr->getLocks(); int lock_idx = -1; - if (!lock_info.label.empty()) { + if (!dec_info.label.empty()) { for (unsigned int i = 0; i < locks.size(); i++) { - if (locks[i].label == lock_info.label) { + if (locks[i].label == dec_info.label) { lock_idx = i; break; } } - } else if (lock_info.lock_idx >= 0) { - if (lock_info.lock_idx >= locks.size()) { + } else if (dec_info.lock_idx >= 0) { + if (dec_info.lock_idx >= locks.size()) { LOG_ERROR("Label index is out of range"); return 1; } - lock_idx = lock_info.lock_idx; + lock_idx = dec_info.lock_idx; } if (lock_idx < 0) { - LOG_ERROR("Lock not found: {}", lock_info.label); + LOG_ERROR("Lock not found: {}", dec_info.label); return 1; } - dec_info[lock_idx] = lock_info; - network.rcpt_idx = lock_idx; // Encryption part - std::map crypto_rcpts; - ToolCrypto enc_crypto(crypto_rcpts); + ToolCrypto enc_crypto(enc_info); ToolNetwork enc_network(&enc_crypto); enc_network.certs = std::move(conf.accept_certs); if (!conf.library.empty()) { enc_crypto.connectLibrary(conf.library); } - for (const auto& rcpt : recipients) { + for (const auto& rcpt : enc_info) { if (rcpt.type == RcptInfo::NCRYPT) { enc_crypto.connectNCrypt(); break; @@ -575,7 +570,7 @@ CDocCipher::ReEncrypt(ToolConf& conf, const RcptInfo& lock_info, std::vector rcpts; - fill_recipients_from_rcpt_info(conf, enc_crypto, rcpts, crypto_rcpts, recipients); + fill_recipients_from_rcpt_info(conf, enc_crypto, rcpts, enc_info); if (rcpts.empty()) { LOG_ERROR("No key for encryption was found"); @@ -699,35 +694,3 @@ void CDocCipher::Locks(const char* file) const lock_id++; } } - -static string GenerateRandomSequence() -{ - constexpr uint32_t upperbound = 'z' - '0' + 1; - constexpr int MaxSequenceLength = 11; - - uint32_t rnd; - uint8_t rndByte; - ostringstream sequence; - for (int cnt = 0; cnt < MaxSequenceLength;) - { - if (RAND_bytes(&rndByte, 1) < 1) - { - rnd = rand() % upperbound + '0'; - } - else - { - rnd = rndByte % upperbound + '0'; - } - - // arc4random_uniform tends to be not available on all platforms. - // rnd = arc4random_uniform(upperbound) + '0'; - - if (isalnum(rnd)) - { - sequence << static_cast(rnd); - cnt++; - } - } - - return sequence.str(); -} diff --git a/cdoc/Lock.h b/cdoc/Lock.h index 250eeb85..7d1d3744 100644 --- a/cdoc/Lock.h +++ b/cdoc/Lock.h @@ -19,7 +19,7 @@ #ifndef __LOCK_H__ #define __LOCK_H__ -#include +#include #include #include @@ -74,20 +74,6 @@ struct CDOC_EXPORT Lock SHARE_SERVER }; - /** - * @brief The public key type - */ - enum PKType : unsigned char { - /** - * Elliptic curve - */ - ECC, - /** - * RSA - */ - RSA - }; - /** * @brief Extra parameters depending on key type */ @@ -178,9 +164,13 @@ struct CDOC_EXPORT Lock */ Type type = Type::UNKNOWN; /** - * @brief algorithm type for public key based locks + * @brief The algorithm type for public key based locks + */ + Algorithm pk_type = Algorithm::ECC; + /** + * @brief The elliptic curve used */ - PKType pk_type = PKType::ECC; + Curve ec_type = Curve::SECP_384_R1; /** * @brief the lock label @@ -215,7 +205,7 @@ struct CDOC_EXPORT Lock * @brief check whether public key lock uses RSA algorithm * @return true if pk_type is RSA */ - constexpr bool isRSA() const noexcept { return pk_type == PKType::RSA; } + constexpr bool isRSA() const noexcept { return pk_type == Algorithm::RSA; } Lock() noexcept = default; Lock(Type _type) noexcept : type(_type) {}; diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 0e699844..63f30311 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -60,20 +60,28 @@ Recipient::makeSymmetric(std::string label, int32_t kdf_iter) } Recipient -Recipient::makePublicKey(std::string label, std::vector public_key, PKType pk_type) +Recipient::makeRSA(std::string label, std::vector public_key) { if (public_key.empty()) return {Type::NONE}; Recipient rcpt(Type::PUBLIC_KEY); rcpt.label = std::move(label); - rcpt.pk_type = pk_type; - if (pk_type == PKType::ECC && public_key[0] == 0x30) { - // 0x30 identifies SEQUENCE tag in ASN.1 encoding - auto evp = Crypto::fromECPublicKeyDer(public_key); - rcpt.rcpt_key = Crypto::toPublicKeyDer(evp.get()); - } else { - rcpt.rcpt_key = std::move(public_key); - } + rcpt.pk_type = RSA; + rcpt.rcpt_key = std::move(public_key); + return rcpt; +} + +Recipient +Recipient::makeECC(std::string label, std::vector public_key, Curve ec_type) +{ + if (public_key.empty()) + return {Type::NONE}; + Recipient rcpt(Type::PUBLIC_KEY); + rcpt.label = std::move(label); + rcpt.pk_type = ECC; + // 0x30 identifies SEQUENCE tag in ASN.1 encoding + auto evp = Crypto::fromECPublicKeyDer(public_key); + rcpt.rcpt_key = Crypto::toPublicKeyDer(evp.get()); return rcpt; } @@ -87,15 +95,26 @@ Recipient::makeCertificate(std::string label, std::vector cert) rcpt.label = std::move(label); rcpt.cert = std::move(cert); rcpt.rcpt_key = x509.getPublicKey(); - rcpt.pk_type = (x509.getAlgorithm() == libcdoc::Certificate::RSA) ? PKType::RSA : PKType::ECC; + rcpt.pk_type = (x509.getAlgorithm() == libcdoc::Certificate::RSA) ? Algorithm::RSA : Algorithm::ECC; rcpt.expiry_ts = x509.getNotAfter(); return rcpt; } Recipient -Recipient::makeServer(std::string label, std::vector public_key, PKType pk_type, std::string server_id) +Recipient::makeServerRSA(std::string label, std::vector public_key, std::string server_id) +{ + Recipient rcpt = makeRSA(std::move(label), std::move(public_key)); + rcpt.server_id = std::move(server_id); + const auto six_months_from_now = std::chrono::system_clock::now() + std::chrono::months(6); + const auto expiry_ts = std::chrono::system_clock::to_time_t(six_months_from_now); + rcpt.expiry_ts = uint64_t(expiry_ts); + return rcpt; +} + +Recipient +Recipient::makeServerECC(std::string label, std::vector public_key, Curve ec_type, std::string server_id) { - Recipient rcpt = makePublicKey(std::move(label), std::move(public_key), pk_type); + Recipient rcpt = makeECC(std::move(label), std::move(public_key), ec_type); rcpt.server_id = std::move(server_id); const auto six_months_from_now = std::chrono::system_clock::now() + std::chrono::months(6); const auto expiry_ts = std::chrono::system_clock::to_time_t(six_months_from_now); diff --git a/cdoc/Recipient.h b/cdoc/Recipient.h index 0b04755b..5a0ae66c 100644 --- a/cdoc/Recipient.h +++ b/cdoc/Recipient.h @@ -19,7 +19,7 @@ #ifndef __RECIPIENT_H__ #define __RECIPIENT_H__ -#include +#include #include #include @@ -56,21 +56,7 @@ struct CDOC_EXPORT Recipient { KEYSHARE }; - /** - * @brief The public key type - */ - enum PKType : uint8_t { - /** - * Elliptic curve - */ - ECC, - /** - * RSA - */ - RSA - }; - - Recipient() = default; + Recipient() = default; /** * @brief The recipient type @@ -79,7 +65,8 @@ struct CDOC_EXPORT Recipient { /** * @brief The public key type */ - PKType pk_type = PKType::ECC; + Algorithm pk_type = Algorithm::ECC; + Curve ec_type = Curve::SECP_384_R1; /** * @brief The number of iterations for PBKDF. Value 0 means directly provided symmetric key. */ @@ -154,7 +141,7 @@ struct CDOC_EXPORT Recipient { /** * @brief Clear all values and set type to NONE */ - void clear() { type = Type::NONE; pk_type = PKType::ECC; label.clear(); kdf_iter = 0; rcpt_key.clear(); cert.clear(); } + void clear() { type = Type::NONE; pk_type = Algorithm::ECC; label.clear(); kdf_iter = 0; rcpt_key.clear(); cert.clear(); } /** * @brief A convenience method to check whether two recipients are both public key based and have the same keys. @@ -176,6 +163,9 @@ struct CDOC_EXPORT Recipient { * @return a new Recipient structure */ static Recipient makeSymmetric(std::string label, int32_t kdf_iter); + + static Recipient makeRSA(std::string label, std::vector public_key); + static Recipient makeECC(std::string label, std::vector public_key, Curve ec_type); /** * @brief Create a new public key based Recipient * @param label the label text @@ -183,7 +173,17 @@ struct CDOC_EXPORT Recipient { * @param pk_type the algorithm type (either ECC or RSA) * @return a new Recipient structure */ - static Recipient makePublicKey(std::string label, std::vector public_key, PKType pk_type); + static Recipient makePublicKey(std::string label, std::vector public_key, Algorithm pk_type) { + switch(pk_type) { + case RSA: + return makeRSA(label, public_key); + case ECC: + return makeECC(label, public_key, Curve::SECP_384_R1); + default: + return {}; + } + } + /** * @brief Create a new certificate based Recipient * @param label the label text @@ -192,6 +192,8 @@ struct CDOC_EXPORT Recipient { */ static Recipient makeCertificate(std::string label, std::vector cert); + static Recipient makeServerRSA(std::string label, std::vector public_key, std::string server_id); + static Recipient makeServerECC(std::string label, std::vector public_key, Curve ec_type, std::string server_id); /** * @brief Create a new capsule server based Recipient * If the label is empty, a machine-readable label text (public key version) is automatically generated according to CDoc2 specification. @@ -202,7 +204,16 @@ struct CDOC_EXPORT Recipient { * @param server_id the keyserver id * @return a new Recipient structure */ - static Recipient makeServer(std::string label, std::vector public_key, PKType pk_type, std::string server_id); + static Recipient makeServer(std::string label, std::vector public_key, Algorithm pk_type, std::string server_id) { + switch(pk_type) { + case RSA: + return makeServerRSA(label, public_key, server_id); + case ECC: + return makeServerECC(label, public_key, Curve::SECP_384_R1, server_id); + default: + return {}; + } + } /** * @brief Create a new capsule server based Recipient diff --git a/cdoc/schema/recipients.fbs b/cdoc/schema/recipients.fbs index 57b0e117..76061314 100644 --- a/cdoc/schema/recipients.fbs +++ b/cdoc/schema/recipients.fbs @@ -31,13 +31,15 @@ union KeyDetailsUnion { } // Elliptic curve type enum for ECCPublicKey recipient +// The clients should not crash with unknown values and try to continue, if possible. enum EllipticCurve:byte { UNKNOWN, - secp384r1 + secp384r1, + secp256r1 } table RsaKeyDetails { - //RSA pub key in DER - RFC8017 RSA Public Key Syntax (A.1.1) https://www.rfc-editor.org/rfc/rfc8017#page-54 + // RSA pub key in DER - RFC8017 RSA Public Key Syntax (A.1.1) https://www.rfc-editor.org/rfc/rfc8017#page-54 recipient_public_key: [ubyte] (required); } @@ -45,8 +47,8 @@ table EccKeyDetails { // Elliptic curve type enum curve: EllipticCurve = UNKNOWN; - //EC pub key in TLS 1.3 format https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2 - //for secp384r1 curve: 0x04 + X 48 coord bytes + Y coord 48 bytes) + // EC pub key in TLS 1.3 format https://datatracker.ietf.org/doc/html/rfc8446#section-4.2.8.2 + // for secp384r1 curve: 0x04 + X 48 coord bytes + Y coord 48 bytes) recipient_public_key: [ubyte] (required); } @@ -90,16 +92,14 @@ table PBKDF2Capsule { kdf_iterations: int32; } -// ShamirKeyShare share. One per key server. +// KeyShare url. Response KeyShare is defined https://github.com/open-eid/cdoc2-openapi/blob/55a0b02adae0d8c61f2589a47555a93e4cf31971/cdoc2-key-shares-openapi.yaml#L144 table KeyShare { //full url of key share, ex https://cdoc2-keyserver.dev.riaint.ee:8443/key-shares/SS0123456789ABCDEF - //url: string (required); - - // or server URL + // is defined as server_base_url // https://cdoc2-keyserver.dev.riaint.ee:8443/ server_base_url: string (required); - //SS0123456789ABCDEF + // and share_id SS0123456789ABCDEF share_id: string (required); } @@ -113,11 +113,16 @@ enum SharesScheme:byte { N_OF_N } -// SymmetricKey that is split between keyservers using Shamir Secret Sharing scheme +// SymmetricKey that is split between cdoc2-shares-servers table KeySharesCapsule { shares: [KeyShare] (required); salt: [ubyte] (required); recipient_type: KeyShareRecipientType = UNKNOWN; shares_scheme: SharesScheme = UNKNOWN; + + // recipient identifier, prefixed with type ("etsi/"). Part after "etsi/" must match subject/serialnumber in recipient certificate + // provided with auth token https://github.com/open-eid/cdoc2-openapi/blob/55a0b02adae0d8c61f2589a47555a93e4cf31971/cdoc2-key-shares-openapi.yaml#L54 + // example recipient_id "etsi/PNOEE-48010010101" where string after "etsi/" is ETSI Semantics Identifier defined in "ETSI EN 319 412-1" + // In future might support other identifiers in format "private/VENDOR/identifier" recipient_id: string (required); } diff --git a/libcdoc.i b/libcdoc.i index 3cb3cf31..ae3c2d22 100644 --- a/libcdoc.i +++ b/libcdoc.i @@ -178,9 +178,12 @@ Type getType() { return $self->type; } - PKType getPKType() { + Algorithm getAlgorithm() { return $self->pk_type; } + Curve getCurve() { + return $self->ec_type; + } std::string getLabel() { return $self->label; } diff --git a/test/data/ec-secp256r1-cert.der b/test/data/ec-secp256r1-cert.der new file mode 100644 index 0000000000000000000000000000000000000000..01e8b7477598f17b5a6ba570bd486b0d98343800 GIT binary patch literal 598 zcmXqLVhS>7V*I&)nTe5!NyN}m=q6V|)CJk%d;8t?+UF!(-?iF+i;Y98&EuRc3p0~J zy&<;&CmVAp3!5;LtE-`wfd+`f%%kXac&e*^kh_kTqocR3caUdrh?k?Ej%6;TQlj{8 zA^yHxwuQDL?vW`W6F;WMUlvdPJ?WlluEM*_+f0w=Cr5Wrop>*}bY8u1@8yyvmJ93r z7Y7>z8pr}eMwX97j77xJv$%Mnj1u>ockelE*Iy0g-!R?FKprHm%pzeR)_`3BKS+Tv zBjbM-Rs&`rg&e!g?hFPlObQX^C7uSqyldP07){aIRF3v literal 0 HcmV?d00001 diff --git a/test/data/ec-secp256r1-priv.der b/test/data/ec-secp256r1-priv.der new file mode 100644 index 0000000000000000000000000000000000000000..0a6599309c1bd6605cf9b0f17d8248629fea4906 GIT binary patch literal 138 zcmV;50CoQ`frkPC05B5<2P%e0&OHJF1_&yKNX|V20S5$aFlzz<0R$kGnKnNa|36}d zV?l@>#}AmB!A0^vE|hfXcF?b_fDD|WL<2$q1Y+HY8UWkAiQTx8VHf)n4~}gHp*<8w sS!7g^@n>Jt7i0U9-ZgC??rGZs^Z!o8PEnpiHZ^9KNX<0LWw literal 0 HcmV?d00001 diff --git a/test/libcdoc_boost.cpp b/test/libcdoc_boost.cpp index 1401dc39..81dfecc7 100644 --- a/test/libcdoc_boost.cpp +++ b/test/libcdoc_boost.cpp @@ -54,6 +54,9 @@ constexpr string_view TargetFile("test_data.txt.cdoc"); constexpr string_view ECPrivKeyFile("ec-secp384r1-priv.der"); constexpr string_view ECPubKeyFile("ec-secp384r1-pub.der"); constexpr string_view ECCertFile("ec-secp384r1-cert.der"); +constexpr string_view EC256PrivKeyFile("ec-secp256r1-priv.der"); +constexpr string_view EC256PubKeyFile("ec-secp256r1-pub.der"); +constexpr string_view EC256CertFile("ec-secp256r1-cert.der"); constexpr string_view RSAPrivKeyFile("rsa_2048_priv.der"); constexpr string_view RSAPubKeyFile("rsa_2048_pub.der"); constexpr string_view RSACertFile("rsa_2048_cert.der"); From db3a58b877cb9dad82f51878ff5c55d366778da0 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Tue, 17 Feb 2026 14:14:48 +0200 Subject: [PATCH 18/50] Added CDoc.h --- cdoc/CDoc.h | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 741b1e6c..bd0e4b08 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -19,7 +19,7 @@ #ifndef __CDOC_H__ #define __CDOC_H__ -#include "Exports.h" +#include #include #include @@ -129,6 +129,29 @@ CDOC_EXPORT std::string getErrorStr(int64_t code); CDOC_EXPORT std::string getVersion(); +/** + * @brief The public key algorithm + */ +enum Algorithm : uint8_t { + /** + * Elliptic curve + */ + ECC, + /** + * RSA + */ + RSA +}; + +/** + * @brief The EC curve used + */ +enum Curve : uint8_t { + SECP_384_R1, + SECP_256_R1 +}; + + // Logging interface /** From df088d7a7b5da645f1de070d3e0737524aff47fb Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 18 Feb 2026 09:55:57 +0200 Subject: [PATCH 19/50] Added cdoc-tool support and testing for secp256 curve --- cdoc/CDocCipher.cpp | 62 ++++++++++++++++++++++++++++++++++-------- cdoc/CMakeLists.txt | 2 +- cdoc/NetworkBackend.h | 2 +- cdoc/Recipient.cpp | 1 + cdoc/cdoc-tool.cpp | 46 ++++++++++++++++--------------- doc/tool.md | 2 +- test/libcdoc_boost.cpp | 16 ++++++----- 7 files changed, 87 insertions(+), 44 deletions(-) diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index f94ca413..54bac5f2 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -19,6 +19,7 @@ #include "CDocCipher.h" #include "CDocReader.h" #include "CDoc2.h" +#include "Crypto.h" #include "Lock.h" #include "NetworkBackend.h" #include "PKCS11Backend.h" @@ -35,6 +36,7 @@ #include #include #include +#include using namespace std; using namespace libcdoc; @@ -140,7 +142,6 @@ struct ToolCrypto : public libcdoc::CryptoBackend { EVP_PKEY *params = nullptr; if ((EVP_PKEY_paramgen_init(ctx.get()) < 0) || - (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx.get(), NID_secp384r1) < 0) || (EVP_PKEY_CTX_set_ec_param_enc(ctx.get(), OPENSSL_EC_NAMED_CURVE) < 0) || (EVP_PKEY_paramgen(ctx.get(), ¶ms) < 0)) return libcdoc::CRYPTO_ERROR; @@ -222,6 +223,15 @@ struct ToolNetwork : public libcdoc::NetworkBackend { }; +static int +EVP_PKEY_get_nid(EVP_PKEY *pkey) +{ + std::array name; + if (SSL_FAILED(EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name.data(), name.size(), nullptr), "EVP_PKEY_get_utf8_string_param")) + return NID_undef; + return OBJ_sn2nid(name.data()); +} + int CDocCipher::writer_push(CDocWriter& writer, const vector& rcpts, const vector& files) { for (const libcdoc::Recipient& rcpt : rcpts) { @@ -276,24 +286,51 @@ fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector d(i2d_PublicKey(pkey, nullptr), 0); - uint8_t *p = d.data(); - i2d_PublicKey(pkey, &p); - if (id == EVP_PKEY_EC) { - key = libcdoc::Recipient::makePublicKey(label, rcpt.secret, libcdoc::Algorithm::ECC); - } else if (id == EVP_PKEY_RSA) { - key = libcdoc::Recipient::makePublicKey(label, rcpt.secret, libcdoc::Algorithm::RSA); + if (algo == libcdoc::Algorithm::RSA) { + key = libcdoc::Recipient::makeRSA(label, rcpt.secret); + } else { + key = libcdoc::Recipient::makeECC(label, rcpt.secret, curve); } } LOG_DBG("Creating public key:"); } else if (rcpt.type == RcptInfo::Type::P11_SYMMETRIC) { key = libcdoc::Recipient::makeSymmetric(label, 0); + key.key_name = rcpt.label; } else if (rcpt.type == RcptInfo::Type::P11_PKI) { std::vector val; bool rsa; @@ -312,6 +349,7 @@ fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector public_key, Curve ec_ Recipient rcpt(Type::PUBLIC_KEY); rcpt.label = std::move(label); rcpt.pk_type = ECC; + rcpt.ec_type = ec_type; // 0x30 identifies SEQUENCE tag in ASN.1 encoding auto evp = Crypto::fromECPublicKeyDer(public_key); rcpt.rcpt_key = Crypto::toPublicKeyDer(evp.get()); diff --git a/cdoc/cdoc-tool.cpp b/cdoc/cdoc-tool.cpp index 30bd7364..1dacf2d5 100644 --- a/cdoc/cdoc-tool.cpp +++ b/cdoc/cdoc-tool.cpp @@ -52,28 +52,30 @@ static void print_usage(ostream& ofs) ofs << "cdoc-tool encrypt --rcpt RECIPIENT [--rcpt...] [-v1] [--genlabel] --out OUTPUTFILE FILE [FILE...]" << endl; ofs << " Encrypt files for one or more recipients" << endl; ofs << " RECIPIENT has to be one of the following:" << endl; - ofs << " [label]:cert:CERTIFICATE_FILE - public key from certificate" << endl; - ofs << " [label]:pkey:SECRET_KEY_HEX - public key" << endl; - ofs << " [label]:pfkey:PUB_KEY_FILE - path to DER file with EC (secp384r1 curve) public key" << endl; - ofs << " [label]:skey:SECRET_KEY_HEX - AES key" << endl; - ofs << " [label]:pw:PASSWORD - Derive key using PWBKDF" << endl; + ofs << " [label]:cert:CERTIFICATE_FILE - public key from certificate file (DER format)" << endl; + ofs << " [label]:pkey:SECRET_KEY_HEX - hex encoded public key (DER format; rsa, secp384r1 or secp256r1 key)." << endl; + ofs << " [label]:pfkey:PUB_KEY_FILE - public key from file (DER format; rsa, secp384r1 or secp256r1 key)." << endl; + ofs << " [label]:skey:SECRET_KEY_HEX - AES key, hex encoded" << endl; + ofs << " [label]:pw:PASSWORD - AES key derived from password with PWBKDF" << endl; ofs << " [label]:p11sk:SLOT:[PIN]:[PKCS11 ID]:[PKCS11 LABEL] - use AES key from PKCS11 module" << endl; ofs << " [label]:p11pk:SLOT:[PIN]:[PKCS11 ID]:[PKCS11 LABEL] - use public key from PKCS11 module" << endl; - ofs << " [label]:share:ID - use keyshares with given ID (personal code)" << endl; - ofs << " -v1 - creates CDOC1 version container. Supported only for encryption with certificate." << endl; - ofs << " --genlabel - If specified, the lock label is generated." << endl; + ofs << " [label]:share:ID - keyshares with given ID (personal code)" << endl; + ofs << " -v1 - creates CDOC1 version container. Supported only for encryption with certificate." << endl; + ofs << " --genlabel - Generate machine-readable label." << endl; ofs << endl; ofs << "cdoc-tool decrypt ARGUMENTS FILE [OUTPU_DIR]" << endl; - ofs << " Decrypt container using lock specified by label" << endl; + ofs << " Decrypt CDoc container using lock specified by label or number" << endl; ofs << " Supported arguments" << endl; - ofs << " --label LABEL - CDOC container's lock label" << endl; - ofs << " --label_idx INDEX - CDOC container's lock 1-based label index" << endl; - ofs << " --slot SLOT - PKCS11 slot number" << endl; - ofs << " --password PASSWORD - lock's password" << endl; - ofs << " --secret SECRET - secret phrase (AES key)" << endl; - ofs << " --pin PIN - PKCS11 pin" << endl; - ofs << " --key-id - PKCS11 key ID" << endl; - ofs << " --key-label - PKCS11 key label" << endl; + ofs << " --label LABEL - lock label" << endl; + ofs << " --label_idx INDEX - lock number (1-based)" << endl; + ofs << " --pkey PRIVATE_KEY_HEX - hex encoded private key (DER format)" << endl; + ofs << " --pfkey PRIVATE_KEY_HEX - private key from file (DER format)" << endl; + ofs << " --slot SLOT - PKCS11 slot number" << endl; + ofs << " --password PASSWORD - lock's password" << endl; + ofs << " --secret SECRET - secret (AES) key (hex encoded key value)" << endl; + ofs << " --pin PIN - PKCS11 pin" << endl; + ofs << " --key-id - PKCS11 key ID" << endl; + ofs << " --key-label - PKCS11 key label" << endl; ofs << endl; ofs << "cdoc-tool locks FILE" << endl; ofs << endl; @@ -81,11 +83,11 @@ static void print_usage(ostream& ofs) ofs << " Re-encrypts container for different recipient(s)" << endl; ofs << endl; ofs << "Common arguments:" << endl; - ofs << " --library - path to the PKCS11 library to be used" << endl; - ofs << " --server ID URL(S) - specifies a key or share server. The recipient key will be stored in server instead of in the document." << endl; - ofs << " for key server the url is either fetch or send url. For share server it is comma-separated list of share server urls." << endl; - ofs << " --accept FILENAME - specifies an accepted server certificate (in der encoding)" << endl; - ofs << " --log-level LEVEL - set logging level (FATAL, ERROR, WARNING, INFO, DEBUG, TRACE)" << endl; + ofs << " --library - path to the PKCS11 library to be used" << endl; + ofs << " --server ID URL(S) - specifies a key or share server. The recipient key will be stored in server instead of in the document." << endl; + ofs << " for key server the url is either fetch or send url. For share server it is comma-separated list of share server urls." << endl; + ofs << " --accept FILENAME - keyserver certificate file (in der encoding)" << endl; + ofs << " --log-level LEVEL - set logging level (FATAL, ERROR, WARNING, INFO, DEBUG, TRACE)" << endl; } static std::vector diff --git a/doc/tool.md b/doc/tool.md index 24ade630..5801e3b9 100644 --- a/doc/tool.md +++ b/doc/tool.md @@ -51,7 +51,7 @@ One or more recipients must be specified, each with its own encryption method. | `[label]:cert:CERTIFICATE_HEX` | Encryption public-key from certificate. The certificate must be provided as hex-encoded string | | `[label]:skey:SECRET_KEY_HEX` | Symmetric encryption with AES key. The key must be provided as hex-encoded string | | `[label]:pkey:SECRET_KEY_HEX` | Encryption with public-key. The key must be provided as hex-encoded string | -| `[label]:pfkey:PUB_KEY_FILE` | Encryption with public-key where the key is provided via path to DER file with EC (**secp384r1** curve) public key | +| `[label]:pfkey:PUB_KEY_FILE` | Encryption with public-key. The key (**secp384r1** or **secp256r1**) is read from the DER-encoded file. | | `[label]:pw:PASSWORD` | Encryption with derive key using PWBKDF | | `[label]:p11sk:SLOT:[PIN]:[PKCS11 ID]:[PKCS11 LABEL]` | Encryption with AES key from PKCS11 module | | `[label]:p11pk:SLOT:[PIN]:[PKCS11 ID]:[PKCS11 LABEL]` | Encryption with public key from PKCS11 module | diff --git a/test/libcdoc_boost.cpp b/test/libcdoc_boost.cpp index 81dfecc7..1768e002 100644 --- a/test/libcdoc_boost.cpp +++ b/test/libcdoc_boost.cpp @@ -274,7 +274,7 @@ encryptV2(const std::vector& files, const std::string& container, c } static void -decrypt(const std::vector& files, const std::string& container, const std::string& dir, libcdoc::RcptInfo& rcpt) +decrypt(const std::vector& files, const std::string& container, const std::string& dir, libcdoc::RcptInfo& rcpt, bool remove = true) { libcdoc::ToolConf conf; conf.input_files.push_back(container); @@ -289,7 +289,7 @@ decrypt(const std::vector& files, const std::string& container, con } path = fs::path(container); - if (fs::exists(path)) { + if (remove && fs::exists(path)) { error_code e; fs::remove(path, e); if(e) @@ -297,10 +297,10 @@ decrypt(const std::vector& files, const std::string& container, con } static void -decrypt(const std::vector& files, const std::string& container, const std::string& dir, const std::vector& key) +decrypt(const std::vector& files, const std::string& container, const std::string& dir, const std::vector& key, int idx = 0, bool remove = true) { - libcdoc::RcptInfo rcpt {.type=libcdoc::RcptInfo::LOCK, .secret=key, .lock_idx=0}; - decrypt(files, container, dir, rcpt); + libcdoc::RcptInfo rcpt {.type=libcdoc::RcptInfo::LOCK, .secret=key, .lock_idx=idx}; + decrypt(files, container, dir, rcpt, remove); } static int unicode_to_utf8 (unsigned int uval, uint8_t *d, uint64_t size) @@ -581,7 +581,8 @@ BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithECKey, EncryptFixture, * utf::description("Encrypting a file with EC key")) { std::vector rcpts { - {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(ECPubKeyFile)} + {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(ECPubKeyFile)}, + {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(EC256PubKeyFile)} }; encrypt(2, {checkDataFile(sources[0])}, formTargetFile("ECKeyUsage.cdoc"), rcpts); } @@ -589,7 +590,8 @@ BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithECKey, DecryptFixture, * utf::depends_on("ECKeyUsage/EncryptWithECKey") * utf::description("Decrypting a file with with EC private key")) { - decrypt({checkDataFile(sources[0])}, checkTargetFile("ECKeyUsage.cdoc"), tmpDataPath.string(), fetchDataFile(ECPrivKeyFile)); + decrypt({checkDataFile(sources[0])}, checkTargetFile("ECKeyUsage.cdoc"), tmpDataPath.string(), fetchDataFile(ECPrivKeyFile), 0, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile("ECKeyUsage.cdoc"), tmpDataPath.string(), fetchDataFile(EC256PrivKeyFile), 1, true); } BOOST_AUTO_TEST_SUITE_END() From 4a7a230a9f1c70c0c3bc41b489bd54f07e8a8fd9 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 18 Feb 2026 10:42:49 +0200 Subject: [PATCH 20/50] Allow unknown curve type while decrypting --- cdoc/CDoc.h | 1 + cdoc/CDoc2Reader.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 7eaac424..20001059 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -147,6 +147,7 @@ enum Algorithm : uint8_t { * @brief The EC curve used */ enum Curve : uint8_t { + UNKNOWN, SECP_384_R1, SECP_256_R1 }; diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 87d7c683..9a43d25f 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -514,8 +514,8 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor } else if (capsule->curve() == EllipticCurve::secp256r1) { lock.ec_type = Curve::SECP_256_R1; } else { - LOG_ERROR("Unsupported ECC curve: skipping"); - return; + LOG_WARN("Unknown ECC curve: {}", (int) capsule->curve()); + lock.ec_type = Curve::UNKNOWN; } lock.type = Lock::Type::PUBLIC_KEY; lock.pk_type = Algorithm::ECC; @@ -544,8 +544,8 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor } else if (eccDetails->curve() == EllipticCurve::secp256r1) { lock.ec_type = Curve::SECP_256_R1; } else { - LOG_ERROR("Unsupported ECC curve: skipping"); - return; + LOG_WARN("Unknown ECC curve: {}", (int) eccDetails->curve()); + lock.ec_type = Curve::UNKNOWN; } lock.pk_type = Algorithm::ECC; lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(eccDetails->recipient_public_key())); From 4f41425da378cf68980dbd418c0681c53eff201d Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 18 Feb 2026 15:37:03 +0200 Subject: [PATCH 21/50] Minor cleanups --- cdoc/CDoc.h | 3 ++- cdoc/CDoc2Reader.cpp | 12 ++++++------ cdoc/CDoc2Writer.cpp | 4 ++-- cdoc/Lock.h | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 20001059..01ac043b 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -133,6 +133,7 @@ CDOC_EXPORT std::string getVersion(); * @brief The public key algorithm */ enum Algorithm : uint8_t { + UNKNOWN_ALGORITHM, /** * Elliptic curve */ @@ -147,7 +148,7 @@ enum Algorithm : uint8_t { * @brief The EC curve used */ enum Curve : uint8_t { - UNKNOWN, + UNKNOWN_CURVE, SECP_384_R1, SECP_256_R1 }; diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 9a43d25f..81ae1e49 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -509,16 +509,16 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor { case Capsule::recipients_ECCPublicKeyCapsule: if(const auto *capsule = recipient.capsule_as_recipients_ECCPublicKeyCapsule()) { + lock.type = Lock::Type::PUBLIC_KEY; + lock.pk_type = Algorithm::ECC; if(capsule->curve() == EllipticCurve::secp384r1) { lock.ec_type = Curve::SECP_384_R1; } else if (capsule->curve() == EllipticCurve::secp256r1) { lock.ec_type = Curve::SECP_256_R1; } else { LOG_WARN("Unknown ECC curve: {}", (int) capsule->curve()); - lock.ec_type = Curve::UNKNOWN; + lock.ec_type = Curve::UNKNOWN_CURVE; } - lock.type = Lock::Type::PUBLIC_KEY; - lock.pk_type = Algorithm::ECC; lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(capsule->recipient_public_key())); lock.setBytes(Lock::Params::KEY_MATERIAL, toUint8Vector(capsule->sender_public_key())); LOG_DBG("Load PK: {}", toHex(lock.getBytes(Lock::Params::RCPT_KEY))); @@ -539,16 +539,16 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor switch (details) { case KeyDetailsUnion::EccKeyDetails: if(const EccKeyDetails *eccDetails = capsule->recipient_key_details_as_EccKeyDetails()) { + lock.pk_type = Algorithm::ECC; + lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(eccDetails->recipient_public_key())); if(eccDetails->curve() == EllipticCurve::secp384r1) { lock.ec_type = Curve::SECP_384_R1; } else if (eccDetails->curve() == EllipticCurve::secp256r1) { lock.ec_type = Curve::SECP_256_R1; } else { LOG_WARN("Unknown ECC curve: {}", (int) eccDetails->curve()); - lock.ec_type = Curve::UNKNOWN; + lock.ec_type = Curve::UNKNOWN_CURVE; } - lock.pk_type = Algorithm::ECC; - lock.setBytes(Lock::Params::RCPT_KEY, toUint8Vector(eccDetails->recipient_public_key())); } break; case KeyDetailsUnion::RsaKeyDetails: diff --git a/cdoc/CDoc2Writer.cpp b/cdoc/CDoc2Writer.cpp index 4b299f0f..3f09fa17 100644 --- a/cdoc/CDoc2Writer.cpp +++ b/cdoc/CDoc2Writer.cpp @@ -98,7 +98,7 @@ CDoc2Writer::writeHeader(const std::vector &recipients) struct ECData { int ossl_nid; cdoc20::recipients::EllipticCurve fb_type; - const char *server_name; + const char *api_id; }; static std::map ecdata = { @@ -300,7 +300,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vectorsendKey(cinfo, send_url, rcpt.rcpt_key, key_material, ecdata[rcpt.ec_type].server_name, rcpt.expiry_ts); + auto result = network->sendKey(cinfo, send_url, rcpt.rcpt_key, key_material, ecdata[rcpt.ec_type].api_id, rcpt.expiry_ts); if (result < 0) { setLastError(network->getLastErrorStr(result)); LOG_ERROR("{}", last_error); diff --git a/cdoc/Lock.h b/cdoc/Lock.h index 7d1d3744..0d9dfd35 100644 --- a/cdoc/Lock.h +++ b/cdoc/Lock.h @@ -19,7 +19,7 @@ #ifndef __LOCK_H__ #define __LOCK_H__ -#include +#include #include #include From 7f4aa1c462f1fa394c43eda3a589285dadba4ba0 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 19 Feb 2026 16:13:40 +0200 Subject: [PATCH 22/50] Removed Algorithm enum from Certificate and PKCS11 cleanup --- cdoc/CDoc1Reader.cpp | 6 +++--- cdoc/CDocCipher.cpp | 16 +++++++--------- cdoc/Certificate.cpp | 4 ++-- cdoc/Certificate.h | 7 +------ cdoc/CryptoBackend.h | 6 +++--- cdoc/PKCS11Backend.cpp | 9 +++++---- cdoc/PKCS11Backend.h | 11 +++++------ cdoc/Recipient.cpp | 2 +- cdoc/Recipient.h | 25 ++++++++++++++++++++----- 9 files changed, 47 insertions(+), 39 deletions(-) diff --git a/cdoc/CDoc1Reader.cpp b/cdoc/CDoc1Reader.cpp index 3933f3d1..5c45da88 100644 --- a/cdoc/CDoc1Reader.cpp +++ b/cdoc/CDoc1Reader.cpp @@ -89,12 +89,12 @@ CDoc1Reader::getLockForCert(const std::vector& cert) ll.encrypted_fmk.empty()) continue; switch(cc.getAlgorithm()) { - case libcdoc::Certificate::RSA: + case libcdoc::RSA: if (ll.getString(Lock::Params::METHOD) == libcdoc::Crypto::RSA_MTH) { return i; } break; - case libcdoc::Certificate::ECC: + case libcdoc::ECC: if(!ll.getBytes(Lock::Params::KEY_MATERIAL).empty() && std::find(SUPPORTED_KWAES.cbegin(), SUPPORTED_KWAES.cend(), ll.getString(Lock::Params::METHOD)) != SUPPORTED_KWAES.cend()) { return i; @@ -331,7 +331,7 @@ CDoc1Reader::CDoc1Reader(libcdoc::DataSource *src, bool delete_on_close) Certificate ssl(cert); key.setBytes(Lock::CERT, std::move(cert)); key.setBytes(Lock::RCPT_KEY, ssl.getPublicKey()); - key.pk_type = (ssl.getAlgorithm() == libcdoc::Certificate::RSA) ? Algorithm::RSA : Algorithm::ECC; + key.pk_type = ssl.getAlgorithm(); } // EncryptedData/KeyInfo/EncryptedKey/KeyInfo/CipherData/CipherValue else if(reader.isElement("CipherValue")) diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index 54bac5f2..fb692f01 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -206,8 +206,7 @@ struct ToolNetwork : public libcdoc::NetworkBackend { libcdoc::result_t getClientTLSCertificate(std::vector& dst) override final { if (rcpt_idx >= crypto->rcpts.size()) rcpt_idx = 0; const libcdoc::RcptInfo& rcpt = crypto->rcpts[rcpt_idx]; - bool rsa = false; - return crypto->p11->getCertificate(dst, rsa, rcpt.p11.slot, rcpt.secret, rcpt.p11.key_id, rcpt.p11.key_label); + return crypto->p11->getCertificate(dst, rcpt.p11.slot, rcpt.secret, rcpt.p11.key_id, rcpt.p11.key_label); } libcdoc::result_t getPeerTLSCertificates(std::vector> &dst) override final { @@ -333,18 +332,18 @@ fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector val; - bool rsa; + libcdoc::Algorithm algo; ToolPKCS11* p11 = dynamic_cast(crypto.p11.get()); - int result = p11->getPublicKey(val, rsa, rcpt.p11.slot, rcpt.secret, rcpt.p11.key_id, rcpt.p11.key_label); + int result = p11->getPublicKey(val, algo, rcpt.p11.slot, rcpt.secret, rcpt.p11.key_id, rcpt.p11.key_label); if (result != libcdoc::OK) { LOG_ERROR("No such public key: {}", rcpt.p11.key_label); continue; } - LOG_DBG("Public key ({}): {}", rsa ? "rsa" : "ecc", toHex(val)); + LOG_DBG("Public key ({}): {}", (algo == libcdoc::Algorithm::RSA) ? "rsa" : "ecc", toHex(val)); if (!conf.servers.empty()) { - key = libcdoc::Recipient::makeServer(label, val, rsa ? libcdoc::Algorithm::RSA : libcdoc::Algorithm::ECC, conf.servers[0].ID); + key = libcdoc::Recipient::makeServer(label, val, algo, conf.servers[0].ID); } else { - key = libcdoc::Recipient::makePublicKey(label, val, rsa ? libcdoc::Algorithm::RSA : libcdoc::Algorithm::ECC); + key = libcdoc::Recipient::makePublicKey(label, val, algo); } } else if (rcpt.type == RcptInfo::Type::PASSWORD) { LOG_DBG("Creating password key:"); @@ -440,10 +439,9 @@ int CDocCipher::Decrypt(ToolConf& conf, const RcptInfo& recipient) } lock_idx = recipient.lock_idx; } else if (crypto.p11) { - bool isRsa; vector cert_bytes; ToolPKCS11* p11 = dynamic_cast(crypto.p11.get()); - int64_t result = p11->getCertificate(cert_bytes, isRsa, (int) recipient.p11.slot, recipient.secret, recipient.p11.key_id, recipient.p11.key_label); + int64_t result = p11->getCertificate(cert_bytes, (int) recipient.p11.slot, recipient.secret, recipient.p11.key_id, recipient.p11.key_label); if (result != libcdoc::OK) { LOG_ERROR("Certificate reading from SC card failed. Key label: {}", recipient.p11.key_label); return 1; diff --git a/cdoc/Certificate.cpp b/cdoc/Certificate.cpp index 60769c41..24dc274f 100644 --- a/cdoc/Certificate.cpp +++ b/cdoc/Certificate.cpp @@ -144,7 +144,7 @@ Certificate::getPublicKey() const return {}; } -Certificate::Algorithm +libcdoc::Algorithm Certificate::getAlgorithm() const { if(!cert) @@ -153,7 +153,7 @@ Certificate::getAlgorithm() const EVP_PKEY *pkey = X509_get0_pubkey(cert.get()); int alg = EVP_PKEY_get_base_id(pkey); - return (alg == EVP_PKEY_RSA) ? Algorithm::RSA : Algorithm::ECC; + return (alg == EVP_PKEY_RSA) ? Algorithm::RSA : (alg == EVP_PKEY_EC) ? Algorithm::ECC : Algorithm::UNKNOWN_ALGORITHM; } std::vector Certificate::getDigest() const diff --git a/cdoc/Certificate.h b/cdoc/Certificate.h index 4fd7a54d..d477521a 100644 --- a/cdoc/Certificate.h +++ b/cdoc/Certificate.h @@ -19,7 +19,7 @@ #ifndef SSLCERTIFICATE_H #define SSLCERTIFICATE_H -#include "Exports.h" +#include "CDoc.h" #include "utils/memory.h" @@ -32,11 +32,6 @@ namespace libcdoc { class Certificate { public: - enum Algorithm : unsigned char { - RSA, - ECC - }; - enum EIDType : unsigned char { Unknown, IDCard, diff --git a/cdoc/CryptoBackend.h b/cdoc/CryptoBackend.h index bf63d5b4..04253a2c 100644 --- a/cdoc/CryptoBackend.h +++ b/cdoc/CryptoBackend.h @@ -72,7 +72,7 @@ struct CDOC_EXPORT CryptoBackend { /** * @brief Derive shared secret * - * Derive a shared secret from private key of given lock and public key using ECDH1 algorithm. + * Derive a shared secret using the private key of recipient and the public key of lock using ECDH1 algorithm. * @param dst the container for shared secret * @param public_key ECDH public key used to derive shared secret * @param idx lock index (0-based) in container @@ -153,11 +153,11 @@ struct CDOC_EXPORT CryptoBackend { int32_t kdf_iter, unsigned int idx); /** - * @brief sign Sign message with given algorithm + * @brief sign Sign message with given hash algorithm using the private key of given lock * @param dst the destination container for signed message * @param algorithm hashing algorithm * @param digest a message to sign - * @param idx lock or recipient index (0-based) in container + * @param idx lock index (0-based) in container * @return error code or OK */ virtual result_t sign(std::vector& dst, HashAlgorithm algorithm, const std::vector &digest, unsigned int idx) { diff --git a/cdoc/PKCS11Backend.cpp b/cdoc/PKCS11Backend.cpp index 914004c8..4f7cd58f 100644 --- a/cdoc/PKCS11Backend.cpp +++ b/cdoc/PKCS11Backend.cpp @@ -319,7 +319,7 @@ libcdoc::PKCS11Backend::usePrivateKey(int slot, const std::vector& pin, } libcdoc::result_t -libcdoc::PKCS11Backend::getCertificate(std::vector& val, bool& rsa, int slot, const std::vector& pin, const std::vector& id, const std::string& label) +libcdoc::PKCS11Backend::getCertificate(std::vector& val, int slot, const std::vector& pin, const std::vector& id, const std::string& label) { if(!d) return CRYPTO_ERROR; if (!d->session) { @@ -339,7 +339,7 @@ libcdoc::PKCS11Backend::getCertificate(std::vector& val, bool& rsa, int } libcdoc::result_t -libcdoc::PKCS11Backend::getPublicKey(std::vector& val, bool& rsa, int slot, const std::vector& pin, const std::vector& id, const std::string& label) +libcdoc::PKCS11Backend::getPublicKey(std::vector& val, libcdoc::Algorithm& algorithm, int slot, const std::vector& pin, const std::vector& id, const std::string& label) { if(!d) return CRYPTO_ERROR; if (!d->session) { @@ -355,8 +355,9 @@ libcdoc::PKCS11Backend::getPublicKey(std::vector& val, bool& rsa, int s LOG_DBG("PKCS11: getValue CKA_KEY_TYPE error"); return CRYPTO_ERROR; } - rsa = (*((CK_KEY_TYPE *) v.data()) == CKK_RSA); - if (rsa) return libcdoc::NOT_IMPLEMENTED; + if (*((CK_KEY_TYPE *) v.data()) != CKK_EC) + return libcdoc::NOT_IMPLEMENTED; + algorithm = Algorithm::ECC; v = d->attribute(d->session, handle, CKA_EC_PARAMS); if (v.empty()) { LOG_DBG("PKCS11: getValue CKA_EC_PARAMS error"); diff --git a/cdoc/PKCS11Backend.h b/cdoc/PKCS11Backend.h index 5deb3ed7..17993000 100644 --- a/cdoc/PKCS11Backend.h +++ b/cdoc/PKCS11Backend.h @@ -106,28 +106,27 @@ struct CDOC_EXPORT PKCS11Backend : public CryptoBackend { * Get a certificate value given slot, label and id. * Both key id and label have to match unless either one is empty. * @param val a destination container for value - * @param rsa will be set true is certificate uses RSA key * @param slot the slot to use - * @param pin the pin code + * @param pin the pin code or empty if public * @param id certificate id or empty vector * @param label certificate label or empty vector * @return error code or OK */ - result_t getCertificate(std::vector& val, bool& rsa, int slot, const std::vector& pin, const std::vector& id, const std::string& label); + result_t getCertificate(std::vector& val, int slot, const std::vector& pin, const std::vector& id, const std::string& label); /** * @brief get public key value * * Get a public key value given slot, label and id. * Both key id and label have to match unless either one is empty. * @param val a destination container for value - * @param rsa will be set true is public key uses RSA key + * @param algorithm the output parameter for public key algorithm * @param slot the slot to use - * @param pin the pin code + * @param pin the pin code or empty if public * @param id public key id or empty vector * @param label public key label or empty vector * @return error code or OK */ - result_t getPublicKey(std::vector& val, bool& rsa, int slot, const std::vector& pin, const std::vector& id, const std::string& label); + result_t getPublicKey(std::vector& val, libcdoc::Algorithm& algorithm, int slot, const std::vector& pin, const std::vector& id, const std::string& label); /** * @brief loads key for encryption/decryption diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index bfd03274..fcfa66ba 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -96,7 +96,7 @@ Recipient::makeCertificate(std::string label, std::vector cert) rcpt.label = std::move(label); rcpt.cert = std::move(cert); rcpt.rcpt_key = x509.getPublicKey(); - rcpt.pk_type = (x509.getAlgorithm() == libcdoc::Certificate::RSA) ? Algorithm::RSA : Algorithm::ECC; + rcpt.pk_type = x509.getAlgorithm(); rcpt.expiry_ts = x509.getNotAfter(); return rcpt; } diff --git a/cdoc/Recipient.h b/cdoc/Recipient.h index 5a0ae66c..fa624ebc 100644 --- a/cdoc/Recipient.h +++ b/cdoc/Recipient.h @@ -138,11 +138,6 @@ struct CDOC_EXPORT Recipient { */ bool isKeyShare() const { return type == Type::KEYSHARE; } - /** - * @brief Clear all values and set type to NONE - */ - void clear() { type = Type::NONE; pk_type = Algorithm::ECC; label.clear(); kdf_iter = 0; rcpt_key.clear(); cert.clear(); } - /** * @brief A convenience method to check whether two recipients are both public key based and have the same keys. * @param other another Recipient @@ -158,16 +153,29 @@ struct CDOC_EXPORT Recipient { /** * @brief Create a new symmetric key based Recipient + * + * If the label is empty, a machine-readable label will be created according to CDoc2 specification + * * @param label the label text * @param kdf_iter the number of PBKDF iterations (0 if full key is provided) * @return a new Recipient structure */ static Recipient makeSymmetric(std::string label, int32_t kdf_iter); + /** + * @brief Create a new public key based Recipient with RSA algorithm + * + * @param label + * @param public_key + * @return Recipient + */ static Recipient makeRSA(std::string label, std::vector public_key); static Recipient makeECC(std::string label, std::vector public_key, Curve ec_type); /** * @brief Create a new public key based Recipient + * + * If the label is empty, a machine-readable label will be created according to CDoc2 specification + * * @param label the label text * @param public_key the public key value * @param pk_type the algorithm type (either ECC or RSA) @@ -186,6 +194,9 @@ struct CDOC_EXPORT Recipient { /** * @brief Create a new certificate based Recipient + * + * If the label is empty, a machine-readable label will be created according to CDoc2 specification + * * @param label the label text * @param cert the certificate value (der-encoded) * @return a new Recipient structure @@ -196,6 +207,7 @@ struct CDOC_EXPORT Recipient { static Recipient makeServerECC(std::string label, std::vector public_key, Curve ec_type, std::string server_id); /** * @brief Create a new capsule server based Recipient + * * If the label is empty, a machine-readable label text (public key version) is automatically generated according to CDoc2 specification. * * @param label the label text @@ -217,6 +229,7 @@ struct CDOC_EXPORT Recipient { /** * @brief Create a new capsule server based Recipient + * * If the label is empty, a machine-readable label text (either eID or certificate version) is automatically generated according to CDoc2 specification. * * @param label the label text @@ -229,6 +242,8 @@ struct CDOC_EXPORT Recipient { /** * @brief Create new keyshare recipient * + * If the label text is empty, a machine-readable label will be created according to CDoc2 specification + * * @param label the label text * @param server_id the id of share server group * @param recipient_id the recipient id (PNOEE-01234567890) From 7d5f35dc77cbdb5c08737b7f7026e967605868f4 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Tue, 3 Mar 2026 13:59:43 +0200 Subject: [PATCH 23/50] Export CDocCipher internally for cdoc-tool --- cdoc/CDocCipher.h | 3 ++- cdoc/CMakeLists.txt | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cdoc/CDocCipher.h b/cdoc/CDocCipher.h index 42b76cfb..3138dc50 100644 --- a/cdoc/CDocCipher.h +++ b/cdoc/CDocCipher.h @@ -23,6 +23,7 @@ #include "CDocWriter.h" #include "RcptInfo.h" #include "ToolConf.h" +#include "Exports.h" #include #include @@ -31,7 +32,7 @@ namespace libcdoc { -class CDocCipher +class CDOC_EXPORT CDocCipher { public: CDocCipher() = default; diff --git a/cdoc/CMakeLists.txt b/cdoc/CMakeLists.txt index 61e43697..9117edaa 100644 --- a/cdoc/CMakeLists.txt +++ b/cdoc/CMakeLists.txt @@ -58,6 +58,7 @@ add_library(cdoc CDoc2.h Wrapper.h utils/memory.h + CDocCipher.cpp CDocCipher.h ) set_target_properties(cdoc PROPERTIES VERSION ${PROJECT_VERSION} @@ -89,9 +90,9 @@ target_link_libraries(cdoc PRIVATE ) if(BUILD_TOOLS) - add_executable(cdoc-tool cdoc-tool.cpp CDocCipher.cpp) + add_executable(cdoc-tool cdoc-tool.cpp) target_include_directories(cdoc-tool PRIVATE ${OPENSSL_INCLUDE_DIR}) - target_link_libraries(cdoc-tool cdoc_ver cdoc OpenSSL::SSL) + target_link_libraries(cdoc-tool cdoc_ver cdoc) set_target_properties(cdoc-tool PROPERTIES INSTALL_RPATH $<$:@executable_path/../lib> ) From 1022623d13bdec661a341ecdb22cbb367f951097 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 11 Mar 2026 18:40:33 +0200 Subject: [PATCH 24/50] Fix label parsing --- cdoc/Recipient.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 0e699844..d5acb415 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -257,7 +257,11 @@ map Recipient::parseLabel(const string& label) string::size_type base64IndPos = label_wo_prefix.find(LABELBASE64IND); if (base64IndPos == string::npos) { - label_to_prcss = std::move(label_wo_prefix); + if (label_wo_prefix.starts_with(",")) { + label_to_prcss = label_wo_prefix.substr(1); + } else { + label_to_prcss = std::move(label_wo_prefix); + } } else { @@ -278,7 +282,10 @@ map Recipient::parseLabel(const string& label) } else { - parsed_label[urlDecode(label_data_parts[0])] = urlDecode(label_data_parts[1]); + std::string key = urlDecode(label_data_parts[0]); + std::string value = urlDecode(label_data_parts[1]); + std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c){ return std::tolower(c); }); + parsed_label[key] = value; } } From fe80e763b3ac091fb91a14164c661fc5a0cb72ff Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 13 Mar 2026 17:20:11 +0200 Subject: [PATCH 25/50] Handle "PaxHeaders" paths correctly --- cdoc/Tar.cpp | 141 ++++++++++++++++++++++++++++++++++----------------- cdoc/Tar.h | 4 +- 2 files changed, 98 insertions(+), 47 deletions(-) diff --git a/cdoc/Tar.cpp b/cdoc/Tar.cpp index c4775fdb..32a11f1b 100644 --- a/cdoc/Tar.cpp +++ b/cdoc/Tar.cpp @@ -96,7 +96,15 @@ struct libcdoc::Header { referenceChecksum == checkSum.second; } - bool operator==(const Header&) const = default; + std::string getName() const { + return std::string(name.data(), std::min(name.size(), strlen(name.data()))); + } + + int64_t getSize() const { + return fromOctal(size); + } + + bool operator==(const Header&) const = default; }; static_assert (sizeof(Header) == BLOCKSIZE, "Header struct size is incorrect"); @@ -145,8 +153,10 @@ libcdoc::TarConsumer::writeHeader(const Header &h) noexcept { } libcdoc::result_t -libcdoc::TarConsumer::writeHeader(Header &h, int64_t size) noexcept { +libcdoc::TarConsumer::writeHeader(Header &h, const std::string& name, int64_t size) noexcept { h.chksum.fill(' '); + size_t len = std::min(name.size(), h.name.size()); + std::copy_n(name.cbegin(), len, h.name.begin()); toOctal(h.size, size); toOctal(h.chksum, h.checksum().first); return writeHeader(h); @@ -205,18 +215,30 @@ libcdoc::TarConsumer::open(const std::string& name, int64_t size) _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()); - // TODO: Create pax record if name contains special symbols - if(name.size() > 100 || size > 07777777) { + bool need_pax_name = (name.size() >= 100); + for (auto c : name) { + if ((c & 0x80) || (c < ' ')) { + need_pax_name = true; + break; + } + } + if(need_pax_name || size > 07777777) { + LOG_DBG("Writing Pax header: name {} size {}", name, size); h.typeflag = 'x'; std::string paxData; - if(name.size() > 100) + if(need_pax_name) paxData += toPaxRecord("path", name); if(size > 07777777) paxData += toPaxRecord("size", std::to_string(size)); - if (auto rv = writeHeader(h, paxData.size()); rv != OK) + std::filesystem::path path(name); + if (path.has_parent_path()) { + path = path.parent_path() / "PaxHeaders.X" / path.filename(); + } else { + path = std::filesystem::path("./PaxHeaders.X") / path.filename(); + } + LOG_DBG("Pax path: {}", path.string()); + if (auto rv = writeHeader(h, path.string(), paxData.size()); rv != OK) return rv; if (auto rv = _dst->write((const uint8_t *) paxData.data(), paxData.size()); rv != paxData.size()) return rv < OK ? rv : OUTPUT_ERROR; @@ -224,7 +246,7 @@ libcdoc::TarConsumer::open(const std::string& name, int64_t size) return rv; } h.typeflag = '0'; - return writeHeader(h, size); + return writeHeader(h, name, size); } libcdoc::TarSource::TarSource(DataSource *src, bool take_ownership) @@ -271,11 +293,54 @@ libcdoc::TarSource::isEof() noexcept return _eof; } +libcdoc::result_t +libcdoc::TarSource::readPaxHeader(const Header& hdr, std::string& name, int64_t& size) +{ + int64_t h_size = hdr.getSize(); + std::vector pax_in(h_size); + result_t result = _src->read((uint8_t *) pax_in.data(), pax_in.size()); + if (result != h_size) { + _error = INPUT_STREAM_ERROR; + return _error; + } + std::string paxData(pax_in.data(), pax_in.size()); + _src->skip(padding(h_size)); + // Parse Pax data + std::stringstream ss(paxData); + for(const std::string &data: split(paxData, '\n')) { + if(data.empty()) break; + size_t eq_pos = data.find_first_of('='); + if (eq_pos == std::string::npos) { + _error = DATA_FORMAT_ERROR; + return _error; + } + std::string headerValue = data.substr(eq_pos + 1, data.size() - eq_pos - 1); + size_t sp_pos = data.find_first_of(' '); + if ((sp_pos == std::string::npos) || (sp_pos >= eq_pos)) { + _error = DATA_FORMAT_ERROR; + return _error; + } + std::string lenStr = data.substr(0, sp_pos); + std::string keyWord = data.substr(sp_pos + 1, eq_pos - sp_pos - 1); + if(data.size() + 1 != stoi(lenStr)) { + _error = DATA_FORMAT_ERROR; + return _error; + } + LOG_DBG("PAX {} : {}", keyWord, headerValue); + if(keyWord == "path") + name = std::move(headerValue); + if(keyWord == "size") + size = stoll(headerValue); + } + return OK; +} + libcdoc::result_t libcdoc::TarSource::next(std::string& name, int64_t& size) { Header h; + // Skip if not at the start of a block if (_pos < _block_size) { int64_t result = _src->skip(_block_size - _pos); _pos = 0; @@ -286,18 +351,23 @@ libcdoc::TarSource::next(std::string& name, int64_t& size) return _error; } } + while (!_src->isEof()) { + // Read header int64_t result = _src->read((uint8_t *)&h, BLOCKSIZE); if (result != BLOCKSIZE) { _error = INPUT_STREAM_ERROR; return _error; } if (h.isNull()) { + // Two null headers mark end of archive + LOG_DBG("NULL header"); result = _src->read((uint8_t *)&h, BLOCKSIZE); if (result != BLOCKSIZE) { _error = INPUT_STREAM_ERROR; return _error; } + LOG_DBG("EOF"); _eof = true; return END_OF_STREAM; } @@ -305,18 +375,15 @@ libcdoc::TarSource::next(std::string& name, int64_t& size) _error = DATA_FORMAT_ERROR; return _error; } + LOG_DBG("Header typeflag {} name {} size {}", h.typeflag, h.getName(), h.getSize()); - std::string h_name = std::string(h.name.data(), std::min(h.name.size(), strlen(h.name.data()))); - size_t h_size = fromOctal(h.size); + std::string h_name; + int64_t h_size = -1; if(h.typeflag == 'x') { - std::vector pax_in(h_size); - result = _src->read((uint8_t *) pax_in.data(), pax_in.size()); - if (result != h_size) { - _error = INPUT_STREAM_ERROR; + _error = readPaxHeader(h, h_name, h_size); + if (_error != OK) return _error; - } - std::string paxData(pax_in.data(), pax_in.size()); - _src->skip(padding(h_size)); + // Read ustar header result = _src->read((uint8_t *)&h, BLOCKSIZE); if (result != BLOCKSIZE) { _error = INPUT_STREAM_ERROR; @@ -326,40 +393,22 @@ libcdoc::TarSource::next(std::string& name, int64_t& size) _error = DATA_FORMAT_ERROR; return _error; } - h_size = fromOctal(h.size); - std::stringstream ss(paxData); - for(const std::string &data: split(paxData, '\n')) { - if(data.empty()) break; - size_t eq_pos = data.find_first_of('='); - if (eq_pos == std::string::npos) { - _error = DATA_FORMAT_ERROR; - return _error; - } - std::string headerValue = data.substr(eq_pos + 1, data.size() - eq_pos - 1); - size_t sp_pos = data.find_first_of(' '); - if ((sp_pos == std::string::npos) || (sp_pos >= eq_pos)) { - _error = DATA_FORMAT_ERROR; - return _error; - } - std::string lenStr = data.substr(0, sp_pos); - std::string keyWord = data.substr(sp_pos + 1, eq_pos - sp_pos - 1); - if(data.size() + 1 != stoi(lenStr)) { - _error = DATA_FORMAT_ERROR; - return _error; - } - if(keyWord == "path") h_name = std::move(headerValue); - if(keyWord == "size") h_size = stoi(headerValue); + if(h.typeflag != '0' && h.typeflag != 0) { + _error = DATA_FORMAT_ERROR; + return _error; } } - if(h.typeflag == '0' || h.typeflag == 0) { - name = std::move(h_name); - size = h_size; + if (h.typeflag == '0' || h.typeflag == 0) { + name = (h_name.empty()) ? h.getName() : std::move(h_name); + size = (h_size < 0) ? h.getSize() : h_size; _pos = 0; - _data_size = h_size; - _block_size = h_size + padding(h_size); + _data_size = size; + _block_size = size + padding(size); _eof = false; return OK; } else { + // Skip other header types ('g') + h_size = h.getSize(); _src->skip(h_size + padding(h_size)); } } diff --git a/cdoc/Tar.h b/cdoc/Tar.h index 0ec7c832..a75be590 100644 --- a/cdoc/Tar.h +++ b/cdoc/Tar.h @@ -37,7 +37,7 @@ struct TarConsumer final : public MultiDataConsumer libcdoc::result_t open(const std::string& name, int64_t size) final; private: result_t writeHeader(const Header &h) noexcept; - result_t writeHeader(Header &h, int64_t size) noexcept; + result_t writeHeader(Header &h, const std::string& name, int64_t size) noexcept; result_t writePadding(int64_t size) noexcept; DataConsumer *_dst; @@ -64,6 +64,8 @@ struct TarSource : public MultiDataSource size_t _block_size; size_t _data_size; size_t _pos; + + libcdoc::result_t readPaxHeader(const Header& hdr, std::string& name, int64_t& size); }; } // namespace libcdoc From 1be01a709ef9aaba7c322b6fed3caf6fa2f9e4cb Mon Sep 17 00:00:00 2001 From: lauris71 Date: Mon, 16 Mar 2026 17:31:22 +0200 Subject: [PATCH 26/50] Apply suggestions from code review Co-authored-by: Raul Metsma --- cdoc/Tar.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/cdoc/Tar.cpp b/cdoc/Tar.cpp index 32a11f1b..90484854 100644 --- a/cdoc/Tar.cpp +++ b/cdoc/Tar.cpp @@ -297,16 +297,14 @@ libcdoc::result_t libcdoc::TarSource::readPaxHeader(const Header& hdr, std::string& name, int64_t& size) { int64_t h_size = hdr.getSize(); - std::vector pax_in(h_size); - result_t result = _src->read((uint8_t *) pax_in.data(), pax_in.size()); + std::string paxData(h_size, 0); + result_t result = _src->read((uint8_t *) paxData.data(), paxData.size()); if (result != h_size) { _error = INPUT_STREAM_ERROR; return _error; } - std::string paxData(pax_in.data(), pax_in.size()); _src->skip(padding(h_size)); // Parse Pax data - std::stringstream ss(paxData); for(const std::string &data: split(paxData, '\n')) { if(data.empty()) break; size_t eq_pos = data.find_first_of('='); From 7d533a0d0d00d68c360c63f978b8eebf82fe4f42 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 25 Mar 2026 15:29:28 +0200 Subject: [PATCH 27/50] Fix tar filenames if only PAX size is used --- cdoc/Tar.cpp | 15 +++++++-------- cdoc/Tar.h | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/cdoc/Tar.cpp b/cdoc/Tar.cpp index 90484854..02077958 100644 --- a/cdoc/Tar.cpp +++ b/cdoc/Tar.cpp @@ -153,7 +153,9 @@ libcdoc::TarConsumer::writeHeader(const Header &h) noexcept { } libcdoc::result_t -libcdoc::TarConsumer::writeHeader(Header &h, const std::string& name, int64_t size) noexcept { +libcdoc::TarConsumer::writeHeader(const std::string& name, int64_t size, char typeflag) noexcept { + Header h {}; + h.typeflag = typeflag; h.chksum.fill(' '); size_t len = std::min(name.size(), h.name.size()); std::copy_n(name.cbegin(), len, h.name.begin()); @@ -214,7 +216,6 @@ libcdoc::TarConsumer::open(const std::string& name, int64_t size) _current_size = size; _current_written = 0; - Header h {}; bool need_pax_name = (name.size() >= 100); for (auto c : name) { @@ -225,7 +226,6 @@ libcdoc::TarConsumer::open(const std::string& name, int64_t size) } if(need_pax_name || size > 07777777) { LOG_DBG("Writing Pax header: name {} size {}", name, size); - h.typeflag = 'x'; std::string paxData; if(need_pax_name) paxData += toPaxRecord("path", name); @@ -238,15 +238,14 @@ libcdoc::TarConsumer::open(const std::string& name, int64_t size) path = std::filesystem::path("./PaxHeaders.X") / path.filename(); } LOG_DBG("Pax path: {}", path.string()); - if (auto rv = writeHeader(h, path.string(), paxData.size()); rv != OK) + if (auto rv = writeHeader(path.string(), paxData.size(), 'x'); rv != OK) return rv; if (auto rv = _dst->write((const uint8_t *) paxData.data(), paxData.size()); rv != paxData.size()) return rv < OK ? rv : OUTPUT_ERROR; if (auto rv = writePadding(paxData.size()); rv != OK) return rv; } - h.typeflag = '0'; - return writeHeader(h, name, size); + return writeHeader(name, size, '0'); } libcdoc::TarSource::TarSource(DataSource *src, bool take_ownership) @@ -297,8 +296,8 @@ libcdoc::result_t libcdoc::TarSource::readPaxHeader(const Header& hdr, std::string& name, int64_t& size) { int64_t h_size = hdr.getSize(); - std::string paxData(h_size, 0); - result_t result = _src->read((uint8_t *) paxData.data(), paxData.size()); + std::string paxData(h_size, 0); + result_t result = _src->read((uint8_t *) paxData.data(), paxData.size()); if (result != h_size) { _error = INPUT_STREAM_ERROR; return _error; diff --git a/cdoc/Tar.h b/cdoc/Tar.h index a75be590..729b83b9 100644 --- a/cdoc/Tar.h +++ b/cdoc/Tar.h @@ -37,7 +37,7 @@ struct TarConsumer final : public MultiDataConsumer libcdoc::result_t open(const std::string& name, int64_t size) final; private: result_t writeHeader(const Header &h) noexcept; - result_t writeHeader(Header &h, const std::string& name, int64_t size) noexcept; + result_t writeHeader(const std::string& name, int64_t size, char typeflag = '0') noexcept; result_t writePadding(int64_t size) noexcept; DataConsumer *_dst; From 8bb1c866b8ea93d930c65f5620fc9cdd2f03ea75 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 26 Mar 2026 13:03:29 +0200 Subject: [PATCH 28/50] Moved parseLabel to Lock, take minimum of expiry time --- cdoc/CDoc2.h | 19 ++++++++++ cdoc/CDoc2Writer.cpp | 2 ++ cdoc/CDocCipher.cpp | 2 +- cdoc/Lock.cpp | 56 ++++++++++++++++++++++++++++++ cdoc/Lock.h | 7 ++++ cdoc/Recipient.cpp | 78 ++---------------------------------------- cdoc/Recipient.h | 7 ---- test/libcdoc_boost.cpp | 7 ++-- 8 files changed, 91 insertions(+), 87 deletions(-) diff --git a/cdoc/CDoc2.h b/cdoc/CDoc2.h index 61c38451..ce3813cc 100644 --- a/cdoc/CDoc2.h +++ b/cdoc/CDoc2.h @@ -43,6 +43,25 @@ std::string getSaltForExpand(const std::string& label); // Get salt bitstring for HKDF expand method std::string getSaltForExpand(const std::vector& key_material, const std::vector& rcpt_key); +/** + * @brief Prefix with what starts machine generated Lock's label. + */ +constexpr std::string_view LABELPREFIX{"data:"}; + +/** + * @brief String after label prefix indicating, the rest of the label is Base64 encoded. + */ +constexpr std::string_view LABELBASE64IND{";base64,"}; + +/** + * @brief EID type values for machine-readable label + */ +static constexpr std::string_view eid_strs[] = { + "Unknown", + "ID-card", + "Digi-ID", + "Digi-ID E-RESIDENT" +}; } // namespace CDoc2 } // namespace libcdoc diff --git a/cdoc/CDoc2Writer.cpp b/cdoc/CDoc2Writer.cpp index a79271b6..eceeeffe 100644 --- a/cdoc/CDoc2Writer.cpp +++ b/cdoc/CDoc2Writer.cpp @@ -119,6 +119,7 @@ createRSAServerCapsule(flatbuffers::FlatBufferBuilder& builder, const libcdoc::R rsaKeyServer.Union(), builder.CreateString(rcpt.server_id), builder.CreateString(transaction_id)); + if (rcpt.expiry_ts) expiry_time = std::min(rcpt.expiry_ts, expiry_time); return cdoc20::header::CreateRecipientRecord(builder, cdoc20::header::Capsule::recipients_KeyServerCapsule, capsule.Union(), @@ -153,6 +154,7 @@ createECCServerCapsule(flatbuffers::FlatBufferBuilder& builder, const libcdoc::R eccKeyServer.Union(), builder.CreateString(rcpt.server_id), builder.CreateString(transaction_id)); + if (rcpt.expiry_ts) expiry_time = std::min(rcpt.expiry_ts, expiry_time); return cdoc20::header::CreateRecipientRecord(builder, cdoc20::header::Capsule::recipients_KeyServerCapsule, capsule.Union(), diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index c94efe9f..b8575ddf 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -673,7 +673,7 @@ void CDocCipher::Locks(const char* file) const int lock_id = 1; for (const Lock& lock : rdr->getLocks()) { - map parsed_label(Recipient::parseLabel(lock.label)); + map parsed_label(Lock::parseLabel(lock.label)); if (parsed_label.empty()) { // Human-readable label cout << lock_id << ": " << lock.label << endl; diff --git a/cdoc/Lock.cpp b/cdoc/Lock.cpp index b5be546d..6d715ba9 100644 --- a/cdoc/Lock.cpp +++ b/cdoc/Lock.cpp @@ -18,6 +18,7 @@ #include "Lock.h" +#include "CDoc2.h" #include "Certificate.h" #include "Utils.h" @@ -52,5 +53,60 @@ Lock::setInt(Params key, int32_t val) params[key] = std::move(bytes); } +std::map +Lock::parseLabel(const std::string& label) +{ + std::map parsed_label; + // Check if provided label starts with the machine generated label prefix. + if (!label.starts_with(CDoc2::LABELPREFIX)) + { + return parsed_label; + } + + std::string label_wo_prefix(label.substr(CDoc2::LABELPREFIX.size())); + + // Label to be processed + std::string label_to_prcss; + + // We ignore mediatype part + + // Check, if the label is Base64 encoded + auto base64IndPos = label_wo_prefix.find(CDoc2::LABELBASE64IND); + if (base64IndPos == std::string::npos) + { + if (label_wo_prefix.starts_with(",")) { + label_to_prcss = label_wo_prefix.substr(1); + } else { + label_to_prcss = std::move(label_wo_prefix); + } + } + else + { + std::string base64_label(label_wo_prefix.substr(base64IndPos + CDoc2::LABELBASE64IND.size())); + std::vector decodedLabel(fromBase64(base64_label)); + label_to_prcss.assign(decodedLabel.cbegin(), decodedLabel.cend()); + } + + auto label_parts(split(label_to_prcss, '&')); + for (auto& part : label_parts) + { + auto label_data_parts(split(part, '=')); + if (label_data_parts.size() != 2) + { + // Invalid label data. We just ignore them. + LOG_ERROR("The label '{}' is invalid", label); + } + else + { + std::string key = urlDecode(label_data_parts[0]); + std::string value = urlDecode(label_data_parts[1]); + std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c){ return std::tolower(c); }); + parsed_label[key] = value; + } + } + + return parsed_label; +} + } // namespace libcdoc diff --git a/cdoc/Lock.h b/cdoc/Lock.h index 250eeb85..07225e8d 100644 --- a/cdoc/Lock.h +++ b/cdoc/Lock.h @@ -239,6 +239,13 @@ struct CDOC_EXPORT Lock */ void setInt(Params param, int32_t val); + /** + * @brief parse machine-readable CDoc2 label + * @param label the label + * @return a map of key-value pairs + */ + static std::map parseLabel(const std::string& label); + bool operator== (const Lock& other) const noexcept = default; private: diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 02928900..3459f41d 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -30,26 +30,6 @@ using namespace std; namespace libcdoc { -/** - * @brief Prefix with what starts machine generated Lock's label. - */ -constexpr string_view LABELPREFIX{"data:"}; - -/** - * @brief String after label prefix indicating, the rest of the label is Base64 encoded. - */ -constexpr string_view LABELBASE64IND{";base64,"}; - -/** - * @brief EID type values for machine-readable label - */ -static constexpr std::string_view eid_strs[] = { - "Unknown", - "ID-card", - "Digi-ID", - "Digi-ID E-RESIDENT" -}; - Recipient Recipient::makeSymmetric(std::string label, int32_t kdf_iter) { @@ -143,7 +123,7 @@ Recipient::isTheSameRecipient(const std::vector& public_key) const static void buildLabel(std::ostream& ofs, std::string_view type, std::initializer_list> components) { - ofs << LABELPREFIX; + ofs << CDoc2::LABELPREFIX; ofs << "v" << '=' << std::to_string(CDoc2::KEYLABELVERSION) << '&' << "type" << '=' << type; for (const auto& [key, value] : components) { @@ -155,7 +135,7 @@ buildLabel(std::ostream& ofs, std::string_view type, std::initializer_list Recipient::parseLabel(const string& label) -{ - map parsed_label; - // Check if provided label starts with the machine generated label prefix. - if (!label.starts_with(LABELPREFIX)) - { - return parsed_label; - } - - string label_wo_prefix(label.substr(LABELPREFIX.size())); - - // Label to be processed - string label_to_prcss; - - // We ignore mediatype part - - // Check, if the label is Base64 encoded - string::size_type base64IndPos = label_wo_prefix.find(LABELBASE64IND); - if (base64IndPos == string::npos) - { - if (label_wo_prefix.starts_with(",")) { - label_to_prcss = label_wo_prefix.substr(1); - } else { - label_to_prcss = std::move(label_wo_prefix); - } - } - else - { - string base64_label(label_wo_prefix.substr(base64IndPos + LABELBASE64IND.size())); - vector decodedLabel(fromBase64(base64_label)); - label_to_prcss.assign(decodedLabel.cbegin(), decodedLabel.cend()); - } - - vector label_parts(split(label_to_prcss, '&')); - for (vector::const_reference part : label_parts) - { - vector label_data_parts(split(part, '=')); - if (label_data_parts.size() != 2) - { - // Invalid label data. We just ignore them. - LOG_ERROR("The label '{}' is invalid", label); - } - else - { - std::string key = urlDecode(label_data_parts[0]); - std::string value = urlDecode(label_data_parts[1]); - std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c){ return std::tolower(c); }); - parsed_label[key] = value; - } - } - - return parsed_label; -} - } // namespace libcdoc diff --git a/cdoc/Recipient.h b/cdoc/Recipient.h index 0b04755b..f1aa2698 100644 --- a/cdoc/Recipient.h +++ b/cdoc/Recipient.h @@ -235,13 +235,6 @@ struct CDOC_EXPORT Recipient { */ std::string getLabel(const std::vector> &extra) const; - /** - * @brief parse machine-readable CDoc2 label - * @param label the label - * @return a map of key-value pairs - */ - static std::map parseLabel(const std::string& label); - bool operator== (const Recipient& other) const = default; protected: Recipient(Type _type) : type(_type) {}; diff --git a/test/libcdoc_boost.cpp b/test/libcdoc_boost.cpp index 1401dc39..88a221d3 100644 --- a/test/libcdoc_boost.cpp +++ b/test/libcdoc_boost.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -731,7 +732,7 @@ BOOST_AUTO_TEST_CASE(PlainLabelParsing) { const string label("data:v=1&type=ID-card&serial_number=PNOEE-38001085718&cn=J%C3%95EORG%2CJAAK-KRISTJAN%2C38001085718"); - auto result = libcdoc::Recipient::parseLabel(label); + auto result = libcdoc::Lock::parseLabel(label); for (const auto& [key, value] : ExpectedParsedLabel) { auto result_pair = result.find(key); @@ -747,7 +748,7 @@ BOOST_AUTO_TEST_CASE(Base64LabelParsing) { const string label("data:;base64,dj0xJnR5cGU9SUQtY2FyZCZzZXJpYWxfbnVtYmVyPVBOT0VFLTM4MDAxMDg1NzE4JmNuPUolQzMlOTVFT1JHJTJDSkFBSy1LUklTVEpBTiUyQzM4MDAxMDg1NzE4"); - auto result = libcdoc::Recipient::parseLabel(label); + auto result = libcdoc::Lock::parseLabel(label); for (const auto& [key, value] : ExpectedParsedLabel) { auto result_pair = result.find(key); @@ -763,7 +764,7 @@ BOOST_AUTO_TEST_CASE(Base64LabelParsingWithMediaType) { const string label("data:application/x-www-form-urlencoded;base64,dj0xJnR5cGU9SUQtY2FyZCZzZXJpYWxfbnVtYmVyPVBOT0VFLTM4MDAxMDg1NzE4JmNuPUolQzMlOTVFT1JHJTJDSkFBSy1LUklTVEpBTiUyQzM4MDAxMDg1NzE4"); - auto result = libcdoc::Recipient::parseLabel(label); + auto result = libcdoc::Lock::parseLabel(label); for (const auto& [key, value] : ExpectedParsedLabel) { auto result_pair = result.find(key); From a9bb1376f888c0277805aea1b9a8d4b0604b7bf7 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 26 Mar 2026 13:18:29 +0200 Subject: [PATCH 29/50] Fixed CDocTool.java --- examples/java/src/main/java/ee/ria/cdoc/CDocTool.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java index 13e932ee..e2a2f7c5 100644 --- a/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java +++ b/examples/java/src/main/java/ee/ria/cdoc/CDocTool.java @@ -369,7 +369,7 @@ static void locks(String path) { static void test() { System.err.println("Testing label generation"); String label = "data:v=1&type=ID-card&serial_number=PNOEE-38001085718&cn=J%C3%95EORG%2CJAAK-KRISTJAN%2C38001085718"; - java.util.Map map = Recipient.parseLabel(label); + java.util.Map map = ee.ria.cdoc.Lock.parseLabel(label); for (String key : map.keySet()) { System.err.format(" %s:%s\n", key, map.get(key)); } From 84a4a92fbf2e80a9e22009a8024da78623d6c36a Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 27 Mar 2026 10:11:49 +0200 Subject: [PATCH 30/50] Use property system for automatic label generation --- cdoc/CDoc.h | 37 ++++++++++++++++++ cdoc/CDoc2.h | 22 ++++++----- cdoc/CDoc2Reader.cpp | 44 +++++++++++----------- cdoc/CDoc2Writer.cpp | 38 +++++++++---------- cdoc/Lock.cpp | 8 ++-- cdoc/Recipient.cpp | 89 +++++++++++++++++++------------------------- cdoc/Recipient.h | 26 ++++++++----- cdoc/WinBackend.cpp | 2 +- 8 files changed, 150 insertions(+), 116 deletions(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 960f4727..a6500e2b 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -183,6 +183,43 @@ struct FileInfo { int64_t size; }; +/** + * @brief CDoc2-specific public values + * + * This is class (instead of namespace) to streamline Swig wrapping + */ +struct CDOC_EXPORT CDoc2 final { + + /** + * @brief Recipient types for machine-readable labels + * + */ + static constexpr std::string_view TYPE_PASSWORD = "pw"; + static constexpr std::string_view TYPE_SYMMETRIC = "secret"; + static constexpr std::string_view TYPE_PUBLIC_KEY = "pub_key"; + static constexpr std::string_view TYPE_CERTIFICATE = "cert"; + static constexpr std::string_view TYPE_UNKNOWN = "Unknown"; + static constexpr std::string_view TYPE_ID_CARD = "ID-card"; + static constexpr std::string_view TYPE_DIGI_ID = "Digi-ID"; + static constexpr std::string_view TYPE_DIGI_ID_E_RESIDENT = "Digi-ID E-RESIDENT"; + + /** + * @brief Recipient data for machine-readable labels + * + */ + static constexpr std::string_view LBL_VERSION = "v"; + static constexpr std::string_view LBL_TYPE = "type"; + static constexpr std::string_view LBL_FILE = "file"; + static constexpr std::string_view LBL_LABEL = "label"; + static constexpr std::string_view LBL_CN = "cn"; + static constexpr std::string_view LBL_SERIAL_NUMBER = "serial_number"; + static constexpr std::string_view LBL_LAST_NAME = "last_name"; + static constexpr std::string_view LBL_FIRST_NAME = "first_name"; + static constexpr std::string_view LBL_CERT_SHA1 = "cert_sha1"; + + CDoc2() = delete; +}; + }; // namespace libcdoc #endif // CDOC_H diff --git a/cdoc/CDoc2.h b/cdoc/CDoc2.h index ce3813cc..9f7b3902 100644 --- a/cdoc/CDoc2.h +++ b/cdoc/CDoc2.h @@ -19,10 +19,12 @@ #ifndef __CDOC2_H__ #define __CDOC2_H__ +#include "CDoc.h" + #include namespace libcdoc { -namespace CDoc2 { +namespace CDoc2Internal { static constexpr std::string_view LABEL = "CDOC\x02"; static constexpr std::string_view CEK = "CDOC20cek"; @@ -53,15 +55,15 @@ constexpr std::string_view LABELPREFIX{"data:"}; */ constexpr std::string_view LABELBASE64IND{";base64,"}; -/** - * @brief EID type values for machine-readable label - */ -static constexpr std::string_view eid_strs[] = { - "Unknown", - "ID-card", - "Digi-ID", - "Digi-ID E-RESIDENT" -}; + /** + * @brief EID type values for machine-readable label + */ + static constexpr std::string_view eid_strs[] = { + CDoc2::TYPE_UNKNOWN, + CDoc2::TYPE_ID_CARD, + CDoc2::TYPE_DIGI_ID, + CDoc2::TYPE_DIGI_ID_E_RESIDENT + }; } // namespace CDoc2 } // namespace libcdoc diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 478e51ab..daeb688d 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -47,19 +47,19 @@ using namespace libcdoc; // Get salt bitstring for HKDF expand method std::string -libcdoc::CDoc2::getSaltForExpand(const std::string& label) +libcdoc::CDoc2Internal::getSaltForExpand(const std::string& label) { std::ostringstream oss; - oss << libcdoc::CDoc2::KEK << cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) << label; + oss << libcdoc::CDoc2Internal::KEK << cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) << label; return oss.str(); } // Get salt bitstring for HKDF expand method std::string -libcdoc::CDoc2::getSaltForExpand(const std::vector& key_material, const std::vector& rcpt_key) +libcdoc::CDoc2Internal::getSaltForExpand(const std::vector& key_material, const std::vector& rcpt_key) { std::ostringstream oss; - oss << libcdoc::CDoc2::KEK + oss << libcdoc::CDoc2Internal::KEK << cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) << std::string_view((const char*)rcpt_key.data(), rcpt_key.size()) << std::string_view((const char*)key_material.data(), key_material.size()); @@ -149,7 +149,7 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) if (lock.type == Lock::Type::PASSWORD) { // Password LOG_DBG("password"); - std::string info_str = libcdoc::CDoc2::getSaltForExpand(lock.label); + std::string info_str = libcdoc::CDoc2Internal::getSaltForExpand(lock.label); LOG_DBG("info: {}", toHex(info_str)); std::vector kek_pm; 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) { @@ -163,7 +163,7 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) } else if (lock.type == Lock::Type::SYMMETRIC_KEY) { // Symmetric key LOG_DBG("symmetric"); - std::string info_str = libcdoc::CDoc2::getSaltForExpand(lock.label); + std::string info_str = libcdoc::CDoc2Internal::getSaltForExpand(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) { @@ -217,16 +217,16 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) } } else { std::vector kek_pm; - int result = crypto->deriveHMACExtract(kek_pm, key_material, toUint8Vector(libcdoc::CDoc2::KEKPREMASTER), lock_idx); + int result = crypto->deriveHMACExtract(kek_pm, key_material, toUint8Vector(libcdoc::CDoc2Internal::KEKPREMASTER), lock_idx); if (result < 0) { setLastError(crypto->getLastErrorStr(result)); 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)); + std::string info_str = libcdoc::CDoc2Internal::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); + kek = libcdoc::Crypto::expand(kek_pm, info_str, libcdoc::CDoc2Internal::KEY_LEN); } } else if (lock.type == Lock::Type::SHARE_SERVER) { /* SALT */ @@ -345,7 +345,7 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) LOG_ERROR("{}", last_error); return libcdoc::CRYPTO_ERROR; } - std::vector hhk = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2::HMAC); + std::vector hhk = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2Internal::HMAC); LOG_TRACE_KEY("xor: {}", lock.encrypted_fmk); LOG_TRACE_KEY("fmk: {}", fmk); @@ -409,11 +409,11 @@ CDoc2Reader::beginDecryption(const std::vector& fmk) } } priv->_at_nonce = false; - std::vector cek = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2::CEK); + std::vector cek = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2Internal::CEK); LOG_TRACE_KEY("cek: {}", cek); - priv->dec = std::make_unique(*priv->_src, EVP_chacha20_poly1305(), cek, libcdoc::CDoc2::NONCE_LEN); - std::vector aad = toUint8Vector(libcdoc::CDoc2::PAYLOAD); + priv->dec = std::make_unique(*priv->_src, EVP_chacha20_poly1305(), cek, libcdoc::CDoc2Internal::NONCE_LEN); + std::vector aad = toUint8Vector(libcdoc::CDoc2Internal::PAYLOAD); aad.insert(aad.end(), priv->header_data.cbegin(), priv->header_data.cend()); aad.insert(aad.end(), priv->headerHMAC.cbegin(), priv->headerHMAC.cend()); if(auto rv = priv->dec->updateAAD(aad); rv != OK) { @@ -621,16 +621,16 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) setLastError(t_("Invalid CDoc 2.0 header")); - uint8_t in[libcdoc::CDoc2::LABEL.size()]; - if (priv->_src->read(in, libcdoc::CDoc2::LABEL.size()) != libcdoc::CDoc2::LABEL.size()) { + uint8_t in[libcdoc::CDoc2Internal::LABEL.size()]; + if (priv->_src->read(in, libcdoc::CDoc2Internal::LABEL.size()) != libcdoc::CDoc2Internal::LABEL.size()) { LOG_ERROR("{}", last_error); return; } - if (memcmp(libcdoc::CDoc2::LABEL.data(), in, libcdoc::CDoc2::LABEL.size())) { + if (memcmp(libcdoc::CDoc2Internal::LABEL.data(), in, libcdoc::CDoc2Internal::LABEL.size())) { LOG_ERROR("{}", last_error); return; } - //if (libcdoc::CDoc2::LABEL.compare(0, libcdoc::CDoc2::LABEL.size(), (const char *) in)) return; + //if (libcdoc::CDoc2Internal::LABEL.compare(0, libcdoc::CDoc2Internal::LABEL.size(), (const char *) in)) return; // Read 32-bit header length in big endian order uint8_t c[4]; @@ -648,13 +648,13 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) LOG_ERROR("{}", last_error); return; } - priv->headerHMAC.resize(libcdoc::CDoc2::KEY_LEN); - if (priv->_src->read(priv->headerHMAC.data(), libcdoc::CDoc2::KEY_LEN) != libcdoc::CDoc2::KEY_LEN) { + priv->headerHMAC.resize(libcdoc::CDoc2Internal::KEY_LEN); + if (priv->_src->read(priv->headerHMAC.data(), libcdoc::CDoc2Internal::KEY_LEN) != libcdoc::CDoc2Internal::KEY_LEN) { LOG_ERROR("{}", last_error); return; } - priv->_nonce_pos = libcdoc::CDoc2::LABEL.size() + 4 + header_len + libcdoc::CDoc2::KEY_LEN; + priv->_nonce_pos = libcdoc::CDoc2Internal::LABEL.size() + 4 + header_len + libcdoc::CDoc2Internal::KEY_LEN; priv->_at_nonce = true; flatbuffers::Verifier verifier(priv->header_data.data(), priv->header_data.size()); @@ -687,12 +687,12 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) bool CDoc2Reader::isCDoc2File(libcdoc::DataSource *src) { - std::array in {}; + std::array in {}; if (src->read(in.data(), in.size()) != in.size()) { LOG_DBG("CDoc2Reader::isCDoc2File: Cannot read tag"); return false; } - if (libcdoc::CDoc2::LABEL.compare(0, in.size(), (char *) in.data(), in.size())) { + if (libcdoc::CDoc2Internal::LABEL.compare(0, in.size(), (char *) in.data(), in.size())) { LOG_DBG("CDoc2Reader::isCDoc2File: Invalid tag: {}", toHex(in)); return false; } diff --git a/cdoc/CDoc2Writer.cpp b/cdoc/CDoc2Writer.cpp index eceeeffe..349aa0f6 100644 --- a/cdoc/CDoc2Writer.cpp +++ b/cdoc/CDoc2Writer.cpp @@ -49,9 +49,9 @@ CDoc2Writer::writeHeader(const std::vector &recipients) return libcdoc::WRONG_ARGUMENTS; } std::vector rnd; - if(auto rv = crypto->random(rnd, libcdoc::CDoc2::KEY_LEN); rv < 0) + if(auto rv = crypto->random(rnd, libcdoc::CDoc2Internal::KEY_LEN); rv < 0) return rv; - std::vector fmk = libcdoc::Crypto::extract(rnd, {libcdoc::CDoc2::SALT.cbegin(), libcdoc::CDoc2::SALT.cend()}); + std::vector fmk = libcdoc::Crypto::extract(rnd, {libcdoc::CDoc2Internal::SALT.cbegin(), libcdoc::CDoc2Internal::SALT.cend()}); std::fill(rnd.begin(), rnd.end(), 0); LOG_TRACE_KEY("fmk: {}", fmk); @@ -62,8 +62,8 @@ CDoc2Writer::writeHeader(const std::vector &recipients) return rv; } - auto hhk = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2::HMAC); - auto cek = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2::CEK); + auto hhk = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2Internal::HMAC); + auto cek = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2Internal::CEK); std::fill(fmk.begin(), fmk.end(), 0); LOG_TRACE_KEY("cek: {}", cek); LOG_TRACE_KEY("hhk: {}", hhk); @@ -75,16 +75,16 @@ CDoc2Writer::writeHeader(const std::vector &recipients) // FIXME: not big/little endian friendly uint8_t header_len[] {uint8_t(hs >> 24), uint8_t((hs >> 16) & 0xff), uint8_t((hs >> 8) & 0xff), uint8_t(hs & 0xff)}; - dst->write((const uint8_t *) libcdoc::CDoc2::LABEL.data(), libcdoc::CDoc2::LABEL.size()); + dst->write((const uint8_t *) libcdoc::CDoc2Internal::LABEL.data(), libcdoc::CDoc2Internal::LABEL.size()); dst->write((const uint8_t *) &header_len, 4); dst->write(header.data(), header.size()); dst->write(headerHMAC.data(), headerHMAC.size()); std::vector nonce; - crypto->random(nonce, libcdoc::CDoc2::NONCE_LEN); + crypto->random(nonce, libcdoc::CDoc2Internal::NONCE_LEN); LOG_TRACE_KEY("nonce: {}", nonce); auto cipher = std::make_unique(*dst, EVP_chacha20_poly1305(), Crypto::Key(std::move(cek), nonce)); - std::vector aad(libcdoc::CDoc2::PAYLOAD.cbegin(), libcdoc::CDoc2::PAYLOAD.cend()); + std::vector aad(libcdoc::CDoc2Internal::PAYLOAD.cbegin(), libcdoc::CDoc2Internal::PAYLOAD.cend()); aad.insert(aad.end(), header.cbegin(), header.cend()); aad.insert(aad.end(), headerHMAC.cbegin(), headerHMAC.cend()); if(auto rv = cipher->writeAAD(aad); rv < 0) @@ -198,7 +198,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector> fb_rcpts; - std::vector xor_key(libcdoc::CDoc2::KEY_LEN); + std::vector xor_key(libcdoc::CDoc2Internal::KEY_LEN); for (unsigned int rcpt_idx = 0; rcpt_idx < recipients.size(); rcpt_idx++) { const libcdoc::Recipient& rcpt = recipients.at(rcpt_idx); if (rcpt.isPKI()) { @@ -223,7 +223,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vectorrandom(kek, libcdoc::CDoc2::KEY_LEN); + crypto->random(kek, libcdoc::CDoc2Internal::KEY_LEN); if (libcdoc::Crypto::xor_data(xor_key, fmk, kek) != libcdoc::OK) { setLastError("Internal error"); LOG_ERROR("{}", last_error); @@ -267,8 +267,8 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector sharedSecret = libcdoc::Crypto::deriveSharedSecret(ephKey.get(), publicKey.get()); key_material = libcdoc::Crypto::toPublicKeyDer(ephKey.get()); - std::vector kekPm = libcdoc::Crypto::extract(sharedSecret, std::vector(libcdoc::CDoc2::KEKPREMASTER.cbegin(), libcdoc::CDoc2::KEKPREMASTER.cend())); - std::string info_str = libcdoc::CDoc2::getSaltForExpand(key_material, rcpt.rcpt_key); + std::vector kekPm = libcdoc::Crypto::extract(sharedSecret, std::vector(libcdoc::CDoc2Internal::KEKPREMASTER.cbegin(), libcdoc::CDoc2Internal::KEKPREMASTER.cend())); + std::string info_str = libcdoc::CDoc2Internal::getSaltForExpand(key_material, rcpt.rcpt_key); kek = libcdoc::Crypto::expand(kekPm, info_str, fmk.size()); if (libcdoc::Crypto::xor_data(xor_key, fmk, kek) != libcdoc::OK) { @@ -302,16 +302,16 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector kek_pm(libcdoc::CDoc2::KEY_LEN); + std::string info_str = libcdoc::CDoc2Internal::getSaltForExpand(rcpt.getLabel({})); + std::vector kek_pm(libcdoc::CDoc2Internal::KEY_LEN); std::vector salt; - int64_t result = crypto->random(salt, libcdoc::CDoc2::KEY_LEN); + int64_t result = crypto->random(salt, libcdoc::CDoc2Internal::KEY_LEN); if (result < 0) { setLastError(crypto->getLastErrorStr(result)); return result; } std::vector pw_salt; - result = crypto->random(pw_salt, libcdoc::CDoc2::KEY_LEN); + result = crypto->random(pw_salt, libcdoc::CDoc2Internal::KEY_LEN); if (result < 0) { setLastError(crypto->getLastErrorStr(result)); return result; @@ -321,7 +321,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vectorgetLastErrorStr(result)); return result; } - std::vector kek = libcdoc::Crypto::expand(kek_pm, info_str, libcdoc::CDoc2::KEY_LEN); + std::vector kek = libcdoc::Crypto::expand(kek_pm, info_str, libcdoc::CDoc2Internal::KEY_LEN); LOG_DBG("Label: {}", rcpt.label); LOG_DBG("KDF iter: {}", rcpt.kdf_iter); @@ -368,11 +368,11 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector key_material_salt; - crypto->random(key_material_salt, libcdoc::CDoc2::KEY_LEN); + crypto->random(key_material_salt, libcdoc::CDoc2Internal::KEY_LEN); //KeyMaterial_i = CSRNG(256) std::vector key_material; - crypto->random(key_material, libcdoc::CDoc2::KEY_LEN); + crypto->random(key_material, libcdoc::CDoc2Internal::KEY_LEN); //KEK_i_pm = HKDF_Extract(KeyMaterialSalt_i, KeyMaterial_i) std::vector kek_pm = libcdoc::Crypto::extract(key_material_salt, key_material); @@ -394,7 +394,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector> kek_shares(N_SHARES); for (int i = 1; i < N_SHARES; i++) { // KEK_i_share_j = CSRNG(256) - crypto->random(kek_shares[i], libcdoc::CDoc2::KEY_LEN); + crypto->random(kek_shares[i], libcdoc::CDoc2Internal::KEY_LEN); } // KEK_i_share_1 = XOR(KEK_i, KEK_i_share_2, KEK_i_share_3,..., KEK_i_share_n) kek_shares[0] = std::move(kek); diff --git a/cdoc/Lock.cpp b/cdoc/Lock.cpp index 6d715ba9..83bcda24 100644 --- a/cdoc/Lock.cpp +++ b/cdoc/Lock.cpp @@ -58,12 +58,12 @@ Lock::parseLabel(const std::string& label) { std::map parsed_label; // Check if provided label starts with the machine generated label prefix. - if (!label.starts_with(CDoc2::LABELPREFIX)) + if (!label.starts_with(CDoc2Internal::LABELPREFIX)) { return parsed_label; } - std::string label_wo_prefix(label.substr(CDoc2::LABELPREFIX.size())); + std::string label_wo_prefix(label.substr(CDoc2Internal::LABELPREFIX.size())); // Label to be processed std::string label_to_prcss; @@ -71,7 +71,7 @@ Lock::parseLabel(const std::string& label) // We ignore mediatype part // Check, if the label is Base64 encoded - auto base64IndPos = label_wo_prefix.find(CDoc2::LABELBASE64IND); + auto base64IndPos = label_wo_prefix.find(CDoc2Internal::LABELBASE64IND); if (base64IndPos == std::string::npos) { if (label_wo_prefix.starts_with(",")) { @@ -82,7 +82,7 @@ Lock::parseLabel(const std::string& label) } else { - std::string base64_label(label_wo_prefix.substr(base64IndPos + CDoc2::LABELBASE64IND.size())); + std::string base64_label(label_wo_prefix.substr(base64IndPos + CDoc2Internal::LABELBASE64IND.size())); std::vector decodedLabel(fromBase64(base64_label)); label_to_prcss.assign(decodedLabel.cbegin(), decodedLabel.cend()); } diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 3459f41d..91fd3a43 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -121,60 +121,44 @@ Recipient::isTheSameRecipient(const std::vector& public_key) const } static void -buildLabel(std::ostream& ofs, std::string_view type, std::initializer_list> components) +buildLabel(std::ostream& ofs, std::string_view type, const std::map lbl_parts, std::initializer_list> extra) { - ofs << CDoc2::LABELPREFIX; - ofs << "v" << '=' << std::to_string(CDoc2::KEYLABELVERSION) << '&' - << "type" << '=' << type; - for (const auto& [key, value] : components) { + auto parts = lbl_parts; + if (parts.contains("v")) + parts.erase("v"); + if (parts.contains("type")) + parts.erase("type"); + for (const auto& [key, value] : extra) { + if (!value.empty()) + parts[key] = value; + } + ofs << CDoc2Internal::LABELPREFIX; + ofs << CDoc2::LBL_VERSION << '=' << std::to_string(CDoc2Internal::KEYLABELVERSION) << '&' + << CDoc2::LBL_TYPE << '=' << type; + for (const auto& [key, value] : parts) { if (!value.empty()) ofs << '&' << urlEncode(key) << '=' << urlEncode(value); } } static void -BuildLabelEID(std::ostream& ofs, Certificate::EIDType type, const Certificate& x509) -{ - buildLabel(ofs, CDoc2::eid_strs[type], { - {"cn", x509.getCommonName()}, - {"serial_number", x509.getSerialNumber()}, - {"last_name", x509.getSurname()}, - {"first_name", x509.getGivenName()}, - }); -} - -static void -BuildLabelCertificate(std::ostream &ofs, const std::string& file, const Certificate& x509) +BuildLabelEID(std::ostream& ofs, Certificate::EIDType type, const Certificate& x509, const std::map& lbl_parts) { - buildLabel(ofs, "cert", { - {"file", file}, - {"cn", x509.getCommonName()}, - {"cert_sha1", toHex(x509.getDigest())} + + buildLabel(ofs, CDoc2Internal::eid_strs[type], lbl_parts, { + {CDoc2::LBL_CN, x509.getCommonName()}, + {CDoc2::LBL_SERIAL_NUMBER, x509.getSerialNumber()}, + {CDoc2::LBL_LAST_NAME, x509.getSurname()}, + {CDoc2::LBL_FIRST_NAME, x509.getGivenName()}, }); } static void -BuildLabelPublicKey(std::ostream &ofs, const std::string& file) +BuildLabelCertificate(std::ostream &ofs, const Certificate& x509, const std::map& lbl_parts) { - buildLabel(ofs, "pub_key", { - {"file", file} - }); -} - -static void -BuildLabelSymmetricKey(std::ostream &ofs, const std::string& label, const std::string& file) -{ - buildLabel(ofs, "secret", { - {"label", label}, - {"file", file} - }); -} - -static void -BuildLabelPassword(std::ostream &ofs, const std::string& label) -{ - buildLabel(ofs, "pw", { - {"label", label} + buildLabel(ofs, CDoc2::TYPE_CERTIFICATE, lbl_parts, { + {CDoc2::LBL_CN, x509.getCommonName()}, + {CDoc2::LBL_CERT_SHA1, toHex(x509.getDigest())} }); } @@ -183,6 +167,15 @@ Recipient::getLabel(const std::vector parts; + for (const auto& [key, value] : lbl_parts) { + if (!value.empty()) + parts[key] = value; + } + for (const auto& [key, value] : extra) { + if (!value.empty()) + parts[key] = value; + } std::ostringstream ofs; switch(type) { case NONE: @@ -190,30 +183,26 @@ Recipient::getLabel(const std::vector 0) { - BuildLabelPassword(ofs, key_name); + buildLabel(ofs, CDoc2::TYPE_PASSWORD, parts, {}); } else { - BuildLabelSymmetricKey(ofs, key_name, file_name); + buildLabel(ofs, CDoc2::TYPE_SYMMETRIC, parts, {}); } break; case PUBLIC_KEY: if (!cert.empty()) { Certificate x509(cert); if (auto eid = x509.getEIDType(); eid != Certificate::Unknown) { - BuildLabelEID(ofs, eid, x509); + BuildLabelEID(ofs, eid, x509, parts); } else { - BuildLabelCertificate(ofs, file_name, x509); + BuildLabelCertificate(ofs, x509, parts); } } else { - BuildLabelPublicKey(ofs, file_name); + buildLabel(ofs, CDoc2::TYPE_PUBLIC_KEY, parts, {}); } break; case KEYSHARE: break; } - for (const auto& [key, value] : extra) { - if (!value.empty()) - ofs << '&' << urlEncode(key) << '=' << urlEncode(value); - } LOG_DBG("Generated label: {}", ofs.str()); return ofs.str(); } diff --git a/cdoc/Recipient.h b/cdoc/Recipient.h index f1aa2698..d51da86a 100644 --- a/cdoc/Recipient.h +++ b/cdoc/Recipient.h @@ -109,16 +109,6 @@ struct CDOC_EXPORT Recipient { * */ uint64_t expiry_ts = 0; - /** - * @brief key/certificate filename for machine-readable label - * - */ - std::string file_name; - /** - * @brief public key/password name for machine-readable label - * - */ - std::string key_name; /** * @brief test whether the Recipient structure is initialized @@ -235,9 +225,25 @@ struct CDOC_EXPORT Recipient { */ std::string getLabel(const std::vector> &extra) const; + /** + * @brief Set a property for automatic label generation + * + * @param key the property name + * @param value the property value + */ + void setLabelValue(std::string_view key, std::string_view value) { + if (!value.empty()) { + lbl_parts[std::string(key)] = value; + } else { + lbl_parts.erase(std::string(key)); + } + } + bool operator== (const Recipient& other) const = default; protected: Recipient(Type _type) : type(_type) {}; +private: + std::map lbl_parts; }; } // namespace libcdoc diff --git a/cdoc/WinBackend.cpp b/cdoc/WinBackend.cpp index fbc8b55a..588d01a4 100644 --- a/cdoc/WinBackend.cpp +++ b/cdoc/WinBackend.cpp @@ -244,7 +244,7 @@ libcdoc::WinBackend::deriveHMACExtract(std::vector& dst, const std::vec dst.resize(int(size)); err = NCryptDeriveKey(sharedSecret, BCRYPT_KDF_HMAC, ¶ms, PBYTE(dst.data()), size, &size, 0); if (err == ERROR_SUCCESS) { - dst.resize(CDoc2::KEY_LEN); + dst.resize(CDoc2Internal::KEY_LEN); result = OK; } } From 4fb60a3756c0a28ca0cd199e5465d791900c1f1a Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 27 Mar 2026 11:10:35 +0200 Subject: [PATCH 31/50] remove final from CDoc2 --- cdoc/CDoc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index a6500e2b..2497eabd 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -188,7 +188,7 @@ struct FileInfo { * * This is class (instead of namespace) to streamline Swig wrapping */ -struct CDOC_EXPORT CDoc2 final { +struct CDOC_EXPORT CDoc2 { /** * @brief Recipient types for machine-readable labels From 7f94de49bf92fc77b4f24ae1637a16ef41128b6c Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 27 Mar 2026 12:01:16 +0200 Subject: [PATCH 32/50] Exclude CDoc2 from swig for the time being --- libcdoc.i | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libcdoc.i b/libcdoc.i index b25531ac..b34b421a 100644 --- a/libcdoc.i +++ b/libcdoc.i @@ -51,6 +51,9 @@ %ignore libcdoc::FileListConsumer; %ignore libcdoc::FileListSource; +// Ignore until there is straightfoward string_view translation +%ignore libcdoc::CDoc2; + %ignore libcdoc::CDocWriter::createWriter(int version, DataConsumer *dst, bool take_ownership, Configuration *conf, CryptoBackend *crypto, NetworkBackend *network); %ignore libcdoc::CDocWriter::createWriter(int version, std::ostream& ofs, Configuration *conf, CryptoBackend *crypto, NetworkBackend *network); %ignore libcdoc::CDocWriter::encrypt(MultiDataSource& src, const std::vector& recipients); From 3493f793a82758850ae82ed89037a6e52c6e96d1 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 27 Mar 2026 12:15:13 +0200 Subject: [PATCH 33/50] Remove commented-out code --- cdoc/CDoc.h | 2 +- cdoc/CDoc2Reader.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 2497eabd..a6500e2b 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -188,7 +188,7 @@ struct FileInfo { * * This is class (instead of namespace) to streamline Swig wrapping */ -struct CDOC_EXPORT CDoc2 { +struct CDOC_EXPORT CDoc2 final { /** * @brief Recipient types for machine-readable labels diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index daeb688d..3f828ba4 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -630,7 +630,6 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) LOG_ERROR("{}", last_error); return; } - //if (libcdoc::CDoc2Internal::LABEL.compare(0, libcdoc::CDoc2Internal::LABEL.size(), (const char *) in)) return; // Read 32-bit header length in big endian order uint8_t c[4]; From cb799439ede321673f086fe3e4f786ae779a0fb8 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 27 Mar 2026 12:29:05 +0200 Subject: [PATCH 34/50] Remove 'final' once more --- cdoc/CDoc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index a6500e2b..2497eabd 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -188,7 +188,7 @@ struct FileInfo { * * This is class (instead of namespace) to streamline Swig wrapping */ -struct CDOC_EXPORT CDoc2 final { +struct CDOC_EXPORT CDoc2 { /** * @brief Recipient types for machine-readable labels From 90fd645de49aa504cd5ded8b73cf1b8823ee7887 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Fri, 27 Mar 2026 12:50:33 +0200 Subject: [PATCH 35/50] Made CDoc2 back to namespace --- cdoc/CDoc.h | 9 +-------- cdoc/CDoc2.h | 20 ++++++++++---------- cdoc/CDoc2Reader.cpp | 42 +++++++++++++++++++++--------------------- cdoc/CDoc2Writer.cpp | 38 +++++++++++++++++++------------------- cdoc/Lock.cpp | 8 ++++---- cdoc/Recipient.cpp | 6 +++--- cdoc/WinBackend.cpp | 2 +- 7 files changed, 59 insertions(+), 66 deletions(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 2497eabd..3da767a6 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -183,12 +183,7 @@ struct FileInfo { int64_t size; }; -/** - * @brief CDoc2-specific public values - * - * This is class (instead of namespace) to streamline Swig wrapping - */ -struct CDOC_EXPORT CDoc2 { +namespace CDoc2 { /** * @brief Recipient types for machine-readable labels @@ -216,8 +211,6 @@ struct CDOC_EXPORT CDoc2 { static constexpr std::string_view LBL_LAST_NAME = "last_name"; static constexpr std::string_view LBL_FIRST_NAME = "first_name"; static constexpr std::string_view LBL_CERT_SHA1 = "cert_sha1"; - - CDoc2() = delete; }; }; // namespace libcdoc diff --git a/cdoc/CDoc2.h b/cdoc/CDoc2.h index 9f7b3902..1be34d68 100644 --- a/cdoc/CDoc2.h +++ b/cdoc/CDoc2.h @@ -24,7 +24,7 @@ #include namespace libcdoc { -namespace CDoc2Internal { +namespace CDoc2 { static constexpr std::string_view LABEL = "CDOC\x02"; static constexpr std::string_view CEK = "CDOC20cek"; @@ -55,15 +55,15 @@ constexpr std::string_view LABELPREFIX{"data:"}; */ constexpr std::string_view LABELBASE64IND{";base64,"}; - /** - * @brief EID type values for machine-readable label - */ - static constexpr std::string_view eid_strs[] = { - CDoc2::TYPE_UNKNOWN, - CDoc2::TYPE_ID_CARD, - CDoc2::TYPE_DIGI_ID, - CDoc2::TYPE_DIGI_ID_E_RESIDENT - }; +/** + * @brief EID type values for machine-readable label + */ +static constexpr std::string_view eid_strs[] = { + CDoc2::TYPE_UNKNOWN, + CDoc2::TYPE_ID_CARD, + CDoc2::TYPE_DIGI_ID, + CDoc2::TYPE_DIGI_ID_E_RESIDENT +}; } // namespace CDoc2 } // namespace libcdoc diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 3f828ba4..4e9b1199 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -47,19 +47,19 @@ using namespace libcdoc; // Get salt bitstring for HKDF expand method std::string -libcdoc::CDoc2Internal::getSaltForExpand(const std::string& label) +libcdoc::CDoc2::getSaltForExpand(const std::string& label) { std::ostringstream oss; - oss << libcdoc::CDoc2Internal::KEK << cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) << label; + oss << libcdoc::CDoc2::KEK << cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) << label; return oss.str(); } // Get salt bitstring for HKDF expand method std::string -libcdoc::CDoc2Internal::getSaltForExpand(const std::vector& key_material, const std::vector& rcpt_key) +libcdoc::CDoc2::getSaltForExpand(const std::vector& key_material, const std::vector& rcpt_key) { std::ostringstream oss; - oss << libcdoc::CDoc2Internal::KEK + oss << libcdoc::CDoc2::KEK << cdoc20::header::EnumNameFMKEncryptionMethod(cdoc20::header::FMKEncryptionMethod::XOR) << std::string_view((const char*)rcpt_key.data(), rcpt_key.size()) << std::string_view((const char*)key_material.data(), key_material.size()); @@ -149,7 +149,7 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) if (lock.type == Lock::Type::PASSWORD) { // Password LOG_DBG("password"); - std::string info_str = libcdoc::CDoc2Internal::getSaltForExpand(lock.label); + std::string info_str = libcdoc::CDoc2::getSaltForExpand(lock.label); LOG_DBG("info: {}", toHex(info_str)); std::vector kek_pm; 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) { @@ -163,7 +163,7 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) } else if (lock.type == Lock::Type::SYMMETRIC_KEY) { // Symmetric key LOG_DBG("symmetric"); - std::string info_str = libcdoc::CDoc2Internal::getSaltForExpand(lock.label); + std::string info_str = libcdoc::CDoc2::getSaltForExpand(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) { @@ -217,16 +217,16 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) } } else { std::vector kek_pm; - int result = crypto->deriveHMACExtract(kek_pm, key_material, toUint8Vector(libcdoc::CDoc2Internal::KEKPREMASTER), lock_idx); + int result = crypto->deriveHMACExtract(kek_pm, key_material, toUint8Vector(libcdoc::CDoc2::KEKPREMASTER), lock_idx); if (result < 0) { setLastError(crypto->getLastErrorStr(result)); LOG_ERROR("{}", last_error); return result; } LOG_TRACE_KEY("Key kekPm: {}", kek_pm); - std::string info_str = libcdoc::CDoc2Internal::getSaltForExpand(key_material, lock.getBytes(Lock::Params::RCPT_KEY)); + 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::CDoc2Internal::KEY_LEN); + kek = libcdoc::Crypto::expand(kek_pm, info_str, libcdoc::CDoc2::KEY_LEN); } } else if (lock.type == Lock::Type::SHARE_SERVER) { /* SALT */ @@ -345,7 +345,7 @@ CDoc2Reader::getFMK(std::vector& fmk, unsigned int lock_idx) LOG_ERROR("{}", last_error); return libcdoc::CRYPTO_ERROR; } - std::vector hhk = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2Internal::HMAC); + std::vector hhk = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2::HMAC); LOG_TRACE_KEY("xor: {}", lock.encrypted_fmk); LOG_TRACE_KEY("fmk: {}", fmk); @@ -409,11 +409,11 @@ CDoc2Reader::beginDecryption(const std::vector& fmk) } } priv->_at_nonce = false; - std::vector cek = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2Internal::CEK); + std::vector cek = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2::CEK); LOG_TRACE_KEY("cek: {}", cek); - priv->dec = std::make_unique(*priv->_src, EVP_chacha20_poly1305(), cek, libcdoc::CDoc2Internal::NONCE_LEN); - std::vector aad = toUint8Vector(libcdoc::CDoc2Internal::PAYLOAD); + priv->dec = std::make_unique(*priv->_src, EVP_chacha20_poly1305(), cek, libcdoc::CDoc2::NONCE_LEN); + std::vector aad = toUint8Vector(libcdoc::CDoc2::PAYLOAD); aad.insert(aad.end(), priv->header_data.cbegin(), priv->header_data.cend()); aad.insert(aad.end(), priv->headerHMAC.cbegin(), priv->headerHMAC.cend()); if(auto rv = priv->dec->updateAAD(aad); rv != OK) { @@ -621,12 +621,12 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) setLastError(t_("Invalid CDoc 2.0 header")); - uint8_t in[libcdoc::CDoc2Internal::LABEL.size()]; - if (priv->_src->read(in, libcdoc::CDoc2Internal::LABEL.size()) != libcdoc::CDoc2Internal::LABEL.size()) { + uint8_t in[libcdoc::CDoc2::LABEL.size()]; + if (priv->_src->read(in, libcdoc::CDoc2::LABEL.size()) != libcdoc::CDoc2::LABEL.size()) { LOG_ERROR("{}", last_error); return; } - if (memcmp(libcdoc::CDoc2Internal::LABEL.data(), in, libcdoc::CDoc2Internal::LABEL.size())) { + if (memcmp(libcdoc::CDoc2::LABEL.data(), in, libcdoc::CDoc2::LABEL.size())) { LOG_ERROR("{}", last_error); return; } @@ -647,13 +647,13 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) LOG_ERROR("{}", last_error); return; } - priv->headerHMAC.resize(libcdoc::CDoc2Internal::KEY_LEN); - if (priv->_src->read(priv->headerHMAC.data(), libcdoc::CDoc2Internal::KEY_LEN) != libcdoc::CDoc2Internal::KEY_LEN) { + priv->headerHMAC.resize(libcdoc::CDoc2::KEY_LEN); + if (priv->_src->read(priv->headerHMAC.data(), libcdoc::CDoc2::KEY_LEN) != libcdoc::CDoc2::KEY_LEN) { LOG_ERROR("{}", last_error); return; } - priv->_nonce_pos = libcdoc::CDoc2Internal::LABEL.size() + 4 + header_len + libcdoc::CDoc2Internal::KEY_LEN; + priv->_nonce_pos = libcdoc::CDoc2::LABEL.size() + 4 + header_len + libcdoc::CDoc2::KEY_LEN; priv->_at_nonce = true; flatbuffers::Verifier verifier(priv->header_data.data(), priv->header_data.size()); @@ -686,12 +686,12 @@ CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) bool CDoc2Reader::isCDoc2File(libcdoc::DataSource *src) { - std::array in {}; + std::array in {}; if (src->read(in.data(), in.size()) != in.size()) { LOG_DBG("CDoc2Reader::isCDoc2File: Cannot read tag"); return false; } - if (libcdoc::CDoc2Internal::LABEL.compare(0, in.size(), (char *) in.data(), in.size())) { + if (libcdoc::CDoc2::LABEL.compare(0, in.size(), (char *) in.data(), in.size())) { LOG_DBG("CDoc2Reader::isCDoc2File: Invalid tag: {}", toHex(in)); return false; } diff --git a/cdoc/CDoc2Writer.cpp b/cdoc/CDoc2Writer.cpp index 349aa0f6..eceeeffe 100644 --- a/cdoc/CDoc2Writer.cpp +++ b/cdoc/CDoc2Writer.cpp @@ -49,9 +49,9 @@ CDoc2Writer::writeHeader(const std::vector &recipients) return libcdoc::WRONG_ARGUMENTS; } std::vector rnd; - if(auto rv = crypto->random(rnd, libcdoc::CDoc2Internal::KEY_LEN); rv < 0) + if(auto rv = crypto->random(rnd, libcdoc::CDoc2::KEY_LEN); rv < 0) return rv; - std::vector fmk = libcdoc::Crypto::extract(rnd, {libcdoc::CDoc2Internal::SALT.cbegin(), libcdoc::CDoc2Internal::SALT.cend()}); + std::vector fmk = libcdoc::Crypto::extract(rnd, {libcdoc::CDoc2::SALT.cbegin(), libcdoc::CDoc2::SALT.cend()}); std::fill(rnd.begin(), rnd.end(), 0); LOG_TRACE_KEY("fmk: {}", fmk); @@ -62,8 +62,8 @@ CDoc2Writer::writeHeader(const std::vector &recipients) return rv; } - auto hhk = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2Internal::HMAC); - auto cek = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2Internal::CEK); + auto hhk = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2::HMAC); + auto cek = libcdoc::Crypto::expand(fmk, libcdoc::CDoc2::CEK); std::fill(fmk.begin(), fmk.end(), 0); LOG_TRACE_KEY("cek: {}", cek); LOG_TRACE_KEY("hhk: {}", hhk); @@ -75,16 +75,16 @@ CDoc2Writer::writeHeader(const std::vector &recipients) // FIXME: not big/little endian friendly uint8_t header_len[] {uint8_t(hs >> 24), uint8_t((hs >> 16) & 0xff), uint8_t((hs >> 8) & 0xff), uint8_t(hs & 0xff)}; - dst->write((const uint8_t *) libcdoc::CDoc2Internal::LABEL.data(), libcdoc::CDoc2Internal::LABEL.size()); + dst->write((const uint8_t *) libcdoc::CDoc2::LABEL.data(), libcdoc::CDoc2::LABEL.size()); dst->write((const uint8_t *) &header_len, 4); dst->write(header.data(), header.size()); dst->write(headerHMAC.data(), headerHMAC.size()); std::vector nonce; - crypto->random(nonce, libcdoc::CDoc2Internal::NONCE_LEN); + crypto->random(nonce, libcdoc::CDoc2::NONCE_LEN); LOG_TRACE_KEY("nonce: {}", nonce); auto cipher = std::make_unique(*dst, EVP_chacha20_poly1305(), Crypto::Key(std::move(cek), nonce)); - std::vector aad(libcdoc::CDoc2Internal::PAYLOAD.cbegin(), libcdoc::CDoc2Internal::PAYLOAD.cend()); + std::vector aad(libcdoc::CDoc2::PAYLOAD.cbegin(), libcdoc::CDoc2::PAYLOAD.cend()); aad.insert(aad.end(), header.cbegin(), header.cend()); aad.insert(aad.end(), headerHMAC.cbegin(), headerHMAC.cend()); if(auto rv = cipher->writeAAD(aad); rv < 0) @@ -198,7 +198,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector> fb_rcpts; - std::vector xor_key(libcdoc::CDoc2Internal::KEY_LEN); + std::vector xor_key(libcdoc::CDoc2::KEY_LEN); for (unsigned int rcpt_idx = 0; rcpt_idx < recipients.size(); rcpt_idx++) { const libcdoc::Recipient& rcpt = recipients.at(rcpt_idx); if (rcpt.isPKI()) { @@ -223,7 +223,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vectorrandom(kek, libcdoc::CDoc2Internal::KEY_LEN); + crypto->random(kek, libcdoc::CDoc2::KEY_LEN); if (libcdoc::Crypto::xor_data(xor_key, fmk, kek) != libcdoc::OK) { setLastError("Internal error"); LOG_ERROR("{}", last_error); @@ -267,8 +267,8 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector sharedSecret = libcdoc::Crypto::deriveSharedSecret(ephKey.get(), publicKey.get()); key_material = libcdoc::Crypto::toPublicKeyDer(ephKey.get()); - std::vector kekPm = libcdoc::Crypto::extract(sharedSecret, std::vector(libcdoc::CDoc2Internal::KEKPREMASTER.cbegin(), libcdoc::CDoc2Internal::KEKPREMASTER.cend())); - std::string info_str = libcdoc::CDoc2Internal::getSaltForExpand(key_material, rcpt.rcpt_key); + std::vector kekPm = libcdoc::Crypto::extract(sharedSecret, std::vector(libcdoc::CDoc2::KEKPREMASTER.cbegin(), libcdoc::CDoc2::KEKPREMASTER.cend())); + std::string info_str = libcdoc::CDoc2::getSaltForExpand(key_material, rcpt.rcpt_key); kek = libcdoc::Crypto::expand(kekPm, info_str, fmk.size()); if (libcdoc::Crypto::xor_data(xor_key, fmk, kek) != libcdoc::OK) { @@ -302,16 +302,16 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector kek_pm(libcdoc::CDoc2Internal::KEY_LEN); + std::string info_str = libcdoc::CDoc2::getSaltForExpand(rcpt.getLabel({})); + std::vector kek_pm(libcdoc::CDoc2::KEY_LEN); std::vector salt; - int64_t result = crypto->random(salt, libcdoc::CDoc2Internal::KEY_LEN); + int64_t result = crypto->random(salt, libcdoc::CDoc2::KEY_LEN); if (result < 0) { setLastError(crypto->getLastErrorStr(result)); return result; } std::vector pw_salt; - result = crypto->random(pw_salt, libcdoc::CDoc2Internal::KEY_LEN); + result = crypto->random(pw_salt, libcdoc::CDoc2::KEY_LEN); if (result < 0) { setLastError(crypto->getLastErrorStr(result)); return result; @@ -321,7 +321,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vectorgetLastErrorStr(result)); return result; } - std::vector kek = libcdoc::Crypto::expand(kek_pm, info_str, libcdoc::CDoc2Internal::KEY_LEN); + std::vector kek = libcdoc::Crypto::expand(kek_pm, info_str, libcdoc::CDoc2::KEY_LEN); LOG_DBG("Label: {}", rcpt.label); LOG_DBG("KDF iter: {}", rcpt.kdf_iter); @@ -368,11 +368,11 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector key_material_salt; - crypto->random(key_material_salt, libcdoc::CDoc2Internal::KEY_LEN); + crypto->random(key_material_salt, libcdoc::CDoc2::KEY_LEN); //KeyMaterial_i = CSRNG(256) std::vector key_material; - crypto->random(key_material, libcdoc::CDoc2Internal::KEY_LEN); + crypto->random(key_material, libcdoc::CDoc2::KEY_LEN); //KEK_i_pm = HKDF_Extract(KeyMaterialSalt_i, KeyMaterial_i) std::vector kek_pm = libcdoc::Crypto::extract(key_material_salt, key_material); @@ -394,7 +394,7 @@ CDoc2Writer::buildHeader(std::vector& header, const std::vector> kek_shares(N_SHARES); for (int i = 1; i < N_SHARES; i++) { // KEK_i_share_j = CSRNG(256) - crypto->random(kek_shares[i], libcdoc::CDoc2Internal::KEY_LEN); + crypto->random(kek_shares[i], libcdoc::CDoc2::KEY_LEN); } // KEK_i_share_1 = XOR(KEK_i, KEK_i_share_2, KEK_i_share_3,..., KEK_i_share_n) kek_shares[0] = std::move(kek); diff --git a/cdoc/Lock.cpp b/cdoc/Lock.cpp index f61d6f2d..d5dd3614 100644 --- a/cdoc/Lock.cpp +++ b/cdoc/Lock.cpp @@ -59,12 +59,12 @@ Lock::parseLabel(const std::string& label) { std::map parsed_label; // Check if provided label starts with the machine generated label prefix. - if (!label.starts_with(CDoc2Internal::LABELPREFIX)) + if (!label.starts_with(CDoc2::LABELPREFIX)) { return parsed_label; } - std::string label_wo_prefix(label.substr(CDoc2Internal::LABELPREFIX.size())); + std::string label_wo_prefix(label.substr(CDoc2::LABELPREFIX.size())); // Label to be processed std::string label_to_prcss; @@ -72,7 +72,7 @@ Lock::parseLabel(const std::string& label) // We ignore mediatype part // Check, if the label is Base64 encoded - auto base64IndPos = label_wo_prefix.find(CDoc2Internal::LABELBASE64IND); + auto base64IndPos = label_wo_prefix.find(CDoc2::LABELBASE64IND); if (base64IndPos == std::string::npos) { if (label_wo_prefix.starts_with(",")) { @@ -83,7 +83,7 @@ Lock::parseLabel(const std::string& label) } else { - std::string base64_label(label_wo_prefix.substr(base64IndPos + CDoc2Internal::LABELBASE64IND.size())); + std::string base64_label(label_wo_prefix.substr(base64IndPos + CDoc2::LABELBASE64IND.size())); label_to_prcss = jwt::base::decode(base64_label); } diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 91fd3a43..6929ec07 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -132,8 +132,8 @@ buildLabel(std::ostream& ofs, std::string_view type, const std::map& dst, const std::string& url, const std::string& recipient, const std::vector& share) { @@ -354,6 +359,7 @@ libcdoc::NetworkBackend::sendShare(std::vector& dst, const std::string& return OK; } +#endif libcdoc::result_t libcdoc::NetworkBackend::fetchKey (std::vector& dst, const std::string& url, const std::string& transaction_id) @@ -392,6 +398,7 @@ libcdoc::NetworkBackend::fetchKey (std::vector& dst, const std::string& return libcdoc::OK; } +#ifdef HAS_KEYSHARES libcdoc::result_t libcdoc::NetworkBackend::fetchNonce(std::vector& dst, const std::string& url, const std::string& share_id) { @@ -475,6 +482,7 @@ libcdoc::NetworkBackend::fetchShare(ShareInfo& share, const std::string& url, co share = {std::move(shareval), std::move(recipient)}; return OK; } +#endif ECDSA_SIG * ecdsa_do_sign(const unsigned char *dgst, int dgst_len, const BIGNUM * /*inv*/, const BIGNUM * /*rp*/, EC_KEY *eckey) @@ -527,6 +535,7 @@ rsa_sign(int type, const unsigned char *m, unsigned int m_len, unsigned char *si return 1; } +#ifdef HAS_KEYSHARES libcdoc::result_t libcdoc::NetworkBackend::showVerificationCode(unsigned int code) { @@ -879,3 +888,4 @@ libcdoc::NetworkBackend::signMID(std::vector& dst, std::vector return OK; } +#endif diff --git a/cdoc/NetworkBackend.h b/cdoc/NetworkBackend.h index 446cff7d..4d1dc3d9 100644 --- a/cdoc/NetworkBackend.h +++ b/cdoc/NetworkBackend.h @@ -29,6 +29,7 @@ struct CDOC_EXPORT NetworkBackend { * */ static constexpr int NETWORK_ERROR = -300; +#ifdef HAS_KEYSHARES // MID/SID error codes // User refused the session static constexpr int MIDSID_USER_REFUSED = -350; @@ -62,9 +63,10 @@ struct CDOC_EXPORT NetworkBackend { static constexpr int MIDSID_DELIVERY_ERROR = -364; // Invalid response from card static constexpr int MIDSID_SIM_ERROR = -365; +#endif /** - * @brief Share information returned by server + * @brief Capsule information returned by capsule server * */ struct CapsuleInfo { @@ -79,8 +81,9 @@ struct CDOC_EXPORT NetworkBackend { */ uint64_t expiry_time; }; +#ifdef HAS_KEYSHARES /** - * @brief Share information returned by server + * @brief Share information returned by share server * */ struct ShareInfo { @@ -95,6 +98,7 @@ struct CDOC_EXPORT NetworkBackend { */ std::string recipient; }; +#endif /** * @brief Proxy credentials used for network access @@ -146,6 +150,7 @@ struct CDOC_EXPORT NetworkBackend { * @return error code or OK */ virtual result_t sendKey (CapsuleInfo& dst, const std::string& url, const std::vector& rcpt_key, const std::vector &key_material, const std::string& type, uint64_t expiry_ts); +#ifdef HAS_KEYSHARES /** * @brief send key share to server * @@ -157,6 +162,7 @@ struct CDOC_EXPORT NetworkBackend { * @return error code or OK */ virtual result_t sendShare(std::vector& dst, const std::string& url, const std::string& recipient, const std::vector& share); +#endif /** * @brief fetch key material from keyserver * @@ -167,6 +173,7 @@ struct CDOC_EXPORT NetworkBackend { * @return error code or OK */ virtual result_t fetchKey (std::vector& dst, const std::string& url, const std::string& transaction_id); +#ifdef HAS_KEYSHARES /** * @brief fetch authentication nonce from share server * @param dst a destination container for nonce @@ -185,7 +192,7 @@ struct CDOC_EXPORT NetworkBackend { * @return error code or OK */ virtual result_t fetchShare(ShareInfo& share, const std::string& url, const std::string& share_id, const std::string& ticket, const std::vector& cert); - +#endif /** * @brief get client TLS certificate in der format @@ -234,6 +241,7 @@ struct CDOC_EXPORT NetworkBackend { return NOT_IMPLEMENTED; } +#ifdef HAS_KEYSHARES /** * @brief show MID/SID verification code * @@ -277,6 +285,7 @@ struct CDOC_EXPORT NetworkBackend { result_t signMID(std::vector& dst, std::vector& cert, const std::string& url, const std::string& rp_uuid, const std::string& rp_name, const std::string& phone, const std::string& rcpt_id, const std::vector& digest, CryptoBackend::HashAlgorithm algo); +#endif }; } // namespace libcdoc diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index bafef456..49e7f653 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -140,6 +140,7 @@ Recipient::makeServer(const Lock &lock, std::string server_id) return rcpt; } +#ifdef HAS_KEYSHARES Recipient Recipient::makeShare(std::string label, std::string server_id, std::string recipient_id) { @@ -149,6 +150,7 @@ Recipient::makeShare(std::string label, std::string server_id, std::string recip rcpt.id = std::move(recipient_id); return rcpt; } +#endif bool Recipient::isTheSameRecipient(const Recipient& other) const @@ -195,8 +197,10 @@ Recipient::getLabel(std::map extra) const ofs << '&' << urlEncode(key) << '=' << urlEncode(value); } break; +#ifdef HAS_KEYSHARES case KEYSHARE: break; +#endif } LOG_DBG("Generated label: {}", ofs.str()); return ofs.str(); diff --git a/cdoc/Recipient.h b/cdoc/Recipient.h index 3844f0ee..8374bc4b 100644 --- a/cdoc/Recipient.h +++ b/cdoc/Recipient.h @@ -52,10 +52,12 @@ struct CDOC_EXPORT Recipient { * @brief Public key */ PUBLIC_KEY, +#ifdef HAS_KEYSHARES /** * @brief n of n shared symmetric key */ KEYSHARE +#endif }; Recipient() = default; @@ -84,10 +86,12 @@ struct CDOC_EXPORT Recipient { * @brief The recipient's certificate (if present) */ std::vector cert; +#ifdef HAS_KEYSHARES /** * @brief The recipient id for share server (PNOEE-XXXXXXXXXXX) */ std::string id; +#endif /** * @brief The keyserver or share server list id (if present) */ @@ -123,11 +127,13 @@ struct CDOC_EXPORT Recipient { * @return true if type is SERVER */ bool isKeyServer() const { return (type == Type::PUBLIC_KEY) && !server_id.empty(); } +#ifdef HAS_KEYSHARES /** * @brief check whether Recipient is keyshare * @return true if type is KEYSHARE */ bool isKeyShare() const { return type == Type::KEYSHARE; } +#endif /** * @brief Clear all values and set type to NONE @@ -208,6 +214,7 @@ struct CDOC_EXPORT Recipient { */ static Recipient makeServer(const Lock &lock, std::string server_id); +#ifdef HAS_KEYSHARES /** * @brief Create new keyshare recipient * @@ -217,6 +224,7 @@ struct CDOC_EXPORT Recipient { * @return Recipient a new Recipient structure */ static Recipient makeShare(std::string label, std::string server_id, std::string recipient_id); +#endif /** * @brief Get the label for this recipient diff --git a/cdoc/ToolConf.h b/cdoc/ToolConf.h index f1fe48bb..73212acf 100644 --- a/cdoc/ToolConf.h +++ b/cdoc/ToolConf.h @@ -84,6 +84,7 @@ struct ToolConf : public JSONConfiguration { return sdata.url; } else if (param == Configuration::KEYSERVER_FETCH_URL) { return sdata.url; +#ifdef HAS_KEYSHARES } else if (param == Configuration::SHARE_SERVER_URLS) { // Return JSON std::stringstream ss; @@ -97,6 +98,7 @@ struct ToolConf : public JSONConfiguration { } ss << "]"; return ss.str(); +#endif } } } diff --git a/cdoc/cdoc-tool.cpp b/cdoc/cdoc-tool.cpp index 30bd7364..9caa41fc 100644 --- a/cdoc/cdoc-tool.cpp +++ b/cdoc/cdoc-tool.cpp @@ -49,17 +49,17 @@ enum { static void print_usage(ostream& ofs) { ofs << "cdoc-tool version: " << VERSION_STR << endl; + ofs << "libcdoc version: " << libcdoc::getVersion() << endl; ofs << "cdoc-tool encrypt --rcpt RECIPIENT [--rcpt...] [-v1] [--genlabel] --out OUTPUTFILE FILE [FILE...]" << endl; ofs << " Encrypt files for one or more recipients" << endl; ofs << " RECIPIENT has to be one of the following:" << endl; ofs << " [label]:cert:CERTIFICATE_FILE - public key from certificate" << endl; - ofs << " [label]:pkey:SECRET_KEY_HEX - public key" << endl; + ofs << " [label]:pkey:PUB_KEY_HEX - hex-encoded public key from command line" << endl; ofs << " [label]:pfkey:PUB_KEY_FILE - path to DER file with EC (secp384r1 curve) public key" << endl; ofs << " [label]:skey:SECRET_KEY_HEX - AES key" << endl; - ofs << " [label]:pw:PASSWORD - Derive key using PWBKDF" << endl; + ofs << " [label]:pw:PASSWORD - Derive key from provided password using PWBKDF" << endl; ofs << " [label]:p11sk:SLOT:[PIN]:[PKCS11 ID]:[PKCS11 LABEL] - use AES key from PKCS11 module" << endl; ofs << " [label]:p11pk:SLOT:[PIN]:[PKCS11 ID]:[PKCS11 LABEL] - use public key from PKCS11 module" << endl; - ofs << " [label]:share:ID - use keyshares with given ID (personal code)" << endl; ofs << " -v1 - creates CDOC1 version container. Supported only for encryption with certificate." << endl; ofs << " --genlabel - If specified, the lock label is generated." << endl; ofs << endl; From 4f61b5b5f1f9f8cbff3852709fda2514308a69e7 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 8 Apr 2026 15:43:17 +0300 Subject: [PATCH 42/50] Set version to 0.5.0 --- CMakeLists.txt | 3 ++- cdoc/CDoc1Writer.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 35157852..2de8e8ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ if(POLICY CMP0167) cmake_policy(SET CMP0167 NEW) endif() -project(libcdoc VERSION 0.1.8) +project(libcdoc VERSION 0.5.0) macro(SET_ENV NAME DEF) if(DEFINED ENV{${NAME}}) @@ -21,6 +21,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_C_VISIBILITY_PRESET hidden) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN YES) +set(CMAKE_BUILD_TYPE Debug) set(BUILD_TOOLS YES CACHE BOOL "Build cdoc-tool" ) set(BUILD_SHARED_LIBS YES CACHE BOOL "Build library as SHARED or STATIC") set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Adds a postfix for debug-built libraries.") diff --git a/cdoc/CDoc1Writer.cpp b/cdoc/CDoc1Writer.cpp index 534304d5..5c0ef66c 100644 --- a/cdoc/CDoc1Writer.cpp +++ b/cdoc/CDoc1Writer.cpp @@ -104,7 +104,7 @@ int64_t CDoc1Writer::Private::writeDocument(bool use_ddoc, const std::vector int64_t { - RET_ERROR(writeTextElement(DENC, "EncryptionProperty", {{"Name", "LibraryVersion"}}, "cdoc|0.0.1")); + RET_ERROR(writeTextElement(DENC, "EncryptionProperty", {{"Name", "LibraryVersion"}}, VERSION_STR)); RET_ERROR(writeTextElement(DENC, "EncryptionProperty", {{"Name", "DocumentFormat"}}, documentFormat)); RET_ERROR(writeTextElement(DENC, "EncryptionProperty", {{"Name", "Filename"}}, use_ddoc ? "tmp.ddoc" : files.at(0).name)); for(const FileEntry &file: files) From a9a67a59d3af24a8deb848b734057a14f4638889 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 8 Apr 2026 15:45:23 +0300 Subject: [PATCH 43/50] Remove forced Debug build --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2de8e8ab..41b600d9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,6 @@ set(CMAKE_CXX_STANDARD_REQUIRED YES) set(CMAKE_C_VISIBILITY_PRESET hidden) set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN YES) -set(CMAKE_BUILD_TYPE Debug) set(BUILD_TOOLS YES CACHE BOOL "Build cdoc-tool" ) set(BUILD_SHARED_LIBS YES CACHE BOOL "Build library as SHARED or STATIC") set(CMAKE_DEBUG_POSTFIX "d" CACHE STRING "Adds a postfix for debug-built libraries.") From d25ad52e000dafadaa528a8652b3f61ccfb933b1 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 9 Apr 2026 14:00:16 +0300 Subject: [PATCH 44/50] Fixed Recipient --- cdoc/Recipient.cpp | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 44c6d215..19fb6bcb 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -41,6 +41,33 @@ Recipient::makeSymmetric(std::string label, int32_t kdf_iter) return rcpt; } +Recipient +Recipient::makeRSA(std::string label, std::vector public_key) +{ + if (public_key.empty()) + return {Type::NONE}; + Recipient rcpt(Type::PUBLIC_KEY); + rcpt.label = std::move(label); + rcpt.pk_type = RSA; + rcpt.rcpt_key = std::move(public_key); + return rcpt; +} + +Recipient +Recipient::makeECC(std::string label, std::vector public_key, Curve ec_type) +{ + if (public_key.empty()) + return {Type::NONE}; + Recipient rcpt(Type::PUBLIC_KEY); + rcpt.label = std::move(label); + rcpt.pk_type = ECC; + rcpt.ec_type = ec_type; + // 0x30 identifies SEQUENCE tag in ASN.1 encoding + auto evp = Crypto::fromECPublicKeyDer(public_key); + rcpt.rcpt_key = Crypto::toPublicKeyDer(evp.get()); + return rcpt; +} + Recipient Recipient::makePublicKey(const Lock &lock) { From 1031bbf2a70a68db2c5e7d5fcbf1983506688be8 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 9 Apr 2026 15:13:03 +0300 Subject: [PATCH 45/50] Added secp521r1 curve support --- cdoc/CDoc.h | 3 +- cdoc/CDoc2Reader.cpp | 4 +++ cdoc/CDoc2Writer.cpp | 3 +- cdoc/CDocCipher.cpp | 3 ++ cdoc/schema/recipients.fbs | 3 +- test/libcdoc_boost.cpp | 69 ++++++++++++-------------------------- 6 files changed, 34 insertions(+), 51 deletions(-) diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index 113a8ab2..a5fa2255 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -175,7 +175,8 @@ enum Algorithm : uint8_t { enum Curve : uint8_t { UNKNOWN_CURVE, SECP_384_R1, - SECP_256_R1 + SECP_256_R1, + SECP_521_R1 }; diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index f9a204f5..e9305d74 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -515,6 +515,8 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor lock.ec_type = Curve::SECP_384_R1; } else if (capsule->curve() == EllipticCurve::secp256r1) { lock.ec_type = Curve::SECP_256_R1; + } else if (capsule->curve() == EllipticCurve::secp521r1) { + lock.ec_type = Curve::SECP_521_R1; } else { LOG_WARN("Unknown ECC curve: {}", (int) capsule->curve()); lock.ec_type = Curve::UNKNOWN_CURVE; @@ -545,6 +547,8 @@ CDoc2Reader::Private::buildLock(Lock& lock, const cdoc20::header::RecipientRecor lock.ec_type = Curve::SECP_384_R1; } else if (eccDetails->curve() == EllipticCurve::secp256r1) { lock.ec_type = Curve::SECP_256_R1; + } else if (eccDetails->curve() == EllipticCurve::secp521r1) { + lock.ec_type = Curve::SECP_521_R1; } else { LOG_WARN("Unknown ECC curve: {}", (int) eccDetails->curve()); lock.ec_type = Curve::UNKNOWN_CURVE; diff --git a/cdoc/CDoc2Writer.cpp b/cdoc/CDoc2Writer.cpp index d6c2690d..97d858a8 100644 --- a/cdoc/CDoc2Writer.cpp +++ b/cdoc/CDoc2Writer.cpp @@ -102,7 +102,8 @@ struct ECData { static std::map ecdata = { {Curve::SECP_384_R1, {NID_secp384r1, cdoc20::recipients::EllipticCurve::secp384r1, "ecc_secp384r1"}}, - {Curve::SECP_256_R1, {NID_X9_62_prime256v1, cdoc20::recipients::EllipticCurve::secp256r1, "ecc_secp256r1"}} + {Curve::SECP_256_R1, {NID_X9_62_prime256v1, cdoc20::recipients::EllipticCurve::secp256r1, "ecc_secp256r1"}}, + {Curve::SECP_521_R1, {NID_secp521r1, cdoc20::recipients::EllipticCurve::secp521r1, "ecc_secp521r1"}} }; static flatbuffers::Offset diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index a9ddef5e..7fd7b868 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -307,6 +307,9 @@ fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector rcpts { - {libcdoc::RcptInfo::SKEY, "AES", {}, libcdoc::fromHex(AESKey)} - }; - encrypt(2, {checkDataFile(sources[0])}, formTargetFile("AESKeyUsage.cdoc"), rcpts); -} -BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithAESKey, DecryptFixture, - * utf::depends_on("AESKeyUsage/EncryptWithAESKey") - * utf::description("Decrypting a file with with symmetric AES key")) -{ - decrypt({checkDataFile(sources[0])}, checkTargetFile("AESKeyUsage.cdoc"), tmpDataPath.string(), libcdoc::fromHex(AESKey)); -} -BOOST_AUTO_TEST_SUITE_END() - -// CDoc2 EC public/private key - -BOOST_AUTO_TEST_SUITE(ECKeyUsage) -BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithECKey, EncryptFixture, - * utf::description("Encrypting a file with EC key")) +BOOST_AUTO_TEST_SUITE(CDoc2KeyUsage) +static constexpr string_view CONTAINER("CDoc2KeyUsage.cdoc"); +BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithCDoc2Key, EncryptFixture, + * utf::description("Encrypting a CDoc2 file with a key")) { std::vector rcpts { {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(ECPubKeyFile)}, - {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(EC256PubKeyFile)} + {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(EC256PubKeyFile)}, + {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(EC521PubKeyFile)}, + {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(RSAPubKeyFile)}, + {libcdoc::RcptInfo::SKEY, "AES", {}, libcdoc::fromHex(AESKey)} }; - encrypt(2, {checkDataFile(sources[0])}, formTargetFile("ECKeyUsage.cdoc"), rcpts); -} -BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithECKey, DecryptFixture, - * utf::depends_on("ECKeyUsage/EncryptWithECKey") - * utf::description("Decrypting a file with with EC private key")) -{ - decrypt({checkDataFile(sources[0])}, checkTargetFile("ECKeyUsage.cdoc"), tmpDataPath.string(), fetchDataFile(ECPrivKeyFile), 0, false); - decrypt({checkDataFile(sources[0])}, checkTargetFile("ECKeyUsage.cdoc"), tmpDataPath.string(), fetchDataFile(EC256PrivKeyFile), 1, true); + encrypt(2, {checkDataFile(sources[0])}, formTargetFile(CONTAINER), rcpts); } -BOOST_AUTO_TEST_SUITE_END() - -// CDoc2 RSA public/private key -BOOST_AUTO_TEST_SUITE(RSAKeyUsage) -BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithRSAKey, EncryptFixture, - * utf::description("Encrypting a file with RSA key")) -{ - std::vector rcpts { - {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(RSAPubKeyFile)} - }; - encrypt(2, {checkDataFile(sources[0])}, formTargetFile("RSAKeyUsage.cdoc"), rcpts); -} -BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithRSAKey, DecryptFixture, - * utf::depends_on("RSAKeyUsage/EncryptWithRSAKey") - * utf::description("Decrypting a file with with RSA private key")) +BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithCDoc2Key, DecryptFixture, + * utf::depends_on("CDoc2KeyUsage/EncryptWithCDoc2Key") + * utf::description("Decrypting a CDoc2 file with a key")) { - decrypt({checkDataFile(sources[0])}, checkTargetFile("RSAKeyUsage.cdoc"), tmpDataPath.string(), fetchDataFile(RSAPrivKeyFile)); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(ECPrivKeyFile), 0, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC256PrivKeyFile), 1, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC521PrivKeyFile), 2, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(RSAPrivKeyFile), 3, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), libcdoc::fromHex(AESKey), 4, true); } BOOST_AUTO_TEST_SUITE_END() From 0deebb2832cc1ac16f4aec5732e568961aa97939 Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Tue, 28 Apr 2026 13:42:45 +0300 Subject: [PATCH 46/50] Remove unused variable --- cdoc/CDocCipher.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index 7fd7b868..a28b6ef7 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -217,7 +217,6 @@ struct ToolNetwork : public libcdoc::NetworkBackend { libcdoc::result_t signTLS(std::vector& dst, libcdoc::CryptoBackend::HashAlgorithm algorithm, const std::vector &digest) override final { if (rcpt_idx >= crypto->rcpts.size()) rcpt_idx = 0; - const libcdoc::RcptInfo& rcpt = crypto->rcpts[rcpt_idx]; return crypto->p11->sign(dst, algorithm, digest, rcpt_idx); } From 2c01fb5c21c1bb8bb34c80fe10e38f5ef67917cc Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Tue, 28 Apr 2026 13:47:11 +0300 Subject: [PATCH 47/50] Added secp521r1 test keys/certificates --- test/data/ec-secp521r1-cert.der | Bin 0 -> 735 bytes test/data/ec-secp521r1-priv.der | Bin 0 -> 241 bytes test/data/ec-secp521r1-pub.der | Bin 0 -> 158 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/data/ec-secp521r1-cert.der create mode 100644 test/data/ec-secp521r1-priv.der create mode 100644 test/data/ec-secp521r1-pub.der diff --git a/test/data/ec-secp521r1-cert.der b/test/data/ec-secp521r1-cert.der new file mode 100644 index 0000000000000000000000000000000000000000..411ae7a5f7ee72401dc5a8418f7c683908a1998d GIT binary patch literal 735 zcmXqLV!Cb6#ALI8nTe5!NyPr6%87sn=lrWI_1AN)P5YW9CeLla#m1r4=5fxJg_+5q z-jLgXlZ`o)g-w{r)zwhTKm){K=27%IJk`}d$X&q?Uz;9jew)%s>PrqQ%3DqRUXqKmsJrDl8b_=kMyOi)4y{oH(zMnSqIc zrJGyaO9&8wjwmg9D6-k&RWmk%d8-xv`Cbg<;~y#m9CO1g$Ka z5cZn=ug8QJ(@bi`d#$fd58*$tX!-YvF~+w)8#9PX>|B*+b93(6)Xo;^jk6QY-rr+a zzic5D;V#3N{^a-@BVNVq4N^NVY+QX?e9?|wX+`swx>f728{IR!sq9?+oU0));Qu~L z&Xs??yb`3GcZF;gjQSG$OIYGur%IvG;$VY716g3;%JQ*@v54e}NIkeHzVDurr)bEm z{2zX<=hkQ$$b+PnStJa^8n7$i2PqI{Wc<&j(Wvkm!~)eHV>8)t6Kc$fBqBkz6y|^D_Vy z7`du$JlUPCWr>6vv72Ew@7@O|(>NMfM;rlXVHt*gCC(}3@SVtTIXXMB1 zG7TYXuo}70v8&q`qPV(da-XF}dn^Ys-Z9xDLwn~6fKgEYzBvi0{Ypw;8bi8Nvk_YK MUHTIk&WR*(GVqK;y#N3J literal 0 HcmV?d00001 From 043d31a88550738f635ce889c0086ea484a7729e Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 29 Apr 2026 11:19:07 +0300 Subject: [PATCH 48/50] Fix recipient --- cdoc/Lock.cpp | 17 +++++++++++------ cdoc/Recipient.cpp | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/cdoc/Lock.cpp b/cdoc/Lock.cpp index ce9f9079..5014d2fe 100644 --- a/cdoc/Lock.cpp +++ b/cdoc/Lock.cpp @@ -30,18 +30,23 @@ namespace libcdoc { std::string Lock::getString(Params key) const { - const std::vector& bytes = params.at(key); - return {(const char *) bytes.data(), bytes.size()}; + if (params.contains(key)) { + const std::vector& bytes = params.at(key); + return {(const char *) bytes.data(), bytes.size()}; + } + return {}; } int32_t Lock::getInt(Params key) const { - const std::vector& bytes = params.at(key); int32_t val = 0; - for (int i = 0; (i < bytes.size()) && (i < 4); i++) { - val = (val << 8) | bytes.at(i); - } + if (params.contains(key)) { + const std::vector& bytes = params.at(key); + for (int i = 0; (i < bytes.size()) && (i < 4); i++) { + val = (val << 8) | bytes.at(i); + } + } return val; } diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 49e7f653..88cf579a 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -212,7 +212,7 @@ Recipient::validate() const switch(type) { case SYMMETRIC_KEY: // Either user-defined label or LABEL property is required - return !label.empty() || lbl_parts.contains("CDoc2::Label::LABEL"); + return !label.empty() || lbl_parts.contains(std::string(CDoc2::Label::LABEL)); case PUBLIC_KEY: // Public key should not be empty return !rcpt_key.empty(); From 6c8062922fb98ce8c5d02be35e6ae7ee417e507c Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Wed, 6 May 2026 13:16:02 +0300 Subject: [PATCH 49/50] Cleaned Recipient building --- .gitignore | 2 + cdoc/CDoc.h | 14 ------ cdoc/CDocCipher.cpp | 47 ++------------------ cdoc/Certificate.cpp | 8 ++++ cdoc/Certificate.h | 1 + cdoc/Crypto.cpp | 24 +++++++++++ cdoc/Crypto.h | 2 + cdoc/NetworkBackend.h | 2 +- cdoc/Recipient.cpp | 98 ++++++++++++++++++++++++++++++++++-------- cdoc/Recipient.h | 63 ++------------------------- test/libcdoc_boost.cpp | 32 ++++++++------ 11 files changed, 143 insertions(+), 150 deletions(-) diff --git a/.gitignore b/.gitignore index f10d741a..d2bd5631 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,5 @@ obj-x86_64-linux-gnu/ test/data/tmp/ vcpkg/ vcpkg_installed/ +examples/java/.gradle +examples/java/bin diff --git a/cdoc/CDoc.h b/cdoc/CDoc.h index a5fa2255..235d39a3 100644 --- a/cdoc/CDoc.h +++ b/cdoc/CDoc.h @@ -31,20 +31,6 @@ namespace libcdoc { */ using result_t = int64_t; -/** - * @brief The public key type - */ -enum class PKType : uint8_t { - /** - * Elliptic curve - */ - ECC, - /** - * RSA - */ - RSA -}; - enum { /** * @brief Operation completed successfully diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index a28b6ef7..20e99ad2 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -287,49 +287,10 @@ fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector +Certificate::getPublicKeyLong() const +{ + if(cert) + return Crypto::toPublicKeyDerLong(X509_get0_pubkey(cert.get())); + return {}; +} + Algorithm Certificate::getAlgorithm() const { diff --git a/cdoc/Certificate.h b/cdoc/Certificate.h index 316369ca..69c593fd 100644 --- a/cdoc/Certificate.h +++ b/cdoc/Certificate.h @@ -49,6 +49,7 @@ class Certificate { EIDType getEIDType() const; std::vector getPublicKey() const; + std::vector getPublicKeyLong() const; Algorithm getAlgorithm() const; time_t getNotAfter() const; diff --git a/cdoc/Crypto.cpp b/cdoc/Crypto.cpp index 29b09f3e..47238ec2 100644 --- a/cdoc/Crypto.cpp +++ b/cdoc/Crypto.cpp @@ -310,6 +310,17 @@ Crypto::pbkdf2_sha256(const std::vector& pw, const std::vector return key; } +Crypto::EVP_PKEY_ptr +Crypto::fromPublicKeyDer(const std::vector &der) +{ + if (auto key = d2i(der, nullptr)) + return key; + auto key = d2i(der, EVP_PKEY_RSA, nullptr); + if(!key) + LOG_SSL_ERROR("d2i_PublicKey"); + return key; +} + Crypto::EVP_PKEY_ptr Crypto::fromRSAPublicKeyDer(const std::vector &der) { @@ -376,6 +387,19 @@ Crypto::toPublicKeyDer(EVP_PKEY *key) return der; } +std::vector +Crypto::toPublicKeyDerLong(EVP_PKEY *key) +{ + if(!key) return {}; + std::vector der(i2d_PUBKEY(key, nullptr), 0); + if(auto *p = der.data(); i2d_PUBKEY(key, &p) != der.size()) + { + LOG_SSL_ERROR("i2d_PUBKEY"); + der.clear(); + } + return der; +} + std::vector Crypto::random(uint32_t len) { diff --git a/cdoc/Crypto.h b/cdoc/Crypto.h index d60bffe5..0c2bcaa4 100644 --- a/cdoc/Crypto.h +++ b/cdoc/Crypto.h @@ -89,9 +89,11 @@ class Crypto static EVP_PKEY_ptr fromECPublicKeyDer(const std::vector &der, int curveName); /* Create public key from long encoding (0x30...) */ static EVP_PKEY_ptr fromECPublicKeyDer(const std::vector &der); + static EVP_PKEY_ptr fromPublicKeyDer(const std::vector &der); static EVP_PKEY_ptr genECKey(EVP_PKEY *params); static std::vector toPublicKeyDer(EVP_PKEY *key); + static std::vector toPublicKeyDerLong(EVP_PKEY *key); static std::vector random(uint32_t len = 32); static int xor_data(std::vector& dst, const std::vector &lhs, const std::vector &rhs); diff --git a/cdoc/NetworkBackend.h b/cdoc/NetworkBackend.h index 8410f195..3ec43618 100644 --- a/cdoc/NetworkBackend.h +++ b/cdoc/NetworkBackend.h @@ -145,7 +145,7 @@ struct CDOC_EXPORT NetworkBackend { * @param url server url * @param rcpt_key recipient's public key * @param key_material encrypted KEK or ECDH public Key used to derive shared secret - * @param type algorithm type, currently either "rsa", "ecc_secp384r1" or "ecc_secp256r1" + * @param type algorithm type, currently either "rsa", "ecc_secp384r1", "ecc_secp256r1" or "ecc_secp521r1" * @param expiry_ts the requested capsule expiry timestamp, 0 - use server default * @return error code or OK */ diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index 86921468..fe281030 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -27,6 +27,9 @@ #include #include +#include +#include + using namespace std; namespace libcdoc { @@ -41,24 +44,26 @@ Recipient::makeSymmetric(std::string label, int32_t kdf_iter) return rcpt; } -Recipient -Recipient::makeRSA(std::string label, std::vector public_key) +static Recipient +makeRSA(std::string label, std::vector public_key) { if (public_key.empty()) - return {Type::NONE}; - Recipient rcpt(Type::PUBLIC_KEY); + return {}; + Recipient rcpt; + rcpt.type = Recipient::Type::PUBLIC_KEY; rcpt.label = std::move(label); rcpt.pk_type = RSA; rcpt.rcpt_key = std::move(public_key); return rcpt; } -Recipient -Recipient::makeECC(std::string label, std::vector public_key, Curve ec_type) +static Recipient +makeECC(std::string label, std::vector public_key, Curve ec_type) { if (public_key.empty()) - return {Type::NONE}; - Recipient rcpt(Type::PUBLIC_KEY); + return {}; + Recipient rcpt; + rcpt.type = Recipient::Type::PUBLIC_KEY; rcpt.label = std::move(label); rcpt.pk_type = ECC; rcpt.ec_type = ec_type; @@ -68,6 +73,39 @@ Recipient::makeECC(std::string label, std::vector public_key, Curve ec_ return rcpt; } +static int +EVP_PKEY_get_nid(EVP_PKEY *pkey) +{ + std::array name; + if (SSL_FAILED(EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name.data(), name.size(), nullptr), "EVP_PKEY_get_utf8_string_param")) + return NID_undef; + return OBJ_sn2nid(name.data()); +} + +Recipient +Recipient::makePublicKey(std::string label, std::vector public_key) { + auto pkey = Crypto::fromPublicKeyDer(public_key); + if (!pkey) { + return {}; + } + int id = EVP_PKEY_get_id(pkey.get()); + if (id == EVP_PKEY_RSA) { + return makeRSA(label, public_key); + } else { + int nid = EVP_PKEY_get_nid(pkey.get()); + switch(nid) { + case NID_secp384r1: + return makeECC(label, public_key, Curve::SECP_384_R1); + case NID_X9_62_prime256v1: + return makeECC(label, public_key, Curve::SECP_256_R1); + case NID_secp521r1: + return makeECC(label, public_key, Curve::SECP_521_R1); + default: + return {}; + } + } +} + Recipient Recipient::makePublicKey(const Lock &lock) { @@ -98,11 +136,10 @@ Recipient::makeCertificate(std::string label, std::vector cert) Certificate x509(cert); if (!x509) return {Type::NONE}; - Recipient rcpt(Type::PUBLIC_KEY); - rcpt.label = std::move(label); + Recipient rcpt = makePublicKey(label, x509.getPublicKeyLong()); + if (rcpt.type == Type::NONE) + return rcpt; rcpt.cert = std::move(cert); - rcpt.rcpt_key = x509.getPublicKey(); - rcpt.pk_type = x509.getAlgorithm(); rcpt.expiry_ts = x509.getNotAfter(); if (auto eid = x509.getEIDType(); eid != Certificate::Unknown) { rcpt.lbl_parts = { @@ -122,8 +159,8 @@ Recipient::makeCertificate(std::string label, std::vector cert) return rcpt; } -Recipient -Recipient::makeServerRSA(std::string label, std::vector public_key, std::string server_id) +static Recipient +makeServerRSA(std::string label, std::vector public_key, std::string server_id) { Recipient rcpt = makeRSA(std::move(label), std::move(public_key)); rcpt.server_id = std::move(server_id); @@ -133,8 +170,8 @@ Recipient::makeServerRSA(std::string label, std::vector public_key, std return rcpt; } -Recipient -Recipient::makeServerECC(std::string label, std::vector public_key, Curve ec_type, std::string server_id) +static Recipient +makeServerECC(std::string label, std::vector public_key, Curve ec_type, std::string server_id) { Recipient rcpt = makeECC(std::move(label), std::move(public_key), ec_type); rcpt.server_id = std::move(server_id); @@ -145,10 +182,33 @@ Recipient::makeServerECC(std::string label, std::vector public_key, Cur } Recipient -Recipient::makeServer(std::string label, std::vector cert, std::string server_id) -{ - Recipient rcpt = makeCertificate(std::move(label), std::move(cert)); +Recipient::makeServer(std::string label, std::vector public_key, std::string server_id) { + Recipient rcpt; + auto pkey = Crypto::fromPublicKeyDer(public_key); + if (!pkey) { + rcpt = makeCertificate(std::move(label), std::move(public_key)); + } else { + int id = EVP_PKEY_get_id(pkey.get()); + if (id == EVP_PKEY_RSA) { + rcpt = makeRSA(label, public_key); + } else { + int nid = EVP_PKEY_get_nid(pkey.get()); + switch(nid) { + case NID_secp384r1: + rcpt = makeECC(label, public_key, Curve::SECP_384_R1); + case NID_X9_62_prime256v1: + rcpt = makeECC(label, public_key, Curve::SECP_256_R1); + case NID_secp521r1: + rcpt = makeECC(label, public_key, Curve::SECP_521_R1); + default: + return rcpt; + } + } + } rcpt.server_id = std::move(server_id); + const auto six_months_from_now = std::chrono::system_clock::now() + std::chrono::months(6); + const auto expiry_ts = std::chrono::system_clock::to_time_t(six_months_from_now); + rcpt.expiry_ts = (rcpt.expiry_ts) ? std::min(rcpt.expiry_ts, uint64_t(expiry_ts)) : uint64_t(expiry_ts); return rcpt; } diff --git a/cdoc/Recipient.h b/cdoc/Recipient.h index 0b567cd5..82a9d564 100644 --- a/cdoc/Recipient.h +++ b/cdoc/Recipient.h @@ -160,15 +160,6 @@ struct CDOC_EXPORT Recipient { */ static Recipient makeSymmetric(std::string label, int32_t kdf_iter); - /** - * @brief Create a new public key based Recipient with RSA algorithm - * - * @param label - * @param public_key - * @return Recipient - */ - static Recipient makeRSA(std::string label, std::vector public_key); - static Recipient makeECC(std::string label, std::vector public_key, Curve ec_type); /** * @brief Create a new public key based Recipient * @@ -176,19 +167,9 @@ struct CDOC_EXPORT Recipient { * * @param label the label text * @param public_key the public key value - * @param pk_type the algorithm type (either ECC or RSA) * @return a new Recipient structure */ - static Recipient makePublicKey(std::string label, std::vector public_key, Algorithm pk_type) { - switch(pk_type) { - case RSA: - return makeRSA(label, public_key); - case ECC: - return makeECC(label, public_key, Curve::SECP_384_R1); - default: - return {}; - } - } + static Recipient makePublicKey(std::string label, std::vector public_key); /** * @brief Create a new public key based Recipient @@ -207,41 +188,17 @@ struct CDOC_EXPORT Recipient { */ static Recipient makeCertificate(std::string label, std::vector cert); - static Recipient makeServerRSA(std::string label, std::vector public_key, std::string server_id); - static Recipient makeServerECC(std::string label, std::vector public_key, Curve ec_type, std::string server_id); /** * @brief Create a new capsule server based Recipient * * If the label is empty, a machine-readable label text (public key version) is automatically generated according to CDoc2 specification. * * @param label the label text - * @param public_key the public key value - * @param pk_type the algorithm type (either ECC or RSA) - * @param server_id the keyserver id - * @return a new Recipient structure - */ - static Recipient makeServer(std::string label, std::vector public_key, Algorithm pk_type, std::string server_id) { - switch(pk_type) { - case RSA: - return makeServerRSA(label, public_key, server_id); - case ECC: - return makeServerECC(label, public_key, Curve::SECP_384_R1, server_id); - default: - return {}; - } - } - - /** - * @brief Create a new capsule server based Recipient - * - * If the label is empty, a machine-readable label text (either eID or certificate version) is automatically generated according to CDoc2 specification. - * - * @param label the label text - * @param cert the recipient's certificate (der-encoded) + * @param public_key Recipient's public key or certificate (der-encoded) * @param server_id the keyserver id * @return a new Recipient structure */ - static Recipient makeServer(std::string label, std::vector cert, std::string server_id); + static Recipient makeServer(std::string label, std::vector public_key, std::string server_id); /** * @brief Create a new capsule server based Recipient @@ -293,20 +250,6 @@ struct CDOC_EXPORT Recipient { */ bool validate() const; - /** - * @brief Set a property for automatic label generation - * - * @param key the property name - * @param value the property value - */ - void setLabelValue(std::string_view key, std::string_view value) { - if (!value.empty()) { - lbl_parts[std::string(key)] = value; - } else { - lbl_parts.erase(std::string(key)); - } - } - bool operator== (const Recipient& other) const = default; protected: Recipient(Type _type) : type(_type) {}; diff --git a/test/libcdoc_boost.cpp b/test/libcdoc_boost.cpp index 87e8c403..2ee7f070 100644 --- a/test/libcdoc_boost.cpp +++ b/test/libcdoc_boost.cpp @@ -51,9 +51,9 @@ constexpr string_view SourceFile3("test_data3.txt"); * @brief Encrypted file name. */ constexpr string_view TargetFile("test_data.txt.cdoc"); -constexpr string_view ECPrivKeyFile("ec-secp384r1-priv.der"); -constexpr string_view ECPubKeyFile("ec-secp384r1-pub.der"); -constexpr string_view ECCertFile("ec-secp384r1-cert.der"); +constexpr string_view EC384PrivKeyFile("ec-secp384r1-priv.der"); +constexpr string_view EC384PubKeyFile("ec-secp384r1-pub.der"); +constexpr string_view EC384CertFile("ec-secp384r1-cert.der"); constexpr string_view EC256PrivKeyFile("ec-secp256r1-priv.der"); constexpr string_view EC256PubKeyFile("ec-secp256r1-pub.der"); constexpr string_view EC256CertFile("ec-secp256r1-cert.der"); @@ -572,9 +572,12 @@ BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithCDoc2Key, EncryptFixture, * utf::description("Encrypting a CDoc2 file with a key")) { std::vector rcpts { - {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(ECPubKeyFile)}, + {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(EC384PubKeyFile)}, + {libcdoc::RcptInfo::CERT, {}, fetchDataFile(EC384CertFile)}, {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(EC256PubKeyFile)}, + {libcdoc::RcptInfo::CERT, {}, fetchDataFile(EC256CertFile)}, {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(EC521PubKeyFile)}, + {libcdoc::RcptInfo::CERT, {}, fetchDataFile(EC521CertFile)}, {libcdoc::RcptInfo::PKEY, {}, {}, fetchDataFile(RSAPubKeyFile)}, {libcdoc::RcptInfo::SKEY, "AES", {}, libcdoc::fromHex(AESKey)} }; @@ -585,11 +588,14 @@ BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithCDoc2Key, DecryptFixture, * utf::depends_on("CDoc2KeyUsage/EncryptWithCDoc2Key") * utf::description("Decrypting a CDoc2 file with a key")) { - decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(ECPrivKeyFile), 0, false); - decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC256PrivKeyFile), 1, false); - decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC521PrivKeyFile), 2, false); - decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(RSAPrivKeyFile), 3, false); - decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), libcdoc::fromHex(AESKey), 4, true); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC384PrivKeyFile), 0, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC384PrivKeyFile), 1, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC256PrivKeyFile), 2, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC256PrivKeyFile), 3, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC521PrivKeyFile), 4, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(EC521PrivKeyFile), 5, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), fetchDataFile(RSAPrivKeyFile), 6, false); + decrypt({checkDataFile(sources[0])}, checkTargetFile(CONTAINER), tmpDataPath.string(), libcdoc::fromHex(AESKey), 7, true); } BOOST_AUTO_TEST_SUITE_END() @@ -599,13 +605,13 @@ BOOST_AUTO_TEST_SUITE(CDoc1ECKeySingle) BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithECKeyV1, EncryptFixture, * utf::description("Encrypting a file with EC key in CDoc1 format")) { - encryptV1({checkDataFile(sources[0])}, formTargetFile("ECKeyUsageV1.cdoc"), fetchDataFile(ECCertFile)); + encryptV1({checkDataFile(sources[0])}, formTargetFile("ECKeyUsageV1.cdoc"), fetchDataFile(EC384CertFile)); } BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithECKeyV1, DecryptFixture, * utf::depends_on("CDoc1ECKeySingle/EncryptWithECKeyV1") * utf::description("Decrypting a file in CDoc1 format with with EC private key")) { - decrypt({checkDataFile(sources[0])}, checkTargetFile("ECKeyUsageV1.cdoc"), tmpDataPath.string(), fetchDataFile(ECPrivKeyFile)); + decrypt({checkDataFile(sources[0])}, checkTargetFile("ECKeyUsageV1.cdoc"), tmpDataPath.string(), fetchDataFile(EC384PrivKeyFile)); } BOOST_AUTO_TEST_SUITE_END() @@ -613,13 +619,13 @@ BOOST_AUTO_TEST_SUITE(CDoc1ECKeyMulti) BOOST_FIXTURE_TEST_CASE_WITH_DECOR(EncryptWithECKeyV1Multi, EncryptFixture, * utf::description("Encrypting multiple files with EC key in CDoc1 format")) { - encryptV1({checkDataFile(sources[0]), checkDataFile(sources[1]), checkDataFile(sources[2])}, formTargetFile("ECKeyUsageV1Multi.cdoc"), fetchDataFile(ECCertFile)); + encryptV1({checkDataFile(sources[0]), checkDataFile(sources[1]), checkDataFile(sources[2])}, formTargetFile("ECKeyUsageV1Multi.cdoc"), fetchDataFile(EC384CertFile)); } BOOST_FIXTURE_TEST_CASE_WITH_DECOR(DecryptWithECKeyV1Multi, DecryptFixture, * utf::depends_on("CDoc1ECKeyMulti/EncryptWithECKeyV1Multi") * utf::description("Decrypting multiple files in CDoc1 format with with EC private key")) { - decrypt({checkDataFile(sources[0]), checkDataFile(sources[1]), checkDataFile(sources[2])}, checkTargetFile("ECKeyUsageV1Multi.cdoc"), tmpDataPath.string(), fetchDataFile(ECPrivKeyFile)); + decrypt({checkDataFile(sources[0]), checkDataFile(sources[1]), checkDataFile(sources[2])}, checkTargetFile("ECKeyUsageV1Multi.cdoc"), tmpDataPath.string(), fetchDataFile(EC384PrivKeyFile)); } BOOST_AUTO_TEST_SUITE_END() From 098ebe8482eceef1d8ab6604fd4b8b07b14566ee Mon Sep 17 00:00:00 2001 From: Lauris Kaplinski Date: Thu, 7 May 2026 10:39:16 +0300 Subject: [PATCH 50/50] Unify server and public key recipient constructors --- cdoc/CDocCipher.cpp | 11 +++--- cdoc/PKCS11Backend.cpp | 3 +- cdoc/PKCS11Backend.h | 3 +- cdoc/Recipient.cpp | 81 +++++++++++++++--------------------------- cdoc/Recipient.h | 34 +++++------------- 5 files changed, 45 insertions(+), 87 deletions(-) diff --git a/cdoc/CDocCipher.cpp b/cdoc/CDocCipher.cpp index f05cf52b..9f445895 100644 --- a/cdoc/CDocCipher.cpp +++ b/cdoc/CDocCipher.cpp @@ -267,7 +267,7 @@ fill_recipients_from_rcpt_info(ToolConf& conf, ToolCrypto& crypto, std::vector val; - libcdoc::Algorithm algo; ToolPKCS11* p11 = dynamic_cast(crypto.p11.get()); - int result = p11->getPublicKey(val, algo, rcpt.p11.slot, rcpt.secret, rcpt.p11.key_id, rcpt.p11.key_label); + int result = p11->getPublicKey(val, rcpt.p11.slot, rcpt.secret, rcpt.p11.key_id, rcpt.p11.key_label); if (result != libcdoc::OK) { LOG_ERROR("No such public key: {}", rcpt.p11.key_label); continue; } - LOG_DBG("Public key ({}): {}", (algo == libcdoc::Algorithm::RSA) ? "rsa" : "ecc", toHex(val)); + LOG_DBG("Public key: {}", toHex(val)); if (!conf.servers.empty()) { - key = libcdoc::Recipient::makeServer(std::move(label), val, conf.servers[0].ID); + key = libcdoc::Recipient::makePublicKey(std::move(label), val, conf.servers[0].ID); } else { key = libcdoc::Recipient::makePublicKey(std::move(label), val); } diff --git a/cdoc/PKCS11Backend.cpp b/cdoc/PKCS11Backend.cpp index c298d4a5..d69e7428 100644 --- a/cdoc/PKCS11Backend.cpp +++ b/cdoc/PKCS11Backend.cpp @@ -339,7 +339,7 @@ libcdoc::PKCS11Backend::getCertificate(std::vector& val, int slot, cons } libcdoc::result_t -libcdoc::PKCS11Backend::getPublicKey(std::vector& val, libcdoc::Algorithm& algorithm, int slot, const std::vector& pin, const std::vector& id, const std::string& label) +libcdoc::PKCS11Backend::getPublicKey(std::vector& val, int slot, const std::vector& pin, const std::vector& id, const std::string& label) { if(!d) return CRYPTO_ERROR; if (!d->session) { @@ -357,7 +357,6 @@ libcdoc::PKCS11Backend::getPublicKey(std::vector& val, libcdoc::Algorit } if (*((CK_KEY_TYPE *) v.data()) != CKK_EC) return libcdoc::NOT_IMPLEMENTED; - algorithm = Algorithm::ECC; v = d->attribute(d->session, handle, CKA_EC_PARAMS); if (v.empty()) { LOG_DBG("PKCS11: getValue CKA_EC_PARAMS error"); diff --git a/cdoc/PKCS11Backend.h b/cdoc/PKCS11Backend.h index 17993000..b0084ad8 100644 --- a/cdoc/PKCS11Backend.h +++ b/cdoc/PKCS11Backend.h @@ -119,14 +119,13 @@ struct CDOC_EXPORT PKCS11Backend : public CryptoBackend { * Get a public key value given slot, label and id. * Both key id and label have to match unless either one is empty. * @param val a destination container for value - * @param algorithm the output parameter for public key algorithm * @param slot the slot to use * @param pin the pin code or empty if public * @param id public key id or empty vector * @param label public key label or empty vector * @return error code or OK */ - result_t getPublicKey(std::vector& val, libcdoc::Algorithm& algorithm, int slot, const std::vector& pin, const std::vector& id, const std::string& label); + result_t getPublicKey(std::vector& val, int slot, const std::vector& pin, const std::vector& id, const std::string& label); /** * @brief loads key for encryption/decryption diff --git a/cdoc/Recipient.cpp b/cdoc/Recipient.cpp index bc9148c0..1077f5a7 100644 --- a/cdoc/Recipient.cpp +++ b/cdoc/Recipient.cpp @@ -83,31 +83,40 @@ EVP_PKEY_get_nid(EVP_PKEY *pkey) } Recipient -Recipient::makePublicKey(std::string label, std::vector public_key) { +Recipient::makePublicKey(std::string label, std::vector public_key, std::string server_id) { auto pkey = Crypto::fromPublicKeyDer(public_key); - if (!pkey) { + if (!pkey) return {}; - } + Recipient rcpt; int id = EVP_PKEY_get_id(pkey.get()); if (id == EVP_PKEY_RSA) { - return makeRSA(label, public_key); + rcpt = makeRSA(label, public_key); } else { int nid = EVP_PKEY_get_nid(pkey.get()); switch(nid) { - case NID_secp384r1: - return makeECC(label, public_key, Curve::SECP_384_R1); - case NID_X9_62_prime256v1: - return makeECC(label, public_key, Curve::SECP_256_R1); - case NID_secp521r1: - return makeECC(label, public_key, Curve::SECP_521_R1); - default: - return {}; + case NID_secp384r1: + rcpt = makeECC(label, public_key, Curve::SECP_384_R1); + break; + case NID_X9_62_prime256v1: + rcpt = makeECC(label, public_key, Curve::SECP_256_R1); + break; + case NID_secp521r1: + rcpt = makeECC(label, public_key, Curve::SECP_521_R1); + break; + default: + return rcpt; } } + if (!server_id.empty()) { + rcpt.server_id = std::move(server_id); + const auto six_months_from_now = std::chrono::system_clock::now() + std::chrono::months(6); + rcpt.expiry_ts = std::chrono::system_clock::to_time_t(six_months_from_now); + } + return rcpt; } Recipient -Recipient::makePublicKey(const Lock &lock) +Recipient::makePublicKey(const Lock &lock, std::string server_id) { auto params = Lock::parseLabel(lock.label); Recipient rcpt(Type::PUBLIC_KEY); @@ -127,11 +136,12 @@ Recipient::makePublicKey(const Lock &lock) params.erase(CDoc2::Label::EXPIRY); } rcpt.lbl_parts = std::move(params); + rcpt.server_id = std::move(server_id); return rcpt; } Recipient -Recipient::makeCertificate(std::string label, std::vector cert) +Recipient::makeCertificate(std::string label, std::vector cert, std::string server_id) { Certificate x509(cert); if (!x509) @@ -156,45 +166,12 @@ Recipient::makeCertificate(std::string label, std::vector cert) {std::string(CDoc2::Label::CERT_SHA1), toHex(x509.getDigest())}, }; } - return rcpt; -} - -Recipient -Recipient::makeServer(std::string label, std::vector public_key, std::string server_id) { - Recipient rcpt; - auto pkey = Crypto::fromPublicKeyDer(public_key); - if (!pkey) { - rcpt = makeCertificate(std::move(label), std::move(public_key)); - } else { - int id = EVP_PKEY_get_id(pkey.get()); - if (id == EVP_PKEY_RSA) { - rcpt = makeRSA(label, public_key); - } else { - int nid = EVP_PKEY_get_nid(pkey.get()); - switch(nid) { - case NID_secp384r1: - rcpt = makeECC(label, public_key, Curve::SECP_384_R1); - case NID_X9_62_prime256v1: - rcpt = makeECC(label, public_key, Curve::SECP_256_R1); - case NID_secp521r1: - rcpt = makeECC(label, public_key, Curve::SECP_521_R1); - default: - return rcpt; - } - } + if (!server_id.empty()) { + rcpt.server_id = std::move(server_id); + const auto six_months_from_now = std::chrono::system_clock::now() + std::chrono::months(6); + const auto expiry_ts = std::chrono::system_clock::to_time_t(six_months_from_now); + rcpt.expiry_ts = std::min(rcpt.expiry_ts, uint64_t(expiry_ts)); } - rcpt.server_id = std::move(server_id); - const auto six_months_from_now = std::chrono::system_clock::now() + std::chrono::months(6); - const auto expiry_ts = std::chrono::system_clock::to_time_t(six_months_from_now); - rcpt.expiry_ts = (rcpt.expiry_ts) ? std::min(rcpt.expiry_ts, uint64_t(expiry_ts)) : uint64_t(expiry_ts); - return rcpt; -} - -Recipient -Recipient::makeServer(const Lock &lock, std::string server_id) -{ - auto rcpt = makePublicKey(lock); - rcpt.server_id = std::move(server_id); return rcpt; } diff --git a/cdoc/Recipient.h b/cdoc/Recipient.h index 82a9d564..31798bcd 100644 --- a/cdoc/Recipient.h +++ b/cdoc/Recipient.h @@ -142,8 +142,9 @@ struct CDOC_EXPORT Recipient { * @return true if the public keys are identical */ bool isTheSameRecipient(const Recipient &other) const; + /** - * @brief A convenience method to check whether a recipient is public key based and has the given keys. + * @brief A convenience method to check whether a recipient is public key based and has the given key. * @param public_key a public key to test * @return true if the public keys are identical */ @@ -167,16 +168,19 @@ struct CDOC_EXPORT Recipient { * * @param label the label text * @param public_key the public key value + * @param server_id the keyserver id or empty string for offline capsule * @return a new Recipient structure */ - static Recipient makePublicKey(std::string label, std::vector public_key); + static Recipient makePublicKey(std::string label, std::vector public_key, std::string server_id = {}); /** * @brief Create a new public key based Recipient * @param lock Lock to derive parameters from + * @param server_id the keyserver id or empty string for offline capsule * @return a new Recipient structure */ - static Recipient makePublicKey(const Lock &lock); + static Recipient makePublicKey(const Lock &lock, std::string server_id = {}); + /** * @brief Create a new certificate based Recipient * @@ -184,30 +188,10 @@ struct CDOC_EXPORT Recipient { * * @param label the label text * @param cert the certificate value (der-encoded) + * @param server_id the keyserver id or empty string for offline capsule * @return a new Recipient structure */ - static Recipient makeCertificate(std::string label, std::vector cert); - - /** - * @brief Create a new capsule server based Recipient - * - * If the label is empty, a machine-readable label text (public key version) is automatically generated according to CDoc2 specification. - * - * @param label the label text - * @param public_key Recipient's public key or certificate (der-encoded) - * @param server_id the keyserver id - * @return a new Recipient structure - */ - static Recipient makeServer(std::string label, std::vector public_key, std::string server_id); - - /** - * @brief Create a new capsule server based Recipient - * - * @param lock Lock to derive parameters from - * @param server_id the keyserver id - * @return a new Recipient structure - */ - static Recipient makeServer(const Lock &lock, std::string server_id); + static Recipient makeCertificate(std::string label, std::vector cert, std::string server_id = {}); #ifdef HAS_KEYSHARES /**