Skip to content

Commit

Permalink
Updated for certificate directory support
Browse files Browse the repository at this point in the history
- added certificate bundle wrapper
- added general openssl types
- added some helpers
- updated certificate delete for bundle support

Signed-off-by: Ioan Bogdan <[email protected]>
  • Loading branch information
AssemblyJohn authored and ioanbogdan committed Oct 10, 2023
1 parent f621aab commit b88dd99
Show file tree
Hide file tree
Showing 9 changed files with 698 additions and 327 deletions.
66 changes: 66 additions & 0 deletions include/evse_utilities.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@

#ifndef EVSE_UTILITIES_HPP
#define EVSE_UTILITIES_HPP

#include <filesystem>
#include <fstream>
#include <iostream>

namespace evse_security {

class EvseUtils {
public:
static bool delete_file(const std::filesystem::path& file_path) {
if (std::filesystem::is_regular_file(file_path))
return std::filesystem::remove(file_path);

return false;
}

static bool read_from_file(const std::filesystem::path& file_path, std::string& out_data) {
if (std::filesystem::is_regular_file(file_path)) {
std::ifstream file(file_path, std::ios::binary);

if (file.is_open()) {
out_data = std::string((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
return true;
}
}

return false;
}

static bool write_to_file(const std::filesystem::path& file_path, const std::string& data,
std::ios::openmode mode) {
try {
std::ofstream fs(file_path, mode | std::ios::binary);
if (!fs.is_open()) {
// EVLOG_error << "Error opening file: " << file_path;
return false;
}
fs.write(data.c_str(), data.size());

if (!fs) {
// EVLOG_error << "Error writing to file: " << file_path;
return false;
}
return true;
} catch (const std::exception& e) {
// EVLOG_error << "Unknown error occured while writing to file: " << file_path;
return false;
}

return true;
}

static std::string get_random_file_name(const std::string& extension) {
char path[] = "XXXXXX";
mktemp(path);

return std::string(path) + extension;
}
};

} // namespace evse_security

#endif
60 changes: 60 additions & 0 deletions include/sec_types.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#ifndef EVSE_SEC_TYPES_HPP
#define EVSE_SEC_TYPES_HPP

#include <memory>
#include <openssl/x509v3.h>

template <> class std::default_delete<X509> {
public:
void operator()(X509* ptr) const {
::X509_free(ptr);
}
};

template <> class std::default_delete<X509_STORE> {
public:
void operator()(X509_STORE* ptr) const {
::X509_STORE_free(ptr);
}
};

template <> class std::default_delete<X509_STORE_CTX> {
public:
void operator()(X509_STORE_CTX* ptr) const {
::X509_STORE_CTX_free(ptr);
}
};

template <> class std::default_delete<X509_REQ> {
public:
void operator()(X509_REQ* ptr) const {
::X509_REQ_free(ptr);
}
};

template <> class std::default_delete<EVP_PKEY> {
public:
void operator()(EVP_PKEY* ptr) const {
::EVP_PKEY_free(ptr);
}
};

template <> class std::default_delete<BIO> {
public:
void operator()(BIO* ptr) const {
::BIO_free(ptr);
}
};

namespace evse_security {

using X509_ptr = std::unique_ptr<X509>;
using X509_STORE_ptr = std::unique_ptr<X509_STORE>;
using X509_STORE_CTX_ptr = std::unique_ptr<X509_STORE_CTX>;
using X509_REQ_ptr = std::unique_ptr<X509_REQ>;
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY>;
using BIO_ptr = std::unique_ptr<BIO>;

} // namespace evse_security

#endif
95 changes: 95 additions & 0 deletions include/x509_bundle.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@

// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef X509_BUNDLE_HPP
#define X509_BUNDLE_HPP

#include <x509_wrapper.hpp>

namespace evse_security {

/// @brief X509 certificate bundle, used for holding multiple X509Wrappers. Supports
/// operations like add/delete importing/exporting certificates
class X509CertificateBundle {
public:
X509CertificateBundle(const std::filesystem::path& path, const EncodingFormat encoding);
X509CertificateBundle(const std::string& certificate, const EncodingFormat encoding);

public:
/// @brief Gets if this certificate bundle comes from a single certificate bundle file
/// @return
bool is_using_bundle_file() {
return (source == X509CertificateSource::FILE);
}

/// @brief Gets if this certificate bundle comes from an entire directory
/// @return
bool is_using_directory() {
return (source == X509CertificateSource::DIRECTORY);
}

public:
/// @brief Splits the certificate (chain) into single certificates
/// @return vector containing single certificates
std::vector<X509Wrapper> split();

/// @brief If we already have the certificate
bool contains_certificate(const X509Wrapper& certificate);
/// @brief If we already have the certificate
bool contains_certificate(const CertificateHashData& certificate_hash);

/// @brief Updates a single certificate in the chain. Only in memory, use @ref export_certificates to filesystem
/// export
/// @param certificate certificate to update
/// @return true if the certificate was found and updated, false otherwise. If true is returned the provided
/// certificate is invalidated
bool update_certificate(X509Wrapper& certificate);

/// @brief Deletes a single certificate in the chain. Only in memory, use @ref export_certificates to filesystem
/// export
/// @return true if the certificate was found and deleted, false otherwise
bool delete_certificate(const X509Wrapper& certificate);
bool delete_certificate(const CertificateHashData& data);

/// @brief Deletes all certificates. Only in memory, use @ref export_certificates to filesystem export
void delete_all_certificates();

/// @brief Returns a full exportable representation of a certificate bundle file in PEM format
std::string to_export_string() const;

/// @brief Exports the full certificate chain either as individual files if it is using a directory
/// or as a bundle if it uses a bundle file, at the initially provided path. Also deletes/adds the updated
/// certificates
/// @return true on success, false otherwise
bool export_certificates();

/// @brief Syncs the file structure with the certificate store adding certificates that are not found on the
/// storeage and deleting the certificates that are not contained in this bundle
bool sync_to_certificate_store();

public:
static std::vector<X509_ptr> load_certificates(const std::string& data, const EncodingFormat encoding);

static bool is_certificate_file(const std::filesystem::path& file) {
return std::filesystem::is_regular_file(file) &&
((file.extension() == PEM_EXTENSION) || (file.extension() == DER_EXTENSION));
}

private:
/// @brief Adds to our certificate list the certificates found in the file
/// @return number of added certificates
void add_certifcates(const std::string& data, const EncodingFormat encoding,
const std::optional<std::filesystem::path>& path);

private:
// Certificates in this chain, can only be loaded either from a bundle or a dir folder, never combined
std::vector<X509Wrapper> certificates;
// Relevant bundle or directory for this certificate chain
std::filesystem::path path;
// Source from where we created the certificates. If 'string' the 'export' functions will not work
X509CertificateSource source;
};

} // namespace evse_security

#endif // X509_BUNDLE_HPP
78 changes: 48 additions & 30 deletions include/x509_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include <stdexcept>
#include <string>

#include <sec_types.hpp>
#include <types.hpp>

namespace evse_security {
Expand All @@ -20,19 +21,33 @@ class CertificateLoadException : public std::runtime_error {
using std::runtime_error::runtime_error;
};

/// @brief Convenience wrapper around openssl X509 certificate. Can contain multiple certificates
class X509Wrapper {
using X509_ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
enum class X509CertificateSource {
// Built from a certificate file
FILE,
// Built from a directory of certificates
DIRECTORY,
// Build from a raw string
STRING
};

const std::filesystem::path PEM_EXTENSION = ".pem";
const std::filesystem::path DER_EXTENSION = ".der";
const std::filesystem::path KEY_EXTENSION = ".key";

/// @brief Convenience wrapper around openssl X509 certificate
class X509Wrapper {
public:
// Constructors
X509Wrapper(const std::string& certificate, const EncodingFormat encoding);
X509Wrapper(const std::filesystem::path& path, const EncodingFormat encoding);
X509Wrapper(const std::filesystem::path& file, const EncodingFormat encoding);
X509Wrapper(const std::string& data, const EncodingFormat encoding);

/// @brief Since it implies ownership full transfer, must be very carefull with this that's why it's explicit
/// If another object owns the x509 will destroy it and if another one tries to use the dead reference
/// it will crash the program
/// If another object owns the x509 will destroy it and if another one tries to use the dead reference will crash
/// the program
explicit X509Wrapper(X509* x509);
explicit X509Wrapper(X509_ptr&& x509);

X509Wrapper(X509* x509, const std::filesystem::path& file);
X509Wrapper(X509_ptr&& x509, const std::filesystem::path& file);

X509Wrapper(const X509Wrapper& other);
X509Wrapper(X509Wrapper&& other) = default;
Expand All @@ -41,16 +56,14 @@ class X509Wrapper {

/// @brief Gets raw X509 pointer
/// @return
X509* get() const;
inline X509* get() const {
return x509.get();
}

/// @brief Resets raw X509 pointer to given \p x509
/// @param x509
void reset(X509* x509);

/// @brief Splits the certificate (chain) into single certificates
/// @return vector containing single certificates
std::vector<X509Wrapper> split();

/// @brief Gets valid_in
/// @return seconds until certificate is valid; if > 0 cert is not yet valid
int get_valid_in() const;
Expand All @@ -59,13 +72,9 @@ class X509Wrapper {
/// @return seconds until certificate is expired; if < 0 cert has expired
int get_valid_to() const;

/// @brief Gets str
/// @result raw certificate string
std::string get_str() const;

/// @brief Gets optional path of certificate
/// @brief Gets optional file of certificate
/// @result
std::optional<std::filesystem::path> get_path() const;
std::optional<std::filesystem::path> get_file() const;

/// @brief Gets CN of certificate
/// @result
Expand All @@ -91,25 +100,34 @@ class X509Wrapper {
/// @return
std::string get_responder_url() const;

/// @brief Gets the bin64 string representation of this certificate
/// @brief Gets the export string representation for this certificate
/// @return
std::string to_base64_string() const;
std::string get_export_string() const;

/// @brief Gets if this certificate file is containing multiple certificates
/// @return
bool is_bundle() {
return x509.size() > 1;
public:
X509Wrapper& operator=(X509Wrapper&& other) = default;

/// @return true if the two certificates are the same
bool operator==(const X509Wrapper& other) const {
return get_issuer_name_hash() == other.get_issuer_name_hash() &&
get_issuer_key_hash() == other.get_issuer_key_hash() && get_serial_number() == other.get_serial_number();
}

bool operator==(const CertificateHashData& other) const {
return get_issuer_name_hash() == other.issuer_name_hash && get_issuer_key_hash() == other.issuer_key_hash &&
get_serial_number() == other.serial_number;
}

private:
void load_certificate(const std::string& data, const EncodingFormat encoding);
void update_validity();

private:
std::vector<X509_ptr> x509;
int valid_in; // seconds; if > 0 cert is not yet valid
int valid_to; // seconds; if < 0 cert has expired
std::optional<std::filesystem::path> path;
X509_ptr x509; // X509 wrapper object
std::int64_t valid_in; // seconds; if > 0 cert is not yet valid
std::int64_t valid_to; // seconds; if < 0 cert has expired

// Relevant file in which this certificate resides
std::optional<std::filesystem::path> file;
};

} // namespace evse_security
Expand Down
1 change: 1 addition & 0 deletions lib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ target_sources(evse_security
evse_security.cpp
x509_wrapper.cpp
types.cpp
x509_bundle.cpp
)

target_include_directories(evse_security
Expand Down
Loading

0 comments on commit b88dd99

Please sign in to comment.