Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix/66 gracefull crash handling #70

Merged
merged 17 commits into from
May 10, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like this already returns a optional path, is that what this comment meant?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opps, simply forgot a line.

/// @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
Loading