Skip to content

Commit

Permalink
Major refactoring to simplify configuration code
Browse files Browse the repository at this point in the history
  • Loading branch information
0xg0nz0 committed Apr 4, 2024
1 parent 74c79b4 commit 0f1b79f
Show file tree
Hide file tree
Showing 7 changed files with 192 additions and 98 deletions.
11 changes: 11 additions & 0 deletions sdk/net/crypto/crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include <fstream>
#include "fmt/format.h"
#include "spdlog/spdlog.h"
#include "ssl_engine.h"

iggy::crypto::LocalCertificateStore::LocalCertificateStore(const std::optional<std::filesystem::path> certDir) {
auto certDirAbs = std::filesystem::absolute(certDir.value_or(std::filesystem::current_path())).make_preferred();
Expand Down Expand Up @@ -59,3 +60,13 @@ const std::vector<uint8_t> iggy::crypto::LocalKeyStore::getPrivateKey(const std:
}
return keyData;
}

template <>
void iggy::crypto::CRL<WOLFSSL_CTX*>::configure(WOLFSSL_CTX* handle, const iggy::crypto::PKIEnvironment<WOLFSSL_CTX*>& pkiEnv) {}

template <>
void iggy::crypto::OCSP<WOLFSSL_CTX*>::configure(WOLFSSL_CTX* handle, const iggy::crypto::PKIEnvironment<WOLFSSL_CTX*>& pkiEnv) {}

template <>
void iggy::crypto::CertificateAuthority<WOLFSSL_CTX*>::configure(WOLFSSL_CTX* handle,
const iggy::crypto::PKIEnvironment<WOLFSSL_CTX*>& pkiEnv) {}
114 changes: 108 additions & 6 deletions sdk/net/crypto/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,28 @@ class LocalKeyStore : public KeyStore {
const std::vector<uint8_t> getPrivateKey(const std::string keyPath) const override;
};

// forward declaration
template <typename HandleType>
class PKIEnvironment;

/**
* Base type for objects that can be configured with library-specific calls. You will need
* a specialization for each library supported, e.g. wolfSSL, OpenSSL/BoringSSL, mbedTLS, etc.,
* which makes that library C API calls for handling the particular object's settings.
*/
template <typename HandleType>
class Configurable {
/**
* Crypto library-specific implementation to configure the revocation method, usually with C API.
*/
virtual void configure(HandleType handle, const PKIEnvironment<HandleType>& pkiEnv) = 0;
};

