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;