Skip to content

Commit

Permalink
Interim commit of SSLContext refactoring #2, move complete
Browse files Browse the repository at this point in the history
  • Loading branch information
0xg0nz0 committed Mar 31, 2024
1 parent 23e0b49 commit a18aecc
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 120 deletions.
File renamed without changes.
40 changes: 30 additions & 10 deletions sdk/net/crypto.h → sdk/net/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ class LocalCertificateStore : public CertificateStore {
explicit LocalCertificateStore(const std::optional<std::filesystem::path> certDir = std::nullopt);
~LocalCertificateStore() = default;

/**
* @brief Gets the default instance, which loads from the current working directory.
*/
static LocalCertificateStore& getDefault() {
static LocalCertificateStore instance;
return instance;
}

/**
* Retrieves the certificate from the filesystem, translating the abstract
* path to a filesystem-appropriate absolute path.
Expand Down Expand Up @@ -84,6 +92,14 @@ class LocalKeyStore : public KeyStore {
explicit LocalKeyStore(const std::optional<std::filesystem::path> privateKeyDir = std::nullopt);
~LocalKeyStore() = default;

/**
* @brief Gets the default instance, which loads from the current working directory.
*/
static LocalKeyStore& getDefault() {
static LocalKeyStore instance;
return instance;
}

/**
* Retrieves the private key materials from the filesystem, translating the abstract
* path to a filesystem-appropriate absolute path.
Expand Down Expand Up @@ -131,10 +147,10 @@ class CRL : public RevocationMethod {
*/
class OCSP : public RevocationMethod {
private:
std::optional<ada::url> ocspOverrideUrl;
const std::optional<ada::url> ocspOverrideUrl;

public:
explicit OCSP(std::optional<ada::url> ocspOverrideUrl = std::nullopt)
explicit OCSP(const std::optional<ada::url> ocspOverrideUrl = std::nullopt)
: ocspOverrideUrl(ocspOverrideUrl) {}

/**
Expand All @@ -149,19 +165,23 @@ class OCSP : public RevocationMethod {
*/
class CertificateAuthority {
private:
const std::optional<std::string> overrideCaCertificatePath;
std::vector<std::string> trustedPeerCertificatePaths;
const RevocationMethod& revocationMethod;
std::optional<std::string> overrideCaCertificatePath;
std::vector<std::string> trustedPeerCertificatePaths = std::vector<std::string>();
RevocationMethod* revocationMethod;

public:
explicit CertificateAuthority(const std::optional<std::string> overrideCaCertificatePath = std::nullopt,
const RevocationMethod& revocationMethod = OCSP())
explicit CertificateAuthority(std::optional<std::string> overrideCaCertificatePath = std::nullopt,
RevocationMethod* revocationMethod = new OCSP())
: overrideCaCertificatePath(overrideCaCertificatePath)
, revocationMethod(revocationMethod) {}

CertificateAuthority(const CertificateAuthority& other) = default;
CertificateAuthority(CertificateAuthority&& other) noexcept = default;
~CertificateAuthority() = default;
/**
* @brief Gets the default instance, which uses the system CA store and OCSP.
*/
static CertificateAuthority& getDefault() {
static CertificateAuthority instance;
return instance;
}

/**
* @brief If specified (default: not), the filesystem path to the CA certificate path file.
Expand Down
96 changes: 42 additions & 54 deletions sdk/net/tls.cc → sdk/net/crypto/ssl.cc
Original file line number Diff line number Diff line change
@@ -1,17 +1,36 @@
#include "tls.h"
#include "ssl.h"
#include <numeric>
#include "fmt/format.h"

iggy::tls::TLSContext::TLSContext(const iggy::crypto::CertificateAuthority& certAuth,
const iggy::crypto::CertificateStore& certStore,
const iggy::crypto::KeyStore& keyStore,
bool useClientCert,
const std::vector<std::string>& ciphers)
: certAuth(certAuth)
, certStore(certStore)
, keyStore(keyStore)
, useClientCert(useClientCert)
, ciphers(ciphers) {
const std::vector<std::string> iggy::ssl::SSLOptions::getDefaultCipherList() {
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
#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");
#else
ciphers.push_back("PSK-AES128-GCM-SHA256");
#endif
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(); });

Expand All @@ -27,8 +46,9 @@ iggy::tls::TLSContext::TLSContext(const iggy::crypto::CertificateAuthority& cert
std::string delimiter = ":";
std::string joinedCiphers;

if (!ciphers.empty()) {
joinedCiphers = std::accumulate(std::next(ciphers.begin()), ciphers.end(), ciphers[0],
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());
Expand All @@ -38,35 +58,29 @@ iggy::tls::TLSContext::TLSContext(const iggy::crypto::CertificateAuthority& cert
}
}

iggy::tls::TLSContext::TLSContext(const TLSContext& other)
: certAuth(other.certAuth)
, certStore(other.certStore)
, keyStore(other.keyStore)
, useClientCert(useClientCert)
, ciphers(ciphers) {
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::tls::TLSContext::TLSContext(TLSContext&& other)
: certAuth(certAuth)
, certStore(certStore)
, keyStore(keyStore)
, useClientCert(useClientCert)
, ciphers(ciphers) {
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::tls::TLSContext::~TLSContext() {
iggy::ssl::SSLContext::~SSLContext() {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
}
}

iggy::tls::TLSContext& iggy::tls::TLSContext::operator=(const iggy::tls::TLSContext& other) {
iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(const iggy::ssl::SSLContext& other) {
if (this != &other) {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
Expand All @@ -76,7 +90,7 @@ iggy::tls::TLSContext& iggy::tls::TLSContext::operator=(const iggy::tls::TLSCont
return *this;
}

iggy::tls::TLSContext& iggy::tls::TLSContext::operator=(TLSContext&& other) {
iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(SSLContext&& other) {
if (this != &other) {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
Expand All @@ -86,29 +100,3 @@ iggy::tls::TLSContext& iggy::tls::TLSContext::operator=(TLSContext&& other) {
}
return *this;
}

const std::vector<std::string> iggy::tls::TLSContext::getDefaultCipherList() {
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
#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");
#else
ciphers.push_back("PSK-AES128-GCM-SHA256");
#endif
return ciphers;
}
151 changes: 151 additions & 0 deletions sdk/net/crypto/ssl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#pragma once

#include <wolfssl/options.h>
#include <wolfssl/ssl.h>
#include <wolfssl/wolfcrypt/settings.h>
#include <mutex>
#include <optional>
#include <string>
#include "crypto.h"

namespace iggy {
namespace ssl {

/**
* @brief The type of peer endpoint represented by the local end of the socket.
*/
enum PeerType { CLIENT, SERVER };

/**
* @brief The version of the SSL/TLS protocol to use.
*
* 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 };

/**
* @brief All options related to SSL/TLS are in -- what ciphers to use, client vs. server, etc..
*
* Mutable configuration object containing all options related to SSL/TLS. It offers reasonable defaults for a strict client. If you need to
* talk to an SSL/TLS server that has not been hardened you may need to modify this.
*/
class SSLOptions {
private:
std::optional<std::string> peerCertPath = std::nullopt;
PeerType peerType = PeerType::CLIENT;
ProtocolVersion minimumSupportedProtocolVersion = TLSV1_3;
std::vector<std::string> ciphers = getDefaultCipherList();

public:
/**
* Creates a default set of options for a TLS 1.3-compatible client.
*/
SSLOptions() = default;

/**
* @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();

/**
* @brief Gets the list of requested supported ciphers; will be validated by the context during init.
*/
const std::vector<std::string>& getCiphers() const { return this->ciphers; }

/**
* @brief Sets the list of requested supported ciphers; will be validated by the context during init.
*/
void setCiphers(const std::vector<std::string>& ciphers) { this->ciphers = ciphers; }
};

/**
* @brief All options related to the environment library is in -- where to load CA, certificates and keys.
*
* Mutable configuration object containing our hooks to load CA certificates, peer & trusted certificates, and keys. It offers reasonable
* defaults if you are loading from PEM files on the filesystem and are OK using the operating system default CA store with OCSP.
*/
class PKIEnvironment {
private:
iggy::crypto::CertificateAuthority& certAuth;
iggy::crypto::CertificateStore& certStore;
iggy::crypto::KeyStore& keyStore;

public:
PKIEnvironment(iggy::crypto::CertificateAuthority& certAuth = iggy::crypto::CertificateAuthority::getDefault(),
iggy::crypto::CertificateStore& certStore = iggy::crypto::LocalCertificateStore::getDefault(),
iggy::crypto::KeyStore& keyStore = iggy::crypto::LocalKeyStore::getDefault())
: certAuth(certAuth)
, certStore(certStore)
, keyStore(keyStore) {}

/**
* @brief Gets the certificate authority to use for verifying peer certificates; defaults to local system CA store.
*/
const iggy::crypto::CertificateAuthority& getCertificateAuthority() const { return this->certAuth; }

/**
* @brief Sets an alternative certificate authority to use for verifying peer certificates, e.g. if you use a custom CA service,
* API-based secret store like Vault or 1Password, or a custom database.
*/
void setCertificateAuthority(const iggy::crypto::CertificateAuthority& certAuth) { this->certAuth = certAuth; }

/**
* @brief Gets the certificate store to use for loading this peer's own certificate and any trusted peer certificates; defaults to a
* local filesystem store.
*/
const iggy::crypto::CertificateStore& getCertificateStore() const { return this->certStore; }

/**
* @brief Sets an alternative certificate store to use for loading this peer's own certificate and any trusted peer certificates, e.g.
* if you use a database.
*/
void setCertificateStore(const iggy::crypto::CertificateStore& certStore) { this->certStore = certStore; }

/**
* @brief Gets the certificate store to use for loading private key materials; defaults to a local filesystem store.
*/
const iggy::crypto::KeyStore& getKeyStore() const { return this->keyStore; }

/**
* @brief Sets an alternative key store to use for loading private key materials, e.g. if you use an API-based secret store like Vault
* or 1Password, cloud HSM-based vault, or a custom database.
*/
void setKeyStore(const iggy::crypto::KeyStore& keyStore) { this->keyStore = keyStore; }
};

/**
* @brief An SSL/TLS context for use in secure communication.
*
* This class is a portable wrapper around the WolfSSL context object, providing a C++ interface to the underlying C library, encapsulating
* all the defaults of how to initialize and clean up the SSL context. It does not expose every possible option, and the intention is to
* support OpenSSL and BoringSSL in the future.
*/
class SSLContext {
private:
static std::once_flag sslInitDone;

const SSLOptions& options;
const PKIEnvironment& pkiEnv;

WOLFSSL_CTX* ctx;
WOLFSSL_CERT_MANAGER* cm;

public:
explicit SSLContext(const SSLOptions& options = SSLOptions(), const PKIEnvironment& pkiEnv = PKIEnvironment());
SSLContext(const SSLContext& other);
SSLContext(SSLContext&& other);
~SSLContext();

SSLContext& operator=(const SSLContext& other);
SSLContext& operator=(SSLContext&& other);

/**
* @brief Gets access to the underlying C SSL context handle. Subsequent internal C library calls should make a static cast to ensure
* that the expected handle type is used.
*/
void* getNativeHandle() const { return this->ctx; }
};
}; // namespace ssl
}; // namespace iggy
Loading

0 comments on commit a18aecc

Please sign in to comment.