Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/66 gracefull crash handling #604

Merged
merged 12 commits into from
May 10, 2024
2 changes: 1 addition & 1 deletion dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ websocketpp:
cmake_condition: "LIBOCPP_ENABLE_DEPRECATED_WEBSOCKETPP"
libevse-security:
git: https://github.com/EVerest/libevse-security.git
git_tag: v0.6.0
git_tag: b56760f
libwebsockets:
git: https://github.com/warmcat/libwebsockets.git
git_tag: v4.3.3
Expand Down
13 changes: 7 additions & 6 deletions include/ocpp/common/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,19 @@ class EvseSecurity {
/// \param organization
/// \param common
/// \return the PEM formatted certificate signing request
AssemblyJohn marked this conversation as resolved.
Show resolved Hide resolved
virtual std::string generate_certificate_signing_request(const CertificateSigningUseEnum& certificate_type,
const std::string& country,
const std::string& organization, const std::string& common,
bool use_tpm) = 0;
virtual GetCertificateSignRequestResult
generate_certificate_signing_request(const CertificateSigningUseEnum& certificate_type, const std::string& country,
const std::string& organization, const std::string& common, bool use_tpm) = 0;

/// \brief Searches the leaf certificate for the given \p certificate_type and retrieves the most recent certificate
/// that is already valid and the respective key . If no certificate is present or no key is matching the
/// certificate, this function returns std::nullopt
/// \param certificate_type type of the leaf certificate
/// \param encoding specifies PEM or DER format
AssemblyJohn marked this conversation as resolved.
Show resolved Hide resolved
/// \return key pair of certificate and key if present, else std::nullopt
virtual std::optional<KeyPair> get_key_pair(const CertificateSigningUseEnum& certificate_type) = 0;
/// \param include_ocsp if we should include certificate ocsp data
/// \return info of certificate and key if present, else std::nullopt
AssemblyJohn marked this conversation as resolved.
Show resolved Hide resolved
virtual std::optional<CertificateInfo> get_leaf_certificate_info(const CertificateSigningUseEnum& certificate_type,
bool include_ocsp = false) = 0;

/// \brief Updates the certificate and key links for the given \p certificate_type
virtual bool update_certificate_links(const CertificateSigningUseEnum& certificate_type) = 0;
Expand Down
17 changes: 11 additions & 6 deletions include/ocpp/common/evse_security_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,17 +48,20 @@ class EvseSecurityImpl : public EvseSecurity {
void update_ocsp_cache(const CertificateHashDataType& certificate_hash_data,
const std::string& ocsp_response) override;
bool is_ca_certificate_installed(const CaCertificateType& certificate_type) override;
std::string generate_certificate_signing_request(const CertificateSigningUseEnum& certificate_type,
const std::string& country, const std::string& organization,
const std::string& common, bool use_tpm) override;
std::optional<KeyPair> get_key_pair(const CertificateSigningUseEnum& certificate_type) override;
GetCertificateSignRequestResult
generate_certificate_signing_request(const CertificateSigningUseEnum& certificate_type, const std::string& country,
const std::string& organization, const std::string& common,
bool use_tpm) override;
std::optional<CertificateInfo> get_leaf_certificate_info(const CertificateSigningUseEnum& certificate_type,
AssemblyJohn marked this conversation as resolved.
Show resolved Hide resolved
bool include_ocsp = false) override;
bool update_certificate_links(const CertificateSigningUseEnum& certificate_type) override;
std::string get_verify_file(const CaCertificateType& certificate_type) override;
int get_leaf_expiry_days_count(const CertificateSigningUseEnum& certificate_type) override;
};

namespace conversions {

GetCertificateSignRequestStatus to_ocpp(evse_security::GetCertificateSignRequestStatus other);
CaCertificateType to_ocpp(evse_security::CaCertificateType other);
CertificateSigningUseEnum to_ocpp(evse_security::LeafCertificateType other);
CertificateType to_ocpp(evse_security::CertificateType other);
Expand All @@ -70,7 +73,8 @@ DeleteCertificateResult to_ocpp(evse_security::DeleteCertificateResult other);
CertificateHashDataType to_ocpp(evse_security::CertificateHashData other);
CertificateHashDataChain to_ocpp(evse_security::CertificateHashDataChain other);
OCSPRequestData to_ocpp(evse_security::OCSPRequestData other);
KeyPair to_ocpp(evse_security::KeyPair other);
CertificateOCSP to_ocpp(evse_security::CertificateOCSP other);
CertificateInfo to_ocpp(evse_security::CertificateInfo other);

evse_security::CaCertificateType from_ocpp(CaCertificateType other);
evse_security::LeafCertificateType from_ocpp(LeafCertificateType other);
Expand All @@ -83,7 +87,8 @@ evse_security::DeleteCertificateResult from_ocpp(DeleteCertificateResult other);
evse_security::CertificateHashData from_ocpp(CertificateHashDataType other);
evse_security::CertificateHashDataChain from_ocpp(CertificateHashDataChain other);
evse_security::OCSPRequestData from_ocpp(OCSPRequestData other);
evse_security::KeyPair from_ocpp(KeyPair other);
evse_security::CertificateOCSP from_ocpp(CertificateOCSP other);
evse_security::CertificateInfo from_ocpp(CertificateInfo other);

}; // namespace conversions

Expand Down
35 changes: 30 additions & 5 deletions include/ocpp/common/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,11 +531,30 @@
std::string responderUrl;
};

