Skip to content

Commit

Permalink
feat: add TPM2 support for EvseV2G TLS server private key (EVerest#1021)
Browse files Browse the repository at this point in the history
* feat: add TPM2 support for EvseV2G TLS server private key

Note arbitrary sign/verify is not currently supported in the
OpenSSL utility layer (openssl_util.cpp)

TPM2 support requires -DUSING_TPM2 to cmake

Signed-off-by: James Chapman <[email protected]>

* fix: added comment to explain resetting the global provider settings

Signed-off-by: James Chapman <[email protected]>

---------

Signed-off-by: James Chapman <[email protected]>
  • Loading branch information
james-ctc authored Feb 18, 2025
1 parent 0c6449f commit af9ced9
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 29 deletions.
31 changes: 31 additions & 0 deletions lib/staging/tls/openssl_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@

#include "openssl_util.hpp"

#include <evse_security/crypto/openssl/openssl_provider.hpp>

#include <openssl/bio.h>
#include <openssl/crypto.h>
#include <openssl/ecdsa.h>
Expand Down Expand Up @@ -130,6 +132,7 @@ template <> bool sha(const void* data, std::size_t len, openssl::sha_512_digest_
} // namespace

namespace openssl {
using evse_security::OpenSSLProvider;

DER::DER(std::size_t size) : DER(nullptr, size) {
}
Expand Down Expand Up @@ -388,6 +391,28 @@ std::string base64_encode(const std::uint8_t* data, std::size_t len, bool newLin
}

pkey_ptr load_private_key(const char* filename, const char* password) {
/*
* should read the file into memory to check the key type so the correct
* provider can be selected. For simplicity reuse existing function
* that causes key file to be opened an additional time
*/

bool custom_key = false;

if (filename != nullptr) {
fs::path keyfile{std::string(filename)};
custom_key = evse_security::is_custom_private_key_file(keyfile);
}

OpenSSLProvider provider;

if (custom_key) {
// set global provider to custom settings
provider.set_global_mode(OpenSSLProvider::mode_t::custom_provider);
} else {
provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
}

pkey_ptr private_key{nullptr, nullptr};
auto* bio = BIO_new_file(filename, "r");
if (bio != nullptr) {
Expand All @@ -399,6 +424,12 @@ pkey_ptr load_private_key(const char* filename, const char* password) {
}
BIO_free(bio);
}

if (custom_key) {
// reset global provider back to default settings
provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
}

return private_key;
}

Expand Down
13 changes: 13 additions & 0 deletions lib/staging/tls/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ set(TLS_TEST_FILES
openssl-pki.conf
ocsp_response.der
pki.sh
pki-tpm.sh
)

add_custom_command(
Expand Down Expand Up @@ -45,6 +46,15 @@ target_sources(${TLS_GTEST_NAME} PRIVATE
../tls.cpp
)

if(USING_TPM2)
target_sources(${TLS_GTEST_NAME} PRIVATE
tls_connection_test_tpm.cpp
)
target_compile_definitions(${TLS_GTEST_NAME} PRIVATE
USING_TPM2
)
endif()

target_link_libraries(${TLS_GTEST_NAME}
PRIVATE
GTest::gtest
Expand Down Expand Up @@ -79,6 +89,7 @@ target_link_libraries(${TLS_MAIN_NAME}
PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
everest::evse_security
everest::staging::util
)

Expand Down Expand Up @@ -107,6 +118,7 @@ target_link_libraries(${TLS_CLIENT_NAME}
PRIVATE
OpenSSL::SSL
OpenSSL::Crypto
everest::evse_security
everest::staging::util
)

Expand Down Expand Up @@ -136,6 +148,7 @@ target_link_libraries(${TLS_PATCH_NAME}
GTest::gtest_main
OpenSSL::SSL
OpenSSL::Crypto
everest::evse_security
everest::staging::util
)

Expand Down
10 changes: 10 additions & 0 deletions lib/staging/tls/tests/gtest_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,16 @@ int main(int argc, char** argv) {
}
return 1;
}
#ifdef USING_TPM2
if (std::system("./pki-tpm.sh") != 0) {
std::cerr << "Problem creating TPM test certificates and keys" << std::endl;
char buf[PATH_MAX];
if (getcwd(&buf[0], sizeof(buf)) != nullptr) {
std::cerr << "./pki-tpm.sh not found in " << buf << std::endl;
}
return 1;
}
#endif
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
32 changes: 32 additions & 0 deletions lib/staging/tls/tests/openssl_util_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@
#include <cstddef>
#include <cstring>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl_util.hpp>
#include <vector>

#include <evse_security/crypto/openssl/openssl_provider.hpp>

bool operator==(const ::openssl::certificate_ptr& lhs, const ::openssl::certificate_ptr& rhs) {
using ::openssl::certificate_to_pem;
if (lhs && rhs) {
Expand Down Expand Up @@ -314,6 +317,35 @@ TEST(openssl, signVerify) {
EXPECT_TRUE(openssl::verify(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
}

#ifdef USING_TPM2
TEST(opensslTpm, signVerify) {
auto pkey = openssl::load_private_key("tpm_pki/server_priv.pem", nullptr);
ASSERT_TRUE(pkey);

// looks like sign/verify may not be supported
// TODO(james-ctc) investigation needed
// perhaps the public key needs to be extracted

#if 0
if (EVP_PKEY_can_sign(pkey.get()) == 1) {
std::array<std::uint8_t, 256> sig_der{};
std::size_t sig_der_len{sig_der.size()};
openssl::sha_256_digest_t digest;
evse_security::OpenSSLProvider provider;
provider.set_global_mode(evse_security::OpenSSLProvider::mode_t::custom_provider);
EXPECT_TRUE(openssl::sha_256(&sign_test[0], openssl::sha_256_digest_size, digest));

EXPECT_TRUE(openssl::sign(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
// std::cout << "signature size: " << sig_der_len << std::endl;

EXPECT_TRUE(openssl::verify(pkey.get(), sig_der.data(), sig_der_len, digest.data(), digest.size()));
} else {
std::cout << "sign/verify not supported" << std::endl;
}
#endif
}
#endif

TEST(openssl, signVerifyBn) {
auto pkey = openssl::load_private_key("server_priv.pem", nullptr);
ASSERT_TRUE(pkey);
Expand Down
57 changes: 57 additions & 0 deletions lib/staging/tls/tests/pki/pki-tpm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/bin/sh

base=.
cfg=./openssl-pki.conf
dir=tpm_pki

[ ! -f "$cfg" ] && echo "missing openssl-pki.conf" && exit 1

generate() {
local base=$1
local dir=$2
mkdir -p ${base}/${dir}

local root_priv=${base}/${dir}/server_root_priv.pem
local ca_priv=${base}/${dir}/server_ca_priv.pem
local server_priv=${base}/${dir}/server_priv.pem

local root_cert=${base}/${dir}/server_root_cert.pem
local ca_cert=${base}/${dir}/server_ca_cert.pem
local server_cert=${base}/${dir}/server_cert.pem
local cert_path=${base}/${dir}/server_chain.pem

local tpmA="-provider"
local tpmB="tpm2"
local propA="-propquery"
local propB="?provider=tpm2"

# generate keys
for i in ${root_priv} ${ca_priv} ${server_priv}
do
openssl genpkey -config ${cfg} ${tpmA} ${tpmB} ${propA} ${propB} -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out $i
done

export OPENSSL_CONF=${cfg}
# generate root cert
echo "Generate root"
openssl req ${tpmA} ${tpmB} -provider default ${propA} ${propB} \
-config ${cfg} -x509 -section req_server_root -extensions v3_server_root \
-key ${root_priv} -out ${root_cert}
# generate ca cert
echo "Generate ca"
openssl req ${tpmA} ${tpmB} -provider default ${propA} ${propB} \
-config ${cfg} -x509 -section req_server_ca -extensions v3_server_ca \
-key ${ca_priv} -CA ${root_cert} \
-CAkey ${root_priv} -out ${ca_cert}
# generate server cert
echo "Generate server"
openssl req ${tpmA} ${tpmB} -provider default ${propA} ${propB} \
-config ${cfg} -x509 -section req_server -extensions v3_server \
-key ${server_priv} -CA ${ca_cert} \
-CAkey ${ca_priv} -out ${server_cert}

# create bundle
cat ${server_cert} ${ca_cert} > ${cert_path}
}

generate $base $dir
14 changes: 0 additions & 14 deletions lib/staging/tls/tests/tls_connection_test.cpp
Original file line number Diff line number Diff line change
@@ -1,20 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest

/**
* \file testing patched version of OpenSSL
*
* These tests will only pass on a patched version of OpenSSL.
* (they should compile and run fine with some test failures)
*
* It is recommended to also run tests alongside Wireshark
* e.g. `./patched_test --gtest_filter=TlsTest.TLS12`
* to check that the Server Hello record is correctly formed:
* - no status_request or status_request_v2 then no Certificate Status record
* - status_request or status_request_v2 then there is a Certificate Status record
* - never both status_request and status_request_v2
*/

#include "tls_connection_test.hpp"

#include <memory>
Expand Down
35 changes: 35 additions & 0 deletions lib/staging/tls/tests/tls_connection_test.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,5 +285,40 @@ class TlsTest : public testing::Test {
}
};

class TlsTestTpm : public TlsTest {
protected:
void SetUp() override {
server_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
// server_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
server_config.ciphersuites = "";
auto& ref0 = server_config.chains.emplace_back();
ref0.certificate_chain_file = "tpm_pki/server_chain.pem";
ref0.private_key_file = "tpm_pki/server_priv.pem";
ref0.trust_anchor_file = "tpm_pki/server_root_cert.pem";
ref0.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
// auto& ref1 = server_config.chains.emplace_back();
// ref1.certificate_chain_file = "alt_server_chain.pem";
// ref1.private_key_file = "alt_server_priv.pem";
// ref1.trust_anchor_file = "alt_server_root_cert.pem";
// ref1.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
server_config.host = "localhost";
server_config.service = "8444";
server_config.ipv6_only = false;
server_config.verify_client = false;
server_config.io_timeout_ms = 1000; // no lower than 200ms, valgrind need much higher

client_config.cipher_list = "ECDHE-ECDSA-AES128-SHA256";
// client_config.ciphersuites = "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384";
// client_config.certificate_chain_file = "client_chain.pem";
// client_config.private_key_file = "client_priv.pem";
client_config.verify_locations_file = "tpm_pki/server_root_cert.pem";
client_config.io_timeout_ms = 1000;
client_config.verify_server = true;
client_config.status_request = false;
client_config.status_request_v2 = false;
client.reset();
}
};

} // namespace
#endif // TLS_CONNECTION_TEST_HPP_
34 changes: 34 additions & 0 deletions lib/staging/tls/tests/tls_connection_test_tpm.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2024 Pionix GmbH and Contributors to EVerest

#include "tls_connection_test.hpp"
#include <openssl_util.hpp>

#include <memory>
#include <mutex>
#include <poll.h>

using namespace std::chrono_literals;

namespace {
using result_t = tls::Connection::result_t;
using tls::status_request::ClientStatusRequestV2;

constexpr auto server_root_CN = "00000000";
constexpr auto alt_server_root_CN = "11111111";
constexpr auto WAIT_FOR_SERVER_START_TIMEOUT = 50ms;

// ----------------------------------------------------------------------------
// The tests

TEST_F(TlsTestTpm, StartConnectDisconnect) {
start();
connect();
// no status requested
EXPECT_TRUE(is_set(flags_t::connected));
EXPECT_TRUE(is_reset(flags_t::status_request_cb));
EXPECT_TRUE(is_reset(flags_t::status_request));
EXPECT_TRUE(is_reset(flags_t::status_request_v2));
}

} // namespace
19 changes: 15 additions & 4 deletions lib/staging/tls/tests/tls_main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ using namespace std::chrono_literals;

namespace {

const char* short_opts = "ch36";
const char* short_opts = "ch36t";
bool disable_tls1_3{false};
bool enable_tpm{false};
bool ipv6_only{false};
bool verify_client{false};

Expand All @@ -41,12 +42,16 @@ void parse_options(int argc, char** argv) {
case '6':
ipv6_only = true;
break;
case 't':
enable_tpm = true;
break;
case 'h':
case '?':
std::cout << "Usage: " << argv[0] << " [-c|-3|-6]" << std::endl;
std::cout << " -c verify client" << std::endl;
std::cout << " -3 disable TLS 1.3" << std::endl;
std::cout << " -6 IPv6 only" << std::endl;
std::cout << " -t enable TPM" << std::endl;
exit(1);
break;
default:
Expand Down Expand Up @@ -115,9 +120,15 @@ int main(int argc, char** argv) {
}

auto& ref0 = config.chains.emplace_back();
ref0.certificate_chain_file = "server_chain.pem";
ref0.private_key_file = "server_priv.pem";
ref0.trust_anchor_file = "server_root_cert.pem";
if (enable_tpm) {
ref0.certificate_chain_file = "tpm_pki/server_chain.pem";
ref0.private_key_file = "tpm_pki/server_priv.pem";
ref0.trust_anchor_file = "tpm_pki/server_root_cert.pem";
} else {
ref0.certificate_chain_file = "server_chain.pem";
ref0.private_key_file = "server_priv.pem";
ref0.trust_anchor_file = "server_root_cert.pem";
}
ref0.ocsp_response_files = {"ocsp_response.der", "ocsp_response.der"};
auto& ref1 = config.chains.emplace_back();
ref1.certificate_chain_file = "alt_server_chain.pem";
Expand Down
Loading

0 comments on commit af9ced9

Please sign in to comment.