Skip to content

Commit ac0538e

Browse files
Add InvalidCsmsCertificate notification (#419)
* Added InvalidCsmsCertificate notification --------- Signed-off-by: Marc Emmers <m.emmers@alfen.com> Signed-off-by: Kai-Uwe Hermann <kai-uwe.hermann@pionix.de> Co-authored-by: Kai-Uwe Hermann <kai-uwe.hermann@pionix.de>
1 parent 84b604a commit ac0538e

File tree

8 files changed

+50
-6
lines changed

8 files changed

+50
-6
lines changed

include/ocpp/common/websocket/websocket.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ class Websocket {
5555
/// \brief register a \p callback that is called when the websocket receives a message
5656
void register_message_callback(const std::function<void(const std::string& message)>& callback);
5757

58+
/// \brief register a \p callback that is called when the websocket could not connect with a specific reason
59+
void register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback);
60+
5861
/// \brief send a \p message over the websocket
5962
/// \returns true if the message was sent successfully
6063
bool send(const std::string& message);

include/ocpp/common/websocket/websocket_base.hpp

+8
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ struct WebsocketConnectionOptions {
3838
bool use_tpm_tls;
3939
};
4040

41+
enum class ConnectionFailedReason {
42+
InvalidCSMSCertificate = 0,
43+
};
44+
4145
///
4246
/// \brief contains a websocket abstraction
4347
///
@@ -49,6 +53,7 @@ class WebsocketBase {
4953
std::function<void()> disconnected_callback;
5054
std::function<void(const websocketpp::close::status::value reason)> closed_callback;
5155
std::function<void(const std::string& message)> message_callback;
56+
std::function<void(ConnectionFailedReason)> connection_failed_callback;
5257
websocketpp::lib::shared_ptr<boost::asio::steady_timer> reconnect_timer;
5358
std::unique_ptr<Everest::SteadyTimer> ping_timer;
5459
websocketpp::connection_hdl handle;
@@ -122,6 +127,9 @@ class WebsocketBase {
122127
/// \brief register a \p callback that is called when the websocket receives a message
123128
void register_message_callback(const std::function<void(const std::string& message)>& callback);
124129

130+
/// \brief register a \p callback that is called when the websocket could not connect with a specific reason
131+
void register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback);
132+
125133
/// \brief send a \p message over the websocket
126134
/// \returns true if the message was sent successfully
127135
virtual bool send(const std::string& message) = 0;

include/ocpp/common/websocket/websocket_tls.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ class WebsocketTLS final : public WebsocketBase {
3030
/// and how verification of the server certificate is handled
3131
tls_context on_tls_init(std::string hostname, websocketpp::connection_hdl hdl, int32_t security_profile);
3232

33+
/// \brief Verify that the csms certificate's commonName matches the CSMS FQDN
34+
bool verify_csms_cn(const std::string& hostname, bool preverified, boost::asio::ssl::verify_context& ctx);
35+
3336
/// \brief Connect to a TLS websocket
3437
void connect_tls();
3538

include/ocpp/v201/charge_point.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ class ChargePoint : ocpp::ChargingStationBase {
214214
BootReasonEnum bootreason;
215215
int network_configuration_priority;
216216
bool disable_automatic_websocket_reconnects;
217+
bool skip_invalid_csms_certificate_notifications;
217218

218219
/// \brief Component responsible for maintaining and persisting the operational status of CS, EVSEs, and connectors.
219220
std::shared_ptr<ComponentStateManager> component_state_manager;

lib/ocpp/common/websocket/websocket.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ void Websocket::register_message_callback(const std::function<void(const std::st
8787
this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); });
8888
}
8989

90+
void Websocket::register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback) {
91+
this->websocket->register_connection_failed_callback(callback);
92+
}
93+
9094
bool Websocket::send(const std::string& message) {
9195
this->logging->charge_point("Unknown", message);
9296
return this->websocket->send(message);

lib/ocpp/common/websocket/websocket_base.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ void WebsocketBase::register_message_callback(const std::function<void(const std
5151
this->message_callback = callback;
5252
}
5353

54+
void WebsocketBase::register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback) {
55+
this->connection_failed_callback = callback;
56+
}
57+
5458
bool WebsocketBase::initialized() {
5559
if (this->connected_callback == nullptr) {
5660
EVLOG_error << "Not properly initialized: please register connected callback.";

lib/ocpp/common/websocket/websocket_tls.cpp

+9-6
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@ static std::vector<std::string> get_subject_alt_names(const X509* x509) {
4545
return list;
4646
}
4747

48-
// verify that the csms certificate's commonName matches the CSMS FQDN
49-
bool verify_csms_cn(const std::string& hostname, bool preverified, boost::asio::ssl::verify_context& ctx) {
48+
bool WebsocketTLS::verify_csms_cn(const std::string& hostname, bool preverified,
49+
boost::asio::ssl::verify_context& ctx) {
5050

5151
// Error depth gives the depth in the chain (with 0 = leaf certificate) where
5252
// a potential (!) error occurred; error here means current error code and can also be "OK".
@@ -59,6 +59,8 @@ bool verify_csms_cn(const std::string& hostname, bool preverified, boost::asio::
5959
int error = X509_STORE_CTX_get_error(ctx.native_handle());
6060
EVLOG_warning << "Invalid certificate error '" << X509_verify_cert_error_string(error) << "' (at chain depth '"
6161
<< depth << "')";
62+
63+
this->connection_failed_callback(ConnectionFailedReason::InvalidCSMSCertificate);
6264
}
6365

6466
// only check for CSMS server certificate
@@ -71,6 +73,7 @@ bool verify_csms_cn(const std::string& hostname, bool preverified, boost::asio::
7173
char common_name[256];
7274
if (X509_NAME_get_text_by_NID(subject_name, NID_commonName, common_name, sizeof(common_name)) <= 0) {
7375
EVLOG_error << "Could not extract CN from CSMS server certificate";
76+
this->connection_failed_callback(ConnectionFailedReason::InvalidCSMSCertificate);
7477
return false;
7578
}
7679

@@ -96,6 +99,7 @@ bool verify_csms_cn(const std::string& hostname, bool preverified, boost::asio::
9699
s << " '" << alt_name << "'";
97100
}
98101
EVLOG_warning << s.str();
102+
this->connection_failed_callback(ConnectionFailedReason::InvalidCSMSCertificate);
99103
return false;
100104
}
101105

@@ -289,8 +293,9 @@ tls_context WebsocketTLS::on_tls_init(std::string hostname, websocketpp::connect
289293

290294
context->set_verify_mode(boost::asio::ssl::verify_peer);
291295
if (this->connection_options.verify_csms_common_name) {
292-
context->set_verify_callback(websocketpp::lib::bind(
293-
&verify_csms_cn, hostname, websocketpp::lib::placeholders::_1, websocketpp::lib::placeholders::_2));
296+
context->set_verify_callback([this, hostname](bool preverified, boost::asio::ssl::verify_context& ctx) {
297+
return this->verify_csms_cn(hostname, preverified, ctx);
298+
});
294299

295300
} else {
296301
EVLOG_warning << "Not verifying the CSMS certificates commonName with the Fully Qualified Domain Name "
@@ -424,8 +429,6 @@ void WebsocketTLS::on_fail_tls(tls_client* c, websocketpp::connection_hdl hdl) {
424429
const auto ec = con->get_ec();
425430
this->log_on_fail(ec, con->get_transport_ec(), con->get_response_code());
426431

427-
// TODO(piet): Trigger SecurityEvent in case InvalidCentralSystemCertificate
428-
429432
// -1 indicates to always attempt to reconnect
430433
if (this->connection_options.max_connection_attempts == -1 or
431434
this->connection_attempts <= this->connection_options.max_connection_attempts) {

lib/ocpp/v201/charge_point.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ ChargePoint::ChargePoint(const std::map<int32_t, int32_t>& evse_connector_struct
5959
registration_status(RegistrationStatusEnum::Rejected),
6060
network_configuration_priority(0),
6161
disable_automatic_websocket_reconnects(false),
62+
skip_invalid_csms_certificate_notifications(false),
6263
reset_scheduled(false),
6364
reset_scheduled_evseids{},
6465
firmware_status(FirmwareStatusEnum::Idle),
@@ -713,6 +714,9 @@ void ChargePoint::init_websocket() {
713714
this->init_certificate_expiration_check_timers(); // re-init as timers are stopped on disconnect
714715
}
715716
this->time_disconnected = std::chrono::time_point<std::chrono::steady_clock>();
717+
718+
// We have a connection again so next time it fails we should send the notification again
719+
this->skip_invalid_csms_certificate_notifications = false;
716720
});
717721

718722
this->websocket->register_disconnected_callback([this]() {
@@ -746,6 +750,20 @@ void ChargePoint::init_websocket() {
746750
}
747751
});
748752

753+
this->websocket->register_connection_failed_callback([this](ConnectionFailedReason reason) {
754+
switch (reason) {
755+
case ConnectionFailedReason::InvalidCSMSCertificate:
756+
if (!this->skip_invalid_csms_certificate_notifications) {
757+
this->security_event_notification_req(CiString<50>(ocpp::security_events::INVALIDCSMSCERTIFICATE),
758+
std::nullopt, true, true);
759+
this->skip_invalid_csms_certificate_notifications = true;
760+
} else {
761+
EVLOG_debug << "Skipping InvalidCsmsCertificate SecurityEvent since it has been sent already";
762+
}
763+
break;
764+
}
765+
});
766+
749767
this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); });
750768
}
751769

0 commit comments

Comments
 (0)