Skip to content

Commit

Permalink
Multiple root trusted CA leafs (#91)
Browse files Browse the repository at this point in the history
* Added interface changes for supporting trusted CA keys (multiple roots)
* Added get full certs implementation
* Add tests for testing multi-root return
* Tests implementation with alternate root
* Implemented comments
* Documentation update related to library limitations

---------

Signed-off-by: AssemblyJohn <[email protected]>
  • Loading branch information
AssemblyJohn authored Oct 21, 2024
1 parent a80ea8b commit 5c0b665
Show file tree
Hide file tree
Showing 11 changed files with 434 additions and 99 deletions.
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 CertificateOCSP {
};

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 @@ struct GetCertificateInfoResult {
std::optional<CertificateInfo> info;
};

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

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

0 comments on commit 5c0b665

Please sign in to comment.