From 392eabe4c6775c87d414c48c1244d325b06c6a6c Mon Sep 17 00:00:00 2001 From: pietfried Date: Wed, 9 Aug 2023 17:32:07 +0200 Subject: [PATCH 1/7] * added CertSigningRepeatTimes and CertSigningWaitMinimum to ctlrlr component vars * added handling for security: a02, a03 and a04 Signed-off-by: pietfried Signed-off-by: Fabian Klemm --- include/ocpp/v201/charge_point.hpp | 12 ++ .../ocpp/v201/ctrlr_component_variables.hpp | 2 + lib/ocpp/v16/charge_point_impl.cpp | 1 - lib/ocpp/v201/charge_point.cpp | 140 ++++++++++++++++++ 4 files changed, 154 insertions(+), 1 deletion(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index ce566ab78..e8a131d7b 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -19,6 +19,7 @@ #include "ocpp/v201/messages/Get15118EVCertificate.hpp" #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -162,6 +164,8 @@ class ChargePoint : ocpp::ChargingStationBase { // time keeping std::chrono::time_point heartbeat_request_time; + Everest::SteadyTimer certificate_signed_timer; + // states RegistrationStatusEnum registration_status; OperationalStatusEnum operational_state; @@ -199,6 +203,9 @@ class ChargePoint : ocpp::ChargingStationBase { /// \brief If `reset_scheduled` is true and the reset is for a specific evse id, it will be stored in this member. std::set reset_scheduled_evseids; + int csr_attempt; + std::optional awaited_certificate_signing_use_enum; + // callback struct Callbacks callbacks; @@ -322,6 +329,7 @@ class ChargePoint : ocpp::ChargingStationBase { // 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); + void sign_certificate_req(const ocpp::CertificateSigningUseEnum& certificate_signing_use); // Functional Block B: Provisioning void boot_notification_req(const BootReasonEnum& reason); @@ -355,6 +363,10 @@ class ChargePoint : ocpp::ChargingStationBase { /* OCPP message handlers */ + // Functional Block A: Security + void handle_certificate_signed_req(Call call); + void handle_sign_certificate_response(CallResult call_result); + // Functional Block B: Provisioning void handle_boot_notification_response(CallResult call_result); void handle_set_variables_req(Call call); diff --git a/include/ocpp/v201/ctrlr_component_variables.hpp b/include/ocpp/v201/ctrlr_component_variables.hpp index 2670e9e65..45d44671e 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -164,6 +164,8 @@ extern const ComponentVariable& SampledDataTxUpdatedMeasurands; extern const ComponentVariable& AdditionalRootCertificateCheck; extern const ComponentVariable& BasicAuthPassword; extern const ComponentVariable& CertificateEntries; +extern const ComponentVariable& CertSigningRepeatTimes; +extern const ComponentVariable& CertSigningWaitMinimum; extern const ComponentVariable& SecurityCtrlrIdentity; extern const ComponentVariable& MaxCertificateChainSize; extern const ComponentVariable& OrganizationName; diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 9cce89b71..edd3d78ac 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -2108,7 +2108,6 @@ void ChargePointImpl::handleCertificateSignedRequest(ocpp::Callevse_security->update_leaf_certificate( certificateChain, ocpp::CertificateSigningUseEnum::ChargingStationCertificate); if (result == ocpp::InstallCertificateResult::Accepted) { diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index bdbb5565e..bf4a69292 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -15,6 +15,7 @@ namespace v201 { const auto DEFAULT_BOOT_NOTIFICATION_RETRY_INTERVAL = std::chrono::seconds(30); const auto WEBSOCKET_INIT_DELAY = std::chrono::seconds(2); +const auto INITIAL_CERTIFICATE_REQUESTS_DELAY = std::chrono::seconds(60); bool Callbacks::all_callbacks_valid() const { return this->is_reset_allowed_callback != nullptr and this->reset_callback != nullptr and @@ -56,6 +57,7 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct firmware_status(FirmwareStatusEnum::Idle), upload_log_status(UploadLogStatusEnum::Idle), bootreason(BootReasonEnum::PowerUp), + csr_attempt(1), callbacks(callbacks) { // Make sure the received callback struct is completely filled early before we actually start running if (!this->callbacks.all_callbacks_valid()) { @@ -152,6 +154,7 @@ void ChargePoint::start_websocket() { void ChargePoint::stop() { this->heartbeat_timer.stop(); this->boot_notification_timer.stop(); + this->certificate_signed_timer.stop(); this->websocket_timer.stop(); this->disconnect_websocket(websocketpp::close::status::going_away); this->message_queue->stop(); @@ -827,6 +830,8 @@ void ChargePoint::handle_message(const EnhancedMessage& messa break; case MessageType::TriggerMessage: this->handle_trigger_message(json_message); + case MessageType::SignCertificate: + this->handle_sign_certificate_response(json_message); break; case MessageType::HeartbeatResponse: this->handle_heartbeat_response(json_message); @@ -837,6 +842,9 @@ void ChargePoint::handle_message(const EnhancedMessage& messa case MessageType::GetLocalListVersion: this->handle_get_local_authorization_list_version_req(json_message); break; + case MessageType::CertificateSigned: + this->handle_certificate_signed_req(json_message); + break; case MessageType::GetTransactionStatus: this->handle_get_transaction_status(json_message); break; @@ -857,6 +865,7 @@ void ChargePoint::handle_message(const EnhancedMessage& messa const auto call_error = CallError(message.uniqueId, "NotImplemented", "", json({})); this->send(call_error); } + break; } } @@ -1306,6 +1315,51 @@ void ChargePoint::security_event_notification_req(const CiString<50>& event_type } } +void ChargePoint::sign_certificate_req(const ocpp::CertificateSigningUseEnum& certificate_signing_use) { + if (this->awaited_certificate_signing_use_enum.has_value()) { + EVLOG_warning + << "Not sending new SignCertificate.req because still waiting for CertificateSigned.req from CSMS"; + return; + } + + SignCertificateRequest req; + + std::optional common; + std::optional country; + std::optional organization; + + if (certificate_signing_use == ocpp::CertificateSigningUseEnum::ChargingStationCertificate) { + common = + this->device_model->get_optional_value(ControllerComponentVariables::ChargeBoxSerialNumber); + organization = + this->device_model->get_optional_value(ControllerComponentVariables::OrganizationName); + country = + this->device_model->get_optional_value(ControllerComponentVariables::ISO15118CtrlrCountryName) + .value_or("DE"); + } else { + common = + this->device_model->get_optional_value(ControllerComponentVariables::ChargeBoxSerialNumber); + this->device_model->get_optional_value( + ControllerComponentVariables::ISO15118CtrlrOrganizationName); + country = + this->device_model->get_optional_value(ControllerComponentVariables::ISO15118CtrlrCountryName); + } + + if (!common.has_value() or !country.has_value() or !organization.has_value()) { + EVLOG_warning << "Missing configuration of either organizationName, commonName or country to generate CSR"; + return; + } + + const auto csr = this->evse_security->generate_certificate_signing_request(certificate_signing_use, country.value(), + organization.value(), common.value()); + req.csr = csr; + + this->awaited_certificate_signing_use_enum = certificate_signing_use; + + ocpp::Call call(req, this->message_queue->createMessageId()); + this->send(call); +} + void ChargePoint::boot_notification_req(const BootReasonEnum& reason) { EVLOG_debug << "Sending BootNotification"; BootNotificationRequest req; @@ -1495,6 +1549,92 @@ void ChargePoint::notify_customer_information_req(const std::string& data, const } } +void ChargePoint::handle_certificate_signed_req(Call call) { + // reset these parameters + this->csr_attempt = 1; + this->awaited_certificate_signing_use_enum = std::nullopt; + this->certificate_signed_timer.stop(); + + CertificateSignedResponse response; + response.status = CertificateSignedStatusEnum::Rejected; + + const auto certificate_chain = call.msg.certificateChain.get(); + ocpp::CertificateSigningUseEnum cert_signing_use; + + if (!call.msg.certificateType.has_value() or + call.msg.certificateType.value() == CertificateSigningUseEnum::ChargingStationCertificate) { + cert_signing_use = ocpp::CertificateSigningUseEnum::ChargingStationCertificate; + } else { + cert_signing_use = ocpp::CertificateSigningUseEnum::V2GCertificate; + } + + const auto result = this->evse_security->update_leaf_certificate(certificate_chain, cert_signing_use); + + if (result == ocpp::InstallCertificateResult::Accepted) { + response.status = CertificateSignedStatusEnum::Accepted; + } + + ocpp::CallResult call_result(response, call.uniqueId); + this->send(call_result); + + if (result != ocpp::InstallCertificateResult::Accepted) { + // TODO(piet): this->security_event_notification_req("InvalidChargingStationCertificate"); + } + + // reconnect with new certificate if valid and security profile is 3 + if (response.status == CertificateSignedStatusEnum::Accepted and + cert_signing_use == ocpp::CertificateSigningUseEnum::ChargingStationCertificate and + this->device_model->get_value(ControllerComponentVariables::SecurityProfile) == 3) { + this->websocket->disconnect(websocketpp::close::status::service_restart); + } +} + +void ChargePoint::handle_sign_certificate_response(CallResult call_result) { + if (!this->awaited_certificate_signing_use_enum.has_value()) { + EVLOG_warning + << "Received SignCertificate.conf while not awaiting a CertificateSigned.req . This should not happen."; + return; + } + + if (call_result.msg.status == GenericStatusEnum::Accepted) { + // set timer waiting for certificate signed + const auto cert_signing_wait_minimum = + this->device_model->get_optional_value(ControllerComponentVariables::CertSigningWaitMinimum); + const auto cert_signing_repeat_times = + this->device_model->get_optional_value(ControllerComponentVariables::CertSigningRepeatTimes); + + if (!cert_signing_wait_minimum.has_value()) { + EVLOG_warning << "No CertSigningWaitMinimum is configured, will not attempt to retry SignCertificate.req " + "in case CSMS doesn't send CertificateSigned.req"; + return; + } + if (!cert_signing_repeat_times.has_value()) { + EVLOG_warning << "No CertSigningRepeatTimes is configured, will not attempt to retry SignCertificate.req " + "in case CSMS doesn't send CertificateSigned.req"; + return; + } + + if (this->csr_attempt > cert_signing_repeat_times.value()) { + this->csr_attempt = 1; + this->certificate_signed_timer.stop(); + this->awaited_certificate_signing_use_enum = std::nullopt; + return; + } + int retry_backoff_seconds = cert_signing_wait_minimum.value() * std::pow(2, this->csr_attempt); + this->certificate_signed_timer.timeout( + [this]() { + EVLOG_info << "Did not receive CertificateSigned.req in time. Will retry with SignCertificate.req"; + this->csr_attempt++; + this->sign_certificate_req(this->awaited_certificate_signing_use_enum.value()); + }, + std::chrono::seconds(retry_backoff_seconds)); + } else { + this->awaited_certificate_signing_use_enum = std::nullopt; + this->csr_attempt = 1; + EVLOG_warning << "SignCertificate.req has not been accepted by CSMS"; + } +} + void ChargePoint::handle_boot_notification_response(CallResult call_result) { // TODO(piet): B01.FR.06 // TODO(piet): B01.FR.07 From f5ba18d481db58f14124850328b6a5912d35f8fa Mon Sep 17 00:00:00 2001 From: Fabian Klemm Date: Tue, 31 Oct 2023 11:07:49 +0100 Subject: [PATCH 2/7] add/fix cp sign certificate behavior Signed-off-by: Fabian Klemm --- lib/ocpp/v201/charge_point.cpp | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index bf4a69292..2636f9edf 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -830,6 +830,7 @@ void ChargePoint::handle_message(const EnhancedMessage& messa break; case MessageType::TriggerMessage: this->handle_trigger_message(json_message); + break; case MessageType::SignCertificate: this->handle_sign_certificate_response(json_message); break; @@ -1339,7 +1340,7 @@ void ChargePoint::sign_certificate_req(const ocpp::CertificateSigningUseEnum& ce } else { common = this->device_model->get_optional_value(ControllerComponentVariables::ChargeBoxSerialNumber); - this->device_model->get_optional_value( + organization = this->device_model->get_optional_value( ControllerComponentVariables::ISO15118CtrlrOrganizationName); country = this->device_model->get_optional_value(ControllerComponentVariables::ISO15118CtrlrCountryName); @@ -1578,7 +1579,8 @@ void ChargePoint::handle_certificate_signed_req(Call c this->send(call_result); if (result != ocpp::InstallCertificateResult::Accepted) { - // TODO(piet): this->security_event_notification_req("InvalidChargingStationCertificate"); + this->security_event_notification_req("InvalidChargingStationCertificate", std::optional>{}, true, + true); } // reconnect with new certificate if valid and security profile is 3 @@ -2169,10 +2171,15 @@ void ChargePoint::handle_trigger_message(Call call) { } break; + case MessageTriggerEnum::SignChargingStationCertificate: + EVLOG_info << "got SignChargingStationCertificate"; + response.status = TriggerMessageStatusEnum::Accepted; + break; + case MessageTriggerEnum::SignV2GCertificate: + response.status = TriggerMessageStatusEnum::Accepted; + break; // TODO: // PublishFirmwareStatusNotification - // SignChargingStationCertificate - // SignV2GCertificate // SignCombinedCertificate default: @@ -2281,6 +2288,14 @@ void ChargePoint::handle_trigger_message(Call call) { this->send(call); } break; + case MessageTriggerEnum::SignChargingStationCertificate: { + sign_certificate_req(ocpp::CertificateSigningUseEnum::ChargingStationCertificate); + } break; + + case MessageTriggerEnum::SignV2GCertificate: { + sign_certificate_req(ocpp::CertificateSigningUseEnum::V2GCertificate); + } break; + default: EVLOG_error << "Sent a TriggerMessageResponse::Accepted while not following up with a message"; break; From f7b485444ce188249f36f7bfc7240694b13329a4 Mon Sep 17 00:00:00 2001 From: Fabian Klemm Date: Tue, 31 Oct 2023 11:24:56 +0100 Subject: [PATCH 3/7] remove v16 changes Signed-off-by: Fabian Klemm --- lib/ocpp/v16/charge_point_impl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index edd3d78ac..9cce89b71 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -2108,6 +2108,7 @@ void ChargePointImpl::handleCertificateSignedRequest(ocpp::Callevse_security->update_leaf_certificate( certificateChain, ocpp::CertificateSigningUseEnum::ChargingStationCertificate); if (result == ocpp::InstallCertificateResult::Accepted) { From f0ecbd8d02965a36037a03319b9286ce806f2e0d Mon Sep 17 00:00:00 2001 From: Fabian Klemm Date: Thu, 2 Nov 2023 15:56:23 +0100 Subject: [PATCH 4/7] fix retry behavior; add minimum wait Signed-off-by: Fabian Klemm --- lib/ocpp/v201/charge_point.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 2636f9edf..71e1b29ec 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1622,14 +1622,19 @@ void ChargePoint::handle_sign_certificate_response(CallResultawaited_certificate_signing_use_enum = std::nullopt; return; } - int retry_backoff_seconds = cert_signing_wait_minimum.value() * std::pow(2, this->csr_attempt); + int retry_backoff_milliseconds = + std::max(250, 1000 * cert_signing_wait_minimum.value()) * + std::pow(2, this->csr_attempt); // prevent immediate repetition in case of value 0 this->certificate_signed_timer.timeout( [this]() { EVLOG_info << "Did not receive CertificateSigned.req in time. Will retry with SignCertificate.req"; this->csr_attempt++; - this->sign_certificate_req(this->awaited_certificate_signing_use_enum.value()); + const auto current_awaited_certificate_signing_use_enum = + this->awaited_certificate_signing_use_enum.value(); + this->awaited_certificate_signing_use_enum.reset(); + this->sign_certificate_req(current_awaited_certificate_signing_use_enum); }, - std::chrono::seconds(retry_backoff_seconds)); + std::chrono::milliseconds(retry_backoff_milliseconds)); } else { this->awaited_certificate_signing_use_enum = std::nullopt; this->csr_attempt = 1; From 130bfe1b2ce7c83a6959824c392dc943de064de8 Mon Sep 17 00:00:00 2001 From: Fabian Klemm Date: Thu, 2 Nov 2023 15:59:45 +0100 Subject: [PATCH 5/7] fix message type Signed-off-by: Fabian Klemm --- lib/ocpp/v201/charge_point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index 71e1b29ec..f32397faf 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -831,7 +831,7 @@ void ChargePoint::handle_message(const EnhancedMessage& messa case MessageType::TriggerMessage: this->handle_trigger_message(json_message); break; - case MessageType::SignCertificate: + case MessageType::SignCertificateResponse: this->handle_sign_certificate_response(json_message); break; case MessageType::HeartbeatResponse: From 1267275f4e38337fad109cebc231907fd6d3a8e5 Mon Sep 17 00:00:00 2001 From: Fabian Klemm Date: Thu, 2 Nov 2023 16:00:24 +0100 Subject: [PATCH 6/7] remove redundant semicolons Signed-off-by: Fabian Klemm --- include/ocpp/v201/device_model.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/ocpp/v201/device_model.hpp b/include/ocpp/v201/device_model.hpp index 9e4f65fb5..fb776c8fc 100644 --- a/include/ocpp/v201/device_model.hpp +++ b/include/ocpp/v201/device_model.hpp @@ -106,7 +106,7 @@ class DeviceModel { EVLOG_AND_THROW(std::runtime_error( "Directly requested value for ComponentVariable that doesn't exist in the device model storage.")); } - }; + } /// \brief Access to std::optional of a VariableAttribute for the given component, variable and attribute_enum. /// \tparam T Tatatype of the value that is requested @@ -125,7 +125,7 @@ class DeviceModel { } else { return std::nullopt; } - }; + } /// \brief Requests a value of a VariableAttribute specified by combination of \p component_id and \p variable_id /// from the device model storage From c22842ab4251b1e8483b41f821f6b09be1794a88 Mon Sep 17 00:00:00 2001 From: Fabian Klemm Date: Thu, 9 Nov 2023 14:14:37 +0100 Subject: [PATCH 7/7] review adaptions Signed-off-by: Fabian Klemm --- lib/ocpp/v201/charge_point.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index f32397faf..c96177d84 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -1579,7 +1579,8 @@ void ChargePoint::handle_certificate_signed_req(Call c this->send(call_result); if (result != ocpp::InstallCertificateResult::Accepted) { - this->security_event_notification_req("InvalidChargingStationCertificate", std::optional>{}, true, + this->security_event_notification_req("InvalidChargingStationCertificate", + ocpp::conversions::install_certificate_result_to_string(result), true, true); } @@ -2177,7 +2178,6 @@ void ChargePoint::handle_trigger_message(Call call) { break; case MessageTriggerEnum::SignChargingStationCertificate: - EVLOG_info << "got SignChargingStationCertificate"; response.status = TriggerMessageStatusEnum::Accepted; break; case MessageTriggerEnum::SignV2GCertificate: