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

Add timers for certificate expiration check (A03) #250

Merged
merged 6 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
64 changes: 64 additions & 0 deletions config/v201/component_schemas/standardized/InternalCtrlr.json
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,70 @@
"minimum": 512,
"default": "51200",
"type": "integer"
},
"V2GCertificateExpireCheckInitialDelaySeconds": {
"description": "Seconds to wait after boot notification to first check the V2G leaf certificate for expiration and potential renewal",
"variable_name": "V2GCertificateExpireCheckInitialDelaySeconds",
"characteristics": {
"supportsMonitoring": true,
"dataType": "integer"
},
"attributes": [
{
"type": "Actual",
"mutability": "ReadOnly"
}
],
"default": "60",
"type": "integer"
},
"V2GCertificateExpireCheckIntervalSeconds": {
"description": "Seconds between two checks for V2G leaf certificate expiration and potential renewal",
"variable_name": "V2GCertificateExpireCheckIntervalSeconds",
"characteristics": {
"supportsMonitoring": true,
"dataType": "integer"
},
"attributes": [
{
"type": "Actual",
"mutability": "ReadOnly"
}
],
"default": "43200",
"type": "integer"
},
"ClientCertificateExpireCheckInitialDelaySeconds": {
"description": "Seconds to wait after boot notification to first check the client certificate for expiration and potential renewal",
"variable_name": "ClientCertificateExpireCheckInitialDelaySeconds",
"characteristics": {
"supportsMonitoring": true,
"dataType": "integer"
},
"attributes": [
{
"type": "Actual",
"mutability": "ReadOnly"
}
],
"default": "60",
"type": "integer"
},
"ClientCertificateExpireCheckIntervalSeconds": {
"description": "Seconds between two checks for client certificate expiration and potential renewal",
"variable_name": "ClientCertificateExpireCheckIntervalSeconds",
"characteristics": {
"supportsMonitoring": true,
"dataType": "integer"
},
"attributes": [
{
"type": "Actual",
"mutability": "ReadOnly"
}
],
"default": "43200",
"type": "integer"
}
},
"required": [
Expand Down
5 changes: 5 additions & 0 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ class ChargePoint : ocpp::ChargingStationBase {
// timers
Everest::SteadyTimer heartbeat_timer;
Everest::SteadyTimer boot_notification_timer;
Everest::SteadyTimer client_certificate_expiration_check_timer;
Everest::SteadyTimer v2g_certificate_expiration_check_timer;
ClockAlignedTimer aligned_meter_values_timer;

// time keeping
Expand Down Expand Up @@ -218,6 +220,9 @@ class ChargePoint : ocpp::ChargingStationBase {
// internal helper functions
void init_websocket();
WebsocketConnectionOptions get_ws_connection_options(const int32_t configuration_slot);
void init_certificate_expiration_check_timers();
void scheduled_check_client_certificate_expiration();
void scheduled_check_v2g_certificate_expiration();

/// \brief Gets the configured NetworkConnectionProfile based on the given \p configuration_slot . The
/// central system uri ofthe connection options will not contain ws:// or wss:// because this method removes it if
Expand Down
4 changes: 4 additions & 0 deletions include/ocpp/v201/ctrlr_component_variables.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ extern const ComponentVariable& OcspRequestInterval;
extern const ComponentVariable& WebsocketPingPayload;
extern const ComponentVariable& WebsocketPongTimeout;
extern const ComponentVariable& MaxCustomerInformationDataLength;
extern const ComponentVariable& V2GCertificateExpireCheckInitialDelaySeconds;
extern const ComponentVariable& V2GCertificateExpireCheckIntervalSeconds;
extern const ComponentVariable& ClientCertificateExpireCheckInitialDelaySeconds;
extern const ComponentVariable& ClientCertificateExpireCheckIntervalSeconds;
extern const ComponentVariable& AlignedDataCtrlrEnabled;
extern const ComponentVariable& AlignedDataCtrlrAvailable;
extern const ComponentVariable& AlignedDataInterval;
Expand Down
76 changes: 75 additions & 1 deletion lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ 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 @@ -58,6 +57,8 @@ ChargePoint::ChargePoint(const std::map<int32_t, int32_t>& evse_connector_struct
upload_log_status(UploadLogStatusEnum::Idle),
bootreason(BootReasonEnum::PowerUp),
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(); }),
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 @@ -156,6 +157,8 @@ void ChargePoint::stop() {
this->boot_notification_timer.stop();
this->certificate_signed_timer.stop();
this->websocket_timer.stop();
this->client_certificate_expiration_check_timer.stop();
this->v2g_certificate_expiration_check_timer.stop();
this->disconnect_websocket(websocketpp::close::status::going_away);
this->message_queue->stop();
}
Expand Down Expand Up @@ -666,6 +669,7 @@ void ChargePoint::init_websocket() {
}
}
}
this->init_certificate_expiration_check_timers(); // re-init as timers are stopped on disconnect
}
this->time_disconnected = std::chrono::time_point<std::chrono::steady_clock>();
});
Expand All @@ -691,6 +695,9 @@ void ChargePoint::init_websocket() {
// Get the current time point using steady_clock
this->time_disconnected = std::chrono::steady_clock::now();
}

