From 6594386d3882ee9da2aa596bc88fa5148dfce0a4 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 --- lib/evse_security/evse_security.cpp | 68 ++++++++++++++++++++++++----- tests/tests.cpp | 2 + 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/lib/evse_security/evse_security.cpp b/lib/evse_security/evse_security.cpp index 7f802a2..8841903 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(); @@ -882,7 +883,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 +1510,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 +1524,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 +1537,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 +1557,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 +1641,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/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);