From 864358adce2c9f5aa022abfb426a826ec4e85915 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 2 Feb 2026 13:36:51 -0500 Subject: [PATCH] fix: handle edge cases and CI builds --- .github/workflows/ubuntu.yml | 82 ++++++++++ src/ncrypto.cpp | 10 +- tests/basic.cpp | 284 +++++++++++++++++++++++++++++++++++ 3 files changed, 373 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 33d14b4..3b26eb6 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -44,3 +44,85 @@ jobs: run: cmake --build build -j=4 - name: Test run: ctest --output-on-failure --test-dir build + + # Test with OpenSSL 3.2+ to cover Argon2 code path + openssl: + runs-on: ubuntu-latest + env: + OPENSSL_VERSION: "3.4.1" + OPENSSL_DIR: "${{ github.workspace }}/openssl-install" + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - name: Cache OpenSSL + id: cache-openssl + uses: actions/cache@v4 + with: + path: ${{ env.OPENSSL_DIR }} + key: openssl-${{ env.OPENSSL_VERSION }}-${{ runner.os }} + - name: Build OpenSSL + if: steps.cache-openssl.outputs.cache-hit != 'true' + run: | + curl -LO https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz + tar xzf openssl-${OPENSSL_VERSION}.tar.gz + cd openssl-${OPENSSL_VERSION} + ./Configure --prefix=${OPENSSL_DIR} --openssldir=${OPENSSL_DIR}/ssl + make -j$(nproc) + make install_sw + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{github.job}}-openssl + - name: Setup dependencies + run: sudo apt-get update && sudo apt-get install -y ninja-build libgtest-dev + - name: Prepare + run: | + cmake -DNCRYPTO_SHARED_LIBS=ON -G Ninja -B build \ + -DOPENSSL_ROOT_DIR=${OPENSSL_DIR} \ + -DCMAKE_PREFIX_PATH=${OPENSSL_DIR} + - name: Build + run: cmake --build build -j=4 + - name: Test + run: ctest --output-on-failure --test-dir build + env: + LD_LIBRARY_PATH: ${{ env.OPENSSL_DIR }}/lib64:${{ env.OPENSSL_DIR }}/lib + + # Test with OPENSSL_NO_ARGON2 defined (Argon2 tests excluded) + openssl-no-argon2: + runs-on: ubuntu-latest + env: + OPENSSL_VERSION: "3.4.1" + OPENSSL_DIR: "${{ github.workspace }}/openssl-install" + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - name: Cache OpenSSL + id: cache-openssl + uses: actions/cache@v4 + with: + path: ${{ env.OPENSSL_DIR }} + key: openssl-${{ env.OPENSSL_VERSION }}-${{ runner.os }} + - name: Build OpenSSL + if: steps.cache-openssl.outputs.cache-hit != 'true' + run: | + curl -LO https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz + tar xzf openssl-${OPENSSL_VERSION}.tar.gz + cd openssl-${OPENSSL_VERSION} + ./Configure --prefix=${OPENSSL_DIR} --openssldir=${OPENSSL_DIR}/ssl + make -j$(nproc) + make install_sw + - name: ccache + uses: hendrikmuhs/ccache-action@v1.2 + with: + key: ${{github.job}}-openssl-no-argon2 + - name: Setup dependencies + run: sudo apt-get update && sudo apt-get install -y ninja-build libgtest-dev + - name: Prepare + run: | + cmake -DNCRYPTO_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="-DOPENSSL_NO_ARGON2" -G Ninja -B build \ + -DOPENSSL_ROOT_DIR=${OPENSSL_DIR} \ + -DCMAKE_PREFIX_PATH=${OPENSSL_DIR} + - name: Build + run: cmake --build build -j=4 + - name: Test + run: ctest --output-on-failure --test-dir build + env: + LD_LIBRARY_PATH: ${{ env.OPENSSL_DIR }}/lib64:${{ env.OPENSSL_DIR }}/lib diff --git a/src/ncrypto.cpp b/src/ncrypto.cpp index fc4bed2..f98ad56 100644 --- a/src/ncrypto.cpp +++ b/src/ncrypto.cpp @@ -15,6 +15,13 @@ #include #endif +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#include +#ifndef OPENSSL_NO_ARGON2 +#include +#endif +#endif + #include #include #include @@ -23,9 +30,6 @@ #include #include #include -#if OPENSSL_VERSION_NUMBER >= 0x30200000L -#include -#endif #endif #if OPENSSL_WITH_PQC struct PQCMapping { diff --git a/tests/basic.cpp b/tests/basic.cpp index 0d87a25..61c5e13 100644 --- a/tests/basic.cpp +++ b/tests/basic.cpp @@ -534,3 +534,287 @@ TEST(basic, aead_info) { ASSERT_EQ(aead.getMaxTagLength(), 16); } #endif + +// ============================================================================ +// Argon2 KDF tests (OpenSSL 3.2.0+ only) + +#if OPENSSL_VERSION_NUMBER >= 0x30200000L +#ifndef OPENSSL_NO_ARGON2 + +TEST(KDF, argon2i) { + const char* password = "password"; + const unsigned char salt[] = {0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10}; + const size_t length = 32; + + Buffer passBuf{password, strlen(password)}; + Buffer saltBuf{salt, sizeof(salt)}; + Buffer secret{nullptr, 0}; + Buffer ad{nullptr, 0}; + + // Use small parameters for testing + // lanes=1, memcost=16 (KB), iter=3, version=0x13 (1.3) + auto result = argon2(passBuf, + saltBuf, + 1, + length, + 16, + 3, + 0x13, + secret, + ad, + Argon2Type::ARGON2I); + ASSERT_TRUE(result); + ASSERT_EQ(result.size(), length); + + // Verify output is not all zeros + bool allZeros = true; + for (size_t i = 0; i < length; i++) { + if (reinterpret_cast(result.get())[i] != 0) { + allZeros = false; + break; + } + } + ASSERT_FALSE(allZeros); +} + +TEST(KDF, argon2d) { + const char* password = "password"; + const unsigned char salt[] = {0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10}; + const size_t length = 32; + + Buffer passBuf{password, strlen(password)}; + Buffer saltBuf{salt, sizeof(salt)}; + Buffer secret{nullptr, 0}; + Buffer ad{nullptr, 0}; + + auto result = argon2(passBuf, + saltBuf, + 1, + length, + 16, + 3, + 0x13, + secret, + ad, + Argon2Type::ARGON2D); + ASSERT_TRUE(result); + ASSERT_EQ(result.size(), length); +} + +TEST(KDF, argon2id) { + const char* password = "password"; + const unsigned char salt[] = {0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10}; + const size_t length = 32; + + Buffer passBuf{password, strlen(password)}; + Buffer saltBuf{salt, sizeof(salt)}; + Buffer secret{nullptr, 0}; + Buffer ad{nullptr, 0}; + + auto result = argon2(passBuf, + saltBuf, + 1, + length, + 16, + 3, + 0x13, + secret, + ad, + Argon2Type::ARGON2ID); + ASSERT_TRUE(result); + ASSERT_EQ(result.size(), length); +} + +TEST(KDF, argon2_with_secret_and_ad) { + const char* password = "password"; + const unsigned char salt[] = {0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10}; + const unsigned char secretData[] = {0xaa, 0xbb, 0xcc, 0xdd}; + const unsigned char adData[] = {0x11, 0x22, 0x33, 0x44, 0x55}; + const size_t length = 32; + + Buffer passBuf{password, strlen(password)}; + Buffer saltBuf{salt, sizeof(salt)}; + Buffer secret{secretData, sizeof(secretData)}; + Buffer ad{adData, sizeof(adData)}; + + auto result = argon2(passBuf, + saltBuf, + 1, + length, + 16, + 3, + 0x13, + secret, + ad, + Argon2Type::ARGON2ID); + ASSERT_TRUE(result); + ASSERT_EQ(result.size(), length); +} + +TEST(KDF, argon2_empty_password) { + const unsigned char salt[] = {0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10}; + const size_t length = 32; + + Buffer passBuf{"", 0}; + Buffer saltBuf{salt, sizeof(salt)}; + Buffer secret{nullptr, 0}; + Buffer ad{nullptr, 0}; + + // Empty password should still work + auto result = argon2(passBuf, + saltBuf, + 1, + length, + 16, + 3, + 0x13, + secret, + ad, + Argon2Type::ARGON2ID); + ASSERT_TRUE(result); + ASSERT_EQ(result.size(), length); +} + +TEST(KDF, argon2_different_types_produce_different_output) { + const char* password = "password"; + const unsigned char salt[] = {0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10}; + const size_t length = 32; + + Buffer passBuf{password, strlen(password)}; + Buffer saltBuf{salt, sizeof(salt)}; + Buffer secret{nullptr, 0}; + Buffer ad{nullptr, 0}; + + auto resultI = argon2(passBuf, + saltBuf, + 1, + length, + 16, + 3, + 0x13, + secret, + ad, + Argon2Type::ARGON2I); + auto resultD = argon2(passBuf, + saltBuf, + 1, + length, + 16, + 3, + 0x13, + secret, + ad, + Argon2Type::ARGON2D); + auto resultID = argon2(passBuf, + saltBuf, + 1, + length, + 16, + 3, + 0x13, + secret, + ad, + Argon2Type::ARGON2ID); + + ASSERT_TRUE(resultI); + ASSERT_TRUE(resultD); + ASSERT_TRUE(resultID); + + // All three types should produce different outputs + ASSERT_NE(memcmp(resultI.get(), resultD.get(), length), 0); + ASSERT_NE(memcmp(resultI.get(), resultID.get(), length), 0); + ASSERT_NE(memcmp(resultD.get(), resultID.get(), length), 0); +} + +#endif // OPENSSL_NO_ARGON2 +#endif // OPENSSL_VERSION_NUMBER >= 0x30200000L