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

Feature/get ocsp cache data #64

Closed
wants to merge 9 commits into from
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
13 changes: 9 additions & 4 deletions include/evse_security/crypto/interface/crypto_supplier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,20 @@ 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);

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
13 changes: 9 additions & 4 deletions include/evse_security/crypto/openssl/openssl_supplier.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,19 @@ class OpenSSLSupplier : public AbstractCryptoSupplier {
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);

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
35 changes: 33 additions & 2 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 Down Expand Up @@ -181,8 +187,10 @@ class EvseSecurity {
/// the leaf including any possible SUBCAs
/// @param certificate_type type of the leaf certificate
/// @param encoding specifies PEM or DER format
/// @param include_ocsp if OCSP data should be included
/// @return contains response result
GetKeyPairResult get_key_pair(LeafCertificateType certificate_type, EncodingFormat encoding);
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 Down Expand Up @@ -214,11 +222,33 @@ 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);
std::optional<fs::path> retrieve_ocsp_cache_internal(const CertificateHashData& certificate_hash_data);
bool is_ca_certificate_installed_internal(CaCertificateType certificate_type);

/// @brief Determines if the total filesize of certificates is > than the max_filesystem_usage bytes
Expand Down Expand Up @@ -258,6 +288,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
35 changes: 25 additions & 10 deletions include/evse_security/evse_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

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 CERT_HASH_EXTENSION = ".hash";

enum class EncodingFormat {
DER,
PEM,
Expand Down Expand Up @@ -76,7 +82,7 @@
NotFound,
};

