Skip to content

Commit

Permalink
Bugfix/66 gracefull crash handling (#70)
Browse files Browse the repository at this point in the history
* Fixed potential bug when writing OCSP cache, switched to certificate hierarchy usage
* Added possibility to retrieve OCSP response
* Added utilities for encoding/decoding base64
* Added test cases
* Modified OCSP cache to unique filenames
* Refactored get_key_pair for extra info
* Over-write existing OCSP data based on certificate hash
* Added cleanup for OCSP
* Add tests for new OCSP functionality
* Fixed code for tests
* Fixes for OCSP data cleanup and test updates
* Added missing certificate bundle creation exceptions
* Removed non-necessary RSA7680 key generation tests
* Updated some crypto headers and return values and removed openssl warnings when compiling
* Interface updates
* Added filesystem error handling
* Updated docs
* Updated test cases
* Fixed comments

---------

Signed-off-by: AssemblyJohn <[email protected]>
  • Loading branch information
AssemblyJohn authored May 10, 2024
1 parent acc12fe commit 34ced9f
Show file tree
Hide file tree
Showing 22 changed files with 1,327 additions and 362 deletions.
18 changes: 18 additions & 0 deletions include/evse_security/certificate/x509_hierarchy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Copyright Pionix GmbH and Contributors to EVerest
#pragma once

#include <algorithm>
#include <queue>

#include <evse_security/certificate/x509_wrapper.hpp>
Expand Down Expand Up @@ -59,6 +60,10 @@ class X509CertificateHierarchy {
/// @brief Searches for the provided hash, throwing a NoCertificateFound if not found
X509Wrapper find_certificate(const CertificateHashData& hash);

/// @brief Searches for all the certificates with the provided hash, throwing a NoCertificateFound
// if none were found. Can be useful when we have SUB-CAs in multiple bundles
std::vector<X509Wrapper> find_certificates_multi(const CertificateHashData& hash);

public:
std::string to_debug_string();

Expand Down Expand Up @@ -110,6 +115,19 @@ class X509CertificateHierarchy {
/// hierarchy can be incomplete, in case orphan certificates are present in the list
static X509CertificateHierarchy build_hierarchy(std::vector<X509Wrapper>& certificates);

template <typename... Args> static X509CertificateHierarchy build_hierarchy(Args... certificates) {
X509CertificateHierarchy ordered;

(std::for_each(certificates.begin(), certificates.end(),
[&ordered](X509Wrapper& cert) { ordered.insert(std::move(cert)); }),
...); // Fold expr

// Prune the tree
ordered.prune();

return ordered;
}

private:
/// @brief Inserts the certificate in the hierarchy. If it is not a root
/// and a parent is not found, it will be inserted as a temporary orphan
Expand Down
5 changes: 0 additions & 5 deletions include/evse_security/certificate/x509_wrapper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@ enum class X509CertificateSource {
STRING
};

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";

/// @brief Convenience wrapper around openssl X509 certificate
class X509Wrapper {
public:
Expand Down
18 changes: 12 additions & 6 deletions include/evse_security/crypto/interface/crypto_supplier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ class AbstractCryptoSupplier {
/// (not yet valid)
/// @param out_valid_to Valid amount of seconds. A negative value is in the past (expired), a positive one is in the
/// future
static void x509_get_validity(X509Handle* handle, std::int64_t& out_valid_in, std::int64_t& out_valid_to);
static bool x509_get_validity(X509Handle* handle, std::int64_t& out_valid_in, std::int64_t& out_valid_to);

static bool x509_is_selfsigned(X509Handle* handle);
static bool x509_is_child(X509Handle* child, X509Handle* parent);
Expand All @@ -67,15 +67,21 @@ class AbstractCryptoSupplier {
std::optional<std::string> password);

/// @brief Verifies the signature with the certificate handle public key against the data
static bool x509_verify_signature(X509Handle* handle, const std::vector<std::byte>& signature,
const std::vector<std::byte>& data);
static bool x509_verify_signature(X509Handle* handle, const std::vector<std::uint8_t>& signature,
const std::vector<std::uint8_t>& data);

/// @brief Generates a certificate signing request with the provided parameters
static bool x509_generate_csr(const CertificateSigningRequestInfo& generation_info, std::string& out_csr);
static CertificateSignRequestResult x509_generate_csr(const CertificateSigningRequestInfo& generation_info,
std::string& out_csr);

public: // Digesting/decoding utils
static bool digest_file_sha256(const fs::path& path, std::vector<std::byte>& out_digest);
static bool decode_base64_signature(const std::string& signature, std::vector<std::byte>& out_decoded);
static bool digest_file_sha256(const fs::path& path, std::vector<std::uint8_t>& out_digest);

static bool base64_decode_to_bytes(const std::string& base64_string, std::vector<std::uint8_t>& out_decoded);
static bool base64_decode_to_string(const std::string& base64_string, std::string& out_decoded);

static bool base64_encode_from_bytes(const std::vector<std::uint8_t>& bytes, std::string& out_encoded);
static bool base64_encode_from_string(const std::string& string, std::string& out_encoded);
};

} // namespace evse_security
16 changes: 15 additions & 1 deletion include/evse_security/crypto/interface/crypto_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ enum class CryptoKeyType {
EC_secp384r1, // P-384, ~equiv to rsa 7680
RSA_TPM20, // Default TPM RSA, only option allowed for TPM (universal support), 2048 bits
RSA_3072, // Default RSA. Protection lifetime: ~2030
RSA_7680, // Protection lifetime: >2031
RSA_7680, // Protection lifetime: >2031. Very long generation time 8-40s on 16 core PC
};

enum class KeyValidationResult {
Expand All @@ -25,6 +25,16 @@ enum class KeyValidationResult {
Unknown, // Unknown error, not related to provider validation
};

enum class CertificateSignRequestResult {
Valid,
KeyGenerationError, // Error when generating the key, maybe invalid key type
VersioningError, // The version could not be set
PubkeyError, // The public key could not be attached
ExtensionsError, // The extensions could not be appended
SigningError, // The CSR could not be signed, maybe key or signing algo invalid
Unknown, // Any other error
};

struct KeyGenerationInfo {
CryptoKeyType key_type;

Expand Down Expand Up @@ -77,4 +87,8 @@ using KeyHandle_ptr = std::unique_ptr<KeyHandle>;
// Transforms a duration of days into seconds
using days_to_seconds = std::chrono::duration<std::int64_t, std::ratio<86400>>;

namespace conversions {
std::string get_certificate_sign_request_result_to_string(CertificateSignRequestResult e);
}

} // namespace evse_security
18 changes: 12 additions & 6 deletions include/evse_security/crypto/openssl/openssl_supplier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class OpenSSLSupplier : public AbstractCryptoSupplier {
static std::string x509_get_serial_number(X509Handle* handle);
static std::string x509_get_issuer_name_hash(X509Handle* handle);
static std::string x509_get_common_name(X509Handle* handle);
static void x509_get_validity(X509Handle* handle, std::int64_t& out_valid_in, std::int64_t& out_valid_to);
static bool x509_get_validity(X509Handle* handle, std::int64_t& out_valid_in, std::int64_t& out_valid_to);
static bool x509_is_selfsigned(X509Handle* handle);
static bool x509_is_child(X509Handle* child, X509Handle* parent);
static bool x509_is_equal(X509Handle* a, X509Handle* b);
Expand All @@ -37,14 +37,20 @@ class OpenSSLSupplier : public AbstractCryptoSupplier {
const std::optional<fs::path> dir_path, const std::optional<fs::path> file_path);
static KeyValidationResult x509_check_private_key(X509Handle* handle, std::string private_key,
std::optional<std::string> password);
static bool x509_verify_signature(X509Handle* handle, const std::vector<std::byte>& signature,
const std::vector<std::byte>& data);
static bool x509_verify_signature(X509Handle* handle, const std::vector<std::uint8_t>& signature,
const std::vector<std::uint8_t>& data);

static bool x509_generate_csr(const CertificateSigningRequestInfo& csr_info, std::string& out_csr);
static CertificateSignRequestResult x509_generate_csr(const CertificateSigningRequestInfo& csr_info,
std::string& out_csr);

public:
static bool digest_file_sha256(const fs::path& path, std::vector<std::byte>& out_digest);
static bool decode_base64_signature(const std::string& signature, std::vector<std::byte>& out_decoded);
static bool digest_file_sha256(const fs::path& path, std::vector<std::uint8_t>& out_digest);

static bool base64_decode_to_bytes(const std::string& base64_string, std::vector<std::uint8_t>& out_decoded);
static bool base64_decode_to_string(const std::string& base64_string, std::string& out_decoded);

static bool base64_encode_from_bytes(const std::vector<std::uint8_t>& bytes, std::string& out_encoded);
static bool base64_encode_from_string(const std::string& string, std::string& out_encoded);
};

} // namespace evse_security
39 changes: 23 additions & 16 deletions include/evse_security/detail/openssl/openssl_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <memory>
#include <openssl/x509v3.h>

#define EVSE_OPENSSL_VER_3 (OPENSSL_VERSION_NUMBER >= 0x30000000L)

template <> class std::default_delete<X509> {
public:
void operator()(X509* ptr) const {
Expand Down Expand Up @@ -54,20 +56,6 @@ template <> class std::default_delete<EVP_PKEY_CTX> {
}
};

template <> class std::default_delete<EC_KEY> {
public:
void operator()(EC_KEY* ptr) const {
::EC_KEY_free(ptr);
}
};

template <> class std::default_delete<RSA> {
public:
void operator()(RSA* ptr) const {
::RSA_free(ptr);
}
};

template <> class std::default_delete<BIO> {
public:
void operator()(BIO* ptr) const {
Expand All @@ -89,6 +77,22 @@ template <> class std::default_delete<EVP_ENCODE_CTX> {
}
};

#if !EVSE_OPENSSL_VER_3
template <> class std::default_delete<EC_KEY> {
public:
void operator()(EC_KEY* ptr) const {
::EC_KEY_free(ptr);
}
};

template <> class std::default_delete<RSA> {
public:
void operator()(RSA* ptr) const {
::RSA_free(ptr);
}
};
#endif

namespace evse_security {

using X509_ptr = std::unique_ptr<X509>;
Expand All @@ -100,10 +104,13 @@ using X509_STACK_UNSAFE_ptr = std::unique_ptr<STACK_OF(X509)>;
using X509_REQ_ptr = std::unique_ptr<X509_REQ>;
using EVP_PKEY_ptr = std::unique_ptr<EVP_PKEY>;
using EVP_PKEY_CTX_ptr = std::unique_ptr<EVP_PKEY_CTX>;
using EC_KEY_ptr = std::unique_ptr<EC_KEY>;
using RSA_ptr = std::unique_ptr<RSA>;
using BIO_ptr = std::unique_ptr<BIO>;
using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX>;
using EVP_ENCODE_CTX_ptr = std::unique_ptr<EVP_ENCODE_CTX>;

#if !EVSE_OPENSSL_VER_3
using EC_KEY_ptr = std::unique_ptr<EC_KEY>;
using RSA_ptr = std::unique_ptr<RSA>;
#endif

} // namespace evse_security
62 changes: 52 additions & 10 deletions include/evse_security/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ class EvseSecurity {
/// @param ocsp_response the actual OCSP data
void update_ocsp_cache(const CertificateHashData& certificate_hash_data, const std::string& ocsp_response);

// TODO: Switch to path
/// @brief Retrieves from the OCSP cache for the given \p certificate_hash_data
/// @param certificate_hash_data identifies the certificate for which the \p ocsp_response is specified
/// @return the actual OCSP data or an empty value
std::optional<fs::path> retrieve_ocsp_cache(const CertificateHashData& certificate_hash_data);

/// @brief Indicates if a CA certificate for the given \p certificate_type is installed on the filesystem
/// Supports both CA certificate bundles and directories
/// @param certificate_type
Expand All @@ -159,20 +165,23 @@ class EvseSecurity {
/// @param organization
/// @param common
/// @param use_tpm If the TPM should be used for the CSR request
/// @return the PEM formatted certificate signing request
std::string generate_certificate_signing_request(LeafCertificateType certificate_type, const std::string& country,
const std::string& organization, const std::string& common,
bool use_tpm);
/// @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);

/// @brief Generates a certificate signing request for the given \p certificate_type , \p country , \p organization
/// and \p common without using the TPM
/// @param certificate_type
/// @param country
/// @param organization
/// @param common
/// @return the PEM formatted certificate signing request
std::string generate_certificate_signing_request(LeafCertificateType certificate_type, const std::string& country,
const std::string& organization, const std::string& common);
/// @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);

/// @brief Searches the filesystem on the specified directories for the given \p certificate_type and retrieves the
/// most recent certificate that is already valid and the respective key. If no certificate is present or no key is
Expand All @@ -181,8 +190,10 @@ class EvseSecurity {
/// the leaf including any possible SUBCAs
/// @param certificate_type type of the leaf certificate
/// @param encoding specifies PEM or DER format
/// @return contains response result
GetKeyPairResult get_key_pair(LeafCertificateType certificate_type, EncodingFormat encoding);
/// @param include_ocsp if OCSP data should be included
/// @return contains response result, with info related to the certificate chain and response status
GetCertificateInfoResult get_leaf_certificate_info(LeafCertificateType certificate_type, EncodingFormat encoding,
bool include_ocsp = false);

/// @brief Checks and updates the symlinks for the V2G leaf certificates and keys to the most recent valid one
/// @return true if one of the links was updated
Expand All @@ -195,6 +206,9 @@ class EvseSecurity {
/// @return CA certificate file
std::string get_verify_file(CaCertificateType certificate_type);

/// @brief An extension of 'get_verify_file' with error handling included
GetCertificateInfoResult get_ca_certificate_info(CaCertificateType certificate_type);

/// @brief Gets the expiry day count for the leaf certificate of the given \p certificate_type
/// @param certificate_type
/// @return day count until the leaf certificate expires
Expand All @@ -214,13 +228,40 @@ class EvseSecurity {
static bool verify_file_signature(const fs::path& path, const std::string& signing_certificate,
const std::string signature);

/// @brief Decodes the base64 encoded string to the raw byte representation
/// @param base64_string base64 encoded string
/// @return decoded byte vector
static std::vector<std::uint8_t> base64_decode_to_bytes(const std::string& base64_string);

/// @brief Decodes the base64 encoded string to string representation
/// @param base64_string base64 encoded string
/// @return decoded string array
static std::string base64_decode_to_string(const std::string& base64_string);

/// @brief Encodes the raw bytes to a base64 string
/// @param decoded_bytes raw byte array
/// @return encoded base64 string
static std::string base64_encode_from_bytes(const std::vector<std::uint8_t>& bytes);

/// @brief Encodes the string containing raw bytes to a base64 string
/// @param decoded_bytes string containing raw bytes
/// @return encoded base64 string
static std::string base64_encode_from_string(const std::string& string);

private:
// Internal versions of the functions do not lock the mutex
CertificateValidationResult verify_certificate_internal(const std::string& certificate_chain,
LeafCertificateType certificate_type);
GetKeyPairResult get_key_pair_internal(LeafCertificateType certificate_type, EncodingFormat encoding);
GetCertificateInfoResult get_leaf_certificate_info_internal(LeafCertificateType certificate_type,
EncodingFormat encoding, bool include_ocsp = false);
GetCertificateInfoResult get_ca_certificate_info_internal(CaCertificateType certificate_type);
std::optional<fs::path> retrieve_ocsp_cache_internal(const CertificateHashData& certificate_hash_data);
bool is_ca_certificate_installed_internal(CaCertificateType certificate_type);

GetCertificateSignRequestResult
generate_certificate_signing_request_internal(LeafCertificateType certificate_type,
const CertificateSigningRequestInfo& info);

/// @brief Determines if the total filesize of certificates is > than the max_filesystem_usage bytes
bool is_filesystem_full();

Expand Down Expand Up @@ -258,6 +299,7 @@ class EvseSecurity {
FRIEND_TEST(EvseSecurityTests, verify_full_filesystem_install_reject);
FRIEND_TEST(EvseSecurityTests, verify_full_filesystem);
FRIEND_TEST(EvseSecurityTests, verify_expired_csr_deletion);
FRIEND_TEST(EvseSecurityTests, verify_ocsp_garbage_collect);
FRIEND_TEST(EvseSecurityTestsExpired, verify_expired_leaf_deletion);
#endif
};
Expand Down
Loading

0 comments on commit 34ced9f

Please sign in to comment.