diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b3b4a49e..27f5a212 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: container: ubuntu:${{ matrix.container }} strategy: matrix: - container: ['22.04', '24.04', '25.04'] + container: ['22.04', '24.04', '25.10'] arch: ['amd64', 'arm64'] env: DEBIAN_FRONTEND: noninteractive @@ -24,7 +24,7 @@ jobs: - name: Install dependencies run: apt update -qq && apt install --no-install-recommends -y lsb-release build-essential devscripts debhelper lintian pkg-config ${UBUNTU_DEPS} doxygen swig openjdk-17-jdk-headless libpython3-dev python3-setuptools libboost-test-dev - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup changelog run: | export VERSION=$(grep project CMakeLists.txt | egrep -o "([0-9]{1,}\.)+[0-9]{1,}") @@ -37,7 +37,7 @@ jobs: - name: Lintian run: lintian *.deb; - name: Archive artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ubuntu_${{ matrix.container }}_${{ matrix.arch }} path: libcdoc*.* @@ -55,7 +55,7 @@ jobs: triplet: x64-android steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Expose Android NDK env shell: bash run: | @@ -65,7 +65,7 @@ jobs: uses: actions/cache@v4 with: path: ${{ github.workspace }}/vcpkg_cache - key: vcpkg-${{ matrix.target }}-${{ hashFiles('.github/workflows/*', 'vcpkg.json', 'CMakeLists.txt', '**/CMakeLists.txt', 'CMakePresets.json') }} + key: vcpkg-${{ matrix.target }}-${{ hashFiles('vcpkg.json') }} - name: Prepare vcpkg uses: lukka/run-vcpkg@v11 with: @@ -80,7 +80,7 @@ jobs: cmake --build --preset ${{ matrix.target }} cmake --build --preset ${{ matrix.target }} --target install/strip - name: Archive artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.target }} path: | @@ -100,7 +100,7 @@ jobs: DEST: ${{ github.workspace }}/${{ matrix.target }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install dependencies run: | brew update @@ -110,7 +110,7 @@ jobs: uses: actions/cache@v4 with: path: ${{ github.workspace }}/vcpkg_cache - key: vcpkg-${{ matrix.target }}-${{ hashFiles('.github/workflows/*', 'vcpkg.json', 'CMakeLists.txt', '**/CMakeLists.txt', 'CMakePresets.json') }} + key: vcpkg-${{ matrix.target }}-${{ hashFiles('vcpkg.json') }} - name: Prepare vcpkg if: matrix.target != 'macos' uses: lukka/run-vcpkg@v11 @@ -130,7 +130,7 @@ jobs: - name: Install run: cmake --build --preset ${{ matrix.target }} --target install/strip - name: Archive artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.target }} path: ${{ env.DEST }} @@ -147,12 +147,12 @@ jobs: VCPKG_DEFAULT_TRIPLET: ${{ matrix.platform }}-windows-static-md steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Cache vcpkg uses: actions/cache@v4 with: path: ${{ github.workspace }}/vcpkg_cache - key: vcpkg-${{ matrix.image }}-${{ matrix.platform }}-${{ hashFiles('.github/workflows/*', 'vcpkg.json', 'CMakeLists.txt', '**/CMakeLists.txt', 'CMakePresets.json') }} + key: vcpkg-${{ matrix.image }}-${{ matrix.platform }}-${{ hashFiles('vcpkg.json') }} - name: Prepare vcpkg uses: lukka/run-vcpkg@v11 with: @@ -181,7 +181,7 @@ jobs: ctest -V -C RelWithDebInfo --test-dir build cmake --install build --config RelWithDebInfo --prefix ${{ env.DEST }} - name: Archive artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ matrix.image }}_${{ matrix.platform }} path: ${{ env.DEST }} @@ -193,7 +193,7 @@ jobs: contents: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install dependencies run: sudo apt update -qq && sudo apt install --no-install-recommends -y doxygen ${UBUNTU_DEPS} - name: Build docs @@ -215,7 +215,7 @@ jobs: PROJECTNAME: ${{ github.repository }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install dependencies run: sudo apt update -qq && sudo apt install --no-install-recommends -y curl ca-certificates ${UBUNTU_DEPS} - name: Download Coverity Build Tool @@ -250,7 +250,7 @@ jobs: security-events: write steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Install dependencies run: sudo apt update -qq && sudo apt install --no-install-recommends -y ${UBUNTU_DEPS} - name: Initialize CodeQL diff --git a/cdoc/CDoc1Reader.cpp b/cdoc/CDoc1Reader.cpp index ddcb6f7a..6f272220 100644 --- a/cdoc/CDoc1Reader.cpp +++ b/cdoc/CDoc1Reader.cpp @@ -384,6 +384,7 @@ result_t CDoc1Reader::decryptData(const std::vector& fmk, std::string& return result; } + std::vector b64; XMLReader reader(d->dsrc, false); int skipKeyInfo = 0; while (reader.read()) { @@ -397,26 +398,31 @@ result_t CDoc1Reader::decryptData(const std::vector& fmk, std::string& // EncryptedData/CipherData/CipherValue else if(reader.isElement("CipherValue")) { - data = libcdoc::Crypto::decrypt(d->method, fmk, reader.readBase64()); + b64 = reader.readBase64(); break; } } - if(data.empty()) { + if(b64.empty()) { + setLastError("Failed to decode base64 data"); + return libcdoc::IO_ERROR; + } + VectorSource src(b64); + libcdoc::DecryptionSource dec(src, d->method, fmk); + if(dec.isError()) { setLastError("Failed to decrypt data, verify if FMK is correct"); - return libcdoc::CRYPTO_ERROR; + return CRYPTO_ERROR; } + libcdoc::VectorConsumer out(data); setLastError({}); if (d->mime == MIME_ZLIB) { - libcdoc::VectorSource vsrc(data); - libcdoc::ZSource zsrc(&vsrc); - std::vector tmp; - libcdoc::VectorConsumer vcons(tmp); - vcons.writeAll(zsrc); - data = std::move(tmp); + libcdoc::ZSource zsrc(&dec); + out.writeAll(zsrc); mime = d->properties["OriginalMimeType"]; } - else + else { mime = d->mime; - return libcdoc::OK; + out.writeAll(dec); + } + return dec.close(); } diff --git a/cdoc/CDoc2Reader.cpp b/cdoc/CDoc2Reader.cpp index 80ad895e..ac9e891d 100644 --- a/cdoc/CDoc2Reader.cpp +++ b/cdoc/CDoc2Reader.cpp @@ -83,11 +83,9 @@ struct CDoc2Reader::Private { std::vector locks; - std::unique_ptr cipher; - std::unique_ptr tgs; + std::unique_ptr dec; std::unique_ptr zsrc; std::unique_ptr tar; - }; CDoc2Reader::~CDoc2Reader() @@ -389,29 +387,19 @@ CDoc2Reader::beginDecryption(const std::vector& fmk) } priv->_at_nonce = false; std::vector cek = libcdoc::Crypto::expand(fmk, std::vector(libcdoc::CDoc2::CEK.cbegin(), libcdoc::CDoc2::CEK.cend())); - std::vector nonce(libcdoc::CDoc2::NONCE_LEN); - if (priv->_src->read(nonce.data(), libcdoc::CDoc2::NONCE_LEN) != libcdoc::CDoc2::NONCE_LEN) { - setLastError("Error reading nonce"); - LOG_ERROR("{}", last_error); - return libcdoc::IO_ERROR; - } - LOG_TRACE_KEY("cek: {}", cek); - LOG_TRACE_KEY("nonce: {}", nonce); - priv->cipher = std::make_unique(EVP_chacha20_poly1305(), cek, nonce, false); + 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()); aad.insert(aad.end(), priv->header_data.cbegin(), priv->header_data.cend()); aad.insert(aad.end(), priv->headerHMAC.cbegin(), priv->headerHMAC.cend()); - if(!priv->cipher->updateAAD(aad)) { + if(priv->dec->updateAAD(aad) != OK) { setLastError("Wrong decryption key (FMK)"); LOG_ERROR("{}", last_error); return libcdoc::WRONG_KEY; } - priv->tgs = std::make_unique(priv->_src, false, 16); - libcdoc::CipherSource *csrc = new libcdoc::CipherSource(priv->tgs.get(), false, priv->cipher.get()); - priv->zsrc = std::make_unique(csrc, true); + priv->zsrc = std::make_unique(priv->dec.get(), false); priv->tar = std::make_unique(priv->zsrc.get(), false); return libcdoc::OK; @@ -455,21 +443,15 @@ CDoc2Reader::finishDecryption() LOG_WARN("{}", last_error); } - LOG_TRACE_KEY("tag: {}", priv->tgs->tag); - - priv->cipher->setTag(priv->tgs->tag); - if (!priv->cipher->result()) { - setLastError("Stream tag is invalid"); - LOG_ERROR("{}", last_error); - return HASH_MISMATCH; - } setLastError({}); - priv->tgs.reset(); priv->zsrc.reset(); priv->tar.reset(); - priv->cipher->clear(); - priv->cipher.reset(); - return OK; + auto rv = priv->dec->close(); + priv->dec.reset(); + if (rv != OK) { + setLastError("Crypto payload integrity check failed"); + } + return rv; } CDoc2Reader::CDoc2Reader(libcdoc::DataSource *src, bool take_ownership) diff --git a/cdoc/Crypto.cpp b/cdoc/Crypto.cpp index e09c0e86..9040d6ed 100644 --- a/cdoc/Crypto.cpp +++ b/cdoc/Crypto.cpp @@ -54,60 +54,6 @@ constexpr auto d2i(const std::vector &data, Args&&... args) noexcept return make_unique_ptr(F(std::forward(args)..., &p, long(data.size())), Free); } - -Crypto::Cipher::Cipher(const EVP_CIPHER *cipher, const std::vector &key, const std::vector &iv, bool encrypt) - : ctx(EVP_CIPHER_CTX_new()) -{ - EVP_CIPHER_CTX_set_flags(ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); - EVP_CipherInit_ex(ctx, cipher, nullptr, key.data(), iv.empty() ? nullptr : iv.data(), int(encrypt)); -} - -Crypto::Cipher::~Cipher() -{ - EVP_CIPHER_CTX_free(ctx); -} - -bool Crypto::Cipher::updateAAD(const std::vector &data) const -{ - int len = 0; - return !SSL_FAILED(EVP_CipherUpdate(ctx, nullptr, &len, data.data(), int(data.size())), "EVP_CipherUpdate"); -} - -bool -Crypto::Cipher::update(uint8_t *data, int size) const -{ - int len = 0; - return !SSL_FAILED(EVP_CipherUpdate(ctx, data, &len, data, size), "EVP_CipherUpdate"); -} - -bool Crypto::Cipher::result() const -{ - std::vector result(EVP_CIPHER_CTX_block_size(ctx), 0); - int len = int(result.size()); - if(SSL_FAILED(EVP_CipherFinal(ctx, result.data(), &len), "EVP_CipherFinal")) - return false; - if(result.size() != len) - result.resize(len); - return true; -} - -bool Crypto::Cipher::setTag(const std::vector &data) const -{ - return !SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, int(data.size()), (void *) data.data()), "EVP_CIPHER_CTX_ctrl"); -} - -int -Crypto::Cipher::blockSize() const -{ - return EVP_CIPHER_CTX_get_block_size(ctx); -} - -void -Crypto::Cipher::clear() -{ - EVP_CIPHER_CTX_reset(ctx); -} - std::vector Crypto::AESWrap(const std::vector &key, const std::vector &data, bool encrypt) { AES_KEY aes; @@ -141,61 +87,43 @@ const EVP_CIPHER *Crypto::cipher(const std::string &algo) std::vector Crypto::concatKDF(const std::string &hashAlg, uint32_t keyDataLen, const std::vector &z, const std::vector &otherInfo) { - std::vector key; - uint32_t hashLen = SHA384_DIGEST_LENGTH; - if(hashAlg == SHA256_MTH) hashLen = SHA256_DIGEST_LENGTH; - else if(hashAlg == SHA384_MTH) hashLen = SHA384_DIGEST_LENGTH; - else if(hashAlg == SHA512_MTH) hashLen = SHA512_DIGEST_LENGTH; - else return key; - - SHA256_CTX sha256; - SHA512_CTX sha512; - std::vector hash(hashLen, 0); - uint8_t intToFourBytes[4]; + std::vector key; + const EVP_MD *md {}; + if(hashAlg == SHA256_MTH) md = EVP_sha256(); + else if(hashAlg == SHA384_MTH) md = EVP_sha384(); + else if(hashAlg == SHA512_MTH) md = EVP_sha512(); + else { + LOG_WARN("Usnupported hash algo {}", hashAlg); + return key; + } + uint32_t hashLen = EVP_MD_get_size(md); uint32_t reps = keyDataLen / hashLen; if (keyDataLen % hashLen > 0) reps++; - for(uint32_t i = 1; i <= reps; i++) - { - intToFourBytes[0] = uint8_t(i >> 24); - intToFourBytes[1] = uint8_t(i >> 16); - intToFourBytes[2] = uint8_t(i >> 8); - intToFourBytes[3] = uint8_t(i >> 0); - switch(hashLen) - { - case SHA256_DIGEST_LENGTH: - if (SSL_FAILED(SHA256_Init(&sha256), "SHA256_Init") || - SSL_FAILED(SHA256_Update(&sha256, intToFourBytes, 4), "SHA256_Update") || - SSL_FAILED(SHA256_Update(&sha256, z.data(), z.size()), "SHA256_Update") || - SSL_FAILED(SHA256_Update(&sha256, otherInfo.data(), otherInfo.size()), "SHA256_Update") || - SSL_FAILED(SHA256_Final(hash.data(), &sha256), "SHA256_Final")) - return {}; - break; - case SHA384_DIGEST_LENGTH: - if (SSL_FAILED(SHA384_Init(&sha512), "SHA384_Init") || - SSL_FAILED(SHA384_Update(&sha512, intToFourBytes, 4), "SHA384_Update") || - SSL_FAILED(SHA384_Update(&sha512, z.data(), z.size()), "SHA384_Update") || - SSL_FAILED(SHA384_Update(&sha512, otherInfo.data(), otherInfo.size()), "SHA384_Update") || - SSL_FAILED(SHA384_Final(hash.data(), &sha512), "SHA384_Final")) - return {}; - break; - case SHA512_DIGEST_LENGTH: - if (SSL_FAILED(SHA512_Init(&sha512), "SHA512_Init") || - SSL_FAILED(SHA512_Update(&sha512, intToFourBytes, 4), "SHA512_Update") || - SSL_FAILED(SHA512_Update(&sha512, otherInfo.data(), otherInfo.size()), "SHA512_Update") || - SSL_FAILED(SHA512_Final(hash.data(), &sha512), "SHA512_Update")) - return {}; - break; - default: - LOG_WARN("Usnupported hash length {}", hashLen); - return key; - } - key.insert(key.cend(), hash.cbegin(), hash.cend()); - } - key.resize(size_t(keyDataLen)); - return key; + auto ctx = make_unique_ptr(EVP_MD_CTX_new()); + if(!ctx) + { + LOG_SSL_ERROR("EVP_MD_CTX_new"); + return key; + } + + std::vector hash(hashLen, 0); + for(uint32_t i = 1; i <= reps; i++) + { + uint8_t intToFourBytes[4] { uint8_t(i >> 24), uint8_t(i >> 16), uint8_t(i >> 8), uint8_t(i >> 0) }; + unsigned int size = hashLen; + if (SSL_FAILED(EVP_DigestInit(ctx.get(), md), "EVP_DigestInit") || + SSL_FAILED(EVP_DigestUpdate(ctx.get(), intToFourBytes, 4), "EVP_DigestUpdate") || + SSL_FAILED(EVP_DigestUpdate(ctx.get(), z.data(), z.size()), "EVP_DigestUpdate") || + SSL_FAILED(EVP_DigestUpdate(ctx.get(), otherInfo.data(), otherInfo.size()), "EVP_DigestUpdate") || + SSL_FAILED(EVP_DigestFinal(ctx.get(), hash.data(), &size), "EVP_DigestFinal")) + return {}; + key.insert(key.cend(), hash.cbegin(), hash.cend()); + } + key.resize(size_t(keyDataLen)); + return key; } std::vector Crypto::concatKDF(const std::string &hashAlg, uint32_t keyDataLen, const std::vector &z, @@ -234,56 +162,6 @@ Crypto::encrypt(EVP_PKEY *pub, int padding, const std::vector &data) return result; } -std::vector Crypto::decrypt(const std::string &method, const std::vector &key, const std::vector &data) -{ - const EVP_CIPHER *cipher = Crypto::cipher(method); - size_t dataSize = data.size(); - std::vector iv(data.cbegin(), data.cbegin() + EVP_CIPHER_iv_length(cipher)); - if(dataSize < iv.size()) - return {}; - dataSize -= iv.size(); - - LOG_TRACE_KEY("iv {}", iv); - LOG_TRACE_KEY("transport {}", key); - - auto ctx = make_unique_ptr(EVP_CIPHER_CTX_new()); - if (!ctx) - { - LOG_SSL_ERROR("EVP_CIPHER_CTX_new"); - return {}; - } - - if (SSL_FAILED(EVP_CipherInit(ctx.get(), cipher, key.data(), iv.data(), 0), "EVP_CipherInit")) - { - return {}; - } - - if (EVP_CIPHER_mode(cipher) == EVP_CIPH_GCM_MODE) - { - std::vector tag(data.cend() - 16, data.cend()); - if(dataSize < tag.size()) - return {}; - EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, int(tag.size()), tag.data()); - dataSize -= tag.size(); - LOG_DBG("GCM TAG {}", toHex(tag)); - } - - int size = 0; - std::vector result(dataSize + size_t(EVP_CIPHER_CTX_block_size(ctx.get())), 0); - if (SSL_FAILED(EVP_CipherUpdate(ctx.get(), result.data(), &size, &data[iv.size()], int(dataSize)), "EVP_CipherUpdate")) - { - return {}; - } - - int size2 = 0; - if (SSL_FAILED(EVP_CipherFinal(ctx.get(), result.data() + size, &size2), "EVP_CipherFinal")) - { - return {}; - } - result.resize(size_t(size + size2)); - return result; -} - std::vector Crypto::decodeBase64(const uint8_t *data) { std::vector result; @@ -608,14 +486,137 @@ EncryptionConsumer::close() if(SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, int(tag.size()), tag.data()), "EVP_CIPHER_CTX_ctrl")) return CRYPTO_ERROR; LOG_DBG("tag: {}", toHex(tag)); - return dst.write(tag.data(), tag.size()); + if (dst.write(tag.data(), tag.size()) != tag.size()) + return IO_ERROR; } - if(EVP_CIPHER_CTX_flags(ctx.get()) & EVP_CIPH_FLAG_AEAD_CIPHER) + else if(EVP_CIPHER_CTX_flags(ctx.get()) & EVP_CIPH_FLAG_AEAD_CIPHER) { if(SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_GET_TAG, int(tag.size()), tag.data()), "EVP_CIPHER_CTX_ctrl")) return CRYPTO_ERROR; LOG_DBG("tag: {}", toHex(tag)); - return dst.write(tag.data(), tag.size()); + if (dst.write(tag.data(), tag.size()) != tag.size()) + return IO_ERROR; + } + return OK; +} + +DecryptionSource::DecryptionSource(DataSource &src, const std::string &method, const std::vector &key, size_t ivLen) + : DecryptionSource(src, Crypto::cipher(method), key, ivLen) +{} + +DecryptionSource::DecryptionSource(DataSource &src, const EVP_CIPHER *cipher, const std::vector &key, size_t ivLen) + : ctx{EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free} + , src(src) +{ + EVP_CIPHER_CTX_set_flags(ctx.get(), EVP_CIPHER_CTX_FLAG_WRAP_ALLOW); + if (ivLen == 0) + ivLen = EVP_CIPHER_iv_length(cipher); + std::array iv {}; + if(auto rv = src.read(iv.data(), ivLen); size_t(rv) != ivLen) + error = rv < 0 ? rv : IO_ERROR; + else if(SSL_FAILED(EVP_CipherInit_ex(ctx.get(), cipher, nullptr, key.data(), iv.data(), 0), "EVP_CipherInit_ex")) + error = CRYPTO_ERROR; + else if(rv = src.read(tag.data(), tag.size()); size_t(rv) != tag.size()) + error = rv < 0 ? rv : IO_ERROR; + LOG_TRACE_KEY("IV: {}", iv); +} + +result_t DecryptionSource::updateAAD(const std::vector &data) +{ + if (error != OK) + return error; + int len = 0; + if(SSL_FAILED(EVP_CipherUpdate(ctx.get(), nullptr, &len, data.data(), int(data.size())), "EVP_CipherUpdate")) + return CRYPTO_ERROR; + return OK; +} + +result_t DecryptionSource::read(unsigned char *dst, size_t size) +{ + if (error != OK) + return error; + if (!dst || size == 0) + return OK; + if(size <= tag.size()) + { + decltype(tag) tmp; + auto rv = src.read(tmp.data(), size); + if (rv <= 0) { + return rv; + } + size = static_cast(rv); + + // Construct new tag value + std::move_backward(tmp.begin(), tmp.begin() + size, tmp.end()); // Move existing tag data to the end of tmp + std::copy(tag.begin() + size, tag.end(), tmp.begin()); // Fill the beginning of tmp with remaining tag data + + // Copy data to dst and update tag + std::copy_n(tag.begin(), size, dst); + tag = tmp; + + if (int out = 0; + SSL_FAILED(EVP_CipherUpdate(ctx.get(), dst, &out, dst, size), "EVP_CipherUpdate") || + size != out) { + return error = CRYPTO_ERROR; + } + return size; + } + + auto rv = src.read(dst + tag.size(), size - tag.size()); + if (rv <= 0) { + return rv; + } + auto nread = static_cast(rv); + + // Prepend tag data to the beginning of dst + std::copy(tag.begin(), tag.end(), dst); + + // Handle case where less data was read than requested + if (nread < size - tag.size()) { + // Copy tag data from the end of dst to the tag and adjust size + std::copy_n(std::next(dst, nread), tag.size(), tag.begin()); + size = nread; + } else if (rv = src.read(tag.data(), tag.size()); rv < 0) { + return rv; + } else if (auto tagSize = static_cast(rv); tagSize < tag.size()) { + // Handle case where less tag data was read than expected + // Move read tag data to the end of tag and fill the beginning with data from dst + std::move_backward(tag.begin(), tag.begin() + tagSize, tag.end()); + size_t more = tag.size() - tagSize; + std::copy_n(std::next(dst, size - more), more, tag.data()); + size -= more; + } + + if (int out = 0; + SSL_FAILED(EVP_CipherUpdate(ctx.get(), dst, &out, dst, size), "EVP_CipherUpdate") || + size != out) { + return error = CRYPTO_ERROR; + } + return size; +} + +result_t DecryptionSource::close() +{ + if (error != OK) + return error; + + if (EVP_CIPHER_CTX_mode(ctx.get()) == EVP_CIPH_GCM_MODE) { + LOG_DBG("tag: {}", toHex(tag)); + if (SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, int(tag.size()), tag.data()), "EVP_CIPHER_CTX_ctrl")) { + return error = CRYPTO_ERROR; + } } + else if(EVP_CIPHER_CTX_flags(ctx.get()) & EVP_CIPH_FLAG_AEAD_CIPHER) + { + LOG_DBG("tag: {}", toHex(tag)); + if (SSL_FAILED(EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_AEAD_SET_TAG, int(tag.size()), tag.data()), "EVP_CIPHER_CTX_ctrl")) { + return error = CRYPTO_ERROR; + } + } + + 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 OK; } diff --git a/cdoc/Crypto.h b/cdoc/Crypto.h index fc69ce27..5889a9c2 100644 --- a/cdoc/Crypto.h +++ b/cdoc/Crypto.h @@ -22,6 +22,8 @@ #include "utils/memory.h" +#include + using EVP_CIPHER_CTX = struct evp_cipher_ctx_st; using EVP_CIPHER = struct evp_cipher_st; using EVP_PKEY = struct evp_pkey_st; @@ -37,19 +39,6 @@ class Crypto public: using EVP_PKEY_ptr = unique_free_t; - struct Cipher { - EVP_CIPHER_CTX *ctx; - Cipher(const EVP_CIPHER *cipher, const std::vector &key, const std::vector &iv, bool encrypt = true); - ~Cipher(); - bool updateAAD(const std::vector &data) const; - bool update(uint8_t *data, int size) const; - bool result() const; - static constexpr int tagLen() { return 16; } - bool setTag(const std::vector &data) const; - int blockSize() const; - void clear(); - }; - static constexpr std::string_view KWAES128_MTH = "http://www.w3.org/2001/04/xmlenc#kw-aes128"; static constexpr std::string_view KWAES192_MTH = "http://www.w3.org/2001/04/xmlenc#kw-aes192"; static constexpr std::string_view KWAES256_MTH = "http://www.w3.org/2001/04/xmlenc#kw-aes256"; @@ -81,7 +70,6 @@ class Crypto static std::vector concatKDF(const std::string &hashAlg, uint32_t keyDataLen, const std::vector &z, const std::vector &otherInfo); static std::vector concatKDF(const std::string &hashAlg, uint32_t keyDataLen, const std::vector &z, const std::vector &AlgorithmID, const std::vector &PartyUInfo, const std::vector &PartyVInfo); - static std::vector decrypt(const std::string &method, const std::vector &key, const std::vector &data); static std::vector encrypt(EVP_PKEY *pub, int padding, const std::vector &data); static std::vector decodeBase64(const uint8_t *data); static std::vector deriveSharedSecret(EVP_PKEY *pkey, EVP_PKEY *peerPKey); @@ -129,7 +117,7 @@ struct EncryptionConsumer final : public DataConsumer { result_t write(const uint8_t *src, size_t size) final; result_t writeAAD(const std::vector &data); result_t close() final; - bool isError() final { return error != OK; } + bool isError() final { return error != OK || dst.isError(); } private: unique_free_t ctx; @@ -138,4 +126,22 @@ struct EncryptionConsumer final : public DataConsumer { std::vector buf; }; +struct DecryptionSource final : public DataSource { + DecryptionSource(DataSource &src, const std::string &method, const std::vector &key, size_t ivLen = 0); + DecryptionSource(DataSource &src, const EVP_CIPHER *cipher, const std::vector &key, size_t ivLen = 0); + CDOC_DISABLE_MOVE_COPY(DecryptionSource) + + result_t read(unsigned char* dst, size_t size) final; + result_t updateAAD(const std::vector& data); + result_t close(); + bool isError() final { return error != OK || src.isError(); } + bool isEof() final { return src.isEof(); } + +private: + unique_free_t ctx; + DataSource &src; + result_t error = OK; + std::array tag {}; +}; + }; // namespace libcdoc diff --git a/cdoc/Io.h b/cdoc/Io.h index a1c76de5..c7d3d81a 100644 --- a/cdoc/Io.h +++ b/cdoc/Io.h @@ -21,6 +21,7 @@ #include +#include #include #include @@ -311,7 +312,7 @@ struct CDOC_EXPORT VectorSource : public DataSource { result_t read(uint8_t *dst, size_t size) override { size = std::min(size, _data.size() - _ptr); - std::copy(_data.cbegin() + _ptr, _data.cbegin() + _ptr + size, dst); + std::copy_n(_data.cbegin() + _ptr, size, dst); _ptr += size; return size; } diff --git a/cdoc/Utils.h b/cdoc/Utils.h index 112c2161..872ce00b 100644 --- a/cdoc/Utils.h +++ b/cdoc/Utils.h @@ -127,51 +127,4 @@ std::string urlDecode(const std::string &src); } // namespace libcdoc -// A source implementation that always keeps last 16 bytes in tag - -struct TaggedSource : public libcdoc::DataSource { - std::vector tag; - libcdoc::DataSource *_src; - bool _owned; - - TaggedSource(libcdoc::DataSource *src, bool take_ownership, size_t tag_size) : tag(tag_size), _src(src), _owned(take_ownership) { - tag.resize(tag.size()); - _src->read(tag.data(), tag.size()); - } - ~TaggedSource() { - if (_owned) delete(_src); - } - - libcdoc::result_t seek(size_t pos) override final { - if (!_src->seek(pos)) return libcdoc::INPUT_STREAM_ERROR; - if (_src->read(tag.data(), tag.size()) != tag.size()) return libcdoc::INPUT_STREAM_ERROR; - return libcdoc::OK; - } - - libcdoc::result_t read(uint8_t *dst, size_t size) override final { - std::vector t(tag.size()); - uint8_t *tmp = t.data(); - size_t nread = _src->read(dst, size); - if (nread >= tag.size()) { - std::copy(dst + nread - tag.size(), dst + nread, tmp); - std::copy_backward(dst, dst + nread - tag.size(), dst + nread); - std::copy(tag.cbegin(), tag.cend(), dst); - std::copy(tmp, tmp + tag.size(), tag.begin()); - } else { - std::copy(dst, dst + nread, tmp); - std::copy(tag.cbegin(), tag.cbegin() + nread, dst); - std::copy(tag.cbegin() + nread, tag.cend(), tag.begin()); - std::copy(tmp, tmp + nread, tag.end() - nread); - } - return nread; - } - - virtual bool isError() override final { - return _src->isError(); - } - virtual bool isEof() override final { - return _src->isEof(); - } -}; - #endif // UTILS_H diff --git a/cdoc/ZStream.h b/cdoc/ZStream.h index b69fc6ee..163a5752 100644 --- a/cdoc/ZStream.h +++ b/cdoc/ZStream.h @@ -28,30 +28,6 @@ namespace libcdoc { -struct CipherSource : public ChainedSource { - bool _fail = false; - libcdoc::Crypto::Cipher *_cipher; - uint32_t _block_size; - CipherSource(DataSource *src, bool take_ownership, libcdoc::Crypto::Cipher *cipher) - : ChainedSource(src, take_ownership), _cipher(cipher), _block_size(cipher->blockSize()) {} - - libcdoc::result_t read(uint8_t *dst, size_t size) override final { - if (_fail) return INPUT_ERROR; - size_t n_read = _src->read(dst, _block_size * (size / _block_size)); - if (n_read) { - if((n_read % _block_size) || !_cipher->update(dst, n_read)) { - _fail = true; - return INPUT_ERROR; - } - } - return n_read; - } - - virtual bool isError() override final { - return _fail || ChainedSource::isError(); - }; -}; - struct ZConsumer : public ChainedConsumer { static constexpr uint64_t CHUNK = 16LL * 1024LL; z_stream _s {}; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 51da71ab..51d7ca8a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,4 +1,4 @@ -add_executable(unittests libcdoc_boost.cpp ../cdoc/CDocCipher.cpp) +add_executable(unittests libcdoc_boost.cpp ../cdoc/CDocCipher.cpp ../cdoc/Crypto.cpp) target_compile_definitions(unittests PRIVATE DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data") target_link_libraries(unittests OpenSSL::SSL cdoc Boost::unit_test_framework) diff --git a/test/libcdoc_boost.cpp b/test/libcdoc_boost.cpp index 300a2a7e..dc8a5e2e 100644 --- a/test/libcdoc_boost.cpp +++ b/test/libcdoc_boost.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #ifndef DATA_DIR #define DATA_DIR "." @@ -515,3 +517,51 @@ BOOST_AUTO_TEST_CASE(Base64LabelParsingWithMediaType) } BOOST_AUTO_TEST_SUITE_END() + +BOOST_AUTO_TEST_SUITE(StreamingDecryption) + +using BufTypes = std::tuple, std::array, std::array, std::array>; +BOOST_AUTO_TEST_CASE_TEMPLATE(constructor, Buf, BufTypes) +{ + const std::vector srouce_text { + 's', 'o', 'm', 'e', ' ', 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't', '.', '\n', + 's', 'o', 'm', 'e', ' ', 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't', '.', '\n', + 's', 'o', 'm', 'e', ' ', 'p', 'l', 'a', 'i', 'n', 't', 'e', 'x', 't', '.', '\n', + }; + //std::vector aad = {'A', 'A', 'D'}; + Buf buffer{}; + const auto key = libcdoc::Crypto::generateKey(std::string(libcdoc::Crypto::AES256GCM_MTH)); + const auto method = std::string(libcdoc::Crypto::AES256GCM_MTH); + + for(const auto &plaintext_size : {14, 16, 29, 32, 36}) + { + auto plaintext = srouce_text; + plaintext.resize(plaintext_size); + // Encrypt + std::vector encrypted_data; + libcdoc::VectorConsumer encrypted_dst(encrypted_data); + libcdoc::EncryptionConsumer encrypt(encrypted_dst, method, key); + BOOST_CHECK_EQUAL_COLLECTIONS(encrypted_data.begin(), encrypted_data.end(), key.iv.begin(), key.iv.end()); + //BOOST_CHECK_EQUAL(encrypt.writeAAD(aad), libcdoc::OK); + libcdoc::VectorSource plain_src(plaintext); + for(ssize_t read_len = 0; (read_len = plain_src.read(buffer.data(), buffer.size())) > 0; ) { + BOOST_CHECK_EQUAL(encrypt.write(buffer.data(), read_len), read_len); + } + BOOST_CHECK_EQUAL(encrypt.close(), libcdoc::OK); + + // Decrypt + libcdoc::VectorSource encrypted_src(encrypted_data); + libcdoc::DecryptionSource decrypt(encrypted_src, method, key.key); + //BOOST_CHECK_EQUAL(decrypt.readAAD(aad), libcdoc::OK); + std::vector decrypted_text; + for(ssize_t read_len = 0; (read_len = decrypt.read(buffer.data(), buffer.size())) > 0; ) { + decrypted_text.insert(decrypted_text.end(), buffer.data(), buffer.data() + read_len); + } + BOOST_CHECK_EQUAL(decrypt.isError(), false); + BOOST_CHECK_EQUAL(decrypt.close(), libcdoc::OK); + + BOOST_CHECK_EQUAL_COLLECTIONS(plaintext.begin(), plaintext.end(), decrypted_text.begin(), decrypted_text.end()); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/vcpkg.json b/vcpkg.json index 71bfd112..09ae3a76 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -21,7 +21,7 @@ "features": { "tests": { "description": "Build tests", "dependencies": ["boost-test"] } }, - "builtin-baseline": "98e7cd3a7ba579efc543f8854af800d033031eae", + "builtin-baseline": "bc38a15b0bee8bc48a49ea267cc32fbb49aedfc4", "vcpkg-configuration": { "overlay-triplets": ["./vcpkg-triplets"] }