From 59d9f6fece1b00aafbaa90eb91b71ab08c96443e Mon Sep 17 00:00:00 2001 From: James Chapman Date: Thu, 8 Feb 2024 10:51:06 +0100 Subject: [PATCH] feat: OpenSSL provider implementation now default for OpenSSL v3 feat: OpenSSL tpm2 provider load on first use, configured via property strings feat: OpenSSL property strings configurable via cmake feat: unit tests run for OpenSSL v1 and v3 feat: additional unit tests for tpm2 provider (OpenSSL v3 only) provider handling changed following testing on an embedded system. Loading and unloading providers was proving unreliable. Approach changed to load providers early and use the property string to control which provider is used. A mutex has been added so that another call cann't change the provider configuration whilst in use. OpenSSL v3 uses providers. OpenSSL v1 uses previous code. Note: the tpm2-abrmd daemon needs to be running for tpm2 where TLS is being used. Signed-off-by: James Chapman --- CMakeLists.txt | 10 + README.md | 24 ++ .../crypto/openssl/openssl_tpm.hpp | 161 +++++++++ lib/evse_security/CMakeLists.txt | 9 + .../crypto/openssl/openssl_supplier.cpp | 337 +++++++++--------- .../crypto/openssl/openssl_tpm.cpp | 257 +++++++++++++ lib/evse_security/evse_security.cpp | 4 + tests/CMakeLists.txt | 25 +- tests/create-pki.sh | 67 ++++ tests/openssl-pki.conf | 78 ++++ tests/openssl_supplier_test.cpp | 117 ++++++ tests/openssl_supplier_test_tpm.cpp | 140 ++++++++ tests/tests.cpp | 45 ++- 13 files changed, 1102 insertions(+), 172 deletions(-) create mode 100644 include/evse_security/crypto/openssl/openssl_tpm.hpp create mode 100644 lib/evse_security/crypto/openssl/openssl_tpm.cpp create mode 100755 tests/create-pki.sh create mode 100644 tests/openssl-pki.conf create mode 100644 tests/openssl_supplier_test.cpp create mode 100644 tests/openssl_supplier_test_tpm.cpp 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;