Skip to content

Commit

Permalink
Interim commit before SSLContext refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
0xg0nz0 committed Mar 31, 2024
1 parent 43d9f53 commit 23e0b49
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 62 deletions.
48 changes: 40 additions & 8 deletions sdk/net/crypto.cc
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
#include "crypto.h"
#include <fstream>
#include "fmt/format.h"
#include "spdlog/spdlog.h"

iggy::crypto::LocalCertificateStore::LocalCertificateStore(const std::filesystem::path certDir) {
if (!std::filesystem::exists(certDir) && std::filesystem::is_directory(certDir)) {
throw std::invalid_argument(fmt::format("certificate directory does not exist: {}", certDir.string()));
}
if (!std::filesystem::is_directory(certDir)) {
throw std::invalid_argument(fmt::format("certificate directory is not a valid directory: {}", certDir.string()));
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();
spdlog::debug("Loading certificates from {}", certDirAbs.string());
if (!std::filesystem::exists(certDirAbs)) {
throw std::invalid_argument(fmt::format("certificate directory does not exist: {}", certDirAbs.string()));
} else if (!std::filesystem::is_directory(certDirAbs)) {
throw std::invalid_argument(fmt::format("certificate directory is not a valid directory: {}", certDirAbs.string()));
}
this->certDir = certDir;
}

std::vector<uint8_t> iggy::crypto::LocalCertificateStore::getCertificate(std::string certPath) const {
std::filesystem::path certFile = this->certDir / certPath;
const std::vector<uint8_t> iggy::crypto::LocalCertificateStore::getCertificate(const std::string certPath) const {
std::filesystem::path certFile = (this->certDir.value() / certPath).make_preferred();
spdlog::debug("Loading certificate from {}", certFile.string());
if (!std::filesystem::exists(certFile)) {
throw std::invalid_argument(fmt::format("certificate file does not exist: {}", certFile.string()));
}
Expand All @@ -27,3 +30,32 @@ std::vector<uint8_t> iggy::crypto::LocalCertificateStore::getCertificate(std::st
}
return certData;
}

iggy::crypto::LocalKeyStore::LocalKeyStore(const std::optional<std::filesystem::path> keyDir) {
auto keyDirAbs = std::filesystem::absolute(keyDir.value_or(std::filesystem::current_path())).make_preferred();
spdlog::debug("Loading private keys from {}", keyDirAbs.string());
if (!std::filesystem::exists(keyDirAbs)) {
throw std::invalid_argument(fmt::format("key directory does not exist: {}", keyDirAbs.string()));
}
if (!std::filesystem::is_directory(keyDirAbs)) {
throw std::invalid_argument(fmt::format("key directory is not a valid directory: {}", keyDirAbs.string()));
}
this->privateKeyDir = keyDirAbs;
}

const std::vector<uint8_t> iggy::crypto::LocalKeyStore::getPrivateKey(const std::string keyPath) const {
std::filesystem::path keyFile = (this->privateKeyDir.value() / keyPath).make_preferred();
spdlog::debug("Loading private key from {}", keyFile.string());
if (!std::filesystem::exists(keyFile)) {
throw std::invalid_argument(fmt::format("private key file does not exist: {}", keyFile.string()));
}
std::ifstream keyStream(keyFile, std::ios::binary);
if (!keyStream.is_open()) {
throw std::runtime_error(fmt::format("Failed to open private key file: {}", keyFile.string()));
}
std::vector<uint8_t> keyData((std::istreambuf_iterator<char>(keyStream)), std::istreambuf_iterator<char>());
if (keyData.empty()) {
throw std::runtime_error(fmt::format("Invalid private key file (empty): {}", keyFile.string()));
}
return keyData;
}
103 changes: 66 additions & 37 deletions sdk/net/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,53 +21,36 @@ enum CertificateFormat { PEM = 1, ASN1 = 2 };
* or a secret vault.
*/
class CertificateStore {
private:
CertificateFormat certificateFormat;

public:
explicit CertificateStore(CertificateFormat format = CertificateFormat::PEM)
: certificateFormat(format) {}

CertificateStore(const CertificateStore& other)
: certificateFormat(other.certificateFormat) {}

CertificateStore(CertificateStore&& other) noexcept
: certificateFormat(other.certificateFormat) {}
CertificateStore() = default;
virtual ~CertificateStore() = default;

/**
* @brief Retrieves a certificate from the store.
* @param certPath A slash-delimited abstract path to the certificate file;
* this may be translated according to the backend storage to a
* filesystem-specific path or some other convention.
* @return A vector of bytes representing the certificate in PEM or ASN.1
* based on the format.
* @return A vector of bytes representing the certificate in PEM or ASN.1.
*/
virtual std::vector<uint8_t> getCertificate(std::string certPath) const = 0;

/**
* @brief Retrieves the certificate verification chain from root CA
* certificate to leaf.
* @return A vector of bytes representing the certificate chain in PEM or
* ASN.1 based on the format.
*/
virtual std::vector<uint8_t> getCertificateVerificationChain() const = 0;
virtual const std::vector<uint8_t> getCertificate(const std::string certPath) const = 0;
};

/**
* @brief A certificate store that loads certificates from the local filesystem.
*/
class LocalCertificateStore : public CertificateStore {
private:
std::filesystem::path certDir;
std::optional<std::filesystem::path> certDir;

public:
explicit LocalCertificateStore(const std::filesystem::path certDir);
explicit LocalCertificateStore(const std::optional<std::filesystem::path> certDir = std::nullopt);
~LocalCertificateStore() = default;

/**
* Retrieves the certificate from the filesystem, translating the abstract
* path to a filesystem-appropriate absolute path.
*/
std::vector<uint8_t> getCertificate(std::string certPath) const override;
const std::vector<uint8_t> getCertificate(const std::string certPath) const override;
};

/**
Expand All @@ -76,7 +59,17 @@ class LocalCertificateStore : public CertificateStore {
*/
class KeyStore {
public:
virtual std::vector<uint8_t> getPrivateKey(std::string keyPath) const = 0;
KeyStore() = default;
virtual ~KeyStore() = default;

/**
* @brief Retrieves the private key data from the store.
* @param keyPath A slash-delimited abstract path to the key file;
* this may be translated according to the backend storage to a
* filesystem-specific path or some other convention.
* @return A vector of bytes representing the key in PEM or ASN.1.
*/
virtual const std::vector<uint8_t> getPrivateKey(const std::string keyPath) const = 0;
};

/**
Expand All @@ -85,7 +78,17 @@ class KeyStore {
*/
class LocalKeyStore : public KeyStore {
private:
std::optional<std::filesystem::path> privateKeyPath;
std::optional<std::filesystem::path> privateKeyDir;

public:
explicit LocalKeyStore(const std::optional<std::filesystem::path> privateKeyDir = std::nullopt);
~LocalKeyStore() = default;

/**
* Retrieves the private key materials from the filesystem, translating the abstract
* path to a filesystem-appropriate absolute path.
*/
const std::vector<uint8_t> getPrivateKey(const std::string keyPath) const override;
};

/**
Expand All @@ -98,7 +101,8 @@ class RevocationMethod {
};

/**
* @brief Certificate revocation list (CRL)-based revocation method.
* @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 {
private:
Expand All @@ -109,10 +113,21 @@ class CRL : public RevocationMethod {
explicit CRL(std::optional<std::filesystem::path> crlPath = std::nullopt, std::optional<ada::url> crlUrl = std::nullopt)
: crlPath(crlPath)
, crlUrl(crlUrl) {}

/**
* @brief If specified (default: not), the filesystem path to the CRL file.
*/
const std::optional<std::filesystem::path> getCrlPath() const { return this->crlPath; }

/**
* @brief If specified (default: not), an HTTP URL from which to load the CRL.
*/
const std::optional<ada::url> getCrlUrl() const { return this->crlUrl; }
};

/**
* @brief Online Certificate Status Protocol (OCSP)-based revocation method.
* @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 {
private:
Expand All @@ -121,33 +136,47 @@ class OCSP : public RevocationMethod {
public:
explicit OCSP(std::optional<ada::url> ocspOverrideUrl = std::nullopt)
: ocspOverrideUrl(ocspOverrideUrl) {}

/**
* @brief If specified (default: not), an HTTP URL used to check the status of certificates.
*/
const std::optional<ada::url> getOcspOverrideUrl() const { return this->ocspOverrideUrl; }
};

