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:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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