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

Multiple root trusted CA leafs #91

Merged
merged 8 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,9 @@ Defaults:
- Minimum certificates kept: 10
- Maximum storage space: 50 MB
- Maximum certificate entries: 2000

## Limitations

Based on information from [ssl](https://www.ssl.com/article/what-are-root-certificates-and-why-do-they-matter/), self-signed roots are possible, but not supported in our library at the moment.

Cross-signed certificate chains (see [ssl](https://www.ssl.com/blogs/ssl-com-legacy-cross-signed-root-certificate-expiring-on-september-11-2023/)), required for seamless root transitions are not supported at the moment.
4 changes: 4 additions & 0 deletions include/evse_security/certificate/x509_hierarchy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ struct X509Node {

/// @brief Utility class that is able to build a immutable certificate hierarchy
/// with a list of self-signed root certificates and their respective sub-certificates
/// Note: non self-signed roots and cross-signed certificates are not supported now
class X509CertificateHierarchy {
public:
const std::vector<X509Node>& get_hierarchy() const {
Expand All @@ -57,6 +58,9 @@ class X509CertificateHierarchy {
/// @brief returns true if we contain a certificate with the following hash
bool contains_certificate_hash(const CertificateHashData& hash);

/// @brief Searches for the root of the provided leaf, throwing a NoCertificateFound if not found
X509Wrapper find_certificate_root(const X509Wrapper& leaf);

/// @brief Searches for the provided hash, throwing a NoCertificateFound if not found
X509Wrapper find_certificate(const CertificateHashData& hash);

Expand Down
32 changes: 32 additions & 0 deletions include/evse_security/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,25 @@ class EvseSecurity {
GetCertificateInfoResult get_leaf_certificate_info(LeafCertificateType certificate_type, EncodingFormat encoding,
bool include_ocsp = false);

/// @brief Finds the latest valid leafs, for each root certificate that is present on the filesystem, and
/// returns all the newest valid leafs that are present for different roots. This is required, because
/// a query parameter when requesting the leaf is not advisable during the TLS handshake
/// Existing filesystem:
/// ROOT_V2G_Hubject->SUB_CA1->SUB_CA2->Leaf_Invalid_A
/// ROOT_V2G_Hubject->SUB_CA1->SUB_CA2->Leaf_Valid_A
/// ROOT_V2G_Hubject->SUB_CA1->SUB_CA2->Leaf_Valid_B
/// ROOT_V2G_OtherProvider->SUB_CA_O1->SUB_CA_O2->Leav_Valid_A
/// will return:
/// ROOT_V2G_Hubject->SUB_CA1->SUB_CA2->Leaf_Valid_B +
/// ROOT_V2G_OtherProvider->SUB_CA_O1->SUB_CA_O2->Leav_Valid_A
/// Note: non self-signed roots and cross-signed certificates are not supported
/// @param certificate_type type of leaf certificate that we start the search from
/// @param encoding specifies PEM or DER format
/// @param include_ocsp if OCSP data should be included
/// @return contains response result, with info related to the full certificate chains info and response status
GetCertificateFullInfoResult get_all_valid_certificates_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
bool update_certificate_links(LeafCertificateType certificate_type);
Expand Down Expand Up @@ -254,8 +273,21 @@ class EvseSecurity {
// Internal versions of the functions do not lock the mutex
CertificateValidationResult verify_certificate_internal(const std::string& certificate_chain,
LeafCertificateType certificate_type);

GetCertificateInfoResult get_leaf_certificate_info_internal(LeafCertificateType certificate_type,
EncodingFormat encoding, bool include_ocsp = false);

/// @brief Retrieves information related to leaf certificates
/// @param include_ocsp if OCSP information should be included
/// @param include_root if the root certificate of the leaf should be included in the returned list
/// @param include_all_valid if true, all valid leafs will be included, sorted in order, with the newest being
/// first. If false, only the newest one will be returned
GetCertificateFullInfoResult get_full_leaf_certificate_info_internal(LeafCertificateType certificate_type,
EncodingFormat encoding,
bool include_ocsp = false,
bool include_root = false,
bool include_all_valid = 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);
Expand Down
14 changes: 11 additions & 3 deletions include/evse_security/evse_types.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,13 @@
};

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
fs::path key; ///< The path of the PEM or DER encoded private key
std::optional<std::string> certificate_root; ///< The PEM of root certificate used by the leaf, has a value only
/// when using 'get_all_valid_certificates_info'
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 single certificate if found
int certificate_count; ///< The count of certificates, if the chain is available, or 1 if single
/// (the root is not taken into account because of the OCSP cache)
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
};
Expand All @@ -156,6 +159,11 @@
std::optional<CertificateInfo> info;
};

struct GetCertificateFullInfoResult {
GetCertificateInfoStatus status;
std::vector<CertificateInfo> info;

Check notice on line 164 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#L164

struct member 'GetCertificateFullInfoResult::info' is never used.
};

struct GetCertificateSignRequestResult {
GetCertificateSignRequestStatus status;
std::optional<std::string> csr;
Expand Down
22 changes: 22 additions & 0 deletions lib/evse_security/certificate/x509_hierarchy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ bool X509CertificateHierarchy::contains_certificate_hash(const CertificateHashDa
return contains;
}

X509Wrapper X509CertificateHierarchy::find_certificate_root(const X509Wrapper& leaf) {
const X509Wrapper* root_ptr = nullptr;

for (const auto& root : hierarchy) {
if (root.state.is_selfsigned) {
for_each_descendant(
[&](const X509Node& node, int depth) {
// If we found our matching certificate, we also found the root
if (node.certificate == leaf) {
root_ptr = &root.certificate;
}
},
root, 1);
}
}

if (root_ptr)
return *root_ptr;

throw NoCertificateFound("Could not find a certificate root for leaf: " + leaf.get_common_name());
}

X509Wrapper X509CertificateHierarchy::find_certificate(const CertificateHashData& hash) {
X509Wrapper* certificate = nullptr;

Expand Down
Loading
Loading