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

feat: add support for new EVSE security API #919

Merged
merged 1 commit into from
Oct 24, 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
22 changes: 21 additions & 1 deletion lib/staging/tls/openssl_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
#include <cstdint>
#include <cstring>
#include <iostream>
#include <iterator>
#include <memory>
#include <string>

Expand Down Expand Up @@ -462,6 +461,27 @@
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);

Check notice on line 467 in lib/staging/tls/openssl_util.cpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

lib/staging/tls/openssl_util.cpp#L467

Does not handle strings that are not \0-terminated; if given one it may perform an over-read (it could cause a crash if unprotected) (CWE-126).
auto* mem = BIO_new_mem_buf(pem_string, static_cast<int>(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) {
Expand Down
8 changes: 8 additions & 0 deletions lib/staging/tls/openssl_util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions lib/staging/tls/tests/openssl_util_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
45 changes: 45 additions & 0 deletions lib/staging/tls/tests/tls_connection_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string, std::string> 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<std::string, std::string> subject;
Expand Down
4 changes: 4 additions & 0 deletions lib/staging/tls/tls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -934,8 +934,12 @@ bool Server::init_certificates(const std::vector<certificate_config_t>& 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;

Expand Down
1 change: 1 addition & 0 deletions lib/staging/tls/tls.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<ConfigItem> ocsp_response_files; //!< list of OCSP files in certificate chain order
Expand Down
45 changes: 24 additions & 21 deletions modules/EvseV2G/connection/tls_connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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();
dorezyuk marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}

Expand Down
Loading