Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
10 changes: 7 additions & 3 deletions src/ncrypto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@
#include <openssl/hkdf.h>
#endif

#if OPENSSL_VERSION_NUMBER >= 0x30200000L
#include <openssl/thread.h>
#ifndef OPENSSL_NO_ARGON2
#include <vector>
#endif
#endif

#include <algorithm>
#include <array>
#include <cstring>
Expand All @@ -23,9 +30,6 @@
#include <openssl/core_names.h>
#include <openssl/params.h>
#include <openssl/provider.h>
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
#include <openssl/thread.h>
#endif
#endif
#if OPENSSL_WITH_PQC
struct PQCMapping {
Expand Down
284 changes: 284 additions & 0 deletions tests/basic.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const char> passBuf{password, strlen(password)};
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
Buffer<const unsigned char> secret{nullptr, 0};
Buffer<const unsigned char> 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<unsigned char*>(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<const char> passBuf{password, strlen(password)};
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
Buffer<const unsigned char> secret{nullptr, 0};
Buffer<const unsigned char> 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<const char> passBuf{password, strlen(password)};
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
Buffer<const unsigned char> secret{nullptr, 0};
Buffer<const unsigned char> 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<const char> passBuf{password, strlen(password)};
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
Buffer<const unsigned char> secret{secretData, sizeof(secretData)};
Buffer<const unsigned char> 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<const char> passBuf{"", 0};
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
Buffer<const unsigned char> secret{nullptr, 0};
Buffer<const unsigned char> 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<const char> passBuf{password, strlen(password)};
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
Buffer<const unsigned char> secret{nullptr, 0};
Buffer<const unsigned char> 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