Skip to content

Commit

Permalink
* Adding A04 Security Event Notification
Browse files Browse the repository at this point in the history
* 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 <robert@pionix.de>
  • Loading branch information
RobertDeLeeuw authored and Pietfried committed Oct 16, 2023
1 parent f07b61d commit 0c43464
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 8 deletions.
28 changes: 28 additions & 0 deletions include/ocpp/common/types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
20 changes: 20 additions & 0 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include <ocpp/v201/messages/RequestStartTransaction.hpp>
#include <ocpp/v201/messages/RequestStopTransaction.hpp>
#include <ocpp/v201/messages/Reset.hpp>
#include <ocpp/v201/messages/SecurityEventNotification.hpp>
#include <ocpp/v201/messages/SendLocalList.hpp>
#include <ocpp/v201/messages/SetNetworkProfile.hpp>
#include <ocpp/v201/messages/SetVariables.hpp>
Expand Down Expand Up @@ -96,6 +97,13 @@ struct Callbacks {
std::optional<std::function<bool(const NetworkConnectionProfile& network_connection_profile)>>
configure_network_connection_profile_callback;
std::optional<std::function<void(const ocpp::DateTime& currentTime)>> 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<void(const CiString<50>& event_type, const std::optional<CiString<255>>& tech_info)>
security_event_callback;
};

/// \brief Class implements OCPP2.0.1 Charging Station
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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<CiString<255>>& 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<ReportData>& report_data);
Expand Down Expand Up @@ -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<CiString<255>>& tech_info);

/// \brief Data transfer mechanism initiated by charger
/// \param vendorId
/// \param messageId
Expand Down
3 changes: 2 additions & 1 deletion lib/ocpp/common/message_queue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ bool MessageQueue<v201::MessageType>::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;
Expand Down
74 changes: 67 additions & 7 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ ChargePoint::ChargePoint(const std::map<int32_t, int32_t>& 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()) {
Expand Down Expand Up @@ -108,6 +109,7 @@ ChargePoint::ChargePoint(const std::map<int32_t, int32_t>& 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
Expand Down Expand Up @@ -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<FirmwareStatusNotificationRequest> call(req, this->message_queue->createMessageId());
this->send_async<FirmwareStatusNotificationRequest>(call);

if (req.status == FirmwareStatusEnum::Installed) {
std::string firmwareVersionMessage = "New firmware succesfully installed! Version: ";
firmwareVersionMessage.append(
this->device_model->get_value<std::string>(ControllerComponentVariables::FirmwareVersion));
this->security_event_notification_req(CiString<50>(ocpp::security_events::FIRMWARE_UPDATED),
std::optional<CiString<255>>(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<CiString<255>>("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) {
Expand Down Expand Up @@ -448,6 +464,10 @@ void ChargePoint::on_log_status_notification(UploadLogStatusEnum status, int32_t
this->send<LogStatusNotificationRequest>(call);
}

void ChargePoint::on_security_event(const CiString<50>& event_type, const std::optional<CiString<255>>& tech_info) {
this->security_event_notification_req(event_type, tech_info, false, false);
}

template <class T> bool ChargePoint::send(ocpp::Call<T> call) {
this->message_queue->push(call);
return true;
Expand Down Expand Up @@ -1140,6 +1160,25 @@ bool ChargePoint::is_offline() {
return offline;
}

void ChargePoint::security_event_notification_req(const CiString<50>& event_type,
const std::optional<CiString<255>>& 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<SecurityEventNotificationRequest> call(req, this->message_queue->createMessageId());
this->send<SecurityEventNotificationRequest>(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;
Expand Down Expand Up @@ -1345,6 +1384,22 @@ void ChargePoint::handle_boot_notification_response(CallResult<BootNotificationR
for (auto const& [evse_id, evse] : this->evses) {
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<CiString<255>>("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<CiString<255>>("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<std::string>(ControllerComponentVariables::FirmwareVersion));
this->security_event_notification_req(CiString<50>(ocpp::security_events::STARTUP_OF_THE_DEVICE),
std::optional<CiString<255>>(startup_message), true, true);
}
} else {
auto retry_interval = DEFAULT_BOOT_NOTIFICATION_RETRY_INTERVAL;
if (msg.interval > 0) {
Expand Down Expand Up @@ -1875,16 +1930,13 @@ void ChargePoint::handle_trigger_message(Call<TriggerMessageRequest> 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;
Expand Down Expand Up @@ -2039,6 +2091,14 @@ void ChargePoint::handle_firmware_update_req(Call<UpdateFirmwareRequest> call) {

ocpp::CallResult<UpdateFirmwareResponse> call_result(response, call.uniqueId);
this->send<UpdateFirmwareResponse>(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<CiString<255>>("Provided signing certificate is not valid!"), true, false);
}
}

void ChargePoint::handle_data_transfer_req(Call<DataTransferRequest> call) {
Expand Down

0 comments on commit 0c43464

Please sign in to comment.