Skip to content

Commit

Permalink
Feature/custom openssl provider (#86)
Browse files Browse the repository at this point in the history
* Added cmake config for custom provider
* Prepared library for custom provider integration
* Updated readme/docs

---------

Signed-off-by: AssemblyJohn <[email protected]>
  • Loading branch information
AssemblyJohn authored Jul 26, 2024
1 parent b140c17 commit 7624055
Show file tree
Hide file tree
Showing 16 changed files with 178 additions and 119 deletions.
22 changes: 17 additions & 5 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.14)

project(everest-evse_security VERSION 0.6
project(everest-evse_security VERSION 0.8
DESCRIPTION "Implementation of EVSE related security operations"
LANGUAGES CXX C
)
Expand All @@ -14,18 +14,30 @@ option(${PROJECT_NAME}_BUILD_TESTING "Build unit tests, used if included as depe
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)
option(USING_CUSTOM_PROVIDER "Include code for using OpenSSL 3 and the custom provider" OFF)

if((${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME} OR ${PROJECT_NAME}_BUILD_TESTING) AND BUILD_TESTING)
set(LIBEVSE_SECURITY_BUILD_TESTING ON)
endif()

if(USING_TPM2 AND USING_CUSTOM_PROVIDER)
message(FATAL_ERROR, "TPM2 provider and custom provider are incompatible")
endif()

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")
set(CUSTOM_PROVIDER_NAME "tpm2")
endif()

if(USING_CUSTOM_PROVIDER)
set(CUSTOM_PROVIDER_NAME "custom_provider")
endif()

if(USING_TPM2 OR USING_CUSTOM_PROVIDER)
# OpenSSL property string when using the default provider
set(PROPQUERY_PROVIDER_DEFAULT "provider!=${CUSTOM_PROVIDER_NAME}")
# OpenSSL property string when using the tpm2/custom provider
set(PROPQUERY_PROVIDER_CUSTOM "?provider=${CUSTOM_PROVIDER_NAME},${CUSTOM_PROVIDER_NAME}.digest!=yes,${CUSTOM_PROVIDER_NAME}.cipher!=yes")
endif()

# dependencies
if (NOT DISABLE_EDM)
Expand Down
23 changes: 16 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

This is a C++ library for security related operations for charging stations. It respects the requirements specified in OCPP and ISO15118 and can be used in combination with OCPP and ISO15118 implementations.

In the near future this library will also contain support for secure storage on TPM2.0.

All documentation and the issue tracking can be found in our main repository here: https://github.com/EVerest/everest

## Prerequisites
Expand Down Expand Up @@ -43,7 +41,7 @@ We allow any certificate structure with the following recommendations:
- Root CA certificate directories/bundles should not overlap leaf certificates
- It is not recommended to store any SUBCAs in the root certificate bundle (if using files)

**Important:** when requesting leaf certificates with [get_key_pair](https://github.com/EVerest/libevse-security/blob/5cd5f8284229ffd28ae1dfed2137ef194c39e732/lib/evse_security/evse_security.cpp#L820) care should be taken if you require the full certificate chain.
**Important:** when requesting leaf certificates with [get_leaf_certificate_info](https://github.com/EVerest/libevse-security/blob/b140c17b0a5eaf09b60035605ed8aeb84627eb78/include/evse_security/evse_security.hpp#L195) care should be taken if you require the full certificate chain.

If a full chain is **Leaf->SubCA2->SubCA1->Root**, it is recommended to have the root certificate in a single file, **V2G_ROOT_CA.pem** for example. The **Leaf->SubCA2->SubCA1** should be placed in a file e.g. **SECC_CERT_CHAIN.pem**.

Expand All @@ -56,9 +54,9 @@ By default they are not added.
- `cmake -DCSR_DNS_NAME=charger.pionix.de ...` to include a DNS name
- `cmake -DCSR_IP_ADDRESS=192.168.2.1 ...` to include an IPv4 address

When receiving back a signed CSR, the library will take care to create two files, one containing the **Leaf->SubCA2->SubCA1** chain and another containing the single **Leaf**. When they both exist, the return of [get_key_pair](https://github.com/EVerest/libevse-security/blob/5cd5f8284229ffd28ae1dfed2137ef194c39e732/include/evse_security/evse_types.hpp#L126) will contain a path to both the single file and the chain file.
When receiving back a signed CSR, the library will take care to create two files, one containing the **Leaf->SubCA2->SubCA1** chain and another containing the single **Leaf**. When they both exist, the return of [get_leaf_certificate_info](https://github.com/EVerest/libevse-security/blob/b140c17b0a5eaf09b60035605ed8aeb84627eb78/include/evse_security/evse_security.hpp#L195) will contain a path to both the single file and the chain file.

## TPM
## TPM Provider
There is a configuration option to configure OpenSSL for use with a TPM.<br>
`cmake` ... `-DUSING_TPM2=ON`<br>

Expand All @@ -67,8 +65,8 @@ 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_PROVIDER_DEFAULT` is the string to use when selecting the default provider
- `PROPQUERY_PROVIDER_CUSTOM` is the string to use when selecting the tpm2 provider

propquery|action
---------|------
Expand All @@ -83,6 +81,17 @@ For more information see:
- [OpenSSL property](https://www.openssl.org/docs/man3.0/man7/property.html)
- [OpenSSL provider](https://www.openssl.org/docs/man3.0/man7/provider.html)

<b>Note:</b> In case of errors related to CSR signing, update tpm2-openssl to v 1.2.0.

## Custom Provider
There is a configuration option to configure OpenSSL for use with a custom provider.<br>
`cmake` ... `-DUSING_CUSTOM_PROVIDER=ON`<br>

The workflow follows the same steps as using the TPM provider. The library will
have a flag to configure whether it uses the `default` provider or the `custom` one.

<b>Note:</b> The custom provider name has to be defined [here](https://github.com/EVerest/libevse-security/blob/4afe644cb62d0bf06fff1e2ca5d2dbc489342e0c/CMakeLists.txt#L32). Change the name from "custom_provider" to the required provider.

## Garbage Collect

By default a garbage collect function will run and delete all expired leaf certificates and their respective keys, only if the certificate storage is full. A minimum count of leaf certificates will be kept even if they are expired.
Expand Down
2 changes: 1 addition & 1 deletion include/evse_security/crypto/evse_crypto.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

// Include other required suppliers here
#ifdef LIBEVSE_CRYPTO_SUPPLIER_OPENSSL
#include <evse_security/crypto/openssl/openssl_supplier.hpp>
#include <evse_security/crypto/openssl/openssl_crypto_supplier.hpp>
namespace evse_security {
typedef OpenSSLSupplier CryptoSupplier; // Define others with the same 'CryptoSupplier' name
}
Expand Down
2 changes: 2 additions & 0 deletions include/evse_security/crypto/interface/crypto_supplier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class AbstractCryptoSupplier {
/// @brief If any TPM operations are supported
static bool supports_tpm();
static bool supports_tpm_key_creation();
/// @brief If creation from a custom provider is supported
static bool supports_custom_key_creation();

public: // Key utilities
static bool generate_key(const KeyGenerationInfo& generation_info, KeyHandle_ptr& out_key);
Expand Down
5 changes: 3 additions & 2 deletions include/evse_security/crypto/interface/crypto_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,10 @@ enum class CertificateSignRequestResult {
struct KeyGenerationInfo {
CryptoKeyType key_type;

/// @brief If the key should be generated on the TPM, should check before if
/// @brief If the key should be generated using the custom provider. The custom
/// provider can be the TPM if it was so configured. Should check before if
/// the provider supports the operation, or the operation will fail by default
bool generate_on_tpm;
bool generate_on_custom;

/// @brief If we should export the public key to a file
std::optional<std::string> public_key_file;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class OpenSSLSupplier : public AbstractCryptoSupplier {

static bool supports_tpm();
static bool supports_tpm_key_creation();
static bool supports_custom_key_creation();

public:
static bool generate_key(const KeyGenerationInfo& key_info, KeyHandle_ptr& out_key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,26 @@ struct ossl_provider_st; // OpenSSL OSSL_PROVIDER

namespace evse_security {

/// @brief determine if the PEM string is a TSS2 private key
/// @brief determine if the PEM string is a custom private key. Will
/// only work for private keys, public keys will always return true
/// @param private_key_pem string containing the PEM encoded key
/// @return true when "-----BEGIN TSS2 PRIVATE KEY-----" found
/// @return true when file does not start "-----BEGIN PRIVATE KEY-----"
/// @note works irrespective of OpenSSL version
bool is_tpm_key_string(const std::string& private_key_pem);
bool is_custom_private_key_string(const std::string& private_key_pem);

/// @brief determine if the PEM file contains a TSS2 private key
/// @brief determine if the PEM file contains a custom private key. Will
/// only work for private keys, public keys will always return true
/// @param private_key_file_pem filename of the PEM file
/// @return true when file starts "-----BEGIN TSS2 PRIVATE KEY-----"
/// @return true when file does not start "-----BEGIN PRIVATE KEY-----"
/// @note works irrespective of OpenSSL version
bool is_tpm_key_file(const fs::path& private_key_file_pem);
bool is_custom_private_key_file(const fs::path& 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)
/// The 'tpm2' can be replaced with a custom provider (see CMakeLists.txt)
///
/// There are two contexts:
/// - 'global' for general use
Expand All @@ -55,25 +58,25 @@ class OpenSSLProvider {
/// @brief supported propquery strings
enum class mode_t {
default_provider,
tpm2_provider,
custom_provider,
};

private:
typedef std::uint8_t flags_underlying_t;
enum class flags_t : flags_underlying_t {
initialised,
tpm2_available,
global_tpm2,
tls_tpm2,
custom_provider_available,
global_custom_provider,
tls_custom_provider,
};

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_global_prov_custom_p;
static struct ossl_provider_st* s_tls_prov_default_p;
static struct ossl_provider_st* s_tls_prov_tpm_p;
static struct ossl_provider_st* s_tls_prov_custom_p;
static struct ossl_lib_ctx_st* s_tls_libctx_p;

static inline void reset(flags_t f) {
Expand All @@ -97,22 +100,22 @@ class OpenSSLProvider {
/// @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);
bool result = (val != is_set(f));
if (val) {
set(f);
} else {
reset(f);
}
return bResult;
return result;
}

bool load(struct ossl_provider_st*& default_p, struct ossl_provider_st*& tpm2_p, struct ossl_lib_ctx_st* libctx_p,
bool load(struct ossl_provider_st*& default_p, struct ossl_provider_st*& custom_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);
return load(s_global_prov_default_p, s_global_prov_custom_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);
return load(s_tls_prov_default_p, s_tls_prov_custom_p, s_tls_libctx_p, mode);
}

bool set_propstr(struct ossl_lib_ctx_st* libctx, mode_t mode);
Expand All @@ -133,10 +136,10 @@ class OpenSSLProvider {
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;
return (is_set(flags_t::global_custom_provider)) ? mode_t::custom_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;
return (is_set(flags_t::tls_custom_provider)) ? mode_t::custom_provider : mode_t::default_provider;
}

inline const char* propquery_global_str() const {
Expand All @@ -151,9 +154,8 @@ class OpenSSLProvider {
return s_tls_libctx_p;
}

static inline bool supports_tpm() {
return is_set(flags_t::tpm2_available);
}
static bool supports_provider_tpm();
static bool supports_provider_custom();

static void cleanup();
};
Expand Down
6 changes: 4 additions & 2 deletions include/evse_security/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -164,12 +164,14 @@ class EvseSecurity {
/// @param country
/// @param organization
/// @param common
/// @param use_tpm If the TPM should be used for the CSR request
/// @param use_custom_provider If the custom provider (which can be the TPM if using -DUSING_TPM2=ON) should be
/// used for the CSR request
/// @return the status and an optional PEM formatted certificate signing request string
GetCertificateSignRequestResult generate_certificate_signing_request(LeafCertificateType certificate_type,
const std::string& country,
const std::string& organization,
const std::string& common, bool use_tpm);
const std::string& common,
bool use_custom_provider);

/// @brief Generates a certificate signing request for the given \p certificate_type , \p country , \p organization
/// and \p common without using the TPM
Expand Down
2 changes: 1 addition & 1 deletion include/evse_security/evse_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace evse_security {
const fs::path PEM_EXTENSION = ".pem";
const fs::path DER_EXTENSION = ".der";
const fs::path KEY_EXTENSION = ".key";
const fs::path TPM_KEY_EXTENSION = ".tkey";
const fs::path CUSTOM_KEY_EXTENSION = ".tkey";
const fs::path CERT_HASH_EXTENSION = ".hash";

enum class EncodingFormat {
Expand Down
13 changes: 7 additions & 6 deletions lib/evse_security/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ target_sources(evse_security

crypto/interface/crypto_supplier.cpp
crypto/interface/crypto_types.cpp
crypto/openssl/openssl_supplier.cpp
crypto/openssl/openssl_tpm.cpp
crypto/openssl/openssl_crypto_supplier.cpp
crypto/openssl/openssl_provider.cpp
)

target_include_directories(evse_security
Expand Down Expand Up @@ -83,11 +83,12 @@ if(LIBEVSE_CRYPTO_SUPPLIER_OPENSSL)
add_compile_definitions(LIBEVSE_CRYPTO_SUPPLIER_OPENSSL)
endif()

if(USING_TPM2)
if(USING_TPM2 OR USING_CUSTOM_PROVIDER)
target_compile_definitions(evse_security PRIVATE
USING_TPM2
PROPQUERY_DEFAULT="${PROPQUERY_DEFAULT}"
PROPQUERY_TPM2="${PROPQUERY_TPM2}"
USING_CUSTOM_PROVIDER
CUSTOM_PROVIDER_NAME="${CUSTOM_PROVIDER_NAME}"
PROPQUERY_PROVIDER_DEFAULT="${PROPQUERY_PROVIDER_DEFAULT}"
PROPQUERY_PROVIDER_CUSTOM="${PROPQUERY_PROVIDER_CUSTOM}"
)
endif()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <evse_security/crypto/openssl/openssl_supplier.hpp>
#include <evse_security/crypto/openssl/openssl_crypto_supplier.hpp>

#include <evse_security/detail/openssl/openssl_types.hpp>
#include <evse_security/utils/evse_filesystem.hpp>
Expand All @@ -22,7 +22,7 @@
#include <openssl/sha.h>
#include <openssl/x509v3.h>

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

namespace evse_security {

Expand Down Expand Up @@ -68,7 +68,7 @@ const char* OpenSSLSupplier::get_supplier_name() {

bool OpenSSLSupplier::supports_tpm_key_creation() {
OpenSSLProvider provider;
return provider.supports_tpm();
return provider.supports_provider_tpm();
}

static bool export_key_internal(const KeyGenerationInfo& key_info, const EVP_PKEY_ptr& evp_key) {
Expand Down Expand Up @@ -218,8 +218,8 @@ bool OpenSSLSupplier::generate_key(const KeyGenerationInfo& key_info, KeyHandle_
OpenSSLProvider provider;
bool bResult = true;

if (key_info.generate_on_tpm) {
provider.set_global_mode(OpenSSLProvider::mode_t::tpm2_provider);
if (key_info.generate_on_custom) {
provider.set_global_mode(OpenSSLProvider::mode_t::custom_provider);

} else {
provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
Expand Down Expand Up @@ -564,13 +564,13 @@ KeyValidationResult OpenSSLSupplier::x509_check_private_key(X509Handle* handle,

OpenSSLProvider provider;

const bool tpm_key = is_tpm_key_string(private_key);
if (tpm_key) {
provider.set_global_mode(OpenSSLProvider::mode_t::tpm2_provider);
const bool custom_key = is_custom_private_key_string(private_key);
if (custom_key) {
provider.set_global_mode(OpenSSLProvider::mode_t::custom_provider);
} else {
provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
}
EVLOG_debug << "TPM Key: " << tpm_key;
EVLOG_debug << "Is Custom Key: " << custom_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
Expand Down Expand Up @@ -648,8 +648,8 @@ CertificateSignRequestResult OpenSSLSupplier::x509_generate_csr(const Certificat
EVP_PKEY_CTX_ptr ctx;
OpenSSLProvider provider;

if (csr_info.key_info.generate_on_tpm) {
provider.set_global_mode(OpenSSLProvider::mode_t::tpm2_provider);
if (csr_info.key_info.generate_on_custom) {
provider.set_global_mode(OpenSSLProvider::mode_t::custom_provider);
} else {
provider.set_global_mode(OpenSSLProvider::mode_t::default_provider);
}
Expand Down
Loading

0 comments on commit 7624055

Please sign in to comment.