Skip to content
This repository was archived by the owner on Dec 15, 2025. It is now read-only.

Commit bccebf7

Browse files
authored
Bugfix/get all installed leafs (#104)
* Added return of multiple leaf chains in get_installed_certificates * Fixed test cases and removed duplicates from install * Added test-case for multi-leaf certificate retrieval in V2GCertificateChain --------- Signed-off-by: AssemblyJohn <ioan.bogdann@gmail.com>
1 parent dbff89d commit bccebf7

File tree

7 files changed

+256
-90
lines changed

7 files changed

+256
-90
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
cmake_minimum_required(VERSION 3.14)
22

3-
project(everest-evse_security VERSION 0.9.3
3+
project(everest-evse_security VERSION 0.9.4
44
DESCRIPTION "Implementation of EVSE related security operations"
55
LANGUAGES CXX C
66
)

include/evse_security/evse_security.hpp

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,22 @@ struct FilePaths {
4040
LinkPaths links;
4141
};
4242

43+
struct CertificateQueryParams {
44+
LeafCertificateType certificate_type;
45+
EncodingFormat encoding{EncodingFormat::PEM};
46+
/// if OCSP information should be included
47+
bool include_ocsp{false};
48+
/// if the root certificate of the leaf should be included in the returned list
49+
bool include_root{false};
50+
/// if true, all valid leafs will be included, sorted in order, with the newest being
51+
/// first. If false, only the newest one will be returned
52+
bool include_all_valid{false};
53+
/// if true, will remove all duplicates found, since we can find a leaf for example
54+
/// in 2 files, one in 'leaf_single' and one in 'leaf_chain'. For delete routines
55+
/// we need both files returned, while for queries (v2g_chain) we don't need duplicates
56+
bool remove_duplicates{false};
57+
};
58+
4359
// Unchangeable security limit for certificate deletion, a min entry count will be always kept (newest)
4460
static constexpr std::size_t DEFAULT_MINIMUM_CERTIFICATE_ENTRIES = 10;
4561
// 50 MB default limit for filesystem usage
@@ -115,6 +131,8 @@ class EvseSecurity {
115131
LeafCertificateType certificate_type);
116132

117133
/// @brief Retrieves all certificates installed on the filesystem applying the \p certificate_type filter.
134+
/// In the case of the 'V2GCertificateChain', it will return all valid leafs chains, with the newest being
135+
/// the first in the list
118136
/// @param certificate_types
119137
/// @return contains the certificate hash data chains of the requested \p certificate_type
120138
GetInstalledCertificatesResult get_installed_certificate(CertificateType certificate_type);
@@ -285,15 +303,7 @@ class EvseSecurity {
285303
EncodingFormat encoding, bool include_ocsp = false);
286304

287305
/// @brief Retrieves information related to leaf certificates
288-
/// @param include_ocsp if OCSP information should be included
289-
/// @param include_root if the root certificate of the leaf should be included in the returned list
290-
/// @param include_all_valid if true, all valid leafs will be included, sorted in order, with the newest being
291-
/// first. If false, only the newest one will be returned
292-
GetCertificateFullInfoResult get_full_leaf_certificate_info_internal(LeafCertificateType certificate_type,
293-
EncodingFormat encoding,
294-
bool include_ocsp = false,
295-
bool include_root = false,
296-
bool include_all_valid = false);
306+
GetCertificateFullInfoResult get_full_leaf_certificate_info_internal(const CertificateQueryParams& params);
297307

298308
GetCertificateInfoResult get_ca_certificate_info_internal(CaCertificateType certificate_type);
299309
std::optional<fs::path> retrieve_ocsp_cache_internal(const CertificateHashData& certificate_hash_data);

lib/evse_security/evse_security.cpp

Lines changed: 103 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -597,75 +597,85 @@ EvseSecurity::get_installed_certificates(const std::vector<CertificateType>& cer
597597
// retrieve v2g certificate chain
598598
if (std::find(certificate_types.begin(), certificate_types.end(), CertificateType::V2GCertificateChain) !=
599599
certificate_types.end()) {
600-
601-
// Internal since we already acquired the lock
602-
const auto secc_key_pair =
603-
this->get_leaf_certificate_info_internal(LeafCertificateType::V2G, EncodingFormat::PEM);
604-
if (secc_key_pair.status == GetCertificateInfoStatus::Accepted) {
605-
fs::path certificate_path;
606-
607-
if (secc_key_pair.info.value().certificate.has_value())
608-
certificate_path = secc_key_pair.info.value().certificate.value();
609-
else
610-
certificate_path = secc_key_pair.info.value().certificate_single.value();
611-
612-
try {
613-
// Leaf V2G chain, containing (SECCLeaf->SubCA2->SubCA1) or (SECCLeaf)
614-
X509CertificateBundle leaf_bundle(certificate_path, EncodingFormat::PEM);
615-
616-
// V2G chain, containing the certs from the V2G bundle/folder,
617-
// containing (SubCA2->SubCA1->V2GRoot) or (V2GRoot)
618-
const auto ca_bundle_path = this->ca_bundle_path_map.at(CaCertificateType::V2G);
619-
X509CertificateBundle ca_bundle(ca_bundle_path, EncodingFormat::PEM);
620-
621-
// Merge the bundles, adding only uniques for full chain
622-
// (SubCA2->SubCA1->V2GRoot->SECCLeaf) in any order
623-
for (auto& certif : leaf_bundle.split()) {
624-
ca_bundle.add_certificate_unique(std::move(certif));
600+
// Retrieve all valid leaf certificates, we will return
601+
// multiple chains for each valid leaf that we find
602+
CertificateQueryParams params;
603+
params.certificate_type = LeafCertificateType::V2G;
604+
params.include_all_valid = true;
605+
params.remove_duplicates = true;
606+
607+
GetCertificateFullInfoResult secc_key_pairs = get_full_leaf_certificate_info_internal(params);
608+
609+
if (secc_key_pairs.status == GetCertificateInfoStatus::Accepted) {
610+
for (const auto& secc_key_pair : secc_key_pairs.info) {
611+
fs::path certificate_path;
612+
613+
if (secc_key_pair.certificate.has_value()) {
614+
certificate_path = secc_key_pair.certificate.value();
615+
} else if (secc_key_pair.certificate_single.has_value()) {
616+
certificate_path = secc_key_pair.certificate_single.value();
617+
} else {
618+
throw std::runtime_error("Leaf certificate single/bundle not present, should never happen!");
625619
}
626620

627-
// Create the proper certificate hierarchy since the bundle is not ordered
628-
X509CertificateHierarchy& hierarchy = ca_bundle.get_certificate_hierarchy();
629-
EVLOG_debug << "Hierarchy:(V2GCertificateChain)\n" << hierarchy.to_debug_string();
630-
631-
for (auto& root : hierarchy.get_hierarchy()) {
632-
CertificateHashDataChain certificate_hash_data_chain;
633-
certificate_hash_data_chain.certificate_type = CertificateType::V2GCertificateChain;
634-
635-
// Since the hierarchy starts with V2G (Root) -> SubCa1->SubCa2 we have to reorder:
636-
// them with the leaf first when returning to:
637-
// * Leaf [index 0]
638-
// --- SubCa2 [index 1]
639-
// --- SubCa1 [index 2]
640-
// --- --- V2GRoot [index 3]
641-
std::vector<CertificateHashData> hierarchy_hash_data;
642-
643-
// For each root's descendant, excluding the root
644-
X509CertificateHierarchy::for_each_descendant(
645-
[&](const X509Node& child, int depth) { hierarchy_hash_data.push_back(child.hash); }, root);
646-
647-
// Now the hierarchy_hash_data contains SubCA1->SubCA2->SECCLeaf,
648-
// reverse order iteration to conform to the required leaf-first order
649-
if (hierarchy_hash_data.size()) {
650-
bool first_leaf = true;
651-
652-
// Reverse iteration
653-
for (auto it = hierarchy_hash_data.rbegin(); it != hierarchy_hash_data.rend(); ++it) {
654-
if (first_leaf) {
655-
// Leaf is the last
656-
certificate_hash_data_chain.certificate_hash_data = *it;
657-
first_leaf = false;
658-
} else {
659-
certificate_hash_data_chain.child_certificate_hash_data.push_back(*it);
621+
try {
622+
// Leaf V2G chain, containing (SECCLeaf->SubCA2->SubCA1) or (SECCLeaf)
623+
X509CertificateBundle leaf_bundle(certificate_path, EncodingFormat::PEM);
624+
625+
// V2G chain, containing the certs from the V2G bundle/folder,
626+
// containing (SubCA2->SubCA1->V2GRoot) or (V2GRoot)
627+
const auto ca_bundle_path = this->ca_bundle_path_map.at(CaCertificateType::V2G);
628+
X509CertificateBundle ca_bundle(ca_bundle_path, EncodingFormat::PEM);
629+
630+
// Merge the bundles, adding only uniques for full chain
631+
// (SubCA2->SubCA1->V2GRoot->SECCLeaf) in any order
632+
for (auto& certif : leaf_bundle.split()) {
633+
ca_bundle.add_certificate_unique(std::move(certif));
634+
}
635+
636+
// Create the proper certificate hierarchy since the bundle is not ordered
637+
X509CertificateHierarchy& hierarchy = ca_bundle.get_certificate_hierarchy();
638+
EVLOG_debug << "Hierarchy:(V2GCertificateChain)\n" << hierarchy.to_debug_string();
639+
640+
for (auto& root : hierarchy.get_hierarchy()) {
641+
CertificateHashDataChain certificate_hash_data_chain;
642+
certificate_hash_data_chain.certificate_type = CertificateType::V2GCertificateChain;
643+
644+
// Since the hierarchy starts with V2G (Root) -> SubCa1->SubCa2 we have to reorder:
645+
// them with the leaf first when returning to:
646+
// * Leaf [index 0]
647+
// --- SubCa2 [index 1]
648+
// --- SubCa1 [index 2]
649+
// --- --- V2GRoot [index 3]
650+
std::vector<CertificateHashData> hierarchy_hash_data;
651+
652+
// For each root's descendant, excluding the root
653+
X509CertificateHierarchy::for_each_descendant(
654+
[&](const X509Node& child, int depth) { hierarchy_hash_data.push_back(child.hash); }, root);
655+
656+
// Now the hierarchy_hash_data contains SubCA1->SubCA2->SECCLeaf,
657+
// reverse order iteration to conform to the required leaf-first order
658+
if (hierarchy_hash_data.size()) {
659+
bool first_leaf = true;
660+
661+
// Reverse iteration
662+
for (auto it = hierarchy_hash_data.rbegin(); it != hierarchy_hash_data.rend(); ++it) {
663+
if (first_leaf) {
664+
// Leaf is the last
665+
certificate_hash_data_chain.certificate_hash_data = *it;
666+
first_leaf = false;
667+
} else {
668+
certificate_hash_data_chain.child_certificate_hash_data.push_back(*it);
669+
}
660670
}
661-
}
662671

663-
// Add to our chains
664-
certificate_chains.push_back(certificate_hash_data_chain);
672+
// Add to our chains
673+
certificate_chains.push_back(certificate_hash_data_chain);
674+
}
665675
}
676+
} catch (const CertificateLoadException& e) {
677+
EVLOG_error << "Could not load installed leaf certificates: " << e.what();
666678
}
667-
} catch (const CertificateLoadException& e) {
668-
EVLOG_error << "Could not load installed leaf certificates: " << e.what();
669679
}
670680
}
671681
}
@@ -1103,7 +1113,7 @@ GetCertificateFullInfoResult EvseSecurity::get_all_valid_certificates_info(LeafC
11031113
std::lock_guard<std::mutex> guard(EvseSecurity::security_mutex);
11041114

11051115
GetCertificateFullInfoResult result =
1106-
get_full_leaf_certificate_info_internal(certificate_type, encoding, include_ocsp, true, true);
1116+
get_full_leaf_certificate_info_internal({certificate_type, encoding, include_ocsp, true, true});
11071117

11081118
// If we failed, simply return the result
11091119
if (result.status != GetCertificateInfoStatus::Accepted) {
@@ -1149,7 +1159,7 @@ GetCertificateInfoResult EvseSecurity::get_leaf_certificate_info(LeafCertificate
11491159
GetCertificateInfoResult EvseSecurity::get_leaf_certificate_info_internal(LeafCertificateType certificate_type,
11501160
EncodingFormat encoding, bool include_ocsp) {
11511161
GetCertificateFullInfoResult result =
1152-
get_full_leaf_certificate_info_internal(certificate_type, encoding, include_ocsp, false, false);
1162+
get_full_leaf_certificate_info_internal({certificate_type, encoding, include_ocsp, false, false});
11531163
GetCertificateInfoResult internal_result;
11541164

11551165
internal_result.status = result.status;
@@ -1160,10 +1170,10 @@ GetCertificateInfoResult EvseSecurity::get_leaf_certificate_info_internal(LeafCe
11601170
return internal_result;
11611171
}
11621172

1163-
GetCertificateFullInfoResult EvseSecurity::get_full_leaf_certificate_info_internal(LeafCertificateType certificate_type,
1164-
EncodingFormat encoding,
1165-
bool include_ocsp, bool include_root,
1166-
bool include_all_valid) {
1173+
GetCertificateFullInfoResult
1174+
EvseSecurity::get_full_leaf_certificate_info_internal(const CertificateQueryParams& params) {
1175+
auto certificate_type = params.certificate_type;
1176+
11671177
EVLOG_info << "Requesting leaf certificate info: "
11681178
<< conversions::leaf_certificate_type_to_string(certificate_type);
11691179

@@ -1224,15 +1234,31 @@ GetCertificateFullInfoResult EvseSecurity::get_full_leaf_certificate_info_intern
12241234
// Found at least one valid key
12251235
any_valid_key = true;
12261236

1227-
// Copy to latest valid
12281237
KeyPairInternal key_pair{chain.at(0), priv_key_path};
1229-
valid_leafs.emplace_back(std::move(key_pair));
1238+
1239+
if (params.remove_duplicates) {
1240+
// Filter the already added certificates, since we can have a case
1241+
// when a leaf is present in 2 files (single/chain) that causes it
1242+
// to be added to the list twice by the bundle parser
1243+
auto it = std::find_if(valid_leafs.begin(), valid_leafs.end(),
1244+
[&key_pair](const auto& in_key_pair) {
1245+
return in_key_pair.certificate == key_pair.certificate;
1246+
});
1247+
1248+
// None found
1249+
if (it == valid_leafs.end()) {
1250+
valid_leafs.emplace_back(std::move(key_pair));
1251+
}
1252+
} else {
1253+
// Copy to latest valid
1254+
valid_leafs.emplace_back(std::move(key_pair));
1255+
}
12301256

12311257
// We found, break
12321258
EVLOG_info << "Found valid leaf: [" << chain.at(0).get_file().value() << "]";
12331259

12341260
// Collect all if we don't include valid only
1235-
if (include_all_valid == false) {
1261+
if (params.include_all_valid == false) {
12361262
EVLOG_info << "Not requiring all valid leafs, returning";
12371263
return false;
12381264
}
@@ -1329,15 +1355,15 @@ GetCertificateFullInfoResult EvseSecurity::get_full_leaf_certificate_info_intern
13291355
}
13301356

13311357
// Both require the hierarchy build
1332-
if (include_ocsp || include_root) {
1358+
if (params.include_ocsp || params.include_root) {
13331359
X509CertificateBundle root_bundle(root_dir, EncodingFormat::PEM); // Required for hierarchy
13341360

13351361
// The hierarchy is required for both roots and the OCSP cache
13361362
auto hierarchy = X509CertificateHierarchy::build_hierarchy(root_bundle.split(), leaf_directory.split());
13371363
EVLOG_debug << "Hierarchy for root/OCSP data: \n" << hierarchy.to_debug_string();
13381364

13391365
// Include OCSP data if possible
1340-
if (include_ocsp) {
1366+
if (params.include_ocsp) {
13411367
// Search for OCSP data for each certificate
13421368
if (leaf_fullchain != nullptr) {
13431369
for (const auto& chain_certif : *leaf_fullchain) {
@@ -1361,7 +1387,7 @@ GetCertificateFullInfoResult EvseSecurity::get_full_leaf_certificate_info_intern
13611387
}
13621388

13631389
// Include root data if possible
1364-
if (include_root) {
1390+
if (params.include_root) {
13651391
// Search for the root of any of the leafs
13661392
// present either in the chain or single
13671393
try {
@@ -1386,11 +1412,11 @@ GetCertificateFullInfoResult EvseSecurity::get_full_leaf_certificate_info_intern
13861412
info.certificate_count = chain_len;
13871413
info.password = this->private_key_password;
13881414

1389-
if (include_ocsp) {
1415+
if (params.include_ocsp) {
13901416
info.ocsp = certificate_ocsp;
13911417
}
13921418

1393-
if (include_root && leafs_root.has_value()) {
1419+
if (params.include_root && leafs_root.has_value()) {
13941420
info.certificate_root = leafs_root.value();
13951421
}
13961422

tests/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ setup_target_for_coverage_gcovr_xml(
4949

5050
file(COPY
5151
"${CMAKE_CURRENT_SOURCE_DIR}/generate_test_certs.sh"
52-
"${CMAKE_CURRENT_SOURCE_DIR}/generate_test_certs_multi.sh"
52+
"${CMAKE_CURRENT_SOURCE_DIR}/generate_test_certs_root_multi.sh"
53+
"${CMAKE_CURRENT_SOURCE_DIR}/generate_test_certs_leaf_multi.sh"
5354
DESTINATION "${CMAKE_BINARY_DIR}/tests"
5455
)
5556

0 commit comments

Comments
 (0)