Skip to content

Commit

Permalink
Add unit test for default TLS 1.2 / 1.3 cipher configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
0xg0nz0 committed Mar 31, 2024
1 parent a18aecc commit 4743845
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 87 deletions.
8 changes: 6 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ externalproject_add(wolfssl
GIT_TAG v5.7.0-stable
PREFIX ${CMAKE_BINARY_DIR}/wolfssl
BUILD_IN_SOURCE 1
CONFIGURE_COMMAND autoreconf -i COMMAND <SOURCE_DIR>/configure --prefix=<INSTALL_DIR> --enable-all --enable-harden --enable-keylog-export --disable-ech
CONFIGURE_COMMAND autoreconf -i COMMAND <SOURCE_DIR>/configure --prefix=<INSTALL_DIR> --enable-all --enable-harden --enable-keylog-export --enable-static --disable-ech
BUILD_COMMAND make -j ${NPROC}
BUILD_BYPRODUCTS ${CMAKE_BINARY_DIR}/wolfssl/lib/libwolfssl.a
INSTALL_COMMAND make install
UPDATE_COMMAND ""
)
Expand Down Expand Up @@ -69,6 +70,7 @@ externalproject_add(curl
)

set(WOLFSSL_INCLUDE_DIR ${CMAKE_BINARY_DIR}/wolfssl/include)
set(WOLFSSL_LIB_DIR ${CMAKE_BINARY_DIR}/wolfssl/lib)
set(NGHTTP3_INCLUDE_DIR ${CMAKE_BINARY_DIR}/nghttp3/include)
set(NGTCP2_INCLUDE_DIR ${CMAKE_BINARY_DIR}/ngtcp2/include)
set(CURL_INCLUDE_DIR ${CMAKE_BINARY_DIR}/curl/include)
Expand All @@ -88,14 +90,16 @@ target_include_directories(iggy PRIVATE
${NGTCP2_INCLUDE_DIR}
${CURL_INCLUDE_DIR}
)
add_dependencies(iggy curl)
add_dependencies(iggy curl wolfssl)
target_link_libraries(
iggy PRIVATE

ada::ada
fmt::fmt
libuv::uv_a
unofficial-sodium::sodium
${CURL_LIB_DIR}/libcurl.a
${WOLFSSL_LIB_DIR}/libwolfssl.a
)

# even though this is related to unit tests, to get a full report we need to ensure that
Expand Down
209 changes: 130 additions & 79 deletions sdk/net/crypto/ssl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,101 +2,152 @@
#include <numeric>
#include "fmt/format.h"

