From 780db3a811f6917b85497b5f36c78ab45c36bfff Mon Sep 17 00:00:00 2001 From: florinmihut Date: Tue, 22 Oct 2024 11:09:17 +0200 Subject: [PATCH 1/5] Fix for providing powermeter public key (#924) * Fix for providing powermeter public key - the key was missing in the transaction data and from the interface. EVSE Manager has to republish the key which is needed by other modules. --------- Signed-off-by: florinmihut --- interfaces/evse_manager.yaml | 7 +++++-- modules/EvseManager/evse/evse_managerImpl.cpp | 6 +++++- modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp | 1 + modules/LemDCBM400600/main/powermeterImpl.cpp | 2 +- 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/interfaces/evse_manager.yaml b/interfaces/evse_manager.yaml index 39c14a01e..2f4a439e2 100644 --- a/interfaces/evse_manager.yaml +++ b/interfaces/evse_manager.yaml @@ -26,7 +26,7 @@ cmds: type: boolean authorize_response: description: >- - Reports the result of an authorization request to the EvseManager. + Reports the result of an authorization request to the EvseManager. Contains the provided_token for which authorization was requested and the validation_result arguments: @@ -150,6 +150,9 @@ vars: description: Measured dataset type: object $ref: /powermeter#/Powermeter + powermeter_public_key_ocmf: + description: Powermeter public key + type: string evse_id: description: EVSE ID including the connector number, e.g. DE*PNX*E123456*1 type: string @@ -159,7 +162,7 @@ vars: $ref: /evse_board_support#/HardwareCapabilities iso15118_certificate_request: description: >- - The vehicle requests the SECC to deliver the certificate that belong + The vehicle requests the SECC to deliver the certificate that belong to the currently valid contract of the vehicle. Response will be reported async via set_get_certificate_response type: object diff --git a/modules/EvseManager/evse/evse_managerImpl.cpp b/modules/EvseManager/evse/evse_managerImpl.cpp index 168b678c5..a6b205615 100644 --- a/modules/EvseManager/evse/evse_managerImpl.cpp +++ b/modules/EvseManager/evse/evse_managerImpl.cpp @@ -112,10 +112,14 @@ void evse_managerImpl::init() { // /Interface to Node-RED debug UI if (mod->r_powermeter_billing().size() > 0) { - mod->r_powermeter_billing()[0]->subscribe_powermeter([this](const types::powermeter::Powermeter p) { + mod->r_powermeter_billing()[0]->subscribe_powermeter([this](const types::powermeter::Powermeter& p) { // Republish data on proxy powermeter struct publish_powermeter(p); }); + mod->r_powermeter_billing()[0]->subscribe_public_key_ocmf([this](const std::string& public_key_ocmf) { + // Republish data on proxy powermeter public_key_ocmf + publish_powermeter_public_key_ocmf(public_key_ocmf); + }); } } diff --git a/modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp b/modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp index bb54e6f36..7416668d7 100644 --- a/modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp +++ b/modules/LemDCBM400600/main/lem_dcbm_400600_controller.cpp @@ -176,6 +176,7 @@ LemDCBM400600Controller::stop_transaction(const std::string& transaction_id) { this->request_device_to_stop_transaction(tid); } auto signed_meter_value = types::units_signed::SignedMeterValue{fetch_ocmf_result(tid), "", "OCMF"}; + signed_meter_value.public_key.emplace(public_key_ocmf); return types::powermeter::TransactionStopResponse{types::powermeter::TransactionRequestStatus::OK, {}, // Empty start_signed_meter_value signed_meter_value}; diff --git a/modules/LemDCBM400600/main/powermeterImpl.cpp b/modules/LemDCBM400600/main/powermeterImpl.cpp index d08c3184e..7a074f227 100644 --- a/modules/LemDCBM400600/main/powermeterImpl.cpp +++ b/modules/LemDCBM400600/main/powermeterImpl.cpp @@ -32,11 +32,11 @@ void powermeterImpl::init() { void powermeterImpl::ready() { // Start the live_measure_publisher thread, which periodically publishes the live measurements of the device this->live_measure_publisher_thread = std::thread([this] { - this->publish_public_key_ocmf(this->controller->get_public_key_ocmf()); while (true) { try { if (!this->controller->is_initialized()) { this->controller->init(); + this->publish_public_key_ocmf(this->controller->get_public_key_ocmf()); std::this_thread::sleep_for( std::chrono::milliseconds(mod->config.resilience_initial_connection_retry_delay)); } else { From 2f9152f3f71339d5cf9f578da815513269d0ba4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piet=20G=C3=B6mpel?= <37657534+Pietfried@users.noreply.github.com> Date: Wed, 23 Oct 2024 09:44:07 +0200 Subject: [PATCH 2/5] Introduce RequestCompositeScheduleUnit argument in OCPP module (#914) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Set numberPhases also for Watt Unit in OCPP * Added parameter RequestCompositeScheduleUnit for OCPP module to use it when requesting composite schedules --------- Signed-off-by: Piet Gömpel Signed-off-by: Cornelius Claussen Signed-off-by: Piet Gömpel <37657534+Pietfried@users.noreply.github.com> Co-authored-by: Cornelius Claussen --- dependencies.yaml | 2 +- modules/OCPP/OCPP.cpp | 28 ++++++++++++++++++++-------- modules/OCPP/OCPP.hpp | 3 +++ modules/OCPP/manifest.yaml | 9 +++++++++ 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/dependencies.yaml b/dependencies.yaml index 3dea366cc..c2a099e9d 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -66,7 +66,7 @@ libevse-security: # OCPP libocpp: git: https://github.com/EVerest/libocpp.git - git_tag: 5c7f10cdfd9aa70db80ccf43ace250e3322be00c + git_tag: c6a6e01b27c994f170d2b21d2befa13ad5c9ca20 cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBOCPP" # Josev Josev: diff --git a/modules/OCPP/OCPP.cpp b/modules/OCPP/OCPP.cpp index 9d032b07d..ec06d3933 100644 --- a/modules/OCPP/OCPP.cpp +++ b/modules/OCPP/OCPP.cpp @@ -83,11 +83,11 @@ void OCPP::set_external_limits(const std::mapcharge_point->disconnect_websocket(); }); } + const auto composite_schedule_unit = get_unit_or_default(this->config.RequestCompositeScheduleUnit); + // publish charging schedules at least once on startup const auto charging_schedules = this->charge_point->get_all_enhanced_composite_charging_schedules( - this->config.PublishChargingScheduleDurationS); + this->config.PublishChargingScheduleDurationS, composite_schedule_unit); this->set_external_limits(charging_schedules); this->publish_charging_schedules(charging_schedules); - this->charging_schedules_timer = std::make_unique([this]() { + this->charging_schedules_timer = std::make_unique([this, composite_schedule_unit]() { const auto charging_schedules = this->charge_point->get_all_enhanced_composite_charging_schedules( - this->config.PublishChargingScheduleDurationS); + this->config.PublishChargingScheduleDurationS, composite_schedule_unit); this->set_external_limits(charging_schedules); this->publish_charging_schedules(charging_schedules); }); @@ -677,12 +689,12 @@ void OCPP::ready() { this->charging_schedules_timer->interval(std::chrono::seconds(this->config.PublishChargingScheduleIntervalS)); } - this->charge_point->register_signal_set_charging_profiles_callback([this]() { + this->charge_point->register_signal_set_charging_profiles_callback([this, composite_schedule_unit]() { // this is executed when CSMS sends new ChargingProfile that is accepted by // the ChargePoint EVLOG_info << "Received new Charging Schedules from CSMS"; const auto charging_schedules = this->charge_point->get_all_enhanced_composite_charging_schedules( - this->config.PublishChargingScheduleDurationS); + this->config.PublishChargingScheduleDurationS, composite_schedule_unit); this->set_external_limits(charging_schedules); this->publish_charging_schedules(charging_schedules); }); diff --git a/modules/OCPP/OCPP.hpp b/modules/OCPP/OCPP.hpp index deb467907..631584657 100644 --- a/modules/OCPP/OCPP.hpp +++ b/modules/OCPP/OCPP.hpp @@ -63,6 +63,7 @@ struct Conf { int PublishChargingScheduleDurationS; std::string MessageLogPath; int MessageQueueResumeDelay; + std::string RequestCompositeScheduleUnit; }; class OCPP : public Everest::ModuleBase { @@ -138,6 +139,7 @@ class OCPP : public Everest::ModuleBase { // ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1 // insert your private definitions here std::filesystem::path ocpp_share_path; + ocpp::v16::ChargingRateUnit composite_schedule_charging_rate_unit; void set_external_limits(const std::map& charging_schedules); void publish_charging_schedules(const std::map& charging_schedules); @@ -162,6 +164,7 @@ class OCPP : public Everest::ModuleBase { }; // ev@087e516b-124c-48df-94fb-109508c7cda9:v1 +// insert other definitions here // ev@087e516b-124c-48df-94fb-109508c7cda9:v1 } // namespace module diff --git a/modules/OCPP/manifest.yaml b/modules/OCPP/manifest.yaml index c9ae86032..dc4534a22 100644 --- a/modules/OCPP/manifest.yaml +++ b/modules/OCPP/manifest.yaml @@ -35,6 +35,15 @@ config: description: Time (seconds) to delay resuming the message queue after reconnecting type: integer default: 0 + RequestCompositeScheduleUnit: + description: >- + Unit in which composite schedules are requested and shared within EVerest. It is recommended to use + Amps for AC and Watts for DC charging stations. + Allowed values: + - 'A' for Amps + - 'W' for Watts + type: string + default: 'A' provides: main: description: This is a OCPP 1.6 charge point From 36d2faee6c578dde3ddc0a14d1d34b75430b9dc8 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 23 Oct 2024 16:08:00 +0300 Subject: [PATCH 3/5] Feature/multi root trusted ca (#923) * Security interface modifications for extended certificate retrieval * Updated sec module interfaces/implementation * Updated dependencies --------- Signed-off-by: AssemblyJohn --- dependencies.yaml | 6 +++--- interfaces/evse_security.yaml | 20 ++++++++++++++++++ lib/staging/evse_security/conversions.cpp | 1 + .../EvseSecurity/main/evse_securityImpl.cpp | 20 ++++++++++++++++++ .../EvseSecurity/main/evse_securityImpl.hpp | 3 +++ types/evse_security.yaml | 21 +++++++++++++++++++ 6 files changed, 68 insertions(+), 3 deletions(-) diff --git a/dependencies.yaml b/dependencies.yaml index c2a099e9d..cd918c3be 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -56,11 +56,11 @@ libcurl: cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBCURL" # EvseSecurity -# This has to appear before libocpp in this file since it is also a direct dependency of libocpp -# and would otherwise be overwritten by the version used there +# This has to appear before libocpp in this file since it is also a direct dependency +# of libocpp and would otherwise be overwritten by the version used there libevse-security: git: https://github.com/EVerest/libevse-security.git - git_tag: v0.8.0 + git_tag: v0.9.1 cmake_condition: "EVEREST_DEPENDENCY_ENABLED_LIBEVSE_SECURITY" # OCPP diff --git a/interfaces/evse_security.yaml b/interfaces/evse_security.yaml index 1bc784415..1af1f11fd 100644 --- a/interfaces/evse_security.yaml +++ b/interfaces/evse_security.yaml @@ -154,6 +154,26 @@ cmds: description: The response to the requested command type: object $ref: /evse_security#/GetCertificateInfoResult + get_all_valid_certificates_info: + description: >- + Finds the latest valid leafs, for each root certificate that is present on the filesystem, + and returns all the newest valid leafs that are present for different roots + arguments: + certificate_type: + description: Specifies the leaf certificate type + type: string + $ref: /evse_security#/LeafCertificateType + encoding: + description: Specifies the encoding of the key + type: string + $ref: /evse_security#/EncodingFormat + include_ocsp: + description: Specifies whether per-certificate OCSP data is also requested + type: boolean + result: + description: The response to the requested command + type: object + $ref: /evse_security#/GetCertificateFullInfoResult get_verify_file: description: Command to get the file path of a CA bundle that can be used for verification arguments: diff --git a/lib/staging/evse_security/conversions.cpp b/lib/staging/evse_security/conversions.cpp index 216c48807..909f98962 100644 --- a/lib/staging/evse_security/conversions.cpp +++ b/lib/staging/evse_security/conversions.cpp @@ -451,6 +451,7 @@ types::evse_security::OCSPRequestDataList to_everest(evse_security::OCSPRequestD types::evse_security::CertificateInfo to_everest(evse_security::CertificateInfo other) { types::evse_security::CertificateInfo lhs; lhs.key = other.key; + lhs.certificate_root = other.certificate_root; lhs.certificate = other.certificate; lhs.certificate_single = other.certificate_single; lhs.password = other.password; diff --git a/modules/EvseSecurity/main/evse_securityImpl.cpp b/modules/EvseSecurity/main/evse_securityImpl.cpp index 050782928..29fe3a5d1 100644 --- a/modules/EvseSecurity/main/evse_securityImpl.cpp +++ b/modules/EvseSecurity/main/evse_securityImpl.cpp @@ -121,6 +121,26 @@ evse_securityImpl::handle_get_leaf_certificate_info(types::evse_security::LeafCe return response; } +types::evse_security::GetCertificateFullInfoResult +evse_securityImpl::handle_get_all_valid_certificates_info(types::evse_security::LeafCertificateType& certificate_type, + types::evse_security::EncodingFormat& encoding, + bool& include_ocsp) { + types::evse_security::GetCertificateFullInfoResult response; + + const auto full_leaf_info = this->evse_security->get_all_valid_certificates_info( + conversions::from_everest(certificate_type), conversions::from_everest(encoding), include_ocsp); + + response.status = conversions::to_everest(full_leaf_info.status); + + if (full_leaf_info.status == evse_security::GetCertificateInfoStatus::Accepted) { + for (const auto& info : full_leaf_info.info) { + response.info.push_back(conversions::to_everest(info)); + } + } + + return response; +} + std::string evse_securityImpl::handle_get_verify_file(types::evse_security::CaCertificateType& certificate_type) { return this->evse_security->get_verify_file(conversions::from_everest(certificate_type)); } diff --git a/modules/EvseSecurity/main/evse_securityImpl.hpp b/modules/EvseSecurity/main/evse_securityImpl.hpp index 0b278ae6f..768d88e40 100644 --- a/modules/EvseSecurity/main/evse_securityImpl.hpp +++ b/modules/EvseSecurity/main/evse_securityImpl.hpp @@ -60,6 +60,9 @@ class evse_securityImpl : public evse_securityImplBase { virtual types::evse_security::GetCertificateInfoResult handle_get_leaf_certificate_info(types::evse_security::LeafCertificateType& certificate_type, types::evse_security::EncodingFormat& encoding, bool& include_ocsp) override; + virtual types::evse_security::GetCertificateFullInfoResult + handle_get_all_valid_certificates_info(types::evse_security::LeafCertificateType& certificate_type, + types::evse_security::EncodingFormat& encoding, bool& include_ocsp) override; virtual std::string handle_get_verify_file(types::evse_security::CaCertificateType& certificate_type) override; virtual int handle_get_leaf_expiry_days_count(types::evse_security::LeafCertificateType& certificate_type) override; virtual bool handle_verify_file_signature(std::string& file_path, std::string& signing_certificate, diff --git a/types/evse_security.yaml b/types/evse_security.yaml index 5fdce9f00..3e5a90e4f 100644 --- a/types/evse_security.yaml +++ b/types/evse_security.yaml @@ -227,6 +227,9 @@ types: key: description: The path of the PEM or DER encoded private key type: string + certificate_root: + description: The PEM of the root certificate that issued this leaf + type: string certificate: description: The path of the PEM or DER encoded certificate chain type: string @@ -260,4 +263,22 @@ types: description: The requested info type: object $ref: /evse_security#/CertificateInfo + GetCertificateFullInfoResult: + description: Response to the command get_all_valid_certificates_info + type: object + required: + - status + - info + properties: + status: + description: The status of the requested command + type: string + $ref: /evse_security#/GetCertificateInfoStatus + info: + description: The requested info + type: array + items: + minimum: 0 + type: object + $ref: /evse_security#/CertificateInfo From a6867fdd621f2d117e1a158f089bbfa60e734bd0 Mon Sep 17 00:00:00 2001 From: James Chapman <147724513+james-ctc@users.noreply.github.com> Date: Thu, 24 Oct 2024 08:07:36 +0100 Subject: [PATCH 4/5] feat: add support for new EVSE security API (#919) Now uses call_get_all_valid_certificates_info() to obtain multiple server certificate chains in order to support trusted_ca_keys. Signed-off-by: James Chapman --- lib/staging/tls/openssl_util.cpp | 22 ++++++++- lib/staging/tls/openssl_util.hpp | 8 ++++ lib/staging/tls/tests/openssl_util_test.cpp | 30 +++++++++++++ lib/staging/tls/tests/tls_connection_test.cpp | 45 +++++++++++++++++++ lib/staging/tls/tls.cpp | 4 ++ lib/staging/tls/tls.hpp | 1 + modules/EvseV2G/connection/tls_connection.cpp | 45 ++++++++++--------- 7 files changed, 133 insertions(+), 22 deletions(-) diff --git a/lib/staging/tls/openssl_util.cpp b/lib/staging/tls/openssl_util.cpp index af2648c3b..2152d23fd 100644 --- a/lib/staging/tls/openssl_util.cpp +++ b/lib/staging/tls/openssl_util.cpp @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -462,6 +461,27 @@ bool signature_to_bn(bn_t& r, bn_t& s, const std::uint8_t* sig_p, std::size_t le return bRes; }; +certificate_list load_certificates_pem(const char* pem_string) { + certificate_list result{}; + if (pem_string != nullptr) { + const auto len = std::strlen(pem_string); + auto* mem = BIO_new_mem_buf(pem_string, static_cast(len)); + X509* cert = nullptr; + + while (!BIO_eof(mem)) { + if (PEM_read_bio_X509(mem, &cert, nullptr, nullptr) == nullptr) { + log_error("PEM_read_bio_X509"); + break; + } else { + result.emplace_back(certificate_ptr{cert, &X509_free}); + cert = nullptr; + } + } + BIO_free(mem); + } + return result; +} + certificate_list load_certificates(const char* filename) { certificate_list result{}; if (filename != nullptr) { diff --git a/lib/staging/tls/openssl_util.hpp b/lib/staging/tls/openssl_util.hpp index 00127ddd3..08e65ae33 100644 --- a/lib/staging/tls/openssl_util.hpp +++ b/lib/staging/tls/openssl_util.hpp @@ -335,6 +335,14 @@ DER bn_to_signature(const std::uint8_t* r, const std::uint8_t* s); */ bool signature_to_bn(openssl::bn_t& r, openssl::bn_t& s, const std::uint8_t* sig_p, std::size_t len); +/** + * \brief load any PEM encoded certificates from a string + * \param[in] pem_string + * \return a list of 0 or more certificates + * \note PEM string only supports certificates and not other PEM types + */ +certificate_list load_certificates_pem(const char* pem_string); + /** * \brief load any PEM encoded certificates from a file * \param[in] filename diff --git a/lib/staging/tls/tests/openssl_util_test.cpp b/lib/staging/tls/tests/openssl_util_test.cpp index 7f9f7a1bc..49ad7db98 100644 --- a/lib/staging/tls/tests/openssl_util_test.cpp +++ b/lib/staging/tls/tests/openssl_util_test.cpp @@ -540,6 +540,36 @@ TEST(certificate, toPem) { // std::cout << pem << std::endl; } +TEST(certificate, loadPemSingle) { + auto certs = ::openssl::load_certificates("client_ca_cert.pem"); + ASSERT_EQ(certs.size(), 1); + auto pem = ::openssl::certificate_to_pem(certs[0].get()); + EXPECT_FALSE(pem.empty()); + + auto pem_certs = ::openssl::load_certificates_pem(pem.c_str()); + ASSERT_EQ(pem_certs.size(), 1); + EXPECT_EQ(certs[0], pem_certs[0]); +} + +TEST(certificate, loadPemMulti) { + auto certs = ::openssl::load_certificates("client_chain.pem"); + ASSERT_GT(certs.size(), 1); + std::string pem; + for (const auto& cert : certs) { + pem += ::openssl::certificate_to_pem(cert.get()); + } + EXPECT_FALSE(pem.empty()); + // std::cout << pem << std::endl << "Output" << std::endl; + + auto pem_certs = ::openssl::load_certificates_pem(pem.c_str()); + ASSERT_EQ(pem_certs.size(), certs.size()); + for (auto i = 0; i < certs.size(); i++) { + SCOPED_TRACE(std::to_string(i)); + // std::cout << ::openssl::certificate_to_pem(pem_certs[i].get()) << std::endl; + EXPECT_EQ(certs[i], pem_certs[i]); + } +} + TEST(certificate, verify) { auto client = ::openssl::load_certificates("client_cert.pem"); auto chain = ::openssl::load_certificates("client_chain.pem"); diff --git a/lib/staging/tls/tests/tls_connection_test.cpp b/lib/staging/tls/tests/tls_connection_test.cpp index 9bcef8993..1361cee2a 100644 --- a/lib/staging/tls/tests/tls_connection_test.cpp +++ b/lib/staging/tls/tests/tls_connection_test.cpp @@ -741,6 +741,51 @@ TEST_F(TlsTest, TCKeysKey) { EXPECT_EQ(subject["CN"], alt_server_root_CN); } +TEST_F(TlsTest, TCKeysKeyPem) { + // same as TCKeysKey but using a PEM string trust anchor rather than file + std::map subject; + + client_config.trusted_ca_keys = true; + client_config.verify_locations_file = "alt_server_root_cert.pem"; + add_ta_key_hash("alt_server_root_cert.pem"); + + auto client_handler_fn = [this, &subject](tls::Client::ConnectionPtr& connection) { + if (connection) { + if (connection->connect() == result_t::success) { + this->set(ClientTest::flags_t::connected); + subject = openssl::certificate_subject(connection->peer_certificate()); + connection->shutdown(); + } + } + }; + + // convert file to PEM in config + for (auto& cfg : server_config.chains) { + const auto certs = ::openssl::load_certificates(cfg.trust_anchor_file); + std::string pem; + for (const auto& cert : certs) { + pem += ::openssl::certificate_to_pem(cert.get()); + } + // std::cout << cfg.trust_anchor_file << ": " << certs.size() << std::endl; + ASSERT_FALSE(pem.empty()); + cfg.trust_anchor_file = nullptr; + cfg.trust_anchor_pem = pem.c_str(); + } + + start(); + connect(client_handler_fn); + EXPECT_TRUE(is_set(flags_t::connected)); + EXPECT_EQ(subject["CN"], alt_server_root_CN); + + client_config.trusted_ca_keys_data.x509_name.clear(); + add_ta_key_hash("client_root_cert.pem"); + add_ta_key_hash("alt_server_root_cert.pem"); + + connect(client_handler_fn); + EXPECT_TRUE(is_set(flags_t::connected)); + EXPECT_EQ(subject["CN"], alt_server_root_CN); +} + TEST_F(TlsTest, TCKeysName) { // trusted_ca_keys - subject name matches std::map subject; diff --git a/lib/staging/tls/tls.cpp b/lib/staging/tls/tls.cpp index 097982449..966baf873 100644 --- a/lib/staging/tls/tls.cpp +++ b/lib/staging/tls/tls.cpp @@ -934,8 +934,12 @@ bool Server::init_certificates(const std::vector& chain_fi for (const auto& i : chain_files) { auto certs = openssl::load_certificates(i.certificate_chain_file); auto tas = openssl::load_certificates(i.trust_anchor_file); + auto tas_pem = openssl::load_certificates_pem(i.trust_anchor_pem); auto pkey = openssl::load_private_key(i.private_key_file, i.private_key_password); + // combine all trust anchor certificates + std::move(tas_pem.begin(), tas_pem.end(), std::back_inserter(tas)); + if (certs.size() > 0) { openssl::chain_t chain; diff --git a/lib/staging/tls/tls.hpp b/lib/staging/tls/tls.hpp index 4bc40911c..bc9a28a41 100644 --- a/lib/staging/tls/tls.hpp +++ b/lib/staging/tls/tls.hpp @@ -359,6 +359,7 @@ class Server { //!< server certificate is the first certificate in the file followed by any intermediate CAs ConfigItem certificate_chain_file{nullptr}; ConfigItem trust_anchor_file{nullptr}; //!< one or more trust anchor PEM certificates + ConfigItem trust_anchor_pem{nullptr}; //!< one or more trust anchor PEM certificates ConfigItem private_key_file{nullptr}; //!< key associated with the server certificate ConfigItem private_key_password{nullptr}; //!< optional password to read private key std::vector ocsp_response_files; //!< list of OCSP files in certificate chain order diff --git a/modules/EvseV2G/connection/tls_connection.cpp b/modules/EvseV2G/connection/tls_connection.cpp index 586613ad2..b13a0147f 100644 --- a/modules/EvseV2G/connection/tls_connection.cpp +++ b/modules/EvseV2G/connection/tls_connection.cpp @@ -3,7 +3,6 @@ #include "tls_connection.hpp" #include "connection.hpp" -#include "everest/logging.hpp" #include "log.hpp" #include "v2g.hpp" #include "v2g_server.hpp" @@ -142,30 +141,34 @@ bool build_config(tls::Server::config_t& config, struct v2g_context* ctx) { // information from libevse-security const auto cert_info = - ctx->r_security->call_get_leaf_certificate_info(LeafCertificateType::V2G, EncodingFormat::PEM, false); + ctx->r_security->call_get_all_valid_certificates_info(LeafCertificateType::V2G, EncodingFormat::PEM, true); if (cert_info.status != GetCertificateInfoStatus::Accepted) { dlog(DLOG_LEVEL_ERROR, "Failed to read cert_info! Not Accepted"); } else { - if (cert_info.info) { - const auto& info = cert_info.info.value(); - const auto cert_path = info.certificate.value_or(""); - const auto key_path = info.key; - - // workaround (see above libevse-security comment) - const auto key_password = info.password.value_or(""); - - auto& ref = config.chains.emplace_back(); - ref.certificate_chain_file = cert_path.c_str(); - ref.private_key_file = key_path.c_str(); - ref.private_key_password = key_password.c_str(); - - if (info.ocsp) { - for (const auto& ocsp : info.ocsp.value()) { - const char* file{nullptr}; - if (ocsp.ocsp_path) { - file = ocsp.ocsp_path.value().c_str(); + if (!cert_info.info.empty()) { + // process all known certificate chains + for (const auto& chain : cert_info.info) { + const auto cert_path = chain.certificate.value_or(""); + const auto key_path = chain.key; + const auto root_pem = chain.certificate_root.value_or(""); + + // workaround (see above libevse-security comment) + const auto key_password = chain.password.value_or(""); + + auto& ref = config.chains.emplace_back(); + ref.certificate_chain_file = cert_path.c_str(); + ref.private_key_file = key_path.c_str(); + ref.private_key_password = key_password.c_str(); + ref.trust_anchor_pem = root_pem.c_str(); + + if (chain.ocsp) { + for (const auto& ocsp : chain.ocsp.value()) { + const char* file{nullptr}; + if (ocsp.ocsp_path) { + file = ocsp.ocsp_path.value().c_str(); + } + ref.ocsp_response_files.push_back(file); } - ref.ocsp_response_files.push_back(file); } } From 25472a1a8c01f193cc550e245dc656076845fd19 Mon Sep 17 00:00:00 2001 From: Sebastian Lukas <45936573+SebaLukas@users.noreply.github.com> Date: Thu, 24 Oct 2024 10:23:06 +0200 Subject: [PATCH 5/5] Adding tls key logger for EvseV2G (openssl part) (#910) Signed-off-by: Sebastian Lukas Signed-off-by: James Chapman Co-authored-by: James Chapman --- config/config-sil-dc-tls.yaml | 1 + lib/staging/tls/openssl_util.hpp | 5 + lib/staging/tls/tests/gtest_main.cpp | 3 + lib/staging/tls/tls.cpp | 149 +++++++++++++++++- lib/staging/tls/tls.hpp | 34 +++- modules/EvseV2G/EvseV2G.cpp | 3 + modules/EvseV2G/connection/tls_connection.cpp | 4 + modules/IsoMux/IsoMux.cpp | 24 +++ modules/IsoMux/connection/tls_connection.cpp | 2 + modules/IsoMux/v2g.hpp | 2 + 10 files changed, 223 insertions(+), 4 deletions(-) diff --git a/config/config-sil-dc-tls.yaml b/config/config-sil-dc-tls.yaml index 7645821b2..d1f32668d 100644 --- a/config/config-sil-dc-tls.yaml +++ b/config/config-sil-dc-tls.yaml @@ -4,6 +4,7 @@ active_modules: config_module: device: auto tls_security: force + tls_key_logging: true connections: security: - module_id: evse_security diff --git a/lib/staging/tls/openssl_util.hpp b/lib/staging/tls/openssl_util.hpp index 08e65ae33..7c3f0afed 100644 --- a/lib/staging/tls/openssl_util.hpp +++ b/lib/staging/tls/openssl_util.hpp @@ -499,6 +499,7 @@ bool certificate_subject_public_key_sha_1(openssl::sha_1_digest_t& digest, const enum class log_level_t : std::uint8_t { debug, + info, warning, error, }; @@ -523,6 +524,10 @@ static inline void log_debug(const std::string& str) { log(log_level_t::debug, str); } +static inline void log_info(const std::string& str) { + log(log_level_t::info, str); +} + using log_handler_t = void (*)(log_level_t level, const std::string& err); /** diff --git a/lib/staging/tls/tests/gtest_main.cpp b/lib/staging/tls/tests/gtest_main.cpp index b73259c2a..02b6af7a3 100644 --- a/lib/staging/tls/tests/gtest_main.cpp +++ b/lib/staging/tls/tests/gtest_main.cpp @@ -17,6 +17,9 @@ void log_handler(openssl::log_level_t level, const std::string& str) { case openssl::log_level_t::debug: // std::cout << "DEBUG: " << str << std::endl; break; + case openssl::log_level_t::info: + std::cout << "INFO: " << str << std::endl; + break; case openssl::log_level_t::warning: std::cout << "WARN: " << str << std::endl; break; diff --git a/lib/staging/tls/tls.cpp b/lib/staging/tls/tls.cpp index 966baf873..45a7b47f0 100644 --- a/lib/staging/tls/tls.cpp +++ b/lib/staging/tls/tls.cpp @@ -6,6 +6,7 @@ #include "extensions/trusted_ca_keys.hpp" #include "openssl_util.hpp" +#include #include #include #include @@ -13,8 +14,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -63,6 +66,7 @@ template <> class default_delete { } // namespace std using ::openssl::log_error; +using ::openssl::log_info; using ::openssl::log_warning; namespace { @@ -692,8 +696,43 @@ std::uint32_t ServerConnection::m_count{0}; std::mutex ServerConnection::m_cv_mutex; std::condition_variable ServerConnection::m_cv; +namespace { + +int ssl_keylog_file_index{-1}; +int ssl_keylog_server_index{-1}; + +void keylog_callback(const SSL* ssl, const char* line) { + + auto keylog_server = static_cast(SSL_get_ex_data(ssl, ssl_keylog_server_index)); + + std::string key_log_msg = "TLS Handshake keys on port "; + key_log_msg += std::to_string(keylog_server->get_port()) + ": "; + key_log_msg += std::string(line); + + log_info(key_log_msg); + + if (keylog_server->get_fd() != -1) { + const auto result = keylog_server->send(line); + if (result not_eq strlen(line)) { + log_error("key_logging_server send() failed!"); + } + } + + auto keylog_file_path = + static_cast(SSL_CTX_get_ex_data(SSL_get_SSL_CTX(ssl), ssl_keylog_file_index)); + + if (not keylog_file_path->empty()) { + std::ofstream ofs; + ofs.open(keylog_file_path->string(), std::ofstream::out | std::ofstream::app); + ofs << line << std::endl; + ofs.close(); + } +} + +} // namespace + ServerConnection::ServerConnection(SslContext* ctx, int soc, const char* ip_in, const char* service_in, - std::int32_t timeout_ms) : + std::int32_t timeout_ms, const ConfigItem& tls_key_interface) : Connection(ctx, soc, ip_in, service_in, timeout_ms), m_tck_data{m_trusted_ca_keys, m_flags} { { std::lock_guard lock(m_cv_mutex); @@ -703,6 +742,12 @@ ServerConnection::ServerConnection(SslContext* ctx, int soc, const char* ip_in, SSL_set_accept_state(m_context->ctx.get()); ServerStatusRequestV2::set_data(m_context->ctx.get(), &m_flags); ServerTrustedCaKeys::set_data(m_context->ctx.get(), &m_tck_data); + + if (tls_key_interface != nullptr) { + const auto port = std::stoul(service_in); + m_keylog_server = std::make_unique(std::string(tls_key_interface), port); + SSL_set_ex_data(m_context->ctx.get(), ssl_keylog_server_index, m_keylog_server.get()); + } } } @@ -885,6 +930,26 @@ bool Server::init_ssl(const config_t& cfg) { // use the first server chain result = configure_ssl_ctx(ctx, cfg.ciphersuites, cfg.cipher_list, cfg.chains[0], true); if (result) { + + if (cfg.tls_key_logging) { + tls_key_log_file_path = std::filesystem::path(cfg.tls_key_logging_path) /= "tls_session_keys.log"; + + ssl_keylog_file_index = SSL_CTX_get_ex_new_index(0, std::string("").data(), nullptr, nullptr, nullptr); + ssl_keylog_server_index = SSL_get_ex_new_index(0, std::string("").data(), nullptr, nullptr, nullptr); + + if (ssl_keylog_file_index == -1 or ssl_keylog_server_index == -1) { + auto error_msg = std::string("_get_ex_new_index failed: ssl_keylog_file_index: "); + error_msg += std::to_string(ssl_keylog_file_index); + error_msg += ", ssl_keylog_server_index: " + std::to_string(ssl_keylog_server_index); + log_error(error_msg); + } else { + SSL_CTX_set_ex_data(ctx, ssl_keylog_file_index, &tls_key_log_file_path); + + SSL_CTX_set_keylog_callback(ctx, keylog_callback); + m_tls_key_interface = cfg.host; + } + } + int mode = SSL_VERIFY_NONE; // TODO(james-ctc): verify may need to change based on TLS version @@ -1062,8 +1127,9 @@ void Server::wait_for_connection(const ConnectionHandler& handler) { // new connection, pass to handler auto* ip = BIO_ADDR_hostname_string(peer.get(), 1); auto* service = BIO_ADDR_service_string(peer.get(), 1); - auto connection = - std::make_unique(m_context->ctx.get(), soc, ip, service, m_timeout_ms); + + auto connection = std::make_unique(m_context->ctx.get(), soc, ip, service, + m_timeout_ms, m_tls_key_interface); handler(std::move(connection)); OPENSSL_free(ip); OPENSSL_free(service); @@ -1345,4 +1411,81 @@ Client::override_t Client::default_overrides() { }; } +// ---------------------------------------------------------------------------- +// TlsKeyLoggingServer + +TlsKeyLoggingServer::TlsKeyLoggingServer(const std::string& interface_name, uint16_t port_) : port(port_) { + static constexpr auto LINK_LOCAL_MULTICAST = "ff02::1"; + bool result{true}; + + fd = socket(AF_INET6, SOCK_DGRAM, 0); + if (fd == -1) { + log_error("Could not create socket"); + result = false; + } + + if (result) { + // source setup + // find port between 49152-65535 + auto could_bind = false; + auto source_port = 49152; + for (; source_port < 65535; source_port++) { + sockaddr_in6 source_address = {AF_INET6, htons(source_port), 0, {}, 0}; + if (bind(fd, reinterpret_cast(&source_address), sizeof(sockaddr_in6)) == 0) { + could_bind = true; + break; + } + } + + if (could_bind) { + log_info("UDP socket bound to source port: " + std::to_string(source_port)); + } else { + log_error("Could not bind"); + result = false; + } + } + + if (result) { + auto mreq = ipv6_mreq{}; + const auto index = if_nametoindex(interface_name.c_str()); + mreq.ipv6mr_interface = index; + if (inet_pton(AF_INET6, LINK_LOCAL_MULTICAST, &mreq.ipv6mr_multiaddr) <= 0) { + log_error("Failed to setup multicast address"); + result = false; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) < 0) { + log_error("Could not add multicast group membership"); + result = false; + } + + if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)) < 0) { + log_error("Could not set interface name:" + interface_name); + result = false; + } + + destination_address = {AF_INET6, htons(port), 0, {}, 0}; + if (inet_pton(AF_INET6, LINK_LOCAL_MULTICAST, &destination_address.sin6_addr) <= 0) { + log_error("Failed to setup server address, reset key_log_fd"); + result = false; + } + } + + if (!result && fd != -1) { + close(fd); + fd = -1; + } +} + +TlsKeyLoggingServer::~TlsKeyLoggingServer() { + if (fd != -1) { + close(fd); + } +} + +ssize_t TlsKeyLoggingServer::send(const char* line) { + return sendto(fd, line, strlen(line), 0, reinterpret_cast(&destination_address), + sizeof(destination_address)); +} + } // namespace tls diff --git a/lib/staging/tls/tls.hpp b/lib/staging/tls/tls.hpp index bc9a28a41..f48de624e 100644 --- a/lib/staging/tls/tls.hpp +++ b/lib/staging/tls/tls.hpp @@ -12,9 +12,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -57,6 +59,27 @@ class ConfigItem { } }; +class TlsKeyLoggingServer { +public: + TlsKeyLoggingServer(const std::string& interface_name, uint16_t port_); + ~TlsKeyLoggingServer(); + + ssize_t send(const char* line); + + auto get_fd() const { + return fd; + } + + auto get_port() const { + return port; + } + +private: + int fd{-1}; + uint16_t port{0}; + sockaddr_in6 destination_address{}; +}; + // ---------------------------------------------------------------------------- // Connection represents a TLS connection @@ -258,8 +281,11 @@ class ServerConnection : public Connection { StatusFlags m_flags; //!< extension flags server_trusted_ca_keys_t m_tck_data; //!< extension per connection data + std::unique_ptr m_keylog_server{nullptr}; + public: - ServerConnection(SslContext* ctx, int soc, const char* ip_in, const char* service_in, std::int32_t timeout_ms); + ServerConnection(SslContext* ctx, int soc, const char* ip_in, const char* service_in, std::int32_t timeout_ms, + const ConfigItem& tls_key_interface); ServerConnection() = delete; ServerConnection(const ServerConnection&) = delete; ServerConnection(ServerConnection&&) = delete; @@ -381,6 +407,9 @@ class Server { ConfigItem service{nullptr}; //!< TLS port number as a string int socket{INVALID_SOCKET}; //!< use this specific socket - bypasses socket setup in init_socket() when set bool ipv6_only{true}; //!< listen on IPv6 only, when false listen on IPv4 only + + bool tls_key_logging{false}; //!< tls key logging is active when true + std::string tls_key_logging_path; //!< tls key logging file path }; using ConnectionPtr = std::unique_ptr; @@ -409,6 +438,9 @@ class Server { static int s_sig_int; //!< signal to use to wakeup serve() ConfigurationCallback m_init_callback{nullptr}; //!< callback to retrieve SSL configuration + ConfigItem m_tls_key_interface{nullptr}; + std::filesystem::path tls_key_log_file_path{}; + /** * \brief initialise the server socket * \param[in] cfg server configuration diff --git a/modules/EvseV2G/EvseV2G.cpp b/modules/EvseV2G/EvseV2G.cpp index d5567a796..be9d2f70e 100644 --- a/modules/EvseV2G/EvseV2G.cpp +++ b/modules/EvseV2G/EvseV2G.cpp @@ -16,6 +16,9 @@ void log_handler(openssl::log_level_t level, const std::string& str) { case openssl::log_level_t::debug: // ignore debug logs break; + case openssl::log_level_t::info: + EVLOG_info << str; + break; case openssl::log_level_t::warning: EVLOG_warning << str; break; diff --git a/modules/EvseV2G/connection/tls_connection.cpp b/modules/EvseV2G/connection/tls_connection.cpp index b13a0147f..25d374c0a 100644 --- a/modules/EvseV2G/connection/tls_connection.cpp +++ b/modules/EvseV2G/connection/tls_connection.cpp @@ -139,6 +139,10 @@ bool build_config(tls::Server::config_t& config, struct v2g_context* ctx) { config.socket = ctx->tls_socket.fd; config.io_timeout_ms = static_cast(ctx->network_read_timeout_tls); + config.tls_key_logging = ctx->tls_key_logging; + config.tls_key_logging_path = ctx->tls_key_logging_path; + config.host = ctx->if_name; + // information from libevse-security const auto cert_info = ctx->r_security->call_get_all_valid_certificates_info(LeafCertificateType::V2G, EncodingFormat::PEM, true); diff --git a/modules/IsoMux/IsoMux.cpp b/modules/IsoMux/IsoMux.cpp index 521d292fb..f9a4669e7 100644 --- a/modules/IsoMux/IsoMux.cpp +++ b/modules/IsoMux/IsoMux.cpp @@ -6,6 +6,27 @@ #include "log.hpp" #include "sdp.hpp" +#include +namespace { +void log_handler(openssl::log_level_t level, const std::string& str) { + switch (level) { + case openssl::log_level_t::debug: + // ignore debug logs + break; + case openssl::log_level_t::info: + EVLOG_info << str; + break; + case openssl::log_level_t::warning: + EVLOG_warning << str; + break; + case openssl::log_level_t::error: + default: + EVLOG_error << str; + break; + } +} +} // namespace + struct v2g_context* v2g_ctx = nullptr; namespace module { @@ -21,6 +42,9 @@ void IsoMux::init() { v2g_ctx->proxy_port_iso20 = config.proxy_port_iso20; v2g_ctx->selected_iso20 = false; + v2g_ctx->tls_key_logging = config.tls_key_logging; + + (void)openssl::set_log_handler(log_handler); v2g_ctx->tls_server = &tls_server; invoke_init(*p_charger); diff --git a/modules/IsoMux/connection/tls_connection.cpp b/modules/IsoMux/connection/tls_connection.cpp index 3d3f7b1c0..d98abfc9f 100644 --- a/modules/IsoMux/connection/tls_connection.cpp +++ b/modules/IsoMux/connection/tls_connection.cpp @@ -122,6 +122,8 @@ bool build_config(tls::Server::config_t& config, struct v2g_context* ctx) { config.socket = ctx->tls_socket.fd; config.io_timeout_ms = static_cast(ctx->network_read_timeout_tls); + config.tls_key_logging = ctx->tls_key_logging; + // information from libevse-security const auto cert_info = ctx->r_security->call_get_leaf_certificate_info(LeafCertificateType::V2G, EncodingFormat::PEM, false); diff --git a/modules/IsoMux/v2g.hpp b/modules/IsoMux/v2g.hpp index 20ebdd371..58be0cdbc 100644 --- a/modules/IsoMux/v2g.hpp +++ b/modules/IsoMux/v2g.hpp @@ -146,6 +146,8 @@ struct v2g_context { } tls_socket; tls::Server* tls_server; + bool tls_key_logging; + enum V2gMsgTypeId current_v2g_msg; /* holds the last v2g msg type */ int state; /* holds the current state id */ std::atomic_bool is_connection_terminated; /* Is set to true if the connection is terminated (CP State A/F, shutdown