Skip to content

Commit

Permalink
feat: add support for new EVSE security API
Browse files Browse the repository at this point in the history
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 <james.chapman@pionix.de>
  • Loading branch information
james-ctc committed Oct 23, 2024
1 parent 36d2fae commit f00202b
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 22 deletions.
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 @@ 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);

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

0 comments on commit f00202b

Please sign in to comment.