Skip to content

Commit 57cae0f

Browse files
committed
fix: handle edge cases and CI builds
1 parent f474dea commit 57cae0f

File tree

3 files changed

+373
-3
lines changed

3 files changed

+373
-3
lines changed

.github/workflows/ubuntu.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,85 @@ jobs:
4444
run: cmake --build build -j=4
4545
- name: Test
4646
run: ctest --output-on-failure --test-dir build
47+
48+
# Test with OpenSSL 3.2+ to cover Argon2 code path
49+
openssl:
50+
runs-on: ubuntu-latest
51+
env:
52+
OPENSSL_VERSION: "3.4.1"
53+
OPENSSL_DIR: "${{ github.workspace }}/openssl-install"
54+
steps:
55+
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
56+
- name: Cache OpenSSL
57+
id: cache-openssl
58+
uses: actions/cache@v4
59+
with:
60+
path: ${{ env.OPENSSL_DIR }}
61+
key: openssl-${{ env.OPENSSL_VERSION }}-${{ runner.os }}
62+
- name: Build OpenSSL
63+
if: steps.cache-openssl.outputs.cache-hit != 'true'
64+
run: |
65+
curl -LO https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz
66+
tar xzf openssl-${OPENSSL_VERSION}.tar.gz
67+
cd openssl-${OPENSSL_VERSION}
68+
./Configure --prefix=${OPENSSL_DIR} --openssldir=${OPENSSL_DIR}/ssl
69+
make -j$(nproc)
70+
make install_sw
71+
- name: ccache
72+
uses: hendrikmuhs/ccache-action@v1.2
73+
with:
74+
key: ${{github.job}}-openssl
75+
- name: Setup dependencies
76+
run: sudo apt-get update && sudo apt-get install -y ninja-build libgtest-dev
77+
- name: Prepare
78+
run: |
79+
cmake -DNCRYPTO_SHARED_LIBS=ON -G Ninja -B build \
80+
-DOPENSSL_ROOT_DIR=${OPENSSL_DIR} \
81+
-DCMAKE_PREFIX_PATH=${OPENSSL_DIR}
82+
- name: Build
83+
run: cmake --build build -j=4
84+
- name: Test
85+
run: ctest --output-on-failure --test-dir build
86+
env:
87+
LD_LIBRARY_PATH: ${{ env.OPENSSL_DIR }}/lib64:${{ env.OPENSSL_DIR }}/lib
88+
89+
# Test with OPENSSL_NO_ARGON2 defined (Argon2 tests excluded)
90+
openssl-no-argon2:
91+
runs-on: ubuntu-latest
92+
env:
93+
OPENSSL_VERSION: "3.4.1"
94+
OPENSSL_DIR: "${{ github.workspace }}/openssl-install"
95+
steps:
96+
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4
97+
- name: Cache OpenSSL
98+
id: cache-openssl
99+
uses: actions/cache@v4
100+
with:
101+
path: ${{ env.OPENSSL_DIR }}
102+
key: openssl-${{ env.OPENSSL_VERSION }}-${{ runner.os }}
103+
- name: Build OpenSSL
104+
if: steps.cache-openssl.outputs.cache-hit != 'true'
105+
run: |
106+
curl -LO https://github.com/openssl/openssl/releases/download/openssl-${OPENSSL_VERSION}/openssl-${OPENSSL_VERSION}.tar.gz
107+
tar xzf openssl-${OPENSSL_VERSION}.tar.gz
108+
cd openssl-${OPENSSL_VERSION}
109+
./Configure --prefix=${OPENSSL_DIR} --openssldir=${OPENSSL_DIR}/ssl
110+
make -j$(nproc)
111+
make install_sw
112+
- name: ccache
113+
uses: hendrikmuhs/ccache-action@v1.2
114+
with:
115+
key: ${{github.job}}-openssl-no-argon2
116+
- name: Setup dependencies
117+
run: sudo apt-get update && sudo apt-get install -y ninja-build libgtest-dev
118+
- name: Prepare
119+
run: |
120+
cmake -DNCRYPTO_SHARED_LIBS=ON -DCMAKE_CXX_FLAGS="-DOPENSSL_NO_ARGON2" -G Ninja -B build \
121+
-DOPENSSL_ROOT_DIR=${OPENSSL_DIR} \
122+
-DCMAKE_PREFIX_PATH=${OPENSSL_DIR}
123+
- name: Build
124+
run: cmake --build build -j=4
125+
- name: Test
126+
run: ctest --output-on-failure --test-dir build
127+
env:
128+
LD_LIBRARY_PATH: ${{ env.OPENSSL_DIR }}/lib64:${{ env.OPENSSL_DIR }}/lib