enum class GetKeyPairStatus {
enum class GetCertificateInfoStatus {
Accepted,
Rejected,
NotFound,
Expand Down Expand Up @@ -123,28 +129,37 @@
struct OCSPRequestDataList {
std::vector<OCSPRequestData> ocsp_request_data_list; ///< A list of OCSP request data
};
struct KeyPair {
fs::path key; ///< The path of the PEM or DER encoded private key
fs::path certificate; ///< The path of the PEM or DER encoded certificate chain
fs::path certificate_single; ///< The path of the PEM or DER encoded certificate
std::optional<std::string> password; ///< Specifies the password for the private key if encrypted

struct CertificateOCSP {
CertificateHashData hash; ///< Hash of the certificate for which the OCSP data is held

Check notice on line 134 in include/evse_security/evse_types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/evse_security/evse_types.hpp#L134

struct member 'CertificateOCSP::hash' is never used.
std::optional<fs::path> ocsp_path; ///< Path to the file in which the certificate OCSP data is held
};
struct GetKeyPairResult {
GetKeyPairStatus status;
std::optional<KeyPair> pair;

struct CertificateInfo {
fs::path key; ///< The path of the PEM or DER encoded private key
std::optional<fs::path> certificate; ///< The path of the PEM or DER encoded certificate chain if found
std::optional<fs::path> certificate_single; ///< The path of the PEM or DER encoded certificate if found
int certificate_count; ///< The count of certificates, if the chain is available, or 1 if single

Check notice on line 142 in include/evse_security/evse_types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/evse_security/evse_types.hpp#L142

struct member 'CertificateInfo::certificate_count' is never used.
std::optional<std::string> password; ///< Specifies the password for the private key if encrypted
std::vector<CertificateOCSP> ocsp; ///< The ordered list of OCSP certificate data based on the chain file order

Check notice on line 144 in include/evse_security/evse_types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/evse_security/evse_types.hpp#L144

struct member 'CertificateInfo::ocsp' is never used.
};

struct GetCertificateInfoResult {
GetCertificateInfoStatus status;

Check notice on line 148 in include/evse_security/evse_types.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/evse_security/evse_types.hpp#L148

struct member 'GetCertificateInfoResult::status' is never used.
std::optional<CertificateInfo> info;
};
namespace conversions {
std::string encoding_format_to_string(EncodingFormat e);
std::string ca_certificate_type_to_string(CaCertificateType e);
std::string leaf_certificate_type_to_string(LeafCertificateType e);
std::string leaf_certificate_type_to_filename(LeafCertificateType e);
std::string certificate_type_to_string(CertificateType e);
std::string hash_algorithm_to_string(HashAlgorithm e);
HashAlgorithm string_to_hash_algorithm(const std::string& s);
std::string install_certificate_result_to_string(InstallCertificateResult e);
std::string delete_certificate_result_to_string(DeleteCertificateResult e);
std::string get_installed_certificates_status_to_string(GetInstalledCertificatesStatus e);
std::string get_key_pair_status_to_string(GetKeyPairStatus e);
std::string get_certificate_info_status_to_string(GetCertificateInfoStatus e);
} // namespace conversions

} // namespace evse_security
12 changes: 11 additions & 1 deletion include/evse_security/utils/evse_filesystem.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#include <evse_security/utils/evse_filesystem_types.hpp>

struct CertificateHashData;

namespace evse_security::filesystem_utils {

bool is_subdirectory(const fs::path& base, const fs::path& subdir);
Expand All @@ -21,8 +23,16 @@ bool write_to_file(const fs::path& file_path, const std::string& data, std::ios:
/// returns false, this function will also immediately return
/// @return True if the file was properly opened false otherwise
bool process_file(const fs::path& file_path, size_t buffer_size,
std::function<bool(const std::byte*, std::size_t, bool last_chunk)>&& func);
std::function<bool(const std::uint8_t*, std::size_t, bool last_chunk)>&& func);

std::string get_random_file_name(const std::string& extension);

/// @brief Attempts to read a certificate hash from a file. The extension is taken into account
/// @return True if we could read, false otherwise
bool read_hash_from_file(const fs::path& file_path, CertificateHashData& out_hash);

/// @brief Attempts to write a certificate hash to a file
/// @return True if we could write, false otherwise
bool write_hash_to_file(const fs::path& file_path, const CertificateHashData& hash);

} // namespace evse_security::filesystem_utils
15 changes: 15 additions & 0 deletions lib/evse_security/certificate/x509_bundle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <evse_security/certificate/x509_bundle.hpp>

#include <algorithm>
#include <fstream>

#include <everest/logging.hpp>
#include <evse_security/crypto/evse_crypto.hpp>
Expand Down Expand Up @@ -44,6 +45,20 @@ X509CertificateBundle::X509CertificateBundle(const fs::path& path, const Encodin
hierarchy_invalidated(true) {
this->path = path;

// In case the path is missing, create it
if (fs::exists(path) == false) {
if (path.has_extension()) {
if (path.extension() == PEM_EXTENSION) {
// Create file if we have an PEM extension
std::ofstream new_file(path.c_str());
new_file.close();
}
} else {
// Else create a directory
fs::create_directories(path);
}
}

if (fs::is_directory(path)) {
source = X509CertificateSource::DIRECTORY;

Expand Down
14 changes: 14 additions & 0 deletions lib/evse_security/certificate/x509_hierarchy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,20 @@ X509Wrapper X509CertificateHierarchy::find_certificate(const CertificateHashData
throw NoCertificateFound("Could not find a certificate for hash: " + hash.issuer_name_hash);
}

std::vector<X509Wrapper> X509CertificateHierarchy::find_certificates_multi(const CertificateHashData& hash) {
std::vector<X509Wrapper> certificates;

for_each([&](X509Node& node) {
if (node.hash == hash) {
certificates.push_back(node.certificate);
}

return true;
});

return certificates;
}

std::string X509CertificateHierarchy::to_debug_string() {
std::stringstream str;

Expand Down
23 changes: 18 additions & 5 deletions lib/evse_security/crypto/interface/crypto_supplier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,21 +86,34 @@ KeyValidationResult AbstractCryptoSupplier::x509_check_private_key(X509Handle* h
default_crypto_supplier_usage_error() return KeyValidationResult::Unknown;
}

bool AbstractCryptoSupplier::x509_verify_signature(X509Handle* handle, const std::vector<std::byte>& signature,
const std::vector<std::byte>& data) {
bool AbstractCryptoSupplier::x509_verify_signature(X509Handle* handle, const std::vector<std::uint8_t>& signature,
const std::vector<std::uint8_t>& data) {
default_crypto_supplier_usage_error() return false;
}

bool AbstractCryptoSupplier::x509_generate_csr(const CertificateSigningRequestInfo& csr_info, std::string& out_csr) {
default_crypto_supplier_usage_error() return false;
}

bool AbstractCryptoSupplier::digest_file_sha256(const fs::path& path, std::vector<std::byte>& out_digest) {
bool AbstractCryptoSupplier::digest_file_sha256(const fs::path& path, std::vector<std::uint8_t>& out_digest) {
default_crypto_supplier_usage_error() return false;
}

bool AbstractCryptoSupplier::decode_base64_signature(const std::string& signature,
std::vector<std::byte>& out_decoded) {
bool AbstractCryptoSupplier::base64_decode_to_bytes(const std::string& base64_string,
std::vector<std::uint8_t>& out_decoded) {
default_crypto_supplier_usage_error() return false;
}

bool AbstractCryptoSupplier::base64_decode_to_string(const std::string& base64_string, std::string& out_decoded) {
default_crypto_supplier_usage_error() return false;
}

bool AbstractCryptoSupplier::base64_encode_from_bytes(const std::vector<std::uint8_t>& bytes,
std::string& out_encoded) {
default_crypto_supplier_usage_error() return false;
}

bool AbstractCryptoSupplier::base64_encode_from_string(const std::string& string, std::string& out_encoded) {
default_crypto_supplier_usage_error() return false;
}

Expand Down
Loading
Loading