From 88c6a9944143cca56c22740013c0d70236c5ba17 Mon Sep 17 00:00:00 2001 From: AssemblyJohn Date: Thu, 25 Apr 2024 14:46:42 +0300 Subject: [PATCH] Added cleanup for OCSP Signed-off-by: AssemblyJohn --- .../evse_security/utils/evse_filesystem.hpp | 4 + lib/evse_security/evse_security.cpp | 76 ++++++++++++++----- lib/evse_security/utils/evse_filesystem.cpp | 24 ++++++ tests/tests.cpp | 2 + 4 files changed, 87 insertions(+), 19 deletions(-) diff --git a/include/evse_security/utils/evse_filesystem.hpp b/include/evse_security/utils/evse_filesystem.hpp index 4c7fddb..67b9afb 100644 --- a/include/evse_security/utils/evse_filesystem.hpp +++ b/include/evse_security/utils/evse_filesystem.hpp @@ -31,4 +31,8 @@ std::string get_random_file_name(const std::string& extension); /// @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 diff --git a/lib/evse_security/evse_security.cpp b/lib/evse_security/evse_security.cpp index 7f802a2..b379df6 100644 --- a/lib/evse_security/evse_security.cpp +++ b/lib/evse_security/evse_security.cpp @@ -798,7 +798,7 @@ void EvseSecurity::update_ocsp_cache(const CertificateHashData& certificate_hash fs::create_directories(ocsp_path); } else { // Iterate existing hashes - for (const auto& hash_entry : fs::recursive_directory_iterator(ocsp_path)) { + for (const auto& hash_entry : fs::directory_iterator(ocsp_path)) { if (hash_entry.is_regular_file()) { CertificateHashData read_hash; @@ -810,7 +810,8 @@ void EvseSecurity::update_ocsp_cache(const CertificateHashData& certificate_hash fs::path ocsp_path = hash_entry.path(); ocsp_path.replace_extension(CERT_HASH_EXTENSION); - std::ofstream fs(ocsp_path.c_str()); + // Discard previous content + std::ofstream fs(ocsp_path.c_str(), std::ios::trunc); fs << ocsp_response; fs.close(); @@ -831,13 +832,7 @@ void EvseSecurity::update_ocsp_cache(const CertificateHashData& certificate_hash fs << ocsp_response; fs.close(); - // Write out the related hash - std::ofstream hs(hash_file_path.c_str()); - hs << certificate_hash_data.issuer_name_hash; - hs << certificate_hash_data.issuer_key_hash; - hs << certificate_hash_data.serial_number; - hs.close(); - + filesystem_utils::write_hash_to_file(hash_file_path, certificate_hash_data); EVLOG_debug << "OCSP certificate hash not found, written at path: " << ocsp_file_path; } else { EVLOG_error << "Could not find OCSP cache patch directory!"; @@ -882,7 +877,7 @@ EvseSecurity::retrieve_ocsp_cache_internal(const CertificateHashData& certificat if (filesystem_utils::read_hash_from_file(ocsp_entry.path(), read_hash) && (read_hash == certificate_hash_data)) { fs::path replaced_ext = ocsp_entry.path(); - replaced_ext.replace_extension(".der"); + replaced_ext.replace_extension(DER_EXTENSION); std::ifstream in_fs(replaced_ext.c_str()); std::string ocsp_response; @@ -1509,12 +1504,12 @@ void EvseSecurity::garbage_collect() { EVLOG_info << "Starting garbage collect!"; - std::vector> leaf_paths; + std::vector> leaf_paths; - leaf_paths.push_back( - std::make_pair(this->directories.csms_leaf_cert_directory, this->directories.csms_leaf_key_directory)); - leaf_paths.push_back( - std::make_pair(this->directories.secc_leaf_cert_directory, this->directories.secc_leaf_key_directory)); + leaf_paths.push_back(std::make_tuple(this->directories.csms_leaf_cert_directory, + this->directories.csms_leaf_key_directory, CaCertificateType::CSMS)); + leaf_paths.push_back(std::make_tuple(this->directories.secc_leaf_cert_directory, + this->directories.secc_leaf_key_directory, CaCertificateType::V2G)); // Delete certificates first, give the option to cleanup the dangling keys afterwards std::set invalid_certificate_files; @@ -1523,7 +1518,10 @@ void EvseSecurity::garbage_collect() { std::set protected_private_keys; // Order by latest valid, and keep newest with a safety limit - for (auto const& [cert_dir, key_dir] : leaf_paths) { + for (auto const& [cert_dir, key_dir, ca_type] : leaf_paths) { + // Root bundle required for hash of OCSP cache + X509CertificateBundle root_bundle(ca_bundle_path_map[ca_type], EncodingFormat::PEM); + X509CertificateBundle expired_certs(cert_dir, EncodingFormat::PEM); // Only handle if we have more than the minimum certificates entry @@ -1533,8 +1531,8 @@ void EvseSecurity::garbage_collect() { // Order by expiry date, and keep even expired certificates with a minimum of 10 certificates expired_certs.for_each_chain_ordered( - [this, &invalid_certificate_files, &skipped, &key_directory, - &protected_private_keys](const fs::path& file, const std::vector& chain) { + [this, &invalid_certificate_files, &skipped, &key_directory, &protected_private_keys, + &root_bundle](const fs::path& file, const std::vector& chain) { // By default delete all empty if (chain.size() <= 0) { invalid_certificate_files.emplace(file); @@ -1553,6 +1551,46 @@ void EvseSecurity::garbage_collect() { invalid_certificate_files.emplace(key_file); } catch (NoPrivateKeyException& e) { } + + // Construct full hierarchy so that we can extract the hash + auto full_list = root_bundle.split(); + for (const X509Wrapper& certif : chain) { + full_list.push_back(certif); + } + + X509CertificateHierarchy hierarchy = + X509CertificateHierarchy::build_hierarchy(full_list); + + try { + CertificateHashData ocsp_hash = hierarchy.get_certificate_hash(chain[0]); + + // Find OCSP cache with hash + if (chain[0].get_file().has_value()) { + const auto ocsp_path = chain[0].get_file().value().parent_path() / "ocsp"; + + if (fs::exists(ocsp_path)) { + for (const auto& hash_entry : fs::directory_iterator(ocsp_path)) { + if (hash_entry.is_regular_file() == false) { + continue; + } + // Attempt hash read + CertificateHashData read_hash; + + if (filesystem_utils::read_hash_from_file(hash_entry.path(), + read_hash) && + read_hash == ocsp_hash) { + + auto oscp_data_path = hash_entry.path(); + oscp_data_path.replace_extension(CERT_HASH_EXTENSION); + + invalid_certificate_files.emplace(hash_entry.path()); + invalid_certificate_files.emplace(oscp_data_path); + } + } + } + } + } catch (const NoCertificateFound& e) { + } } } } else { @@ -1597,7 +1635,7 @@ void EvseSecurity::garbage_collect() { // at a further invocation after the GC timer will elapse a few times. This behavior // was added so that if we have a reset and the CSMS sends us a CSR response while we were // down it should still be processed when we boot up and NOT delete the CSRs - for (auto const& [cert_dir, keys_dir] : leaf_paths) { + for (auto const& [cert_dir, keys_dir, ca_type] : leaf_paths) { fs::path cert_path = cert_dir; fs::path key_path = keys_dir; diff --git a/lib/evse_security/utils/evse_filesystem.cpp b/lib/evse_security/utils/evse_filesystem.cpp index 80aef74..b3c163a 100644 --- a/lib/evse_security/utils/evse_filesystem.cpp +++ b/lib/evse_security/utils/evse_filesystem.cpp @@ -140,4 +140,28 @@ bool read_hash_from_file(const fs::path& file_path, CertificateHashData& out_has return false; } +bool write_hash_to_file(const fs::path& file_path, const CertificateHashData& hash) { + auto real_path = file_path; + + if (file_path.has_extension() == false || file_path.extension() != CERT_HASH_EXTENSION) { + real_path.replace_extension(CERT_HASH_EXTENSION); + } + + try { + // Write out the related hash + std::ofstream hs(real_path.c_str()); + hs << hash.issuer_name_hash; + hs << hash.issuer_key_hash; + hs << hash.serial_number; + hs.close(); + + return true; + } catch (const std::exception& e) { + EVLOG_error << "Unknown error occurred writing cert hash file: " << file_path; + return false; + } + + return false; +} + } // namespace evse_security::filesystem_utils diff --git a/tests/tests.cpp b/tests/tests.cpp index 1e11485..970fc8f 100644 --- a/tests/tests.cpp +++ b/tests/tests.cpp @@ -833,6 +833,8 @@ TEST_F(EvseSecurityTestsExpired, verify_expired_leaf_deletion) { // Garbage collect evse_security->garbage_collect(); + // TODO: (ioan) test OCSP cache deletion + // Ensure that we have 10 certificates, since we only keep 10, the newest { X509CertificateBundle full_certs(fs::path("certs/client/cso"), EncodingFormat::PEM);