Skip to content

Commit

Permalink
Fixed some potentially wrong uses of certificate hash, clarified cert…
Browse files Browse the repository at this point in the history
…ificate names

Signed-off-by: AssemblyJohn <[email protected]>
  • Loading branch information
AssemblyJohn committed Mar 13, 2024
1 parent 6df4b62 commit cea0c20
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 39 deletions.
1 change: 1 addition & 0 deletions include/evse_security/evse_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ namespace conversions {
std::string encoding_format_to_string(EncodingFormat e);
std::string ca_certificate_type_to_string(CaCertificateType e);
std::string leaf_certificate_type_to_string(LeafCertificateType e);
std::string leaf_certificate_type_to_filename(LeafCertificateType e);
std::string certificate_type_to_string(CertificateType e);
std::string hash_algorithm_to_string(HashAlgorithm e);
std::string install_certificate_result_to_string(InstallCertificateResult e);
Expand Down
1 change: 1 addition & 0 deletions lib/evse_security/certificate/x509_bundle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,7 @@ void X509CertificateBundle::invalidate_hierarchy() {

X509CertificateHierarchy& X509CertificateBundle::get_certficate_hierarchy() {
if (hierarchy_invalidated) {
EVLOG_info << "Export for string is invalid!";
hierarchy_invalidated = false;

auto certificates = split();
Expand Down
31 changes: 19 additions & 12 deletions lib/evse_security/certificate/x509_hierarchy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,26 +121,29 @@ std::string X509CertificateHierarchy::to_debug_string() {
return str.str();
}

void X509CertificateHierarchy::insert(X509Wrapper&& certificate) {
void X509CertificateHierarchy::insert(X509Wrapper&& inserted_certificate) {
// Invalid hash
static CertificateHashData invalid_hash{HashAlgorithm::SHA256, {}, {}, {}};

if (false == certificate.is_selfsigned()) {
if (false == inserted_certificate.is_selfsigned()) {
// If this certif has any link to any of the existing certificates
bool hierarchy_found = false;

// Create a new node, is not self-signed and is not a permanent orphan
X509Node new_node = {{0, 0, 0}, certificate, invalid_hash, certificate, {}};
X509Node new_node = {{0, 0, 0}, inserted_certificate, invalid_hash, inserted_certificate, {}};

// Search through all the list for a link
for_each([&](X509Node& top) {
if (top.certificate.is_child(certificate)) {
if (top.certificate.is_child(inserted_certificate)) {
// Some sanity checks
if (top.state.is_selfsigned)
if (top.state.is_selfsigned) {
throw InvalidStateException(
"Newly added certificate can't be parent of a self-signed certificate!");
if (top.state.is_hash_computed)
}

if (top.state.is_hash_computed) {
throw InvalidStateException("Existing non-root top certificate can't have a valid hash!");
}

// If the top certificate is a descendant of the certificate we're adding

Expand All @@ -158,12 +161,12 @@ void X509CertificateHierarchy::insert(X509Wrapper&& certificate) {
// Set the new top
top = std::move(new_node);
hierarchy_found = true; // Found a link
} else if (certificate.is_child(top.certificate)) {
} else if (inserted_certificate.is_child(top.certificate)) {
// If the certificate is the descendant of top certificate

// Calculate hash and set issuer
new_node.state = {0, 0, 1};
new_node.hash = certificate.get_certificate_hash_data(top.certificate);
new_node.hash = inserted_certificate.get_certificate_hash_data(top.certificate);
new_node.issuer = X509Wrapper(top.certificate); // Set the new issuer

// Add it to the top's descendant list
Expand All @@ -181,7 +184,11 @@ void X509CertificateHierarchy::insert(X509Wrapper&& certificate) {
}
} else {
// If it is self-signed insert it in the roots, with the state set as a self-signed and a properly computed hash
hierarchy.push_back({{1, 0, 1}, certificate, certificate.get_certificate_hash_data(), certificate, {}});
hierarchy.push_back({{1, 0, 1},
inserted_certificate,
inserted_certificate.get_certificate_hash_data(),
inserted_certificate,
{}});

// Attempt a partial prune, by searching through all the contained temporary orphan certificates
// and trying to add them to the newly inserted root certificate, if that is possible
Expand All @@ -198,14 +205,14 @@ void X509CertificateHierarchy::insert(X509Wrapper&& certificate) {
throw InvalidStateException("Orphan certificate can't have a proper hash!");

// If it is a child of the new root certificate insert it to it's list and break
if (node.certificate.is_child(certificate)) {
if (node.certificate.is_child(inserted_certificate)) {
auto& new_root = hierarchy.back();

// Hash is properly computed now
node.hash = node.certificate.get_certificate_hash_data(new_root.certificate);
node.state.is_hash_computed = 1;
node.state.is_orphan = 0; // Not an orphan any more
node.issuer = X509Wrapper(certificate); // Set the new valid issuer
node.state.is_orphan = 0; // Not an orphan any more
node.issuer = X509Wrapper(inserted_certificate); // Set the new valid issuer

// Add to the newly inserted root child list
new_root.children.push_back(std::move(node));
Expand Down
7 changes: 7 additions & 0 deletions lib/evse_security/certificate/x509_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ std::string X509Wrapper::get_issuer_key_hash() const {
if (is_selfsigned()) {
return get_key_hash();
} else {
// See 'OCPP 2.0.1 Spec: 2.6. CertificateHashDataType'
throw std::logic_error("get_issuer_key_hash must only be used on self-signed certs");
}
}
Expand All @@ -170,6 +171,12 @@ CertificateHashData X509Wrapper::get_certificate_hash_data(const X509Wrapper& is
CertificateHashData certificate_hash_data;
certificate_hash_data.hash_algorithm = HashAlgorithm::SHA256;
certificate_hash_data.issuer_name_hash = this->get_issuer_name_hash();

// OCPP 2.0.1 Spec: 2.6. CertificateHashDataType
// issuerKeyHash: The hash of the DER encoded public key: the
// value (excluding tag and length) of the subject public key
// field in the issuer’s certificate.

// Issuer key hash
certificate_hash_data.issuer_key_hash = issuer.get_key_hash();
certificate_hash_data.serial_number = this->get_serial_number();
Expand Down
76 changes: 49 additions & 27 deletions lib/evse_security/evse_security.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -435,13 +435,16 @@ InstallCertificateResult EvseSecurity::update_leaf_certificate(const std::string

// Write certificate to file
std::string extra_filename = filesystem_utils::get_random_file_name(PEM_EXTENSION.string());
std::string file_name = conversions::leaf_certificate_type_to_filename(certificate_type) + extra_filename;

const auto file_name = std::string("SECC_LEAF_") + extra_filename;
const auto file_path = cert_path / file_name;
std::string str_cert = leaf_certificate.get_export_string();

// Also write chain to file
const auto chain_file_name = std::string("CPO_CERT_CHAIN_") + extra_filename;
const auto chain_file_name = std::string("CPO_CERT_") +
conversions::leaf_certificate_type_to_filename(certificate_type) + "CHAIN_" +
extra_filename;

const auto chain_file_path = cert_path / chain_file_name;
std::string str_chain_cert = chain_certificate.to_export_string();

Expand Down Expand Up @@ -620,18 +623,29 @@ OCSPRequestDataList EvseSecurity::get_ocsp_request_data() {
OCSPRequestDataList response;
std::vector<OCSPRequestData> ocsp_request_data_list;

X509CertificateBundle ca_bundle(this->ca_bundle_path_map.at(CaCertificateType::V2G), EncodingFormat::PEM);
const auto certificates_of_bundle = ca_bundle.split();
for (const auto& certificate : certificates_of_bundle) {
std::string responder_url = certificate.get_responder_url();
if (!responder_url.empty()) {
auto certificate_hash_data = certificate.get_certificate_hash_data();
OCSPRequestData ocsp_request_data = {certificate_hash_data, responder_url};
ocsp_request_data_list.push_back(ocsp_request_data);
}
try {
X509CertificateBundle ca_bundle(this->ca_bundle_path_map.at(CaCertificateType::V2G), EncodingFormat::PEM);

// Build hierarchy for the bundle
auto& hierarchy = ca_bundle.get_certficate_hierarchy();

// Iterate cache, get hashes
hierarchy.for_each([&](const X509Node& node) {
std::string responder_url = node.certificate.get_responder_url();
if (!responder_url.empty()) {
auto certificate_hash_data = node.hash;
OCSPRequestData ocsp_request_data = {certificate_hash_data, responder_url};
ocsp_request_data_list.push_back(ocsp_request_data);
}

return true;
});

response.ocsp_request_data_list = ocsp_request_data_list;
} catch (const CertificateLoadException& e) {
EVLOG_error << "Could not get ocsp cache, certificate load failure: " << e.what();
}

response.ocsp_request_data_list = ocsp_request_data_list;
return response;
}

Expand Down Expand Up @@ -663,7 +677,7 @@ void EvseSecurity::update_ocsp_cache(const CertificateHashData& certificate_hash
}
}
} catch (const CertificateLoadException& e) {
EVLOG_error << "Could not update ocsp cache, certificate load failure!";
EVLOG_error << "Could not update ocsp cache, certificate load failure: " << e.what();
}
}

Expand Down Expand Up @@ -706,7 +720,7 @@ std::string EvseSecurity::generate_certificate_signing_request(LeafCertificateTy

// Make a difference between normal and tpm keys for identification
const auto file_name =
std::string("SECC_LEAF_") +
conversions::leaf_certificate_type_to_filename(certificate_type) +
filesystem_utils::get_random_file_name(use_tpm ? TPM_KEY_EXTENSION.string() : KEY_EXTENSION.string());

if (certificate_type == LeafCertificateType::CSMS) {
Expand Down Expand Up @@ -955,25 +969,33 @@ bool EvseSecurity::update_certificate_links(LeafCertificateType certificate_type
std::string EvseSecurity::get_verify_file(CaCertificateType certificate_type) {
std::lock_guard<std::mutex> guard(EvseSecurity::security_mutex);

// Support bundle files, in case the certificates contain
// multiple entries (should be 3) as per the specification
X509CertificateBundle verify_file(this->ca_bundle_path_map.at(certificate_type), EncodingFormat::PEM);
try {
// Support bundle files, in case the certificates contain
// multiple entries (should be 3) as per the specification
X509CertificateBundle verify_file(this->ca_bundle_path_map.at(certificate_type), EncodingFormat::PEM);

EVLOG_debug << "Requesting certificate file: [" << conversions::ca_certificate_type_to_string(certificate_type)
<< "] file:" << verify_file.get_path();
EVLOG_info << "Requesting certificate file: [" << conversions::ca_certificate_type_to_string(certificate_type)
<< "] file:" << verify_file.get_path();

// If we are using a directory, search for the first valid root file
if (verify_file.is_using_directory()) {
auto& hierarchy = verify_file.get_certficate_hierarchy();
// If we are using a directory, search for the first valid root file
if (verify_file.is_using_directory()) {
auto& hierarchy = verify_file.get_certficate_hierarchy();

// Get all roots and search for a valid self-signed
for (auto& root : hierarchy.get_hierarchy()) {
if (root.certificate.is_selfsigned() && root.certificate.is_valid())
return root.certificate.get_file().value_or("");
// Get all roots and search for a valid self-signed
for (auto& root : hierarchy.get_hierarchy()) {
if (root.certificate.is_selfsigned() && root.certificate.is_valid())
return root.certificate.get_file().value_or("");
}
}

return verify_file.get_path().string();
} catch (const CertificateLoadException& e) {
EVLOG_error << "Could not obtain verify file, wrong format for certificate: "
<< this->ca_bundle_path_map.at(certificate_type) << " with error: " << e.what();
}

return verify_file.get_path().string();
throw NoCertificateFound("Could not find any CA certificate for: " +
conversions::ca_certificate_type_to_string(certificate_type));
}

int EvseSecurity::get_leaf_expiry_days_count(LeafCertificateType certificate_type) {
Expand Down
13 changes: 13 additions & 0 deletions lib/evse_security/evse_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ std::string leaf_certificate_type_to_string(LeafCertificateType e) {
}
};

std::string leaf_certificate_type_to_filename(LeafCertificateType e) {
switch (e) {
case LeafCertificateType::CSMS:
return "CSMS_LEAF_";
case LeafCertificateType::V2G:
return "SECC_LEAF_";
case LeafCertificateType::MF:
return "MF_LEAF_";
default:
throw std::out_of_range("Could not convert LeafCertificateType to string");
}
}

std::string certificate_type_to_string(CertificateType e) {
switch (e) {
case CertificateType::V2GRootCertificate:
Expand Down

0 comments on commit cea0c20

Please sign in to comment.