diff --git a/include/ocpp/common/websocket/websocket_tls_tpm.hpp b/include/ocpp/common/websocket/websocket_tls_tpm.hpp index 655f0b146..63c50f9a1 100644 --- a/include/ocpp/common/websocket/websocket_tls_tpm.hpp +++ b/include/ocpp/common/websocket/websocket_tls_tpm.hpp @@ -6,7 +6,13 @@ #include #include +#include +#include #include +#include + +struct ssl_ctx_st; + namespace ocpp { struct ConnectionData; @@ -46,7 +52,8 @@ class WebsocketTlsTPM final : public WebsocketBase { int process_callback(void* wsi_ptr, int callback_reason, void* user, void* in, size_t len); private: - void tls_init(); + void tls_init(struct ssl_ctx_st* ctx, const std::string& path_chain, const std::string& path_key, bool tpm_key, + std::optional& password); void client_loop(); void recv_loop(); diff --git a/lib/ocpp/common/websocket/websocket_tls_tpm.cpp b/lib/ocpp/common/websocket/websocket_tls_tpm.cpp index c584259c4..1f1a2672c 100644 --- a/lib/ocpp/common/websocket/websocket_tls_tpm.cpp +++ b/lib/ocpp/common/websocket/websocket_tls_tpm.cpp @@ -1,19 +1,27 @@ // SPDX-License-Identifier: Apache-2.0 // Copyright 2020 - 2023 Pionix GmbH and Contributors to EVerest +#include #include -#include - #include #include -#include -#include +#include #include #include #include +#include +#include +#define USING_OPENSSL_3 (OPENSSL_VERSION_NUMBER >= 0x30000000L) + +#if USING_OPENSSL_3 +#include +#else +#define SSL_CTX_new_ex(LIB, PROP, METHOD) SSL_CTX_new(METHOD) +#endif + template <> class std::default_delete { public: void operator()(lws_context* ptr) const { @@ -21,14 +29,6 @@ template <> class std::default_delete { } }; -template <> class std::default_delete { -public: - void operator()(OSSL_LIB_CTX* ptr) const { - ::OPENSSL_thread_stop_ex(ptr); - ::OSSL_LIB_CTX_free(ptr); - } -}; - template <> class std::default_delete { public: void operator()(SSL_CTX* ptr) const { @@ -38,6 +38,9 @@ template <> class std::default_delete { namespace ocpp { +using evse_security::is_tpm_key_filename; +using evse_security::OpenSSLProvider; + enum class EConnectionState { INITIALIZE, ///< Initialization state CONNECTING, ///< Trying to connect @@ -114,7 +117,6 @@ struct ConnectionData { // This public block will only be used from client loop thread, no locking needed // Openssl context, must be destroyed in this order std::unique_ptr sec_context; - std::unique_ptr sec_lib_context; // libwebsockets state std::unique_ptr lws_ctx; @@ -211,57 +213,8 @@ constexpr auto local_protocol_name = "lws-everest-client"; static const struct lws_protocols protocols[] = {{local_protocol_name, callback_minimal, 0, 0, 0, NULL, 0}, LWS_PROTOCOL_LIST_TERM}; -static void create_sec_context(bool use_tpm, OSSL_LIB_CTX*& out_libctx, SSL_CTX*& out_ctx) { - OSSL_LIB_CTX* libctx = OSSL_LIB_CTX_new(); - - if (libctx == nullptr) { - EVLOG_AND_THROW(std::runtime_error("Unable to create ssl lib ctx.")); - } - - out_libctx = libctx; - - if (use_tpm) { - OSSL_PROVIDER* prov_tpm2 = nullptr; - OSSL_PROVIDER* prov_default = nullptr; - - if ((prov_tpm2 = OSSL_PROVIDER_load(libctx, "tpm2")) == nullptr) { - EVLOG_AND_THROW(std::runtime_error("Could not load provider tpm2.")); - } - - if (!OSSL_PROVIDER_self_test(prov_tpm2)) { - EVLOG_AND_THROW(std::runtime_error("Could not self-test provider tpm2.")); - } - - if ((prov_default = OSSL_PROVIDER_load(libctx, "default")) == nullptr) { - EVLOG_AND_THROW(std::runtime_error("Could not load provider default.")); - } - - if (!OSSL_PROVIDER_self_test(prov_default)) { - EVLOG_AND_THROW(std::runtime_error("Could not self-test provider default.")); - } - } - - const SSL_METHOD* method = SSLv23_client_method(); - SSL_CTX* ctx = SSL_CTX_new_ex(libctx, nullptr, method); - - if (ctx == nullptr) { - EVLOG_AND_THROW(std::runtime_error("Unable to create ssl ctx.")); - } - - out_ctx = ctx; -} - -void WebsocketTlsTPM::tls_init() { - SSL_CTX* ctx = nullptr; - - if (auto* data = conn_data.get()) { - ctx = data->sec_context.get(); - } - - if (nullptr == ctx) { - EVLOG_AND_THROW(std::runtime_error("Invalid SSL context!")); - } - +void WebsocketTlsTPM::tls_init(SSL_CTX* ctx, const std::string& path_chain, const std::string& path_key, bool tpm_key, + std::optional& password) { auto rc = SSL_CTX_set_cipher_list(ctx, this->connection_options.supported_ciphers_12.c_str()); if (rc != 1) { EVLOG_debug << "SSL_CTX_set_cipher_list return value: " << rc; @@ -276,29 +229,25 @@ void WebsocketTlsTPM::tls_init() { SSL_CTX_set_ecdh_auto(ctx, 1); if (this->connection_options.security_profile == 3) { - const char* path_key = nullptr; - const char* path_chain = nullptr; - - const auto certificate_key_pair = - this->evse_security->get_key_pair(CertificateSigningUseEnum::ChargingStationCertificate); - - if (!certificate_key_pair.has_value()) { - EVLOG_AND_THROW(std::runtime_error( - "Connecting with security profile 3 but no client side certificate is present or valid")); + if ((path_chain.empty()) || (path_key.empty())) { + EVLOG_error << "Cert chain: " << path_chain << " key: " << path_key; + EVLOG_AND_THROW(std::runtime_error("No certificate and key for SSL")); } - path_chain = certificate_key_pair.value().certificate_path.c_str(); - path_key = certificate_key_pair.value().key_path.c_str(); - - if (1 != SSL_CTX_use_certificate_chain_file(ctx, path_chain)) { + if (1 != SSL_CTX_use_certificate_chain_file(ctx, path_chain.c_str())) { + ERR_print_errors_fp(stderr); EVLOG_AND_THROW(std::runtime_error("Could not use client certificate file within SSL context")); } - if (1 != SSL_CTX_use_PrivateKey_file(ctx, path_key, SSL_FILETYPE_PEM)) { + SSL_CTX_set_default_passwd_cb_userdata(ctx, reinterpret_cast(password.value_or("").data())); + + if (1 != SSL_CTX_use_PrivateKey_file(ctx, path_key.c_str(), SSL_FILETYPE_PEM)) { + ERR_print_errors_fp(stderr); EVLOG_AND_THROW(std::runtime_error("Could not set private key file within SSL context")); } if (false == SSL_CTX_check_private_key(ctx)) { + ERR_print_errors_fp(stderr); EVLOG_AND_THROW(std::runtime_error("Could not check private key within SSL context")); } } @@ -338,7 +287,7 @@ void WebsocketTlsTPM::recv_loop() { return; } - EVLOG_debug << "Init recv loop with ID: " << std::this_thread::get_id(); + EVLOG_debug << "Init recv loop with ID: " << std::hex << std::this_thread::get_id(); while (false == data->is_interupted()) { // Process all messages @@ -367,7 +316,7 @@ void WebsocketTlsTPM::recv_loop() { } } - EVLOG_debug << "Exit recv loop with ID: " << std::this_thread::get_id(); + EVLOG_debug << "Exit recv loop with ID: " << std::hex << std::this_thread::get_id(); } void WebsocketTlsTPM::client_loop() { @@ -398,24 +347,68 @@ void WebsocketTlsTPM::client_loop() { info.fd_limit_per_thread = 1 + 1 + 1; - bool use_tpm = connection_options.use_tpm_tls; + // Setup context - need to know the key type first - // Setup context - OSSL_LIB_CTX* lib_ctx; - SSL_CTX* ssl_ctx; + std::string path_key; + std::string path_chain; + std::optional password; - create_sec_context(use_tpm, lib_ctx, ssl_ctx); + if (this->connection_options.security_profile == 3) { - // Connection acquire the contexts - conn_data->sec_lib_context = std::unique_ptr(lib_ctx); - conn_data->sec_context = std::unique_ptr(ssl_ctx); + const auto certificate_key_pair = + this->evse_security->get_key_pair(CertificateSigningUseEnum::ChargingStationCertificate); + + if (!certificate_key_pair.has_value()) { + EVLOG_AND_THROW(std::runtime_error( + "Connecting with security profile 3 but no client side certificate is present or valid")); + } + + path_chain = certificate_key_pair.value().certificate_path; + if (path_chain.empty()) { + path_chain = certificate_key_pair.value().certificate_single_path; + } + path_key = certificate_key_pair.value().key_path; + password = certificate_key_pair.value().password; + } + + SSL_CTX* ssl_ctx = nullptr; + bool tpm_key = false; + + { + if (!path_key.empty()) { + tpm_key = is_tpm_key_filename(path_key); +#ifdef DEBUG + EVLOG_info << "TPM Key: " << tpm_key; +#endif + } + + OpenSSLProvider provider; + + if (tpm_key) { + provider.set_tls_mode(OpenSSLProvider::mode_t::tpm2_provider); + } else { + provider.set_tls_mode(OpenSSLProvider::mode_t::default_provider); + } + + EVLOG_info << "Using TLS propquery: " << provider.propquery_tls_str(); + const SSL_METHOD* method = SSLv23_client_method(); + ssl_ctx = SSL_CTX_new_ex(provider, provider.propquery_tls_str(), method); + + if (ssl_ctx == nullptr) { + ERR_print_errors_fp(stderr); + EVLOG_AND_THROW(std::runtime_error("Unable to create ssl ctx.")); + } + } // Init TLS data - tls_init(); + tls_init(ssl_ctx, path_chain, path_key, tpm_key, password); // Setup our context info.provided_client_ssl_ctx = ssl_ctx; + // Connection acquire the contexts + conn_data->sec_context = std::unique_ptr(ssl_ctx); + lws_context* lws_ctx = lws_create_context(&info); if (nullptr == lws_ctx) { EVLOG_error << "lws init failed!"; @@ -466,7 +459,7 @@ void WebsocketTlsTPM::client_loop() { data->update_state(EConnectionState::FINALIZED); } - EVLOG_debug << "Init client loop with ID: " << std::this_thread::get_id(); + EVLOG_debug << "Init client loop with ID: " << std::hex << std::this_thread::get_id(); // Process while we're running int n = 0; @@ -486,7 +479,7 @@ void WebsocketTlsTPM::client_loop() { } // Client loop finished for our tid - EVLOG_debug << "Exit client loop with ID: " << std::this_thread::get_id(); + EVLOG_debug << "Exit client loop with ID: " << std::hex << std::this_thread::get_id(); } // Will be called from external threads as well @@ -965,6 +958,7 @@ int WebsocketTlsTPM::process_callback(void* wsi_ptr, int callback_reason, void* case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: EVLOG_error << "CLIENT_CONNECTION_ERROR: " << (in ? reinterpret_cast(in) : "(null)"); + ERR_print_errors_fp(stderr); if (data->get_state() == EConnectionState::CONNECTING) { data->update_state(EConnectionState::ERROR); diff --git a/lib/ocpp/v16/charge_point_impl.cpp b/lib/ocpp/v16/charge_point_impl.cpp index 02f1194ee..894e33277 100644 --- a/lib/ocpp/v16/charge_point_impl.cpp +++ b/lib/ocpp/v16/charge_point_impl.cpp @@ -2186,6 +2186,7 @@ void ChargePointImpl::handleExtendedTriggerMessageRequest(ocpp::Callevse_security->generate_certificate_signing_request(