From e7d9de171188d2bc721d7749aaa6e5f28afde5ef Mon Sep 17 00:00:00 2001 From: Maaike Zijderveld Date: Thu, 30 Jan 2025 21:29:41 +0100 Subject: [PATCH] Move certificate stuff of iso15118 to security functional block. (#959) Signed-off-by: Maaike Zijderveld, iolar --- include/ocpp/v201/charge_point.hpp | 20 +- .../ocpp/v201/functional_blocks/security.hpp | 31 +++ lib/ocpp/v201/charge_point.cpp | 237 +----------------- lib/ocpp/v201/functional_blocks/security.cpp | 234 ++++++++++++++++- .../v201/functional_blocks/CMakeLists.txt | 4 + 5 files changed, 277 insertions(+), 249 deletions(-) diff --git a/include/ocpp/v201/charge_point.hpp b/include/ocpp/v201/charge_point.hpp index 819573ddf..ef830953a 100644 --- a/include/ocpp/v201/charge_point.hpp +++ b/include/ocpp/v201/charge_point.hpp @@ -33,7 +33,6 @@ #include #include -#include "ocpp/v201/messages/Get15118EVCertificate.hpp" #include #include #include @@ -41,17 +40,15 @@ #include #include #include -#include +#include #include #include #include -#include #include #include #include #include #include -#include #include #include #include @@ -380,8 +377,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa // timers Everest::SteadyTimer boot_notification_timer; - Everest::SteadyTimer client_certificate_expiration_check_timer; - Everest::SteadyTimer v2g_certificate_expiration_check_timer; ClockAlignedTimer aligned_meter_values_timer; // states @@ -436,9 +431,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa // internal helper functions void initialize(const std::map& evse_connector_structure, const std::string& message_log_path); - void init_certificate_expiration_check_timers(); - void scheduled_check_client_certificate_expiration(); - void scheduled_check_v2g_certificate_expiration(); void websocket_connected_callback(const int configuration_slot, const NetworkConnectionProfile& network_connection_profile); void websocket_disconnected_callback(const int configuration_slot, @@ -633,11 +625,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa // Functional Block L: Firmware management void handle_firmware_update_req(Call call); - // Functional Block M: ISO 15118 Certificate Management - void handle_get_installed_certificate_ids_req(Call call); - void handle_install_certificate_req(Call call); - void handle_delete_certificate_req(Call call); - // Functional Block N: Diagnostics void handle_get_log_req(Call call); void handle_customer_information_req(Call call); @@ -665,11 +652,6 @@ class ChargePoint : public ChargePointInterface, private ocpp::ChargingStationBa }; } - /// \brief Helper function to determine if a certificate installation should be allowed - /// \param cert_type is the certificate type to be checked - /// \return true if it should be allowed - bool should_allow_certificate_install(InstallCertificateUseEnum cert_type) const; - protected: std::shared_ptr smart_charging_handler; diff --git a/include/ocpp/v201/functional_blocks/security.hpp b/include/ocpp/v201/functional_blocks/security.hpp index 84d4930b3..8efa895e3 100644 --- a/include/ocpp/v201/functional_blocks/security.hpp +++ b/include/ocpp/v201/functional_blocks/security.hpp @@ -10,6 +10,10 @@ #include #include +#include +#include +#include +#include #include namespace ocpp::v201 { @@ -28,6 +32,11 @@ class SecurityInterface : public MessageHandlerInterface { virtual void sign_certificate_req(const ocpp::CertificateSigningUseEnum& certificate_signing_use, const bool initiated_by_trigger_message = false) = 0; virtual void stop_certificate_signed_timer() = 0; + virtual void init_certificate_expiration_check_timers() = 0; + virtual void stop_certificate_expiration_check_timers() = 0; + + virtual Get15118EVCertificateResponse + on_get_15118_ev_certificate_request(const Get15118EVCertificateRequest& request) = 0; }; class Security : public SecurityInterface { @@ -38,6 +47,10 @@ class Security : public SecurityInterface { virtual ~Security(); void handle_message(const EnhancedMessage& message) override; virtual void stop_certificate_signed_timer() override; + void init_certificate_expiration_check_timers() override; + void stop_certificate_expiration_check_timers() override; + Get15118EVCertificateResponse + on_get_15118_ev_certificate_request(const Get15118EVCertificateRequest& request) override; private: // Members MessageDispatcherInterface& message_dispatcher; @@ -52,6 +65,8 @@ class Security : public SecurityInterface { int csr_attempt; std::optional awaited_certificate_signing_use_enum; Everest::SteadyTimer certificate_signed_timer; + Everest::SteadyTimer client_certificate_expiration_check_timer; + Everest::SteadyTimer v2g_certificate_expiration_check_timer; private: // Functions /* OCPP message requests */ @@ -63,7 +78,23 @@ class Security : public SecurityInterface { const bool initiated_by_trigger_message = false) override; /* OCPP message handlers */ + + // Functional Block A: Security void handle_certificate_signed_req(Call call); void handle_sign_certificate_response(CallResult call_result); + + // Functional Block M: ISO 15118 Certificate Management + void handle_get_installed_certificate_ids_req(Call call); + void handle_install_certificate_req(Call call); + void handle_delete_certificate_req(Call call); + + // Internal helper functions + + /// \brief Helper function to determine if a certificate installation should be allowed + /// \param cert_type is the certificate type to be checked + /// \return true if it should be allowed + bool should_allow_certificate_install(InstallCertificateUseEnum cert_type) const; + void scheduled_check_client_certificate_expiration(); + void scheduled_check_v2g_certificate_expiration(); }; } // namespace ocpp::v201 diff --git a/lib/ocpp/v201/charge_point.cpp b/lib/ocpp/v201/charge_point.cpp index c000a3954..3ba5e02d4 100644 --- a/lib/ocpp/v201/charge_point.cpp +++ b/lib/ocpp/v201/charge_point.cpp @@ -50,8 +50,6 @@ ChargePoint::ChargePoint(const std::map& evse_connector_struct monitoring_updater( device_model, [this](const std::vector& events) { this->notify_event_req(events); }, [this]() { return this->is_offline(); }), - client_certificate_expiration_check_timer([this]() { this->scheduled_check_client_certificate_expiration(); }), - v2g_certificate_expiration_check_timer([this]() { this->scheduled_check_v2g_certificate_expiration(); }), callbacks(callbacks) { if (!this->device_model) { @@ -145,8 +143,7 @@ void ChargePoint::stop() { this->availability->stop_heartbeat_timer(); this->boot_notification_timer.stop(); this->connectivity_manager->disconnect(); - this->client_certificate_expiration_check_timer.stop(); - this->v2g_certificate_expiration_check_timer.stop(); + this->security->stop_certificate_expiration_check_timers(); this->monitoring_updater.stop_monitoring(); this->message_queue->stop(); this->security->stop_certificate_signed_timer(); @@ -226,42 +223,7 @@ void ChargePoint::on_session_started(const int32_t evse_id, const int32_t connec Get15118EVCertificateResponse ChargePoint::on_get_15118_ev_certificate_request(const Get15118EVCertificateRequest& request) { - Get15118EVCertificateResponse response; - - if (!this->device_model - ->get_optional_value(ControllerComponentVariables::ContractCertificateInstallationEnabled) - .value_or(false)) { - EVLOG_warning << "Can not fulfill Get15118EVCertificateRequest because ContractCertificateInstallationEnabled " - "is configured as false!"; - response.status = Iso15118EVCertificateStatusEnum::Failed; - return response; - } - - EVLOG_debug << "Received Get15118EVCertificateRequest " << request; - auto future_res = this->message_dispatcher->dispatch_call_async(ocpp::Call(request)); - - if (future_res.wait_for(DEFAULT_WAIT_FOR_FUTURE_TIMEOUT) == std::future_status::timeout) { - EVLOG_warning << "Waiting for Get15118EVCertificateRequest.conf future timed out!"; - response.status = Iso15118EVCertificateStatusEnum::Failed; - return response; - } - - const auto response_message = future_res.get(); - EVLOG_debug << "Received Get15118EVCertificateResponse " << response_message.message; - if (response_message.messageType != MessageType::Get15118EVCertificateResponse) { - response.status = Iso15118EVCertificateStatusEnum::Failed; - return response; - } - - try { - ocpp::CallResult call_result = response_message.message; - return call_result.msg; - } catch (const EnumConversionException& e) { - EVLOG_error << "EnumConversionException during handling of message: " << e.what(); - auto call_error = CallError(response_message.uniqueId, "FormationViolation", e.what(), json({})); - this->message_dispatcher->dispatch_call_error(call_error); - return response; - } + return this->security->on_get_15118_ev_certificate_request(request); } void ChargePoint::on_transaction_started(const int32_t evse_id, const int32_t connector_id, @@ -966,27 +928,6 @@ void ChargePoint::initialize(const std::map& evse_connector_st VARIABLE_ATTRIBUTE_VALUE_SOURCE_INTERNAL, true); } -void ChargePoint::init_certificate_expiration_check_timers() { - - // Timers started with initial delays; callback functions are supposed to re-schedule on their own! - - // Client Certificate only needs to be checked for SecurityProfile 3; if SecurityProfile changes, timers get - // re-initialized at reconnect - if (this->device_model->get_value(ControllerComponentVariables::SecurityProfile) == 3) { - this->client_certificate_expiration_check_timer.timeout(std::chrono::seconds( - this->device_model - ->get_optional_value(ControllerComponentVariables::ClientCertificateExpireCheckInitialDelaySeconds) - .value_or(60))); - } - - // V2G Certificate timer is started in any case; condition (V2GCertificateInstallationEnabled) is validated in - // callback (ChargePoint::scheduled_check_v2g_certificate_expiration) - this->v2g_certificate_expiration_check_timer.timeout(std::chrono::seconds( - this->device_model - ->get_optional_value(ControllerComponentVariables::V2GCertificateExpireCheckInitialDelaySeconds) - .value_or(60))); -} - void ChargePoint::handle_message(const EnhancedMessage& message) { const auto& json_message = message.message; try { @@ -1055,20 +996,14 @@ void ChargePoint::handle_message(const EnhancedMessage& messa break; case MessageType::CertificateSigned: case MessageType::SignCertificateResponse: + case MessageType::GetInstalledCertificateIds: + case MessageType::InstallCertificate: + case MessageType::DeleteCertificate: this->security->handle_message(message); break; case MessageType::GetTransactionStatus: this->handle_get_transaction_status(json_message); break; - case MessageType::GetInstalledCertificateIds: - this->handle_get_installed_certificate_ids_req(json_message); - break; - case MessageType::InstallCertificate: - this->handle_install_certificate_req(json_message); - break; - case MessageType::DeleteCertificate: - this->handle_delete_certificate_req(json_message); - break; case MessageType::CustomerInformation: this->handle_customer_information_req(json_message); break; @@ -1790,7 +1725,7 @@ void ChargePoint::handle_boot_notification_response(CallResultboot_notification_timer.stop(); - this->init_certificate_expiration_check_timers(); + this->security->init_certificate_expiration_check_timers(); this->update_aligned_data_interval(); this->component_state_manager->send_status_notification_all_connectors(); this->ocsp_updater.start(); @@ -2792,116 +2727,6 @@ void ChargePoint::handle_firmware_update_req(Call call) { } } -void ChargePoint::handle_get_installed_certificate_ids_req(Call call) { - EVLOG_debug << "Received GetInstalledCertificateIdsRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; - GetInstalledCertificateIdsResponse response; - - const auto msg = call.msg; - - // prepare argument for getRootCertificate - std::vector certificate_types; - if (msg.certificateType.has_value()) { - certificate_types = ocpp::evse_security_conversions::from_ocpp_v201(msg.certificateType.value()); - } else { - certificate_types.push_back(CertificateType::CSMSRootCertificate); - certificate_types.push_back(CertificateType::MFRootCertificate); - certificate_types.push_back(CertificateType::MORootCertificate); - certificate_types.push_back(CertificateType::V2GCertificateChain); - certificate_types.push_back(CertificateType::V2GRootCertificate); - } - - // retrieve installed certificates - const auto certificate_hash_data_chains = this->evse_security->get_installed_certificates(certificate_types); - - // convert the common type back to the v201 type(s) for the response - std::vector certificate_hash_data_chain_v201; - for (const auto& certificate_hash_data_chain_entry : certificate_hash_data_chains) { - certificate_hash_data_chain_v201.push_back( - ocpp::evse_security_conversions::to_ocpp_v201(certificate_hash_data_chain_entry)); - } - - if (certificate_hash_data_chain_v201.empty()) { - response.status = GetInstalledCertificateStatusEnum::NotFound; - } else { - response.certificateHashDataChain = certificate_hash_data_chain_v201; - response.status = GetInstalledCertificateStatusEnum::Accepted; - } - - ocpp::CallResult call_result(response, call.uniqueId); - this->message_dispatcher->dispatch_call_result(call_result); -} - -bool ChargePoint::should_allow_certificate_install(InstallCertificateUseEnum cert_type) const { - const int security_profile = this->device_model->get_value(ControllerComponentVariables::SecurityProfile); - - if (security_profile > 1) { - return true; - } - switch (cert_type) { - case InstallCertificateUseEnum::CSMSRootCertificate: - return this->device_model - ->get_optional_value(ControllerComponentVariables::AllowCSMSRootCertInstallWithUnsecureConnection) - .value_or(true); - - case InstallCertificateUseEnum::ManufacturerRootCertificate: - return this->device_model - ->get_optional_value(ControllerComponentVariables::AllowMFRootCertInstallWithUnsecureConnection) - .value_or(true); - default: - return true; - } -} - -void ChargePoint::handle_install_certificate_req(Call call) { - EVLOG_debug << "Received InstallCertificateRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; - - const auto msg = call.msg; - InstallCertificateResponse response; - - if (!should_allow_certificate_install(msg.certificateType)) { - response.status = InstallCertificateStatusEnum::Rejected; - response.statusInfo = StatusInfo(); - response.statusInfo->reasonCode = "UnsecureConnection"; - response.statusInfo->additionalInfo = "CertificateInstallationNotAllowedWithUnsecureConnection"; - } else { - const auto result = this->evse_security->install_ca_certificate( - msg.certificate.get(), ocpp::evse_security_conversions::from_ocpp_v201(msg.certificateType)); - response.status = ocpp::evse_security_conversions::to_ocpp_v201(result); - if (response.status == InstallCertificateStatusEnum::Accepted) { - const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS; - std::string tech_info = - "Installed certificate: " + conversions::install_certificate_use_enum_to_string(msg.certificateType); - this->security->security_event_notification_req(CiString<50>(security_event), CiString<255>(tech_info), - true, utils::is_critical(security_event)); - } - } - ocpp::CallResult call_result(response, call.uniqueId); - this->message_dispatcher->dispatch_call_result(call_result); -} - -void ChargePoint::handle_delete_certificate_req(Call call) { - EVLOG_debug << "Received DeleteCertificateRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; - - const auto msg = call.msg; - DeleteCertificateResponse response; - - const auto certificate_hash_data = ocpp::evse_security_conversions::from_ocpp_v201(msg.certificateHashData); - - const auto status = this->evse_security->delete_certificate(certificate_hash_data); - - response.status = ocpp::evse_security_conversions::to_ocpp_v201(status); - - if (response.status == DeleteCertificateStatusEnum::Accepted) { - const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS; - std::string tech_info = "Deleted certificate wit serial number: " + msg.certificateHashData.serialNumber.get(); - this->security->security_event_notification_req(CiString<50>(security_event), CiString<255>(tech_info), true, - utils::is_critical(security_event)); - } - - ocpp::CallResult call_result(response, call.uniqueId); - this->message_dispatcher->dispatch_call_result(call_result); -} - void ChargePoint::handle_get_log_req(Call call) { const GetLogResponse response = this->callbacks.get_log_request_callback(call.msg); @@ -3148,51 +2973,6 @@ std::optional ChargePoint::data_transfer_req(const DataTra return this->data_transfer->data_transfer_req(request); } -void ChargePoint::scheduled_check_client_certificate_expiration() { - - EVLOG_info << "Checking if CSMS client certificate has expired"; - int expiry_days_count = - this->evse_security->get_leaf_expiry_days_count(ocpp::CertificateSigningUseEnum::ChargingStationCertificate); - if (expiry_days_count < 30) { - EVLOG_info << "CSMS client certificate is invalid in " << expiry_days_count - << " days. Requesting new certificate with certificate signing request"; - this->security->sign_certificate_req(ocpp::CertificateSigningUseEnum::ChargingStationCertificate); - } else { - EVLOG_info << "CSMS client certificate is still valid."; - } - - this->client_certificate_expiration_check_timer.interval(std::chrono::seconds( - this->device_model - ->get_optional_value(ControllerComponentVariables::ClientCertificateExpireCheckIntervalSeconds) - .value_or(12 * 60 * 60))); -} - -void ChargePoint::scheduled_check_v2g_certificate_expiration() { - if (this->device_model->get_optional_value(ControllerComponentVariables::V2GCertificateInstallationEnabled) - .value_or(false)) { - EVLOG_info << "Checking if V2GCertificate has expired"; - int expiry_days_count = - this->evse_security->get_leaf_expiry_days_count(ocpp::CertificateSigningUseEnum::V2GCertificate); - if (expiry_days_count < 30) { - EVLOG_info << "V2GCertificate is invalid in " << expiry_days_count - << " days. Requesting new certificate with certificate signing request"; - this->security->sign_certificate_req(ocpp::CertificateSigningUseEnum::V2GCertificate); - } else { - EVLOG_info << "V2GCertificate is still valid."; - } - } else { - if (this->device_model->get_optional_value(ControllerComponentVariables::PnCEnabled).value_or(false)) { - EVLOG_warning << "PnC is enabled but V2G certificate installation is not, so no certificate expiration " - "check is performed."; - } - } - - this->v2g_certificate_expiration_check_timer.interval(std::chrono::seconds( - this->device_model - ->get_optional_value(ControllerComponentVariables::V2GCertificateExpireCheckIntervalSeconds) - .value_or(12 * 60 * 60))); -} - void ChargePoint::websocket_connected_callback(const int configuration_slot, const NetworkConnectionProfile& network_connection_profile) { this->message_queue->resume(this->message_queue_resume_delay); @@ -3218,7 +2998,7 @@ void ChargePoint::websocket_connected_callback(const int configuration_slot, EVLOG_debug << "offline for less than offline threshold "; this->component_state_manager->send_status_notification_changed_connectors(); } - this->init_certificate_expiration_check_timers(); // re-init as timers are stopped on disconnect + this->security->init_certificate_expiration_check_timers(); // re-init as timers are stopped on disconnect } } this->time_disconnected = std::chrono::time_point(); @@ -3241,8 +3021,7 @@ void ChargePoint::websocket_disconnected_callback(const int configuration_slot, this->time_disconnected = std::chrono::steady_clock::now(); } - this->client_certificate_expiration_check_timer.stop(); - this->v2g_certificate_expiration_check_timer.stop(); + this->security->stop_certificate_expiration_check_timers(); if (this->callbacks.connection_state_changed_callback.has_value()) { this->callbacks.connection_state_changed_callback.value()(false, configuration_slot, network_connection_profile); diff --git a/lib/ocpp/v201/functional_blocks/security.cpp b/lib/ocpp/v201/functional_blocks/security.cpp index 215d2d6ed..b2d6a74dc 100644 --- a/lib/ocpp/v201/functional_blocks/security.cpp +++ b/lib/ocpp/v201/functional_blocks/security.cpp @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -22,11 +23,14 @@ Security::Security(MessageDispatcherInterface& message_dispatcher, connectivity_manager(connectivity_manager), ocsp_updater(ocsp_updater), security_event_callback(security_event_callback), - csr_attempt(1) { + csr_attempt(1), + client_certificate_expiration_check_timer([this]() { this->scheduled_check_client_certificate_expiration(); }), + v2g_certificate_expiration_check_timer([this]() { this->scheduled_check_v2g_certificate_expiration(); }) { } Security::~Security() { stop_certificate_signed_timer(); + stop_certificate_expiration_check_timers(); } void Security::handle_message(const EnhancedMessage& message) { @@ -38,6 +42,15 @@ void Security::handle_message(const EnhancedMessage& message) { case MessageType::SignCertificateResponse: this->handle_sign_certificate_response(json_message); break; + case MessageType::GetInstalledCertificateIds: + this->handle_get_installed_certificate_ids_req(json_message); + break; + case MessageType::InstallCertificate: + this->handle_install_certificate_req(json_message); + break; + case MessageType::DeleteCertificate: + this->handle_delete_certificate_req(json_message); + break; default: throw MessageTypeNotImplementedException(message.messageType); } @@ -47,6 +60,71 @@ void Security::stop_certificate_signed_timer() { this->certificate_signed_timer.stop(); } +Get15118EVCertificateResponse +Security::on_get_15118_ev_certificate_request(const Get15118EVCertificateRequest& request) { + Get15118EVCertificateResponse response; + + if (!this->device_model + .get_optional_value(ControllerComponentVariables::ContractCertificateInstallationEnabled) + .value_or(false)) { + EVLOG_warning << "Can not fulfill Get15118EVCertificateRequest because ContractCertificateInstallationEnabled " + "is configured as false!"; + response.status = Iso15118EVCertificateStatusEnum::Failed; + return response; + } + + EVLOG_debug << "Received Get15118EVCertificateRequest " << request; + auto future_res = this->message_dispatcher.dispatch_call_async(ocpp::Call(request)); + + if (future_res.wait_for(DEFAULT_WAIT_FOR_FUTURE_TIMEOUT) == std::future_status::timeout) { + EVLOG_warning << "Waiting for Get15118EVCertificateRequest.conf future timed out!"; + response.status = Iso15118EVCertificateStatusEnum::Failed; + return response; + } + + const auto response_message = future_res.get(); + EVLOG_debug << "Received Get15118EVCertificateResponse " << response_message.message; + if (response_message.messageType != MessageType::Get15118EVCertificateResponse) { + response.status = Iso15118EVCertificateStatusEnum::Failed; + return response; + } + + try { + ocpp::CallResult call_result = response_message.message; + return call_result.msg; + } catch (const EnumConversionException& e) { + EVLOG_error << "EnumConversionException during handling of message: " << e.what(); + auto call_error = CallError(response_message.uniqueId, "FormationViolation", e.what(), json({})); + this->message_dispatcher.dispatch_call_error(call_error); + return response; + } +} + +void Security::init_certificate_expiration_check_timers() { + // Timers started with initial delays; callback functions are supposed to re-schedule on their own! + + // Client Certificate only needs to be checked for SecurityProfile 3; if SecurityProfile changes, timers get + // re-initialized at reconnect + if (this->device_model.get_value(ControllerComponentVariables::SecurityProfile) == 3) { + this->client_certificate_expiration_check_timer.timeout(std::chrono::seconds( + this->device_model + .get_optional_value(ControllerComponentVariables::ClientCertificateExpireCheckInitialDelaySeconds) + .value_or(60))); + } + + // V2G Certificate timer is started in any case; condition (V2GCertificateInstallationEnabled) is validated in + // callback (ChargePoint::scheduled_check_v2g_certificate_expiration) + this->v2g_certificate_expiration_check_timer.timeout(std::chrono::seconds( + this->device_model + .get_optional_value(ControllerComponentVariables::V2GCertificateExpireCheckInitialDelaySeconds) + .value_or(60))); +} + +void Security::stop_certificate_expiration_check_timers() { + this->client_certificate_expiration_check_timer.stop(); + this->v2g_certificate_expiration_check_timer.stop(); +} + void Security::security_event_notification_req(const CiString<50>& event_type, const std::optional>& tech_info, const bool triggered_internally, const bool critical, @@ -254,4 +332,158 @@ void Security::handle_sign_certificate_response(CallResult call) { + EVLOG_debug << "Received GetInstalledCertificateIdsRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; + GetInstalledCertificateIdsResponse response; + + const auto msg = call.msg; + + // prepare argument for getRootCertificate + std::vector certificate_types; + if (msg.certificateType.has_value()) { + certificate_types = ocpp::evse_security_conversions::from_ocpp_v201(msg.certificateType.value()); + } else { + certificate_types.push_back(CertificateType::CSMSRootCertificate); + certificate_types.push_back(CertificateType::MFRootCertificate); + certificate_types.push_back(CertificateType::MORootCertificate); + certificate_types.push_back(CertificateType::V2GCertificateChain); + certificate_types.push_back(CertificateType::V2GRootCertificate); + } + + // retrieve installed certificates + const auto certificate_hash_data_chains = this->evse_security.get_installed_certificates(certificate_types); + + // convert the common type back to the v201 type(s) for the response + std::vector certificate_hash_data_chain_v201; + for (const auto& certificate_hash_data_chain_entry : certificate_hash_data_chains) { + certificate_hash_data_chain_v201.push_back( + ocpp::evse_security_conversions::to_ocpp_v201(certificate_hash_data_chain_entry)); + } + + if (certificate_hash_data_chain_v201.empty()) { + response.status = GetInstalledCertificateStatusEnum::NotFound; + } else { + response.certificateHashDataChain = certificate_hash_data_chain_v201; + response.status = GetInstalledCertificateStatusEnum::Accepted; + } + + ocpp::CallResult call_result(response, call.uniqueId); + this->message_dispatcher.dispatch_call_result(call_result); +} + +void Security::handle_install_certificate_req(Call call) { + EVLOG_debug << "Received InstallCertificateRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; + + const auto msg = call.msg; + InstallCertificateResponse response; + + if (!should_allow_certificate_install(msg.certificateType)) { + response.status = InstallCertificateStatusEnum::Rejected; + response.statusInfo = StatusInfo(); + response.statusInfo->reasonCode = "UnsecureConnection"; + response.statusInfo->additionalInfo = "CertificateInstallationNotAllowedWithUnsecureConnection"; + } else { + const auto result = this->evse_security.install_ca_certificate( + msg.certificate.get(), ocpp::evse_security_conversions::from_ocpp_v201(msg.certificateType)); + response.status = ocpp::evse_security_conversions::to_ocpp_v201(result); + if (response.status == InstallCertificateStatusEnum::Accepted) { + const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS; + std::string tech_info = + "Installed certificate: " + conversions::install_certificate_use_enum_to_string(msg.certificateType); + this->security_event_notification_req(CiString<50>(security_event), CiString<255>(tech_info), true, + utils::is_critical(security_event)); + } + } + ocpp::CallResult call_result(response, call.uniqueId); + this->message_dispatcher.dispatch_call_result(call_result); +} + +void Security::handle_delete_certificate_req(Call call) { + EVLOG_debug << "Received DeleteCertificateRequest: " << call.msg << "\nwith messageId: " << call.uniqueId; + + const auto msg = call.msg; + DeleteCertificateResponse response; + + const auto certificate_hash_data = ocpp::evse_security_conversions::from_ocpp_v201(msg.certificateHashData); + + const auto status = this->evse_security.delete_certificate(certificate_hash_data); + + response.status = ocpp::evse_security_conversions::to_ocpp_v201(status); + + if (response.status == DeleteCertificateStatusEnum::Accepted) { + const auto& security_event = ocpp::security_events::RECONFIGURATIONOFSECURITYPARAMETERS; + std::string tech_info = "Deleted certificate wit serial number: " + msg.certificateHashData.serialNumber.get(); + this->security_event_notification_req(CiString<50>(security_event), CiString<255>(tech_info), true, + utils::is_critical(security_event)); + } + + ocpp::CallResult call_result(response, call.uniqueId); + this->message_dispatcher.dispatch_call_result(call_result); +} + +bool Security::should_allow_certificate_install(InstallCertificateUseEnum cert_type) const { + const int security_profile = this->device_model.get_value(ControllerComponentVariables::SecurityProfile); + + if (security_profile > 1) { + return true; + } + switch (cert_type) { + case InstallCertificateUseEnum::CSMSRootCertificate: + return this->device_model + .get_optional_value(ControllerComponentVariables::AllowCSMSRootCertInstallWithUnsecureConnection) + .value_or(true); + + case InstallCertificateUseEnum::ManufacturerRootCertificate: + return this->device_model + .get_optional_value(ControllerComponentVariables::AllowMFRootCertInstallWithUnsecureConnection) + .value_or(true); + default: + return true; + } +} + +void Security::scheduled_check_client_certificate_expiration() { + EVLOG_info << "Checking if CSMS client certificate has expired"; + int expiry_days_count = + this->evse_security.get_leaf_expiry_days_count(ocpp::CertificateSigningUseEnum::ChargingStationCertificate); + if (expiry_days_count < 30) { + EVLOG_info << "CSMS client certificate is invalid in " << expiry_days_count + << " days. Requesting new certificate with certificate signing request"; + this->sign_certificate_req(ocpp::CertificateSigningUseEnum::ChargingStationCertificate); + } else { + EVLOG_info << "CSMS client certificate is still valid."; + } + + this->client_certificate_expiration_check_timer.interval(std::chrono::seconds( + this->device_model + .get_optional_value(ControllerComponentVariables::ClientCertificateExpireCheckIntervalSeconds) + .value_or(12 * 60 * 60))); +} + +void Security::scheduled_check_v2g_certificate_expiration() { + if (this->device_model.get_optional_value(ControllerComponentVariables::V2GCertificateInstallationEnabled) + .value_or(false)) { + EVLOG_info << "Checking if V2GCertificate has expired"; + int expiry_days_count = + this->evse_security.get_leaf_expiry_days_count(ocpp::CertificateSigningUseEnum::V2GCertificate); + if (expiry_days_count < 30) { + EVLOG_info << "V2GCertificate is invalid in " << expiry_days_count + << " days. Requesting new certificate with certificate signing request"; + this->sign_certificate_req(ocpp::CertificateSigningUseEnum::V2GCertificate); + } else { + EVLOG_info << "V2GCertificate is still valid."; + } + } else { + if (this->device_model.get_optional_value(ControllerComponentVariables::PnCEnabled).value_or(false)) { + EVLOG_warning << "PnC is enabled but V2G certificate installation is not, so no certificate expiration " + "check is performed."; + } + } + + this->v2g_certificate_expiration_check_timer.interval(std::chrono::seconds( + this->device_model + .get_optional_value(ControllerComponentVariables::V2GCertificateExpireCheckIntervalSeconds) + .value_or(12 * 60 * 60))); +} + } // namespace ocpp::v201 diff --git a/tests/lib/ocpp/v201/functional_blocks/CMakeLists.txt b/tests/lib/ocpp/v201/functional_blocks/CMakeLists.txt index b535b5fab..e82e9366e 100644 --- a/tests/lib/ocpp/v201/functional_blocks/CMakeLists.txt +++ b/tests/lib/ocpp/v201/functional_blocks/CMakeLists.txt @@ -13,7 +13,11 @@ set(TEST_SECURITY_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/../device_model_test_helpe ${LIBOCPP_LIB_PATH}/ocpp/v201/functional_blocks/security.cpp ${LIBOCPP_LIB_PATH}/ocpp/v201/messages/SecurityEventNotification.cpp ${LIBOCPP_LIB_PATH}/ocpp/v201/messages/CertificateSigned.cpp + ${LIBOCPP_LIB_PATH}/ocpp/v201/messages/DeleteCertificate.cpp + ${LIBOCPP_LIB_PATH}/ocpp/v201/messages/GetInstalledCertificateIds.cpp + ${LIBOCPP_LIB_PATH}/ocpp/v201/messages/InstallCertificate.cpp ${LIBOCPP_LIB_PATH}/ocpp/v201/messages/SignCertificate.cpp + ${LIBOCPP_LIB_PATH}/ocpp/v201/messages/Get15118EVCertificate.cpp ${LIBOCPP_LIB_PATH}/ocpp/v201/messages/Reset.cpp ${LIBOCPP_TEST_INCLUDE_COMMON_SOURCES} ${LIBOCPP_TEST_INCLUDE_V201_SOURCES}