src/ncrypto.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,13 @@
1515
#include <openssl/hkdf.h>
1616
#endif
1717

18+
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
19+
#include <openssl/thread.h>
20+
#ifndef OPENSSL_NO_ARGON2
21+
#include <vector>
22+
#endif
23+
#endif
24+
1825
#include <algorithm>
1926
#include <array>
2027
#include <cstring>
@@ -23,9 +30,6 @@
2330
#include <openssl/core_names.h>
2431
#include <openssl/params.h>
2532
#include <openssl/provider.h>
26-
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
27-
#include <openssl/thread.h>
28-
#endif
2933
#endif
3034
#if OPENSSL_WITH_PQC
3135
struct PQCMapping {

tests/basic.cpp

Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,287 @@ TEST(basic, aead_info) {
534534
ASSERT_EQ(aead.getMaxTagLength(), 16);
535535
}
536536
#endif
537+
538+
// ============================================================================
539+
// Argon2 KDF tests (OpenSSL 3.2.0+ only)
540+
541+
#if OPENSSL_VERSION_NUMBER >= 0x30200000L
542+
#ifndef OPENSSL_NO_ARGON2
543+
544+
TEST(KDF, argon2i) {
545+
const char* password = "password";
546+
const unsigned char salt[] = {0x01,
547+
0x02,
548+
0x03,
549+
0x04,
550+
0x05,
551+
0x06,
552+
0x07,
553+
0x08,
554+
0x09,
555+
0x0a,
556+
0x0b,
557+
0x0c,
558+
0x0d,
559+
0x0e,
560+
0x0f,
561+
0x10};
562+
const size_t length = 32;
563+
564+
Buffer<const char> passBuf{password, strlen(password)};
565+
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
566+
Buffer<const unsigned char> secret{nullptr, 0};
567+
Buffer<const unsigned char> ad{nullptr, 0};
568+
569+
// Use small parameters for testing
570+
// lanes=1, memcost=16 (KB), iter=3, version=0x13 (1.3)
571+
auto result = argon2(passBuf,
572+
saltBuf,
573+
1,
574+
length,
575+
16,
576+
3,
577+
0x13,
578+
secret,
579+
ad,
580+
Argon2Type::ARGON2I);
581+
ASSERT_TRUE(result);
582+
ASSERT_EQ(result.size(), length);
583+
584+
// Verify output is not all zeros
585+
bool allZeros = true;
586+
for (size_t i = 0; i < length; i++) {
587+
if (reinterpret_cast<unsigned char*>(result.get())[i] != 0) {
588+
allZeros = false;
589+
break;
590+
}
591+
}
592+
ASSERT_FALSE(allZeros);
593+
}
594+
595+
TEST(KDF, argon2d) {
596+
const char* password = "password";
597+
const unsigned char salt[] = {0x01,
598+
0x02,
599+
0x03,
600+
0x04,
601+
0x05,
602+
0x06,
603+
0x07,
604+
0x08,
605+
0x09,
606+
0x0a,
607+
0x0b,
608+
0x0c,
609+
0x0d,
610+
0x0e,
611+
0x0f,
612+
0x10};
613+
const size_t length = 32;
614+
615+
Buffer<const char> passBuf{password, strlen(password)};
616+
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
617+
Buffer<const unsigned char> secret{nullptr, 0};
618+
Buffer<const unsigned char> ad{nullptr, 0};
619+
620+
auto result = argon2(passBuf,
621+
saltBuf,
622+
1,
623+
length,
624+
16,
625+
3,
626+
0x13,
627+
secret,
628+
ad,
629+
Argon2Type::ARGON2D);
630+
ASSERT_TRUE(result);
631+
ASSERT_EQ(result.size(), length);
632+
}
633+
634+
TEST(KDF, argon2id) {
635+
const char* password = "password";
636+
const unsigned char salt[] = {0x01,
637+
0x02,
638+
0x03,
639+
0x04,
640+
0x05,
641+
0x06,
642+
0x07,
643+
0x08,
644+
0x09,
645+
0x0a,
646+
0x0b,
647+
0x0c,
648+
0x0d,
649+
0x0e,
650+
0x0f,
651+
0x10};
652+
const size_t length = 32;
653+
654+
Buffer<const char> passBuf{password, strlen(password)};
655+
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
656+
Buffer<const unsigned char> secret{nullptr, 0};
657+
Buffer<const unsigned char> ad{nullptr, 0};
658+
659+
auto result = argon2(passBuf,
660+
saltBuf,
661+
1,
662+
length,
663+
16,
664+
3,
665+
0x13,
666+
secret,
667+
ad,
668+
Argon2Type::ARGON2ID);
669+
ASSERT_TRUE(result);
670+
ASSERT_EQ(result.size(), length);
671+
}
672+
673+
TEST(KDF, argon2_with_secret_and_ad) {
674+
const char* password = "password";
675+
const unsigned char salt[] = {0x01,
676+
0x02,
677+
0x03,
678+
0x04,
679+
0x05,
680+
0x06,
681+
0x07,
682+
0x08,
683+
0x09,
684+
0x0a,
685+
0x0b,
686+
0x0c,
687+
0x0d,
688+
0x0e,
689+
0x0f,
690+
0x10};
691+
const unsigned char secretData[] = {0xaa, 0xbb, 0xcc, 0xdd};
692+
const unsigned char adData[] = {0x11, 0x22, 0x33, 0x44, 0x55};
693+
const size_t length = 32;
694+
695+
Buffer<const char> passBuf{password, strlen(password)};
696+
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
697+
Buffer<const unsigned char> secret{secretData, sizeof(secretData)};
698+
Buffer<const unsigned char> ad{adData, sizeof(adData)};
699+
700+
auto result = argon2(passBuf,
701+
saltBuf,
702+
1,
703+
length,
704+
16,
705+
3,
706+
0x13,
707+
secret,
708+
ad,
709+
Argon2Type::ARGON2ID);
710+
ASSERT_TRUE(result);
711+
ASSERT_EQ(result.size(), length);
712+
}
713+
714+
TEST(KDF, argon2_empty_password) {
715+
const unsigned char salt[] = {0x01,
716+
0x02,
717+
0x03,
718+
0x04,
719+
0x05,
720+
0x06,
721+
0x07,
722+
0x08,
723+
0x09,
724+
0x0a,
725+
0x0b,
726+
0x0c,
727+
0x0d,
728+
0x0e,
729+
0x0f,
730+
0x10};
731+
const size_t length = 32;
732+
733+
Buffer<const char> passBuf{"", 0};
734+
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
735+
Buffer<const unsigned char> secret{nullptr, 0};
736+
Buffer<const unsigned char> ad{nullptr, 0};
737+
738+
// Empty password should still work
739+
auto result = argon2(passBuf,
740+
saltBuf,
741+
1,
742+
length,
743+
16,
744+
3,
745+
0x13,
746+
secret,
747+
ad,
748+
Argon2Type::ARGON2ID);
749+
ASSERT_TRUE(result);
750+
ASSERT_EQ(result.size(), length);
751+
}
752+
753+
TEST(KDF, argon2_different_types_produce_different_output) {
754+
const char* password = "password";
755+
const unsigned char salt[] = {0x01,
756+
0x02,
757+
0x03,
758+
0x04,
759+
0x05,
760+
0x06,
761+
0x07,
762+
0x08,
763+
0x09,
764+
0x0a,
765+
0x0b,
766+
0x0c,
767+
0x0d,
768+
0x0e,
769+
0x0f,
770+
0x10};
771+
const size_t length = 32;
772+
773+
Buffer<const char> passBuf{password, strlen(password)};
774+
Buffer<const unsigned char> saltBuf{salt, sizeof(salt)};
775+
Buffer<const unsigned char> secret{nullptr, 0};
776+
Buffer<const unsigned char> ad{nullptr, 0};
777+
778+
auto resultI = argon2(passBuf,
779+
saltBuf,
780+
1,
781+
length,
782+
16,
783+
3,
784+
0x13,
785+
secret,
786+
ad,
787+
Argon2Type::ARGON2I);
788+
auto resultD = argon2(passBuf,
789+
saltBuf,
790+
1,
791+
length,
792+
16,
793+
3,
794+
0x13,
795+
secret,
796+
ad,
797+
Argon2Type::ARGON2D);
798+
auto resultID = argon2(passBuf,
799+
saltBuf,
800+
1,
801+
length,
802+
16,
803+
3,
804+
0x13,
805+
secret,
806+
ad,
807+
Argon2Type::ARGON2ID);
808+
809+
ASSERT_TRUE(resultI);
810+
ASSERT_TRUE(resultD);
811+
ASSERT_TRUE(resultID);
812+
813+
// All three types should produce different outputs
814+
ASSERT_NE(memcmp(resultI.get(), resultD.get(), length), 0);
815+
ASSERT_NE(memcmp(resultI.get(), resultID.get(), length), 0);
816+
ASSERT_NE(memcmp(resultD.get(), resultID.get(), length), 0);
817+
}
818+
819+
#endif // OPENSSL_NO_ARGON2
820+
#endif // OPENSSL_VERSION_NUMBER >= 0x30200000L

0 commit comments

Comments
 (0)