From a002ef31fcf15eb35ef502af68ea48f0a7d6b202 Mon Sep 17 00:00:00 2001 From: pietfried Date: Wed, 9 Aug 2023 17:32:07 +0200 Subject: [PATCH] * 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 | 5 - lib/ocpp/v201/charge_point.cpp | 140 ++++++++++++++++++ 4 files changed, 154 insertions(+), 5 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 6951dcfb2..d33214b62 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include #include #include +#include #include #include #include @@ -135,6 +137,8 @@ class ChargePoint : ocpp::ChargingStationBase { // time keeping std::chrono::time_point heartbeat_request_time; + Everest::SteadyTimer certificate_signed_timer; + // states RegistrationStatusEnum registration_status; WebsocketConnectionStatusEnum websocket_connection_status; @@ -173,6 +177,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; @@ -275,6 +282,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); @@ -308,6 +316,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 0cbb785e3..db7ca4370 100644 --- a/include/ocpp/v201/ctrlr_component_variables.hpp +++ b/include/ocpp/v201/ctrlr_component_variables.hpp @@ -163,6 +163,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 2fbb53aa0..88bb4605a 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -1135,10 +1135,6 @@ void ChargePointImpl::handleBootNotificationResponse(ocpp::CallResultclient_certificate_timer->timeout(INITIAL_CERTIFICATE_REQUESTS_DELAY); } - if (this->is_pnc_enabled()) { - this->ocsp_request_timer->timeout(INITIAL_CERTIFICATE_REQUESTS_DELAY); - } - break; } case RegistrationStatus::Pending: @@ -2085,7 +2081,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 0d7800f7c..b05cfae05 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -13,6 +13,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 @@ -53,6 +54,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()) { @@ -127,6 +129,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->websocket->disconnect(websocketpp::close::status::going_away); this->message_queue->stop(); @@ -734,6 +737,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); @@ -744,11 +749,15 @@ 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; default: if (message.messageTypeId == MessageTypeId::CALL) { const auto call_error = CallError(message.uniqueId, "NotImplemented", "", json({})); this->send(call_error); } + break; } } @@ -1212,6 +1221,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; @@ -1386,6 +1440,92 @@ void ChargePoint::notify_event_req(const std::vector& events) { this->send(call); } +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