Skip to content

Commit

Permalink
Merge pull request #18 from EVerest/refactor/header_dependency_manage…
Browse files Browse the repository at this point in the history
…ment

Refactor for libevse
  • Loading branch information
AssemblyJohn authored Nov 16, 2023
2 parents 5b2e35b + c0b9fad commit f730972
Show file tree
Hide file tree
Showing 27 changed files with 1,680 additions and 1,106 deletions.
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ endif()

option(LIBEVSE_SECURITY_USE_BOOST_FILESYSTEM "Usage of boost/filesystem.hpp instead of std::filesystem" OFF)

option(LIBEVSE_CRYPTO_SUPPLIER_OPENSSL "Default OpenSSL cryptography supplier" ON)

# dependencies
find_package(OpenSSL REQUIRED)
Expand All @@ -40,6 +41,7 @@ if (EVSE_SECURITY_INSTALL)
install(
DIRECTORY include/
TYPE INCLUDE
PATTERN "detail" EXCLUDE
)

evc_setup_package(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef X509_BUNDLE_HPP
#define X509_BUNDLE_HPP
#pragma once

#include <map>

#include <x509_hierarchy.hpp>
#include <x509_wrapper.hpp>
#include <evse_security/certificate/x509_hierarchy.hpp>
#include <evse_security/certificate/x509_wrapper.hpp>

namespace evse_security {

Expand Down Expand Up @@ -65,14 +64,7 @@ class X509CertificateBundle {
}

/// @return Contained certificate count
int get_certificate_count() const {
int count = 0;
for (const auto& chain : certificates) {
count += chain.second.size();
}

return count;
}
int get_certificate_count() const;

fs::path get_path() const {
return path;
Expand Down Expand Up @@ -157,8 +149,6 @@ class X509CertificateBundle {
X509CertificateBundle& operator=(X509CertificateBundle&& other) = default;

public:
/// @brief Loads all certificates from the string data that can contain multiple cetifs
static std::vector<X509_ptr> load_certificates(const std::string& data, const EncodingFormat encoding);
/// @brief Returns the latest valid certif that we might contain
static X509Wrapper get_latest_valid_certificate(const std::vector<X509Wrapper>& certificates);

Expand All @@ -170,7 +160,7 @@ class X509CertificateBundle {
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<fs::path>& path);
void add_certificates(const std::string& data, const EncodingFormat encoding, const std::optional<fs::path>& path);

/// @brief operation to be executed after each add/delete to this bundle
void invalidate_hierarchy();
Expand All @@ -190,5 +180,3 @@ class X509CertificateBundle {
};

} // namespace evse_security

#endif // X509_BUNDLE_HPP
110 changes: 110 additions & 0 deletions include/evse_security/certificate/x509_hierarchy.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once

#include <queue>

#include <evse_security/certificate/x509_wrapper.hpp>

namespace evse_security {

class NoCertificateFound : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};

struct X509Node {
X509Wrapper certificate;
CertificateHashData hash;

X509Wrapper issuer;
std::vector<X509Node> children;
};

/// @brief Utility class that is able to build a immutable certificate hierarchy
/// with a list of self-signed root certificates and their respective sub-certificates
class X509CertificateHierarchy {
public:
const std::vector<X509Node>& get_hierarchy() const {
return hierarchy;
}

/// @brief Checks if the provided certificate is a self-signed root CA certificate
/// contained in our hierarchy
bool is_root(const X509Wrapper& certificate) const;

/// @brief Collects all the descendants of the provided certificate
/// @param top Certificate that issued the descendants
std::vector<X509Wrapper> collect_descendants(const X509Wrapper& top);

/// @brief obtains the hash data of the certificate, finding its issuer if needed
CertificateHashData get_certificate_hash(const X509Wrapper& certificate);

/// @brief returns true if we contain a certificate with the following hash
bool contains_certificate_hash(const CertificateHashData& hash);

/// @brief Searches for the provided hash, throwing a NoCertificateFound if not found
X509Wrapper find_certificate(const CertificateHashData& hash);

public:
std::string to_debug_string();

/// @brief Breadth-first iteration through all the hierarchy of
/// certificates. Will break when the function returns false
template <typename function> void for_each(function func) {
std::queue<std::reference_wrapper<X509Node>> queue;
for (auto& root : hierarchy) {
// Process roots
if (!func(root))
return;

for (auto& child : root.children) {
queue.push(child);
}
}

while (!queue.empty()) {
X509Node& top = queue.front();
queue.pop();

// Process node
if (!func(top))
return;

for (auto& child : top.children) {
queue.push(child);
}
}
}

public:
/// @brief Depth-first descendant iteration
template <typename function> static void for_each_descendant(function func, const X509Node& node, int depth = 0) {
if (node.children.empty())
return;

for (const auto& child : node.children) {
func(child, depth);

if (!child.children.empty()) {
for_each_descendant(func, child, (depth + 1));
}
}
}

public:
/// @brief Builds a proper certificate hierarchy from the provided certificates. The
/// hierarchy can be incomplete, in case orphan certificates are present in the list
static X509CertificateHierarchy build_hierarchy(std::vector<X509Wrapper>& certificates);

private:
/// @brief Attempts to add to the hierarchy the provided certificate
/// @return True if we found within our hierarchy any certificate that
/// owns the provided certificate, false otherwise
bool try_add_to_hierarchy(X509Wrapper&& certificate);

private:
std::vector<X509Node> hierarchy;
};

} // namespace evse_security
Original file line number Diff line number Diff line change
@@ -1,26 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef X509_WRAPPER_HPP
#define X509_WRAPPER_HPP
#pragma once

#include <memory>
#include <openssl/bio.h>
#include <openssl/pem.h>
#include <openssl/x509.h>
#include <stdexcept>
#include <string>
#include <support_older_cpp_versions.hpp>

#include <sec_types.hpp>
#include <types.hpp>
#include <evse_security/crypto/interface/crypto_types.hpp>
#include <evse_security/evse_types.hpp>
#include <evse_security/utils/evse_filesystem_types.hpp>

namespace evse_security {

class CertificateLoadException : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};

enum class X509CertificateSource {
// Built from a certificate file
FILE,
Expand All @@ -34,22 +25,14 @@ const fs::path PEM_EXTENSION = ".pem";
const fs::path DER_EXTENSION = ".der";
const fs::path KEY_EXTENSION = ".key";

using ossl_days_to_seconds = std::chrono::duration<std::int64_t, std::ratio<86400>>;

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

/// @brief Since it implies ownership full transfer, must be very careful 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 will crash
/// the program
explicit X509Wrapper(X509* x509);
explicit X509Wrapper(X509_ptr&& x509);

X509Wrapper(X509* x509, const fs::path& file);
X509Wrapper(X509_ptr&& x509, const fs::path& file);
explicit X509Wrapper(X509Handle_ptr&& x509);
X509Wrapper(X509Handle_ptr&& x509, const fs::path& file);

X509Wrapper(const X509Wrapper& other);
X509Wrapper(X509Wrapper&& other) = default;
Expand All @@ -64,16 +47,11 @@ class X509Wrapper {
bool is_selfsigned() const;

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

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

/// @brief Gets valid_in
/// @return seconds until certificate is valid; if > 0 cert is not yet valid
int get_valid_in() const;
Expand All @@ -86,6 +64,8 @@ class X509Wrapper {
/// @result
std::optional<fs::path> get_file() const;

void set_file(fs::path& path);

/// @brief Gets the source of this certificate, if it is from a file it's 'FILE'
/// but it can also be constructed from a string, or another certificate
X509CertificateSource get_source() const;
Expand Down Expand Up @@ -140,8 +120,6 @@ class X509Wrapper {
public:
X509Wrapper& operator=(X509Wrapper&& other) = default;

void update_file(fs::path& path);

/// @return true if the two certificates are the same
bool operator==(const X509Wrapper& other) const;

Expand All @@ -154,7 +132,7 @@ class X509Wrapper {
void update_validity();

private:
X509_ptr x509; // X509 wrapper object
X509Handle_ptr x509; // X509 wrapper object
std::int64_t valid_in; // seconds; if > 0 cert is not yet valid, negative value means past, positive is in future
std::int64_t valid_to; // seconds; if < 0 cert has expired, negative value means past, positive is in future

Expand All @@ -163,5 +141,3 @@ class X509Wrapper {
};

} // namespace evse_security

#endif // X509_WRAPPER_HPP
13 changes: 13 additions & 0 deletions include/evse_security/crypto/evse_crypto.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once

#include <evse_security/crypto/interface/crypto_supplier.hpp>

// Include other required suppliers here
#ifdef LIBEVSE_CRYPTO_SUPPLIER_OPENSSL
#include <evse_security/crypto/openssl/openssl_supplier.hpp>
namespace evse_security {
typedef OpenSSLSupplier CryptoSupplier; // Define others with the same 'CryptoSupplier' name
}
#endif
70 changes: 70 additions & 0 deletions include/evse_security/crypto/interface/crypto_supplier.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once

#include <optional>
#include <stdexcept>
#include <vector>

#include <evse_security/crypto/interface/crypto_types.hpp>
#include <evse_security/evse_types.hpp>
#include <evse_security/utils/evse_filesystem_types.hpp>

namespace evse_security {

/// @brief All cryptography suppliers must conform to this class. Do not
/// use this class directly, just include the evse_crypto.hpp
class AbstractCryptoSupplier {
public:
/// @brief Loads all certificates from the string data that can contain multiple cetifs
static std::vector<X509Handle_ptr> load_certificates(const std::string& data, const EncodingFormat encoding);

public: // X509 certificate utilities
static std::string x509_to_string(X509Handle* handle);
static std::string x509_get_responder_url(X509Handle* handle);
static std::string x509_get_key_hash(X509Handle* handle);
static std::string x509_get_serial_number(X509Handle* handle);
static std::string x509_get_issuer_name_hash(X509Handle* handle);
static std::string x509_get_common_name(X509Handle* handle);

/// @brief Returns the time validity for a certificate
/// @param out_valid_in Valid in amount of seconds. A negative value is in the past, a positive one is in the future
/// (not yet valid)
/// @param out_valid_to Valid amount of seconds. A negative value is in the past (expired), a positive one is in the
/// future
static void x509_get_validity(X509Handle* handle, std::int64_t& out_valid_in, std::int64_t& out_valid_to);

static bool x509_is_selfsigned(X509Handle* handle);
static bool x509_is_child(X509Handle* child, X509Handle* parent);
static bool x509_is_equal(X509Handle* a, X509Handle* b);

static X509Handle_ptr x509_duplicate_unique(X509Handle* handle);

/// @brief Verifies the provided target against the certificate chain
/// @param target Target to verify
/// @param parents Parents chain, until the root
/// @param dir_path Optional directory path that can be used for certificate store lookup
/// @param file_path Optional certificate file path that can be used for certificate store lookup
/// @return
static CertificateValidationError x509_verify_certificate_chain(X509Handle* target,
const std::vector<X509Handle*>& parents,
const std::optional<fs::path> dir_path,
const std::optional<fs::path> file_path);

/// @brief Checks if the private key is consistent with the provided handle
static bool x509_check_private_key(X509Handle* handle, std::string private_key,
std::optional<std::string> password);

/// @brief Verifies the signature with the certificate handle public key against the data
static bool x509_verify_signature(X509Handle* handle, const std::vector<std::byte>& signature,
const std::vector<std::byte>& data);

/// @brief Generates a certificate signing request with the provided parameters
static bool x509_generate_csr(const CertificateSigningRequestInfo& generation_info, std::string& out_csr);

public: // Digesting/decoding utils
static bool digest_file_sha256(const fs::path& path, std::vector<std::byte>& out_digest);
static bool decode_base64_signature(const std::string& signature, std::vector<std::byte>& out_decoded);
};

} // namespace evse_security
Loading

0 comments on commit f730972

Please sign in to comment.