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

validate CSMS-URL: opt. schema must fit with security-profile; chargepoint-ID deprecated in URL #201

Merged
merged 52 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
d590bae
new `helpers::URIAppendPath()`
Dominik-K Sep 28, 2023
28a3d52
append chargepoint ID to CSMS-URI
Dominik-K Sep 28, 2023
ad9f9dc
rename `cs_uri` to `csms_uri`
Dominik-K Sep 28, 2023
ed1495c
fix in `websocket_plain.cpp`; consolidate both to avoid copy and past…
Dominik-K Sep 28, 2023
ec0d20d
move `Uri`-struct to `websocket_common.cpp`
Dominik-K Sep 29, 2023
5a7103f
v201: use `SecurityCtrlrIdentity` instead `ChargePointId`
Dominik-K Oct 4, 2023
54e4f1c
Merge branch 'main' into fix/websockets/CP-ID
Dominik-K Oct 4, 2023
6062783
merge fix
Dominik-K Oct 4, 2023
f2d05dd
use `set_path()`, use `websocketpp` with workaround
Dominik-K Oct 13, 2023
fb54d8f
transfer `uri` around to avoid re-parsing
Dominik-K Oct 16, 2023
d4323ed
rename to `websocket_uri.*pp`
Dominik-K Oct 18, 2023
977c6d1
reworked with static `Uri::parse_from_string()`
Dominik-K Oct 18, 2023
88b8abd
use `set_connection_options()` generically
Dominik-K Oct 18, 2023
606a26b
`find . -iname '*.*pp' | xargs clang-format -i`
Dominik-K Oct 18, 2023
3143cba
fix linter issues
Dominik-K Oct 18, 2023
bbfafbf
Codacy & own PR-reviews
Dominik-K Oct 19, 2023
8b5b665
`clang-format` again :-(
Dominik-K Oct 19, 2023
79994bb
make Codacy happy again!?!
Dominik-K Oct 19, 2023
1651642
Merge branch 'main' into fix/websockets/CP-ID
Dominik-K Oct 23, 2023
f2f3f0a
remove `typedef Chargepoint`
Dominik-K Oct 23, 2023
534ae87
`WebsocketConnectionOptions.Uri`
Dominik-K Oct 23, 2023
7fb19ca
init `Uri` before adding to `WebsocketConnectionOptions`
Dominik-K Oct 23, 2023
1008d05
run `set_connection_options()` in `Websocket...` constructor
Dominik-K Oct 23, 2023
2ceec0d
single source for `chargepoint_id`
Dominik-K Oct 23, 2023
a4e1cca
check against security-profile at `set_connection_options()`
Dominik-K Oct 23, 2023
161ee4e
check conforming URI-scheme and security-profile
Dominik-K Oct 23, 2023
76aa1e7
make `clang-format` happy again
Dominik-K Oct 23, 2023
87ef3ef
fixes from review
Dominik-K Oct 27, 2023
afd502a
Merge branch 'fix/websockets/CP-ID' into websockets_options_URI
Dominik-K Oct 30, 2023
6f1e600
rename to singural form; TODO for using `SecurityProfile`-type
Dominik-K Oct 30, 2023
1250380
Merge branch 'main' into fix/websockets/CP-ID
Dominik-K Oct 31, 2023
acd942b
Merge branch 'fix/websockets/CP-ID' into websockets_options_URI
Dominik-K Oct 31, 2023
a919540
WIP: check CP-ID against path-base instead of full path
Dominik-K Oct 31, 2023
3bd6d62
remove CP-ID from path if last segment
Dominik-K Nov 2, 2023
8b8e269
WIP: check OCPP1.6 with security-profile=1
Dominik-K Nov 2, 2023
fbfcae7
remove optional last slash
Dominik-K Nov 3, 2023
39ddcc4
Merge branch 'main' into fix/websockets/CP-ID
Dominik-K Nov 6, 2023
bf9bdc6
fix correct path splitting
Dominik-K Nov 6, 2023
f5f0091
fix fallthrough in switch
Dominik-K Nov 6, 2023
75a1705
1.6: connection to SteVe working
Dominik-K Nov 6, 2023
fe08ff2
path normalization
Dominik-K Nov 7, 2023
30e81e2
Merge branch 'main' into fix/websockets/CP-ID
Dominik-K Nov 7, 2023
3af51e3
(merge) fixes
Dominik-K Nov 7, 2023
e9263b1
make clang-format happy again
Dominik-K Nov 8, 2023
11c0a93
re-format touched JSONs with new `.editorconfig`
Dominik-K Nov 8, 2023
2bfa19e
fix doubled `last_segment`
Dominik-K Nov 8, 2023
151dcd6
Merge branch 'main' into fix/websockets/CP-ID
Dominik-K Nov 9, 2023
2cb761d
use Doxygen comments
Dominik-K Nov 9, 2023
ae3922a
allow `SecurityProfile = 0` for OCPP 1.6
Dominik-K Nov 9, 2023
5375b55
require `SecurityCtrlr.Identity`
Dominik-K Nov 9, 2023
5295347
Merge branch 'main' into fix/websockets/CP-ID
Dominik-K Nov 9, 2023
bcc88df
make `clang-format` happy again
Dominik-K Nov 9, 2023
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
6 changes: 6 additions & 0 deletions config/v201/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,12 @@
"Actual": "1"
}
},
"Identity": {
"variable_name": "Identity",
"attributes": {
"Actual": "cp001"
}
},
"BasicAuthPassword": {
"variable_name": "BasicAuthPassword",
"attributes": {
Expand Down
13 changes: 7 additions & 6 deletions include/ocpp/common/websocket/websocket_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,17 @@
#include <thread>

#include <everest/timer.hpp>

#include <ocpp/common/types.hpp>

#include <websocketpp/client.hpp>
#include <websocketpp/config/asio_client.hpp>

#include <ocpp/common/types.hpp>
#include <ocpp/common/websocket/websocket_uri.hpp>

namespace ocpp {

struct WebsocketConnectionOptions {
OcppProtocolVersion ocpp_version;
std::string cs_uri;
std::string csms_uri; // the URI of the CSMS
int security_profile;
std::string chargepoint_id;
std::optional<std::string> authorization_key;
Expand Down Expand Up @@ -49,7 +49,7 @@ class WebsocketBase {
std::function<void(const std::string& message)> message_callback;
websocketpp::lib::shared_ptr<boost::asio::steady_timer> reconnect_timer;
std::unique_ptr<Everest::SteadyTimer> ping_timer;
std::string uri;
ocpp::Uri uri; // to be set in derived classes
websocketpp::connection_hdl handle;
std::mutex reconnect_mutex;
std::mutex connection_mutex;
Expand Down Expand Up @@ -92,7 +92,8 @@ class WebsocketBase {
virtual bool connect() = 0;

/// \brief sets this connection_options to the given \p connection_options and resets the connection_attempts
void set_connection_options(const WebsocketConnectionOptions& connection_options);
virtual void set_connection_options(const WebsocketConnectionOptions& connection_options) = 0;
void set_connection_options_base(const WebsocketConnectionOptions& connection_options);

/// \brief reconnect the websocket after the delay
virtual void reconnect(std::error_code reason, long delay) = 0;
Expand Down
2 changes: 2 additions & 0 deletions include/ocpp/common/websocket/websocket_plain.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ class WebsocketPlain final : public WebsocketBase {
/// \brief Called when a plaintext websocket connection fails to be established
void on_fail_plain(client* c, websocketpp::connection_hdl hdl);

void set_connection_options(const WebsocketConnectionOptions& connection_options) override;

public:
/// \brief Creates a new WebsocketPlain object with the providede \p connection_options
explicit WebsocketPlain(const WebsocketConnectionOptions& connection_options);
Expand Down
9 changes: 3 additions & 6 deletions include/ocpp/common/websocket/websocket_tls.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <websocketpp/client.hpp>
#include <websocketpp/config/asio_client.hpp>

#include <ocpp/common/evse_security.hpp>
#include <ocpp/common/websocket/websocket_base.hpp>

namespace ocpp {
Expand All @@ -25,12 +26,6 @@ class WebsocketTLS final : public WebsocketBase {
tls_client wss_client;
std::shared_ptr<EvseSecurity> evse_security;
websocketpp::lib::shared_ptr<websocketpp::lib::thread> websocket_thread;

/// \brief Extracts the hostname from the provided \p uri
/// FIXME(kai): this only works with a very limited subset of hostnames and should be extended to work spec conform
/// \returns the extracted hostname
std::string get_hostname(std::string uri);

/// \brief Called when a TLS websocket connection gets initialized, manages the supported TLS versions, cipher lists
/// 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);
Expand All @@ -50,6 +45,8 @@ class WebsocketTLS final : public WebsocketBase {
/// \brief Called when a TLS websocket connection fails to be established
void on_fail_tls(tls_client* c, websocketpp::connection_hdl hdl);

void set_connection_options(const WebsocketConnectionOptions& connection_options) override;

public:
/// \brief Creates a new Websocket object with the providede \p connection_options
explicit WebsocketTLS(const WebsocketConnectionOptions& connection_options,
Expand Down
53 changes: 53 additions & 0 deletions include/ocpp/common/websocket/websocket_uri.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#ifndef OCPP_WEBSOCKET_URI_HPP
#define OCPP_WEBSOCKET_URI_HPP

#include <ocpp/types/simple.hpp>
Dominik-K marked this conversation as resolved.
Show resolved Hide resolved
#include <string>
#include <websocketpp/uri.hpp>

namespace ocpp {

class Uri {
public:
Uri(){};

void init(bool secure, std::string host, int port, std::string path); // TODO should return an error if invalid
Dominik-K marked this conversation as resolved.
Show resolved Hide resolved

// if a `chargepoint_id` is given, it will be checked that it is the same as the one in the URI-path, if set
// if invalid, it throws `invalid_argument` exception
static Uri parse_from_string(std::string const& uri,
ChargepointId chargepoint_id); // TODO should return an `expected<>`

void set_secure(bool secure) {
this->secure = secure;
}

std::string get_hostname() {
return this->host;
}

std::string string() {
auto uri = get_websocketpp_uri();
return uri.str();
}

websocketpp::uri get_websocketpp_uri() { // FIXME: wrap needed `websocketpp:uri` functionality inside `Uri`
return websocketpp::uri(this->secure, this->host, this->port, this->path);
}

private:
Uri(bool secure, std::string host, uint16_t port, std::string path) :
secure(secure), host(host), port(port), path(path) {
}

bool secure;
std::string host;
uint16_t port;
std::string path;
};

} // namespace ocpp

#endif // OCPP_WEBSOCKET_URI_HPP */
11 changes: 11 additions & 0 deletions include/ocpp/types/simple.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest

#ifndef OCPP_TYPES_SIMPLE_HPP
#define OCPP_TYPES_SIMPLE_HPP

#include <string>

typedef std::string ChargepointId;

#endif // OCPP_TYPES_SIMPLE_HPP
1 change: 1 addition & 0 deletions lib/ocpp/common/websocket/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
target_sources(ocpp
PRIVATE
websocket_base.cpp
websocket_uri.cpp
websocket_plain.cpp
websocket_tls.cpp
websocket.cpp
Expand Down
2 changes: 0 additions & 2 deletions lib/ocpp/common/websocket/websocket.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ Websocket::Websocket(const WebsocketConnectionOptions& connection_options, std::
std::shared_ptr<MessageLogging> logging) :
logging(logging) {
if (connection_options.security_profile <= 1) {
EVLOG_debug << "Creating plaintext websocket based on the provided URI: " << connection_options.cs_uri;
this->websocket = std::make_unique<WebsocketPlain>(connection_options);
} else if (connection_options.security_profile >= 2) {
EVLOG_debug << "Creating TLS websocket based on the provided URI: " << connection_options.cs_uri;
this->websocket = std::make_unique<WebsocketTLS>(connection_options, evse_security);
}
}
Expand Down
4 changes: 3 additions & 1 deletion lib/ocpp/common/websocket/websocket_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ WebsocketBase::WebsocketBase(const WebsocketConnectionOptions& connection_option
WebsocketBase::~WebsocketBase() {
}

void WebsocketBase::set_connection_options(const WebsocketConnectionOptions& connection_options) {
void WebsocketBase::set_connection_options_base(const WebsocketConnectionOptions& connection_options) {
this->uri = Uri::parse_from_string(connection_options.csms_uri, connection_options.chargepoint_id);

this->connection_attempts = 0;
this->connection_options = connection_options;
}
Expand Down
19 changes: 14 additions & 5 deletions lib/ocpp/common/websocket/websocket_plain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include <everest/logging.hpp>

#include <memory>
#include <ocpp/common/websocket/websocket_plain.hpp>

#include <boost/optional/optional.hpp>
Expand All @@ -10,27 +11,35 @@ namespace ocpp {

WebsocketPlain::WebsocketPlain(const WebsocketConnectionOptions& connection_options) :
WebsocketBase(connection_options) {

set_connection_options(connection_options);

EVLOG_debug << "Initialised WebsocketPlain with URI: " << this->uri.string();
}

void WebsocketPlain::set_connection_options(const WebsocketConnectionOptions& connection_options) {
set_connection_options_base(connection_options); // initialises this->uri, too

this->uri.set_secure(false);
}

bool WebsocketPlain::connect() {
if (!this->initialized()) {
return false;
}
const auto uri = this->connection_options.cs_uri.insert(0, "ws://");

EVLOG_info << "Connecting to plain websocket at uri: " << uri
EVLOG_info << "Connecting to plain websocket at uri: " << this->uri.string()
<< " with profile: " << this->connection_options.security_profile;

this->ws_client.clear_access_channels(websocketpp::log::alevel::all);
this->ws_client.clear_error_channels(websocketpp::log::elevel::all);
this->ws_client.init_asio();
this->ws_client.start_perpetual();
this->uri = uri;

websocket_thread.reset(new websocketpp::lib::thread(&client::run, &this->ws_client));

this->reconnect_callback = [this](const websocketpp::lib::error_code& ec) {
EVLOG_info << "Reconnecting to plain websocket at uri: " << this->connection_options.cs_uri
EVLOG_info << "Reconnecting to plain websocket at uri: " << this->connection_options.csms_uri
Dominik-K marked this conversation as resolved.
Show resolved Hide resolved
<< " with profile: " << this->connection_options.security_profile;

// close connection before reconnecting
Expand Down Expand Up @@ -120,7 +129,7 @@ void WebsocketPlain::connect_plain() {

websocketpp::lib::error_code ec;

client::connection_ptr con = this->ws_client.get_connection(this->uri, ec);
const client::connection_ptr con = this->ws_client.get_connection(std::make_shared<websocketpp::uri>(this->uri.get_websocketpp_uri()), ec);

if (ec) {
EVLOG_error << "Connection initialization error for plain websocket: " << ec.message();
Expand Down
43 changes: 19 additions & 24 deletions lib/ocpp/common/websocket/websocket_tls.cpp
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest
#include <boost/optional/optional.hpp>

#include <everest/logging.hpp>
#include <memory>

#include <ocpp/common/evse_security.hpp>
#include <ocpp/common/websocket/websocket_uri.hpp>
#include <ocpp/common/websocket/websocket_tls.hpp>

#include <everest/logging.hpp>

namespace ocpp {

WebsocketTLS::WebsocketTLS(const WebsocketConnectionOptions& connection_options,
std::shared_ptr<EvseSecurity> evse_security) :
WebsocketBase(connection_options), evse_security(evse_security) {

EVLOG_debug << "Initialised WebsocketTLS with URI: " << this->uri.string();
}

void WebsocketTLS::set_connection_options(const WebsocketConnectionOptions& connection_options) {
set_connection_options_base(connection_options); // initialises this->uri, too

this->uri.set_secure(true);
}

bool WebsocketTLS::connect() {
if (!this->initialized()) {
return false;
}
this->uri = this->connection_options.cs_uri.insert(0, "wss://");
EVLOG_info << "Connecting TLS websocket to uri: " << this->uri << " with profile "
<< this->connection_options.security_profile;

EVLOG_info << "Connecting TLS websocket to uri: " << this->uri.string()
<< " with security-profile " << this->connection_options.security_profile;

this->wss_client.clear_access_channels(websocketpp::log::alevel::all);
this->wss_client.clear_error_channels(websocketpp::log::elevel::all);
this->wss_client.init_asio();
this->wss_client.start_perpetual();
websocket_thread.reset(new websocketpp::lib::thread(&tls_client::run, &this->wss_client));

this->wss_client.set_tls_init_handler(
websocketpp::lib::bind(&WebsocketTLS::on_tls_init, this, this->get_hostname(this->uri),
websocketpp::lib::bind(&WebsocketTLS::on_tls_init, this, this->uri.get_hostname(),
websocketpp::lib::placeholders::_1, this->connection_options.security_profile));

this->reconnect_callback = [this](const websocketpp::lib::error_code& ec) {
EVLOG_info << "Reconnecting to TLS websocket at uri: " << this->connection_options.cs_uri
EVLOG_info << "Reconnecting to TLS websocket at uri: " << this->connection_options.csms_uri
<< " with profile: " << this->connection_options.security_profile;

// close connection before reconnecting
Expand Down Expand Up @@ -117,22 +128,6 @@ void WebsocketTLS::reconnect(std::error_code reason, long delay) {
// https://github.com/zaphoyd/websocketpp/blob/master/websocketpp/close.hpp
}

std::string WebsocketTLS::get_hostname(std::string uri) {
// FIXME(kai): This only works with a very limited subset of hostnames!
std::string start = "wss://";
std::string stop = "/";
std::string port = ":";
auto hostname_start_pos = start.length();
auto hostname_end_pos = uri.find_first_of(stop, hostname_start_pos);

auto hostname_with_port = uri.substr(hostname_start_pos, hostname_end_pos - hostname_start_pos);
auto port_pos = hostname_with_port.find_first_of(port);
if (port_pos != std::string::npos) {
return hostname_with_port.substr(0, port_pos);
}
return hostname_with_port;
}

tls_context WebsocketTLS::on_tls_init(std::string hostname, websocketpp::connection_hdl hdl, int32_t security_profile) {
tls_context context = websocketpp::lib::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::sslv23);

Expand Down Expand Up @@ -212,7 +207,7 @@ tls_context WebsocketTLS::on_tls_init(std::string hostname, websocketpp::connect
void WebsocketTLS::connect_tls() {
websocketpp::lib::error_code ec;

tls_client::connection_ptr con = this->wss_client.get_connection(this->uri, ec);
const tls_client::connection_ptr con = this->wss_client.get_connection(std::make_shared<websocketpp::uri>(this->uri.get_websocketpp_uri()), ec);

if (ec) {
EVLOG_error << "Connection initialization error for TLS websocket: " << ec.message();
Expand Down
36 changes: 36 additions & 0 deletions lib/ocpp/common/websocket/websocket_uri.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 Pionix GmbH and Contributors to EVerest

#include "ocpp/types/simple.hpp"
Dominik-K marked this conversation as resolved.
Show resolved Hide resolved
#include <ocpp/common/websocket/websocket_uri.hpp>

#include <boost/algorithm/string/predicate.hpp>

#include <stdexcept>
#include <string>
#include <websocketpp/uri.hpp>

namespace ocpp {

Uri Uri::parse_from_string(std::string const& uri, ChargepointId chargepoint_id) {
auto uri_temp = websocketpp::uri(uri);

if (!uri_temp.get_valid()) {
throw std::invalid_argument("Uri constructor: given `uri` is invalid");
}

const auto& hostname = uri_temp.get_host();
Dominik-K marked this conversation as resolved.
Show resolved Hide resolved
if (!hostname.empty()) {
if (hostname != chargepoint_id) {
throw std::invalid_argument("Uri constructor: the chargepoint-ID in the `uri`-path is different to the defined one");
}

chargepoint_id = hostname;
}

auto uri_return = Uri(uri_temp.get_secure(), uri_temp.get_host(), uri_temp.get_port(), uri_temp.get_resource());

return uri_return;
}

} // namespace ocpp
Loading