From 68ff60aa4c9d48c8119d435af8084682d450802b Mon Sep 17 00:00:00 2001 From: "Robert de Leeuw, PIONIX" Date: Thu, 28 Sep 2023 11:09:37 +0200 Subject: [PATCH] * Adding A04 Security Event Notification * Only send critical events to the CSMS * L01.FR.02, L01.FR.02, L01.FR.31 * Fixed TriggerMessage for FirmwareStatusNotifcation for L01.FR.26 Signed-off-by: Robert de Leeuw, PIONIX --- include/ocpp/common/types.hpp | 28 +++++++++++ include/ocpp/v201/charge_point.hpp | 20 ++++++++ lib/ocpp/common/message_queue.cpp | 3 +- lib/ocpp/v201/charge_point.cpp | 74 +++++++++++++++++++++++++++--- 4 files changed, 117 insertions(+), 8 deletions(-) diff --git a/include/ocpp/common/types.hpp b/include/ocpp/common/types.hpp index 7e084aa71..60f587493 100644 --- a/include/ocpp/common/types.hpp +++ b/include/ocpp/common/types.hpp @@ -558,6 +558,34 @@ firmware_status_notification_to_firmware_status_enum_type(const FirmwareStatusNo } // namespace conversions +namespace security_events { + +// This is the list of security events defined in OCPP 2.0.1 (and the 1.6 security whitepper). +// Security events that are marked critical should be pushed to the CSMS. +// This is a non-exhaustive list of security events, when a security event matches the description in the OCPP +// specification of one of the Security Events in this list, for interoperability reasons, the Security Event from this +// list shall be used, instead of adding a new (proprietary) Security Event. + +inline const std::string FIRMWARE_UPDATED = "FirmwareUpdated"; // CRITICAL +inline const std::string FAILEDTOAUTHENTICATEATCSMS = "FailedToAuthenticateAtCsms"; +inline const std::string CSMSFAILEDTOAUTHENTICATE = "CsmsFailedToAuthenticate"; +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 +inline const std::string SECURITYLOGWASCLEARED = "SecurityLogWasCleared"; // CRITICAL +inline const std::string RECONFIGURATIONOFSECURITYPARAMETERS = "ReconfigurationOfSecurityParameters"; +inline const std::string MEMORYEXHAUSTION = "MemoryExhaustion"; // CRITICAL +inline const std::string INVALIDMESSAGES = "InvalidMessages"; +inline const std::string ATTEMPTEDREPLAYATTACKS = "AttemptedReplayAttacks"; +inline const std::string TAMPERDETECTIONACTIVATED = "TamperDetectionActivated"; // CRITICAL +inline const std::string INVALIDFIRMWARESIGNATURE = "InvalidFirmwareSignature"; +inline const std::string INVALIDFIRMWARESIGNINGCERTIFICATE = "InvalidFirmwareSigningCertificate"; +inline const std::string INVALIDCSMSCERTIFICATE = "InvalidCsmsCertificate"; +inline const std::string INVALIDCHARGINGSTATIONCERTIFICATE = "InvalidChargingStationCertificate"; +inline const std::string INVALIDTLSVERSION = "InvalidTLSVersion"; +inline const std::string INVALIDTLSCIPHERSUITE = "InvalidTLSCipherSuite"; +} // namespace security_events + } // namespace ocpp #endif diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 48bbd06f5..7bd77a900 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -96,6 +97,13 @@ struct Callbacks { std::optional> configure_network_connection_profile_callback; std::optional> time_sync_callback; + /// + /// \brief callback function that can be used to react to a security event callback. This callback is + /// called only if the SecurityEvent occured internally within libocpp + /// Typically this callback is used to log security events in the security log + /// + std::function& event_type, const std::optional>& tech_info)> + security_event_callback; }; /// \brief Class implements OCPP2.0.1 Charging Station @@ -135,6 +143,7 @@ class ChargePoint : ocpp::ChargingStationBase { int32_t firmware_status_id; UploadLogStatusEnum upload_log_status; int32_t upload_log_status_id; + BootReasonEnum bootreason; int network_configuration_priority; bool disable_automatic_websocket_reconnects; @@ -255,6 +264,10 @@ class ChargePoint : ocpp::ChargingStationBase { bool is_offline(); /* OCPP message requests */ + // 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); + // Functional Block B: Provisioning void boot_notification_req(const BootReasonEnum& reason); void notify_report_req(const int request_id, const int seq_no, const std::vector& report_data); @@ -472,6 +485,13 @@ class ChargePoint : ocpp::ChargingStationBase { /// void on_log_status_notification(UploadLogStatusEnum status, int32_t requestId); + // \brief Notifies chargepoint that a SecurityEvent has occured. This will send a SecurityEventNotification.req to + // the + /// CSMS + /// \param type type of the security event + /// \param tech_info additional info of the security event + void on_security_event(const CiString<50>& event_type, const std::optional>& tech_info); + /// \brief Data transfer mechanism initiated by charger /// \param vendorId /// \param messageId diff --git a/lib/ocpp/common/message_queue.cpp b/lib/ocpp/common/message_queue.cpp index 8bdd1f021..65e717a3a 100644 --- a/lib/ocpp/common/message_queue.cpp +++ b/lib/ocpp/common/message_queue.cpp @@ -39,7 +39,8 @@ bool MessageQueue::isTransactionMessage( if (message == nullptr) { return false; } - if (message->messageType == v201::MessageType::TransactionEvent) { + if (message->messageType == v201::MessageType::TransactionEvent || + message->messageType == v201::MessageType::SecurityEventNotification) { // A04.FR.02 return true; } return false; diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 188fff21a..6eedeee8d 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -50,6 +50,7 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct reset_scheduled_evseids{}, firmware_status(FirmwareStatusEnum::Idle), upload_log_status(UploadLogStatusEnum::Idle), + bootreason(BootReasonEnum::PowerUp), callbacks(callbacks) { // Make sure the received callback struct is completely filled early before we actually start running if (!this->callbacks.all_callbacks_valid()) { @@ -108,6 +109,7 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct } void ChargePoint::start(BootReasonEnum bootreason) { + this->bootreason = bootreason; this->start_websocket(); this->boot_notification_req(bootreason); // FIXME(piet): Run state machine with correct initial state @@ -151,12 +153,26 @@ void ChargePoint::on_firmware_update_status_notification(int32_t request_id, this->firmware_status = req.status; if (request_id != -1) { - req.requestId = request_id; + req.requestId = request_id; // L01.FR.20 this->firmware_status_id = request_id; } ocpp::Call call(req, this->message_queue->createMessageId()); this->send_async(call); + + if (req.status == FirmwareStatusEnum::Installed) { + std::string firmwareVersionMessage = "New firmware succesfully installed! Version: "; + firmwareVersionMessage.append( + this->device_model->get_value(ControllerComponentVariables::FirmwareVersion)); + this->security_event_notification_req(CiString<50>(ocpp::security_events::FIRMWARE_UPDATED), + std::optional>(firmwareVersionMessage), true, + true); // L01.FR.31 + } else if (req.status == FirmwareStatusEnum::InvalidSignature) { + this->security_event_notification_req( + CiString<50>(ocpp::security_events::INVALIDFIRMWARESIGNATURE), + std::optional>("Signature of the provided firmware is not valid!"), true, + false); // L01.FR.03 + } } void ChargePoint::on_session_started(const int32_t evse_id, const int32_t connector_id) { @@ -448,6 +464,10 @@ void ChargePoint::on_log_status_notification(UploadLogStatusEnum status, int32_t this->send(call); } +void ChargePoint::on_security_event(const CiString<50>& event_type, const std::optional>& tech_info) { + this->security_event_notification_req(event_type, tech_info, false, false); +} + template bool ChargePoint::send(ocpp::Call call) { this->message_queue->push(call); return true; @@ -1140,6 +1160,25 @@ bool ChargePoint::is_offline() { return offline; } +void ChargePoint::security_event_notification_req(const CiString<50>& event_type, + const std::optional>& tech_info, + const bool triggered_internally, const bool critical) { + if (critical) { + EVLOG_debug << "Sending SecurityEventNotification"; + SecurityEventNotificationRequest req; + + req.type = event_type; + req.timestamp = DateTime().to_rfc3339(); + req.techInfo = tech_info; + + ocpp::Call call(req, this->message_queue->createMessageId()); + this->send(call); + } + if (triggered_internally and this->callbacks.security_event_callback != nullptr) { + this->callbacks.security_event_callback(event_type, tech_info); + } +} + void ChargePoint::boot_notification_req(const BootReasonEnum& reason) { EVLOG_debug << "Sending BootNotification"; BootNotificationRequest req; @@ -1345,6 +1384,22 @@ void ChargePoint::handle_boot_notification_response(CallResultevses) { evse->trigger_status_notification_callbacks(); } + + if (this->bootreason == BootReasonEnum::RemoteReset) { + this->security_event_notification_req( + CiString<50>(ocpp::security_events::RESET_OR_REBOOT), + std::optional>("Charging Station rebooted due to requested remote reset!"), true, true); + } else if (this->bootreason == BootReasonEnum::ScheduledReset) { + this->security_event_notification_req( + CiString<50>(ocpp::security_events::RESET_OR_REBOOT), + std::optional>("Charging Station rebooted due to a scheduled reset!"), true, true); + } else { + std::string startup_message = "Charging Station powered up! Firmware version: "; + startup_message.append( + this->device_model->get_value(ControllerComponentVariables::FirmwareVersion)); + this->security_event_notification_req(CiString<50>(ocpp::security_events::STARTUP_OF_THE_DEVICE), + std::optional>(startup_message), true, true); + } } else { auto retry_interval = DEFAULT_BOOT_NOTIFICATION_RETRY_INTERVAL; if (msg.interval > 0) { @@ -1875,16 +1930,13 @@ void ChargePoint::handle_trigger_message(Call call) { case MessageTriggerEnum::FirmwareStatusNotification: { FirmwareStatusNotificationRequest request; switch (this->firmware_status) { - case FirmwareStatusEnum::DownloadFailed: case FirmwareStatusEnum::Idle: - case FirmwareStatusEnum::InstallationFailed: - case FirmwareStatusEnum::Installed: - case FirmwareStatusEnum::InstallVerificationFailed: - case FirmwareStatusEnum::InvalidSignature: + case FirmwareStatusEnum::Installed: // L01.FR.25 request.status = FirmwareStatusEnum::Idle; + // do not set requestId when idle: L01.FR.20 break; - default: // So not idle + default: // So not Idle or Installed // L01.FR.26 request.status = this->firmware_status; request.requestId = this->firmware_status_id; break; @@ -2039,6 +2091,14 @@ void ChargePoint::handle_firmware_update_req(Call call) { ocpp::CallResult call_result(response, call.uniqueId); this->send(call_result); + + if ((response.status == UpdateFirmwareStatusEnum::InvalidCertificate) || + (response.status == UpdateFirmwareStatusEnum::RevokedCertificate)) { + // L01.FR.02 + this->security_event_notification_req( + CiString<50>(ocpp::security_events::INVALIDFIRMWARESIGNINGCERTIFICATE), + std::optional>("Provided signing certificate is not valid!"), true, false); + } } void ChargePoint::handle_data_transfer_req(Call call) {