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); } }