From 8652eac9ea889cff78579a52ecf455e077373b55 Mon Sep 17 00:00:00 2001 From: Kai-Uwe Hermann Date: Mon, 16 Sep 2024 17:11:46 +0200 Subject: [PATCH 1/2] Add critical and timestamp parameters to on_security_event functions This adds "critical" and "timestamp" parameters to the on_security_event functions for OCPP 1.6 and 2.0.1 Criticality is by default determined based on the 1.6 security whitepaper, but can be overwritten externally By default the current datetime is used for the timestamp, but this can also be overwritten externally (for example if the event occured earlier) Signed-off-by: Kai-Uwe Hermann --- include/ocpp/v16/charge_point.hpp | 12 +++- include/ocpp/v16/charge_point_impl.hpp | 15 +++-- include/ocpp/v16/utils.hpp | 22 +++---- include/ocpp/v201/charge_point.hpp | 10 +++- lib/CMakeLists.txt | 1 + lib/ocpp/v16/charge_point.cpp | 5 +- lib/ocpp/v16/charge_point_impl.cpp | 82 +++++++++++++++++--------- lib/ocpp/v16/utils.cpp | 46 +++++++++++++++ lib/ocpp/v201/charge_point.cpp | 13 ++-- 9 files changed, 150 insertions(+), 56 deletions(-) create mode 100644 lib/ocpp/v16/utils.cpp diff --git a/include/ocpp/v16/charge_point.hpp b/include/ocpp/v16/charge_point.hpp index 3d6c04221..1cfae070b 100644 --- a/include/ocpp/v16/charge_point.hpp +++ b/include/ocpp/v16/charge_point.hpp @@ -311,11 +311,17 @@ class ChargePoint { /// be called when an EV is plugged in but the authorization is present within the specified ConnectionTimeout void on_plugin_timeout(int32_t connector); - // \brief Notifies chargepoint that a SecurityEvent occurs. This will send a SecurityEventNotification.req to the + /// \brief Notifies chargepoint that a SecurityEvent occurs. This will send a SecurityEventNotification.req to the /// CSMS - /// \param type type of the security event + /// \param event_type type of the security event /// \param tech_info additional info of the security event - void on_security_event(const std::string& type, const std::string& tech_info); + /// \param critical if set this overwrites the default criticality recommended in the OCPP 1.6 security whitepaper. + /// A critical security event is transmitted as a message to the CSMS, a non-critical one is just written to the + /// security log + /// \param timestamp when this security event occured, if absent the current datetime is assumed + void on_security_event(const CiString<50>& event_type, const std::optional>& tech_info, + const std::optional& critical = std::nullopt, + const std::optional& timestamp = std::nullopt); /// \brief Handles an internal ChangeAvailabilityRequest (in the same way as if it was emitted by the CSMS). /// \param request diff --git a/include/ocpp/v16/charge_point_impl.hpp b/include/ocpp/v16/charge_point_impl.hpp index d18cb68d0..5287ca14a 100644 --- a/include/ocpp/v16/charge_point_impl.hpp +++ b/include/ocpp/v16/charge_point_impl.hpp @@ -337,8 +337,9 @@ class ChargePointImpl : ocpp::ChargingStationBase { void handleInstallCertificateRequest(Call call); void handleGetLogRequest(Call call); void handleSignedUpdateFirmware(Call call); - void securityEventNotification(const std::string& type, const std::string& tech_info, - const bool triggered_internally); + void securityEventNotification(const CiString<50>& event_type, const std::optional>& tech_info, + const bool triggered_internally, const std::optional& critical = std::nullopt, + const std::optional& timestamp = std::nullopt); void switchSecurityProfile(int32_t new_security_profile, int32_t max_connection_attempts); // Local Authorization List profile void handleSendLocalListRequest(Call call); @@ -647,9 +648,15 @@ class ChargePointImpl : ocpp::ChargingStationBase { /// \brief Notifies chargepoint that a SecurityEvent occurs. This will send a SecurityEventNotification.req to the /// CSMS - /// \param type type of the security event + /// \param event_type type of the security event /// \param tech_info additional info of the security event - void on_security_event(const std::string& type, const std::string& tech_info); + /// \param critical if set this overwrites the default criticality recommended in the OCPP 1.6 security whitepaper. + /// A critical security event is transmitted as a message to the CSMS, a non-critical one is just written to the + /// security log + /// \param timestamp when this security event occured, if absent the current datetime is assumed + void on_security_event(const CiString<50>& event_type, const std::optional>& tech_info, + const std::optional& critical = std::nullopt, + const std::optional& timestamp = std::nullopt); /// \brief Handles an internal ChangeAvailabilityRequest (in the same way as if it was emitted by the CSMS). /// \param request diff --git a/include/ocpp/v16/utils.hpp b/include/ocpp/v16/utils.hpp index ead7a42ef..5645e5d37 100644 --- a/include/ocpp/v16/utils.hpp +++ b/include/ocpp/v16/utils.hpp @@ -1,7 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2024 Pionix GmbH and Contributors to EVerest +#ifndef V16_UTILS_HPP +#define V16_UTILS_HPP #include +#include #include #include @@ -9,20 +12,17 @@ namespace ocpp { namespace v16 { namespace utils { -size_t get_message_size(const ocpp::Call& call) { - return json(call).at(CALL_PAYLOAD).dump().length(); -} +size_t get_message_size(const ocpp::Call& call); /// \brief Drops every second entry from transactionData as long as the message size of the \p call is greater than the /// \p max_message_size -void drop_transaction_data(size_t max_message_size, ocpp::Call& call) { - auto& transaction_data = call.msg.transactionData.value(); - while (get_message_size(call) > max_message_size && transaction_data.size() > 2) { - for (size_t i = 1; i < transaction_data.size() - 1; i = i + 2) { - transaction_data.erase(transaction_data.begin() + i); - } - } -} +void drop_transaction_data(size_t max_message_size, ocpp::Call& call); + +/// \brief Determines if a given \p security_event is critical as defined in the OCPP 1.6 security whitepaper +bool is_critical(const std::string& security_event); + } // namespace utils } // namespace v16 } // namespace ocpp + +#endif diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 28027e714..0895db88f 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -352,8 +352,10 @@ class ChargePointInterface { /// \param critical if set this overwrites the default criticality recommended in the OCPP 2.0.1 appendix. A /// critical security event is transmitted as a message to the CSMS, a non-critical one is just written to the /// security log + /// \param timestamp when this security event occured, if absent the current datetime is assumed virtual void on_security_event(const CiString<50>& event_type, const std::optional>& tech_info, - const std::optional& critical = std::nullopt) = 0; + const std::optional& critical = std::nullopt, + const std::optional& timestamp = std::nullopt) = 0; /// \brief Event handler that will update the variable internally when it has been changed on the fly. /// \param set_variable_data contains data of the variable to set @@ -662,7 +664,8 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa // Functional Block A: Security void security_event_notification_req(const CiString<50>& event_type, const std::optional>& tech_info, - const bool triggered_internally, const bool critical); + const bool triggered_internally, const bool critical, + const std::optional& timestamp = std::nullopt); void sign_certificate_req(const ocpp::CertificateSigningUseEnum& certificate_signing_use, const bool initiated_by_trigger_message = false); @@ -944,7 +947,8 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa void on_log_status_notification(UploadLogStatusEnum status, int32_t requestId) override; void on_security_event(const CiString<50>& event_type, const std::optional>& tech_info, - const std::optional& critical = std::nullopt) override; + const std::optional& critical = std::nullopt, + const std::optional& timestamp = std::nullopt) override; void on_variable_changed(const SetVariableData& set_variable_data) override; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 74368b859..9060f896c 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -46,6 +46,7 @@ if(LIBOCPP_ENABLE_V16) ocpp/v16/ocpp_enums.cpp ocpp/v16/ocpp_types.cpp ocpp/v16/types.cpp + ocpp/v16/utils.cpp ) add_subdirectory(ocpp/v16/messages) endif() diff --git a/lib/ocpp/v16/charge_point.cpp b/lib/ocpp/v16/charge_point.cpp index beeb3612c..dfc566496 100644 --- a/lib/ocpp/v16/charge_point.cpp +++ b/lib/ocpp/v16/charge_point.cpp @@ -172,8 +172,9 @@ void ChargePoint::on_plugin_timeout(int32_t connector) { this->charge_point->on_plugin_timeout(connector); } -void ChargePoint::on_security_event(const std::string& type, const std::string& tech_info) { - this->charge_point->on_security_event(type, tech_info); +void ChargePoint::on_security_event(const CiString<50>& event_type, const std::optional>& tech_info, + const std::optional& critical, const std::optional& timestamp) { + this->charge_point->on_security_event(event_type, tech_info, critical, timestamp); } ChangeAvailabilityResponse ChargePoint::on_change_availability(const ChangeAvailabilityRequest& request) { diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 46a9e7f83..5432becc4 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -1053,16 +1053,19 @@ bool ChargePointImpl::start(const std::map& connector_st switch (bootreason) { case BootReasonEnum::RemoteReset: - this->securityEventNotification(CiString<50>(ocpp::security_events::RESET_OR_REBOOT), - "Charging Station rebooted due to requested remote reset!", true); + this->securityEventNotification( + CiString<50>(ocpp::security_events::RESET_OR_REBOOT), + std::optional>("Charging Station rebooted due to requested remote reset!"), true); break; case BootReasonEnum::ScheduledReset: - this->securityEventNotification(CiString<50>(ocpp::security_events::RESET_OR_REBOOT), - "Charging Station rebooted due to a scheduled reset!", true); + this->securityEventNotification( + CiString<50>(ocpp::security_events::RESET_OR_REBOOT), + std::optional>("Charging Station rebooted due to a scheduled reset!"), true); break; case BootReasonEnum::FirmwareUpdate: - this->securityEventNotification(CiString<50>(ocpp::security_events::FIRMWARE_UPDATED), - "Charging Station rebooted due to firmware update!", true); + this->securityEventNotification( + CiString<50>(ocpp::security_events::FIRMWARE_UPDATED), + std::optional>("Charging Station rebooted due to firmware update!"), true); break; case BootReasonEnum::ApplicationReset: case BootReasonEnum::LocalReset: @@ -1072,7 +1075,7 @@ bool ChargePointImpl::start(const std::map& connector_st case BootReasonEnum::Watchdog: default: this->securityEventNotification(CiString<50>(ocpp::security_events::STARTUP_OF_THE_DEVICE), - "The Charge Point has booted", true); + std::optional>("The Charge Point has booted"), true); break; } @@ -1310,13 +1313,15 @@ void ChargePointImpl::message_callback(const std::string& message) { if (json_message.is_array() && json_message.size() > MESSAGE_ID) { auto call_error = CallError(enhanced_message.uniqueId, "FormationViolation", e.what(), json({}, true)); this->send(call_error); - this->securityEventNotification(ocpp::security_events::INVALIDMESSAGES, message, true); + this->securityEventNotification(ocpp::security_events::INVALIDMESSAGES, + std::optional>(message), true); } } catch (const EnumConversionException& e) { EVLOG_error << "EnumConversionException during handling of message: " << e.what(); auto call_error = CallError(enhanced_message.uniqueId, "FormationViolation", e.what(), json({}, true)); this->send(call_error); - this->securityEventNotification(ocpp::security_events::INVALIDMESSAGES, message, true); + this->securityEventNotification(ocpp::security_events::INVALIDMESSAGES, std::optional>(message), + true); } } @@ -2565,7 +2570,8 @@ void ChargePointImpl::sign_certificate(const ocpp::CertificateSigningUseEnum& ce std::string gen_error = "Sign certificate failed due to:" + ocpp::conversions::generate_certificate_signing_request_status_to_string(response.status); - this->securityEventNotification(ocpp::security_events::CSRGENERATIONFAILED, gen_error, true); + this->securityEventNotification(ocpp::security_events::CSRGENERATIONFAILED, + std::optional>(gen_error), true); return; } @@ -2621,8 +2627,9 @@ void ChargePointImpl::handleCertificateSignedRequest(ocpp::Callsend(call_result); if (response.status == CertificateSignedStatusEnumType::Rejected) { - this->securityEventNotification(ocpp::security_events::INVALIDCHARGEPOINTCERTIFICATE, - ocpp::conversions::install_certificate_result_to_string(result), true); + this->securityEventNotification( + ocpp::security_events::INVALIDCHARGEPOINTCERTIFICATE, + std::optional>(ocpp::conversions::install_certificate_result_to_string(result)), true); } // reconnect with new certificate if valid and security profile is 3 @@ -2705,8 +2712,9 @@ void ChargePointImpl::handleInstallCertificateRequest(ocpp::Callsend(call_result); if (response.status == InstallCertificateStatusEnumType::Rejected) { - this->securityEventNotification(ocpp::security_events::INVALIDCENTRALSYSTEMCERTIFICATE, - ocpp::conversions::install_certificate_result_to_string(result), true); + this->securityEventNotification( + ocpp::security_events::INVALIDCENTRALSYSTEMCERTIFICATE, + std::optional>(ocpp::conversions::install_certificate_result_to_string(result)), true); } } @@ -2742,27 +2750,39 @@ void ChargePointImpl::handleSignedUpdateFirmware(ocpp::CallsecurityEventNotification(ocpp::security_events::INVALIDFIRMWARESIGNINGCERTIFICATE, - "Certificate is invalid.", true); + std::optional>("Certificate is invalid."), true); } } -void ChargePointImpl::securityEventNotification(const std::string& type, const std::string& tech_info, - const bool triggered_internally) { - +void ChargePointImpl::securityEventNotification(const CiString<50>& event_type, + const std::optional>& tech_info, + const bool triggered_internally, const std::optional& critical, + const std::optional& timestamp) { SecurityEventNotificationRequest req; - req.type = type; - req.techInfo.emplace(tech_info); - req.timestamp = ocpp::DateTime(); + req.type = event_type; + req.techInfo = tech_info; + if (timestamp.has_value()) { + req.timestamp = timestamp.value(); + } else { + req.timestamp = ocpp::DateTime(); + } this->logging->security(json(req).dump()); - if (!this->configuration->getDisableSecurityEventNotifications()) { + auto critical_security_event = true; + if (critical.has_value()) { + critical_security_event = critical.value(); + } else { + critical_security_event = utils::is_critical(event_type); + } + + if (critical_security_event and !this->configuration->getDisableSecurityEventNotifications()) { ocpp::Call call(req, this->message_queue->createMessageId()); this->send(call); } if (triggered_internally and this->security_event_callback != nullptr) { - this->security_event_callback(type, tech_info); + this->security_event_callback(event_type.get(), tech_info.value_or(CiString<255>("")).get()); } } @@ -2809,7 +2829,7 @@ void ChargePointImpl::signed_firmware_update_status_notification(FirmwareStatusE this->send(call, initiated_by_trigger_message); if (status == FirmwareStatusEnumType::InvalidSignature) { - this->securityEventNotification(ocpp::security_events::INVALIDFIRMWARESIGNATURE, "", true); + this->securityEventNotification(ocpp::security_events::INVALIDFIRMWARESIGNATURE, std::nullopt, true); } if (this->firmware_update_is_pending) { @@ -3539,7 +3559,8 @@ void ChargePointImpl::data_transfer_pnc_sign_certificate() { std::string gen_error = "Data transfer pnc csr failed due to:" + ocpp::conversions::generate_certificate_signing_request_status_to_string(result.status); - this->securityEventNotification(ocpp::security_events::CSRGENERATIONFAILED, gen_error, true); + this->securityEventNotification(ocpp::security_events::CSRGENERATIONFAILED, + std::optional>(gen_error), true); return; } @@ -3748,7 +3769,8 @@ void ChargePointImpl::handle_data_transfer_pnc_certificate_signed(Callsend(call_result); if (certificate_response.status == CertificateSignedStatusEnumType::Rejected) { - this->securityEventNotification(ocpp::security_events::INVALIDCHARGEPOINTCERTIFICATE, tech_info, true); + this->securityEventNotification(ocpp::security_events::INVALIDCHARGEPOINTCERTIFICATE, + std::optional>(tech_info), true); } } catch (const json::exception& e) { EVLOG_warning << "Could not parse data of DataTransfer message CertificateSigned.req: " << e.what(); @@ -4278,7 +4300,8 @@ void ChargePointImpl::on_firmware_update_status_notification(int32_t request_id, } if (firmware_update_status == FirmwareStatusNotification::Installed) { - this->securityEventNotification(ocpp::security_events::FIRMWARE_UPDATED, "Firmware update was installed", true); + this->securityEventNotification(ocpp::security_events::FIRMWARE_UPDATED, + std::optional>("Firmware update was installed"), true); } } @@ -4492,8 +4515,9 @@ void ChargePointImpl::on_plugin_timeout(int32_t connector) { "ConnectionTimeout"); } -void ChargePointImpl::on_security_event(const std::string& type, const std::string& tech_info) { - this->securityEventNotification(type, tech_info, false); +void ChargePointImpl::on_security_event(const CiString<50>& event_type, const std::optional>& tech_info, + const std::optional& critical, const std::optional& timestamp) { + this->securityEventNotification(event_type, tech_info, false, critical, timestamp); } ChangeAvailabilityResponse ChargePointImpl::on_change_availability(const ChangeAvailabilityRequest& request) { diff --git a/lib/ocpp/v16/utils.cpp b/lib/ocpp/v16/utils.cpp new file mode 100644 index 000000000..302b9bae8 --- /dev/null +++ b/lib/ocpp/v16/utils.cpp @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Pionix GmbH and Contributors to EVerest + +#include +#include + +namespace ocpp { +namespace v16 { +namespace utils { + +size_t get_message_size(const ocpp::Call& call) { + return json(call).at(CALL_PAYLOAD).dump().length(); +} + +void drop_transaction_data(size_t max_message_size, ocpp::Call& call) { + auto& transaction_data = call.msg.transactionData.value(); + while (get_message_size(call) > max_message_size && transaction_data.size() > 2) { + for (size_t i = 1; i < transaction_data.size() - 1; i = i + 2) { + transaction_data.erase(transaction_data.begin() + i); + } + } +} + +bool is_critical(const std::string& security_event) { + if (security_event == ocpp::security_events::FIRMWARE_UPDATED) { + return true; + } else if (security_event == ocpp::security_events::SETTINGSYSTEMTIME) { + return true; + } else if (security_event == ocpp::security_events::STARTUP_OF_THE_DEVICE) { + return true; + } else if (security_event == ocpp::security_events::RESET_OR_REBOOT) { + return true; + } else if (security_event == ocpp::security_events::SECURITYLOGWASCLEARED) { + return true; + } else if (security_event == ocpp::security_events::MEMORYEXHAUSTION) { + return true; + } else if (security_event == ocpp::security_events::TAMPERDETECTIONACTIVATED) { + return true; + } + + return false; +} + +} // namespace utils +} // namespace v16 +} // namespace ocpp diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index ef7972fe5..0587106f9 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -933,14 +933,14 @@ void ChargePoint::on_log_status_notification(UploadLogStatusEnum status, int32_t } void ChargePoint::on_security_event(const CiString<50>& event_type, const std::optional>& tech_info, - const std::optional& critical) { + const std::optional& critical, const std::optional& timestamp) { auto critical_security_event = true; if (critical.has_value()) { critical_security_event = critical.value(); } else { critical_security_event = utils::is_critical(event_type); } - this->security_event_notification_req(event_type, tech_info, false, critical_security_event); + this->security_event_notification_req(event_type, tech_info, false, critical_security_event, timestamp); } void ChargePoint::on_variable_changed(const SetVariableData& set_variable_data) { @@ -1815,12 +1815,17 @@ bool ChargePoint::is_offline() { void ChargePoint::security_event_notification_req(const CiString<50>& event_type, const std::optional>& tech_info, - const bool triggered_internally, const bool critical) { + const bool triggered_internally, const bool critical, + const std::optional& timestamp) { EVLOG_debug << "Sending SecurityEventNotification"; SecurityEventNotificationRequest req; req.type = event_type; - req.timestamp = DateTime().to_rfc3339(); + if (timestamp.has_value()) { + req.timestamp = timestamp.value().to_rfc3339(); + } else { + req.timestamp = DateTime().to_rfc3339(); + } req.techInfo = tech_info; this->logging->security(json(req).dump()); if (critical) { From 7123fd093f0c1b8b4ee63d5827ab6e2f8fe95b91 Mon Sep 17 00:00:00 2001 From: Kai-Uwe Hermann Date: Mon, 23 Sep 2024 12:42:03 +0200 Subject: [PATCH 2/2] Bump version to 0.17.0 Signed-off-by: Kai-Uwe Hermann --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a33d0963a..c091d1163 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.14) project(ocpp - VERSION 0.16.2 + VERSION 0.17.0 DESCRIPTION "A C++ implementation of the Open Charge Point Protocol" LANGUAGES CXX )