Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
|
||||
#include <evse_security/certificate/x509_hierarchy.hpp>
|
||||
#include <evse_security/certificate/x509_wrapper.hpp>
|
||||
|
||||
namespace evse_security {
|
||||
|
||||
/// @brief Custom exception that is thrown when no private key could be found for a selected certificate
|
||||
class NoPrivateKeyException : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/// @brief Custom exception that is thrown when no valid certificate could be found for the specified filesystem
|
||||
/// locations
|
||||
class NoCertificateValidException : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/// @brief Custom exception that is thrown when a invalid operation is with the current state
|
||||
/// on the certificate bundle
|
||||
class InvalidOperationException : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
/// @brief X509 certificate bundle, used for holding multiple X509Wrappers. Supports
|
||||
/// operations like add/delete importing/exporting certificates. Can use either a
|
||||
/// directory with multiple certificates (or bundles of certificates) or a single
|
||||
/// file with one or more certificates in it.
|
||||
class X509CertificateBundle {
|
||||
public:
|
||||
X509CertificateBundle(const fs::path& path, const EncodingFormat encoding);
|
||||
X509CertificateBundle(const std::string& certificate, const EncodingFormat encoding);
|
||||
|
||||
X509CertificateBundle(X509CertificateBundle&& other) = default;
|
||||
X509CertificateBundle(const X509CertificateBundle& other) = delete;
|
||||
|
||||
/// @brief Gets if this certificate bundle comes from a single certificate bundle file
|
||||
/// @return
|
||||
bool is_using_bundle_file() const {
|
||||
return (source == X509CertificateSource::FILE);
|
||||
}
|
||||
|
||||
/// @brief Gets if this certificate bundle comes from an entire directory
|
||||
/// @return
|
||||
bool is_using_directory() const {
|
||||
return (source == X509CertificateSource::DIRECTORY);
|
||||
}
|
||||
|
||||
/// @return True if multiple certificates are contained within
|
||||
bool is_bundle() const {
|
||||
return (get_certificate_count() > 1);
|
||||
}
|
||||
|
||||
bool empty() const {
|
||||
return certificates.empty();
|
||||
}
|
||||
|
||||
/// @return Contained certificate count
|
||||
int get_certificate_count() const;
|
||||
|
||||
/// @return Contained certificate chains count
|
||||
int get_certificate_chains_count() const;
|
||||
|
||||
fs::path get_path() const {
|
||||
return path;
|
||||
}
|
||||
|
||||
X509CertificateSource get_source() const {
|
||||
return source;
|
||||
}
|
||||
|
||||
/// @brief Iterates through all the contained certificate chains (file, certificates)
|
||||
/// while the provided function returns true
|
||||
template <typename function> void for_each_chain(function func) {
|
||||
for (const auto& chain : certificates) {
|
||||
if (!func(chain.first, chain.second)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Same as 'for_each_chain' but it also uses a predicate for ordering
|
||||
template <typename function, typename ordering> void for_each_chain_ordered(function func, ordering order) {
|
||||
struct Chain {
|
||||
const fs::path* path;
|
||||
const std::vector<X509Wrapper>* certificates;
|
||||
};
|
||||
|
||||
std::vector<Chain> ordered;
|
||||
ordered.reserve(certificates.size());
|
||||
for (auto& [path, certs] : certificates) {
|
||||
ordered.push_back(Chain{&path, &certs});
|
||||
}
|
||||
|
||||
std::sort(std::begin(ordered), std::end(ordered),
|
||||
[&order](Chain& a, Chain& b) { return order(*a.certificates, *b.certificates); });
|
||||
|
||||
for (const auto& chain : ordered) {
|
||||
if (!func(*chain.path, *chain.certificates)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @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 hash
|
||||
bool contains_certificate(const CertificateHashData& certificate_hash);
|
||||
|
||||
/// @brief Finds a certificate based on its hash, returning an empty optional if none is found
|
||||
std::optional<X509Wrapper> find_certificate(const CertificateHashData& certificate_hash,
|
||||
bool case_insensitive_comparison = false);
|
||||
|
||||
/// @brief Adds a single certificate in the bundle. Only in memory, use @ref export_certificates to filesystem
|
||||
void add_certificate(X509Wrapper&& certificate);
|
||||
|
||||
/// @brief Adds a single certificate in the bundle, only if it is not contained
|
||||
/// already. Only in memory, use @ref export_certificates to filesystem
|
||||
void add_certificate_unique(X509Wrapper&& certificate);
|
||||
|
||||
/// @brief Updates an already existing certificate if it is found
|
||||
bool update_certificate(X509Wrapper&& certificate);
|
||||
|
||||
/// @brief Deletes all instances of the provided certificate. Only in memory, use @ref export_certificates
|
||||
/// to filesystem export
|
||||
/// @param include_issued If true the child certificates will also be deleted, if any are found, for
|
||||
/// example if we delete CA1 from CA1->CA2->Leaf, all the chain will be deleted
|
||||
/// @param include_top If true the certificates that issues this will also be deleted if any are found, for
|
||||
/// example if we have a chain CA1->CA2->Leaf and we delete the Leaf, CA1 and CA2 will also be deleted
|
||||
/// @return the certificates that have been removed from memory
|
||||
std::vector<X509Wrapper> delete_certificate(const X509Wrapper& certificate, bool include_issued, bool include_top);
|
||||
|
||||
/// @brief Deletes all certificates with the provided certificate hash. Only in memory,
|
||||
/// use @ref export_certificates to filesystem export
|
||||
/// @param include_issued If true the child certificates will also be deleted, if any are found, for
|
||||
/// example if we delete CA1 from CA1->CA2->Leaf, all the chain will be deleted
|
||||
/// @param include_top If true the certificates that issues this will also be deleted if any are found, for
|
||||
/// example if we have a chain CA1->CA2->Leaf and we delete the Leaf, CA1 and CA2 will also be deleted
|
||||
/// @return the certificates that have been removed from memory
|
||||
std::vector<X509Wrapper> delete_certificate(const CertificateHashData& data, bool include_issued, bool include_top);
|
||||
|
||||
/// @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 Returns a full exportable representation of a certificate sub-chain, if found
|
||||
std::string to_export_string(const fs::path& chain) 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
|
||||
/// storage and deleting the certificates that are not contained in this bundle
|
||||
bool sync_to_certificate_store();
|
||||
|
||||
/// @brief returns the latest valid certificate within this bundle
|
||||
X509Wrapper get_latest_valid_certificate();
|
||||
|
||||
/// @brief Utility that returns current the certificate hierarchy of this bundle
|
||||
/// Invalidated on any add/delete operation
|
||||
X509CertificateHierarchy& get_certificate_hierarchy();
|
||||
|
||||
X509CertificateBundle& operator=(X509CertificateBundle&& other) = default;
|
||||
|
||||
/// @brief Returns the latest valid certif that we might contain
|
||||
static X509Wrapper get_latest_valid_certificate(const std::vector<X509Wrapper>& certificates);
|
||||
|
||||
static bool is_certificate_file(const fs::path& file) {
|
||||
return fs::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_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();
|
||||
|
||||
// Structure of the bundle - maps files to the certificates stored in them
|
||||
// For certificates coming from a string, uses a default empty path
|
||||
std::map<fs::path, std::vector<X509Wrapper>> certificates;
|
||||
// Relevant bundle file or directory for the certificates
|
||||
fs::path path;
|
||||
// Source from where we created the certificates. If 'string' the 'export' functions will not work
|
||||
X509CertificateSource source;
|
||||
|
||||
// Cached certificate hierarchy, invalidated on any operation
|
||||
X509CertificateHierarchy hierarchy;
|
||||
bool hierarchy_invalidated;
|
||||
};
|
||||
|
||||
} // namespace evse_security
|
||||
@@ -0,0 +1,166 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#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;
|
||||
};
|
||||
|
||||
class InvalidStateException : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
struct NodeState {
|
||||
std::uint32_t is_selfsigned : 1;
|
||||
std::uint32_t is_orphan : 1; // 0 means temporary orphan, 1 is permanent orphan, no relevance if it is self-signed
|
||||
};
|
||||
|
||||
struct X509Node {
|
||||
NodeState state;
|
||||
|
||||
X509Wrapper certificate; ///< Certificate that we hold
|
||||
std::optional<CertificateHashData>
|
||||
hash; ///< Precomputed certificate hash, in case of an orphan certificate it might not be set
|
||||
|
||||
X509Wrapper issuer; ///< Issuer of this certificate, can be == with certificate if we are a root
|
||||
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
|
||||
/// Note: non self-signed roots and cross-signed certificates are not supported now
|
||||
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_internal_root(const X509Wrapper& certificate) const;
|
||||
|
||||
/// @brief Collects all the descendants of the provided certificate, in the order
|
||||
/// from the root towards the leaf (ROOT->SUBCA1->SUBCA2->LEAF)
|
||||
/// NOTE: this can be a problem when returning certificates to the CSMS that can
|
||||
/// require to have an order from the leaf to the root, case in which the descendants
|
||||
/// have to be reverse iterated
|
||||
///
|
||||
/// @param top Certificate that issued the descendants
|
||||
std::vector<X509Wrapper> collect_descendants(const X509Wrapper& top);
|
||||
|
||||
/// @brief Collects all the top certificates of the provided leaf, in the
|
||||
/// order from the leaf towards the top (LEAF->SUBCA2->SUBCA1)
|
||||
/// @param leaf Leaf certificate for which we collect the top certificates
|
||||
std::vector<X509Wrapper> collect_top(const X509Wrapper& leaf);
|
||||
|
||||
/// @brief Obtains the hash data of the certificate, finding its issuer if needed
|
||||
/// @return True if a hash could be found, false otherwise
|
||||
bool get_certificate_hash(const X509Wrapper& certificate, CertificateHashData& out_hash);
|
||||
|
||||
/// @brief returns true if we contain a certificate with the following hash
|
||||
bool contains_certificate_hash(const CertificateHashData& hash, bool case_insensitive_comparison);
|
||||
|
||||
/// @brief Searches for the root of the provided leaf, returning an empty optional if none was found
|
||||
std::optional<X509Wrapper> find_certificate_root(const X509Wrapper& leaf);
|
||||
|
||||
/// @brief Searches for the provided hash, returning an empty optional if none was found
|
||||
std::optional<X509Wrapper> find_certificate(const CertificateHashData& hash,
|
||||
bool case_insensitive_comparison = false);
|
||||
|
||||
/// @brief Searches for all the certificates with the provided hash, throwing a NoCertificateFound
|
||||
// if none were found. Can be useful when we have SUB-CAs in multiple bundles
|
||||
std::vector<X509Wrapper> find_certificates_multi(const CertificateHashData& hash);
|
||||
|
||||
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); // NOLINT(modernize-use-emplace)
|
||||
}
|
||||
}
|
||||
|
||||
while (!queue.empty()) {
|
||||
X509Node& top = queue.front();
|
||||
queue.pop();
|
||||
|
||||
// Process node
|
||||
if (!func(top)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& child : top.children) {
|
||||
queue.push(child); // NOLINT(modernize-use-emplace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @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);
|
||||
|
||||
template <typename... Args> static X509CertificateHierarchy build_hierarchy(Args... certificates) {
|
||||
X509CertificateHierarchy ordered;
|
||||
|
||||
(std::for_each(certificates.begin(), certificates.end(),
|
||||
#if defined(__GNUC__) && !defined(__clang__) && (__GNUC__ < 9)
|
||||
[ordered_ptr = &ordered](X509Wrapper& cert) { ordered_ptr->insert(std::move(cert)); }),
|
||||
#else
|
||||
[&ordered](X509Wrapper& cert) { ordered.insert(std::move(cert)); }),
|
||||
#endif
|
||||
...); // Fold expr
|
||||
|
||||
// Prune the tree
|
||||
ordered.prune();
|
||||
|
||||
return ordered;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<std::pair<const X509Node*, int>> find_certificate_root_node(const X509Wrapper& leaf);
|
||||
|
||||
/// @brief Inserts the certificate in the hierarchy. If it is not a root
|
||||
/// and a parent is not found, it will be inserted as a temporary orphan
|
||||
void insert(X509Wrapper&& certificate);
|
||||
|
||||
/// @brief After inserting all certificates in the hierarchy, attempts
|
||||
/// to parent all temporarly orphan certificates, marking the ones that
|
||||
/// were not successfully parented as permanently orphan
|
||||
void prune();
|
||||
|
||||
std::vector<X509Node> hierarchy;
|
||||
};
|
||||
|
||||
} // namespace evse_security
|
||||
@@ -0,0 +1,141 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#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 {
|
||||
|
||||
enum class X509CertificateSource {
|
||||
// Built from a certificate file
|
||||
FILE,
|
||||
// Built from a directory of certificates
|
||||
DIRECTORY,
|
||||
// Build from a raw string
|
||||
STRING
|
||||
};
|
||||
|
||||
/// @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);
|
||||
|
||||
explicit X509Wrapper(X509Handle_ptr&& x509);
|
||||
X509Wrapper(X509Handle_ptr&& x509, const fs::path& file);
|
||||
|
||||
X509Wrapper(const X509Wrapper& other);
|
||||
X509Wrapper(X509Wrapper&& other) = default;
|
||||
|
||||
~X509Wrapper() = default;
|
||||
|
||||
/// @brief Returns true if this certificate is the child of the provided parent
|
||||
bool is_child(const X509Wrapper& parent) const;
|
||||
|
||||
/// @brief Returns true if this certificate is self-signed
|
||||
bool is_selfsigned() const;
|
||||
|
||||
/// @brief Gets x509 raw handle
|
||||
inline X509Handle* get() const {
|
||||
return x509.get();
|
||||
}
|
||||
|
||||
/// @brief Gets valid_in
|
||||
/// @return seconds until certificate is valid; if > 0 cert is not yet valid
|
||||
int64_t get_valid_in() const;
|
||||
|
||||
/// @brief Gets valid_in
|
||||
/// @return seconds until certificate is expired; if < 0 cert has expired
|
||||
int64_t get_valid_to() const;
|
||||
|
||||
/// @brief Gets optional file of certificate
|
||||
/// @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;
|
||||
|
||||
/// @brief Gets CN of certificate
|
||||
/// @result
|
||||
std::string get_common_name() const;
|
||||
|
||||
/// @brief Gets issuer name hash of certificate
|
||||
/// @result
|
||||
std::string get_issuer_name_hash() const;
|
||||
|
||||
/// @brief Gets issuer key hash of certificate. As per the specification
|
||||
/// this is not the hash of our public key, but the hash of the pubkey of
|
||||
/// the parent certificate. If it is a self-signed root, then the key hash
|
||||
/// and the issuer key hash are the same
|
||||
/// @result
|
||||
std::string get_issuer_key_hash() const;
|
||||
|
||||
/// @brief Gets key hash of this certificate
|
||||
/// @result
|
||||
std::string get_key_hash() const;
|
||||
|
||||
/// @brief Gets serial number of certificate
|
||||
/// @result
|
||||
std::string get_serial_number() const;
|
||||
|
||||
/// @brief Gets certificate hash data of a self-signed certificate
|
||||
/// @return
|
||||
CertificateHashData get_certificate_hash_data() const;
|
||||
|
||||
/// @brief Gets certificate hash data of certificate with an issuer
|
||||
/// @return
|
||||
CertificateHashData get_certificate_hash_data(const X509Wrapper& issuer) const;
|
||||
|
||||
/// @brief Gets OCSP responder URL of certificate if present, else returns an empty string
|
||||
/// @return
|
||||
std::string get_responder_url() const;
|
||||
|
||||
/// @brief Gets the export string representation for this certificate
|
||||
/// @return
|
||||
std::string get_export_string() const;
|
||||
|
||||
/// @brief If the certificate is within the validity date. Can return false in 2 cases,
|
||||
/// if it is expired (current date > valid_to) or if (current data < valid_in), that is
|
||||
/// we are not in force yet
|
||||
bool is_valid() const;
|
||||
|
||||
/// @brief If the certificate will be valid in the future, that is (current date > valid_in)
|
||||
/// and (current data > valid_to)
|
||||
bool is_valid_in_future() const;
|
||||
|
||||
/// @brief If the certificate has expired
|
||||
bool is_expired() const;
|
||||
|
||||
X509Wrapper& operator=(X509Wrapper&& other) = default;
|
||||
|
||||
/// @return true if the two certificates are the same
|
||||
bool operator==(const X509Wrapper& other) const;
|
||||
|
||||
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 update_validity();
|
||||
|
||||
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
|
||||
|
||||
// Relevant file in which this certificate resides
|
||||
std::optional<fs::path> file;
|
||||
|
||||
std::string debug_common_name;
|
||||
};
|
||||
|
||||
} // namespace evse_security
|
||||
@@ -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_crypto_supplier.hpp>
|
||||
namespace evse_security {
|
||||
using CryptoSupplier = OpenSSLSupplier; // Define others with the same 'CryptoSupplier' name
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,88 @@
|
||||
// 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 Name of supplier, char is not required to be released
|
||||
static const char* get_supplier_name();
|
||||
|
||||
/// @brief If any TPM operations are supported
|
||||
static bool supports_tpm();
|
||||
static bool supports_tpm_key_creation();
|
||||
/// @brief If creation from a custom provider is supported
|
||||
static bool supports_custom_key_creation();
|
||||
|
||||
// Key utilities
|
||||
static bool generate_key(const KeyGenerationInfo& generation_info, KeyHandle_ptr& out_key);
|
||||
|
||||
/// @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);
|
||||
|
||||
// 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 bool 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 CertificateValidationResult
|
||||
x509_verify_certificate_chain(X509Handle* target, const std::vector<X509Handle*>& parents,
|
||||
const std::vector<X509Handle*>& untrusted_subcas, bool allow_future_certificates,
|
||||
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 KeyValidationResult 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::uint8_t>& signature,
|
||||
const std::vector<std::uint8_t>& data);
|
||||
|
||||
/// @brief Generates a certificate signing request with the provided parameters
|
||||
static CertificateSignRequestResult x509_generate_csr(const CertificateSigningRequestInfo& generation_info,
|
||||
std::string& out_csr);
|
||||
|
||||
// Digesting/decoding utils
|
||||
static bool digest_file_sha256(const fs::path& path, std::vector<std::uint8_t>& out_digest);
|
||||
|
||||
static bool base64_decode_to_bytes(const std::string& base64_string, std::vector<std::uint8_t>& out_decoded);
|
||||
static bool base64_decode_to_string(const std::string& base64_string, std::string& out_decoded);
|
||||
|
||||
static bool base64_encode_from_bytes(const std::vector<std::uint8_t>& bytes, std::string& out_encoded);
|
||||
static bool base64_encode_from_string(const std::string& string, std::string& out_encoded);
|
||||
};
|
||||
|
||||
} // namespace evse_security
|
||||
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <evse_security/utils/evse_filesystem_types.hpp>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
namespace evse_security {
|
||||
|
||||
// NOLINTNEXTLINE(cert-int09-c, readability-enum-initial-value): prefer implicit initialization for readability
|
||||
enum class CryptoKeyType {
|
||||
EC_prime256v1, // Default EC. P-256, ~equiv to rsa 3072
|
||||
EC_secp384r1, // P-384, ~equiv to rsa 7680
|
||||
RSA_2048,
|
||||
RSA_TPM20 = RSA_2048, // Default TPM RSA, only option allowed for TPM (universal support), 2048 bits
|
||||
RSA_3072, // Default RSA. Protection lifetime: ~2030
|
||||
RSA_7680, // Protection lifetime: >2031. Very long generation time 8-40s on 16 core PC
|
||||
};
|
||||
|
||||
enum class KeyValidationResult {
|
||||
Valid,
|
||||
KeyLoadFailure, // The key could not be loaded, might be an password or invalid string
|
||||
Invalid, // The key is not linked to the specified certificate
|
||||
Unknown, // Unknown error, not related to provider validation
|
||||
};
|
||||
|
||||
enum class CertificateSignRequestResult {
|
||||
Valid,
|
||||
KeyGenerationError, // Error when generating the key, maybe invalid key type
|
||||
VersioningError, // The version could not be set
|
||||
PubkeyError, // The public key could not be attached
|
||||
ExtensionsError, // The extensions could not be appended
|
||||
SigningError, // The CSR could not be signed, maybe key or signing algo invalid
|
||||
Unknown, // Any other error
|
||||
};
|
||||
|
||||
struct KeyGenerationInfo {
|
||||
CryptoKeyType key_type;
|
||||
|
||||
/// @brief If the key should be generated using the custom provider. The custom
|
||||
/// provider can be the TPM if it was so configured. Should check before if
|
||||
/// the provider supports the operation, or the operation will fail by default
|
||||
bool generate_on_custom;
|
||||
|
||||
/// @brief If we should export the public key to a file
|
||||
std::optional<fs::path> public_key_file;
|
||||
|
||||
/// @brief If we should export the private key to a file
|
||||
std::optional<fs::path> private_key_file;
|
||||
/// @brief If we should have a pass for the private key file
|
||||
std::optional<std::string> private_key_pass;
|
||||
};
|
||||
|
||||
struct CertificateSigningRequestInfo {
|
||||
// Minimal mandatory
|
||||
int n_version;
|
||||
std::string country;
|
||||
std::string organization;
|
||||
std::string commonName;
|
||||
|
||||
/// @brief incude a subjectAlternativeName DNSName
|
||||
std::optional<std::string> dns_name;
|
||||
/// @brief incude a subjectAlternativeName IPAddress
|
||||
std::optional<std::string> ip_address;
|
||||
|
||||
KeyGenerationInfo key_info;
|
||||
};
|
||||
class CertificateLoadException : public std::runtime_error {
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
struct CryptoHandle {
|
||||
virtual ~CryptoHandle() = default;
|
||||
};
|
||||
|
||||
/// @brief Handle abstraction to crypto lib X509 certificate
|
||||
struct X509Handle : public CryptoHandle {};
|
||||
|
||||
/// @brief Handle abstraction to crypto lib key
|
||||
struct KeyHandle : public CryptoHandle {};
|
||||
|
||||
using X509Handle_ptr = std::unique_ptr<X509Handle>;
|
||||
using KeyHandle_ptr = std::unique_ptr<KeyHandle>;
|
||||
|
||||
// Transforms a duration of days into seconds
|
||||
using days_to_seconds = std::chrono::duration<std::int64_t, std::ratio<86400>>;
|
||||
|
||||
namespace conversions {
|
||||
std::string get_certificate_sign_request_result_to_string(CertificateSignRequestResult e);
|
||||
}
|
||||
|
||||
} // namespace evse_security
|
||||
@@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#ifdef LIBEVSE_CRYPTO_SUPPLIER_OPENSSL
|
||||
|
||||
#include <evse_security/crypto/interface/crypto_supplier.hpp>
|
||||
|
||||
namespace evse_security {
|
||||
|
||||
class OpenSSLSupplier : public AbstractCryptoSupplier {
|
||||
public:
|
||||
static const char* get_supplier_name();
|
||||
|
||||
static bool supports_tpm();
|
||||
static bool supports_tpm_key_creation();
|
||||
static bool supports_custom_key_creation();
|
||||
|
||||
static bool generate_key(const KeyGenerationInfo& key_info, KeyHandle_ptr& out_key);
|
||||
|
||||
static std::vector<X509Handle_ptr> load_certificates(const std::string& data, const EncodingFormat encoding);
|
||||
|
||||
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);
|
||||
static bool 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);
|
||||
static CertificateValidationResult
|
||||
x509_verify_certificate_chain(X509Handle* target, const std::vector<X509Handle*>& parents,
|
||||
const std::vector<X509Handle*>& untrusted_subcas, bool allow_future_certificates,
|
||||
const std::optional<fs::path> dir_path, const std::optional<fs::path> file_path);
|
||||
static KeyValidationResult x509_check_private_key(X509Handle* handle, std::string private_key,
|
||||
std::optional<std::string> password);
|
||||
static bool x509_verify_signature(X509Handle* handle, const std::vector<std::uint8_t>& signature,
|
||||
const std::vector<std::uint8_t>& data);
|
||||
|
||||
static CertificateSignRequestResult x509_generate_csr(const CertificateSigningRequestInfo& csr_info,
|
||||
std::string& out_csr);
|
||||
|
||||
static bool digest_file_sha256(const fs::path& path, std::vector<std::uint8_t>& out_digest);
|
||||
|
||||
static bool base64_decode_to_bytes(const std::string& base64_string, std::vector<std::uint8_t>& out_decoded);
|
||||
static bool base64_decode_to_string(const std::string& base64_string, std::string& out_decoded);
|
||||
|
||||
static bool base64_encode_from_bytes(const std::vector<std::uint8_t>& bytes, std::string& out_encoded);
|
||||
static bool base64_encode_from_string(const std::string& string, std::string& out_encoded);
|
||||
};
|
||||
|
||||
} // namespace evse_security
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,171 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#ifndef OPENSSL_TPM_HPP
|
||||
#define OPENSSL_TPM_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include <evse_security/utils/evse_filesystem_types.hpp>
|
||||
|
||||
// opaque types (from OpenSSL)
|
||||
struct ossl_lib_ctx_st; // OpenSSL OSSL_LIB_CTX;
|
||||
struct ossl_provider_st; // OpenSSL OSSL_PROVIDER
|
||||
|
||||
namespace evse_security {
|
||||
|
||||
/// @brief determine if the PEM string is a custom private key. Will
|
||||
/// only work for private keys, public keys will always return true
|
||||
/// @param private_key_pem string containing the PEM encoded key
|
||||
/// @return true when file does not start "-----BEGIN PRIVATE KEY-----"
|
||||
/// @note works irrespective of OpenSSL version
|
||||
bool is_custom_private_key_string(const std::string& private_key_pem);
|
||||
|
||||
/// @brief determine if the PEM file contains a custom private key. Will
|
||||
/// only work for private keys, public keys will always return true
|
||||
/// @param private_key_file_pem filename of the PEM file
|
||||
/// @return true when file does not start "-----BEGIN PRIVATE KEY-----"
|
||||
/// @note works irrespective of OpenSSL version
|
||||
bool is_custom_private_key_file(const fs::path& private_key_file_pem);
|
||||
|
||||
/// @brief Manage the loading and configuring of OpenSSL providers
|
||||
///
|
||||
/// There are two providers considered:
|
||||
/// - 'default'
|
||||
/// - 'tpm2' for working with TSS2 keys (protected by a TPM)
|
||||
/// The 'tpm2' can be replaced with a custom provider (see CMakeLists.txt)
|
||||
///
|
||||
/// There are two contexts:
|
||||
/// - 'global' for general use
|
||||
/// - 'tls' for TLS connections
|
||||
///
|
||||
/// The class also acts as a scoped mutex to prevent changes in the
|
||||
/// provider configuration during crypto operations
|
||||
///
|
||||
/// @note OpenSSL SSL_CTX caches the propquery so updates via
|
||||
/// this class may not be effective. See SSL_CTX_new_ex()
|
||||
///
|
||||
/// This code provides a null implementation when OpenSSL 3 or later isn't
|
||||
/// used. The null implementation is also used when -DUSING_TPM2=OFF is
|
||||
/// set with cmake.
|
||||
///
|
||||
/// @note the tpm2-abrmd daemon is needed to support openssl-tpm2 for TLS
|
||||
class OpenSSLProvider {
|
||||
public:
|
||||
/// @brief supported propquery strings
|
||||
enum class mode_t {
|
||||
default_provider,
|
||||
custom_provider,
|
||||
};
|
||||
|
||||
private:
|
||||
using flags_underlying_t = std::uint8_t;
|
||||
enum class flags_t : flags_underlying_t {
|
||||
initialised,
|
||||
custom_provider_available,
|
||||
global_custom_provider,
|
||||
tls_custom_provider,
|
||||
};
|
||||
|
||||
static std::mutex s_mux;
|
||||
static flags_underlying_t s_flags;
|
||||
|
||||
static struct ossl_provider_st* s_global_prov_default_p;
|
||||
static struct ossl_provider_st* s_global_prov_custom_p;
|
||||
static struct ossl_provider_st* s_tls_prov_default_p;
|
||||
static struct ossl_provider_st* s_tls_prov_custom_p;
|
||||
static struct ossl_lib_ctx_st* s_tls_libctx_p;
|
||||
|
||||
static inline void reset(flags_t f) {
|
||||
s_flags &= ~(1 << static_cast<flags_underlying_t>(f));
|
||||
}
|
||||
|
||||
static inline void set(flags_t f) {
|
||||
s_flags |= 1 << static_cast<flags_underlying_t>(f);
|
||||
}
|
||||
|
||||
static inline bool is_set(flags_t f) {
|
||||
return (s_flags & (1 << static_cast<flags_underlying_t>(f))) != 0;
|
||||
}
|
||||
|
||||
static inline bool is_reset(flags_t f) {
|
||||
return !is_set(f);
|
||||
}
|
||||
|
||||
/// @brief uodate the flag
|
||||
/// @param f - flag to update
|
||||
/// @param val - whether to set or reset the flag
|
||||
/// @return true when the flag was changed
|
||||
static inline bool update(flags_t f, bool val) {
|
||||
const bool result = (val != is_set(f));
|
||||
if (val) {
|
||||
set(f);
|
||||
} else {
|
||||
reset(f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool load(struct ossl_provider_st*& default_p, struct ossl_provider_st*& custom_p, struct ossl_lib_ctx_st* libctx_p,
|
||||
mode_t mode);
|
||||
inline bool load_global(mode_t mode) {
|
||||
return load(s_global_prov_default_p, s_global_prov_custom_p, nullptr, mode);
|
||||
}
|
||||
inline bool load_tls(mode_t mode) {
|
||||
return load(s_tls_prov_default_p, s_tls_prov_custom_p, s_tls_libctx_p, mode);
|
||||
}
|
||||
|
||||
bool set_propstr(struct ossl_lib_ctx_st* libctx, mode_t mode);
|
||||
bool set_mode(struct ossl_lib_ctx_st* libctx, mode_t mode);
|
||||
|
||||
public:
|
||||
OpenSSLProvider();
|
||||
~OpenSSLProvider(); // NOLINT(performance-trivially-destructible): dtor impl dependent on ifdef
|
||||
|
||||
inline void set_global_mode(mode_t mode) {
|
||||
set_mode(nullptr, mode);
|
||||
}
|
||||
|
||||
inline void set_tls_mode(mode_t mode) {
|
||||
set_mode(s_tls_libctx_p, mode);
|
||||
}
|
||||
|
||||
const char* propquery(mode_t mode) const;
|
||||
|
||||
inline mode_t propquery_global() const {
|
||||
return (is_set(flags_t::global_custom_provider)) ? mode_t::custom_provider : mode_t::default_provider;
|
||||
}
|
||||
inline mode_t propquery_tls() const {
|
||||
return (is_set(flags_t::tls_custom_provider)) ? mode_t::custom_provider : mode_t::default_provider;
|
||||
}
|
||||
|
||||
inline const char* propquery_global_str() const {
|
||||
return propquery(propquery_global());
|
||||
}
|
||||
inline const char* propquery_tls_str() const {
|
||||
return propquery(propquery_tls());
|
||||
}
|
||||
inline const char* propquery_default() const {
|
||||
return propquery(mode_t::default_provider);
|
||||
}
|
||||
inline const char* propquery_custom() const {
|
||||
return propquery(mode_t::custom_provider);
|
||||
}
|
||||
|
||||
/// @brief return the TLS OSSL library context
|
||||
inline operator struct ossl_lib_ctx_st *() {
|
||||
return s_tls_libctx_p;
|
||||
}
|
||||
|
||||
static bool supports_provider_tpm();
|
||||
static bool supports_provider_custom();
|
||||
|
||||
static void cleanup();
|
||||
};
|
||||
|
||||
} // namespace evse_security
|
||||
|
||||
#endif // OPENSSL_TPM_HPP
|
||||
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#ifdef LIBEVSE_CRYPTO_SUPPLIER_OPENSSL
|
||||
|
||||
#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<STACK_OF(X509)> {
|
||||
public:
|
||||
void operator()(STACK_OF(X509) * ptr) const {
|
||||
sk_X509_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<EVP_PKEY_CTX> {
|
||||
public:
|
||||
void operator()(EVP_PKEY_CTX* ptr) const {
|
||||
::EVP_PKEY_CTX_free(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
template <> class std::default_delete<BIO> {
|
||||
public:
|
||||
void operator()(BIO* ptr) const {
|
||||
::BIO_free(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
template <> class std::default_delete<EVP_MD_CTX> {
|
||||
public:
|
||||
void operator()(EVP_MD_CTX* ptr) const {
|
||||
::EVP_MD_CTX_destroy(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
template <> class std::default_delete<EVP_ENCODE_CTX> {
|
||||
public:
|
||||
void operator()(EVP_ENCODE_CTX* ptr) const {
|
||||
::EVP_ENCODE_CTX_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>;
|
||||
// Unsafe since it does not free contained elements, only the stack, the element
|
||||
// cleanup has to be done manually
|
||||
using X509_STACK_UNSAFE_ptr = std::unique_ptr<STACK_OF(X509)>;
|
||||
using X509_REQ_ptr = std::unique_ptr<X509_REQ>;
|
||||
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY>;
|
||||
using EVP_PKEY_CTX_ptr = std::unique_ptr<EVP_PKEY_CTX>;
|
||||
using BIO_ptr = std::unique_ptr<BIO>;
|
||||
using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX>;
|
||||
using EVP_ENCODE_CTX_ptr = std::unique_ptr<EVP_ENCODE_CTX>;
|
||||
|
||||
struct X509Handle;
|
||||
struct KeyHandle;
|
||||
|
||||
struct X509HandleOpenSSL : public X509Handle {
|
||||
X509HandleOpenSSL(X509* certificate) : x509(certificate) {
|
||||
}
|
||||
|
||||
X509* get() {
|
||||
return x509.get();
|
||||
}
|
||||
|
||||
private:
|
||||
X509_ptr x509;
|
||||
};
|
||||
|
||||
struct KeyHandleOpenSSL : public KeyHandle {
|
||||
KeyHandleOpenSSL(EVP_PKEY* key) : key(key) {
|
||||
}
|
||||
|
||||
EVP_PKEY* get() {
|
||||
return key.get();
|
||||
}
|
||||
|
||||
private:
|
||||
EVP_PKEY_ptr key;
|
||||
};
|
||||
|
||||
} // namespace evse_security
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,370 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <everest/timer.hpp>
|
||||
|
||||
#include <evse_security/crypto/evse_crypto.hpp>
|
||||
#include <evse_security/evse_types.hpp>
|
||||
#include <evse_security/utils/evse_filesystem_types.hpp>
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <set>
|
||||
|
||||
#ifdef BUILD_TESTING_EVSE_SECURITY
|
||||
#include <gtest/gtest_prod.h>
|
||||
#endif
|
||||
|
||||
namespace evse_security {
|
||||
|
||||
struct LinkPaths {
|
||||
fs::path secc_leaf_cert_link;
|
||||
fs::path secc_leaf_key_link;
|
||||
fs::path cpo_cert_chain_link;
|
||||
};
|
||||
|
||||
struct DirectoryPaths {
|
||||
fs::path csms_leaf_cert_directory; /**< csms leaf certificate for OCPP shall be located in this directory */
|
||||
fs::path csms_leaf_key_directory; /**< csms leaf key shall be located in this directory */
|
||||
fs::path secc_leaf_cert_directory; /**< secc leaf certificate for ISO15118 shall be located in this directory */
|
||||
fs::path secc_leaf_key_directory; /**< secc leaf key shall be located in this directory */
|
||||
};
|
||||
struct FilePaths {
|
||||
// bundle paths
|
||||
fs::path csms_ca_bundle;
|
||||
fs::path mf_ca_bundle;
|
||||
fs::path mo_ca_bundle;
|
||||
fs::path v2g_ca_bundle;
|
||||
|
||||
DirectoryPaths directories;
|
||||
LinkPaths links;
|
||||
};
|
||||
|
||||
struct CertificateQueryParams {
|
||||
LeafCertificateType certificate_type;
|
||||
EncodingFormat encoding{EncodingFormat::PEM};
|
||||
/// if OCSP information should be included
|
||||
bool include_ocsp{false};
|
||||
/// if the root certificate of the leaf should be included in the returned list
|
||||
bool include_root{false};
|
||||
/// if true, all valid leafs will be included, sorted in order, with the newest being
|
||||
/// first. If false, only the newest one will be returned
|
||||
bool include_all_valid{false};
|
||||
/// if true the leafs that will be valid in the future will be included, with the newest
|
||||
/// being first
|
||||
bool include_future_valid{false};
|
||||
/// if true, will remove all duplicates found, since we can find a leaf for example
|
||||
/// in 2 files, one in 'leaf_single' and one in 'leaf_chain'. For delete routines
|
||||
/// we need both files returned, while for queries (v2g_chain) we don't need duplicates
|
||||
bool remove_duplicates{false};
|
||||
};
|
||||
|
||||
// Unchangeable security limit for certificate deletion, a min entry count will be always kept (newest)
|
||||
static constexpr std::size_t DEFAULT_MINIMUM_CERTIFICATE_ENTRIES = 10;
|
||||
// 50 MB default limit for filesystem usage
|
||||
static constexpr std::uintmax_t DEFAULT_MAX_FILESYSTEM_SIZE = 1024 * 1024 * 50;
|
||||
// Default maximum 2000 certificate entries
|
||||
static constexpr std::uintmax_t DEFAULT_MAX_CERTIFICATE_ENTRIES = 2000;
|
||||
|
||||
// Expiry for CSRs that did not receive a response CSR, 60 minutes
|
||||
static constexpr std::chrono::seconds DEFAULT_CSR_EXPIRY(3600);
|
||||
|
||||
// Garbage collect default time, 20 minutes
|
||||
static constexpr std::chrono::seconds DEFAULT_GARBAGE_COLLECT_TIME(20 * 60);
|
||||
|
||||
/// @brief This class holds filesystem paths to CA bundle file locations and directories for leaf certificates
|
||||
class EvseSecurity {
|
||||
|
||||
public:
|
||||
/// @brief Constructor initializes the certificate and key storage using the given \p file_paths for the different
|
||||
/// PKIs. For CA certificates either CA bundle files or directories containing the certificates can be specified.
|
||||
/// For the SECC and CSMS leaf certificates, directories must be specified.
|
||||
/// @param file_paths specifies the certificate and key storage locations on the filesystem
|
||||
/// @param private_key_password optional password for encrypted private keys
|
||||
/// @param max_fs_usage_bytes optional maximum filesystem usage for certificates. Defaults to
|
||||
/// 'DEFAULT_MAX_FILESYSTEM_SIZE'
|
||||
/// @param max_fs_certificate_store_entries optional maximum certificate entries. Defaults to
|
||||
/// 'DEFAULT_MAX_CERTIFICATE_ENTRIES'
|
||||
/// @param csr_expiry optional expiry time for a CSR entry. If the time is exceeded it will be deleted. Defaults to
|
||||
/// 'DEFAULT_CSR_EXPIRY'
|
||||
/// @param garbage_collect_time optional garbage collect time. How often we will delete expired CSRs and
|
||||
/// certificates. Defaults to 'DEFAULT_GARBAGE_COLLECT_TIME'
|
||||
EvseSecurity(const FilePaths& file_paths, const std::optional<std::string>& private_key_password = std::nullopt,
|
||||
const std::optional<std::uintmax_t>& max_fs_usage_bytes = std::nullopt,
|
||||
const std::optional<std::uintmax_t>& max_fs_certificate_store_entries = std::nullopt,
|
||||
const std::optional<std::chrono::seconds>& csr_expiry = std::nullopt,
|
||||
const std::optional<std::chrono::seconds>& garbage_collect_time = std::nullopt);
|
||||
|
||||
/// @brief Destructor
|
||||
~EvseSecurity();
|
||||
|
||||
/// @brief Installs the given \p certificate within the specified CA bundle file or directory if directories are
|
||||
/// used. If the certificate already exists it will only be updated
|
||||
/// @param certificate PEM formatted CA certificate
|
||||
/// @param certificate_type specifies the CA certificate type
|
||||
/// @return result of the operation
|
||||
InstallCertificateResult install_ca_certificate(const std::string& certificate, CaCertificateType certificate_type);
|
||||
|
||||
/// @brief Deletes the certificate specified by \p certificate_hash_data . If a CA certificate is specified, the
|
||||
/// certificate is removed from the bundle or directory. If a leaf certificate is specified, the file will be
|
||||
/// removed from the filesystem. It will also delete all certificates issued by this certificate, so that no invalid
|
||||
/// hierarchies persisted on the filesystem
|
||||
/// @param certificate_hash_data specifies the certificate to be deleted
|
||||
/// @return result of the operation
|
||||
DeleteResult delete_certificate(const CertificateHashData& certificate_hash_data);
|
||||
|
||||
/// @brief Verifies the given \p certificate_chain against the respective CA
|
||||
/// trust anchors (indicated by the given \p certificate_type) for the leaf.
|
||||
/// @param certificate_chain PEM formatted certificate or certificate chain
|
||||
/// @param certificate_type type of the root certificate for which the chain is verified
|
||||
/// @return result of the operation
|
||||
CertificateValidationResult verify_certificate(const std::string& certificate_chain,
|
||||
const LeafCertificateType certificate_type);
|
||||
|
||||
/// @brief Verifies the given \p certificate_chain against the respective CA
|
||||
/// trust anchors (indicated by the given \p certificate_types) for the leaf.
|
||||
/// @param certificate_chain PEM formatted certificate or certificate chain
|
||||
/// @param certificate_types types of the root certificates for which the chain is verified
|
||||
/// @return result of the operation
|
||||
CertificateValidationResult verify_certificate(const std::string& certificate_chain,
|
||||
const std::vector<LeafCertificateType>& certificate_types);
|
||||
|
||||
/// @brief Verifies the given \p certificate_chain for the given \p certificate_type against the respective CA
|
||||
/// certificates for the leaf and if valid installs the certificate on the filesystem. Before installing on the
|
||||
/// filesystem, this function checks if a private key is present for the given certificate on the filesystem. Two
|
||||
/// files are installed, one containing the single leaf (presuming it is the first in the chain) and also the full
|
||||
/// certificate chain. The \ref get_key_pair function will return a path to both files if they exist, the one
|
||||
/// containing the single leaf, and the file containing the leaf including the SUBCAs if present
|
||||
/// @param certificate_chain PEM formatted certificate or certificate chain
|
||||
/// @param certificate_type type of the leaf certificate
|
||||
/// @return result of the operation
|
||||
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.
|
||||
/// In the case of the 'V2GCertificateChain', it will return all valid leafs chains, with the newest being
|
||||
/// the first in the list
|
||||
/// @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
|
||||
GetInstalledCertificatesResult get_installed_certificates(const std::vector<CertificateType>& certificate_types);
|
||||
|
||||
/// @brief Retrieves the certificate count applying the \p certificate_types filter.
|
||||
int get_count_of_installed_certificates(const std::vector<CertificateType>& certificate_types);
|
||||
|
||||
/// @brief Command to retrieve the OCSP request data of the V2G certificates (SubCAs and possibly V2G leaf)
|
||||
/// @return contains OCSP request data
|
||||
OCSPRequestDataList get_v2g_ocsp_request_data();
|
||||
|
||||
/// @brief Retrieves the OCSP request data of the given MO contract \p certificate_chain. Since
|
||||
/// the provided MO chain can be signed by both the MO_ROOT and the V2G_ROOT, the received chain
|
||||
/// will be tested against both
|
||||
/// @param certificate_chain PEM formatted MO certificate or MO certificate chain
|
||||
/// @return contains OCSP request data
|
||||
OCSPRequestDataList get_mo_ocsp_request_data(const std::string& certificate_chain);
|
||||
|
||||
/// @brief Updates the OCSP cache for the given \p certificate_hash_data with the given \p ocsp_response
|
||||
/// @param certificate_hash_data identifies the certificate for which the \p ocsp_response is specified
|
||||
/// @param ocsp_response the actual OCSP data
|
||||
void update_ocsp_cache(const CertificateHashData& certificate_hash_data, const std::string& ocsp_response);
|
||||
|
||||
// TODO: Switch to path
|
||||
/// @brief Retrieves from the OCSP cache for the given \p certificate_hash_data
|
||||
/// @param certificate_hash_data identifies the certificate for which the \p ocsp_response is specified
|
||||
/// @return the actual OCSP data or an empty value
|
||||
std::optional<fs::path> retrieve_ocsp_cache(const CertificateHashData& certificate_hash_data);
|
||||
|
||||
/// @brief Indicates if a CA certificate for the given \p certificate_type is installed on the filesystem
|
||||
/// Supports both CA certificate bundles and directories
|
||||
/// @param certificate_type
|
||||
/// @return true if CA certificate is present, else false
|
||||
bool is_ca_certificate_installed(CaCertificateType certificate_type);
|
||||
|
||||
/// @brief Should be invoked when a certificate CSR was not properly generated by the CSMS
|
||||
/// and that the pairing key that was generated should be deleted
|
||||
void certificate_signing_request_failed(const std::string& csr, LeafCertificateType certificate_type);
|
||||
|
||||
/// @brief Generates a certificate signing request for the given \p certificate_type , \p country , \p organization
|
||||
/// and \p common , uses the TPM if \p use_tpm is true
|
||||
/// @param certificate_type
|
||||
/// @param country
|
||||
/// @param organization
|
||||
/// @param common
|
||||
/// @param use_custom_provider If the custom provider (which can be the TPM if using -DUSING_TPM2=ON) should be
|
||||
/// used for the CSR request
|
||||
/// @return the status and an optional PEM formatted certificate signing request string
|
||||
GetCertificateSignRequestResult generate_certificate_signing_request(LeafCertificateType certificate_type,
|
||||
const std::string& country,
|
||||
const std::string& organization,
|
||||
const std::string& common,
|
||||
bool use_custom_provider);
|
||||
|
||||
/// @brief Generates a certificate signing request for the given \p certificate_type , \p country , \p organization
|
||||
/// and \p common without using the TPM
|
||||
/// @param certificate_type
|
||||
/// @param country
|
||||
/// @param organization
|
||||
/// @param common
|
||||
/// @return the status and an optional PEM formatted certificate signing request string
|
||||
GetCertificateSignRequestResult 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
|
||||
/// matching the certificate, this function returns a GetKeyPairStatus other than "Accepted". The function \ref
|
||||
/// update_leaf_certificate will install two files for each leaf, one containing the single leaf and one containing
|
||||
/// the leaf including any possible SUBCAs
|
||||
/// @param certificate_type type of the leaf certificate
|
||||
/// @param encoding specifies PEM or DER format
|
||||
/// @param include_ocsp if OCSP data should be included
|
||||
/// @return contains response result, with info related to the certificate chain and response status
|
||||
GetCertificateInfoResult get_leaf_certificate_info(LeafCertificateType certificate_type, EncodingFormat encoding,
|
||||
bool include_ocsp = false);
|
||||
|
||||
/// @brief Finds the latest valid leafs, for each root certificate that is present on the filesystem, and
|
||||
/// returns all the newest valid leafs that are present for different roots. This is required, because
|
||||
/// a query parameter when requesting the leaf is not advisable during the TLS handshake
|
||||
/// Existing filesystem:
|
||||
/// ROOT_V2G_Hubject->SUB_CA1->SUB_CA2->Leaf_Invalid_A
|
||||
/// ROOT_V2G_Hubject->SUB_CA1->SUB_CA2->Leaf_Valid_A
|
||||
/// ROOT_V2G_Hubject->SUB_CA1->SUB_CA2->Leaf_Valid_B
|
||||
/// ROOT_V2G_OtherProvider->SUB_CA_O1->SUB_CA_O2->Leav_Valid_A
|
||||
/// will return:
|
||||
/// ROOT_V2G_Hubject->SUB_CA1->SUB_CA2->Leaf_Valid_B +
|
||||
/// ROOT_V2G_OtherProvider->SUB_CA_O1->SUB_CA_O2->Leav_Valid_A
|
||||
/// Note: non self-signed roots and cross-signed certificates are not supported
|
||||
/// @param certificate_type type of leaf certificate that we start the search from
|
||||
/// @param encoding specifies PEM or DER format
|
||||
/// @param include_ocsp if OCSP data should be included
|
||||
/// @return contains response result, with info related to the full certificate chains info and response status
|
||||
GetCertificateFullInfoResult get_all_valid_certificates_info(LeafCertificateType certificate_type,
|
||||
EncodingFormat encoding, bool include_ocsp = false);
|
||||
|
||||
/// @brief Checks and updates the symlinks for the V2G leaf certificates and keys to the most recent valid one
|
||||
/// @return true if one of the links was updated
|
||||
bool update_certificate_links(LeafCertificateType certificate_type);
|
||||
|
||||
/// @brief Retrieves the PEM formatted CA bundle file for the given \p certificate_type It is not recommended to
|
||||
/// add the SUBCAs to any root certificate bundle, but to leave them in the leaf file. See \ref
|
||||
/// update_leaf_certificate
|
||||
/// @param certificate_type
|
||||
/// @return CA certificate file
|
||||
std::string get_verify_file(CaCertificateType certificate_type);
|
||||
|
||||
/// @brief Retrieves the PEM formatted CA bundle location for the given \p certificate_type It is not recommended to
|
||||
/// add the SUBCAs to any root certificate bundle, but to leave them in the leaf file. Returns either file
|
||||
/// or directory where the certificates are located. In the case of directory, does also rehashing the directory.
|
||||
/// @param certificate_type
|
||||
/// @return CA certificate location
|
||||
std::string get_verify_location(CaCertificateType certificate_type);
|
||||
|
||||
/// @brief An extension of 'get_verify_file' with error handling included
|
||||
GetCertificateInfoResult get_ca_certificate_info(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
|
||||
int get_leaf_expiry_days_count(LeafCertificateType certificate_type);
|
||||
|
||||
/// @brief Collects and deletes unfulfilled CSR private keys. It also deletes the expired
|
||||
/// certificates. The caller must be sure the system clock is properly set for detecting expired
|
||||
/// certificates. A minimum of 'DEFAULT_MINIMUM_CERTIFICATE_ENTRIES' certificates to
|
||||
/// have a safeguard against a poorly set system clock
|
||||
void garbage_collect();
|
||||
|
||||
/// @brief Verifies the file at the given \p path using the provided \p signing_certificate and \p signature
|
||||
/// @param path
|
||||
/// @param signing_certificate
|
||||
/// @param signature
|
||||
/// @return true if the verification was successful, false if not
|
||||
static bool verify_file_signature(const fs::path& path, const std::string& signing_certificate,
|
||||
const std::string signature);
|
||||
|
||||
/// @brief Decodes the base64 encoded string to the raw byte representation
|
||||
/// @param base64_string base64 encoded string
|
||||
/// @return decoded byte vector
|
||||
static std::vector<std::uint8_t> base64_decode_to_bytes(const std::string& base64_string);
|
||||
|
||||
/// @brief Decodes the base64 encoded string to string representation
|
||||
/// @param base64_string base64 encoded string
|
||||
/// @return decoded string array
|
||||
static std::string base64_decode_to_string(const std::string& base64_string);
|
||||
|
||||
/// @brief Encodes the raw bytes to a base64 string
|
||||
/// @param decoded_bytes raw byte array
|
||||
/// @return encoded base64 string
|
||||
static std::string base64_encode_from_bytes(const std::vector<std::uint8_t>& bytes);
|
||||
|
||||
/// @brief Encodes the string containing raw bytes to a base64 string
|
||||
/// @param decoded_bytes string containing raw bytes
|
||||
/// @return encoded base64 string
|
||||
static std::string base64_encode_from_string(const std::string& string);
|
||||
|
||||
private:
|
||||
// Internal versions of the functions do not lock the mutex
|
||||
CertificateValidationResult verify_certificate_internal(const std::string& certificate_chain,
|
||||
const std::vector<LeafCertificateType>& certificate_types);
|
||||
|
||||
GetCertificateInfoResult get_leaf_certificate_info_internal(LeafCertificateType certificate_type,
|
||||
EncodingFormat encoding, bool include_ocsp = false);
|
||||
|
||||
/// @brief Retrieves information related to leaf certificates
|
||||
GetCertificateFullInfoResult get_full_leaf_certificate_info_internal(const CertificateQueryParams& params);
|
||||
|
||||
GetCertificateInfoResult get_ca_certificate_info_internal(CaCertificateType certificate_type);
|
||||
std::optional<fs::path> retrieve_ocsp_cache_internal(const CertificateHashData& certificate_hash_data);
|
||||
|
||||
bool is_ca_certificate_installed_internal(CaCertificateType certificate_type);
|
||||
|
||||
GetCertificateSignRequestResult
|
||||
generate_certificate_signing_request_internal(LeafCertificateType certificate_type,
|
||||
const CertificateSigningRequestInfo& info);
|
||||
|
||||
/// @brief Determines if the total filesize of certificates is > than the max_filesystem_usage bytes
|
||||
bool is_filesystem_full();
|
||||
|
||||
static std::mutex security_mutex;
|
||||
|
||||
// why not reusing the FilePaths here directly (storage duplication)
|
||||
std::map<CaCertificateType, fs::path> ca_bundle_path_map;
|
||||
DirectoryPaths directories;
|
||||
LinkPaths links;
|
||||
|
||||
// CSRs that were generated and require an expiry time
|
||||
std::map<fs::path, std::chrono::time_point<std::chrono::steady_clock>> managed_csr;
|
||||
|
||||
// Maximum filesystem usage
|
||||
std::uintmax_t max_fs_usage_bytes;
|
||||
// Maximum filesystem certificate entries
|
||||
std::uintmax_t max_fs_certificate_store_entries;
|
||||
// Default csr expiry in seconds
|
||||
std::chrono::seconds csr_expiry;
|
||||
// Default time to garbage collect
|
||||
std::chrono::seconds garbage_collect_time;
|
||||
|
||||
// GC timer
|
||||
Everest::SteadyTimer garbage_collect_timer;
|
||||
|
||||
// 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
|
||||
|
||||
// Define here all tests that require internal function usage
|
||||
#ifdef BUILD_TESTING_EVSE_SECURITY
|
||||
FRIEND_TEST(EvseSecurityTests, verify_directory_bundles);
|
||||
FRIEND_TEST(EvseSecurityTests, verify_full_filesystem_install_reject);
|
||||
FRIEND_TEST(EvseSecurityTests, verify_full_filesystem);
|
||||
FRIEND_TEST(EvseSecurityTests, verify_expired_csr_deletion);
|
||||
FRIEND_TEST(EvseSecurityTests, verify_ocsp_garbage_collect);
|
||||
FRIEND_TEST(EvseSecurityTestsExpired, verify_expired_leaf_deletion);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace evse_security
|
||||
@@ -0,0 +1,225 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <evse_security/utils/evse_filesystem_types.hpp>
|
||||
|
||||
namespace evse_security {
|
||||
|
||||
const fs::path PEM_EXTENSION = ".pem";
|
||||
const fs::path DER_EXTENSION = ".der";
|
||||
const fs::path KEY_EXTENSION = ".key";
|
||||
const fs::path CUSTOM_KEY_EXTENSION = ".tkey";
|
||||
const fs::path CERT_HASH_EXTENSION = ".hash";
|
||||
|
||||
enum class EncodingFormat {
|
||||
DER,
|
||||
PEM,
|
||||
};
|
||||
|
||||
enum class CaCertificateType {
|
||||
V2G,
|
||||
MO,
|
||||
CSMS,
|
||||
MF,
|
||||
};
|
||||
|
||||
enum class LeafCertificateType {
|
||||
CSMS,
|
||||
V2G,
|
||||
MF,
|
||||
MO,
|
||||
};
|
||||
|
||||
enum class CertificateType {
|
||||
V2GRootCertificate,
|
||||
MORootCertificate,
|
||||
CSMSRootCertificate,
|
||||
V2GCertificateChain,
|
||||
MFRootCertificate,
|
||||
};
|
||||
|
||||
enum class HashAlgorithm {
|
||||
SHA256,
|
||||
SHA384,
|
||||
SHA512,
|
||||
};
|
||||
|
||||
enum class CertificateValidationResult {
|
||||
Valid,
|
||||
Expired,
|
||||
InvalidSignature,
|
||||
IssuerNotFound,
|
||||
InvalidLeafSignature,
|
||||
InvalidChain,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
enum class InstallCertificateResult {
|
||||
InvalidSignature,
|
||||
InvalidCertificateChain,
|
||||
InvalidFormat,
|
||||
InvalidCommonName,
|
||||
NoRootCertificateInstalled,
|
||||
Expired,
|
||||
CertificateStoreMaxLengthExceeded,
|
||||
WriteError,
|
||||
Accepted,
|
||||
};
|
||||
|
||||
enum class DeleteCertificateResult {
|
||||
Accepted,
|
||||
Failed,
|
||||
NotFound,
|
||||
};
|
||||
|
||||
enum class GetInstalledCertificatesStatus {
|
||||
Accepted,
|
||||
NotFound,
|
||||
};
|
||||
|
||||
enum class GetCertificateInfoStatus {
|
||||
Accepted,
|
||||
Rejected,
|
||||
NotFound,
|
||||
NotFoundValid,
|
||||
PrivateKeyNotFound,
|
||||
};
|
||||
|
||||
enum class GetCertificateSignRequestStatus {
|
||||
Accepted,
|
||||
InvalidRequestedType, ///< Requested a CSR for non CSMS/V2G leafs
|
||||
KeyGenError, ///< The key could not be generated with the requested/default parameters
|
||||
GenerationError, ///< Any other error when creating the CSR
|
||||
};
|
||||
|
||||
inline bool str_cast_insensitive_cmp(const std::string& a, const std::string& b) {
|
||||
if (a.size() != b.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::equal(a.begin(), a.end(), b.begin(), b.end(), [](std::string::value_type a, std::string::value_type b) {
|
||||
return (std::tolower(a) == std::tolower(b));
|
||||
});
|
||||
}
|
||||
|
||||
// types of evse_security
|
||||
|
||||
struct CertificateHashData {
|
||||
HashAlgorithm hash_algorithm; ///< Algorithm used for the hashes provided
|
||||
std::string issuer_name_hash; ///< The hash of the issuer's distinguished name (DN), calculated over the DER
|
||||
///< encoding of the issuer's name field.
|
||||
std::string issuer_key_hash; ///< The hash of the DER encoded public key: the value (excluding tag and length) of
|
||||
///< 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.
|
||||
|
||||
std::string debug_common_name; ///< Common name for easy debug
|
||||
|
||||
bool operator==(const CertificateHashData& Other) const {
|
||||
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;
|
||||
}
|
||||
|
||||
bool case_insensitive_comparison(const CertificateHashData& Other) const {
|
||||
if (hash_algorithm != Other.hash_algorithm) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false == str_cast_insensitive_cmp(issuer_name_hash, Other.issuer_name_hash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false == str_cast_insensitive_cmp(issuer_key_hash, Other.issuer_key_hash)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (false == str_cast_insensitive_cmp(serial_number, Other.serial_number)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_valid() const {
|
||||
return (false == issuer_name_hash.empty()) && (false == issuer_key_hash.empty()) &&
|
||||
(false == serial_number.empty());
|
||||
}
|
||||
};
|
||||
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::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
|
||||
std::vector<CertificateHashDataChain>
|
||||
certificate_hash_data_chain; ///< the hashed certificate data for each requested certificates
|
||||
};
|
||||
struct DeleteResult {
|
||||
DeleteCertificateResult result; ///< Indicates the status of the request
|
||||
std::optional<CaCertificateType> ca_certificate_type; ///< Valid if we deleted a root
|
||||
std::optional<LeafCertificateType> leaf_certificate_type; ///< Valid if we deleted a leaf
|
||||
};
|
||||
struct OCSPRequestData {
|
||||
std::optional<CertificateHashData> certificate_hash_data; ///< Contains the hash data of the certificate
|
||||
std::optional<std::string> responder_url; ///< Contains the responder URL
|
||||
};
|
||||
struct OCSPRequestDataList {
|
||||
std::vector<OCSPRequestData> ocsp_request_data_list; ///< A list of OCSP request data
|
||||
};
|
||||
|
||||
struct CertificateOCSP {
|
||||
CertificateHashData hash; ///< Hash of the certificate for which the OCSP data is held
|
||||
std::optional<fs::path> ocsp_path; ///< Path to the file in which the certificate OCSP data is held
|
||||
};
|
||||
|
||||
struct CertificateInfo {
|
||||
fs::path key; ///< The path of the PEM or DER encoded private key
|
||||
std::optional<std::string> certificate_root; ///< The PEM of root certificate used by the leaf, has a value only
|
||||
/// when using 'get_all_valid_certificates_info'
|
||||
std::optional<fs::path> certificate; ///< The path of the PEM or DER encoded certificate chain if found
|
||||
std::optional<fs::path> certificate_single; ///< The path of the PEM or DER encoded single certificate if found
|
||||
int certificate_count; ///< The count of certificates, if the chain is available, or 1 if single
|
||||
/// (the root is not taken into account because of the OCSP cache)
|
||||
std::optional<std::string> password; ///< Specifies the password for the private key if encrypted
|
||||
std::vector<CertificateOCSP> ocsp; ///< The ordered list of OCSP certificate data based on the chain file order
|
||||
};
|
||||
|
||||
struct GetCertificateInfoResult {
|
||||
GetCertificateInfoStatus status;
|
||||
std::optional<CertificateInfo> info;
|
||||
};
|
||||
|
||||
struct GetCertificateFullInfoResult {
|
||||
GetCertificateInfoStatus status;
|
||||
std::vector<CertificateInfo> info;
|
||||
};
|
||||
|
||||
struct GetCertificateSignRequestResult {
|
||||
GetCertificateSignRequestStatus status;
|
||||
std::optional<std::string> csr;
|
||||
};
|
||||
|
||||
namespace conversions {
|
||||
std::string encoding_format_to_string(EncodingFormat e);
|
||||
std::string ca_certificate_type_to_string(CaCertificateType e);
|
||||
std::string leaf_certificate_type_to_string(LeafCertificateType e);
|
||||
std::string leaf_certificate_type_to_filename(LeafCertificateType e);
|
||||
std::string certificate_type_to_string(CertificateType e);
|
||||
std::string hash_algorithm_to_string(HashAlgorithm e);
|
||||
HashAlgorithm string_to_hash_algorithm(const std::string& s);
|
||||
std::string install_certificate_result_to_string(InstallCertificateResult e);
|
||||
std::string delete_certificate_result_to_string(DeleteCertificateResult e);
|
||||
std::string get_installed_certificates_status_to_string(GetInstalledCertificatesStatus e);
|
||||
std::string get_certificate_info_status_to_string(GetCertificateInfoStatus e);
|
||||
} // namespace conversions
|
||||
|
||||
} // namespace evse_security
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <evse_security/utils/evse_filesystem_types.hpp>
|
||||
|
||||
namespace evse_security {
|
||||
struct CertificateHashData;
|
||||
}
|
||||
|
||||
namespace evse_security::filesystem_utils {
|
||||
|
||||
bool is_subdirectory(const fs::path& base, const fs::path& subdir);
|
||||
|
||||
/// @brief Should be used to ensure file exists, not for directories
|
||||
bool create_file_if_nonexistent(const fs::path& file_path);
|
||||
/// @brief Ensure a file exists (if there's an extension), or a directory if no extension is found
|
||||
bool create_file_or_dir_if_nonexistent(const fs::path& file_path);
|
||||
bool delete_file(const fs::path& file_path);
|
||||
|
||||
bool read_from_file(const fs::path& file_path, std::string& out_data);
|
||||
bool write_to_file(const fs::path& file_path, const std::string& data, std::ios::openmode mode);
|
||||
|
||||
/// @brief Process the file in chunks with the provided function. If the process function
|
||||
/// returns false, this function will also immediately return
|
||||
/// @return True if the file was properly opened false otherwise
|
||||
bool process_file(const fs::path& file_path, size_t buffer_size,
|
||||
std::function<bool(const std::uint8_t*, std::size_t, bool last_chunk)>&& func);
|
||||
|
||||
std::string get_random_file_name(const std::string& extension);
|
||||
|
||||
/// @brief Attempts to read a certificate hash from a file. The extension is taken into account
|
||||
/// @return True if we could read, false otherwise
|
||||
bool read_hash_from_file(const fs::path& file_path, CertificateHashData& out_hash);
|
||||
|
||||
/// @brief Attempts to write a certificate hash to a file
|
||||
/// @return True if we could write, false otherwise
|
||||
bool write_hash_to_file(const fs::path& file_path, const CertificateHashData& hash);
|
||||
|
||||
} // namespace evse_security::filesystem_utils
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#pragma once
|
||||
|
||||
#ifndef LIBEVSE_SECURITY_USE_BOOST_FILESYSTEM
|
||||
#include <filesystem>
|
||||
#else
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/filesystem/fstream.hpp>
|
||||
#endif
|
||||
|
||||
#ifndef LIBEVSE_SECURITY_USE_BOOST_FILESYSTEM
|
||||
namespace fs = std::filesystem;
|
||||
namespace fsstd = std;
|
||||
#else
|
||||
namespace fs = boost::filesystem;
|
||||
namespace fsstd = boost::filesystem;
|
||||
#endif
|
||||
Reference in New Issue
Block a user