/**
* @brief Authority for verifying certificates -- either through checking
* against a centralized CA, or via a trusted peer relationship.
* @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 {
private:
std::optional<std::string> overrideCaCertificatePath;
const std::optional<std::string> overrideCaCertificatePath;
std::vector<std::string> trustedPeerCertificatePaths;
RevocationMethod& revocationMethod;
const RevocationMethod& revocationMethod;

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

CertificateAuthority(const CertificateAuthority& other) = default;
CertificateAuthority(CertificateAuthority&& other) noexcept = default;
~CertificateAuthority() = default;

std::optional<std::string> getOverrideCaCertificatePath() { return this->overrideCaCertificatePath; }
/**
* @brief If specified (default: not), the filesystem path to the CA certificate path file.
*/
const std::optional<std::string> getOverrideCaCertificatePath() const { return this->overrideCaCertificatePath; }

std::vector<std::string> getTrustedPeerCertificatePaths() { return this->trustedPeerCertificatePaths; }
/**
* @brief Gets all specified trusted peer certificate paths, if any.
*/
const std::vector<std::string> getTrustedPeerCertificatePaths() const { return this->trustedPeerCertificatePaths; }

void addTrustedPeerCertificate(std::string certPath) { this->trustedPeerCertificatePaths.push_back(certPath); }
/**
* @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); }
};

}; // namespace crypto
Expand Down
74 changes: 63 additions & 11 deletions sdk/net/tls.cc
Original file line number Diff line number Diff line change
@@ -1,33 +1,63 @@
#include "tls.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)
const iggy::crypto::KeyStore& keyStore,
bool useClientCert,
const std::vector<std::string>& ciphers)
: certAuth(certAuth)
, certStore(certStore)
, keyStore(keyStore) {
, keyStore(keyStore)
, useClientCert(useClientCert)
, ciphers(ciphers) {
// 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) {
std::string errMsg =
fmt::format("Failed to allocate WolfSSL TLS context: {}", wolfSSL_ERR_error_string(wolfSSL_ERR_get_error(), nullptr));
throw std::runtime_error(errMsg);
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;

if (!ciphers.empty()) {
joinedCiphers = std::accumulate(std::next(ciphers.begin()), ciphers.end(), ciphers[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));
}
}

iggy::tls::TLSContext::TLSContext(const TLSContext& other)
: certAuth(certAuth)
, certStore(certStore)
, keyStore(keyStore) {
: certAuth(other.certAuth)
, certStore(other.certStore)
, keyStore(other.keyStore)
, useClientCert(useClientCert)
, ciphers(ciphers) {
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) {
, keyStore(keyStore)
, useClientCert(useClientCert)
, ciphers(ciphers) {
this->ctx = other.ctx;
this->cm = other.cm;
other.ctx = nullptr;
other.cm = nullptr;
}

iggy::tls::TLSContext::~TLSContext() {
Expand Down Expand Up @@ -57,6 +87,28 @@ iggy::tls::TLSContext& iggy::tls::TLSContext::operator=(TLSContext&& other) {
return *this;
}

void* iggy::tls::TLSContext::getNativeHandle() const {
return this->ctx;
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;
}
Loading

0 comments on commit 23e0b49

Please sign in to comment.