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

Added A04 Security Event Notification #199

Merged
merged 1 commit into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wondering why this approach was used for these values instead of using an enum and the conversion functions like we did for the other types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I choose this as it is not a fixed enum. It is a String in OCPP, and there is a given list of values. But this list can be extended. So it is more inline with OCPP.

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