this->client_certificate_expiration_check_timer.stop();
this->v2g_certificate_expiration_check_timer.stop();
});

this->websocket->register_closed_callback(
Expand All @@ -714,6 +721,27 @@ void ChargePoint::init_websocket() {
this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); });
}

void ChargePoint::init_certificate_expiration_check_timers() {

// Timers started with initial delays; callback functions are supposed to re-schedule on their own!
Pietfried marked this conversation as resolved.
Show resolved Hide resolved

// 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<int>(ControllerComponentVariables::SecurityProfile) == 3) {
this->client_certificate_expiration_check_timer.timeout(std::chrono::seconds(
this->device_model
->get_optional_value<int>(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<int>(ControllerComponentVariables::V2GCertificateExpireCheckInitialDelaySeconds)
.value_or(60)));
}

WebsocketConnectionOptions ChargePoint::get_ws_connection_options(const int32_t configuration_slot) {
const auto network_connection_profile_opt = this->get_network_connection_profile(configuration_slot);

Expand Down Expand Up @@ -1664,6 +1692,7 @@ void ChargePoint::handle_boot_notification_response(CallResult<BootNotificationR
if (msg.interval > 0) {
this->heartbeat_timer.interval([this]() { this->heartbeat_req(); }, std::chrono::seconds(msg.interval));
}
this->init_certificate_expiration_check_timers();
this->update_aligned_data_interval();
// B01.FR.06 Only use boot timestamp if TimeSource contains Heartbeat
if (this->callbacks.time_sync_callback.has_value() &&
Expand Down Expand Up @@ -2689,5 +2718,50 @@ void ChargePoint::handle_get_local_authorization_list_version_req(Call<GetLocalL
this->send<GetLocalListVersionResponse>(call_result);
}

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->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<int>(ControllerComponentVariables::ClientCertificateExpireCheckIntervalSeconds)
.value_or(12 * 60 * 60)));
}

void ChargePoint::scheduled_check_v2g_certificate_expiration() {
if (this->device_model->get_optional_value<bool>(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<bool>(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<int>(ControllerComponentVariables::V2GCertificateExpireCheckIntervalSeconds)
.value_or(12 * 60 * 60)));
}

} // namespace v201
} // namespace ocpp
28 changes: 28 additions & 0 deletions lib/ocpp/v201/ctrlr_component_variables.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,34 @@ const ComponentVariable& MaxCustomerInformationDataLength = {
"MaxCustomerInformationDataLength",
}),
};
const ComponentVariable& V2GCertificateExpireCheckInitialDelaySeconds = {
ControllerComponents::InternalCtrlr,
std::nullopt,
std::optional<Variable>({
"V2GCertificateExpireCheckInitialDelaySeconds",
}),
};
const ComponentVariable& V2GCertificateExpireCheckIntervalSeconds = {
ControllerComponents::InternalCtrlr,
std::nullopt,
std::optional<Variable>({
"V2GCertificateExpireCheckIntervalSeconds",
}),
};
const ComponentVariable& ClientCertificateExpireCheckInitialDelaySeconds = {
ControllerComponents::InternalCtrlr,
std::nullopt,
std::optional<Variable>({
"ClientCertificateExpireCheckInitialDelaySeconds",
}),
};
const ComponentVariable& ClientCertificateExpireCheckIntervalSeconds = {
ControllerComponents::InternalCtrlr,
std::nullopt,
std::optional<Variable>({
"ClientCertificateExpireCheckIntervalSeconds",
}),
};
const ComponentVariable& AlignedDataCtrlrEnabled = {
ControllerComponents::AlignedDataCtrlr,
std::nullopt,
Expand Down