/**
* @brief A mechanism for revoking certificates: through CRL or OCSP.
*/
class RevocationMethod {
template <typename HandleType>
class RevocationMethod : Configurable<HandleType> {
public:
RevocationMethod() = default;
virtual ~RevocationMethod() = default;
Expand All @@ -120,7 +138,8 @@ class RevocationMethod {
* @brief Certificate revocation list (CRL)-based revocation method. If there are no CRL paths or URLs
* specified, the CRL is assumed to be embedded in the CA certificate.
*/
class CRL : public RevocationMethod {
template <typename HandleType>
class CRL : public RevocationMethod<HandleType> {
private:
std::optional<std::filesystem::path> crlPath;
std::optional<ada::url> crlUrl;
Expand All @@ -139,13 +158,19 @@ class CRL : public RevocationMethod {
* @brief If specified (default: not), an HTTP URL from which to load the CRL.
*/
const std::optional<ada::url> getCrlUrl() const { return this->crlUrl; }

/**
* Installs all CRL settings supported in the library into the given handle.
*/
void configure(HandleType handle, const PKIEnvironment<HandleType>& pkiEnv) override;
};

/**
* @brief Online Certificate Status Protocol (OCSP)-based revocation method. If there is no override
* OCSP URL specified, the OCSP URL is assumed to be embedded in the CA certificate.
*/
class OCSP : public RevocationMethod {
template <typename HandleType>
class OCSP : public RevocationMethod<HandleType> {
private:
const std::optional<ada::url> ocspOverrideUrl;
const bool staplingEnabled;
Expand All @@ -164,21 +189,27 @@ class OCSP : public RevocationMethod {
* @brief If enabled, servers will cache OCSP verification checks to improve performance.
*/
const bool isStaplingEnabled() const { return this->staplingEnabled; }

/**
* Installs all OCSP settings supported in the library into the given handle.
*/
void configure(HandleType handle, const PKIEnvironment<HandleType>& pkiEnv) override;
};

/**
* @brief Authority for verifying certificates -- either through checking against a centralized CA, or via a trusted peer relationship. If
* all defaults are taken, the system CA paths will be used, with revocation checking enabled via OCSP.
*/
class CertificateAuthority {
template <typename HandleType>
class CertificateAuthority : public Configurable<HandleType> {
private:
std::optional<std::string> overrideCaCertificatePath;
std::vector<std::string> trustedPeerCertificatePaths = std::vector<std::string>();
RevocationMethod* revocationMethod;
RevocationMethod<HandleType>* revocationMethod;

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

Expand All @@ -204,6 +235,77 @@ class CertificateAuthority {
* @brief Adds a trusted peer certificate path; optional -- if none, only CA-verified certificates will be trusted.
*/
void addTrustedPeerCertificate(const std::string certPath) { this->trustedPeerCertificatePaths.push_back(certPath); }

/**
* @brief Gets the revocation method to use for verifying certificates: CRL or OCSP.
*/
RevocationMethod<HandleType>* getRevocationMethod() const { return this->revocationMethod; }

/**
* Installs all CA settings supported in the library into the given handle.
*/
void configure(HandleType handle, const PKIEnvironment<HandleType>& pkiEnv) override;
};

/**
* @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.
*/
template <typename HandleType>
class PKIEnvironment : Configurable<HandleType> {
private:
CertificateAuthority<HandleType>& certAuth;
CertificateStore& certStore;
KeyStore& keyStore;

public:
PKIEnvironment(CertificateAuthority<HandleType>& certAuth = CertificateAuthority<HandleType>::getDefault(),
CertificateStore& certStore = LocalCertificateStore::getDefault(),
KeyStore& keyStore = 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 CertificateAuthority<HandleType>& 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 CertificateAuthority<HandleType>& 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 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 CertificateStore& certStore) { this->certStore = certStore; }

/**
* @brief Gets the certificate store to use for loading private key materials; defaults to a local filesystem store.
*/
const 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 KeyStore& keyStore) { this->keyStore = keyStore; }

/**
* @brief Installs all PKI settings supported in the library into the given handle, walking the tree of objects.
*/
void configure(HandleType handle, const PKIEnvironment<HandleType>& pkiEnv) override { this->certAuth.configure(handle, pkiEnv); }
};

}; // namespace crypto
Expand Down
73 changes: 48 additions & 25 deletions sdk/net/crypto/ssl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,20 @@
#include <numeric>
#include "fmt/format.h"

const std::vector<std::string> iggy::ssl::SSLOptions::getDefaultCipherList(iggy::ssl::ProtocolVersion protocolVersion) {
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));
}
}

template <>
const std::vector<std::string> iggy::ssl::SSLOptions<WOLFSSL_CTX*>::getDefaultCipherList(iggy::ssl::ProtocolVersion protocolVersion) {
auto ciphers = std::vector<std::string>();

// References:
Expand Down Expand Up @@ -65,7 +78,20 @@ const std::vector<std::string> iggy::ssl::SSLOptions::getDefaultCipherList(iggy:
return ciphers;
}

iggy::ssl::SSLContext::SSLContext(const SSLOptions& options, const PKIEnvironment& pkiEnv)
template <>
void iggy::ssl::SSLOptions<WOLFSSL_CTX*>::configure(WOLFSSL_CTX* handle, const iggy::crypto::PKIEnvironment<WOLFSSL_CTX*>& pkiEnv) {
// TODO
}

template <>
void iggy::ssl::SSLContext<WOLFSSL_CTX*>::raiseSSLError(const std::string& message) const {
char* errMsg = wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr);
throw std::runtime_error(fmt::format("{}: {}", message, errMsg));
}

template <>
iggy::ssl::SSLContext<WOLFSSL_CTX*>::SSLContext(const SSLOptions<WOLFSSL_CTX*>& options,
const iggy::crypto::PKIEnvironment<WOLFSSL_CTX*>& pkiEnv)
: options(options)
, pkiEnv(pkiEnv) {
// before we make any other wolfSSL calls, make sure library is initialized once and only once
Expand All @@ -74,18 +100,20 @@ iggy::ssl::SSLContext::SSLContext(const SSLOptions& options, const PKIEnvironmen
auto protocolVersion = options.getMinimumSupportedProtocolVersion();
if (protocolVersion == iggy::ssl::ProtocolVersion::TLSV1_2) {
this->ctx = wolfSSL_CTX_new(wolfTLSv1_2_client_method());
if (!this->ctx) {
raiseSSLError("Failed to allocate WolfSSL context");
}
wolfSSL_CTX_set_min_proto_version(this->ctx, TLS1_2_VERSION);
} else if (protocolVersion == iggy::ssl::ProtocolVersion::TLSV1_3) {
this->ctx = wolfSSL_CTX_new(wolfTLSv1_3_client_method());
if (!this->ctx) {
raiseSSLError("Failed to allocate WolfSSL context");
}
wolfSSL_CTX_set_min_proto_version(this->ctx, TLS1_3_VERSION);
} else {
auto protocolVersionName = iggy::ssl::getProtocolVersionName(protocolVersion);
throw std::runtime_error(fmt::format("Unsupported protocol version: {}", protocolVersionName));
}
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
Expand All @@ -104,7 +132,8 @@ iggy::ssl::SSLContext::SSLContext(const SSLOptions& options, const PKIEnvironmen
}
}

void iggy::ssl::SSLOptions::validate(bool strict) const {
template <>
void iggy::ssl::SSLOptions<WOLFSSL_CTX*>::validate(bool strict) const {
if (strict) {
if (this->minimumSupportedProtocolVersion != iggy::ssl::ProtocolVersion::TLSV1_3) {
throw std::runtime_error("Only TLS 1.3 is supported in strict mode");
Expand All @@ -115,14 +144,16 @@ void iggy::ssl::SSLOptions::validate(bool strict) const {
}
}

iggy::ssl::SSLContext::SSLContext(const SSLContext& other)
template <>
iggy::ssl::SSLContext<WOLFSSL_CTX*>::SSLContext(const SSLContext<WOLFSSL_CTX*>& 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)
template <>
iggy::ssl::SSLContext<WOLFSSL_CTX*>::SSLContext(SSLContext<WOLFSSL_CTX*>&& other)
: options(other.options)
, pkiEnv(other.pkiEnv) {
this->ctx = other.ctx;
Expand All @@ -131,13 +162,15 @@ iggy::ssl::SSLContext::SSLContext(SSLContext&& other)
other.cm = nullptr;
}

iggy::ssl::SSLContext::~SSLContext() {
template <>
iggy::ssl::SSLContext<WOLFSSL_CTX*>::~SSLContext() {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
}
}

iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(const iggy::ssl::SSLContext& other) {
template <>
iggy::ssl::SSLContext<WOLFSSL_CTX*>& iggy::ssl::SSLContext<WOLFSSL_CTX*>::operator=(const iggy::ssl::SSLContext<WOLFSSL_CTX*>& other) {
if (this != &other) {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
Expand All @@ -147,7 +180,8 @@ iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(const iggy::ssl::SSLCont
return *this;
}

iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(SSLContext&& other) {
template <>
iggy::ssl::SSLContext<WOLFSSL_CTX*>& iggy::ssl::SSLContext<WOLFSSL_CTX*>::operator=(SSLContext<WOLFSSL_CTX*>&& other) {
if (this != &other) {
if (this->ctx) {
wolfSSL_CTX_free(this->ctx);
Expand All @@ -158,16 +192,5 @@ iggy::ssl::SSLContext& iggy::ssl::SSLContext::operator=(SSLContext&& other) {
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();
template <>
std::once_flag iggy::ssl::SSLContext<WOLFSSL_CTX*>::sslInitDone = std::once_flag();
Loading

0 comments on commit 0f1b79f

Please sign in to comment.