Skip to content

Commit

Permalink
Added cleanup/certificate limit features: (#39)
Browse files Browse the repository at this point in the history
* Added cleanup/certificate limit features:
- maximum used filesize limit
- maximum used certificate entries
- deletion of expired csr private keys
- deletion of expired leaf certificates
- relevant test cases
* Added security limits for certificate deletion to prevent bricking the charger
* Update relevant testcases

Signed-off-by: AssemblyJohn <[email protected]>
  • Loading branch information
AssemblyJohn authored Feb 13, 2024
1 parent 69410a8 commit f6fe092
Show file tree
Hide file tree
Showing 7 changed files with 470 additions and 16 deletions.
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;
};

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

0 comments on commit f6fe092

Please sign in to comment.