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