diff --git a/CMakeLists.txt b/CMakeLists.txt
index 757ac09..f6a110a 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -13,6 +13,15 @@ find_package(everest-cmake 0.1 REQUIRED
option(${PROJECT_NAME}_BUILD_TESTING "Build unit tests, used if included as dependency" OFF)
option(BUILD_TESTING "Build unit tests, used if standalone project" OFF)
option(EVSE_SECURITY_INSTALL "Install the library (shared data might be installed anyway)" ${EVC_MAIN_PROJECT})
+option(USING_TPM2 "Include code for using OpenSSL 3 and the tpm2 provider" OFF)
+
+if(USING_TPM2)
+ # OpenSSL property string when using the default provider
+ set(PROPQUERY_DEFAULT "provider!=tpm2")
+ # OpenSSL property string when using the tpm2 provider
+ set(PROPQUERY_TPM2 "?provider=tpm2,tpm2.digest!=yes,tpm2.cipher!=yes")
+endif()
+
# dependencies
if (NOT DISABLE_EDM)
@@ -30,6 +39,7 @@ option(LIBEVSE_CRYPTO_SUPPLIER_OPENSSL "Default OpenSSL cryptography supplier" O
# dependencies
find_package(OpenSSL REQUIRED)
+find_package(date)
add_subdirectory(lib)
diff --git a/README.md b/README.md
index 8f4f0a1..b42d9cc 100644
--- a/README.md
+++ b/README.md
@@ -35,3 +35,27 @@ cmake -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=./dist
make -j$(nproc) install
make test
```
+
+## TPM
+There is a configuration option to configure OpenSSL for use with a TPM.
+`cmake` ... `-DUSING_TPM2=ON`
+Note OpenSSL providers are not available for OpenSSL v1, OpenSSL v3 is required.
+
+The library will use the `UseTPM` flag and the PEM private key file to
+configure whether to use the `default` provider or the `tpm2` provider.
+
+Configuration is managed via propquery strings (see CMakeLists.txt)
+- `PROPQUERY_DEFAULT` is the string to use when selecting the default provider
+- `PROPQUERY_TPM2` is the string to use when selecting the tpm2 provider
+
+propquery|action
+---------|------
+"provider=default"|use the default provider
+"provider=tpm2"|use the tpm2 provider
+"provider!=tpm2"|don't use the tpm provider
+"?provider=tpm2,tpm2.digest!=yes"|prefer the tpm2 provider but not for message digests
+
+For more information see:
+- [Provider for integration of TPM 2.0 to OpenSSL 3.x](https://github.com/tpm2-software/tpm2-openssl)
+- [OpenSSL property](https://www.openssl.org/docs/man3.0/man7/property.html)
+- [OpenSSL provider](https://www.openssl.org/docs/man3.0/man7/provider.html)
diff --git a/include/evse_security/crypto/openssl/openssl_tpm.hpp b/include/evse_security/crypto/openssl/openssl_tpm.hpp
new file mode 100644
index 0000000..f0c5524
--- /dev/null
+++ b/include/evse_security/crypto/openssl/openssl_tpm.hpp
@@ -0,0 +1,161 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Pionix GmbH and Contributors to EVerest
+
+#ifndef OPENSSL_TPM_HPP
+#define OPENSSL_TPM_HPP
+
+#include
+#include
+#include
+#include
+
+// opaque types (from OpenSSL)
+struct ossl_lib_ctx_st; // OpenSSL OSSL_LIB_CTX;
+struct ossl_provider_st; // OpenSSL OSSL_PROVIDER
+
+namespace evse_security {
+
+/// @brief determine if the PEM string is a TSS2 private key
+/// @param private_key_pem string containing the PEM encoded key
+/// @return true when "-----BEGIN TSS2 PRIVATE KEY-----" found
+/// @note works irrespective of OpenSSL version
+bool is_tpm_key_string(const std::string& private_key_pem);
+
+/// @brief determine if the PEM file contains a TSS2 private key
+/// @param private_key_file_pem filename of the PEM file
+/// @return true when file starts "-----BEGIN TSS2 PRIVATE KEY-----"
+/// @note works irrespective of OpenSSL version
+bool is_tpm_key_filename(const std::string& private_key_file_pem);
+
+/// @brief Manage the loading and configuring of OpenSSL providers
+///
+/// There are two providers considered:
+/// - 'default'
+/// - 'tpm2' for working with TSS2 keys (protected by a TPM)
+///
+/// There are two contexts:
+/// - 'global' for general use
+/// - 'tls' for TLS connections
+///
+/// The class also acts as a scoped mutex to prevent changes in the
+/// provider configuration during crypto operations
+///
+/// @note OpenSSL SSL_CTX caches the propquery so updates via
+/// this class may not be effective. See SSL_CTX_new_ex()
+///
+/// This code provides a null implementation when OpenSSL 3 or later isn't
+/// used. The null implementation is also used when -DUSING_TPM2=OFF is
+/// set with cmake.
+///
+/// @note the tpm2-abrmd daemon is needed to support openssl-tpm2 for TLS
+class OpenSSLProvider {
+public:
+ /// @brief supported propquery strings
+ enum class mode_t {
+ default_provider,
+ tpm2_provider,
+ };
+
+private:
+ typedef std::uint8_t flags_underlying_t;
+ enum class flags_t : flags_underlying_t {
+ initialised,
+ tpm2_available,
+ global_tpm2,
+ tls_tpm2,
+ };
+
+ static std::mutex s_mux;
+ static flags_underlying_t s_flags;
+
+ static struct ossl_provider_st* s_global_prov_default_p;
+ static struct ossl_provider_st* s_global_prov_tpm_p;
+ static struct ossl_provider_st* s_tls_prov_default_p;
+ static struct ossl_provider_st* s_tls_prov_tpm_p;
+ static struct ossl_lib_ctx_st* s_tls_libctx_p;
+
+ static inline void reset(flags_t f) {
+ s_flags &= ~(1 << static_cast(f));
+ }
+
+ static inline void set(flags_t f) {
+ s_flags |= 1 << static_cast(f);
+ }
+
+ static inline bool is_set(flags_t f) {
+ return (s_flags & (1 << static_cast(f))) != 0;
+ }
+
+ static inline bool is_reset(flags_t f) {
+ return !is_set(f);
+ }
+
+ /// @brief uodate the flag
+ /// @param f - flag to update
+ /// @param val - whether to set or reset the flag
+ /// @return true when the flag was changed
+ static inline bool update(flags_t f, bool val) {
+ bool bResult = val != is_set(f);
+ if (val) {
+ set(f);
+ } else {
+ reset(f);
+ }
+ return bResult;
+ }
+
+ bool load(struct ossl_provider_st*& default_p, struct ossl_provider_st*& tpm2_p, struct ossl_lib_ctx_st* libctx_p,
+ mode_t mode);
+ inline bool load_global(mode_t mode) {
+ return load(s_global_prov_default_p, s_global_prov_tpm_p, nullptr, mode);
+ }
+ inline bool load_tls(mode_t mode) {
+ return load(s_tls_prov_default_p, s_tls_prov_tpm_p, s_tls_libctx_p, mode);
+ }
+
+ bool set_propstr(struct ossl_lib_ctx_st* libctx, mode_t mode);
+ bool set_mode(struct ossl_lib_ctx_st* libctx, mode_t mode);
+
+public:
+ OpenSSLProvider();
+ ~OpenSSLProvider();
+
+ inline void set_global_mode(mode_t mode) {
+ set_mode(nullptr, mode);
+ }
+
+ inline void set_tls_mode(mode_t mode) {
+ set_mode(s_tls_libctx_p, mode);
+ }
+
+ const char* propquery(mode_t mode) const;
+
+ inline mode_t propquery_global() const {
+ return (is_set(flags_t::global_tpm2)) ? mode_t::tpm2_provider : mode_t::default_provider;
+ }
+ inline mode_t propquery_tls() const {
+ return (is_set(flags_t::tls_tpm2)) ? mode_t::tpm2_provider : mode_t::default_provider;
+ }
+
+ inline const char* propquery_global_str() const {
+ return propquery(propquery_global());
+ }
+ inline const char* propquery_tls_str() const {
+ return propquery(propquery_tls());
+ }
+
+ /// @brief return the TLS OSSL library context
+ inline operator struct ossl_lib_ctx_st *() {
+ return s_tls_libctx_p;
+ }
+
+ static inline bool supports_tpm() {
+ return is_set(flags_t::tpm2_available);
+ }
+
+ static void cleanup();
+};
+
+} // namespace evse_security
+
+#endif // OPENSSL_TPM_HPP
\ No newline at end of file
diff --git a/lib/evse_security/CMakeLists.txt b/lib/evse_security/CMakeLists.txt
index 4955756..48fe206 100644
--- a/lib/evse_security/CMakeLists.txt
+++ b/lib/evse_security/CMakeLists.txt
@@ -15,6 +15,7 @@ target_sources(evse_security
crypto/interface/crypto_supplier.cpp
crypto/openssl/openssl_supplier.cpp
+ crypto/openssl/openssl_tpm.cpp
)
target_include_directories(evse_security
@@ -71,4 +72,12 @@ if(LIBEVSE_CRYPTO_SUPPLIER_OPENSSL)
add_compile_definitions(LIBEVSE_CRYPTO_SUPPLIER_OPENSSL)
endif()
+if(USING_TPM2)
+ target_compile_definitions(evse_security PRIVATE
+ USING_TPM2
+ PROPQUERY_DEFAULT="${PROPQUERY_DEFAULT}"
+ PROPQUERY_TPM2="${PROPQUERY_TPM2}"
+ )
+endif()
+
target_compile_features(evse_security PUBLIC cxx_std_17)
diff --git a/lib/evse_security/crypto/openssl/openssl_supplier.cpp b/lib/evse_security/crypto/openssl/openssl_supplier.cpp
index c72e002..79d8632 100644
--- a/lib/evse_security/crypto/openssl/openssl_supplier.cpp
+++ b/lib/evse_security/crypto/openssl/openssl_supplier.cpp
@@ -19,9 +19,7 @@
#define EVSE_OPENSSL_VER_3 (OPENSSL_VERSION_NUMBER >= 0x30000000L)
-#if EVSE_OPENSSL_VER_3
-#include
-#endif
+#include
namespace evse_security {
@@ -85,36 +83,9 @@ const char* OpenSSLSupplier::get_supplier_name() {
return OPENSSL_VERSION_TEXT;
}
-bool OpenSSLSupplier::supports_tpm() {
-#if EVSE_OPENSSL_VER_3
- static bool support_checked = false;
- static bool supports_tpm = false;
-
- // TODO (ioan): Check if somehow the TPM provider was already
- // loaded case in which we just need to do the self-test
- if (support_checked == false) {
- OSSL_PROVIDER* tpm2_provider = OSSL_PROVIDER_load(nullptr, PROVIDER_TPM);
-
- if (tpm2_provider != nullptr) {
- supports_tpm = OSSL_PROVIDER_available(nullptr, PROVIDER_TPM) && OSSL_PROVIDER_self_test(tpm2_provider);
- OSSL_PROVIDER_unload(tpm2_provider);
- } else {
- supports_tpm = false;
- }
- }
-
- return supports_tpm;
-#else
- return false;
-#endif
-}
-
bool OpenSSLSupplier::supports_tpm_key_creation() {
- if (supports_tpm()) {
- return true;
- }
-
- return false;
+ OpenSSLProvider provider;
+ return provider.supports_tpm();
}
static bool export_key_internal(const KeyGenerationInfo& key_info, const EVP_PKEY_ptr& evp_key) {
@@ -158,176 +129,184 @@ static bool export_key_internal(const KeyGenerationInfo& key_info, const EVP_PKE
return true;
}
-static bool generate_key_internal_tpm(const KeyGenerationInfo& key_info, EVP_PKEY_ptr& out_key) {
-#if EVSE_OPENSSL_VER_3
- // Acquire TPM context
- TPMScopedProvider tpm_provider;
-
- // Generate TPM key. For reference see:
- // https://github.com/Infineon/optiga-tpm-cheatsheet/blob/master/openssl3-lib-general-examples/examples.c#L104
-
- OSSL_PARAM params[2];
- EVP_PKEY_CTX_ptr ctx;
-
- CryptoKeyType type = key_info.key_type;
- if (type == CryptoKeyType::RSA_TPM20) {
- unsigned int bits = 2048;
- params[0] = OSSL_PARAM_construct_uint("bits", &bits);
- ctx = EVP_PKEY_CTX_ptr(EVP_PKEY_CTX_new_from_name(NULL, "RSA", "provider=tpm2"));
- } else if (type == CryptoKeyType::EC_prime256v1 || type == CryptoKeyType::EC_secp384r1) {
- char* group_256 = "P-256";
- char* group_384 = "P-384";
+constexpr const char* kt_rsa = "RSA";
+constexpr const char* kt_ec = "EC";
+
+static bool s_generate_key(const KeyGenerationInfo& key_info, KeyHandle_ptr& out_key, EVP_PKEY_CTX_ptr& ctx) {
+
+ unsigned int bits = 0;
+ char group_256[] = "P-256";
+ char group_384[] = "P-384";
+ char* group = nullptr;
+ std::size_t group_sz = 0;
+ int nid = NID_undef;
+
+ bool bResult = true;
+ bool bEC = true;
+
+ // note when using tpm2 some key_types may not be supported.
+
+ EVLOG_info << "Key parameters";
+ switch (key_info.key_type) {
+ case CryptoKeyType::RSA_TPM20:
+ bits = 2048;
+ bEC = false;
+ break;
+ case CryptoKeyType::RSA_3072:
+ bits = 3072;
+ bEC = false;
+ break;
+ case CryptoKeyType::RSA_7680:
+ bits = 7680;
+ bEC = false;
+ break;
+ case CryptoKeyType::EC_prime256v1:
+ group = group_256;
+ group_sz = sizeof(group_256);
+ nid = NID_X9_62_prime256v1;
+ break;
+ case CryptoKeyType::EC_secp384r1:
+ default:
+ group = group_384;
+ group_sz = sizeof(group_384);
+ nid = NID_secp384r1;
+ break;
+ }
- if (type == CryptoKeyType::EC_prime256v1) {
- params[0] = OSSL_PARAM_construct_utf8_string("group", group_256, sizeof(group_256));
- } else {
- params[0] = OSSL_PARAM_construct_utf8_string("group", group_384, sizeof(group_384));
- }
+#if EVSE_OPENSSL_VER_3
+ OSSL_PARAM params[2] = {NULL, NULL};
- ctx = EVP_PKEY_CTX_ptr(EVP_PKEY_CTX_new_from_name(NULL, "EC", "provider=tpm2"));
+ if (bEC) {
+ params[0] = OSSL_PARAM_construct_utf8_string("group", group, group_sz);
+ EVLOG_info << "Key parameters: EC";
+ ctx = EVP_PKEY_CTX_ptr(EVP_PKEY_CTX_new_from_name(nullptr, kt_ec, nullptr));
} else {
- EVLOG_error << "Failed to find TPM keygen generation type!";
- return false;
+ params[0] = OSSL_PARAM_construct_uint("bits", &bits);
+ EVLOG_info << "Key parameters: RSA";
+ ctx = EVP_PKEY_CTX_ptr(EVP_PKEY_CTX_new_from_name(nullptr, kt_rsa, nullptr));
}
params[1] = OSSL_PARAM_construct_end();
- if (nullptr == ctx.get()) {
- EVLOG_error << "Failed to create tpm2 provider context!";
- return false;
+ if (bResult) {
+ EVLOG_info << "Key parameters done";
+ if (nullptr == ctx.get()) {
+ EVLOG_error << "create key context failed!";
+ ERR_print_errors_fp(stderr);
+ bResult = false;
+ }
}
- if (EVP_PKEY_keygen_init(ctx.get()) <= 0 || EVP_PKEY_CTX_set_params(ctx.get(), params) <= 0) {
- EVLOG_error << "Failed to init tpm2 provider context!";
- return false;
+ if (bResult) {
+ EVLOG_info << "Keygen init";
+ if (EVP_PKEY_keygen_init(ctx.get()) <= 0 || EVP_PKEY_CTX_set_params(ctx.get(), params) <= 0) {
+ EVLOG_error << "Keygen init failed";
+ ERR_print_errors_fp(stderr);
+ bResult = false;
+ }
}
- EVP_PKEY* pkey = NULL;
- if (EVP_PKEY_generate(ctx.get(), &pkey) <= 0) {
- EVLOG_error << "Failed to generate tpm2 key!";
- return false;
+ EVP_PKEY* pkey = nullptr;
+
+ if (bResult) {
+ EVLOG_info << "Key generate";
+ if (EVP_PKEY_generate(ctx.get(), &pkey) <= 0) {
+ EVLOG_error << "Failed to generate tpm2 key!";
+ ERR_print_errors_fp(stderr);
+ bResult = false;
+ }
}
- out_key = EVP_PKEY_ptr(pkey);
+ auto evp_key = EVP_PKEY_ptr(pkey);
- // Export keys too
- return export_key_internal(key_info, out_key);
#else
- return false;
-#endif
-}
-
-static bool generate_key_internal(const KeyGenerationInfo& key_info, EVP_PKEY_ptr& out_key) {
- static unsigned long RSA_PRIME = 65537;
+ constexpr unsigned long RSA_PRIME = 65537;
+ EVP_PKEY_ptr evp_key = EVP_PKEY_ptr(EVP_PKEY_new());
- EVP_PKEY_ptr evp_key;
+ if (bEC) {
+ // Ignore deprecation warnings on the EC gen functions since we need OpenSSL 1.1 support
+ EC_KEY_ptr ec_key(EC_KEY_new_by_curve_name(nid));
- CryptoKeyType type = key_info.key_type;
- if (type == CryptoKeyType::RSA_3072 || type == CryptoKeyType::RSA_7680) {
- evp_key = EVP_PKEY_ptr(EVP_PKEY_new());
+ if (ec_key.get() == nullptr) {
+ EVLOG_error << "Failed create EC key by curve!";
+ bResult = false;
+ }
- int bits = 0;
- if (type == CryptoKeyType::RSA_3072) {
- bits = 3072;
- } else {
- bits = 7680;
+ if (bResult) {
+ // generate ec key pair
+ if (EC_KEY_generate_key(ec_key.get()) != 1) {
+ EVLOG_error << "Failed to generate EC key!";
+ bResult = false;
+ }
}
+ if (bResult) {
+ // Not auto-released since on assign the ec_key will be released with the owner evp_pkey
+ EC_KEY* key = ec_key.release();
+
+ // Assigns the key, we must not release it here, since it is 'owned' by the evp_key
+ EVP_PKEY_assign_EC_KEY(evp_key.get(), key);
+ }
+ } else {
RSA_ptr rsa_key(RSA_generate_key(bits, RSA_PRIME, nullptr, nullptr));
if (rsa_key.get() == nullptr) {
EVLOG_error << "Failed create RSA key!";
- return false;
+ ERR_print_errors_fp(stderr);
+ bResult = false;
}
- // Not auto-released since on assign the ec_key will be released with the owner evp_pkey
- RSA* key = rsa_key.release();
-
- // Assigns the key, we must not release it here, since it is 'owned' by the evp_key
- EVP_PKEY_assign_RSA(evp_key.get(), key);
- } else if (type == CryptoKeyType::EC_prime256v1 || type == CryptoKeyType::EC_secp384r1) {
- evp_key = EVP_PKEY_ptr(EVP_PKEY_new());
-
- int nid = NID_undef;
+ if (bResult) {
+ // Not auto-released since on assign the ec_key will be released with the owner evp_pkey
+ RSA* key = rsa_key.release();
- if (type == CryptoKeyType::EC_prime256v1) {
- nid = NID_X9_62_prime256v1;
- } else {
- nid = NID_secp384r1;
+ // Assigns the key, we must not release it here, since it is 'owned' by the evp_key
+ EVP_PKEY_assign_RSA(evp_key.get(), key);
}
-
- // Ignore deprecation warnings on the EC gen functions since we need OpenSSL 1.1 support
- EC_KEY_ptr ec_key(EC_KEY_new_by_curve_name(nid));
-
- if (ec_key.get() == nullptr) {
- EVLOG_error << "Failed create EC key by curve!";
- return false;
- }
-
- // generate ec key pair
- if (false == EC_KEY_generate_key(ec_key.get())) {
- EVLOG_error << "Failed to generate EC key!";
- return false;
- }
-
- // Not auto-released since on assign the ec_key will be released with the owner evp_pkey
- EC_KEY* key = ec_key.release();
-
- // Assigns the key, we must not release it here, since it is 'owned' by the evp_key
- EVP_PKEY_assign_EC_KEY(evp_key.get(), key);
- } else {
- EVLOG_error << "Failed to find keygen generation type!";
- return false;
}
- // Attempt export key
- if (evp_key) {
- out_key = std::move(evp_key);
- return export_key_internal(key_info, out_key);
+#endif
+
+ if (bResult) {
+ EVLOG_info << "Key export";
+ // Export keys too
+ bResult = export_key_internal(key_info, evp_key);
+ EVP_PKEY* raw_key_handle = evp_key.release();
+ out_key = std::make_unique(raw_key_handle);
}
- EVLOG_error << "Undefined key generation failure!";
- return false;
+ return bResult;
}
bool OpenSSLSupplier::generate_key(const KeyGenerationInfo& key_info, KeyHandle_ptr& out_key) {
- // Sanity checks
- if (key_info.generate_on_tpm && false == supports_tpm()) {
- EVLOG_error << "Failed to generate TPM key! The TPM is not supported!";
- return false;
- }
-
- EVP_PKEY_ptr evp_key;
+ KeyHandle_ptr gen_key;
+ EVP_PKEY_CTX_ptr ctx;
+ OpenSSLProvider provider;
+ bool bResult = true;
if (key_info.generate_on_tpm) {
- // Generate key internally
- if (false == generate_key_internal_tpm(key_info, evp_key)) {
- EVLOG_error << "Failed to internally generate TPM key!";
- return false;
- }
+ provider.set_global_mode(OpenSSLProvider::mode_t::tpm2_provider);
+
} else {
- if (false == generate_key_internal(key_info, evp_key)) {
- EVLOG_error << "Failed to internally generate key!";
- return false;
- }
+ provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
}
- // Errors passed, transfer key handle ownership
- EVP_PKEY* raw_key_handle = evp_key.release();
- out_key = std::make_unique(raw_key_handle);
-
- return true;
+ bResult = s_generate_key(key_info, gen_key, ctx);
+ if (!bResult) {
+ EVLOG_error << "Failed to generate csr pub/priv key!";
+ }
+ return bResult;
}
std::vector OpenSSLSupplier::load_certificates(const std::string& data, const EncodingFormat encoding) {
+ std::vector certificates;
+
BIO_ptr bio(BIO_new_mem_buf(data.data(), static_cast(data.size())));
if (!bio) {
throw CertificateLoadException("Failed to create BIO from data");
}
- std::vector certificates;
-
if (encoding == EncodingFormat::PEM) {
STACK_OF(X509_INFO)* allcerts = PEM_X509_INFO_read_bio(bio.get(), nullptr, nullptr, nullptr);
@@ -567,6 +546,8 @@ CertificateValidationError OpenSSLSupplier::x509_verify_certificate_chain(X509Ha
bool allow_future_certificates,
const std::optional dir_path,
const std::optional file_path) {
+ OpenSSLProvider provider;
+ provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
X509_STORE_ptr store_ptr(X509_STORE_new());
X509_STORE_CTX_ptr store_ctx_ptr(X509_STORE_CTX_new());
@@ -612,22 +593,37 @@ bool OpenSSLSupplier::x509_check_private_key(X509Handle* handle, std::string pri
if (x509 == nullptr)
return false;
+ OpenSSLProvider provider;
+
+ const bool tpm_key = is_tpm_key_string(private_key);
+ if (tpm_key) {
+ provider.set_global_mode(OpenSSLProvider::mode_t::tpm2_provider);
+ } else {
+ provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
+ }
+ EVLOG_info << "TPM Key: " << tpm_key;
+
BIO_ptr bio(BIO_new_mem_buf(private_key.c_str(), -1));
// Passing password string since if NULL is provided, the password CB will be called
EVP_PKEY_ptr evp_pkey(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, (void*)password.value_or("").c_str()));
+ bool bResult = true;
if (!evp_pkey) {
EVLOG_warning << "Invalid evp_pkey: " << private_key << " error: " << ERR_error_string(ERR_get_error(), NULL)
<< " Password configured correctly?";
+ ERR_print_errors_fp(stderr);
- return false;
+ bResult = false;
}
- return (X509_check_private_key(x509, evp_pkey.get()) == 1);
+ bResult = bResult && X509_check_private_key(x509, evp_pkey.get()) == 1;
+ return bResult;
}
bool OpenSSLSupplier::x509_verify_signature(X509Handle* handle, const std::vector& signature,
const std::vector& data) {
+ OpenSSLProvider provider;
+ provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
// extract public key
X509* x509 = get(handle);
@@ -674,10 +670,19 @@ bool OpenSSLSupplier::x509_verify_signature(X509Handle* handle, const std::vecto
}
bool OpenSSLSupplier::x509_generate_csr(const CertificateSigningRequestInfo& csr_info, std::string& out_csr) {
+
KeyHandle_ptr gen_key;
+ EVP_PKEY_CTX_ptr ctx;
+ OpenSSLProvider provider;
- if (false == generate_key(csr_info.key_info, gen_key)) {
- EVLOG_error << "Failed to generate csr pub/priv key!";
+ if (csr_info.key_info.generate_on_tpm) {
+ provider.set_global_mode(OpenSSLProvider::mode_t::tpm2_provider);
+
+ } else {
+ provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
+ }
+
+ if (false == s_generate_key(csr_info.key_info, gen_key, ctx)) {
return false;
}
@@ -717,7 +722,11 @@ bool OpenSSLSupplier::x509_generate_csr(const CertificateSigningRequestInfo& csr
sk_X509_EXTENSION_push(extensions, ext_key_usage);
sk_X509_EXTENSION_push(extensions, ext_basic_constraints);
- if (false == X509_REQ_add_extensions(x509_req_ptr.get(), extensions)) {
+ const bool result = X509_REQ_add_extensions(x509_req_ptr.get(), extensions);
+ X509_EXTENSION_free(ext_key_usage);
+ X509_EXTENSION_free(ext_basic_constraints);
+ sk_X509_EXTENSION_free(extensions);
+ if (!result) {
EVLOG_error << "Failed to add csr extensions!";
return false;
}
@@ -725,17 +734,7 @@ bool OpenSSLSupplier::x509_generate_csr(const CertificateSigningRequestInfo& csr
// sign the certificate with the private key
bool x509_signed = false;
- if (csr_info.key_info.generate_on_tpm) {
-#if EVSE_OPENSSL_VER_3
- TPMScopedProvider tpm_provider;
- x509_signed = X509_REQ_sign(x509_req_ptr.get(), key, EVP_sha256());
-#else
- EVLOG_error << "TPM operations not supported for CSR signing!";
- return false;
-#endif
- } else {
- x509_signed = X509_REQ_sign(x509_req_ptr.get(), key, EVP_sha256());
- }
+ x509_signed = X509_REQ_sign(x509_req_ptr.get(), key, EVP_sha256());
if (x509_signed == false) {
EVLOG_error << "Failed to sign csr!";
diff --git a/lib/evse_security/crypto/openssl/openssl_tpm.cpp b/lib/evse_security/crypto/openssl/openssl_tpm.cpp
new file mode 100644
index 0000000..ad1ee75
--- /dev/null
+++ b/lib/evse_security/crypto/openssl/openssl_tpm.cpp
@@ -0,0 +1,257 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright Pionix GmbH and Contributors to EVerest
+
+#include
+
+#include
+
+#define USING_OPENSSL_3 (OPENSSL_VERSION_NUMBER >= 0x30000000L)
+
+#if USING_OPENSSL_3 && defined(USING_TPM2)
+// OpenSSL3 without TPM will use the default provider anyway
+#include
+#include
+#include
+
+#include
+#define USING_OPENSSL_3_TPM
+#else
+// dummy structures for non-OpenSSL 3
+struct ossl_provider_st {};
+typedef struct ossl_provider_st OSSL_PROVIDER;
+struct ossl_lib_ctx_st;
+typedef struct ossl_lib_ctx_st OSSL_LIB_CTX;
+#endif
+
+namespace evse_security {
+
+bool is_tpm_key_string(const std::string& private_key_pem) {
+ return private_key_pem.find("-----BEGIN TSS2 PRIVATE KEY-----") != std::string::npos;
+}
+
+bool is_tpm_key_filename(const std::string& private_key_file_pem) {
+ std::ifstream key_file(private_key_file_pem);
+ std::string line;
+ std::getline(key_file, line);
+ key_file.close();
+ return line == "-----BEGIN TSS2 PRIVATE KEY-----";
+}
+
+#ifdef USING_OPENSSL_3_TPM
+// ----------------------------------------------------------------------------
+// class OpenSSLProvider OpenSSL 3
+
+static const char* mode_t_str[2] = {
+ "default provider", // mode_t::default_provider
+ "tpm2 provider" // mode_t::tpm2_provider
+};
+
+static_assert(static_cast(OpenSSLProvider::mode_t::default_provider) == 0);
+static_assert(static_cast(OpenSSLProvider::mode_t::tpm2_provider) == 1);
+
+std::ostream& operator<<(std::ostream& out, OpenSSLProvider::mode_t mode) {
+ const unsigned int idx = static_cast(mode);
+ if (idx <= static_cast(OpenSSLProvider::mode_t::tpm2_provider)) {
+ out << mode_t_str[idx];
+ }
+ return out;
+}
+
+static bool s_load_and_test_provider(OSSL_PROVIDER*& provider, OSSL_LIB_CTX* libctx, const char* provider_name) {
+ bool bResult = true;
+#ifdef DEBUG
+ const char* modestr = (libctx == nullptr) ? "global" : "TLS";
+ EVLOG_info << "Loading " << modestr << " provider: " << provider_name;
+#endif
+ if ((provider = OSSL_PROVIDER_load(libctx, provider_name)) == nullptr) {
+ EVLOG_error << "Unable to load OSSL_PROVIDER: " << provider_name;
+ ERR_print_errors_fp(stderr);
+ bResult = false;
+ } else {
+#ifdef DEBUG
+ EVLOG_info << "Testing " << modestr << " provider: " << provider_name;
+#endif
+ if (OSSL_PROVIDER_self_test(provider) == 0) {
+ EVLOG_error << "Self-test failed: OSSL_PROVIDER: " << provider_name;
+ ERR_print_errors_fp(stderr);
+ OSSL_PROVIDER_unload(provider);
+ provider = nullptr;
+ bResult = false;
+ }
+ }
+ return bResult;
+}
+
+std::mutex OpenSSLProvider::s_mux;
+OpenSSLProvider::flags_underlying_t OpenSSLProvider::s_flags = 0;
+
+OSSL_PROVIDER* OpenSSLProvider::s_global_prov_default_p = nullptr;
+OSSL_PROVIDER* OpenSSLProvider::s_global_prov_tpm_p = nullptr;
+OSSL_PROVIDER* OpenSSLProvider::s_tls_prov_default_p = nullptr;
+OSSL_PROVIDER* OpenSSLProvider::s_tls_prov_tpm_p = nullptr;
+OSSL_LIB_CTX* OpenSSLProvider::s_tls_libctx_p = nullptr;
+
+// propquery strings (see CMakeLists.txt)
+static const char* s_default_provider = PROPQUERY_DEFAULT;
+static const char* s_tpm2_provider = PROPQUERY_TPM2;
+
+OpenSSLProvider::OpenSSLProvider() {
+ s_mux.lock();
+
+ if (is_reset(flags_t::initialised)) {
+ set(flags_t::initialised);
+ OPENSSL_atexit(&OpenSSLProvider::cleanup);
+
+ if (s_tls_libctx_p == nullptr) {
+ s_tls_libctx_p = OSSL_LIB_CTX_new();
+ if (s_tls_libctx_p == nullptr) {
+ EVLOG_error << "Unable to create OpenSSL library context";
+ ERR_print_errors_fp(stderr);
+ }
+ }
+
+ // load providers for global context
+ (void)load_global(mode_t::default_provider);
+ (void)load_global(mode_t::tpm2_provider);
+ (void)set_propstr(nullptr, mode_t::default_provider);
+
+ // load providers for tls context
+ (void)load_tls(mode_t::default_provider);
+ (void)load_tls(mode_t::tpm2_provider);
+ (void)set_propstr(s_tls_libctx_p, mode_t::default_provider);
+ }
+}
+
+OpenSSLProvider::~OpenSSLProvider() {
+ s_mux.unlock();
+}
+
+bool OpenSSLProvider::load(OSSL_PROVIDER*& default_p, OSSL_PROVIDER*& tpm2_p, OSSL_LIB_CTX* libctx_p, mode_t mode) {
+ bool bResult = true;
+ switch (mode) {
+ case mode_t::tpm2_provider:
+ if (tpm2_p == nullptr) {
+ bResult = s_load_and_test_provider(tpm2_p, libctx_p, "tpm2");
+ update(flags_t::tpm2_available, bResult);
+ }
+ break;
+ case mode_t::default_provider:
+ default:
+ if (default_p == nullptr) {
+ bResult = s_load_and_test_provider(default_p, libctx_p, "default");
+ }
+ break;
+ }
+ return bResult;
+}
+
+bool OpenSSLProvider::set_propstr(OSSL_LIB_CTX* libctx, mode_t mode) {
+ const char* propstr = propquery(mode);
+#ifdef DEBUG
+ EVLOG_info << "Setting " << ((libctx == nullptr) ? "global" : "tls") << " propquery: " << propstr;
+#endif
+ const bool result = EVP_set_default_properties(libctx, propstr) == 1;
+ if (!result) {
+ EVLOG_error << "Unable to set OpenSSL provider: " << mode;
+ ERR_print_errors_fp(stderr);
+ }
+ return result;
+}
+
+bool OpenSSLProvider::set_mode(OSSL_LIB_CTX* libctx, mode_t mode) {
+ bool bResult;
+ const flags_t f = (libctx == nullptr) ? flags_t::global_tpm2 : flags_t::tls_tpm2;
+
+ const bool apply = update(f, mode == mode_t::tpm2_provider);
+ if (apply) {
+ bResult = set_propstr(libctx, mode);
+ }
+
+ return bResult;
+}
+
+const char* OpenSSLProvider::propquery(mode_t mode) const {
+ const char* propquery_str = nullptr;
+
+ switch (mode) {
+ case mode_t::default_provider:
+ propquery_str = s_default_provider;
+ break;
+ case mode_t::tpm2_provider:
+ propquery_str = s_tpm2_provider;
+ break;
+ default:
+ break;
+ }
+
+ return propquery_str;
+}
+
+void OpenSSLProvider::cleanup() {
+ // at the point this is called logging may not be available
+ // relying on OpenSSL errors
+ std::lock_guard guard(s_mux);
+ if (OSSL_PROVIDER_unload(s_tls_prov_tpm_p) == 0) {
+ ERR_print_errors_fp(stderr);
+ }
+ if (OSSL_PROVIDER_unload(s_tls_prov_default_p) == 0) {
+ ERR_print_errors_fp(stderr);
+ }
+ if (OSSL_PROVIDER_unload(s_global_prov_tpm_p) == 0) {
+ ERR_print_errors_fp(stderr);
+ }
+ if (OSSL_PROVIDER_unload(s_global_prov_default_p) == 0) {
+ ERR_print_errors_fp(stderr);
+ }
+
+ s_tls_prov_tpm_p = nullptr;
+ s_tls_prov_default_p = nullptr;
+ s_global_prov_tpm_p = nullptr;
+ s_global_prov_default_p = nullptr;
+
+ OSSL_LIB_CTX_free(s_tls_libctx_p);
+
+ s_tls_libctx_p = nullptr;
+ s_flags = 0;
+}
+
+#else // USING_OPENSSL_3_TPM
+// ----------------------------------------------------------------------------
+// class OpenSSLProvider dummy where OpenSSL 3 is not available
+
+OpenSSLProvider::flags_underlying_t OpenSSLProvider::s_flags = 0;
+
+OSSL_PROVIDER* OpenSSLProvider::s_global_prov_default_p = nullptr;
+OSSL_PROVIDER* OpenSSLProvider::s_global_prov_tpm_p = nullptr;
+OSSL_PROVIDER* OpenSSLProvider::s_tls_prov_default_p = nullptr;
+OSSL_PROVIDER* OpenSSLProvider::s_tls_prov_tpm_p = nullptr;
+OSSL_LIB_CTX* OpenSSLProvider::s_tls_libctx_p = nullptr;
+
+OpenSSLProvider::OpenSSLProvider() {
+}
+
+OpenSSLProvider::~OpenSSLProvider() {
+}
+
+bool OpenSSLProvider::load(OSSL_PROVIDER*&, OSSL_PROVIDER*&, OSSL_LIB_CTX*, mode_t) {
+ return false;
+}
+
+bool OpenSSLProvider::set_propstr(OSSL_LIB_CTX*, mode_t) {
+ return false;
+}
+
+bool OpenSSLProvider::set_mode(OSSL_LIB_CTX*, mode_t) {
+ return false;
+}
+
+const char* OpenSSLProvider::propquery(mode_t mode) const {
+ return nullptr;
+}
+
+void OpenSSLProvider::cleanup() {
+}
+
+#endif // USING_OPENSSL_3_TPM
+
+} // namespace evse_security
diff --git a/lib/evse_security/evse_security.cpp b/lib/evse_security/evse_security.cpp
index 44ed2ca..4d5da39 100644
--- a/lib/evse_security/evse_security.cpp
+++ b/lib/evse_security/evse_security.cpp
@@ -701,6 +701,8 @@ std::string EvseSecurity::generate_certificate_signing_request(LeafCertificateTy
fs::path key_path;
+ EVLOG_info << "generate_certificate_signing_request: create filename";
+
// Make a difference between normal and tpm keys for identification
const auto file_name =
std::string("SECC_LEAF_") +
@@ -711,6 +713,7 @@ std::string EvseSecurity::generate_certificate_signing_request(LeafCertificateTy
} else if (certificate_type == LeafCertificateType::V2G) {
key_path = this->directories.secc_leaf_key_directory / file_name;
} else {
+ EVLOG_error << "generate_certificate_signing_request: create filename failed";
throw std::runtime_error("Attempt to generate CSR for MF certificate");
}
@@ -730,6 +733,7 @@ std::string EvseSecurity::generate_certificate_signing_request(LeafCertificateTy
info.key_info.private_key_pass = private_key_password;
}
+ EVLOG_info << "generate_certificate_signing_request: start";
if (false == CryptoSupplier::x509_generate_csr(info, csr)) {
throw std::runtime_error("Failed to generate certificate signing request!");
}
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5bfe4cc..471317b 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -1,5 +1,3 @@
-
-
set(TEST_TARGET_NAME ${PROJECT_NAME}_tests)
add_executable(${TEST_TARGET_NAME})
@@ -7,9 +5,11 @@ target_include_directories(${TEST_TARGET_NAME} PUBLIC ${GTEST_INCLUDE_DIRS})
target_sources(${TEST_TARGET_NAME} PRIVATE
tests.cpp
+ openssl_supplier_test.cpp
)
find_package(GTest REQUIRED)
+find_package(OpenSSL REQUIRED)
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
evse_security
@@ -17,6 +17,17 @@ target_link_libraries(${TEST_TARGET_NAME} PRIVATE
${GTEST_MAIN_LIBRARIES}
)
+if(USING_TPM2)
+ target_sources(${TEST_TARGET_NAME} PRIVATE
+ openssl_supplier_test_tpm.cpp
+ )
+ target_compile_definitions(${TEST_TARGET_NAME} PRIVATE
+ USING_TPM2
+ PROPQUERY_DEFAULT="${PROPQUERY_DEFAULT}"
+ PROPQUERY_TPM2="${PROPQUERY_TPM2}"
+ )
+endif()
+
if(LIBEVSE_CRYPTO_SUPPLIER_OPENSSL)
add_compile_definitions(LIBEVSE_CRYPTO_SUPPLIER_OPENSSL)
endif()
@@ -47,3 +58,13 @@ install(
DESTINATION "${CMAKE_BINARY_DIR}/tests"
FILES_MATCHING PATTERN "*"
)
+
+install(
+ PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/create-pki.sh"
+ DESTINATION "${CMAKE_BINARY_DIR}/tests"
+)
+
+install(
+ FILES "${CMAKE_CURRENT_SOURCE_DIR}/openssl-pki.conf"
+ DESTINATION "${CMAKE_BINARY_DIR}/tests"
+)
diff --git a/tests/create-pki.sh b/tests/create-pki.sh
new file mode 100755
index 0000000..427a95b
--- /dev/null
+++ b/tests/create-pki.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+base=.
+cfg=./openssl-pki.conf
+tpm=$1
+
+if [ -z "$tpm" ]; then
+ dir=pki
+else
+ dir=tpm_pki
+fi
+
+[ ! -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}/root_priv.pem
+ local ca_priv=${base}/${dir}/ca_priv.pem
+ local server_priv=${base}/${dir}/server_priv.pem
+
+ local root_cert=${base}/${dir}/root_cert.pem
+ local ca_cert=${base}/${dir}/ca_cert.pem
+ local server_cert=${base}/${dir}/server_cert.pem
+ local cert_path=${base}/${dir}/cert_path.pem
+
+ local tpmA tpmB
+ local propA propB
+ if [ -n "$3" ]; then
+ tpmA="-provider"
+ tpmB="tpm2"
+ propA="-propquery"
+ propB="?provider=tpm2"
+ fi
+
+ # generate keys
+ for i in ${root_priv} ${ca_priv} ${server_priv}
+ do
+ openssl genpkey -config ${cfg} ${tpmA} ${tpmB} ${propA} ${propB} -algorithm RSA -pkeyopt bits:2048 -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_root -extensions v3_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_ca -extensions v3_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 $tpm
diff --git a/tests/openssl-pki.conf b/tests/openssl-pki.conf
new file mode 100644
index 0000000..28e9de0
--- /dev/null
+++ b/tests/openssl-pki.conf
@@ -0,0 +1,78 @@
+openssl_conf = openssl_init
+
+[openssl_init]
+providers = provider_section
+
+[provider_section]
+default = default_section
+tpm2 = tpm2_section
+base = base_section
+
+[default_section]
+activate = 1
+
+[tpm2_section]
+activate = 1
+
+[base_section]
+activate = 1
+
+[tpm2tss_section]
+engine_id = tpm2tss
+dynamic_path = /usr/lib/engines-3/libtpm2tss.so
+init = 1
+
+[req_root]
+distinguished_name = req_dn_root
+utf8 = yes
+prompt = no
+req_extensions = v3_root
+
+[req_ca]
+distinguished_name = req_dn_ca
+utf8 = yes
+prompt = no
+req_extensions = v3_ca
+
+[req_server]
+distinguished_name = req_dn_server
+utf8 = yes
+prompt = no
+req_extensions = v3_server
+
+[req_dn_root]
+C = GB
+O = Pionix
+L = London
+CN = Root Trust Anchor
+
+[req_dn_ca]
+C = GB
+O = Pionix
+L = London
+CN = Intermediate CA
+
+[req_dn_server]
+C = GB
+O = Pionix
+L = London
+CN = 00000000
+
+[v3_root]
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer:always
+basicConstraints = critical, CA:true, pathlen:2
+keyUsage = keyCertSign, cRLSign
+
+[v3_ca]
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer:always
+basicConstraints = critical, CA:true
+keyUsage = keyCertSign, cRLSign
+
+[v3_server]
+subjectKeyIdentifier=hash
+authorityKeyIdentifier=keyid:always,issuer:always
+keyUsage = digitalSignature, keyEncipherment, keyAgreement
+extendedKeyUsage = serverAuth, clientAuth
+subjectAltName = IP:192.168.240.1, DNS:pionix.com
diff --git a/tests/openssl_supplier_test.cpp b/tests/openssl_supplier_test.cpp
new file mode 100644
index 0000000..d292dde
--- /dev/null
+++ b/tests/openssl_supplier_test.cpp
@@ -0,0 +1,117 @@
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+using namespace evse_security;
+
+namespace {
+
+static std::string getFile(const std::string name) {
+ std::ifstream file(name);
+ return std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator());
+}
+
+class OpenSSLSupplierTest : public testing::Test {
+protected:
+ static void SetUpTestSuite() {
+ std::system("./create-pki.sh");
+ }
+};
+
+TEST_F(OpenSSLSupplierTest, generate_key_RSA_TPM20) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::RSA_TPM20, false, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTest, generate_key_RSA_3072) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::RSA_3072, false, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTest, generate_key_RSA_7680) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::RSA_7680, false, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTest, generate_key_EC_prime256v1) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::EC_prime256v1, false, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTest, generate_key_EC_EC_secp384r1) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::EC_secp384r1, false, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTest, load_certificates) {
+ auto file = getFile("pki/cert_path.pem");
+ auto res = OpenSSLSupplier::load_certificates(file, EncodingFormat::PEM);
+ ASSERT_EQ(res.size(), 2);
+}
+
+TEST_F(OpenSSLSupplierTest, x509_check_private_key) {
+ auto cert_leaf = getFile("pki/server_cert.pem");
+ auto res_leaf = OpenSSLSupplier::load_certificates(cert_leaf, EncodingFormat::PEM);
+ auto cert = res_leaf[0].get();
+ auto key = getFile("pki/server_priv.pem");
+ auto res = OpenSSLSupplier::x509_check_private_key(cert, key, std::nullopt);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTest, x509_verify_certificate_chain) {
+ auto cert_path = getFile("pki/cert_path.pem");
+ auto cert_leaf = getFile("pki/server_cert.pem");
+
+ auto res_path = OpenSSLSupplier::load_certificates(cert_path, EncodingFormat::PEM);
+ auto res_leaf = OpenSSLSupplier::load_certificates(cert_leaf, EncodingFormat::PEM);
+
+ std::vector parents;
+
+ for (auto& i : res_path) {
+ parents.push_back(i.get());
+ }
+
+ auto res = OpenSSLSupplier::x509_verify_certificate_chain(res_leaf[0].get(), parents, true, std::nullopt,
+ "pki/root_cert.pem");
+ ASSERT_EQ(res, CertificateValidationError::NoError);
+}
+
+TEST_F(OpenSSLSupplierTest, x509_generate_csr) {
+ std::string csr;
+ CertificateSigningRequestInfo csr_info = {
+ 0,
+ "UK",
+ "Pionix",
+ "0123456789",
+ {CryptoKeyType::EC_prime256v1, false, std::nullopt, "pki/csr_key.pem", std::nullopt}};
+ auto res = OpenSSLSupplier::x509_generate_csr(csr_info, csr);
+ ASSERT_TRUE(res);
+
+ ASSERT_GT(csr.size(), 0);
+}
+
+} // namespace
diff --git a/tests/openssl_supplier_test_tpm.cpp b/tests/openssl_supplier_test_tpm.cpp
new file mode 100644
index 0000000..0c318db
--- /dev/null
+++ b/tests/openssl_supplier_test_tpm.cpp
@@ -0,0 +1,140 @@
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+using namespace evse_security;
+
+namespace {
+
+static std::string getFile(const std::string name) {
+ std::ifstream file(name);
+ return std::string((std::istreambuf_iterator(file)), std::istreambuf_iterator());
+}
+
+class OpenSSLSupplierTpmTest : public testing::Test {
+protected:
+ static void SetUpTestSuite() {
+ std::system("./create-pki.sh tpm");
+ }
+};
+
+TEST_F(OpenSSLSupplierTpmTest, generate_key_RSA_TPM20) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::RSA_TPM20, true, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, generate_key_RSA_3072) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::RSA_3072, true, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, generate_key_RSA_7680) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::RSA_7680, true, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ // not commonly supported by TPMs
+ ASSERT_FALSE(res);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, generate_key_EC_prime256v1) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::EC_prime256v1, true, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, generate_key_EC_EC_secp384r1) {
+ KeyGenerationInfo info = {
+ CryptoKeyType::EC_secp384r1, true, std::nullopt, std::nullopt, std::nullopt,
+ };
+ KeyHandle_ptr key;
+ auto res = OpenSSLSupplier::generate_key(info, key);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, load_certificates) {
+ auto file = getFile("tpm_pki/cert_path.pem");
+ auto res = OpenSSLSupplier::load_certificates(file, EncodingFormat::PEM);
+ ASSERT_EQ(res.size(), 2);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, x509_check_private_key) {
+ auto cert_leaf = getFile("tpm_pki/server_cert.pem");
+ auto res_leaf = OpenSSLSupplier::load_certificates(cert_leaf, EncodingFormat::PEM);
+ auto cert = res_leaf[0].get();
+ auto key = getFile("tpm_pki/server_priv.pem");
+ auto res = OpenSSLSupplier::x509_check_private_key(cert, key, std::nullopt);
+ ASSERT_TRUE(res);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, x509_verify_certificate_chain) {
+ auto cert_path = getFile("tpm_pki/cert_path.pem");
+ auto cert_leaf = getFile("tpm_pki/server_cert.pem");
+
+ auto res_path = OpenSSLSupplier::load_certificates(cert_path, EncodingFormat::PEM);
+ auto res_leaf = OpenSSLSupplier::load_certificates(cert_leaf, EncodingFormat::PEM);
+
+ std::vector parents;
+
+ for (auto& i : res_path) {
+ parents.push_back(i.get());
+ }
+
+ auto res = OpenSSLSupplier::x509_verify_certificate_chain(res_leaf[0].get(), parents, true, std::nullopt,
+ "tpm_pki/root_cert.pem");
+ ASSERT_EQ(res, CertificateValidationError::NoError);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, x509_generate_csr) {
+ std::string csr;
+ CertificateSigningRequestInfo csr_info = {
+ 0,
+ "UK",
+ "Pionix",
+ "0123456789",
+ {CryptoKeyType::EC_prime256v1, true, std::nullopt, "tpm_pki/csr_key.pem", std::nullopt}};
+
+ // std::cout << "tpm2 pre: " << OSSL_PROVIDER_available(nullptr, "tpm2") << std::endl;
+ // std::cout << "base pre: " << OSSL_PROVIDER_available(nullptr, "base") << std::endl;
+ auto res = OpenSSLSupplier::x509_generate_csr(csr_info, csr);
+ // std::cout << "tpm2 post: " << OSSL_PROVIDER_available(nullptr, "tpm2") << std::endl;
+ // std::cout << "base post: " << OSSL_PROVIDER_available(nullptr, "base") << std::endl;
+
+ ASSERT_TRUE(res);
+ ASSERT_GT(csr.size(), 0);
+}
+
+TEST_F(OpenSSLSupplierTpmTest, supports_tpm) {
+ OpenSSLProvider::cleanup();
+ ASSERT_FALSE(OpenSSLProvider::supports_tpm());
+ // calculates
+ OpenSSLProvider provider;
+ // returns cached
+ ASSERT_TRUE(OpenSSLProvider::supports_tpm());
+}
+
+TEST_F(OpenSSLSupplierTpmTest, supports_tpm_key_creation) {
+ OpenSSLProvider::cleanup();
+ ASSERT_FALSE(OpenSSLProvider::supports_tpm());
+ // should calculate
+ ASSERT_TRUE(OpenSSLSupplier::supports_tpm_key_creation());
+}
+
+} // namespace
diff --git a/tests/tests.cpp b/tests/tests.cpp
index c6267c5..37acdf3 100644
--- a/tests/tests.cpp
+++ b/tests/tests.cpp
@@ -13,7 +13,40 @@
#include
#include
+
+#include
+#define USING_OPENSSL_3 (OPENSSL_VERSION_NUMBER >= 0x30000000L)
+
+#if USING_OPENSSL_3
+// provider management has changed - ensure tests still work
+#ifndef USING_TPM2
#include
+#else
+
+// updates so that existing tests run with the OpenSSLProvider
+#include
+#include
+
+namespace evse_security {
+const char* PROVIDER_TPM = "tpm2";
+const char* PROVIDER_DEFAULT = "default";
+typedef OpenSSLProvider TPMScopedProvider;
+
+} // namespace evse_security
+#endif // USING_TPM2
+
+#else
+
+// updates so that tests run under OpenSSL v1
+namespace evse_security {
+const char* PROVIDER_TPM = "tpm2";
+const char* PROVIDER_DEFAULT = "default";
+} // namespace evse_security
+constexpr bool check_openssl_providers(const std::vector&) {
+ return true;
+}
+
+#endif // USING_OPENSSL_3
std::string read_file_to_string(const fs::path filepath) {
fsstd::ifstream t(filepath.string());
@@ -33,6 +66,7 @@ bool equal_certificate_strings(const std::string& cert1, const std::string& cert
return true;
}
+#if USING_OPENSSL_3
bool supports_tpm_usage() {
bool supports_tpm = false;
@@ -79,11 +113,15 @@ bool check_openssl_providers(const std::vector& required_providers)
return true;
}
+static bool supports_tpm = supports_tpm_usage();
+#else
+static bool supports_tpm = false;
+#endif // USING_OPENSSL_3
+
void install_certs() {
std::system("./generate_test_certs.sh");
}
-static bool supports_tpm = supports_tpm_usage();
namespace evse_security {
class EvseSecurityTests : public ::testing::Test {
@@ -287,6 +325,7 @@ TEST_F(EvseSecurityTests, verify_certificate_counts) {
ASSERT_EQ(this->evse_security->get_count_of_installed_certificates({CertificateType::MORootCertificate}), 0);
}
+#if USING_OPENSSL_3
TEST_F(EvseSecurityTests, providers_tests) {
if (supports_tpm == false)
return;
@@ -357,6 +396,9 @@ TEST_F(EvseSecurityTests, providers_tests) {
}
TEST_F(EvseSecurityTests, verify_provider_scope) {
+#ifdef USING_TPM2
+ GTEST_SKIP() << "Skipped: OpenSSLProvider doesn't load and unload providers";
+#endif
if (supports_tpm == false)
return;
@@ -373,6 +415,7 @@ TEST_F(EvseSecurityTests, verify_provider_scope) {
ASSERT_TRUE(check_openssl_providers({PROVIDER_DEFAULT}));
std::cout << "Ending test TPM scoped provider" << std::endl;
}
+#endif // USING_OPENSSL_3
TEST_F(EvseSecurityTests, verify_normal_keygen) {
KeyGenerationInfo info;