Skip to content

Commit

Permalink
Added cleanup for OCSP
Browse files Browse the repository at this point in the history
Signed-off-by: AssemblyJohn <[email protected]>
  • Loading branch information
AssemblyJohn committed Apr 25, 2024
1 parent 30bbac7 commit 88c6a99
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 19 deletions.
4 changes: 4 additions & 0 deletions include/evse_security/utils/evse_filesystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
76 changes: 57 additions & 19 deletions lib/evse_security/evse_security.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();

Expand All @@ -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!";
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1509,12 +1504,12 @@ void EvseSecurity::garbage_collect() {

EVLOG_info << "Starting garbage collect!";

std::vector<std::pair<fs::path, fs::path>> leaf_paths;
std::vector<std::tuple<fs::path, fs::path, CaCertificateType>> 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<fs::path> invalid_certificate_files;
Expand All @@ -1523,7 +1518,10 @@ void EvseSecurity::garbage_collect() {
std::set<fs::path> 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
Expand All @@ -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<X509Wrapper>& chain) {
[this, &invalid_certificate_files, &skipped, &key_directory, &protected_private_keys,
&root_bundle](const fs::path& file, const std::vector<X509Wrapper>& chain) {
// By default delete all empty
if (chain.size() <= 0) {
invalid_certificate_files.emplace(file);
Expand All @@ -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 {
Expand Down Expand Up @@ -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;

Expand Down
24 changes: 24 additions & 0 deletions lib/evse_security/utils/evse_filesystem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 2 additions & 0 deletions tests/tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 88c6a99

Please sign in to comment.