diff --git a/.github/workflows/compile_project.yml b/.github/workflows/compile_project.yml
index d8790aef..9e4d6215 100644
--- a/.github/workflows/compile_project.yml
+++ b/.github/workflows/compile_project.yml
@@ -3,8 +3,6 @@ name: Compile Project (Push/PR/Release)
on:
push:
branches: [ development-tip, stable ]
- pull_request:
- branches: [ development-tip, stable ]
release:
types: [ created ]
diff --git a/.github/workflows/execute_tests.yml b/.github/workflows/execute_tests.yml
new file mode 100644
index 00000000..a1e4e3eb
--- /dev/null
+++ b/.github/workflows/execute_tests.yml
@@ -0,0 +1,66 @@
+name: Run Unit-Tests (PR)
+
+on:
+ pull_request:
+ branches: [ development-tip, stable ]
+
+jobs:
+ build_makefile:
+ name: ${{ matrix.dist }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ dist: [ubuntu_x86_64, macos_x86_64, macos_arm64]
+ prog: [libtoolchain_test]
+ include:
+ - dist: ubuntu_x86_64
+ os: ubuntu-latest
+ arch: x86_64
+ bin_ext:
+ - dist: macos_x86_64
+ os: macos-latest
+ arch: x86_64
+ bin_ext:
+ - dist: macos_arm64
+ os: macos-latest
+ arch: arm64
+ bin_ext:
+ steps:
+ - uses: actions/checkout@v4
+ - name: Clone submodules
+ run: git submodule init && git submodule update
+ - name: Compile ${{ matrix.prog }}
+ run: make PROJECT_PLATFORM_ARCH=${{ matrix.arch }} deps all
+ - name: Run unit-tests ${{ matrix.prog }}
+ run: ./bin/${{ matrix.prog }}${{ matrix.bin_ext }} --exres PASS --slow
+ build_visualstudio:
+ name: ${{ matrix.dist }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ matrix:
+ dist: [win_x64, win_x86]
+ vs_sln: [libtoolchain]
+ bin_name: [libtoolchain-test]
+ include:
+ - dist: win_x64
+ os: windows-latest
+ platform: x64
+ configuration: Release
+ build_path: x64\Release
+ bin_ext: .exe
+ - dist: win_x86
+ os: windows-latest
+ platform: x86
+ configuration: Release
+ build_path: Release
+ bin_ext: .exe
+ steps:
+ - uses: actions/checkout@v4
+ - name: Add msbuild to PATH
+ uses: microsoft/setup-msbuild@v1.3
+ - name: Clone submodules
+ run: git submodule init && git submodule update
+ - name: Compile ${{ matrix.vs_sln }}
+ run: msbuild .\build\visualstudio\${{ matrix.vs_sln }}.sln /p:configuration=${{ matrix.configuration }} /p:platform=${{ matrix.platform }}
+ - name: Run unit-tests ${{ matrix.bin_name }}
+ run: .\build\visualstudio\${{ matrix.build_path }}\${{ matrix.bin_name }}${{ matrix.bin_ext }} --exres PASS
diff --git a/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj b/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj
index 609c1327..b5114fcb 100644
--- a/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj
+++ b/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj
@@ -169,6 +169,7 @@
+
@@ -285,6 +286,7 @@
+
diff --git a/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj.filters b/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj.filters
index d108efee..438b4ddd 100644
--- a/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj.filters
+++ b/build/visualstudio/libtoolchain-test/libtoolchain-test.vcxproj.filters
@@ -147,6 +147,9 @@
Source Files
+
+ Source Files
+
Source Files
@@ -470,7 +473,6 @@
Header Files
-
Header Files
@@ -486,13 +488,15 @@
Header Files
-
Header Files
Header Files
+
+ Header Files
+
Header Files
diff --git a/build/visualstudio/libtoolchain/libtoolchain.vcxproj b/build/visualstudio/libtoolchain/libtoolchain.vcxproj
index 825e2bab..f89a564b 100644
--- a/build/visualstudio/libtoolchain/libtoolchain.vcxproj
+++ b/build/visualstudio/libtoolchain/libtoolchain.vcxproj
@@ -179,6 +179,11 @@
+
+
+
+
+
@@ -223,6 +228,8 @@
+
+
@@ -242,11 +249,6 @@
-
-
-
-
-
@@ -320,6 +322,10 @@
+
+
+
+
@@ -350,6 +356,8 @@
+
+
diff --git a/build/visualstudio/libtoolchain/libtoolchain.vcxproj.filters b/build/visualstudio/libtoolchain/libtoolchain.vcxproj.filters
index a22c5a25..98f76ff6 100644
--- a/build/visualstudio/libtoolchain/libtoolchain.vcxproj.filters
+++ b/build/visualstudio/libtoolchain/libtoolchain.vcxproj.filters
@@ -82,6 +82,18 @@
Source Files\crypto
+
+ Source Files\crypto
+
+
+ Source Files\crypto
+
+
+ Source Files\crypto
+
+
+ Source Files\crypto
+
Source Files\crypto
@@ -172,6 +184,12 @@
Source Files\crypto\detail
+
+ Source Files\crypto\detail
+
+
+ Source Files\crypto\detail
+
Source Files\crypto\detail
@@ -432,6 +450,21 @@
Header Files\tc\crypto
+
+ Header Files\tc\crypto
+
+
+ Header Files\tc\crypto
+
+
+ Header Files\tc\crypto
+
+
+ Header Files\tc\crypto
+
+
+ Header Files\tc\crypto
+
Header Files\tc\crypto
@@ -564,6 +597,12 @@
Header Files\tc\crypto\detail
+
+ Header Files\tc\crypto\detail
+
+
+ Header Files\tc\crypto\detail
+
Header Files\tc\crypto\detail
diff --git a/include/tc/crypto/EccCurveType.h b/include/tc/crypto/EccCurveType.h
new file mode 100644
index 00000000..943f08b5
--- /dev/null
+++ b/include/tc/crypto/EccCurveType.h
@@ -0,0 +1,33 @@
+ /**
+ * @file EccCurveType.h
+ * @brief Declaration of tc::crypto::EccCurveType
+ * @author Jack (jakcron)
+ * @version 0.1
+ * @date 2025/06/01
+ **/
+#pragma once
+
+namespace tc { namespace crypto {
+
+ /**
+ * @struct EccCurveType
+ * @brief Defines supported ECC curves
+ */
+enum EccCurveType
+{
+ ECC_CURVE_TYPE_SECP192R1, /**< The 192-bit curve defined by FIPS 186-4 and SEC1. */
+ ECC_CURVE_TYPE_SECP224R1, /**< The 224-bit curve defined by FIPS 186-4 and SEC1. */
+ ECC_CURVE_TYPE_SECP256R1, /**< The 256-bit curve defined by FIPS 186-4 and SEC1. */
+ ECC_CURVE_TYPE_SECP384R1, /**< The 384-bit curve defined by FIPS 186-4 and SEC1. */
+ ECC_CURVE_TYPE_SECP521R1, /**< The 521-bit curve defined by FIPS 186-4 and SEC1. */
+ ECC_CURVE_TYPE_BP256R1, /**< 256-bit Brainpool curve. */
+ ECC_CURVE_TYPE_BP384R1, /**< 384-bit Brainpool curve. */
+ ECC_CURVE_TYPE_BP512R1, /**< 512-bit Brainpool curve. */
+ ECC_CURVE_TYPE_CURVE25519, /**< Curve25519. */
+ ECC_CURVE_TYPE_SECP192K1, /**< 192-bit "Koblitz" curve. */
+ ECC_CURVE_TYPE_SECP224K1, /**< 224-bit "Koblitz" curve. */
+ ECC_CURVE_TYPE_SECP256K1, /**< 256-bit "Koblitz" curve. */
+ ECC_CURVE_TYPE_CURVE448, /**< Curve448. */
+};
+
+}} // namespace tc::crypto
\ No newline at end of file
diff --git a/include/tc/crypto/EccKey.h b/include/tc/crypto/EccKey.h
new file mode 100644
index 00000000..2b08c444
--- /dev/null
+++ b/include/tc/crypto/EccKey.h
@@ -0,0 +1,95 @@
+ /**
+ * @file EccKey.h
+ * @brief Declarations for structures to store ECC keys.
+ * @author Jack (jakcron)
+ * @version 0.1
+ * @date 2025/06/01
+ **/
+#pragma once
+#include
+#include
+#include
+
+#include
+
+namespace tc { namespace crypto {
+
+
+ /**
+ * @struct EccKey
+ * @brief Struct for storing a ECC key. For use with ECC operations.
+ *
+ * @note The public/private components are not compressed.
+ */
+struct EccKey
+{
+ EccCurveType curve_type;
+ tc::ByteData d; /**< Private component - big endian d integer */
+ tc::ByteData Q; /**< Public component - big endian Q point */
+};
+
+ /**
+ * @struct EccPublicKey
+ * @brief This extends EccKey, exposing a constructor to create an ECC public key.
+ */
+struct EccPublicKey : public EccKey
+{
+ /**
+ * @brief This constructs a @ref EccKey from the public component.
+ *
+ * @param[in] curve_type ECC Curve Type.
+ * @param[in] Q Buffer containing big-endian Q point.
+ * @param[in] Q_size Size in bytes of Q.
+ *
+ * @pre @p ec_type must be of type @ref EccCurveType
+ * @pre @p Q != nullptr
+ * @pre @p Q_size != 0
+ */
+ EccPublicKey(EccCurveType curve_type, const byte_t* Q, size_t Q_size);
+};
+
+ /**
+ * @struct EccPrivateKey
+ * @brief This extends EccKey, exposing a constructor to create an ECC private key from a modulus and private exponent.
+ */
+struct EccPrivateKey : public EccKey
+{
+ /**
+ * @brief This constructs a @ref EccKey from the private and public components.
+ *
+ * @param[in] curve_type ECC Curve Type
+ * @param[in] d Buffer containing big-endian d integer.
+ * @param[in] d_size Size in bytes of d.
+ * @param[in] Q Buffer containing big-endian Q point.
+ * @param[in] Q_size Size in bytes of Q.
+ *
+ * @pre @p ec_type must be of type @ref EccCurveType
+ * @pre @p d != nullptr
+ * @pre @p d_size != 0
+ * @pre @p Q != nullptr
+ * @pre @p Q_size != 0
+ */
+ EccPrivateKey(EccCurveType curve_type, const byte_t* d, size_t d_size, const byte_t* Q, size_t Q_size);
+
+ /**
+ * @brief This constructs a @ref EccKey from the private component (and generate the public component).
+ *
+ * @param[in] curve_type ECC Curve Type
+ * @param[in] d Buffer containing big-endian d integer.
+ * @param[in] d_size Size in bytes of d.
+ *
+ * @pre @p ec_type must be of type @ref EccCurveType
+ * @pre @p d != nullptr
+ * @pre @p d_size != 0
+ */
+ EccPrivateKey(EccCurveType curve_type, const byte_t* d, size_t d_size);
+
+ /**
+ * @brief Generate public key from this private key.
+ *
+ * @return EccKey containing the public key.
+ */
+ EccKey getPublicKey();
+};
+
+}} // namespace tc::crypto
\ No newline at end of file
diff --git a/include/tc/crypto/EccKeyGenerator.h b/include/tc/crypto/EccKeyGenerator.h
new file mode 100644
index 00000000..97af7e01
--- /dev/null
+++ b/include/tc/crypto/EccKeyGenerator.h
@@ -0,0 +1,114 @@
+ /**
+ * @file EccKeyGenerator.h
+ * @brief Declarations for API resources for generating ECC keys.
+ * @author Jack (jakcron)
+ * @version 0.1
+ * @date 2025/06/01
+ **/
+#pragma once
+#include
+#include
+#include
+#include
+
+namespace tc { namespace crypto {
+
+ /**
+ * @class EccKeyGenerator
+ * @brief Class for generating ECC keys.
+ *
+ * @details
+ * The underlying PRNG algorithm is CTR_DBRG.
+ */
+class EccKeyGenerator
+{
+public:
+ /**
+ * @brief Default constructor.
+ */
+ EccKeyGenerator() :
+ mImpl()
+ {}
+
+ /**
+ * @brief Generate an ECC key.
+ *
+ * @param[out] key Reference to generated ECC key.
+ * @param[in] curve_type ECC Curve Type.
+ *
+ * @pre
+ * - @p ec_type must be of type @ref EccCurveType
+ *
+ * @post
+ * - The generated key is written to key.
+ *
+ * @throw tc::crypto::ArgumentException @p curve_type was not supported/valid.
+ */
+ void generateKey(EccKey& key, EccCurveType curve_type)
+ {
+ size_t ecc_int_byte_length = EccUtil::eccIntegerByteLength(curve_type);
+
+ if (ecc_int_byte_length == 0) throw tc::ArgumentException("tc::crypto::EccKeyGenerator::generateKey()", "curve_type was not supported/valid");
+
+ key.curve_type = curve_type;
+ key.d = tc::ByteData(ecc_int_byte_length);
+ key.Q = tc::ByteData(ecc_int_byte_length * 2);
+
+ mImpl.generateKey(curve_type, key.d.data(), key.d.size(), key.Q.data(), key.Q.size());
+ }
+
+ /**
+ * @brief Generate an ECC public key from an ECC private key key.
+ *
+ * @param[out] public_key Object to store generated public key.
+ * @param[in] private_key Private key to generate public key from.
+ *
+ * @post
+ * - The generated public key is written to public_key.
+ *
+ * @throw tc::crypto::ArgumentException @p private_key curve_type was not supported/valid.
+ */
+ void generatePublicKey(EccKey& public_key, const EccKey& private_key)
+ {
+ size_t ecc_int_byte_length = EccUtil::eccIntegerByteLength(private_key.curve_type);
+
+ if (ecc_int_byte_length == 0) throw tc::ArgumentException("tc::crypto::EccKeyGenerator::generatePublicKey()", "curve_type was not supported/valid");
+
+ public_key.curve_type = private_key.curve_type;
+ public_key.d = tc::ByteData(0);
+ public_key.Q = tc::ByteData(ecc_int_byte_length * 2);
+
+ mImpl.generatePublicKey(public_key.curve_type, private_key.d.data(), private_key.d.size(), public_key.Q.data(), public_key.Q.size());
+ }
+
+private:
+ detail::EccKeyGeneratorImpl mImpl;
+};
+
+ /**
+ * @brief Utility function for generating an ECC key.
+ *
+ * @param[out] key Object to store generated ECC key.
+ * @param[in] curve_type Type of ECC curve to generate
+ *
+ * @post
+ * - The generated key is written to key.
+ *
+ * @throw tc::crypto::ArgumentException @p curve_type was not supported/valid.
+ */
+void GenerateEccKey(EccKey& key, EccCurveType curve_type);
+
+ /**
+ * @brief Utility function for generating an ECC public key from an ECC private key.
+ *
+ * @param[out] public_key Object to store generated public key.
+ * @param[in] private_key Private key to generate public key from.
+ *
+ * @post
+ * - The generated public key is written to public_key.
+ *
+ * @throw tc::crypto::ArgumentException @p curve_type was not supported/valid.
+ */
+void GenerateEccPublicKey(EccKey& public_key, const EccKey& private_key);
+
+}} // namespace tc::crypto
\ No newline at end of file
diff --git a/include/tc/crypto/EccUtil.h b/include/tc/crypto/EccUtil.h
new file mode 100644
index 00000000..3c2dd39d
--- /dev/null
+++ b/include/tc/crypto/EccUtil.h
@@ -0,0 +1,32 @@
+ /**
+ * @file EccUtil.h
+ * @brief Declaration of tc::crypto::EccUtil
+ * @author Jack (jakcron)
+ * @version 0.1
+ * @date 2025/06/08
+ **/
+#pragma once
+#include
+#include
+
+namespace tc { namespace crypto {
+
+ /**
+ * @class EccUtil
+ * @brief Utility functions for ECC operations based classes.
+ **/
+class EccUtil
+{
+public:
+ /**
+ * @brief Get length of ECC integer in bits.
+ **/
+ static size_t eccIntegerBitLength(EccCurveType curve_type);
+
+ /**
+ * @brief Get length of ECC integer in bytes (rounding up).
+ **/
+ static size_t eccIntegerByteLength(EccCurveType curve_type);
+};
+
+}} // namespace tc::crypto
\ No newline at end of file
diff --git a/include/tc/crypto/EcdhSharedSecretGenerator.h b/include/tc/crypto/EcdhSharedSecretGenerator.h
new file mode 100644
index 00000000..7722076f
--- /dev/null
+++ b/include/tc/crypto/EcdhSharedSecretGenerator.h
@@ -0,0 +1,133 @@
+ /**
+ * @file EcdhSharedSecretGenerator.h
+ * @brief Declarations for API resources for using ECC keys to generate shared secrets using ECDH.
+ * @author Jack (jakcron)
+ * @version 0.1
+ * @date 2025/08/06
+ **/
+#pragma once
+#include
+#include
+#include
+#include
+
+#include
+
+namespace tc { namespace crypto {
+
+ /**
+ * @class EcdhSharedSecretGenerator
+ * @brief Class for using ECC keys to generate shared secrets using ECDH.
+ */
+class EcdhSharedSecretGenerator
+{
+public:
+ /**
+ * @brief Default constructor.
+ */
+ EcdhSharedSecretGenerator() :
+ mState(State_NotInitialized),
+ mImpl()
+ {}
+
+ /**
+ * @brief Utility function for generating an ECDH shared secret.
+
+ * @param[in] private_key ECC private key of the current party.
+ * @param[in] public_key ECC public key of other party.
+ *
+ * @throw tc::crypto::ArgumentException @p private_key was not a valid private key.
+ * @throw tc::crypto::ArgumentException @p public_key was not a valid public key.
+ * @throw tc::crypto::ArgumentException @p private_key or @p public_key curve type is not supported/valid.
+ * @throw tc::crypto::ArgumentException @p private_key and @p public_key did not have the same curve type.
+ */
+ void initialize(const EccKey& private_key, const EccKey& public_key)
+ {
+ if (private_key.curve_type != public_key.curve_type)
+ {
+ throw tc::ArgumentOutOfRangeException("private_key and public_key did not have the same curve type.");
+ }
+
+ size_t ecc_integer_len = EccUtil::eccIntegerByteLength(private_key.curve_type);
+
+ if (ecc_integer_len == 0)
+ {
+ throw tc::ArgumentOutOfRangeException("private_key or public_key curve type is not supported/valid.");
+ }
+
+ if (private_key.d.size() != ecc_integer_len)
+ {
+ throw tc::ArgumentOutOfRangeException("private_key was not a valid private key.");
+ }
+
+ if (public_key.Q.size() != (ecc_integer_len * 2))
+ {
+ throw tc::ArgumentOutOfRangeException("public_key was not a valid public key.");
+ }
+
+ mPrivateKey = private_key;
+ mPublicKey = public_key;
+
+ mState = State_Initialized;
+ }
+
+ /**
+ * @brief Utility function for generating an ECDH shared secret.
+ *
+ * @param[out] data Buffer to hold shared secret data.
+ * @param[in] data_size Size of @p data buffer.
+ *
+ * @post
+ * - The generated shared secret is written to data up to data_size bytes.
+ * - Shared secret length cannot exceed the bitsize of the curve.
+ *
+ * @throw tc::crypto::ArgumentNullException @p data_size > 0 but @p data was not nullptr.
+ */
+ void generateSharedSecretBytes(byte_t* data, size_t data_size)
+ {
+ if (mState != State_Initialized)
+ return;
+
+ // todo input sani logic here
+ if (data_size > 0 && data != nullptr)
+ {
+ throw tc::ArgumentNullException("data_size > 0 but data was not nullptr.");
+ }
+
+ mImpl.generateSharedSecret(mPrivateKey.curve_type, data, data_size, mPrivateKey.d.data(), mPrivateKey.d.size(), mPublicKey.Q.data(), mPublicKey.Q.size());
+ }
+
+private:
+ enum State
+ {
+ State_NotInitialized,
+ State_Initialized
+ };
+
+ State mState;
+ tc::crypto::EccKey mPrivateKey;
+ tc::crypto::EccKey mPublicKey;
+ detail::EcdhSharedSecretGeneratorImpl mImpl;
+};
+
+ /**
+ * @brief Utility function for generating an ECDH shared secret.
+ *
+ * @param[out] data Buffer to hold shared secret data.
+ * @param[in] data_size Size of @p data buffer.
+ * @param[in] private_key ECC private key of the current party.
+ * @param[in] public_key ECC public key of other party.
+ *
+ * @post
+ * - The generated shared secret is written to data up to data_size bytes.
+ * - Shared secret length cannot exceed the bitsize of the curve.
+ *
+ * @throw tc::crypto::ArgumentNullException @p data_size > 0 but @p data was not nullptr.
+ * @throw tc::crypto::ArgumentException @p private_key was not a valid private key.
+ * @throw tc::crypto::ArgumentException @p public_key was not a valid public key.
+ * @throw tc::crypto::ArgumentException @p private_key or @p public_key curve type is not supported/valid.
+ * @throw tc::crypto::ArgumentException @p private_key and @p public_key did not have the same curve type.
+ */
+void GenerateEcdhSharedSecret(byte_t* data, size_t data_size, const EccKey& private_key, const EccKey& public_key);
+
+}} // namespace tc::crypto
\ No newline at end of file
diff --git a/include/tc/crypto/detail/BlockUtilImpl.h b/include/tc/crypto/detail/BlockUtilImpl.h
index 791a2d4f..1774cd1d 100644
--- a/include/tc/crypto/detail/BlockUtilImpl.h
+++ b/include/tc/crypto/detail/BlockUtilImpl.h
@@ -29,6 +29,30 @@ inline void incr_counter(byte_t* counter, uint64_t incr)
}
}
+template <>
+inline void incr_counter<8>(byte_t* counter, uint64_t incr)
+{
+ tc::bn::be64* counter_words = (tc::bn::be64*)counter;
+
+ uint64_t carry = incr;
+ while (carry > 0)
+ {
+ uint64_t word = counter_words[0].unwrap();
+ uint64_t remaining = std::numeric_limits::max() - word;
+
+ if (remaining > carry)
+ {
+ counter_words[0].wrap(word + carry);
+ carry = 0;
+ }
+ else
+ {
+ counter_words[0].wrap(carry - remaining - 1);
+ carry = 1;
+ }
+ }
+}
+
template <>
inline void incr_counter<16>(byte_t* counter, uint64_t incr)
{
@@ -59,6 +83,12 @@ inline void xor_block(byte_t* dst, const byte_t* src_a, const byte_t* src_b)
for (size_t i = 0; i < BlockSize; i++) { dst[i] = src_a[i] ^ src_b[i];}
}
+template <>
+inline void xor_block<8>(byte_t* dst, const byte_t* src_a, const byte_t* src_b)
+{
+ ((uint64_t*)dst)[0] = ((uint64_t*)src_a)[0] ^ ((uint64_t*)src_b)[0];
+}
+
template <>
inline void xor_block<16>(byte_t* dst, const byte_t* src_a, const byte_t* src_b)
{
diff --git a/include/tc/crypto/detail/EccKeyGeneratorImpl.h b/include/tc/crypto/detail/EccKeyGeneratorImpl.h
new file mode 100644
index 00000000..c2b686c5
--- /dev/null
+++ b/include/tc/crypto/detail/EccKeyGeneratorImpl.h
@@ -0,0 +1,96 @@
+ /**
+ * @file EccKeyGeneratorImpl.h
+ * @brief Declaration of tc::crypto::detail::EccKeyGeneratorImpl
+ * @author Jack (jakcron)
+ * @version 0.1
+ * @date 2025/05/24
+ **/
+#pragma once
+#include
+
+#include
+#include
+#include
+#include
+
+namespace tc { namespace crypto { namespace detail {
+
+ /**
+ * @class EccKeyGeneratorImpl
+ * @brief This class implements the ECC key generation.
+ */
+class EccKeyGeneratorImpl
+{
+public:
+ /**
+ * @brief Default constructor
+ * @details
+ * This initializes ECC key generator state.
+ */
+ EccKeyGeneratorImpl();
+
+ /**
+ * @brief Destructor
+ * @details
+ * Cleans up ECC key generator state.
+ */
+ ~EccKeyGeneratorImpl();
+
+ /**
+ * @brief Generate an ECC key.
+ *
+ * @param[in] ec_type Type of Elliptic Curve @ref EccCurveType
+ * @param[out] d Buffer to store private component.
+ * @param[in] d_size Size of private component buffer.
+ * @param[out] Q Buffer to store public component.
+ * @param[in] Q_size Size of public component buffer.
+ *
+ * @pre
+ * - @p ec_type must be of type @ref EccCurveType
+ * @post
+ * - Key components are exported if the related buffers were not null.
+ *
+ * @note
+ * - Key components can be optionally not exported if the corresponding input variables are null and zero.
+ *
+ * @throw tc::ArgumentOutOfRangeException @p ec_type was not of type @ref EccCurveType
+ * @throw tc::crypto::CryptoException An unexpected error has occurred.
+ * @throw tc::crypto::CryptoException Something failed during generation of a key.
+ * @throw tc::crypto::CryptoException The random generator failed to generate non-zeros.
+ * @throw tc::ArgumentException @p d was not null, but @p d_size was not large enough.
+ * @throw tc::ArgumentException @p Q was not null, but @p Q_size was not large enough.
+ */
+ void generateKey(EccCurveType ec_type, byte_t* d, size_t d_size, byte_t* Q, size_t Q_size);
+
+ /**
+ * @brief Generate an ECC Public key based on the private component.
+ *
+ * @param[in] ec_type Type of Elliptic Curve @ref EccCurveType
+ * @param[in] d Buffer to store private component.
+ * @param[in] d_size Size of private component buffer.
+ * @param[out] Q Buffer to store public component.
+ * @param[in] Q_size Size of public component buffer.
+ *
+ * @pre
+ * - @p ec_type must be of type @ref EccCurveType
+ * @post
+ * - Key components are exported if the related buffers were not null.
+ *
+ * @note
+ * - Key components can be optionally not exported if the corresponding input variables are null and zero.
+ *
+ * @throw tc::ArgumentOutOfRangeException @p ec_type was not of type @ref EccCurveType
+ * @throw tc::crypto::CryptoException An unexpected error has occurred.
+ * @throw tc::crypto::CryptoException Something failed during generation of a key.
+ * @throw tc::ArgumentException @p d was null, or @p d_size was not large enough.
+ * @throw tc::ArgumentException @p Q was not null, but @p Q_size was not large enough.
+ */
+ void generatePublicKey(EccCurveType ec_type, byte_t* d, size_t d_size, byte_t* Q, size_t Q_size);
+private:
+ static const std::string kClassName;
+
+ struct ImplCtx;
+ std::unique_ptr mImplCtx;
+};
+
+}}} // namespace tc::crypto::detail
\ No newline at end of file
diff --git a/include/tc/crypto/detail/EcdhSharedSecretGeneratorImpl.h b/include/tc/crypto/detail/EcdhSharedSecretGeneratorImpl.h
new file mode 100644
index 00000000..9bc49b19
--- /dev/null
+++ b/include/tc/crypto/detail/EcdhSharedSecretGeneratorImpl.h
@@ -0,0 +1,74 @@
+ /**
+ * @file EcdhSharedSecretGeneratorImpl.h
+ * @brief Declaration of tc::crypto::detail::EcdhSharedSecretGeneratorImpl
+ * @author Jack (jakcron)
+ * @version 0.1
+ * @date 2025/08/04
+ **/
+#pragma once
+#include
+
+#include
+#include
+#include
+#include
+
+namespace tc { namespace crypto { namespace detail {
+
+ /**
+ * @class EcdhSharedSecretGeneratorImpl
+ * @brief This class implements the ECDH shared secret generation.
+ */
+class EcdhSharedSecretGeneratorImpl
+{
+public:
+ /**
+ * @brief Default constructor
+ * @details
+ * This initializes ECDH shared secret generator state.
+ */
+ EcdhSharedSecretGeneratorImpl();
+
+ /**
+ * @brief Destructor
+ * @details
+ * Cleans up ECDH shared secret generator state.
+ */
+ ~EcdhSharedSecretGeneratorImpl();
+
+ /**
+ * @brief Generate an ECDH shared secret based on own prvate key, external public key.
+ *
+ * @param[in] ec_type Type of Elliptic Curve @ref EccCurveType
+ * @param[out] z Buffer to store shared secret.
+ * @param[in] z_size Size of shared secret buffer.
+ * @param[in] d Buffer to store private component.
+ * @param[in] d_size Size of private component buffer.
+ * @param[in] Q Buffer to store public component.
+ * @param[in] Q_size Size of public component buffer.
+ *
+ * @pre
+ * - @p ec_type must be of type @ref EccCurveType
+ * - @p d & @p Q must be from the same curve.
+ * @post
+ * - @p z will be populated with generated shared secret.
+ *
+ * @note
+ * - Secret can be optionally not generated if the corresponding input variables are null and zero.
+ *
+ * @throw tc::ArgumentOutOfRangeException @p ec_type was not of type @ref EccCurveType
+ * @throw tc::crypto::CryptoException An unexpected error has occurred.
+ * @throw tc::crypto::CryptoException Something failed during generation of the shared secret.
+ * @throw tc::ArgumentException @p z was not null, but @p z_size was not large enough.
+ * @throw tc::ArgumentException @p d was not null, but @p d_size was not large enough.
+ * @throw tc::ArgumentException @p Q was not null, but @p Q_size was not large enough.
+ */
+ void generateSharedSecret(EccCurveType ec_type, byte_t* z, size_t z_size, const byte_t* d, size_t d_size, const byte_t* Q, size_t Q_size);
+private:
+ static const std::string kClassName;
+
+ struct ImplCtx;
+ std::unique_ptr mImplCtx;
+};
+
+}}} // namespace tc::crypto::detail
\ No newline at end of file
diff --git a/include/tc/os/Environment.h b/include/tc/os/Environment.h
index faa5e33a..55cdd993 100644
--- a/include/tc/os/Environment.h
+++ b/include/tc/os/Environment.h
@@ -2,12 +2,15 @@
* @file Environment.h
* @brief Declarations for API resources for accessing run-time environment
* @author Jack (jakcron)
- * @version 0.1
- * @date 2020/06/12
+ * @version 0.2
+ * @date 2025/08/05
**/
#pragma once
#include
#include
+#include
+
+#include
namespace tc { namespace os {
@@ -25,4 +28,17 @@ namespace tc { namespace os {
*/
bool getEnvVar(const std::string& name, std::string& value);
+ /**
+ * @brief Get OS defined temporary directory path.
+ *
+ * @details This function returns the temporary directory path, that can be used for storing files during run-time of the application.
+ *
+ * @param[out] dir_path Refence to path object to populate with temporary directory path.
+ *
+ * @post @p dir_path will contain the temporary directory path.
+ *
+ * @throws tc::io::DirectoryNotFoundException Temporary directory could not be determined.
+ */
+void getTempDirPath(tc::io::Path& dir_path);
+
}} // namespace tc::cli
\ No newline at end of file
diff --git a/src/crypto/EccKey.cpp b/src/crypto/EccKey.cpp
new file mode 100644
index 00000000..268a112a
--- /dev/null
+++ b/src/crypto/EccKey.cpp
@@ -0,0 +1,58 @@
+#include
+#include
+#include
+
+tc::crypto::EccPublicKey::EccPublicKey(EccCurveType curve_type, const byte_t* Q, size_t Q_size)
+{
+ if (EccUtil::eccIntegerByteLength(curve_type) == 0) throw tc::ArgumentException("tc::crypto::EccPublicKey()", "curve_type was not supported/valid");
+
+ if (Q != nullptr && Q_size != 0)
+ {
+ this->curve_type = curve_type;
+ this->d = tc::ByteData();
+ this->Q = tc::ByteData(Q, Q_size);
+ }
+}
+
+tc::crypto::EccPrivateKey::EccPrivateKey(EccCurveType curve_type, const byte_t* d, size_t d_size, const byte_t* Q, size_t Q_size)
+{
+ if (EccUtil::eccIntegerByteLength(curve_type) == 0) throw tc::ArgumentException("tc::crypto::EccPublicKey()", "curve_type was not supported/valid");
+
+ if (d != nullptr && d_size != 0 && Q != nullptr && Q_size != 0)
+ {
+ this->curve_type = curve_type;
+ this->d = tc::ByteData(d, d_size);
+ this->Q = tc::ByteData(Q, Q_size);
+ }
+}
+
+tc::crypto::EccPrivateKey::EccPrivateKey(EccCurveType curve_type, const byte_t* d, size_t d_size)
+{
+ size_t ecc_int_byte_length = EccUtil::eccIntegerByteLength(curve_type);
+ if (ecc_int_byte_length == 0) throw tc::ArgumentException("tc::crypto::EccPublicKey()", "curve_type was not supported/valid");
+
+ if (d != nullptr && d_size != 0)
+ {
+ this->curve_type = curve_type;
+ this->d = tc::ByteData(d, d_size);
+
+ tc::crypto::EccKey pub_key;
+ GenerateEccPublicKey(pub_key, *this);
+
+ this->Q = pub_key.Q;
+ }
+}
+
+tc::crypto::EccKey tc::crypto::EccPrivateKey::getPublicKey()
+{
+ // generate public component if not present
+ if (this->Q.data() == nullptr || this->Q.size() == 0)
+ {
+ tc::crypto::EccKey pub_key;
+ GenerateEccPublicKey(pub_key, *this);
+
+ this->Q = pub_key.Q;
+ }
+
+ return EccPublicKey(this->curve_type, this->Q.data(), this->Q.size());
+}
\ No newline at end of file
diff --git a/src/crypto/EccKeyGenerator.cpp b/src/crypto/EccKeyGenerator.cpp
new file mode 100644
index 00000000..a8642827
--- /dev/null
+++ b/src/crypto/EccKeyGenerator.cpp
@@ -0,0 +1,13 @@
+#include
+
+void tc::crypto::GenerateEccKey(EccKey& key, EccCurveType curve_type)
+{
+ tc::crypto::EccKeyGenerator impl;
+ impl.generateKey(key, curve_type);
+}
+
+void tc::crypto::GenerateEccPublicKey(EccKey& public_key, const EccKey& private_key)
+{
+ tc::crypto::EccKeyGenerator impl;
+ impl.generatePublicKey(public_key, private_key);
+}
\ No newline at end of file
diff --git a/src/crypto/EccUtil.cpp b/src/crypto/EccUtil.cpp
new file mode 100644
index 00000000..b090bc49
--- /dev/null
+++ b/src/crypto/EccUtil.cpp
@@ -0,0 +1,59 @@
+#include
+
+size_t tc::crypto::EccUtil::eccIntegerBitLength(EccCurveType curve_type)
+{
+ size_t ecc_int_bit_length = 0;
+ switch (curve_type)
+ {
+ case (ECC_CURVE_TYPE_SECP192R1):
+ ecc_int_bit_length = 192;
+ break;
+ case (ECC_CURVE_TYPE_SECP224R1):
+ ecc_int_bit_length = 224;
+ break;
+ case (ECC_CURVE_TYPE_SECP256R1):
+ ecc_int_bit_length = 256;
+ break;
+ case (ECC_CURVE_TYPE_SECP384R1):
+ ecc_int_bit_length = 384;
+ break;
+ case (ECC_CURVE_TYPE_SECP521R1):
+ ecc_int_bit_length = 521;
+ break;
+ case (ECC_CURVE_TYPE_BP256R1):
+ ecc_int_bit_length = 256;
+ break;
+ case (ECC_CURVE_TYPE_BP384R1):
+ ecc_int_bit_length = 384;
+ break;
+ case (ECC_CURVE_TYPE_BP512R1):
+ ecc_int_bit_length = 512;
+ break;
+ case (ECC_CURVE_TYPE_CURVE25519):
+ ecc_int_bit_length = 255;
+ break;
+ case (ECC_CURVE_TYPE_SECP192K1):
+ ecc_int_bit_length = 192;
+ break;
+ case (ECC_CURVE_TYPE_SECP224K1):
+ ecc_int_bit_length = 224;
+ break;
+ case (ECC_CURVE_TYPE_SECP256K1):
+ ecc_int_bit_length = 256;
+ break;
+ case (ECC_CURVE_TYPE_CURVE448):
+ ecc_int_bit_length = 448;
+ break;
+ default:
+ ecc_int_bit_length = 0;
+ break;
+ }
+
+ return ecc_int_bit_length;
+}
+
+size_t tc::crypto::EccUtil::eccIntegerByteLength(EccCurveType curve_type)
+{
+ size_t ecc_int_bit_length = eccIntegerBitLength(curve_type);
+ return ecc_int_bit_length / 8 + (ecc_int_bit_length % 8 != 0);
+}
diff --git a/src/crypto/EcdhSharedSecretGenerator.cpp b/src/crypto/EcdhSharedSecretGenerator.cpp
new file mode 100644
index 00000000..870c1e34
--- /dev/null
+++ b/src/crypto/EcdhSharedSecretGenerator.cpp
@@ -0,0 +1,8 @@
+#include
+
+void tc::crypto::GenerateEcdhSharedSecret(byte_t* data, size_t data_size, const EccKey& private_key, const EccKey& public_key)
+{
+ tc::crypto::EcdhSharedSecretGenerator gen;
+ gen.initialize(private_key, public_key);
+ gen.generateSharedSecretBytes(data, data_size);
+}
\ No newline at end of file
diff --git a/src/crypto/detail/EccKeyGeneratorImpl.cpp b/src/crypto/detail/EccKeyGeneratorImpl.cpp
new file mode 100644
index 00000000..0524e05b
--- /dev/null
+++ b/src/crypto/detail/EccKeyGeneratorImpl.cpp
@@ -0,0 +1,248 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+const std::string tc::crypto::detail::EccKeyGeneratorImpl::kClassName = "tc::crypto::detail::EccKeyGeneratorImpl";
+
+struct tc::crypto::detail::EccKeyGeneratorImpl::ImplCtx
+{
+ mbedtls_ctr_drbg_context ctr_drbg;
+ mbedtls_entropy_context entropy;
+};
+
+tc::crypto::detail::EccKeyGeneratorImpl::EccKeyGeneratorImpl() :
+ mImplCtx(new ImplCtx())
+{
+ mbedtls_entropy_init( &(mImplCtx->entropy) );
+ mbedtls_ctr_drbg_init( &(mImplCtx->ctr_drbg) );
+
+ int ret = mbedtls_ctr_drbg_seed( &(mImplCtx->ctr_drbg), mbedtls_entropy_func, &(mImplCtx->entropy), (const unsigned char *)kClassName.c_str(), kClassName.size() );
+ switch (ret)
+ {
+ case (0):
+ break;
+ case (MBEDTLS_ERR_ENTROPY_SOURCE_FAILED):
+ throw tc::crypto::CryptoException(kClassName, "mbedtls_ctr_drbg_seed() Entropy source failed");
+ default:
+ throw tc::crypto::CryptoException(kClassName, "mbedtls_ctr_drbg_seed() An unexpected error occurred");
+ }
+}
+
+tc::crypto::detail::EccKeyGeneratorImpl::~EccKeyGeneratorImpl()
+{
+ mbedtls_ctr_drbg_free( &(mImplCtx->ctr_drbg) );
+ mbedtls_entropy_free( &(mImplCtx->entropy) );
+}
+
+static mbedtls_ecp_group_id convertToMbedtlsEcpGroupId(tc::crypto::EccCurveType ec_type)
+{
+ mbedtls_ecp_group_id group_id = MBEDTLS_ECP_DP_NONE;
+ switch (ec_type)
+ {
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP192R1:
+ group_id = MBEDTLS_ECP_DP_SECP192R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP224R1:
+ group_id = MBEDTLS_ECP_DP_SECP224R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP256R1:
+ group_id = MBEDTLS_ECP_DP_SECP256R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP384R1:
+ group_id = MBEDTLS_ECP_DP_SECP384R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP521R1:
+ group_id = MBEDTLS_ECP_DP_SECP521R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP256R1:
+ group_id = MBEDTLS_ECP_DP_BP256R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP384R1:
+ group_id = MBEDTLS_ECP_DP_BP384R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP512R1:
+ group_id = MBEDTLS_ECP_DP_BP512R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_CURVE25519:
+ group_id = MBEDTLS_ECP_DP_CURVE25519;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP192K1:
+ group_id = MBEDTLS_ECP_DP_SECP192K1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP224K1:
+ group_id = MBEDTLS_ECP_DP_SECP224K1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP256K1:
+ group_id = MBEDTLS_ECP_DP_SECP256K1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_CURVE448:
+ group_id = MBEDTLS_ECP_DP_CURVE448;
+ break;
+ }
+
+ return group_id;
+}
+
+void tc::crypto::detail::EccKeyGeneratorImpl::generateKey(EccCurveType ec_type, byte_t* d, size_t d_size, byte_t* q, size_t q_size)
+{
+ mbedtls_ecp_group_id group_id = convertToMbedtlsEcpGroupId(ec_type);
+ if (group_id == MBEDTLS_ECP_DP_NONE)
+ {
+ throw tc::ArgumentOutOfRangeException(kClassName, fmt::format("ec_type ({}) was not valid.", (uint32_t)ec_type));
+ }
+
+ mbedtls_ecp_keypair key;
+ mbedtls_ecp_keypair_init( &key );
+
+ int ret = 1;
+
+ // generate key
+ ret = mbedtls_ecp_gen_key( group_id, &key, mbedtls_ctr_drbg_random, &(mImplCtx->ctr_drbg) );
+ switch (ret)
+ {
+ case (0):
+ break;
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_gen_key() An unexpected error occurred. {:x})", ret));
+
+ }
+
+ // determine curve integer length for this key
+ size_t p_len = mbedtls_mpi_size( &key.grp.P );
+
+ // export private component
+ if (d != nullptr)
+ {
+ if (d_size < p_len)
+ {
+ throw tc::ArgumentNullException(kClassName, "d was not null, but d_size was insufficent to store private exponent");
+ }
+
+ ret = mbedtls_mpi_write_binary( &key.d, d, std::min(d_size, p_len) );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_write_binary() An unexpected error occurred. {:x})", ret));
+ }
+ }
+
+ // export public component
+ if (q != nullptr)
+ {
+ // when writing the public component, it seems to be standard to reserve the full integer point for X,Y of Q, regardless of how many bits are actually used
+ // even if Q.Y is empty (length 0)
+ // if key is < min size
+ if (q_size < (p_len * 2))
+ {
+ throw tc::ArgumentNullException(kClassName, "q was not null, q_size was insufficent to store public exponent");
+ }
+
+ ret = mbedtls_mpi_write_binary( &key.Q.X, q, p_len );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_write_binary() An unexpected error occurred. {:x})", ret));
+ }
+
+ ret = mbedtls_mpi_write_binary( &key.Q.Y, q + p_len, p_len );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_write_binary() An unexpected error occurred. {:x})", ret));
+ }
+ }
+
+ // clear key from mbedtls context
+ mbedtls_ecp_keypair_free( &key );
+}
+
+void tc::crypto::detail::EccKeyGeneratorImpl::generatePublicKey(EccCurveType ec_type, byte_t* d, size_t d_size, byte_t* q, size_t q_size)
+{
+ mbedtls_ecp_group_id group_id = convertToMbedtlsEcpGroupId(ec_type);
+ if (group_id == MBEDTLS_ECP_DP_NONE) { throw tc::ArgumentOutOfRangeException(kClassName, "ec_type was not valid."); }
+ if (d == nullptr) { throw tc::ArgumentNullException(kClassName, "d was null"); }
+
+ mbedtls_ecp_keypair key;
+ mbedtls_ecp_keypair_init( &key );
+
+ int ret = 1;
+ // load group
+ ret = mbedtls_ecp_group_load( &key.grp, group_id );
+ switch (ret)
+ {
+ case (0):
+ break;
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_group_load() An unexpected error occurred. {:x})", ret));
+ }
+
+ // import private component (d)
+ ret = mbedtls_mpi_read_binary( &key.d, d, d_size );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_read_binary() An unexpected error occurred.", ret));
+ }
+
+ // generate public component (Q) from private exponent (d) and base point (G)
+ ret = mbedtls_ecp_mul( &key.grp, &key.Q, &key.d, &key.grp.G, mbedtls_ctr_drbg_random, &(mImplCtx->ctr_drbg) );
+ switch (ret)
+ {
+ case (0):
+ break;
+ case (MBEDTLS_ERR_ECP_INVALID_KEY):
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_mul() Invalid public or private key. {:x})", ret));
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_mul() An unexpected error occurred. {:x})", ret));
+ }
+
+ // export public component
+ if (q != nullptr)
+ {
+ size_t p_len = mbedtls_mpi_size( &key.grp.P );
+
+ // if key is < min size
+ if (q_size < (p_len * 2))
+ {
+ throw tc::ArgumentNullException(kClassName, "q was not null, q_size was insufficent to store public exponent");
+ }
+
+ ret = mbedtls_mpi_write_binary( &key.Q.X, q, p_len );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_write_binary() An unexpected error occurred. {:x})", ret));
+ }
+
+ ret = mbedtls_mpi_write_binary( &key.Q.Y, q + p_len, p_len );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_write_binary() An unexpected error occurred. {:x})", ret));
+ }
+ }
+
+ // clear key from mbedtls context
+ mbedtls_ecp_keypair_free( &key );
+}
\ No newline at end of file
diff --git a/src/crypto/detail/EcdhSharedSecretGeneratorImpl.cpp b/src/crypto/detail/EcdhSharedSecretGeneratorImpl.cpp
new file mode 100644
index 00000000..6799e5a0
--- /dev/null
+++ b/src/crypto/detail/EcdhSharedSecretGeneratorImpl.cpp
@@ -0,0 +1,226 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+const std::string tc::crypto::detail::EcdhSharedSecretGeneratorImpl::kClassName = "tc::crypto::detail::EcdhSharedSecretGeneratorImpl";
+
+struct tc::crypto::detail::EcdhSharedSecretGeneratorImpl::ImplCtx
+{
+ mbedtls_ctr_drbg_context ctr_drbg;
+ mbedtls_entropy_context entropy;
+
+ mbedtls_ecp_group grp; // ec group
+ mbedtls_mpi d; // source private key
+ mbedtls_ecp_point Q; // source public key
+ mbedtls_ecp_point P; // target point
+};
+
+tc::crypto::detail::EcdhSharedSecretGeneratorImpl::EcdhSharedSecretGeneratorImpl() :
+ mImplCtx(new ImplCtx())
+{
+ mbedtls_entropy_init( &(mImplCtx->entropy) );
+ mbedtls_ctr_drbg_init( &(mImplCtx->ctr_drbg) );
+
+ int ret = mbedtls_ctr_drbg_seed( &(mImplCtx->ctr_drbg), mbedtls_entropy_func, &(mImplCtx->entropy), (const unsigned char *)kClassName.c_str(), kClassName.size() );
+ switch (ret)
+ {
+ case (0):
+ break;
+ case (MBEDTLS_ERR_ENTROPY_SOURCE_FAILED):
+ throw tc::crypto::CryptoException(kClassName, "mbedtls_ctr_drbg_seed() Entropy source failed");
+ default:
+ throw tc::crypto::CryptoException(kClassName, "mbedtls_ctr_drbg_seed() An unexpected error occurred");
+ }
+
+ mbedtls_ecp_group_init( &(mImplCtx->grp) );
+ mbedtls_mpi_init( &(mImplCtx->d) );
+ mbedtls_ecp_point_init( &(mImplCtx->Q) );
+ mbedtls_ecp_point_init( &(mImplCtx->P) );
+}
+
+tc::crypto::detail::EcdhSharedSecretGeneratorImpl::~EcdhSharedSecretGeneratorImpl()
+{
+ mbedtls_ecp_point_free( &(mImplCtx->P) );
+ mbedtls_ecp_point_free( &(mImplCtx->Q) );
+ mbedtls_mpi_free( &(mImplCtx->d) );
+ mbedtls_ecp_group_free( &(mImplCtx->grp) );
+ mbedtls_ctr_drbg_free( &(mImplCtx->ctr_drbg) );
+ mbedtls_entropy_free( &(mImplCtx->entropy) );
+}
+
+static mbedtls_ecp_group_id convertToMbedtlsEcpGroupId(tc::crypto::EccCurveType ec_type)
+{
+ mbedtls_ecp_group_id group_id = MBEDTLS_ECP_DP_NONE;
+ switch (ec_type)
+ {
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP192R1:
+ group_id = MBEDTLS_ECP_DP_SECP192R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP224R1:
+ group_id = MBEDTLS_ECP_DP_SECP224R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP256R1:
+ group_id = MBEDTLS_ECP_DP_SECP256R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP384R1:
+ group_id = MBEDTLS_ECP_DP_SECP384R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP521R1:
+ group_id = MBEDTLS_ECP_DP_SECP521R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP256R1:
+ group_id = MBEDTLS_ECP_DP_BP256R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP384R1:
+ group_id = MBEDTLS_ECP_DP_BP384R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP512R1:
+ group_id = MBEDTLS_ECP_DP_BP512R1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_CURVE25519:
+ group_id = MBEDTLS_ECP_DP_CURVE25519;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP192K1:
+ group_id = MBEDTLS_ECP_DP_SECP192K1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP224K1:
+ group_id = MBEDTLS_ECP_DP_SECP224K1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP256K1:
+ group_id = MBEDTLS_ECP_DP_SECP256K1;
+ break;
+ case tc::crypto::EccCurveType::ECC_CURVE_TYPE_CURVE448:
+ group_id = MBEDTLS_ECP_DP_CURVE448;
+ break;
+ }
+
+ return group_id;
+}
+
+void tc::crypto::detail::EcdhSharedSecretGeneratorImpl::generateSharedSecret(EccCurveType ec_type, byte_t* z, size_t z_size, const byte_t* d, size_t d_size, const byte_t* Q, size_t Q_size)
+{
+ mbedtls_ecp_group_id group_id = convertToMbedtlsEcpGroupId(ec_type);
+ if (group_id == MBEDTLS_ECP_DP_NONE)
+ {
+ throw tc::ArgumentOutOfRangeException(kClassName, fmt::format("ec_type ({}) was not valid.", (uint32_t)ec_type));
+ }
+
+ int ret;
+
+ // clear source d mpi
+ ret = mbedtls_mpi_lset( &(mImplCtx->d) , 0 );
+ switch (ret)
+ {
+ case (0):
+ break;
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_lset(d) An unexpected error occurred. {:x})", ret));
+ }
+
+ // clear source Q point
+ ret = mbedtls_ecp_set_zero( &(mImplCtx->Q) );
+ switch (ret)
+ {
+ case (0):
+ break;
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_set_zero(Q) An unexpected error occurred. {:x})", ret));
+ }
+
+ // clear source P point
+ ret = mbedtls_ecp_set_zero( &(mImplCtx->P) );
+ switch (ret)
+ {
+ case (0):
+ break;
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_set_zero(P) An unexpected error occurred. {:x})", ret));
+ }
+
+ // load ecp group
+ ret = mbedtls_ecp_group_load( &(mImplCtx->grp), group_id );
+ switch (ret)
+ {
+ case (0):
+ break;
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_group_load() An unexpected error occurred. {:x})", ret));
+ }
+
+ // get p_len from ecp group
+ size_t p_len = mbedtls_mpi_size( &(mImplCtx->grp.P) );
+
+ // import private component (d)
+ if (d_size < p_len)
+ {
+ throw tc::ArgumentOutOfRangeException(kClassName, fmt::format("d size was insufficent {} (expected {})", d_size, p_len));
+ }
+ ret = mbedtls_mpi_read_binary( &(mImplCtx->d), d, d_size );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_read_binary(d) An unexpected error occurred.", ret));
+ }
+
+ // import public component (Q)
+ if (Q_size < (p_len * 2))
+ {
+ throw tc::ArgumentOutOfRangeException(kClassName, fmt::format("Q size was insufficent {} (expected {})", Q_size, (p_len * 2)));
+ }
+ ret = mbedtls_mpi_read_binary( &(mImplCtx->Q.X), Q, p_len );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_read_binary(Q.X) An unexpected error occurred.", ret));
+ }
+ ret = mbedtls_mpi_read_binary( &(mImplCtx->Q.Y), Q + p_len, p_len );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_read_binary(Q.Y) An unexpected error occurred.", ret));
+ }
+
+ // generate shared secret point (P) from private component (d) and public component (Q)
+ ret = mbedtls_ecp_mul( &(mImplCtx->grp), &(mImplCtx->P), &(mImplCtx->d), &(mImplCtx->Q), mbedtls_ctr_drbg_random, &(mImplCtx->ctr_drbg) );
+ switch (ret)
+ {
+ case (0):
+ break;
+ case (MBEDTLS_ERR_ECP_INVALID_KEY):
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_mul() Invalid public or private key. {:x})", ret));
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_ecp_mul() An unexpected error occurred. {:x})", ret));
+ }
+
+ // export shared secret
+ if (z != nullptr)
+ {
+ // if key is < min size
+ if (z_size < p_len)
+ {
+ throw tc::ArgumentNullException(kClassName, "z was not null, z_size was insufficent to store shared secret");
+ }
+
+ ret = mbedtls_mpi_write_binary( &(mImplCtx->P.X), z, p_len );
+ switch (ret)
+ {
+ case (0):
+ break;
+ //case (MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL):
+ default:
+ throw tc::crypto::CryptoException(kClassName, fmt::format("mbedtls_mpi_write_binary() An unexpected error occurred. {:x})", ret));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/encode/Base64Util.cpp b/src/encode/Base64Util.cpp
index 19842190..c8dbbdc1 100644
--- a/src/encode/Base64Util.cpp
+++ b/src/encode/Base64Util.cpp
@@ -8,18 +8,14 @@ inline std::string byteDataAsString(const tc::ByteData& data)
{
std::string str = "";
- if (data.size() > 0)
+ for (size_t i = 0; i < data.size(); i++)
{
- str = std::string((const char*)data.data());
- }
-
- for (size_t i = 0; i < str.size(); i++)
- {
- if ( ! std::isprint(static_cast(str[i])) )
+ if ( ! std::isprint(static_cast(data[i])) )
{
- str = "";
break;
}
+
+ str.push_back(static_cast(data[i]));
}
return str;
diff --git a/src/os/Environment.cpp b/src/os/Environment.cpp
index ecdb68a3..fab1e536 100644
--- a/src/os/Environment.cpp
+++ b/src/os/Environment.cpp
@@ -37,4 +37,40 @@ bool tc::os::getEnvVar(const std::string& name, std::string& value)
}
#endif
return did_find_variable;
+}
+
+static const std::vector kOSTempDirEnvVarList = {"TMPDIR", "TMP", "TEMP", "TEMPDIR"};
+
+void tc::os::getTempDirPath(tc::io::Path& dir_path)
+{
+#if 0
+// #ifdef _WIN32
+ DWORD dir_buffer_size = MAX_PATH+1;
+ std::shared_ptr dir_buffer(new wchar_t[dir_buffer_size]);
+
+ DWORD dir_string_size = GetTempPath2W(dir_buffer_size, dir_buffer.get());
+
+ if (dir_string_size == 0)
+ {
+ throw tc::io::DirectoryNotFoundException("Operating system could not provide temporary directory.");
+ }
+
+ dir_path = tc::io::Path(std::u16string(dir_buffer.get()));
+#else
+ std::string dir_path_str = "";
+ for (size_t i = 0; i < kOSTempDirEnvVarList.size(); i++)
+ {
+ if (tc::os::getEnvVar(kOSTempDirEnvVarList[i], dir_path_str) && dir_path_str != "")
+ {
+ break;
+ }
+ }
+
+ if (dir_path_str.size() == 0)
+ {
+ throw tc::io::DirectoryNotFoundException("Operating system could not provide temporary directory.");
+ }
+
+ dir_path = tc::io::Path(dir_path_str);
+#endif
}
\ No newline at end of file
diff --git a/test/crypto_EccKeyGenerator_TestClass.cpp b/test/crypto_EccKeyGenerator_TestClass.cpp
new file mode 100644
index 00000000..1d36d8bb
--- /dev/null
+++ b/test/crypto_EccKeyGenerator_TestClass.cpp
@@ -0,0 +1,519 @@
+#include "crypto_EccKeyGenerator_TestClass.h"
+
+#include
+
+#include
+#include
+#include
+
+//---------------------------------------------------------
+
+crypto_EccKeyGenerator_TestClass::crypto_EccKeyGenerator_TestClass() :
+ mTestTag("tc::crypto::EccKeyGenerator"),
+ mTestResults()
+{
+}
+
+void crypto_EccKeyGenerator_TestClass::runAllTests(void)
+{
+ test_Class();
+ test_UtilFunc();
+ test_MultipleObjectsCreateDifferentData();
+ test_RepeatedCallsCreateDifferentData();
+}
+
+const std::string& crypto_EccKeyGenerator_TestClass::getTestTag() const
+{
+ return mTestTag;
+}
+
+const std::vector& crypto_EccKeyGenerator_TestClass::getTestResults() const
+{
+ return mTestResults;
+}
+
+//---------------------------------------------------------
+
+void crypto_EccKeyGenerator_TestClass::test_Class()
+{
+ TestResult test_result;
+ test_result.test_name = "test_Class";
+ test_result.result = "NOT RUN";
+ test_result.comments = "";
+
+ try
+ {
+ // create class to store key
+ tc::crypto::EccKey key;
+ tc::crypto::EccKey pubkey;
+
+ // curves to test
+ std::vector curve_test_list;
+ getCurveTestList(curve_test_list);
+
+ tc::crypto::EccKeyGenerator keygen;
+ for (size_t i = 0; i < curve_test_list.size(); i++)
+ {
+ // reset key
+ key.curve_type = tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP192R1;
+ key.d = tc::ByteData();
+ key.Q = tc::ByteData();
+
+ // generate key
+ keygen.generateKey(key, curve_test_list[i].curve_type);
+
+ // check key
+ if (key.curve_type != curve_test_list[i].curve_type)
+ {
+ throw tc::TestException(fmt::format(".generateKey({},key) key.curve_type ({}) did not match expected value ({})", curve_test_list[i].curve_name, (uint32_t)key.curve_type, (uint32_t)curve_test_list[i].curve_type));
+ }
+
+ size_t integer_len_bytes = align(curve_test_list[i].integer_bit_size, 8) / 8;
+ size_t expected_d_size = integer_len_bytes;
+ size_t expected_Q_size = integer_len_bytes*2;
+ if (key.d.size() != expected_d_size)
+ {
+ throw tc::TestException(fmt::format(".generateKey({},key) key.d.size() ({}) did not match expected value ({})", curve_test_list[i].curve_name, key.d.size(), expected_d_size));
+ }
+
+ if (key.Q.size() != expected_Q_size)
+ {
+ throw tc::TestException(fmt::format(".generateKey({},key) key.Q.size() ({}) did not match expected value ({})", curve_test_list[i].curve_name, key.Q.size(), expected_Q_size));
+ }
+
+ // generate public key
+ keygen.generatePublicKey(pubkey, key);
+
+ // check public key type
+ if (pubkey.curve_type != key.curve_type)
+ {
+ throw tc::TestException(fmt::format(".generatePublicKey(pubkey,key) (for curve {}) pubkey.curve_type ({}) did not match key.curve_type ({})", curve_test_list[i].curve_name, (uint32_t)pubkey.curve_type, (uint32_t)key.curve_type));
+ }
+
+ // check public key has no private component
+ if (pubkey.d.size() != 0)
+ {
+ throw tc::TestException(fmt::format(".generatePublicKey(pubkey,key) (for curve {}) pubkey.d.size() ({}) was not 0", curve_test_list[i].curve_name, pubkey.d.size()));
+ }
+
+ // check public key has same public component as key
+ if (pubkey.Q.size() != key.Q.size())
+ {
+ throw tc::TestException(fmt::format(".generatePublicKey(pubkey,key) (for curve {}) pubkey.Q.size() ({}) did not match key.Q.size() ({})", curve_test_list[i].curve_name, pubkey.Q.size(), key.Q.size()));
+ }
+ if (memcmp(pubkey.Q.data(), key.Q.data(), pubkey.Q.size()) != 0)
+ {
+ throw tc::TestException(fmt::format(".generatePublicKey(pubkey,key) (for curve {}) pubkey.Q.data() did not match key.Q.data()", curve_test_list[i].curve_name));
+ }
+ }
+
+ // record result
+ test_result.result = "PASS";
+ test_result.comments = "";
+ }
+ catch (const tc::TestException& e)
+ {
+ // record result
+ test_result.result = "FAIL";
+ test_result.comments = e.what();
+ }
+ catch (const std::exception& e)
+ {
+ // record result
+ test_result.result = "UNHANDLED EXCEPTION";
+ test_result.comments = e.what();
+ }
+
+ // add result to list
+ mTestResults.push_back(std::move(test_result));
+}
+
+void crypto_EccKeyGenerator_TestClass::test_UtilFunc()
+{
+ TestResult test_result;
+ test_result.test_name = "test_UtilFunc";
+ test_result.result = "NOT RUN";
+ test_result.comments = "";
+
+ try
+ {
+ // create class to store key
+ tc::crypto::EccKey key;
+ tc::crypto::EccKey pubkey;
+
+ // curves to test
+ std::vector curve_test_list;
+ getCurveTestList(curve_test_list);
+
+ for (size_t i = 0; i < curve_test_list.size(); i++)
+ {
+ // reset key
+ key.curve_type = tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP192R1;
+ key.d = tc::ByteData();
+ key.Q = tc::ByteData();
+
+ // generate key
+ tc::crypto::GenerateEccKey(key, curve_test_list[i].curve_type);
+
+ // check key
+ if (key.curve_type != curve_test_list[i].curve_type)
+ {
+ throw tc::TestException(fmt::format("tc::crypto::GenerateEccKey({},key) key.curve_type ({}) did not match expected value ({})", curve_test_list[i].curve_name, (uint32_t)key.curve_type, (uint32_t)curve_test_list[i].curve_type));
+ }
+
+ size_t integer_len_bytes = align(curve_test_list[i].integer_bit_size, 8) / 8;
+ size_t expected_d_size = integer_len_bytes;
+ size_t expected_Q_size = integer_len_bytes*2;
+ if (key.d.size() != expected_d_size)
+ {
+ throw tc::TestException(fmt::format("tc::crypto::GenerateEccKey({},key) key.d.size() ({}) did not match expected value ({})", curve_test_list[i].curve_name, key.d.size(), expected_d_size));
+ }
+
+ if (key.Q.size() != expected_Q_size)
+ {
+ throw tc::TestException(fmt::format("tc::crypto::GenerateEccKey({},key) key.Q.size() ({}) did not match expected value ({})", curve_test_list[i].curve_name, key.Q.size(), expected_Q_size));
+ }
+
+ // generate public key
+ tc::crypto::GenerateEccPublicKey(pubkey, key);
+
+ // check public key type
+ if (pubkey.curve_type != key.curve_type)
+ {
+ throw tc::TestException(fmt::format("tc::crypto::GenerateEccPublicKey(pubkey,key) (for curve {}) pubkey.curve_type ({}) did not match key.curve_type ({})", curve_test_list[i].curve_name, (uint32_t)pubkey.curve_type, (uint32_t)key.curve_type));
+ }
+
+ // check public key has no private component
+ if (pubkey.d.size() != 0)
+ {
+ throw tc::TestException(fmt::format("tc::crypto::GenerateEccPublicKey(pubkey,key) (for curve {}) pubkey.d.size() ({}) was not 0", curve_test_list[i].curve_name, pubkey.d.size()));
+ }
+
+ // check public key has same public component as key
+ if (pubkey.Q.size() != key.Q.size())
+ {
+ throw tc::TestException(fmt::format("tc::crypto::GenerateEccPublicKey(pubkey,key) (for curve {}) pubkey.Q.size() ({}) did not match key.Q.size() ({})", curve_test_list[i].curve_name, pubkey.Q.size(), key.Q.size()));
+ }
+ if (memcmp(pubkey.Q.data(), key.Q.data(), pubkey.Q.size()) != 0)
+ {
+ throw tc::TestException(fmt::format("tc::crypto::GenerateEccPublicKey(pubkey,key) (for curve {}) pubkey.Q.data() did not match key.Q.data()", curve_test_list[i].curve_name));
+ }
+ }
+
+ // record result
+ test_result.result = "PASS";
+ test_result.comments = "";
+ }
+ catch (const tc::TestException& e)
+ {
+ // record result
+ test_result.result = "FAIL";
+ test_result.comments = e.what();
+ }
+ catch (const std::exception& e)
+ {
+ // record result
+ test_result.result = "UNHANDLED EXCEPTION";
+ test_result.comments = e.what();
+ }
+
+ // add result to list
+ mTestResults.push_back(std::move(test_result));
+}
+
+void crypto_EccKeyGenerator_TestClass::test_MultipleObjectsCreateDifferentData()
+{
+ TestResult test_result;
+ test_result.test_name = "test_MultipleObjectsCreateDifferentData";
+ test_result.result = "NOT RUN";
+ test_result.comments = "";
+
+ try
+ {
+ // create class to store key
+ tc::crypto::EccKey key1, key2, key3;
+
+ // curves to test
+ std::vector curve_test_list;
+ getCurveTestList(curve_test_list);
+
+ // test for each curve type
+ tc::crypto::EccKeyGenerator keygen;
+ for (size_t i = 0; i < curve_test_list.size(); i++)
+ {
+ // generate keys
+ tc::crypto::EccKeyGenerator keygen1, keygen2, keygen3;
+ keygen1.generateKey(key1, curve_test_list[i].curve_type);
+ keygen2.generateKey(key2, curve_test_list[i].curve_type);
+ keygen3.generateKey(key3, curve_test_list[i].curve_type);
+
+ static const size_t kSimilarityThreshold = 3;
+
+ // check private component
+ size_t privateCmp12 = 0, privateCmp13 = 0, privateCmp23 = 0;
+
+ for (size_t i = 0; i < key1.d.size(); i++)
+ {
+ privateCmp12 += key1.d[i] == key2.d[i];
+ privateCmp13 += key1.d[i] == key3.d[i];
+ privateCmp23 += key2.d[i] == key3.d[i];
+ }
+
+ // check to see if any of the tests were similar
+ if (privateCmp12 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) privateKey 1 & privateKey 2 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, privateCmp12, tc::cli::FormatUtil::formatBytesAsString(key1.d, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.d, false, "")));
+ }
+ if (privateCmp13 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) privateKey 1 & privateKey 3 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, privateCmp13, tc::cli::FormatUtil::formatBytesAsString(key1.d, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.d, false, "")));
+ }
+ if (privateCmp23 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) privateKey 2 & privateKey 3 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, privateCmp23, tc::cli::FormatUtil::formatBytesAsString(key1.d, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.d, false, "")));
+ }
+
+ // check public component
+ size_t publicCmp12 = 0, publicCmp13 = 0, publicCmp23 = 0;
+
+ for (size_t i = 0; i < key1.Q.size(); i++)
+ {
+ if (!curve_test_list[i].curve_defines_Q_y && i >= (key1.Q.size()/2))
+ break;
+
+ publicCmp12 += key1.Q[i] == key2.Q[i];
+ publicCmp13 += key1.Q[i] == key3.Q[i];
+ publicCmp23 += key2.Q[i] == key3.Q[i];
+ }
+
+ // check to see if any of the tests were similar
+ if (publicCmp12 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) publicKey 1 & publicKey 2 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, publicCmp12, tc::cli::FormatUtil::formatBytesAsString(key1.Q, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.Q, false, "")));
+ }
+ if (publicCmp13 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) publicKey 1 & publicKey 3 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, publicCmp13, tc::cli::FormatUtil::formatBytesAsString(key1.Q, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.Q, false, "")));
+ }
+ if (publicCmp23 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) publicKey 2 & publicKey 3 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, publicCmp23, tc::cli::FormatUtil::formatBytesAsString(key1.Q, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.Q, false, "")));
+ }
+ }
+
+ // record result
+ test_result.result = "PASS";
+ test_result.comments = "";
+ }
+ catch (const tc::TestException& e)
+ {
+ // record result
+ test_result.result = "FAIL";
+ test_result.comments = e.what();
+ }
+ catch (const std::exception& e)
+ {
+ // record result
+ test_result.result = "UNHANDLED EXCEPTION";
+ test_result.comments = e.what();
+ }
+
+ // add result to list
+ mTestResults.push_back(std::move(test_result));
+}
+
+void crypto_EccKeyGenerator_TestClass::test_RepeatedCallsCreateDifferentData()
+{
+ TestResult test_result;
+ test_result.test_name = "test_RepeatedCallsCreateDifferentData";
+ test_result.result = "NOT RUN";
+ test_result.comments = "";
+
+ try
+ {
+ // create class to store key
+ tc::crypto::EccKey key1, key2, key3;
+
+ // curves to test
+ std::vector curve_test_list;
+ getCurveTestList(curve_test_list);
+
+ // test for each curve type
+ for (size_t i = 0; i < curve_test_list.size(); i++)
+ {
+ // generate keys
+ tc::crypto::EccKeyGenerator keygen;
+ keygen.generateKey(key1, curve_test_list[i].curve_type);
+ keygen.generateKey(key2, curve_test_list[i].curve_type);
+ keygen.generateKey(key3, curve_test_list[i].curve_type);
+
+ static const size_t kSimilarityThreshold = 3;
+
+ // check private component
+ size_t privateCmp12 = 0, privateCmp13 = 0, privateCmp23 = 0;
+
+ for (size_t i = 0; i < key1.d.size(); i++)
+ {
+ privateCmp12 += key1.d[i] == key2.d[i];
+ privateCmp13 += key1.d[i] == key3.d[i];
+ privateCmp23 += key2.d[i] == key3.d[i];
+ }
+
+ // check to see if any of the tests were similar
+ if (privateCmp12 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) privateKey 1 & privateKey 2 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, privateCmp12, tc::cli::FormatUtil::formatBytesAsString(key1.d, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.d, false, "")));
+ }
+ if (privateCmp13 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) privateKey 1 & privateKey 3 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, privateCmp13, tc::cli::FormatUtil::formatBytesAsString(key1.d, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.d, false, "")));
+ }
+ if (privateCmp23 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) privateKey 2 & privateKey 3 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, privateCmp23, tc::cli::FormatUtil::formatBytesAsString(key1.d, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.d, false, "")));
+ }
+
+ // check public component
+ size_t publicCmp12 = 0, publicCmp13 = 0, publicCmp23 = 0;
+
+ for (size_t i = 0; i < key1.Q.size(); i++)
+ {
+ if (!curve_test_list[i].curve_defines_Q_y && i >= (key1.Q.size()/2))
+ break;
+
+ publicCmp12 += key1.Q[i] == key2.Q[i];
+ publicCmp13 += key1.Q[i] == key3.Q[i];
+ publicCmp23 += key2.Q[i] == key3.Q[i];
+ }
+
+ // check to see if any of the tests were similar
+ if (publicCmp12 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) publicKey 1 & publicKey 2 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, publicCmp12, tc::cli::FormatUtil::formatBytesAsString(key1.Q, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.Q, false, "")));
+ }
+ if (publicCmp13 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) publicKey 1 & publicKey 3 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, publicCmp13, tc::cli::FormatUtil::formatBytesAsString(key1.Q, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.Q, false, "")));
+ }
+ if (publicCmp23 > kSimilarityThreshold)
+ {
+ throw tc::TestException(fmt::format("(curve {}) publicKey 2 & publicKey 3 has {:d} similar bytes ({} vs {})", curve_test_list[i].curve_name, publicCmp23, tc::cli::FormatUtil::formatBytesAsString(key1.Q, false, ""), tc::cli::FormatUtil::formatBytesAsString(key2.Q, false, "")));
+ }
+ }
+
+ // record result
+ test_result.result = "PASS";
+ test_result.comments = "";
+ }
+ catch (const tc::TestException& e)
+ {
+ // record result
+ test_result.result = "FAIL";
+ test_result.comments = e.what();
+ }
+ catch (const std::exception& e)
+ {
+ // record result
+ test_result.result = "UNHANDLED EXCEPTION";
+ test_result.comments = e.what();
+ }
+
+ // add result to list
+ mTestResults.push_back(std::move(test_result));
+}
+
+void crypto_EccKeyGenerator_TestClass::getCurveTestList(std::vector& curve_test_list) const
+{
+ curve_test_list.clear();
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_SECP192R1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP192R1,
+ 192,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_SECP224R1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP224R1,
+ 224,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_SECP256R1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP256R1,
+ 256,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_SECP384R1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP384R1,
+ 384,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_SECP521R1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP521R1,
+ 521,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_BP256R1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP256R1,
+ 256,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_BP384R1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP384R1,
+ 384,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_BP512R1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_BP512R1,
+ 512,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_CURVE25519",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_CURVE25519,
+ 255,
+ false
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_SECP192K1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP192K1,
+ 192,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_SECP224K1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP224K1,
+ 224,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_SECP256K1",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_SECP256K1,
+ 256,
+ true
+ });
+
+ curve_test_list.push_back({
+ "ECC_CURVE_TYPE_CURVE448",
+ tc::crypto::EccCurveType::ECC_CURVE_TYPE_CURVE448,
+ 448,
+ false
+ });
+}
diff --git a/test/crypto_EccKeyGenerator_TestClass.h b/test/crypto_EccKeyGenerator_TestClass.h
new file mode 100644
index 00000000..bde5663c
--- /dev/null
+++ b/test/crypto_EccKeyGenerator_TestClass.h
@@ -0,0 +1,38 @@
+#pragma once
+#include "ITestClass.h"
+#include
+
+class crypto_EccKeyGenerator_TestClass : public ITestClass
+{
+public:
+ crypto_EccKeyGenerator_TestClass();
+
+ // this will run the tests
+ void runAllTests();
+
+ // this is the label for this test (for filtering purposes)
+ const std::string& getTestTag() const;
+
+ // this is where the test results are written
+ const std::vector& getTestResults() const;
+private:
+ std::string mTestTag;
+ std::vector mTestResults;
+
+ void test_sbox();
+
+ void test_Class();
+ void test_UtilFunc();
+
+ void test_MultipleObjectsCreateDifferentData();
+ void test_RepeatedCallsCreateDifferentData();
+
+ struct sCurveInfo {
+ std::string curve_name;
+ tc::crypto::EccCurveType curve_type;
+ size_t integer_bit_size;
+ bool curve_defines_Q_y;
+ };
+
+ void getCurveTestList(std::vector& curve_test_list) const;
+};
\ No newline at end of file
diff --git a/test/main.cpp b/test/main.cpp
index 4f85297a..ac807eaf 100644
--- a/test/main.cpp
+++ b/test/main.cpp
@@ -94,6 +94,7 @@
#include "crypto_Rsa1024PssSha2512Signer_TestClass.h"
#include "crypto_Rsa2048PssSha2512Signer_TestClass.h"
#include "crypto_Rsa4096PssSha2512Signer_TestClass.h"
+#include "crypto_EccKeyGenerator_TestClass.h"
#include "ITestClass.h"
@@ -130,8 +131,10 @@ void runTest(std::vector& global_test_results, const std::regex
global_test_results.push_back(std::move(local_test_results));
}
-void outputResultsToStdout(const std::vector& global_test_results, const std::regex& include_result_regex, const std::regex& exclude_result_regex)
+int outputResultsToStdout(const std::vector& global_test_results, const std::regex& include_result_regex, const std::regex& exclude_result_regex)
{
+ int res = 0;
+
for (auto test_class_itr = global_test_results.begin(); test_class_itr != global_test_results.end(); test_class_itr++)
{
size_t total_tests = 0, total_passed_tests = 0;
@@ -154,7 +157,13 @@ void outputResultsToStdout(const std::vector& global_test_resul
fmt::print("\n");
}
fmt::print("[{:s}] END ({:d}/{:d} passed)\n", test_class_itr->tag, total_passed_tests, total_tests);
+
+ // change return value if tests failed
+ if (total_passed_tests < total_tests)
+ res -= 1;
}
+
+ return res;
}
int main(int argc, char** argv)
@@ -340,7 +349,8 @@ int main(int argc, char** argv)
runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests);
runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests);
runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests);
+ runTest(global_test_results, include_test_regex, exclude_test_regex, include_slow_tests);
// output results
- outputResultsToStdout(global_test_results, include_result_regex, exclude_result_regex);
+ return outputResultsToStdout(global_test_results, include_result_regex, exclude_result_regex);
}
\ No newline at end of file