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 InvalidCsmsCertificate notification #419

Merged
merged 4 commits into from
Jan 30, 2024
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
3 changes: 3 additions & 0 deletions include/ocpp/common/websocket/websocket.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ class Websocket {
/// \brief register a \p callback that is called when the websocket receives a message
void register_message_callback(const std::function<void(const std::string& message)>& callback);

/// \brief register a \p callback that is called when the websocket could not connect with a specific reason
void register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback);

/// \brief send a \p message over the websocket
/// \returns true if the message was sent successfully
bool send(const std::string& message);
Expand Down
8 changes: 8 additions & 0 deletions include/ocpp/common/websocket/websocket_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ struct WebsocketConnectionOptions {
bool use_tpm_tls;
};

enum class ConnectionFailedReason {
InvalidCSMSCertificate = 0,
};

///
/// \brief contains a websocket abstraction
///
Expand All @@ -49,6 +53,7 @@ class WebsocketBase {
std::function<void()> disconnected_callback;
std::function<void(const websocketpp::close::status::value reason)> closed_callback;
std::function<void(const std::string& message)> message_callback;
std::function<void(ConnectionFailedReason)> connection_failed_callback;
websocketpp::lib::shared_ptr<boost::asio::steady_timer> reconnect_timer;
std::unique_ptr<Everest::SteadyTimer> ping_timer;
websocketpp::connection_hdl handle;
Expand Down Expand Up @@ -122,6 +127,9 @@ class WebsocketBase {
/// \brief register a \p callback that is called when the websocket receives a message
void register_message_callback(const std::function<void(const std::string& message)>& callback);

/// \brief register a \p callback that is called when the websocket could not connect with a specific reason
void register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback);

/// \brief send a \p message over the websocket
/// \returns true if the message was sent successfully
virtual bool send(const std::string& message) = 0;
Expand Down
3 changes: 3 additions & 0 deletions include/ocpp/common/websocket/websocket_tls.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class WebsocketTLS final : public WebsocketBase {
/// and how verification of the server certificate is handled
tls_context on_tls_init(std::string hostname, websocketpp::connection_hdl hdl, int32_t security_profile);

/// \brief Verify that the csms certificate's commonName matches the CSMS FQDN
bool verify_csms_cn(const std::string& hostname, bool preverified, boost::asio::ssl::verify_context& ctx);

/// \brief Connect to a TLS websocket
void connect_tls();

Expand Down
1 change: 1 addition & 0 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@
BootReasonEnum bootreason;
int network_configuration_priority;
bool disable_automatic_websocket_reconnects;
bool skip_invalid_csms_certificate_notifications;

Check notice on line 217 in include/ocpp/v201/charge_point.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/v201/charge_point.hpp#L217

class member 'ChargePoint::skip_invalid_csms_certificate_notifications' is never used.

/// \brief Component responsible for maintaining and persisting the operational status of CS, EVSEs, and connectors.
std::shared_ptr<ComponentStateManager> component_state_manager;
Expand Down
4 changes: 4 additions & 0 deletions lib/ocpp/common/websocket/websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ void Websocket::register_message_callback(const std::function<void(const std::st
this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); });
}

void Websocket::register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback) {
this->websocket->register_connection_failed_callback(callback);
}

bool Websocket::send(const std::string& message) {
this->logging->charge_point("Unknown", message);
return this->websocket->send(message);
Expand Down
4 changes: 4 additions & 0 deletions lib/ocpp/common/websocket/websocket_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ void WebsocketBase::register_message_callback(const std::function<void(const std
this->message_callback = callback;
}

void WebsocketBase::register_connection_failed_callback(const std::function<void(ConnectionFailedReason)>& callback) {
this->connection_failed_callback = callback;
}

bool WebsocketBase::initialized() {
if (this->connected_callback == nullptr) {
EVLOG_error << "Not properly initialized: please register connected callback.";
Expand Down
15 changes: 9 additions & 6 deletions lib/ocpp/common/websocket/websocket_tls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ static std::vector<std::string> get_subject_alt_names(const X509* x509) {
return list;
}

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

// Error depth gives the depth in the chain (with 0 = leaf certificate) where
// a potential (!) error occurred; error here means current error code and can also be "OK".
Expand All @@ -59,6 +59,8 @@ bool verify_csms_cn(const std::string& hostname, bool preverified, boost::asio::
int error = X509_STORE_CTX_get_error(ctx.native_handle());
EVLOG_warning << "Invalid certificate error '" << X509_verify_cert_error_string(error) << "' (at chain depth '"
<< depth << "')";

this->connection_failed_callback(ConnectionFailedReason::InvalidCSMSCertificate);
}

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

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

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

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

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

// TODO(piet): Trigger SecurityEvent in case InvalidCentralSystemCertificate

// -1 indicates to always attempt to reconnect
if (this->connection_options.max_connection_attempts == -1 or
this->connection_attempts <= this->connection_options.max_connection_attempts) {
Expand Down
18 changes: 18 additions & 0 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ ChargePoint::ChargePoint(const std::map<int32_t, int32_t>& evse_connector_struct
registration_status(RegistrationStatusEnum::Rejected),
network_configuration_priority(0),
disable_automatic_websocket_reconnects(false),
skip_invalid_csms_certificate_notifications(false),
reset_scheduled(false),
reset_scheduled_evseids{},
firmware_status(FirmwareStatusEnum::Idle),
Expand Down Expand Up @@ -713,6 +714,9 @@ 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>();

// We have a connection again so next time it fails we should send the notification again
this->skip_invalid_csms_certificate_notifications = false;
});

this->websocket->register_disconnected_callback([this]() {
Expand Down Expand Up @@ -746,6 +750,20 @@ void ChargePoint::init_websocket() {
}
});

this->websocket->register_connection_failed_callback([this](ConnectionFailedReason reason) {
switch (reason) {
case ConnectionFailedReason::InvalidCSMSCertificate:
if (!this->skip_invalid_csms_certificate_notifications) {
this->security_event_notification_req(CiString<50>(ocpp::security_events::INVALIDCSMSCERTIFICATE),
std::nullopt, true, true);
this->skip_invalid_csms_certificate_notifications = true;
} else {
EVLOG_debug << "Skipping InvalidCsmsCertificate SecurityEvent since it has been sent already";
}
break;
}
});

this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); });
}

Expand Down
Loading