Skip to content

Commit

Permalink
* added CertSigningRepeatTimes and CertSigningWaitMinimum to ctlrlr c…
Browse files Browse the repository at this point in the history
…omponent vars

* added handling for security: a02, a03 and a04

Signed-off-by: pietfried <[email protected]>
Signed-off-by: Fabian Klemm <[email protected]>
  • Loading branch information
Pietfried authored and klemmpnx committed Oct 26, 2023
1 parent a133941 commit a002ef3
Show file tree
Hide file tree
Showing 4 changed files with 154 additions and 5 deletions.
12 changes: 12 additions & 0 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include <ocpp/v201/messages/Authorize.hpp>
#include <ocpp/v201/messages/BootNotification.hpp>
#include <ocpp/v201/messages/CertificateSigned.hpp>
#include <ocpp/v201/messages/ChangeAvailability.hpp>
#include <ocpp/v201/messages/ClearCache.hpp>
#include <ocpp/v201/messages/DataTransfer.hpp>
Expand All @@ -37,6 +38,7 @@
#include <ocpp/v201/messages/SendLocalList.hpp>
#include <ocpp/v201/messages/SetNetworkProfile.hpp>
#include <ocpp/v201/messages/SetVariables.hpp>
#include <ocpp/v201/messages/SignCertificate.hpp>
#include <ocpp/v201/messages/StatusNotification.hpp>
#include <ocpp/v201/messages/TransactionEvent.hpp>
#include <ocpp/v201/messages/TriggerMessage.hpp>
Expand Down Expand Up @@ -135,6 +137,8 @@ class ChargePoint : ocpp::ChargingStationBase {
// time keeping
std::chrono::time_point<std::chrono::steady_clock> heartbeat_request_time;

Everest::SteadyTimer certificate_signed_timer;

// states
RegistrationStatusEnum registration_status;
WebsocketConnectionStatusEnum websocket_connection_status;
Expand Down Expand Up @@ -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<int32_t> reset_scheduled_evseids;

int csr_attempt;
std::optional<ocpp::CertificateSigningUseEnum> awaited_certificate_signing_use_enum;

// callback struct
Callbacks callbacks;

Expand Down Expand Up @@ -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<CiString<255>>& 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);
Expand Down Expand Up @@ -308,6 +316,10 @@ class ChargePoint : ocpp::ChargingStationBase {

/* OCPP message handlers */

// Functional Block A: Security
void handle_certificate_signed_req(Call<CertificateSignedRequest> call);
void handle_sign_certificate_response(CallResult<SignCertificateResponse> call_result);

// Functional Block B: Provisioning
void handle_boot_notification_response(CallResult<BootNotificationResponse> call_result);
void handle_set_variables_req(Call<SetVariablesRequest> call);
Expand Down
2 changes: 2 additions & 0 deletions include/ocpp/v201/ctrlr_component_variables.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
5 changes: 0 additions & 5 deletions lib/ocpp/v16/charge_point_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1135,10 +1135,6 @@ void ChargePointImpl::handleBootNotificationResponse(ocpp::CallResult<BootNotifi
this->client_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:
Expand Down Expand Up @@ -2085,7 +2081,6 @@ void ChargePointImpl::handleCertificateSignedRequest(ocpp::Call<CertificateSigne

const auto certificateChain = call.msg.certificateChain.get();

// TODO(piet): Choose the right sign use enum!
const auto result = this->evse_security->update_leaf_certificate(
certificateChain, ocpp::CertificateSigningUseEnum::ChargingStationCertificate);
if (result == ocpp::InstallCertificateResult::Accepted) {
Expand Down
140 changes: 140 additions & 0 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -53,6 +54,7 @@ ChargePoint::ChargePoint(const std::map<int32_t, int32_t>& 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()) {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -734,6 +737,8 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& 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);
Expand All @@ -744,11 +749,15 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& 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;
}
}

Expand Down Expand Up @@ -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<std::string> common;
std::optional<std::string> country;
std::optional<std::string> organization;

if (certificate_signing_use == ocpp::CertificateSigningUseEnum::ChargingStationCertificate) {
common =
this->device_model->get_optional_value<std::string>(ControllerComponentVariables::ChargeBoxSerialNumber);
organization =
this->device_model->get_optional_value<std::string>(ControllerComponentVariables::OrganizationName);
country =
this->device_model->get_optional_value<std::string>(ControllerComponentVariables::ISO15118CtrlrCountryName)
.value_or("DE");
} else {
common =
this->device_model->get_optional_value<std::string>(ControllerComponentVariables::ChargeBoxSerialNumber);
this->device_model->get_optional_value<std::string>(
ControllerComponentVariables::ISO15118CtrlrOrganizationName);
country =
this->device_model->get_optional_value<std::string>(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<SignCertificateRequest> call(req, this->message_queue->createMessageId());
this->send<SignCertificateRequest>(call);
}

void ChargePoint::boot_notification_req(const BootReasonEnum& reason) {
EVLOG_debug << "Sending BootNotification";
BootNotificationRequest req;
Expand Down Expand Up @@ -1386,6 +1440,92 @@ void ChargePoint::notify_event_req(const std::vector<EventData>& events) {
this->send<NotifyEventRequest>(call);
}

void ChargePoint::handle_certificate_signed_req(Call<CertificateSignedRequest> 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<CertificateSignedResponse> call_result(response, call.uniqueId);
this->send<CertificateSignedResponse>(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<int>(ControllerComponentVariables::SecurityProfile) == 3) {
this->websocket->disconnect(websocketpp::close::status::service_restart);
}
}

void ChargePoint::handle_sign_certificate_response(CallResult<SignCertificateResponse> 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<int>(ControllerComponentVariables::CertSigningWaitMinimum);
const auto cert_signing_repeat_times =
this->device_model->get_optional_value<int>(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<BootNotificationResponse> call_result) {
// TODO(piet): B01.FR.06
// TODO(piet): B01.FR.07
Expand Down

0 comments on commit a002ef3

Please sign in to comment.