const std::vector<std::string> iggy::ssl::SSLOptions::getDefaultCipherList() {
const std::vector<std::string> iggy::ssl::SSLOptions::getDefaultCipherList(iggy::ssl::ProtocolVersion protocolVersion) {
auto ciphers = std::vector<std::string>();
#if defined(HAVE_AESGCM) && !defined(NO_DH)
#ifdef WOLFSSL_TLS13
ciphers.push_back("TLS13-AES128-GCM-SHA256");
#ifndef WOLFSSL_NO_TLS12
ciphers.push_back("DHE-PSK-AES128-GCM-SHA256");
#endif

// References:
// - https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Security_Cheat_Sheet.html
// - https://ssl-config.mozilla.org

if (protocolVersion == iggy::ssl::ProtocolVersion::TLSV1_3) {
// sanity check to make sure TLS 1.3 is compiled in
#ifdef WOLFSSL_NO_TLS13
throw std::runtime_error("TLS 1.3 is not supported by this build of WolfSSL");
#else
ciphers.push_back("DHE-PSK-AES128-GCM-SHA256");
#endif
#elif defined(HAVE_AESGCM) && defined(WOLFSSL_TLS13)
ciphers.push_back("TLS13-AES128-GCM-SHA256");
#ifndef WOLFSSL_NO_TLS12
ciphers.push_back("PSK-AES128-GCM-SHA256");
#endif
#elif defined(HAVE_NULL_CIPHER)
ciphers.push_back("PSK-NULL-SHA256");
#elif !defined(NO_AES_CBC)
ciphers.push_back("PSK-AES128-CBC-SHA256");
// recommended TLS 1.3 ciphers
#if defined(HAVE_AESGCM) && !defined(NO_AES)
ciphers.push_back("TLS_AES_128_GCM_SHA256");
#if defined(WOLFSSL_SHA384)
ciphers.push_back("TLS_AES_256_GCM_SHA384");
#endif // WOLFSSL_SHA384
#endif // HAVE_AESGCM && !NO_AES
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
ciphers.push_back("TLS_CHACHA20_POLY1305_SHA256");
#endif // HAVE_CHACHA && HAVE_POLY1305
#endif // WOLFSSL_NO_TLS13
} else if (protocolVersion == iggy::ssl::ProtocolVersion::TLSV1_2) {
#ifdef WOLFSSL_NO_TLS12
throw std::runtime_error("TLS 1.2 is not supported by this build of WolfSSL");
#else
ciphers.push_back("PSK-AES128-GCM-SHA256");
#if !defined(HAVE_ECC) || !defined(HAVE_SUPPORTED_CURVES)
throw std::runtime_error("ECC + Supported Curves must be enabled to support ECDHE");
#endif // HAVE_ECC && HAVE_SUPPORTED_CURVES

// recommended TLS 1.2 ciphers
#if defined(HAVE_AESGCM) && !defined(NO_AES)
ciphers.push_back("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256");
#ifndef NO_RSA
ciphers.push_back("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256");
#endif // NO_RSA
#if defined(WOLFSSL_SHA384)
ciphers.push_back("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384");
#endif // WOLFSSL_SHA384
#ifndef NO_RSA
ciphers.push_back("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384");
#endif // NO_RSA
#endif // HAVE_AESGCM && !NO_AES
#if defined(HAVE_CHACHA) && defined(HAVE_POLY1305)
ciphers.push_back("TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305");
#ifndef NO_RSA
ciphers.push_back("TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305");
#endif
#endif
} else {
auto protocolVersionName = iggy::ssl::getProtocolVersionName(protocolVersion);
throw std::runtime_error(fmt::format("Unsupported protocol version: {}", protocolVersionName));
}

if (ciphers.empty()) {
auto protocolVersionName = iggy::ssl::getProtocolVersionName(protocolVersion);
throw std::runtime_error(fmt::format("No ciphers available for the specified protocol version: {}", protocolVersionName));
}
return ciphers;
}

iggy::ssl::SSLContext::SSLContext(const SSLOptions& options, const PKIEnvironment& pkiEnv)
: options(options)
, pkiEnv(pkiEnv) {
// before we make any other wolfSSL calls, make sure library is initialized once and only once
std::call_once(sslInitDone, []() { wolfSSL_Init(); });

// for now we only support a TLS 1.3 client context; if we generalize this code we can expand
this->ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
if (!this->ctx) {
char* errMsg = wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr);
throw std::runtime_error(fmt::format("Failed to allocate WolfSSL TLS context: {}", errMsg));
#endif
}
this->cm = wolfSSL_CTX_GetCertManager(ctx);

// set up the supported ciphers
std::string delimiter = ":";
std::string joinedCiphers;
iggy::ssl::SSLContext::SSLContext(const SSLOptions& options, const PKIEnvironment& pkiEnv)
: options(options)
, pkiEnv(pkiEnv) {
// before we make any other wolfSSL calls, make sure library is initialized once and only once
std::call_once(sslInitDone, []() { wolfSSL_Init(); });

// for now we only support a TLS 1.3 client context; if we generalize this code we can expand
this->ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
if (!this->ctx) {
char* errMsg = wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr);
throw std::runtime_error(fmt::format("Failed to allocate WolfSSL TLS context: {}", errMsg));
}
this->cm = wolfSSL_CTX_GetCertManager(ctx);

// set up the supported ciphers
std::string delimiter = ":";
std::string joinedCiphers;

auto supportedCiphers = options.getCiphers();
if (!supportedCiphers.empty()) {
joinedCiphers = std::accumulate(std::next(supportedCiphers.begin()), supportedCiphers.end(), supportedCiphers[0],
[delimiter](std::string a, std::string b) { return a + delimiter + b; });
auto supportedCiphers = options.getCiphers();
if (!supportedCiphers.empty()) {
joinedCiphers = std::accumulate(std::next(supportedCiphers.begin()), supportedCiphers.end(), supportedCiphers[0],
[delimiter](std::string a, std::string b) { return a + delimiter + b; });
}
int ret = wolfSSL_CTX_set_cipher_list(this->ctx, joinedCiphers.c_str());
if (ret != SSL_SUCCESS) {
char* errMsg = wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr);
throw std::runtime_error(fmt::format("Failed to set cipher list: {}", errMsg));
}
}
int ret = wolfSSL_CTX_set_cipher_list(this->ctx, joinedCiphers.c_str());
if (ret != SSL_SUCCESS) {
char* errMsg = wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr);
throw std::runtime_error(fmt::format("Failed to set cipher list: {}", errMsg));

iggy::ssl::SSLContext::SSLContext(const SSLContext& other)
: options(other.options)
, pkiEnv(other.pkiEnv) {
this->ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
this->cm = wolfSSL_CTX_GetCertManager(ctx);
}
}

iggy::ssl::SSLContext::SSLContext(const SSLContext& other)
: options(other.options)
, pkiEnv(other.pkiEnv) {
this->ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
this->cm = wolfSSL_CTX_GetCertManager(ctx);
}

iggy::ssl::SSLContext::SSLContext(SSLContext&& other)
: options(other.options)
, pkiEnv(other.pkiEnv) {
this->ctx = other.ctx;
this->cm = other.cm;
other.ctx = nullptr;
other.cm = nullptr;
}

iggy::ssl::SSLContext::~SSLContext() {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);

iggy::ssl::SSLContext::SSLContext(SSLContext && other)
: options(other.options)
, pkiEnv(other.pkiEnv) {
this->ctx = other.ctx;
this->cm = other.cm;
other.ctx = nullptr;
other.cm = nullptr;
}
}

iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(const iggy::ssl::SSLContext& other) {
if (this != &other) {
iggy::ssl::SSLContext::~SSLContext() {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
}
this->ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
}
return *this;
}

iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(SSLContext&& other) {
if (this != &other) {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(const iggy::ssl::SSLContext& other) {
if (this != &other) {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
}
this->ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
}
this->ctx = other.ctx;
other.ctx = nullptr;
return *this;
}

iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(SSLContext&& other) {
if (this != &other) {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
}
this->ctx = other.ctx;
other.ctx = nullptr;
}
return *this;
}
return *this;
}

std::string iggy::ssl::getProtocolVersionName(iggy::ssl::ProtocolVersion protocolVersion) {
switch (protocolVersion) {
case iggy::ssl::ProtocolVersion::TLSV1_3:
return "TLSV1_3";
case iggy::ssl::ProtocolVersion::TLSV1_2:
return "TLSV1_2";
default:
int protocolVersionInt = static_cast<int>(protocolVersion);
throw std::runtime_error(fmt::format("Unsupported protocol version code: {}", protocolVersionInt));
}
}

std::once_flag iggy::ssl::SSLContext::sslInitDone = std::once_flag();
13 changes: 9 additions & 4 deletions sdk/net/crypto/ssl.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ enum PeerType { CLIENT, SERVER };
* We do not support the older, less-secure variations since the expectation is the library will be used in a controlled client-server
* environment where the developer can ensure the server endpoint is adequately hardened.
*/
enum ProtocolVersion { SSLV3 = 0, TLSV1_2 = 1, TLSV1_3 = 2 };
enum ProtocolVersion { TLSV1_2 = 0, TLSV1_3 = 1 };

/**
* @brief Helper function to get protocol version name given the enum.
*/
std::string getProtocolVersionName(iggy::ssl::ProtocolVersion protocolVersion);

/**
* @brief All options related to SSL/TLS are in -- what ciphers to use, client vs. server, etc..
Expand All @@ -34,8 +39,8 @@ class SSLOptions {
private:
std::optional<std::string> peerCertPath = std::nullopt;
PeerType peerType = PeerType::CLIENT;
ProtocolVersion minimumSupportedProtocolVersion = TLSV1_3;
std::vector<std::string> ciphers = getDefaultCipherList();
ProtocolVersion minimumSupportedProtocolVersion = ProtocolVersion::TLSV1_3;
std::vector<std::string> ciphers = getDefaultCipherList(ProtocolVersion::TLSV1_3);

public:
/**
Expand All @@ -47,7 +52,7 @@ class SSLOptions {
* @brief Gets the default cipher list for use in SSL/TLS contexts.
* @return A vector of cipher strings, all uppercase.
*/
static const std::vector<std::string> getDefaultCipherList();
static const std::vector<std::string> getDefaultCipherList(ProtocolVersion protocolVersion);

/**
* @brief Gets the list of requested supported ciphers; will be validated by the context during init.
Expand Down
10 changes: 10 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ if(BUILD_TESTS)
client_test.cc
iggy_protocol_provider_test.cc
model_test.cc
ssl_test.cc
)
target_compile_features(iggy_cpp_test PRIVATE cxx_std_17)
target_include_directories(iggy_cpp_test PRIVATE
${SODIUM_INCLUDE_DIR}
${ADA_INCLUDE_DIR}
${WOLFSSL_INCLUDE_DIR}
${NGHTTP3_INCLUDE_DIR}
${NGTCP2_INCLUDE_DIR}
${CURL_INCLUDE_DIR}
)
target_link_libraries(
iggy_cpp_test
Expand Down
1 change: 0 additions & 1 deletion tests/client_test.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#define CATCH_CONFIG_MAIN
#include "../sdk/client.h"
#include "unit_testutils.h"

Expand Down
1 change: 0 additions & 1 deletion tests/model_test.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#define CATCH_CONFIG_MAIN
#include "../sdk/model.h"
#include "unit_testutils.h"

Expand Down
16 changes: 16 additions & 0 deletions tests/ssl_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "../sdk/net/crypto/ssl.h"
#include "unit_testutils.h"

TEST_CASE("SSL configuration", UT_TAG) {
iggy::ssl::SSLOptions options;
auto cipherListTLSV1_2 = options.getDefaultCipherList(iggy::ssl::ProtocolVersion::TLSV1_2);
auto cipherListTLSV1_3 = options.getDefaultCipherList(iggy::ssl::ProtocolVersion::TLSV1_3);

REQUIRE(cipherListTLSV1_2.size() == 6);
REQUIRE(cipherListTLSV1_3.size() == 3);

std::string tls12Cipher = "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384";
std::string tls13Cipher = "TLS_CHACHA20_POLY1305_SHA256";
CHECK(std::find(cipherListTLSV1_2.begin(), cipherListTLSV1_2.end(), tls12Cipher) != cipherListTLSV1_2.end());
CHECK(std::find(cipherListTLSV1_3.begin(), cipherListTLSV1_3.end(), tls13Cipher) != cipherListTLSV1_3.end());
}

0 comments on commit 4743845

Please sign in to comment.