Skip to content

Commit

Permalink
OCPP201: SignCertificate and CertificateSigned (#212)
Browse files Browse the repository at this point in the history
* * added CertSigningRepeatTimes and CertSigningWaitMinimum to ctlrlr component vars
* added handling for security: a02, a03 and a04

Signed-off-by: pietfried <[email protected]>
Signed-off-by: Fabian Klemm <[email protected]>

* add/fix cp sign certificate behavior

Signed-off-by: Fabian Klemm <[email protected]>

* remove v16  changes

Signed-off-by: Fabian Klemm <[email protected]>

* fix retry behavior; add minimum wait

Signed-off-by: Fabian Klemm <[email protected]>

* fix message type

Signed-off-by: Fabian Klemm <[email protected]>

* remove redundant semicolons

Signed-off-by: Fabian Klemm <[email protected]>

* review adaptions

Signed-off-by: Fabian Klemm <[email protected]>

---------

Signed-off-by: pietfried <[email protected]>
Signed-off-by: Fabian Klemm <[email protected]>
Co-authored-by: Fabian Klemm <[email protected]>
  • Loading branch information
Pietfried and klemmpnx authored Nov 9, 2023
1 parent d767505 commit 0846082
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 4 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 @@ -19,6 +19,7 @@
#include "ocpp/v201/messages/Get15118EVCertificate.hpp"
#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/CustomerInformation.hpp>
Expand All @@ -44,6 +45,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 @@ -162,6 +164,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;
OperationalStatusEnum operational_state;
Expand Down Expand Up @@ -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<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 @@ -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<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 @@ -355,6 +363,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 @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions include/ocpp/v201/device_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
164 changes: 162 additions & 2 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -56,6 +57,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 @@ -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();
Expand Down Expand Up @@ -828,6 +831,9 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& messa
case MessageType::TriggerMessage:
this->handle_trigger_message(json_message);
break;
case MessageType::SignCertificateResponse:
this->handle_sign_certificate_response(json_message);
break;
case MessageType::HeartbeatResponse:
this->handle_heartbeat_response(json_message);
break;
Expand All @@ -837,6 +843,9 @@ 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;
case MessageType::GetTransactionStatus:
this->handle_get_transaction_status(json_message);
break;
Expand All @@ -857,6 +866,7 @@ void ChargePoint::handle_message(const EnhancedMessage<v201::MessageType>& messa
const auto call_error = CallError(message.uniqueId, "NotImplemented", "", json({}));
this->send(call_error);
}
break;
}
}

Expand Down Expand Up @@ -1306,6 +1316,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);
organization = 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 @@ -1495,6 +1550,99 @@ void ChargePoint::notify_customer_information_req(const std::string& data, const
}
}

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) {
this->security_event_notification_req("InvalidChargingStationCertificate",
ocpp::conversions::install_certificate_result_to_string(result), true,
true);
}

// 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_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++;
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::milliseconds(retry_backoff_milliseconds));
} 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 Expand Up @@ -2029,10 +2177,14 @@ void ChargePoint::handle_trigger_message(Call<TriggerMessageRequest> call) {
}
break;

case MessageTriggerEnum::SignChargingStationCertificate:
response.status = TriggerMessageStatusEnum::Accepted;
break;
case MessageTriggerEnum::SignV2GCertificate:
response.status = TriggerMessageStatusEnum::Accepted;
break;
// TODO:
// PublishFirmwareStatusNotification
// SignChargingStationCertificate
// SignV2GCertificate
// SignCombinedCertificate

default:
Expand Down Expand Up @@ -2141,6 +2293,14 @@ void ChargePoint::handle_trigger_message(Call<TriggerMessageRequest> call) {
this->send<FirmwareStatusNotificationRequest>(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;
Expand Down

0 comments on commit 0846082

Please sign in to comment.