Skip to content

Commit

Permalink
Merge pull request #2 from EVerest/refactor
Browse files Browse the repository at this point in the history
Refactor
  • Loading branch information
AssemblyJohn authored Sep 29, 2023
2 parents d65ea22 + 549b497 commit b925ded
Show file tree
Hide file tree
Showing 7 changed files with 551 additions and 443 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ endif()

if(BUILD_TESTING_EVSE_SECURITY)
add_subdirectory(tests)
set(CMAKE_BUILD_TYPE Debug)
endif()

107 changes: 32 additions & 75 deletions include/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,28 @@
#include <filesystem>
#include <fstream>
#include <types.hpp>
#include <x509_wrapper.hpp>

namespace evse_security {

struct DirectoryPaths {
std::filesystem::path csms_leaf_cert_directory;
std::filesystem::path csms_leaf_key_directory;
std::filesystem::path secc_leaf_cert_directory;
std::filesystem::path secc_leaf_key_directory;
};
struct FilePaths {
// bundle paths
std::filesystem::path csms_ca_bundle;
std::filesystem::path mf_ca_bundle;
std::filesystem::path mo_ca_bundle;
std::filesystem::path v2g_ca_bundle;
std::filesystem::path csms_leaf_cert_directory;
std::filesystem::path csms_leaf_key_directory;
std::filesystem::path secc_leaf_cert_directory;
std::filesystem::path secc_leaf_key_directory;

DirectoryPaths directories;
};

/// @brief This class holds filesystem paths to CA bundle file locations and directories for leaf certificates
class EvseSecurity {

private:
std::map<CaCertificateType, std::filesystem::path> ca_bundle_path_map;
std::filesystem::path csms_leaf_cert_directory;
std::filesystem::path csms_leaf_key_directory;
std::filesystem::path secc_leaf_cert_directory;
std::filesystem::path secc_leaf_key_directory;

// FIXME(piet): map passwords to encrypted private key files
std::optional<std::string> private_key_password; // used to decrypt encrypted private keys;

std::vector<X509Wrapper> get_leaf_certificates(const LeafCertificateType certificate_type);
std::vector<X509Wrapper> get_leaf_certificates();
std::vector<X509Wrapper> get_ca_certificates();

public:
/// @brief Constructor initializes the certificate and key storage using the given \p file_paths for the different
/// PKIs. For CA certificates CA bundle files must be specified. For the SECC and CSMS leaf certificates,
Expand Down Expand Up @@ -79,9 +69,14 @@ class EvseSecurity {
InstallCertificateResult update_leaf_certificate(const std::string& certificate_chain,
LeafCertificateType certificate_type);

/// @brief Retrieves all certificates installed on the filesystem applying the \p certificate_type filter.
/// @param certificate_types
/// @return contains the certificate hash data chains of the requested \p certificate_type
GetInstalledCertificatesResult get_installed_certificate(CertificateType certificate_type);

/// @brief Retrieves all certificates installed on the filesystem applying the \p certificate_types filter.
/// @param certificate_types
/// @return contains the certificate hash data chains of the requested \p certificate_types
/// @return contains the certificate hash data chains of the requested \p certificate_types
GetInstalledCertificatesResult get_installed_certificates(const std::vector<CertificateType>& certificate_types);

/// @brief Retrieves the OCSP request data of the V2G certificates
Expand All @@ -105,75 +100,37 @@ class EvseSecurity {
/// @param organization
/// @param common
/// @return the PEM formatted certificate signing request
// are these 4 params enough for a full request? I guess there are much more
std::string generate_certificate_signing_request(LeafCertificateType certificate_type, const std::string& country,
const std::string& organization, const std::string& common);

/// @brief Searches the filesystem on the specified directories for the given \p certificate_type and retrieves the
/// most recent certificate that is already valid and the respective key . If no certificate is present or no key is
/// most recent certificate that is already valid and the respective key. If no certificate is present or no key is
/// matching the certificate, this function returns std::nullopt
/// @param certificate_type type of the leaf certificate
/// @param encoding specifies PEM or DER format
/// @return key pair of certificate and key if present, else std::nullopt
std::optional<KeyPair> get_key_pair(LeafCertificateType certificate_type, EncodingFormat encoding);
/// @return contains response result
GetKeyPairResult get_key_pair(LeafCertificateType certificate_type, EncodingFormat encoding);

/// @brief Retrieves the PEM formatted CA bundle file for the given \p certificate_type
/// @param certificate_type
/// @return CA certificate file
/// @return CA certificate file
std::string get_verify_file(CaCertificateType certificate_type);

/// @brief Gets the expiry day count for the leaf certificate of the given \p certificate_type
/// @param certificate_type
/// @return day count until the leaf certificate expires
/// @return day count until the leaf certificate expires
int get_leaf_expiry_days_count(LeafCertificateType certificate_type);
};

/// @brief Searches the specified \p key_path directory for a private key that matches the given \p certificate
/// @param certificate
/// @param key_path directory where keys are located
/// @param password optional password for encrypted private key
/// @return
static std::filesystem::path get_private_key_path(const X509Wrapper& certificate, const std::filesystem::path& key_path,
const std::optional<std::string> password);

/// @brief Retrieves the most recent certificate that is already valid from the given \p certificates
/// @param certificates
/// @return
static X509Wrapper get_latest_valid_certificate(const std::vector<X509Wrapper>& certificates);

/// @brief Writes the given \p data to the file specified with \p file_path . \p mode specifies if \p data overrides
/// file or if \p data is appended to file
/// @param file_path
/// @param data
/// @param mode
/// @return true if file could be written successfully, else fals
static bool write_to_file(const std::filesystem::path& file_path, const std::string& data, std::ios::openmode mode);

/// @brief Deletes the given \p certificate string from the given \p ca_bundle_path
/// @param certificate
/// @param ca_bundle_path
/// @return true if \p certificate string could be removed from \p ca_bundle_path or \p certificate is not part of file,
/// else false
static bool delete_certificate_from_bundle(const std::string& certificate, const std::filesystem::path& ca_bundle_path);

/// @brief Converts openssl \p ec to InstallCertificateResult
/// @param ec
/// @return
static InstallCertificateResult to_install_certificate_result(const int ec);

/// @brief Converts the given \p certificate_types to a vector of CaCertificateType
/// @param certificate_types
/// @return
static std::vector<CaCertificateType> get_ca_certificate_types(const std::vector<CertificateType> certificate_types);

/// @brief Retrieves the CertificateType based on the \p ca_certificate_type
/// @param ca_certificate_type
/// @return
static CertificateType get_certificate_type(const CaCertificateType ca_certificate_type);

/// @brief Generates a random file name based on UTC timestamp using the given \p extension
/// @param extension
/// @return
static std::string get_random_file_name(const std::string& extension);
private:
// why not reusing the FilePaths here directly (storage duplication)
std::map<CaCertificateType, std::filesystem::path> ca_bundle_path_map;
DirectoryPaths directories;

// FIXME(piet): map passwords to encrypted private key files
// is there only one password for all private keys?
std::optional<std::string> private_key_password; // used to decrypt encrypted private keys;
};

/// @brief Custom exception that is thrown when no private key could be found for a selected certificate
class NoPrivateKeyException : public std::runtime_error {
Expand All @@ -190,4 +147,4 @@ class NoCertificateValidException : public std::runtime_error {

} // namespace evse_security

#endif // EVSE_SECURITY_HPP
#endif // EVSE_SECURITY_HPP
28 changes: 24 additions & 4 deletions include/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ enum class CertificateType {
V2GRootCertificate,
MORootCertificate,
CSMSRootCertificate,
V2GCertificateChain,
V2GCertificateChain, // is the certificate chain a certificate type?
MFRootCertificate,
};

Expand All @@ -43,6 +43,7 @@ enum class HashAlgorithm {
SHA512,
};

// the following 3 enum classes should go into evse_security
enum class InstallCertificateResult {
InvalidSignature,
InvalidCertificateChain,
Expand All @@ -61,11 +62,20 @@ enum class DeleteCertificateResult {
NotFound,
};

// why Status here and not Result as before?
enum class GetInstalledCertificatesStatus {
Accepted,
NotFound,
};

enum class GetKeyPairStatus {
Accepted,
Rejected,
NotFound,
NotFoundValid,
PrivateKeyNotFound,
};

// types of evse_security

struct CertificateHashData {
Expand All @@ -76,12 +86,18 @@ struct CertificateHashData {
///< the subject public key field
std::string serial_number; ///< The string representation of the hexadecimal value of the serial number without the
///< prefix "0x" and without leading zeroes.

bool operator==(const CertificateHashData &Other) {
return hash_algorithm == Other.hash_algorithm
&& issuer_name_hash == Other.issuer_name_hash
&& issuer_key_hash == Other.issuer_key_hash
&& serial_number == Other.serial_number;
}
};
struct CertificateHashDataChain {
CertificateType certificate_type; ///< Indicates the type of the certificate for which the hash data is provided
CertificateHashData certificate_hash_data; ///< Contains the hash data of the certificate
std::optional<std::vector<CertificateHashData>>
child_certificate_hash_data; ///< Contains the hash data of the child's certificates
CertificateHashData certificate_hash_data; ///< Contains the hash data of the certificate
std::vector<CertificateHashData> child_certificate_hash_data; ///< Contains the hash data of the child's certificates
};
struct GetInstalledCertificatesResult {
GetInstalledCertificatesStatus status; ///< Indicates the status of the request
Expand All @@ -100,6 +116,10 @@ struct KeyPair {
std::filesystem::path certificate; ///< The path of the PEM or DER encoded certificate
std::optional<std::string> password; ///< Specifies the password for the private key if encrypted
};
struct GetKeyPairResult {
GetKeyPairStatus status;
std::optional<KeyPair> pair;
};

} // namespace evse_security

Expand Down
43 changes: 31 additions & 12 deletions include/x509_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,22 @@ class CertificateLoadException : public std::runtime_error {
using std::runtime_error::runtime_error;
};

/// @brief Convenience wrapper around openssl X509
/// @brief Convenience wrapper around openssl X509 certificate. Can contain multiple certificates
class X509Wrapper {

private:
X509* x509;
int valid_in; // seconds; if > 0 cert is not yet valid
int valid_to; // seconds; if < 0 cert has expired
std::string str;
std::optional<std::filesystem::path> path;

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

using X509_ptr = std::unique_ptr<X509, decltype(&::X509_free)>;
public:
// Constructors
X509Wrapper(const std::string& certificate, const EncodingFormat encoding);
X509Wrapper(const std::filesystem::path& path, const EncodingFormat encoding);
X509Wrapper(X509* x509);

/// @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
explicit X509Wrapper(X509* x509);

X509Wrapper(const X509Wrapper& other);
X509Wrapper(X509Wrapper&& other) = default;

~X509Wrapper();

/// @brief Gets raw X509 pointer
Expand Down Expand Up @@ -91,6 +89,27 @@ class X509Wrapper {
/// @brief Gets OCSP responder URL of certificate if present, else returns an empty string
/// @return
std::string get_responder_url() const;

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

/// @brief Gets if this certificate file is containing multiple certificates
/// @return
bool is_bundle()
{
return x509.size() > 1;
}

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;
};

} // namespace evse_security
Expand Down
Loading

0 comments on commit b925ded

Please sign in to comment.