Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OCPP201: SignCertificate and CertificateSigned #212

Merged
merged 7 commits into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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