struct KeyPair {
fs::path certificate_path; // path to the full certificate chain
fs::path certificate_single_path; // path to the single leaf certificate
fs::path key_path; // path to private key of the leaf certificate
std::optional<std::string> password; // optional password for the private key
enum class GetCertificateSignRequestStatus {
Accepted,
InvalidRequestedType, ///< Requested a CSR for non CSMS/V2G leafs
KeyGenError, ///< The key could not be generated with the requested/default parameters
GenerationError, ///< Any other error when creating the CSR
};

struct GetCertificateSignRequestResult {
GetCertificateSignRequestStatus status;

Check notice on line 542 in include/ocpp/common/types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/types.hpp#L542

struct member 'GetCertificateSignRequestResult::status' is never used.
std::optional<std::string> csr;
};

struct CertificateOCSP {
CertificateHashDataType hash;
std::optional<fs::path> ocsp_path;
};

struct CertificateInfo {
std::optional<fs::path> certificate_path; // path to the full certificate chain
std::optional<fs::path> certificate_single_path; // path to the single leaf certificate
int certificate_count; // count of certs in the chain

Check notice on line 554 in include/ocpp/common/types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/types.hpp#L554

struct member 'CertificateInfo::certificate_count' is never used.
fs::path key_path; // path to private key of the leaf certificate
std::optional<std::string> password; // optional password for the private key
std::vector<CertificateOCSP> ocsp; // OCSP data if requested

Check notice on line 557 in include/ocpp/common/types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/types.hpp#L557

struct member 'CertificateInfo::ocsp' is never used.
};

enum class LeafCertificateType {
Expand Down Expand Up @@ -580,6 +599,11 @@
SignatureVerified
};

namespace conversions {
/// \brief Converts GetCertificateSignRequestStatus to string
std::string generate_certificate_signing_request_status_to_string(const GetCertificateSignRequestStatus status);
} // namespace conversions

namespace conversions {

/// \brief Converts ocpp::FirmwareStatusNotification to v16::FirmwareStatus
Expand Down Expand Up @@ -613,6 +637,7 @@
inline const std::string FIRMWARE_UPDATED = "FirmwareUpdated"; // CRITICAL
inline const std::string FAILEDTOAUTHENTICATEATCSMS = "FailedToAuthenticateAtCsms";
inline const std::string CSMSFAILEDTOAUTHENTICATE = "CsmsFailedToAuthenticate";
inline const std::string CSRGENERATIONFAILED = "CSRGenerationFailed";
inline const std::string SETTINGSYSTEMTIME = "SettingSystemTime"; // CRITICAL
inline const std::string RESET_OR_REBOOT = "ResetOrReboot"; // CRITICAL
inline const std::string STARTUP_OF_THE_DEVICE = "StartupOfTheDevice"; // CRITICAL
Expand Down
86 changes: 71 additions & 15 deletions lib/ocpp/common/evse_security_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,30 @@ bool EvseSecurityImpl::is_ca_certificate_installed(const CaCertificateType& cert
return this->evse_security->is_ca_certificate_installed(conversions::from_ocpp(certificate_type));
}

std::string EvseSecurityImpl::generate_certificate_signing_request(const CertificateSigningUseEnum& certificate_type,
const std::string& country,
const std::string& organization,
const std::string& common, bool use_tpm) {
return this->evse_security->generate_certificate_signing_request(conversions::from_ocpp(certificate_type), country,
organization, common, use_tpm);
GetCertificateSignRequestResult
EvseSecurityImpl::generate_certificate_signing_request(const CertificateSigningUseEnum& certificate_type,
const std::string& country, const std::string& organization,
const std::string& common, bool use_tpm) {
auto csr_response = this->evse_security->generate_certificate_signing_request(
conversions::from_ocpp(certificate_type), country, organization, common, use_tpm);

GetCertificateSignRequestResult result;
result.status = conversions::to_ocpp(csr_response.status);

if (csr_response.csr.has_value()) {
result.csr = csr_response.csr;
}
AssemblyJohn marked this conversation as resolved.
Show resolved Hide resolved

return result;
}

std::optional<KeyPair> EvseSecurityImpl::get_key_pair(const CertificateSigningUseEnum& certificate_type) {
const auto key_pair =
this->evse_security->get_key_pair(conversions::from_ocpp(certificate_type), evse_security::EncodingFormat::PEM);
std::optional<CertificateInfo>
EvseSecurityImpl::get_leaf_certificate_info(const CertificateSigningUseEnum& certificate_type, bool include_ocsp) {
const auto info_response = this->evse_security->get_leaf_certificate_info(
conversions::from_ocpp(certificate_type), evse_security::EncodingFormat::PEM, include_ocsp);

if (key_pair.status == evse_security::GetKeyPairStatus::Accepted && key_pair.pair.has_value()) {
return conversions::to_ocpp(key_pair.pair.value());
if (info_response.status == evse_security::GetCertificateInfoStatus::Accepted && info_response.info.has_value()) {
return conversions::to_ocpp(info_response.info.value());
} else {
return std::nullopt;
}
Expand All @@ -129,6 +139,22 @@ int EvseSecurityImpl::get_leaf_expiry_days_count(const CertificateSigningUseEnum

namespace conversions {

GetCertificateSignRequestStatus to_ocpp(evse_security::GetCertificateSignRequestStatus other) {
switch (other) {
case evse_security::GetCertificateSignRequestStatus::Accepted:
return GetCertificateSignRequestStatus::Accepted;
case evse_security::GetCertificateSignRequestStatus::InvalidRequestedType:
return GetCertificateSignRequestStatus::InvalidRequestedType;
case evse_security::GetCertificateSignRequestStatus::KeyGenError:
return GetCertificateSignRequestStatus::KeyGenError;
case evse_security::GetCertificateSignRequestStatus::GenerationError:
return GetCertificateSignRequestStatus::GenerationError;
default:
throw std::runtime_error(
"Could not convert evse_security::GetCertificateSignRequestStatus to GetCertificateSignRequestStatus");
}
}

CaCertificateType to_ocpp(evse_security::CaCertificateType other) {
switch (other) {
case evse_security::CaCertificateType::V2G:
Expand Down Expand Up @@ -284,12 +310,27 @@ OCSPRequestData to_ocpp(evse_security::OCSPRequestData other) {
return lhs;
}

KeyPair to_ocpp(evse_security::KeyPair other) {
KeyPair lhs;
CertificateOCSP to_ocpp(evse_security::CertificateOCSP other) {
CertificateOCSP lhs;
lhs.hash = to_ocpp(other.hash);
lhs.ocsp_path = other.ocsp_path;
return lhs;
}

CertificateInfo to_ocpp(evse_security::CertificateInfo other) {
CertificateInfo lhs;
lhs.certificate_path = other.certificate;
lhs.certificate_single_path = other.certificate_single;
lhs.certificate_count = other.certificate_count;
lhs.key_path = other.key;
lhs.password = other.password;

if (other.ocsp.empty() == false) {
for (auto& ocsp_data : other.ocsp) {
lhs.ocsp.push_back(to_ocpp(ocsp_data));
}
}

return lhs;
}

Expand Down Expand Up @@ -440,12 +481,27 @@ evse_security::OCSPRequestData from_ocpp(OCSPRequestData other) {
return lhs;
}

evse_security::KeyPair from_ocpp(KeyPair other) {
evse_security::KeyPair lhs;
evse_security::CertificateOCSP from_ocpp(CertificateOCSP other) {
evse_security::CertificateOCSP lhs;
lhs.hash = from_ocpp(other.hash);
lhs.ocsp_path = other.ocsp_path;
return lhs;
}

evse_security::CertificateInfo from_ocpp(CertificateInfo other) {
evse_security::CertificateInfo lhs;
lhs.certificate = other.certificate_path;
lhs.certificate_single = other.certificate_single_path;
lhs.certificate_count = other.certificate_count;
lhs.key = other.key_path;
lhs.password = other.password;

if (other.ocsp.empty() == false) {
for (auto& ocsp_data : other.ocsp) {
lhs.ocsp.push_back(from_ocpp(ocsp_data));
}
}

return lhs;
}

Expand Down
17 changes: 17 additions & 0 deletions lib/ocpp/common/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -996,6 +996,23 @@ std::string double_to_string(double d) {

} // namespace conversions

namespace conversions {
std::string generate_certificate_signing_request_status_to_string(const GetCertificateSignRequestStatus status) {
switch (status) {
case GetCertificateSignRequestStatus::Accepted:
return "Accepted";
case GetCertificateSignRequestStatus::InvalidRequestedType:
return "InvalidRequestedType";
case GetCertificateSignRequestStatus::KeyGenError:
return "KeyGenError";
case GetCertificateSignRequestStatus::GenerationError:
return "GenerationError";
default:
throw std::out_of_range("Could not convert GetCertificateSignRequestStatus to string");
}
}
} // namespace conversions

namespace conversions {
v16::FirmwareStatus firmware_status_notification_to_firmware_status(const FirmwareStatusNotification status) {
switch (status) {
Expand Down
18 changes: 10 additions & 8 deletions lib/ocpp/common/websocket/websocket_libwebsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -449,20 +449,22 @@ void WebsocketTlsTPM::client_loop() {

if (this->connection_options.security_profile == 3) {

const auto certificate_key_pair =
this->evse_security->get_key_pair(CertificateSigningUseEnum::ChargingStationCertificate);
const auto certificate_info =
this->evse_security->get_leaf_certificate_info(CertificateSigningUseEnum::ChargingStationCertificate);

if (!certificate_key_pair.has_value()) {
if (!certificate_info.has_value()) {
EVLOG_AND_THROW(std::runtime_error(
"Connecting with security profile 3 but no client side certificate is present or valid"));
}

path_chain = certificate_key_pair.value().certificate_path;
if (path_chain.empty()) {
path_chain = certificate_key_pair.value().certificate_single_path;
if (certificate_info.value().certificate_path.has_value()) {
path_chain = certificate_info.value().certificate_path.value();
} else {
path_chain = certificate_info.value().certificate_single_path.value();
AssemblyJohn marked this conversation as resolved.
Show resolved Hide resolved
}
path_key = certificate_key_pair.value().key_path;
password = certificate_key_pair.value().password;

path_key = certificate_info.value().key_path;
password = certificate_info.value().password;
}

SSL_CTX* ssl_ctx = nullptr;
Expand Down
23 changes: 13 additions & 10 deletions lib/ocpp/common/websocket/websocket_tls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -171,33 +171,36 @@ tls_context WebsocketTLS::on_tls_init(std::string hostname, websocketpp::connect
}

if (security_profile == 3) {
const auto certificate_key_pair =
this->evse_security->get_key_pair(CertificateSigningUseEnum::ChargingStationCertificate);
const auto certificate_info =
this->evse_security->get_leaf_certificate_info(CertificateSigningUseEnum::ChargingStationCertificate);

if (certificate_key_pair.has_value() && certificate_key_pair.value().password.has_value()) {
std::string passwd = certificate_key_pair.value().password.value();
if (certificate_info.has_value() && certificate_info.value().password.has_value()) {
std::string passwd = certificate_info.value().password.value();
context->set_password_callback(
[passwd](auto max_len, auto purpose) { return passwd.substr(0, max_len); });
}

if (!certificate_key_pair.has_value()) {
if (!certificate_info.has_value()) {
EVLOG_AND_THROW(std::runtime_error(
"Connecting with security profile 3 but no client side certificate is present or valid"));
}
AssemblyJohn marked this conversation as resolved.
Show resolved Hide resolved

// certificate_path contains the chain if not empty. Use certificate chain if available, else use
// certificate_single_path
auto certificate_path = certificate_key_pair.value().certificate_path;
if (certificate_path.empty()) {
certificate_path = certificate_key_pair.value().certificate_single_path;
fs::path certificate_path;

if (certificate_info.value().certificate_path.has_value()) {
certificate_path = certificate_info.value().certificate_path.value();
} else {
certificate_path = certificate_info.value().certificate_single_path.value();
AssemblyJohn marked this conversation as resolved.
Show resolved Hide resolved
}

EVLOG_info << "Using certificate: " << certificate_path;
if (SSL_CTX_use_certificate_chain_file(context->native_handle(), certificate_path.c_str()) != 1) {
EVLOG_AND_THROW(std::runtime_error("Could not use client certificate file within SSL context"));
}
EVLOG_info << "Using key file: " << certificate_key_pair.value().key_path;
if (SSL_CTX_use_PrivateKey_file(context->native_handle(), certificate_key_pair.value().key_path.c_str(),
EVLOG_info << "Using key file: " << certificate_info.value().key_path;
if (SSL_CTX_use_PrivateKey_file(context->native_handle(), certificate_info.value().key_path.c_str(),
SSL_FILETYPE_PEM) != 1) {
EVLOG_AND_THROW(std::runtime_error("Could not set private key file within SSL context"));
}
Expand Down
Loading
Loading