Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added cleanup/certificate limit features: #39

Merged
merged 3 commits into from
Feb 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions include/evse_security/certificate/x509_bundle.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once

#include <algorithm>
#include <map>

#include <evse_security/certificate/x509_hierarchy.hpp>
Expand Down Expand Up @@ -66,6 +67,9 @@ class X509CertificateBundle {
/// @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;
}
Expand All @@ -83,6 +87,28 @@ class X509CertificateBundle {
}
}

/// @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;
corneliusclaussen marked this conversation as resolved.
Show resolved Hide resolved
};

std::vector<Chain> ordered;

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

public:
/// @brief Splits the certificate (chain) into single certificates
/// @return vector containing single certificates
Expand Down
1 change: 1 addition & 0 deletions include/evse_security/certificate/x509_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ enum class X509CertificateSource {
const fs::path PEM_EXTENSION = ".pem";
const fs::path DER_EXTENSION = ".der";
const fs::path KEY_EXTENSION = ".key";
const fs::path TPM_KEY_EXTENSION = ".tkey";

/// @brief Convenience wrapper around openssl X509 certificate
class X509Wrapper {
Expand Down
54 changes: 52 additions & 2 deletions include/evse_security/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@
#include <evse_security/utils/evse_filesystem_types.hpp>

#include <map>
#include <mutex>

#ifdef BUILD_TESTING_EVSE_SECURITY
#include <gtest/gtest_prod.h>
#endif

namespace evse_security {

struct LinkPaths {
Expand All @@ -31,6 +37,16 @@ struct FilePaths {
LinkPaths links;
};

// 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, 10 minutes or reboot
static std::chrono::seconds DEFAULT_CSR_EXPIRY(10 * 60);

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

Expand All @@ -40,7 +56,10 @@ class EvseSecurity {
/// directories are specified.
/// @param file_paths specifies the certificate and key storage locations on the filesystem
/// @param private_key_password optional password for encrypted private keys
EvseSecurity(const FilePaths& file_paths, const std::optional<std::string>& private_key_password = std::nullopt);
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);

/// @brief Destructor
~EvseSecurity();
Expand Down Expand Up @@ -146,6 +165,11 @@ class EvseSecurity {
/// @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. If also deleting the expired
/// certificates, make sure the system clock is properly set for detecting expired certificates
/// @param delete_expired if the expired certificates should be deleted
void garbage_collect(bool delete_expired_certificates);

/// @brief Verifies the file at the given \p path using the provided \p signing_certificate and \p signature
/// @param path
/// @param signing_certificate
Expand All @@ -155,14 +179,40 @@ class EvseSecurity {
const std::string signature);

private:
/// @brief Determines if the total filesize of certificates is > than the max_filesystem_usage bytes
bool is_filesystem_full();

private:
// TODO(ioan): implement library thread-safety
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;

// 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;
std::optional<std::string> private_key_password; // used to decrypt encrypted private keys

private:
// Define here all tests that require internal function usage
#ifdef BUILD_TESTING_EVSE_SECURITY
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_expired_leaf_deletion);
#endif
};

} // namespace evse_security
4 changes: 4 additions & 0 deletions lib/evse_security/certificate/x509_bundle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ int X509CertificateBundle::get_certificate_count() const {
return count;
}

int X509CertificateBundle::get_certificate_chains_count() const {
return certificates.size();
}

void X509CertificateBundle::add_certificates(const std::string& data, const EncodingFormat encoding,
const std::optional<fs::path>& path) {
auto loaded = CryptoSupplier::load_certificates(data, encoding);
Expand Down
Loading
Loading