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: 6e702ef5df568c2f9929a3c3b97a09c0cb4c5b21
libwebsockets:
git: https://github.com/warmcat/libwebsockets.git
git_tag: v4.3.3
Expand Down
30 changes: 16 additions & 14 deletions include/ocpp/common/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,27 @@ class EvseSecurity {
virtual bool is_ca_certificate_installed(const CaCertificateType& certificate_type) = 0;

/// \brief Generates a certificate signing request for the given \p certificate_type , \p country , \p organization
/// and \p common . This function respects the requirements of OCPP specified for the CSMS initiated message
/// SignCertificate.req .
/// and \p common , uses the TPM if \p use_tpm is true
/// \param certificate_type
/// \param country
/// \param organization
/// \param common
/// \return the PEM formatted certificate signing request
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;

/// \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 use_tpm If the TPM should be used for the CSR request
/// \return the status and an optional PEM formatted certificate signing request string
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 filesystem on the specified directories 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 a GetKeyPairStatus other than "Accepted". The function \ref
/// update_leaf_certificate will install two files for each leaf, one containing the single leaf and one containing
/// the leaf including any possible SUBCAs
/// \param certificate_type type of the leaf certificate
/// \param encoding specifies PEM or DER format
/// \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 OCSP data should be included
/// \return contains response result, with info related to the certificate chain and response status
virtual GetCertificateInfoResult 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
18 changes: 12 additions & 6 deletions include/ocpp/common/evse_security_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,29 +48,34 @@ 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;
GetCertificateInfoResult get_leaf_certificate_info(const CertificateSigningUseEnum& certificate_type,
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);
HashAlgorithmEnumType to_ocpp(evse_security::HashAlgorithm other);
GetCertificateInfoStatus to_ocpp(evse_security::GetCertificateInfoStatus other);
InstallCertificateResult to_ocpp(evse_security::InstallCertificateResult other);
CertificateValidationResult to_ocpp(evse_security::CertificateValidationResult other);
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 +88,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
48 changes: 43 additions & 5 deletions include/ocpp/common/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -531,11 +531,43 @@
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
};

enum class GetCertificateInfoStatus {
Accepted,
Rejected,
NotFound,
NotFoundValid,
PrivateKeyNotFound,
};

struct GetCertificateSignRequestResult {
GetCertificateSignRequestStatus status;

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

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/types.hpp#L550

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 562 in include/ocpp/common/types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/types.hpp#L562

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 565 in include/ocpp/common/types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/types.hpp#L565

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

struct GetCertificateInfoResult {
GetCertificateInfoStatus status;

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

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/common/types.hpp#L569

struct member 'GetCertificateInfoResult::status' is never used.
std::optional<CertificateInfo> info;
};

enum class LeafCertificateType {
Expand Down Expand Up @@ -580,6 +612,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 +650,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
115 changes: 95 additions & 20 deletions lib/ocpp/common/evse_security_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,23 +96,34 @@ 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);
}

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);

if (key_pair.status == evse_security::GetKeyPairStatus::Accepted && key_pair.pair.has_value()) {
return conversions::to_ocpp(key_pair.pair.value());
} else {
return std::nullopt;
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);
result.csr = csr_response.csr;

return result;
}

GetCertificateInfoResult 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);

GetCertificateInfoResult result;

result.status = conversions::to_ocpp(info_response.status);
if (info_response.info.has_value()) {
result.info = conversions::to_ocpp(info_response.info.value());
}

return result;
}

bool EvseSecurityImpl::update_certificate_links(const CertificateSigningUseEnum& certificate_type) {
Expand All @@ -129,6 +140,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 @@ -187,6 +214,24 @@ HashAlgorithmEnumType to_ocpp(evse_security::HashAlgorithm other) {
}
}

GetCertificateInfoStatus to_ocpp(evse_security::GetCertificateInfoStatus other) {
switch (other) {
case evse_security::GetCertificateInfoStatus::Accepted:
return GetCertificateInfoStatus::Accepted;
case evse_security::GetCertificateInfoStatus::Rejected:
return GetCertificateInfoStatus::Rejected;
case evse_security::GetCertificateInfoStatus::NotFound:
return GetCertificateInfoStatus::NotFound;
case evse_security::GetCertificateInfoStatus::NotFoundValid:
return GetCertificateInfoStatus::NotFoundValid;
case evse_security::GetCertificateInfoStatus::PrivateKeyNotFound:
return GetCertificateInfoStatus::PrivateKeyNotFound;
default:
throw std::runtime_error(
"Could not convert evse_security::GetCertificateInfoStatus to GetCertificateInfoStatus");
}
}

InstallCertificateResult to_ocpp(evse_security::InstallCertificateResult other) {
switch (other) {
case evse_security::InstallCertificateResult::InvalidSignature:
Expand Down Expand Up @@ -284,12 +329,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 +500,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
25 changes: 16 additions & 9 deletions lib/ocpp/common/websocket/websocket_libwebsockets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,21 +448,28 @@ void WebsocketTlsTPM::client_loop() {
std::optional<std::string> password;

if (this->connection_options.security_profile == 3) {
const auto certificate_response =
this->evse_security->get_leaf_certificate_info(CertificateSigningUseEnum::ChargingStationCertificate);

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

if (!certificate_key_pair.has_value()) {
if (certificate_response.status != ocpp::GetCertificateInfoStatus::Accepted or
!certificate_response.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;
const auto& certificate_info = certificate_response.info.value();

if (certificate_info.certificate_path.has_value()) {
path_chain = certificate_info.certificate_path.value();
} else if (certificate_info.certificate_single_path.has_value()) {
path_chain = certificate_info.certificate_single_path.value();
} else {
EVLOG_AND_THROW(std::runtime_error(
"Connecting with security profile 3 but no client side certificate is present or valid"));
}
path_key = certificate_key_pair.value().key_path;
password = certificate_key_pair.value().password;

path_key = certificate_info.key_path;
password = certificate_info.password;
}

SSL_CTX* ssl_ctx = nullptr;
Expand Down
Loading
Loading