diff --git a/README.md b/README.md index c7111f8b..3cf50d76 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ As of this version : * All the messages defined in the OCPP 1.6 edition 2 protocol have been implemented except GetCompositeSchedule for Charge Point role * All the configuration keys defined in the OCPP 1.6 edition 2 protocol have been implemented for the Charge Point role * All the messages defined in the OCPP 1.6 security whitepaper edition 2 have been implemented +* All the messages defined in the Using ISO 15118 Plug & Charge with OCPP 1.6 Application Note v1.0 have been implemented The user application will have to implement some callbacks to provide the data needed by **Open OCPP** or to handle OCPP events (boot notification, remote start/stop notifications, meter values...). @@ -153,6 +154,11 @@ In the "Owner" column, "S" means that the configuration key behavior is handled | CpoName | S | None | | SecurityProfile | S | None | | SupportedFileTransferProtocols | U | None | +| CentralContractValidationAllowed | S | None +| CertSigningWaitMinimum | S | None | +| CertSigningRepeatTimes | S | None | +| ContractValidationOffline | U/S | The stack will notify the user application for contract validation depending on the value of this parameter +| ISO15118PnCEnabled | S | None | ### OCPP security extensions @@ -211,6 +217,24 @@ If **InternalCertificateManagementEnabled** is set to **true**, the storage of c **Open OCPP** provides helper classes based on OpenSSL to ease private keys, certificate and certificate requests usage : generation, signature, verification. They can be used in the user application callbacks. These helpers can be found in the ocpp::tools::x509 namespace and are widely used in the **Open OCPP** source code and examples. +### OCPP IS15118 PnC extensions + +**Open OCPP** fully supports the whole messaging, data types and configuration keys set associated to the ISO15118 PnC extensions. + +#### Charge Point role + +In Charge Point role these extensions consists mainly on forwarding messages from the ISO15118-2 stack layer to the Central System by using dedicated DataTransfer messages. + +**Open OCPP** implements the forwarding and provides callback and retries capabilities for certificates messages. + +Allthough **Open OCPP** is able to manage a certificate store, the Chare Point certificate used for ISO15118 communication won't be stored in it even if the **InternalCertificateManagementEnabled** configuration key is set to **true**. This is will allow for the ISO15118-2 stack to access this certificate and use it to secure its communications with the vehicule. + +#### Central System role + +In Central System role these extensions consists mainly in certificate management by forwarding request either to OCSP server or Mobility Operators. + +**Open OCPP** provides callbacks where the forwarding to the necessary servers must be implemented. + ### Internal configuration keys The behavior and the configuration of the **Open OCPP** stack can be modified through configuration keys. Some are specific to an OCPP role and some are common. diff --git a/doc/ocpp_1_6_ISO_15118_v10.pdf b/doc/ocpp_1_6_ISO_15118_v10.pdf new file mode 100755 index 00000000..8c6799e4 Binary files /dev/null and b/doc/ocpp_1_6_ISO_15118_v10.pdf differ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index afa0e7f4..27a29794 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,6 +1,8 @@ # Subdirectories add_subdirectory(common) +add_subdirectory(iso15118_chargepoint) +add_subdirectory(iso15118_centralsystem) add_subdirectory(quick_start_centralsystem) add_subdirectory(quick_start_chargepoint) add_subdirectory(quick_start_localcontroller) diff --git a/examples/README.md b/examples/README.md index 8fa0b46e..8c6bf2e5 100644 --- a/examples/README.md +++ b/examples/README.md @@ -6,6 +6,7 @@ The following examples are available for OCPP 1.6 standard : * [Quick start Central System example](./quick_start_centralsystem/README.md) * [Quick start Charge Point example](./quick_start_chargepoint/README.md) +* [Quick start Local Controller example](./quick_start_localcontroller/README.md) * [Remote Charge Point example](./remote_chargepoint/README.md) The following examples are available for OCPP 1.6 security extensions : @@ -13,6 +14,11 @@ The following examples are available for OCPP 1.6 security extensions : * [Security Central System example](./security_centralsystem/README.md) * [Security Charge Point example](./security_chargepoint/README.md) +The following examples are available for OCPP 1.6 IS015118 PnC extensions : + +* [ISO15118 Central System example](./iso15118_centralsystem/README.md) +* [ISO15118 Charge Point example](./iso15118_chargepoint/README.md) + How to run the examples: * Customize the *config.ini* file of the selected example with the URL of the Central System and the other connection parameters has well has the OCPP configuration keys diff --git a/examples/common/DefaultCentralSystemEventsHandler.cpp b/examples/common/DefaultCentralSystemEventsHandler.cpp index c3149ba4..a8232d3b 100644 --- a/examples/common/DefaultCentralSystemEventsHandler.cpp +++ b/examples/common/DefaultCentralSystemEventsHandler.cpp @@ -23,6 +23,7 @@ SOFTWARE. */ #include "DefaultCentralSystemEventsHandler.h" +#include "PrivateKey.h" #include "Sha2.h" #include "String.h" @@ -36,7 +37,17 @@ using namespace ocpp::types; using namespace ocpp::x509; /** @brief Constructor */ -DefaultCentralSystemEventsHandler::DefaultCentralSystemEventsHandler() : m_chargepoints() { } +DefaultCentralSystemEventsHandler::DefaultCentralSystemEventsHandler(std::filesystem::path iso_v2g_root_ca, + std::filesystem::path iso_mo_root_ca, + bool set_pending_status) + : m_iso_v2g_root_ca(iso_v2g_root_ca), + m_iso_mo_root_ca(iso_mo_root_ca), + m_set_pending_status(set_pending_status), + m_chargepoints(), + m_pending_chargepoints(), + m_accepted_chargepoints() +{ +} /** @brief Destructor */ DefaultCentralSystemEventsHandler::~DefaultCentralSystemEventsHandler() { } @@ -71,14 +82,12 @@ void DefaultCentralSystemEventsHandler::chargePointConnected(std::shared_ptridentifier() << "] - Boot notification : vendor = " << vendor << " - model = " << model << " - s/n = " << serial_number << " - firmware = " << firmware_version << endl; - return RegistrationStatus::Accepted; + ocpp::types::RegistrationStatus ret = RegistrationStatus::Accepted; + if (m_event_handler.setPendingEnabled()) + { + auto accepted_chargepoint = m_event_handler.acceptedChargePoints(); + auto iter_accepted = accepted_chargepoint.find(m_chargepoint->identifier()); + if (iter_accepted == accepted_chargepoint.end()) + { + m_event_handler.pendingChargePoints()[m_chargepoint->identifier()] = m_chargepoint; + + ret = RegistrationStatus::Pending; + } + } + + return ret; } /** @copydoc ocpp::types::DataTransferStatus IChargePointRequestHandler::dataTransfer(const std::string&, @@ -307,7 +329,7 @@ void DefaultCentralSystemEventsHandler::ChargePointRequestHandler::logStatusNoti { cout << "[" << m_chargepoint->identifier() << "] - Log status notification : status = " << UploadLogStatusEnumTypeHelper.toString(status) - << " - request_id = " << (request_id.isSet() ? std::to_string(request_id) : "not set"); + << " - request_id = " << (request_id.isSet() ? std::to_string(request_id) : "not set") << endl; } /** @copydoc void IChargePointRequestHandler::securityEventNotification(const std::string&, @@ -377,12 +399,12 @@ bool DefaultCentralSystemEventsHandler::ChargePointRequestHandler::signCertifica else { cout << "[" << m_chargepoint->identifier() - << "] - Failed to generate certificate bundle : " << generate_bundle_cmd_line.str(); + << "] - Failed to generate certificate bundle : " << generate_bundle_cmd_line.str() << endl; } } else { - cout << "[" << m_chargepoint->identifier() << "] - Failed to sign the CSR : " << sign_cert_cmd_line.str(); + cout << "[" << m_chargepoint->identifier() << "] - Failed to sign the CSR : " << sign_cert_cmd_line.str() << endl; } // Remove the temporary files @@ -391,7 +413,7 @@ bool DefaultCentralSystemEventsHandler::ChargePointRequestHandler::signCertifica } else { - cout << "[" << m_chargepoint->identifier() << "] - Unable to create CSR file : " << csr_filename; + cout << "[" << m_chargepoint->identifier() << "] - Unable to create CSR file : " << csr_filename << endl; } } else @@ -413,5 +435,179 @@ void DefaultCentralSystemEventsHandler::ChargePointRequestHandler::signedFirmwar { cout << "[" << m_chargepoint->identifier() << "] - Signed firmware update status notification : status = " << FirmwareStatusEnumTypeHelper.toString(status) - << " - request_id = " << (request_id.isSet() ? std::to_string(request_id) : "not set"); + << " - request_id = " << (request_id.isSet() ? std::to_string(request_id) : "not set") << endl; +} + +// ISO 15118 PnC extensions + +/** @copydoc ocpp::types::IdTokenInfoType IChargePointRequestHandler::iso15118Authorize( + const ocpp::x509::Certificate&, + const std::string&, + const std::vector&, + ocpp::types::Optional&) override; */ +ocpp::types::IdTokenInfoType DefaultCentralSystemEventsHandler::ChargePointRequestHandler::iso15118Authorize( + const ocpp::x509::Certificate& certificate, + const std::string& id_token, + const std::vector& cert_hash_data, + ocpp::types::Optional& cert_status) +{ + cout << "[" << m_chargepoint->identifier() << "] - [ISO15118] Authorize : certificate = " << certificate.pem().size() + << " - id_token = " << id_token << " - cert_hash_data size = " << cert_hash_data.size() << endl; + + // Prepare response + ocpp::types::IdTokenInfoType ret; + ret.status = AuthorizationStatus::Invalid; + + // Check certificate if present + if (certificate.isValid()) + { + cert_status = AuthorizeCertificateStatusEnumType::Accepted; + ret.status = AuthorizationStatus::Accepted; + } + else + { + if (!cert_hash_data.empty()) + { + // Forward to OCSP => TODO with OpenSSL + } + + // For now, always accept + ret.status = AuthorizationStatus::Accepted; + } + + return ret; +} + +/** @copydoc ocpp::types::Iso15118EVCertificateStatusEnumType IChargePointRequestHandler::iso15118GetEVCertificate( + const std::string&, + ocpp::types::CertificateActionEnumType, + const std::string&, + std::string&) */ +ocpp::types::Iso15118EVCertificateStatusEnumType DefaultCentralSystemEventsHandler::ChargePointRequestHandler::iso15118GetEVCertificate( + const std::string& iso15118_schema_version, + ocpp::types::CertificateActionEnumType action, + const std::string& exi_request, + std::string& exi_response) +{ + cout << "[" << m_chargepoint->identifier() + << "] - [ISO15118] Get EV certificate : iso15118_schema_version = " << iso15118_schema_version + << " - action = " << CertificateActionEnumTypeHelper.toString(action) << " - exi_request size = " << exi_request.size() << endl; + + // For the purpose of this example, the EXI response contains directly the EV certificate in PEM format + // In a real system, the certificate is embedded in an EXI message + + // Generate CSR for the EV certificate + X509Document::Subject ev_cert_subject; + ev_cert_subject.country = "FR"; + ev_cert_subject.state = "Savoie"; + ev_cert_subject.location = "Chambery"; + ev_cert_subject.organization = "Open OCPP"; + ev_cert_subject.organization_unit = "Examples"; + ev_cert_subject.common_name = "MO EV certificate"; + ev_cert_subject.email_address = "ca.examples@open-ocpp.org"; + PrivateKey ev_cert_key(PrivateKey::Type::EC, PrivateKey::Curve::PRIME256_V1, ""); + CertificateRequest ev_cert_req(ev_cert_subject, ev_cert_key, Sha2::SHA256); + + // Sign the certificate with the MO root certificate + Certificate mo_root_ca(m_event_handler.moRootCA()); + std::string mo_root_ca_key_path = m_event_handler.moRootCA(); + ocpp::helpers::replace(mo_root_ca_key_path, ".pem", ".key"); + PrivateKey mo_root_ca_key(std::filesystem::path(mo_root_ca_key_path), ""); + Certificate ev_cert(ev_cert_req, mo_root_ca, mo_root_ca_key, Sha2::SHA256, 7300); + + // Put certificate inside the response + exi_response = ev_cert.pem(); + + return Iso15118EVCertificateStatusEnumType::Accepted; +} + +/** @copydoc ocpp::types::GetCertificateStatusEnumType IChargePointRequestHandler::iso15118GetCertificateStatus( + const ocpp::types::OcspRequestDataType&, + std::string&) */ +ocpp::types::GetCertificateStatusEnumType DefaultCentralSystemEventsHandler::ChargePointRequestHandler::iso15118GetCertificateStatus( + const ocpp::types::OcspRequestDataType& ocsp_request, std::string& ocsp_result) +{ + + cout << "[" << m_chargepoint->identifier() + << "] - [ISO15118] Get EV certificate status : serial number = " << ocsp_request.serialNumber.str() << endl; + + // Forward to OCSP => TODO with OpenSSL + (void)ocsp_result; + + return GetCertificateStatusEnumType::Accepted; } + +/** @copydoc bool iso15118SignCertificate(const ocpp::x509::CertificateRequest&) */ +bool DefaultCentralSystemEventsHandler::ChargePointRequestHandler::iso15118SignCertificate( + const ocpp::x509::CertificateRequest& certificate_request) +{ + bool ret = false; + cout << "[" << m_chargepoint->identifier() << "] - [ISO15118] Sign certificate : subject = " << certificate_request.subjectString() + << endl; + + // Load CA certificate which will sign the request + std::filesystem::path ca_cert_path = m_event_handler.v2gRootCA(); + Certificate ca_cert(ca_cert_path); + if (ca_cert.isValid()) + { + // Save the request to a temporary file + Sha2 sha256; + sha256.compute(certificate_request.pem().c_str(), certificate_request.pem().size()); + + std::stringstream name; + name << "/tmp/csr_" << sha256.resultString() << ".pem"; + std::string csr_filename = name.str(); + if (certificate_request.toFile(csr_filename)) + { + // Sign the certificate request to generate a certificate + std::string ca_cert_key_path = ca_cert_path; + ocpp::helpers::replace(ca_cert_key_path, ".pem", ".key"); + ocpp::helpers::replace(ca_cert_key_path, ".crt", ".key"); + std::string certificate_filename = csr_filename + ".crt"; + std::stringstream sign_cert_cmd_line; + sign_cert_cmd_line << "openssl x509 -req -sha256 -days 3650 -in " << csr_filename << " -CA " << ca_cert_path << " -CAkey " + << ca_cert_key_path << " -CAcreateserial -out " << certificate_filename; + int err = WEXITSTATUS(system(sign_cert_cmd_line.str().c_str())); + cout << "Command line : " << sign_cert_cmd_line.str() << " => " << err << endl; + + // Check if the certificate has been generated + if (std::filesystem::exists(certificate_filename)) + { + // Create bundle + std::string bundle_filename = certificate_filename + ".bundle"; + std::stringstream generate_bundle_cmd_line; + generate_bundle_cmd_line << "cat " << certificate_filename << " " << ca_cert_path << " > " << bundle_filename; + err = WEXITSTATUS(system(generate_bundle_cmd_line.str().c_str())); + cout << "Command line : " << generate_bundle_cmd_line.str() << " => " << err << endl; + if (std::filesystem::exists(bundle_filename)) + { + m_generated_certificate = bundle_filename; + ret = true; + } + else + { + cout << "[" << m_chargepoint->identifier() + << "] - [ISO15118] Failed to generate certificate bundle : " << generate_bundle_cmd_line.str() << endl; + } + } + else + { + cout << "[" << m_chargepoint->identifier() << "] - [ISO15118] Failed to sign the CSR : " << sign_cert_cmd_line.str() + << endl; + } + + // Remove the temporary files + std::filesystem::remove(csr_filename); + std::filesystem::remove(certificate_filename); + } + else + { + cout << "[" << m_chargepoint->identifier() << "] - [ISO15118] Unable to create CSR file : " << csr_filename << endl; + } + } + else + { + cout << "[" << m_chargepoint->identifier() << "] - [ISO15118] Unable to load CA certificate : " << ca_cert_path << endl; + } + return ret; +} \ No newline at end of file diff --git a/examples/common/DefaultCentralSystemEventsHandler.h b/examples/common/DefaultCentralSystemEventsHandler.h index b7db5e0f..ab61e59c 100644 --- a/examples/common/DefaultCentralSystemEventsHandler.h +++ b/examples/common/DefaultCentralSystemEventsHandler.h @@ -28,6 +28,7 @@ SOFTWARE. #include "ICentralSystemEventsHandler.h" #include "IChargePointRequestHandler.h" +#include #include /** @brief Default central system event handlers implementation for the examples */ @@ -35,7 +36,9 @@ class DefaultCentralSystemEventsHandler : public ocpp::centralsystem::ICentralSy { public: /** @brief Constructor */ - DefaultCentralSystemEventsHandler(); + DefaultCentralSystemEventsHandler(std::filesystem::path iso_v2g_root_ca = "", + std::filesystem::path iso_mo_root_ca = "", + bool set_pending_status = false); /** @brief Destructor */ virtual ~DefaultCentralSystemEventsHandler(); @@ -177,6 +180,38 @@ class DefaultCentralSystemEventsHandler : public ocpp::centralsystem::ICentralSy void signedFirmwareUpdateStatusNotification(ocpp::types::FirmwareStatusEnumType status, const ocpp::types::Optional& request_id) override; + // ISO 15118 PnC extensions + + /** @copydoc ocpp::types::IdTokenInfoType IChargePointRequestHandler::iso15118Authorize( + const ocpp::x509::Certificate&, + const std::string&, + const std::vector&, + ocpp::types::Optional&) override; */ + ocpp::types::IdTokenInfoType iso15118Authorize( + const ocpp::x509::Certificate& certificate, + const std::string& id_token, + const std::vector& cert_hash_data, + ocpp::types::Optional& cert_status) override; + + /** @copydoc ocpp::types::Iso15118EVCertificateStatusEnumType IChargePointRequestHandler::iso15118GetEVCertificate( + const std::string&, + ocpp::types::CertificateActionEnumType, + const std::string&, + std::string&) */ + ocpp::types::Iso15118EVCertificateStatusEnumType iso15118GetEVCertificate(const std::string& iso15118_schema_version, + ocpp::types::CertificateActionEnumType action, + const std::string& exi_request, + std::string& exi_response) override; + + /** @copydoc ocpp::types::GetCertificateStatusEnumType IChargePointRequestHandler::iso15118GetCertificateStatus( + const ocpp::types::OcspRequestDataType&, + std::string&) */ + ocpp::types::GetCertificateStatusEnumType iso15118GetCertificateStatus(const ocpp::types::OcspRequestDataType& ocsp_request, + std::string& ocsp_result) override; + + /** @copydoc bool iso15118SignCertificate(const ocpp::x509::CertificateRequest&) */ + bool iso15118SignCertificate(const ocpp::x509::CertificateRequest& certificate_request) override; + protected: /** @brief Get the serial number of the charge point */ virtual std::string getChargePointSerialNumber(const std::string& chargepoint_id) @@ -199,12 +234,42 @@ class DefaultCentralSystemEventsHandler : public ocpp::centralsystem::ICentralSy /** @brief Get the list of the connected charge points */ std::map>& chargePoints() { return m_chargepoints; } + /** @brief Get the list of the pending charge points */ + std::map>& pendingChargePoints() + { + return m_pending_chargepoints; + } + + /** @brief Get the list of the accepted charge points */ + std::map>& acceptedChargePoints() + { + return m_accepted_chargepoints; + } + + /** @brief Path to the V2G root CA */ + std::filesystem::path& v2gRootCA() { return m_iso_v2g_root_ca; } + /** @brief Path to the MO root CA */ + std::filesystem::path& moRootCA() { return m_iso_mo_root_ca; } + + /** @brief Indicate if the charge point must be set on pending status upon connection */ + bool setPendingEnabled() const { return m_set_pending_status; } + /** @brief Remove a charge point from the connected charge points */ void removeChargePoint(const std::string& identifier); private: + /** @brief Path to the V2G root CA */ + std::filesystem::path m_iso_v2g_root_ca; + /** @brief Path to the MO root CA */ + std::filesystem::path m_iso_mo_root_ca; + /** @brief Indicate if the charge point must be set on pending status upon connection */ + bool m_set_pending_status; /** @brief Connected charge points */ std::map> m_chargepoints; + /** @brief Pending charge points */ + std::map> m_pending_chargepoints; + /** @brief Accepted charge points */ + std::map> m_accepted_chargepoints; }; #endif // DEFAULTCENTRALSYSTEMEVENTSHANDLER_H diff --git a/examples/common/DefaultChargePointEventsHandler.cpp b/examples/common/DefaultChargePointEventsHandler.cpp index b6ae8dc6..8e051b19 100644 --- a/examples/common/DefaultChargePointEventsHandler.cpp +++ b/examples/common/DefaultChargePointEventsHandler.cpp @@ -330,7 +330,7 @@ ocpp::types::CertificateStatusEnumType DefaultChargePointEventsHandler::caCertif << " - certificate subject = " << certificate.subjectString() << endl; // Check number of installed certificates - if (getNumberOfCaCertificateInstalled(true, true) < m_config.ocppConfig().certificateStoreMaxLength()) + if (getNumberOfCaCertificateInstalled(true, true, true) < m_config.ocppConfig().certificateStoreMaxLength()) { // Compute SHA256 to generate filename Sha2 sha256; @@ -348,7 +348,7 @@ ocpp::types::CertificateStatusEnumType DefaultChargePointEventsHandler::caCertif { // Central System => Check AdditionalRootCertificateCheck configuration key - if (m_config.ocppConfig().additionalRootCertificateCheck() && (getNumberOfCaCertificateInstalled(false, true) == 0)) + if (m_config.ocppConfig().additionalRootCertificateCheck() && (getNumberOfCaCertificateInstalled(false, true, false) == 0)) { // Additionnal checks : // - only 1 CA certificate allowed @@ -474,7 +474,8 @@ ocpp::types::DeleteCertificateStatusEnumType DefaultChargePointEventsHandler::de if (!dir_entry.is_directory()) { std::string filename = dir_entry.path().filename(); - if ((ocpp::helpers::startsWith(filename, "fw_") || ocpp::helpers::startsWith(filename, "cs_")) && + if ((ocpp::helpers::startsWith(filename, "fw_") || ocpp::helpers::startsWith(filename, "cs_") || + ocpp::helpers::startsWith(filename, "iso_")) && ocpp::helpers::endsWith(filename, ".pem")) { Certificate certificate(dir_entry.path()); @@ -605,7 +606,7 @@ std::string DefaultChargePointEventsHandler::getLog(ocpp::types::LogEnumType bool DefaultChargePointEventsHandler::hasCentralSystemCaCertificateInstalled() { // A better implementation would also check the validity dates of the certificates - return ((getNumberOfCaCertificateInstalled(false, true) != 0) && (!m_config.stackConfig().tlsServerCertificateCa().empty())); + return ((getNumberOfCaCertificateInstalled(false, true, false) != 0) && (!m_config.stackConfig().tlsServerCertificateCa().empty())); } /** @copydoc bool IChargePointEventsHandler::hasChargePointCertificateInstalled() */ @@ -670,8 +671,206 @@ ocpp::types::UpdateFirmwareStatusEnumType DefaultChargePointEventsHandler::check return ret; } +// ISO 15118 PnC extensions + +/** @copydoc bool IChargePointEventsHandler::iso15118CheckEvCertificate(const ocpp::x509::Certificate&) */ +bool DefaultChargePointEventsHandler::iso15118CheckEvCertificate(const ocpp::x509::Certificate& certificate) +{ + bool ret = false; + + cout << "ISO15118 EV certificate verification requested : certificate subject = " << certificate.subjectString() << endl; + + // Look for MO certificates + for (auto const& dir_entry : std::filesystem::directory_iterator{m_working_dir}) + { + if (!dir_entry.is_directory()) + { + std::string filename = dir_entry.path().filename(); + if (ocpp::helpers::startsWith(filename, "iso_mo_root_") && ocpp::helpers::endsWith(filename, ".pem")) + { + Certificate mo_cert(dir_entry.path()); + if (certificate.verify(mo_cert.certificateChain())) + { + cout << "Validated against certificate : " << mo_cert.subjectString() << endl; + ret = true; + break; + } + } + } + } + + cout << "EV certificate validated : " << (ret ? "yes" : "no") << endl; + + return ret; +} + +/** @copydoc bool IChargePointEventsHandler::iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate&) */ +bool DefaultChargePointEventsHandler::iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate& certificate) +{ + std::string ca_filename; + bool ret = false; + + cout << "ISO15118 Charge point certificate installation requested : certificate subject = " << certificate.subjectString() << endl; + + // Compute SHA256 to generate filename + Sha2 sha256; + sha256.compute(certificate.pem().c_str(), certificate.pem().size()); + + std::stringstream name; + name << "iso_cp_" << sha256.resultString() << ".pem"; + std::string cert_filename = m_working_dir / name.str(); + + // Save certificate + if (certificate.toFile(cert_filename)) + { + // Retrieve and save the corresponding key/pair with the new certificate + std::string cert_key_filename = cert_filename + ".key"; + std::filesystem::copy("/tmp/charge_point_key.key", cert_key_filename); + + cout << "Certificate saved : " << cert_filename << endl; + ret = true; + } + else + { + cout << "Unable to save certificate : " << cert_filename << endl; + } + + return ret; +} + +/** @copydoc ocpp::types::DeleteCertificateStatusEnumType IChargePointEventsHandler::iso15118DeleteCertificate(ocpp::types::HashAlgorithmEnumType, + const std::string&, + const std::string&, + const std::string&) */ +ocpp::types::DeleteCertificateStatusEnumType DefaultChargePointEventsHandler::iso15118DeleteCertificate( + ocpp::types::HashAlgorithmEnumType hash_algorithm, + const std::string& issuer_name_hash, + const std::string& issuer_key_hash, + const std::string& serial_number) +{ + cout << "ISO15118 certificate deletion requested : hash = " << HashAlgorithmEnumTypeHelper.toString(hash_algorithm) + << " - serial number = " << serial_number << endl; + return deleteCertificate(hash_algorithm, issuer_name_hash, issuer_key_hash, serial_number); +} + +/** @copydoc void IChargePointEventsHandler::iso15118GetInstalledCertificates( + bool, + bool, + bool, + std::vector>>&) */ +void DefaultChargePointEventsHandler::iso15118GetInstalledCertificates( + bool v2g_root_certificate, + bool mo_root_certificate, + bool v2g_certificate_chain, + std::vector>>& + certificates) +{ + cout << "ISO15118 get installed certificates requested : v2g_root_certificate = " << (v2g_root_certificate ? "yes" : "no") + << " - mo_root_certificate = " << (mo_root_certificate ? "yes" : "no") + << " - v2g_certificate_chain = " << (v2g_certificate_chain ? "yes" : "no") << endl; + + for (auto const& dir_entry : std::filesystem::directory_iterator{m_working_dir}) + { + if (!dir_entry.is_directory()) + { + std::string filename = dir_entry.path().filename(); + if (v2g_root_certificate) + { + if (ocpp::helpers::startsWith(filename, "iso_v2g_root_") && ocpp::helpers::endsWith(filename, ".pem")) + { + auto tuple = std::make_tuple(GetCertificateIdUseEnumType::V2GRootCertificate, + Certificate(dir_entry.path()), + std::vector()); + certificates.emplace_back(std::move(tuple)); + } + } + if (mo_root_certificate) + { + if (ocpp::helpers::startsWith(filename, "iso_mo_root_") && ocpp::helpers::endsWith(filename, ".pem")) + { + auto tuple = std::make_tuple(GetCertificateIdUseEnumType::MORootCertificate, + Certificate(dir_entry.path()), + std::vector()); + certificates.emplace_back(std::move(tuple)); + } + } + if (v2g_certificate_chain) + { + if (ocpp::helpers::startsWith(filename, "iso_v2g_chain_") && ocpp::helpers::endsWith(filename, ".pem")) + { + auto tuple = std::make_tuple(GetCertificateIdUseEnumType::V2GCertificateChain, + Certificate(dir_entry.path()), + std::vector()); + certificates.emplace_back(std::move(tuple)); + } + } + } + } +} + +/** @copydoc ocpp::types::InstallCertificateStatusEnumType IChargePointEventsHandler::iso15118CertificateReceived( + ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate&) */ +ocpp::types::InstallCertificateStatusEnumType DefaultChargePointEventsHandler::iso15118CertificateReceived( + ocpp::types::InstallCertificateUseEnumType type, const ocpp::x509::Certificate& certificate) +{ + std::string cert_filename; + InstallCertificateStatusEnumType ret = InstallCertificateStatusEnumType::Rejected; + + cout << "ISO15118 certificate installation requested : type = " << InstallCertificateUseEnumTypeHelper.toString(type) + << " - certificate subject = " << certificate.subjectString() << endl; + + // Check number of installed certificates + if (getNumberOfCaCertificateInstalled(true, true, true) < m_config.ocppConfig().certificateStoreMaxLength()) + { + // Compute SHA256 to generate filename + Sha2 sha256; + sha256.compute(certificate.pem().c_str(), certificate.pem().size()); + + if (type == InstallCertificateUseEnumType::V2GRootCertificate) + { + // V2 root certificate + std::stringstream name; + name << "iso_v2g_root_" << sha256.resultString() << ".pem"; + cert_filename = m_working_dir / name.str(); + } + else + { + // MO root certificate + std::stringstream name; + name << "iso_mo_root_" << sha256.resultString() << ".pem"; + cert_filename = m_working_dir / name.str(); + } + + // Save certificate + if (certificate.toFile(cert_filename)) + { + ret = InstallCertificateStatusEnumType::Accepted; + cout << "Certificate saved : " << cert_filename << endl; + } + else + { + ret = InstallCertificateStatusEnumType::Failed; + cout << "Unable to save certificate : " << cert_filename << endl; + } + } + else + { + cout << "Maximum number of certificates reached" << endl; + } + + return ret; +} + +/** @copydoc void IChargePointEventsHandler::iso15118GenerateCsr(std::string&) */ +void DefaultChargePointEventsHandler::iso15118GenerateCsr(std::string& csr) +{ + cout << "Generate ISO15118 CSR requested" << endl; + generateCsr(csr); +} + /** @brief Get the number of installed CA certificates */ -unsigned int DefaultChargePointEventsHandler::getNumberOfCaCertificateInstalled(bool manufacturer, bool central_system) +unsigned int DefaultChargePointEventsHandler::getNumberOfCaCertificateInstalled(bool manufacturer, bool central_system, bool iso15118) { unsigned int count = 0; for (auto const& dir_entry : std::filesystem::directory_iterator{m_working_dir}) @@ -687,6 +886,10 @@ unsigned int DefaultChargePointEventsHandler::getNumberOfCaCertificateInstalled( { count++; } + if (iso15118 && ocpp::helpers::startsWith(filename, "iso_") && ocpp::helpers::endsWith(filename, ".pem")) + { + count++; + } } } return count; diff --git a/examples/common/DefaultChargePointEventsHandler.h b/examples/common/DefaultChargePointEventsHandler.h index 6100b478..e9df4232 100644 --- a/examples/common/DefaultChargePointEventsHandler.h +++ b/examples/common/DefaultChargePointEventsHandler.h @@ -162,6 +162,44 @@ class DefaultChargePointEventsHandler : public ocpp::chargepoint::IChargePointEv * const ocpp::x509::Certificate&) */ ocpp::types::UpdateFirmwareStatusEnumType checkFirmwareSigningCertificate(const ocpp::x509::Certificate& signing_certificate) override; + // ISO 15118 PnC extensions + + /** @copydoc bool IChargePointEventsHandler::iso15118CheckEvCertificate(const ocpp::x509::Certificate&) */ + bool iso15118CheckEvCertificate(const ocpp::x509::Certificate& certificate) override; + + /** @copydoc bool IChargePointEventsHandler::iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate&) */ + bool iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate& certificate) override; + + /** @copydoc ocpp::types::DeleteCertificateStatusEnumType IChargePointEventsHandler::iso15118DeleteCertificate(ocpp::types::HashAlgorithmEnumType, + const std::string&, + const std::string&, + const std::string&) */ + ocpp::types::DeleteCertificateStatusEnumType iso15118DeleteCertificate(ocpp::types::HashAlgorithmEnumType hash_algorithm, + const std::string& issuer_name_hash, + const std::string& issuer_key_hash, + const std::string& serial_number) override; + + /** @copydoc void IChargePointEventsHandler::iso15118GetInstalledCertificates( + bool, + bool, + bool, + std::vector>>&) */ + void iso15118GetInstalledCertificates( + bool v2g_root_certificate, + bool mo_root_certificate, + bool v2g_certificate_chain, + std::vector>>& + certificates) override; + + /** @copydoc ocpp::types::InstallCertificateStatusEnumType IChargePointEventsHandler::iso15118CertificateReceived( + * ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate&) */ + ocpp::types::InstallCertificateStatusEnumType iso15118CertificateReceived(ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate& certificate) override; + + /** @copydoc void IChargePointEventsHandler::iso15118GenerateCsr(std::string&) */ + void iso15118GenerateCsr(std::string& csr) override; + // API /** @brief Indicate a pending remote start transaction */ @@ -198,7 +236,7 @@ class DefaultChargePointEventsHandler : public ocpp::chargepoint::IChargePointEv std::vector m_remote_start_id_tag; /** @brief Get the number of installed CA certificates */ - unsigned int getNumberOfCaCertificateInstalled(bool manufacturer, bool central_system); + unsigned int getNumberOfCaCertificateInstalled(bool manufacturer, bool central_system, bool iso15118); }; #endif // DEFAULTCHARGEPOINTEVENTSHANDLER_H diff --git a/examples/common/config/CentralSystemConfig.h b/examples/common/config/CentralSystemConfig.h index 6c2c7557..09a42d7b 100644 --- a/examples/common/config/CentralSystemConfig.h +++ b/examples/common/config/CentralSystemConfig.h @@ -84,6 +84,12 @@ class CentralSystemConfig : public ocpp::config::ICentralSystemConfig /** @brief Maximum number of entries in the log (0 = no logs in database) */ unsigned int logMaxEntriesCount() const override { return get("LogMaxEntriesCount"); } + // ISO 15118 PnC extensions + + /** @brief If this variable set to true, then the Central System supports ISO 15118 plug and charge messages via the DataTransfer mechanism as + described in this application note. */ + bool iso15118PnCEnabled() const override { return getBool("Iso15118PnCEnabled"); } + private: /** @brief Configuration file */ ocpp::helpers::IniFile& m_config; diff --git a/examples/common/config/OcppConfig.cpp b/examples/common/config/OcppConfig.cpp index 5561d49a..826d52b6 100644 --- a/examples/common/config/OcppConfig.cpp +++ b/examples/common/config/OcppConfig.cpp @@ -46,9 +46,9 @@ using namespace ocpp::helpers; /** @brief List of configuration values with their attributes */ static const map CONFIGURATION_VALUES = { - /// - /// Stand OCPP configuration - /// + // + // Standart OCPP configuration + // {"AllowOfflineTxForUnknownId", PARAM_READ_WRITE | PARAM_OCPP}, {"AuthorizationCacheEnabled", PARAM_READ_WRITE | PARAM_OCPP}, {"AuthorizeRemoteTxRequests", PARAM_READ_WRITE | PARAM_OCPP}, @@ -93,6 +93,9 @@ static const map CONFIGURATION_VALUES = { {"ConnectorSwitch3to1PhaseSupported", PARAM_READ | PARAM_OCPP}, {"MaxChargingProfilesInstalled", PARAM_READ | PARAM_OCPP}, {"MaxChargingProfilesInstalled", PARAM_READ | PARAM_OCPP}, + // + // Security extensions + // {"AdditionalRootCertificateCheck", PARAM_READ | PARAM_OCPP}, {"AuthorizationKey", PARAM_WRITE | PARAM_OCPP}, {"CertificateSignedMaxChainSize", PARAM_READ | PARAM_OCPP}, @@ -100,10 +103,18 @@ static const map CONFIGURATION_VALUES = { {"CpoName", PARAM_READ | PARAM_WRITE | PARAM_OCPP}, {"SecurityProfile", PARAM_READ_WRITE | PARAM_OCPP}, {"SupportedFileTransferProtocols", PARAM_READ | PARAM_OCPP}, + // + // ISO 15118 PnC extensions + // + {"CentralContractValidationAllowed", PARAM_READ_WRITE | PARAM_OCPP}, + {"CertSigningWaitMinimum", PARAM_READ_WRITE | PARAM_OCPP}, + {"CertSigningRepeatTimes", PARAM_READ_WRITE | PARAM_OCPP}, + {"ContractValidationOffline", PARAM_READ_WRITE | PARAM_OCPP}, + {"Iso15118PnCEnabled", PARAM_READ_WRITE | PARAM_OCPP}, - /// - /// Charge point configuration - /// + // + // Charge point configuration + // {"ConnexionUrl", PARAM_READ_WRITE | PARAM_REBOOT}, {"ChargePointIdentifier", PARAM_READ_WRITE | PARAM_REBOOT}, {"FirmwareVersion", PARAM_READ}}; diff --git a/examples/common/config/OcppConfig.h b/examples/common/config/OcppConfig.h index 8d9e4c68..c9556612 100644 --- a/examples/common/config/OcppConfig.h +++ b/examples/common/config/OcppConfig.h @@ -263,6 +263,37 @@ class OcppConfig : public ocpp::config::IOcppConfig Allowed values : FTP, FTPS, HTTP, HTTPS, SFTP */ std::string supportedFileTransferProtocols() const override { return getString("SupportedFileTransferProtocols"); } + // + // ISO 15118 PnC extensions + // + + /** @brief If this variable exists and has the value true, then the Charge Point can provide a contract certificate that it cannot + validate to the Central System for validation as part of the Authorize.req */ + bool centralContractValidationAllowed() const override { return getBool("CentralContractValidationAllowed"); } + + /** @brief This configuration key defines how long the Charge Point has to wait (in seconds) before generating another CSR, in the case the + Central System accepts the SignCertificate.req, but never returns the signed certificate back. This value will be doubled after every + attempt. The amount of attempts is configured at CertSigningRepeatTimes. If the certificate signing process is slow, this setting + allows the Central System to tell the Charge Point to allow more time. + Negative values must be rejected. The value 0 means that the Charge Point does not generate another CSR (leaving it up to the + Central System to trigger another certificate installation). */ + std::chrono::seconds certSigningWaitMinimum() const override { return get("CertSigningWaitMinimum"); } + + /** @brief This configuration key can be used to configure the amount of times the Charge Point SHALL double the previous back-off time, + starting with the number of seconds configured at CertSigningWaitMinimum, every time the back-off time expires without having + received the CertificateSigned.req containing the signed certificate based on the CSR generated. When the maximum number of + increments is reached, the Charge Point SHALL stop resending the SignCertificate.req, until it is requested by the Central System + using a TriggerMessage.req. + Negative values must be rejected. The value 0 means that the Charge Point does not double the back-off time. */ + unsigned int certSigningRepeatTimes() const override { return get("CertSigningRepeatTimes"); } + + /** @brief If this variable is true, then the Charge Point will try to validate a contract certificate when it is offline. */ + bool contractValidationOffline() const override { return getBool("ContractValidationOffline"); } + + /** @brief If this variable set to true, then the Charge Point supports ISO 15118 plug and charge messages via the DataTransfer mechanism as + described in this application note. */ + bool iso15118PnCEnabled() const override { return getBool("Iso15118PnCEnabled"); } + private: /** @brief Configuration file */ ocpp::helpers::IniFile& m_config; diff --git a/examples/iso15118_centralsystem/CMakeLists.txt b/examples/iso15118_centralsystem/CMakeLists.txt new file mode 100644 index 00000000..bbb92dbd --- /dev/null +++ b/examples/iso15118_centralsystem/CMakeLists.txt @@ -0,0 +1,24 @@ +###################################################### +# ISO15118 central system example project # +###################################################### + +# Executable target +add_executable(iso15118_centralsystem + main.cpp +) + +# Additionnal libraries path +target_link_directories(iso15118_centralsystem PRIVATE ${BIN_DIR}) + +# Dependencies +target_link_libraries(iso15118_centralsystem + examples_common + centralsystem +) + + +# Copy to binary directory +ADD_CUSTOM_COMMAND(TARGET iso15118_centralsystem + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/config/iso15118_centralsystem.ini ${BIN_DIR}/ +) diff --git a/examples/iso15118_centralsystem/README.md b/examples/iso15118_centralsystem/README.md new file mode 100644 index 00000000..892a7c8e --- /dev/null +++ b/examples/iso15118_centralsystem/README.md @@ -0,0 +1,33 @@ +# ISO15118 Central System example + +## Disclaimer + +The certificate management implemented in this example does not follow the state of the arts recommendations : + +* Secure storage of private keys / authentication credentials +* No password on some private keys +* ... and surely some more + +The choosen implementation has only be made to have a simple and comprehensive example of how to use **Open OCPP** features. + +## Description + +This example simulates a central system which accepts any charge point and implements the ISO15118 extensions. + +The central system loops on its connected charge points. For each charge point it simulates the following operations : + +* Get configuration to check if ISO15118 is enabled on charge point side +* List the ISO15118 certificates installed on the charge point +* Delete the ISO15118 certificates installed on the charge point +* Install new ISO1518 root certificates on the charge point +* Trigger an ISO15118 sign certificate request on charge point's side + +**Note :** Some of the ISO15118 messages are not containing real data since it would need an actual ISO15118-2 stack and an EV simulator. They +are just provided to demonstrate how to use them. + +## Command line + +iso15118_centralsystem [-w working_dir] [-r] + +* -w : Working directory where to store the configuration file (Default = current directory) +* -r : Reset all the OCPP persistent data diff --git a/examples/iso15118_centralsystem/config/iso15118_centralsystem.ini b/examples/iso15118_centralsystem/config/iso15118_centralsystem.ini new file mode 100644 index 00000000..0a222f37 --- /dev/null +++ b/examples/iso15118_centralsystem/config/iso15118_centralsystem.ini @@ -0,0 +1,19 @@ +[CentralSystem] +DatabasePath=./iso15118_centralsystem.db +JsonSchemasPath=../../schemas/ +ListenUrl=wss://127.0.0.1:8080/openocpp/ +CallRequestTimeout=2000 +WebSocketPingInterval=30 +BootNotificationRetryInterval=30 +HeartbeatInterval=3600 +HttpBasicAuthent=false +Tlsv12CipherList=ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-WITH-AES-256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:TLS-PSK-WITH-AES-256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-WITH-AES-128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:TLS-PSK-WITH-AES-128-GCM-SHA256 +Tlsv13CipherList=TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +TlsEcdhCurve=prime256v1 +TlsServerCertificate=../../examples/certificates/open-ocpp_central-system.crt +TlsServerCertificatePrivateKey=../../examples/certificates/open-ocpp_central-system.key +TlsServerCertificatePrivateKeyPassphrase= +TlsServerCertificateCa=../../examples/certificates/open-ocpp_ca.crt +TlsClientCertificateAuthent=true +LogMaxEntriesCount=2000 +Iso15118PnCEnabled=true diff --git a/examples/iso15118_centralsystem/main.cpp b/examples/iso15118_centralsystem/main.cpp new file mode 100644 index 00000000..af5e34fa --- /dev/null +++ b/examples/iso15118_centralsystem/main.cpp @@ -0,0 +1,303 @@ +/* +MIT License + +Copyright (c) 2020 Cedric Jimenez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sel +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "CentralSystemDemoConfig.h" +#include "Certificate.h" +#include "CertificateRequest.h" +#include "DefaultCentralSystemEventsHandler.h" +#include "ICentralSystem.h" +#include "PrivateKey.h" + +#include +#include +#include +#include + +using namespace ocpp::centralsystem; +using namespace ocpp::types; +using namespace ocpp::x509; + +/** @brief Create the certificates for the ISO15118 communications */ +static void createIso15118Certificates(std::filesystem::path iso_v2g_root_ca, + std::filesystem::path iso_v2g_root_ca_key, + std::filesystem::path iso_mo_root_ca, + std::filesystem::path iso_mo_root_ca_key); + +/** @brief Entry point */ +int main(int argc, char* argv[]) +{ + // Default parameters + std::string working_dir = "."; + bool reset_all = false; + + // Check parameters + if (argc > 1) + { + const char* param = nullptr; + bool bad_param = false; + argv++; + while ((argc != 1) && !bad_param) + { + if (strcmp(*argv, "-h") == 0) + { + bad_param = true; + } + else if ((strcmp(*argv, "-w") == 0) && (argc > 1)) + { + argv++; + argc--; + working_dir = *argv; + } + else if (strcmp(*argv, "-r") == 0) + { + reset_all = true; + } + else + { + param = *argv; + bad_param = true; + } + + // Next param + argc--; + argv++; + } + if (bad_param) + { + if (param) + { + std::cout << "Invalid parameter : " << param << std::endl; + } + std::cout << "Usage : iso15118_centralsystem [-w working_dir] [-r]" << std::endl; + std::cout + << " -w : Working directory where to store the configuration file and the local database (Default = current directory)" + << std::endl; + std::cout << " -r : Reset all the OCPP persistent data" << std::endl; + return 1; + } + } + + std::cout << "Starting central system with :" << std::endl; + std::cout << " - working_dir = " << working_dir << std::endl; + + // Generate certificates for ISO15118 + std::filesystem::path iso_v2g_root_ca(working_dir); + iso_v2g_root_ca /= "cs_iso_v2g_root_ca.pem"; + std::filesystem::path iso_v2g_root_ca_key(working_dir); + iso_v2g_root_ca_key /= "cs_iso_v2g_root_ca.key"; + std::filesystem::path iso_mo_root_ca(working_dir); + iso_mo_root_ca /= "cs_iso_mo_root_ca.pem"; + std::filesystem::path iso_mo_root_ca_key(working_dir); + iso_mo_root_ca_key /= "cs_iso_mo_root_ca.key"; + if (reset_all) + { + std::filesystem::remove(iso_v2g_root_ca); + std::filesystem::remove(iso_v2g_root_ca_key); + std::filesystem::remove(iso_mo_root_ca); + std::filesystem::remove(iso_mo_root_ca_key); + } + if (!std::filesystem::exists(iso_v2g_root_ca)) + { + createIso15118Certificates(iso_v2g_root_ca, iso_v2g_root_ca_key, iso_mo_root_ca, iso_mo_root_ca_key); + } + + // Instanciate certificates + Certificate v2g_root_cert(iso_v2g_root_ca); + Certificate mo_root_cert(iso_mo_root_ca); + + // Configuration + std::filesystem::path path(working_dir); + path /= "iso15118_centralsystem.ini"; + CentralSystemDemoConfig config(path); + + // Event handler + DefaultCentralSystemEventsHandler event_handler(iso_v2g_root_ca, iso_mo_root_ca, true); + + // Instanciate central system + std::unique_ptr central_system = ICentralSystem::create(config.stackConfig(), event_handler); + if (reset_all) + { + central_system->resetData(); + } + central_system->start(); + + // From now on the stack is alive :) + + // App loop + while (true) + { + // For each pending charge point + for (auto& iter_chargepoint : event_handler.pendingChargePoints()) + { + auto chargepoint = iter_chargepoint.second; + auto iter_accepted = event_handler.acceptedChargePoints().find(chargepoint->identifier()); + if (iter_accepted == event_handler.acceptedChargePoints().end()) + { + std::cout << "---------------------------------------------" << std::endl; + std::cout << "Pending Charge point : " << chargepoint->identifier() << std::endl; + std::cout << "---------------------------------------------" << std::endl; + + // Check if the chargepoint supports ISO15118 + std::vector config_keys; + std::vector unknown_keys; + if (!chargepoint->getConfiguration(std::vector{"Iso15118PnCEnabled"}, config_keys, unknown_keys)) + { + std::cout << "Unable to get the status of ISO15118 implementation on charge point side" << std::endl; + } + if (!config_keys.empty() && (config_keys[0].value.value() == "true")) + { + std::cout << "Charge point supports ISO15118 PnC extensions" << std::endl; + + // List installed certificates + std::vector certificates; + if (chargepoint->iso15118GetInstalledCertificateIds( + std::vector{GetCertificateIdUseEnumType::V2GRootCertificate, + GetCertificateIdUseEnumType::MORootCertificate}, + certificates)) + { + // Delete installed certificates + for (const auto& certificate : certificates) + { + DeleteCertificateStatusEnumType ret = chargepoint->iso15118DeleteCertificate(certificate.certificateHashData); + std::cout << "Delete certificate [" << GetCertificateIdUseEnumTypeHelper.toString(certificate.certificateType) + << "] => " << DeleteCertificateStatusEnumTypeHelper.toString(ret) << std::endl; + } + } + else + { + std::cout << "Unable to list installed certificates" << std::endl; + } + + // Install V2G root certificate + InstallCertificateStatusEnumType install_ret = + chargepoint->iso15118InstallCertificate(InstallCertificateUseEnumType::V2GRootCertificate, v2g_root_cert); + std::cout << "Install V2G root CA => " << InstallCertificateStatusEnumTypeHelper.toString(install_ret) << std::endl; + + // Install MO root certificate + install_ret = chargepoint->iso15118InstallCertificate(InstallCertificateUseEnumType::MORootCertificate, mo_root_cert); + std::cout << "Install MO root CA => " << InstallCertificateStatusEnumTypeHelper.toString(install_ret) << std::endl; + + // Trigger a request to sign a new V2G certificate + if (chargepoint->iso15118TriggerSignCertificate()) + { + // Waits for the request from the charge point + unsigned int retries = 0; + auto my_chargepoint = event_handler.chargePoints().find(chargepoint->identifier())->second; + while (retries < 10u) + { + if (my_chargepoint->generatedCertificate().empty()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + retries++; + } + else + { + // Send generated certificate + Certificate certificate_chain(std::filesystem::path(my_chargepoint->generatedCertificate())); + if (!chargepoint->iso15118CertificateSigned(certificate_chain)) + { + std::cout << "Unable to send generated certificate : " << my_chargepoint->generatedCertificate() + << std::endl; + } + break; + } + } + if (my_chargepoint->generatedCertificate().empty()) + { + std::cout << "Sign certificate request not received" << std::endl; + } + } + else + { + std::cout << "Unable to trigger a sign certificate request" << std::endl; + } + } + else + { + std::cout << "Charge point doesn't support ISO15118 PnC extensions" << std::endl; + } + + // Accept charge point + event_handler.acceptedChargePoints()[chargepoint->identifier()] = chargepoint; + + // Trigger a boot notification to force it to update its registration status + chargepoint->triggerMessage(MessageTrigger::BootNotification, Optional()); + } + } + + std::this_thread::sleep_for(std::chrono::seconds(1)); + } + + return 0; +} + +/** @brief Create the certificates for the ISO15118 communications */ +static void createIso15118Certificates(std::filesystem::path iso_v2g_root_ca, + std::filesystem::path iso_v2g_root_ca_key, + std::filesystem::path iso_mo_root_ca, + std::filesystem::path iso_mo_root_ca_key) +{ + // V2G root CA + PrivateKey v2g_key(PrivateKey::Type::EC, PrivateKey::Curve::PRIME256_V1, ""); + v2g_key.privateToFile(iso_v2g_root_ca_key); + + CertificateRequest::Subject v2g_subject; + v2g_subject.country = "FR"; + v2g_subject.state = "Savoie"; + v2g_subject.location = "Chambery"; + v2g_subject.organization = "Open OCPP"; + v2g_subject.organization_unit = "Examples"; + v2g_subject.common_name = "V2G root CA"; + v2g_subject.email_address = "ca.examples@open-ocpp.org"; + + CertificateRequest::Extensions ca_extensions; + ca_extensions.basic_constraints.present = true; + ca_extensions.basic_constraints.is_ca = true; + ca_extensions.basic_constraints.path_length = 1u; + ca_extensions.subject_alternate_names.push_back("localhost"); + ca_extensions.subject_alternate_names.push_back("127.0.0.1"); + + CertificateRequest v2g_req(v2g_subject, ca_extensions, v2g_key); + + Certificate v2g_cert(v2g_req, v2g_key, Sha2::Type::SHA256, 7300u); + v2g_cert.toFile(iso_v2g_root_ca); + + // MO root CA + PrivateKey mo_key(PrivateKey::Type::EC, PrivateKey::Curve::PRIME256_V1, ""); + mo_key.privateToFile(iso_mo_root_ca_key); + + CertificateRequest::Subject mo_subject; + mo_subject.country = "FR"; + mo_subject.state = "Savoie"; + mo_subject.location = "Chambery"; + mo_subject.organization = "Open OCPP"; + mo_subject.organization_unit = "Examples"; + mo_subject.common_name = "MO root CA"; + mo_subject.email_address = "ca.examples@open-ocpp.org"; + + CertificateRequest mo_req(mo_subject, ca_extensions, mo_key); + + Certificate mo_cert(mo_req, mo_key, Sha2::Type::SHA256, 7300u); + mo_cert.toFile(iso_mo_root_ca); +} diff --git a/examples/iso15118_chargepoint/CMakeLists.txt b/examples/iso15118_chargepoint/CMakeLists.txt new file mode 100644 index 00000000..79f8da57 --- /dev/null +++ b/examples/iso15118_chargepoint/CMakeLists.txt @@ -0,0 +1,23 @@ +###################################################### +# ISO15118 charge point example project # +###################################################### + +# Executable target +add_executable(iso15118_chargepoint + main.cpp +) + +# Additionnal libraries path +target_link_directories(iso15118_chargepoint PRIVATE ${BIN_DIR}) + +# Dependencies +target_link_libraries(iso15118_chargepoint + examples_common +) + + +# Copy to binary directory +ADD_CUSTOM_COMMAND(TARGET iso15118_chargepoint + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/config/iso15118_chargepoint.ini ${BIN_DIR}/ +) diff --git a/examples/iso15118_chargepoint/README.md b/examples/iso15118_chargepoint/README.md new file mode 100644 index 00000000..3789d5b0 --- /dev/null +++ b/examples/iso15118_chargepoint/README.md @@ -0,0 +1,35 @@ +# ISO15118 Charge Point example + +## Disclaimer + +The certificate management implemented in this example does not follow the state of the arts recommendations : + +* Secure storage of private keys / authentication credentials +* No password on some private keys +* ... and surely some more + +The choosen implementation has only be made to have a simple and comprehensive example of how to use **Open OCPP** features. + +## Description + +This example simulates a charge point which start an ISO15118 charging session a token id and a contract certificate. + +The charge point loops on its connectors. For each connector it simulates the following operations : + +* Id tag authorization +* Status notifications (Preparing, Charging, Finishing, Available) +* Transaction start/stop + +Each charging sessions lasts 30s and there is a 10s break between 2 charging sessions. + +**Note :** Some of the ISO15118 messages are not containing real data since it would need an actual ISO15118-2 stack and an EV simulator. They +are just provided to demonstrate how to use them. + +## Command line + +iso15118_chargepoint [-t id_tag] [-w working_dir] [-r] [-d] + +* -t : Id tag to use (Default = AABBCCDDEEFF) +* -w : Working directory where to store the configuration file (Default = current directory) +* -r : Reset all the OCPP persistent data +* -d : Reset all the connector persistent data diff --git a/examples/iso15118_chargepoint/config/iso15118_chargepoint.ini b/examples/iso15118_chargepoint/config/iso15118_chargepoint.ini new file mode 100644 index 00000000..b9410621 --- /dev/null +++ b/examples/iso15118_chargepoint/config/iso15118_chargepoint.ini @@ -0,0 +1,99 @@ +[ChargePoint] +DatabasePath=./iso15118_chargepoint.db +JsonSchemasPath=../../schemas/ +ConnexionUrl=wss://127.0.0.1:8080/openocpp/ +Tlsv12CipherList=ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-WITH-AES-256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:TLS-PSK-WITH-AES-256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-WITH-AES-128-GCM-SHA256:DHE-RSA-AES128-GCM-SHA256:TLS-PSK-WITH-AES-128-GCM-SHA256 +Tlsv13CipherList=TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 +TlsServerCertificateCa=../../examples/certificates/open-ocpp_ca.crt +TlsClientCertificate=../../examples/certificates/open-ocpp_charge-point.crt +TlsClientCertificatePrivateKey=../../examples/certificates/open-ocpp_charge-point.key +TlsClientCertificatePrivateKeyPassphrase= +TlsAllowSelfSignedCertificates=false +TlsAllowExpiredCertificates=false +TlsAcceptNonTrustedCertificates=false +TlsSkipServerNameCheck=false +ChargePointIdentifier=ChargePointTest +ConnectionTimeout=2000 +RetryInterval=1000 +CallRequestTimeout=2000 +ChargeBoxSerialNumber=S/N9876543210 +ChargePointModel=Open OCPP CP +ChargePointSerialNumber=S/N0123456789 +ChargePointVendor=Open OCPP +FirmwareVersion=0.1 +Iccid= +Imsi= +MeterSerialNumber= +MeterType= +OperatingVoltage=230 +AuthentCacheMaxEntriesCount=1000 +LogMaxEntriesCount=2000 +InternalCertificateManagementEnabled=true +SecurityEventNotificationEnabled=true +SecurityLogMaxEntriesCount=1000 +ClientCertificateRequestHashType=sha256 +ClientCertificateRequestKeyType=ec +ClientCertificateRequestRsaKeyLength=4096 +ClientCertificateRequestEcCurve=prime256v1 +ClientCertificateRequestSubjectCountry=France +ClientCertificateRequestSubjectState=Savoie +ClientCertificateRequestSubjectLocation=Chambery +ClientCertificateRequestSubjectOrganizationUnit=Examples +ClientCertificateRequestSubjectEmail=charge.point@open-ocpp.org + +[Ocpp] +AllowOfflineTxForUnknownId=true +AuthorizationCacheEnabled=true +AuthorizeRemoteTxRequests=true +BlinkRepeat=10 +ClockAlignedDataInterval=100 +ConnectionTimeOut=3600 +ConnectorPhaseRotation=1.RST,2.RST,3.RST +ConnectorPhaseRotationMaxLength=3 +GetConfigurationMaxKeys=150 +HeartbeatInterval=15 +LightIntensity=50 +LocalAuthorizeOffline=true +LocalPreAuthorize=true +MaxEnergyOnInvalidId=0 +MeterValuesAlignedData=Current.Import,Energy.Active.Import.Register,Power.Active.Import +MeterValuesAlignedDataMaxLength=10 +MeterValuesSampledData=Current.Import,Energy.Active.Import.Register,Power.Active.Import +MeterValuesSampledDataMaxLength=10 +MeterValueSampleInterval=5 +MinimumStatusDuration=2 +NumberOfConnectors=2 +ResetRetries=0 +StopTransactionOnEVSideDisconnect=true +StopTransactionOnInvalidId=true +StopTxnAlignedData= +StopTxnAlignedDataMaxLength=10 +StopTxnSampledData= +StopTxnSampledDataMaxLength=10 +SupportedFeatureProfiles=Core,FirmwareManagement,LocalAuthListManagement,Reservation,SmartCharging,RemoteTrigger +SupportedFeatureProfilesMaxLength=6 +TransactionMessageAttempts=3 +TransactionMessageRetryInterval=5 +UnlockConnectorOnEVSideDisconnect=true +WebSocketPingInterval=10 +LocalAuthListEnabled=true +LocalAuthListMaxLength=5000 +SendLocalListMaxLength=100 +ReserveConnectorZeroSupported=true +ChargeProfileMaxStackLevel=10 +ChargingScheduleAllowedChargingRateUnit=Current,Power +ChargingScheduleMaxPeriods=2 +ConnectorSwitch3to1PhaseSupported=false +MaxChargingProfilesInstalled=20 +AdditionalRootCertificateCheck=false +AuthorizationKey=abcdef +CertificateSignedMaxChainSize=10000 +CertificateStoreMaxLength=50 +CpoName=Open OCPP +SecurityProfile=0 +SupportedFileTransferProtocols=FTP,FTPS,HTTP,HTTPS +CentralContractValidationAllowed=true +CertSigningWaitMinimum=2 +CertSigningRepeatTimes=1 +ContractValidationOffline=true +Iso15118PnCEnabled=true diff --git a/examples/iso15118_chargepoint/main.cpp b/examples/iso15118_chargepoint/main.cpp new file mode 100644 index 00000000..e5de7fe7 --- /dev/null +++ b/examples/iso15118_chargepoint/main.cpp @@ -0,0 +1,243 @@ +/* +MIT License + +Copyright (c) 2020 Cedric Jimenez + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "ChargePointDemoConfig.h" +#include "DefaultChargePointEventsHandler.h" +#include "IChargePoint.h" + +#include +#include +#include +#include + +using namespace ocpp::chargepoint; +using namespace ocpp::types; +using namespace ocpp::x509; + +/** @brief Entry point */ +int main(int argc, char* argv[]) +{ + // Default parameters + std::string token_id = "AABBCCDDEEFF"; + std::string working_dir = "."; + bool reset_all = false; + bool reset_connectors = false; + + // Check parameters + if (argc > 1) + { + const char* param = nullptr; + bool bad_param = false; + argv++; + while ((argc != 1) && !bad_param) + { + if (strcmp(*argv, "-h") == 0) + { + bad_param = true; + } + else if ((strcmp(*argv, "-t") == 0) && (argc > 1)) + { + argv++; + argc--; + token_id = *argv; + } + else if ((strcmp(*argv, "-w") == 0) && (argc > 1)) + { + argv++; + argc--; + working_dir = *argv; + } + else if (strcmp(*argv, "-r") == 0) + { + reset_all = true; + } + else if (strcmp(*argv, "-d") == 0) + { + reset_connectors = true; + } + else + { + param = *argv; + bad_param = true; + } + + // Next param + argc--; + argv++; + } + if (bad_param) + { + if (param) + { + std::cout << "Invalid parameter : " << param << std::endl; + } + std::cout << "Usage : iso15118_chargepoint [-t token_id] [-w working_dir] [-r] [-d]" << std::endl; + std::cout << " -t : Token id to use (Default = AABBCCDDEEFF)" << std::endl; + std::cout << " -w : Working directory where to store the configuration file (Default = current directory)" << std::endl; + std::cout << " -r : Reset all the OCPP persistent data" << std::endl; + std::cout << " -d : Reset all the connector persistent data" << std::endl; + return 1; + } + } + + std::cout << "Starting charge point with :" << std::endl; + std::cout << " - token_id = " << token_id << std::endl; + std::cout << " - working_dir = " << working_dir << std::endl; + + // Configuration + std::filesystem::path path(working_dir); + path /= "iso15118_chargepoint.ini"; + ChargePointDemoConfig config(path); + + std::filesystem::path ev_cert_path(working_dir); + ev_cert_path /= "iso_cp_ev_cert.pem"; + + // Event handler + DefaultChargePointEventsHandler event_handler(config, working_dir); + + // Instanciate charge point + std::unique_ptr charge_point = IChargePoint::create(config.stackConfig(), config.ocppConfig(), event_handler); + if (reset_connectors) + { + charge_point->resetConnectorData(); + } + if (reset_all) + { + charge_point->resetData(); + } + event_handler.setChargePoint(*charge_point.get()); + charge_point->start(); + + // From now on the stack is alive :) + + // App loop + while (true) + { + // Wait to be accepted by Central System + while (charge_point->getRegistrationStatus() != RegistrationStatus::Accepted) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100u)); + } + + // Ask for a new ISO15118 EV certificate to use for PnC + std::string exi_request = "An EXI encoded request coming from the ISO15118-2 stack"; + std::string exi_response; + if (!charge_point->iso15118GetEVCertificate("1.0", CertificateActionEnumType::Install, exi_request, exi_response)) + { + std::cout << "Unable to install new EV certificate" << std::endl; + } + else + { + // For the purpose of this example, the EXI response contains directly the EV certificate in PEM format + // In a real system, the certificate is embedded in an EXI message + Certificate ev_cert(exi_response); + if (ev_cert.isValid()) + { + // Save certificate + ev_cert.toFile(ev_cert_path); + } + else + { + std::cout << "Invalid EV certificate" << std::endl; + } + } + + // Get the status of a certificate + OcspRequestDataType ocsp_request; + ocsp_request.hashAlgorithm = HashAlgorithmEnumType::SHA384; + ocsp_request.issuerKeyHash.assign("AABBCCDDEEFF"); + ocsp_request.issuerNameHash.assign("0102030405"); + ocsp_request.responderURL.assign("https://open-ocpp.org"); + ocsp_request.serialNumber.assign("S/N12345678"); + std::string ocsp_result; + if (charge_point->iso15118GetCertificateStatus(ocsp_request, ocsp_result)) + { + std::cout << "Unable to retrieve the certificate status" << std::endl; + } + + // Test loop + while (true) + { + // For each connector + for (unsigned int connector_id = 1; connector_id <= config.ocppConfig().numberOfConnectors(); connector_id++) + { + // Ask for authorization on a token and a certificate + Certificate ev_certificate(ev_cert_path); + std::vector cert_hash_data; + Optional cert_status; + OcspRequestDataType ocsp_request; + ocsp_request.hashAlgorithm = HashAlgorithmEnumType::SHA384; + ocsp_request.issuerKeyHash.assign("AABBCCDDEEFF"); + ocsp_request.issuerNameHash.assign("0102030405"); + ocsp_request.responderURL.assign("https://open-ocpp.org"); + ocsp_request.serialNumber.assign("S/N12345678"); + cert_hash_data.emplace_back(ocsp_request); + AuthorizationStatus status = charge_point->iso15118Authorize(ev_certificate, token_id, cert_hash_data, cert_status); + if (status == AuthorizationStatus::Accepted) + { + std::cout << "Token id and certificate authorized" << std::endl; + + // Preparing state + charge_point->statusNotification(connector_id, ChargePointStatus::Preparing); + std::this_thread::sleep_for(std::chrono::seconds(1u)); + + // Try to start charging session + status = charge_point->startTransaction(connector_id, token_id); + if (status == AuthorizationStatus::Accepted) + { + std::cout << "Transaction authorized, start charging" << std::endl; + + // Charging state + charge_point->statusNotification(connector_id, ChargePointStatus::Charging); + std::this_thread::sleep_for(std::chrono::seconds(30u)); + + // End transaction + charge_point->stopTransaction(connector_id, token_id, Reason::Local); + + // Finishing state + charge_point->statusNotification(connector_id, ChargePointStatus::Finishing); + std::this_thread::sleep_for(std::chrono::seconds(1u)); + } + else + { + std::cout << "Transaction not authorized by Central System : " << AuthorizationStatusHelper.toString(status) + << std::endl; + } + + // Available state + charge_point->statusNotification(connector_id, ChargePointStatus::Available); + } + else + { + std::cout << "Id tag not authorized by Central System : " << AuthorizationStatusHelper.toString(status) << std::endl; + } + + // Wait before next charging session + std::this_thread::sleep_for(std::chrono::seconds(10u)); + } + } + } + + return 0; +} diff --git a/examples/quick_start_centralsystem/config/quick_start_centralsystem.ini b/examples/quick_start_centralsystem/config/quick_start_centralsystem.ini index c263efd6..4de064ea 100644 --- a/examples/quick_start_centralsystem/config/quick_start_centralsystem.ini +++ b/examples/quick_start_centralsystem/config/quick_start_centralsystem.ini @@ -16,3 +16,4 @@ TlsServerCertificatePrivateKeyPassphrase= TlsServerCertificateCa=../../examples/certificates/open-ocpp_ca.crt TlsClientCertificateAuthent=true LogMaxEntriesCount=2000 +Iso15118PnCEnabled=false diff --git a/examples/quick_start_chargepoint/config/quick_start_chargepoint.ini b/examples/quick_start_chargepoint/config/quick_start_chargepoint.ini index 1f4ca9db..fe0451f6 100644 --- a/examples/quick_start_chargepoint/config/quick_start_chargepoint.ini +++ b/examples/quick_start_chargepoint/config/quick_start_chargepoint.ini @@ -89,6 +89,11 @@ AdditionalRootCertificateCheck=false AuthorizationKey=abcdef CertificateSignedMaxChainSize=10000 CertificateStoreMaxLength=50 -CpoName=SteVe +CpoName=Open OCPP SecurityProfile=0 SupportedFileTransferProtocols=FTP,FTPS,HTTP,HTTPS +CentralContractValidationAllowed=true +CertSigningWaitMinimum=2 +CertSigningRepeatTimes=1 +ContractValidationOffline=true +Iso15118PnCEnabled=false diff --git a/examples/remote_chargepoint/config/remote_chargepoint.ini b/examples/remote_chargepoint/config/remote_chargepoint.ini index 9e8582e9..f717c9bb 100644 --- a/examples/remote_chargepoint/config/remote_chargepoint.ini +++ b/examples/remote_chargepoint/config/remote_chargepoint.ini @@ -89,6 +89,11 @@ AdditionalRootCertificateCheck=false AuthorizationKey=abcdef CertificateSignedMaxChainSize=10000 CertificateStoreMaxLength=50 -CpoName=SteVe +CpoName=Open OCPP SecurityProfile=0 SupportedFileTransferProtocols=FTP,FTPS,HTTP,HTTPS +CentralContractValidationAllowed=true +CertSigningWaitMinimum=2 +CertSigningRepeatTimes=1 +ContractValidationOffline=true +Iso15118PnCEnabled=false diff --git a/examples/security_centralsystem/config/security_centralsystem_p0.ini b/examples/security_centralsystem/config/security_centralsystem_p0.ini index 640d4247..698c4a64 100644 --- a/examples/security_centralsystem/config/security_centralsystem_p0.ini +++ b/examples/security_centralsystem/config/security_centralsystem_p0.ini @@ -16,3 +16,4 @@ TlsServerCertificatePrivateKeyPassphrase= TlsServerCertificateCa= TlsClientCertificateAuthent=false LogMaxEntriesCount=2000 +Iso15118PnCEnabled=false diff --git a/examples/security_centralsystem/config/security_centralsystem_p1.ini b/examples/security_centralsystem/config/security_centralsystem_p1.ini index 6aff97f6..59180f6b 100644 --- a/examples/security_centralsystem/config/security_centralsystem_p1.ini +++ b/examples/security_centralsystem/config/security_centralsystem_p1.ini @@ -16,3 +16,4 @@ TlsServerCertificatePrivateKeyPassphrase= TlsServerCertificateCa= TlsClientCertificateAuthent=false LogMaxEntriesCount=2000 +Iso15118PnCEnabled=false diff --git a/examples/security_centralsystem/config/security_centralsystem_p2.ini b/examples/security_centralsystem/config/security_centralsystem_p2.ini index ace6a0dd..43fa15a3 100644 --- a/examples/security_centralsystem/config/security_centralsystem_p2.ini +++ b/examples/security_centralsystem/config/security_centralsystem_p2.ini @@ -16,3 +16,4 @@ TlsServerCertificatePrivateKeyPassphrase= TlsServerCertificateCa=../../examples/certificates/open-ocpp_ca.crt TlsClientCertificateAuthent=false LogMaxEntriesCount=2000 +Iso15118PnCEnabled=false diff --git a/examples/security_centralsystem/config/security_centralsystem_p3.ini b/examples/security_centralsystem/config/security_centralsystem_p3.ini index 98a32545..56519d8f 100644 --- a/examples/security_centralsystem/config/security_centralsystem_p3.ini +++ b/examples/security_centralsystem/config/security_centralsystem_p3.ini @@ -16,3 +16,4 @@ TlsServerCertificatePrivateKeyPassphrase= TlsServerCertificateCa=../../examples/certificates/open-ocpp_ca.crt TlsClientCertificateAuthent=true LogMaxEntriesCount=2000 +Iso15118PnCEnabled=false diff --git a/examples/security_chargepoint/config/security_chargepoint.ini b/examples/security_chargepoint/config/security_chargepoint.ini index f33ee001..9ec16bf2 100644 --- a/examples/security_chargepoint/config/security_chargepoint.ini +++ b/examples/security_chargepoint/config/security_chargepoint.ini @@ -92,3 +92,8 @@ CertificateStoreMaxLength=50 CpoName=Open OCPP SecurityProfile=0 SupportedFileTransferProtocols=FTP,FTPS,HTTP,HTTPS +CentralContractValidationAllowed=true +CertSigningWaitMinimum=2 +CertSigningRepeatTimes=1 +ContractValidationOffline=true +Iso15118PnCEnabled=false diff --git a/src/centralsystem/chargepoint/ChargePointHandler.cpp b/src/centralsystem/chargepoint/ChargePointHandler.cpp index 10e080e0..1de2b58d 100644 --- a/src/centralsystem/chargepoint/ChargePointHandler.cpp +++ b/src/centralsystem/chargepoint/ChargePointHandler.cpp @@ -20,9 +20,8 @@ along with OpenOCPP. If not, see . #include "ICentralSystemConfig.h" #include "IChargePointRequestHandler.h" #include "IRpc.h" -#include "Logger.h" +#include "Iso15118.h" #include "MessageDispatcher.h" -#include "MessagesConverter.h" using namespace ocpp::messages; using namespace ocpp::types; @@ -57,6 +56,7 @@ ChargePointHandler::ChargePointHandler(const std::string& SIGNED_FIRMWARE_STATUS_NOTIFICATION_ACTION, messages_converter), m_identifier(identifier), m_stack_config(stack_config), + m_messages_converter(messages_converter), m_handler(nullptr) { msg_dispatcher.registerHandler(AUTHORIZE_ACTION, *dynamic_cast*>(this)); @@ -187,26 +187,71 @@ bool ChargePointHandler::handleMessage(const ocpp::messages::DataTransferReq& re bool ret = false; (void)error_message; - LOG_INFO << "[" << m_identifier << "] - Data transfer requested : vendorId = " << request.vendorId.str() - << " - messageId = " << (request.messageId.isSet() ? request.messageId.value().str() : "not set") - << " - data = " << (request.data.isSet() ? request.data.value() : "not set"); - - // Notify request - if (m_handler) + // Check if ISO15518 support is enabled + bool message_handled = false; + if (m_stack_config.iso15118PnCEnabled() && (request.vendorId == ISO15118_VENDOR_ID) && m_handler) { - response.status = m_handler->dataTransfer(request.vendorId, request.messageId.value(), request.data.value(), response.data.value()); - if (response.data.value().empty()) + // Known messages + if (request.messageId.value() == ISO15118_AUTHORIZE_ACTION) + { + // Iso15118Authorize + response.status = handleMessage( + "Iso15118Authorize", request.data.value(), response.data.value()); + } + else if (request.messageId.value() == GET_15118_EV_CERTIFICATE_ACTION) + { + // Get15118EVCertificate + response.status = handleMessage( + "Get15118EVCertificate", request.data.value(), response.data.value()); + } + else if (request.messageId.value() == GET_CERTIFICATE_STATUS_ACTION) + { + // GetCertificateStatus + response.status = handleMessage( + "GetCertificateStatus", request.data.value(), response.data.value()); + } + else if (request.messageId.value() == SIGN_CERTIFICATE_ACTION) { - response.data.clear(); + // SignCertificate + response.status = + handleMessage("SignCertificate", request.data.value(), response.data.value()); + } + else + { + // Unknown message + LOG_ERROR << "[ISO15118] Unknown message : " << request.messageId.value().str(); + response.status = DataTransferStatus::UnknownMessageId; } - LOG_INFO << "[" << m_identifier << "] - Data transfer : status = " << DataTransferStatusHelper.toString(response.status) - << " - data = " << (request.data.isSet() ? request.data.value() : "not set"); - ret = true; + message_handled = true; + ret = true; } - else + + // Notify message if it hasn't been handled already + if (!message_handled) { - error_code = ocpp::rpc::IRpc::RPC_ERROR_INTERNAL; + LOG_INFO << "[" << m_identifier << "] - Data transfer requested : vendorId = " << request.vendorId.str() + << " - messageId = " << (request.messageId.isSet() ? request.messageId.value().str() : "not set") + << " - data = " << (request.data.isSet() ? request.data.value() : "not set"); + + // Notify request + if (m_handler) + { + response.status = + m_handler->dataTransfer(request.vendorId, request.messageId.value(), request.data.value(), response.data.value()); + if (response.data.value().empty()) + { + response.data.clear(); + } + + LOG_INFO << "[" << m_identifier << "] - Data transfer : status = " << DataTransferStatusHelper.toString(response.status) + << " - data = " << (request.data.isSet() ? request.data.value() : "not set"); + ret = true; + } + else + { + error_code = ocpp::rpc::IRpc::RPC_ERROR_INTERNAL; + } } return ret; @@ -600,5 +645,99 @@ bool ChargePointHandler::handleMessage(const ocpp::messages::SignedFirmwareStatu return ret; } +// ISO 15118 PnC extensions + +/** @brief Handle an Iso15118Authorize request */ +void ChargePointHandler::handleMessage(const ocpp::messages::Iso15118AuthorizeReq& request, ocpp::messages::Iso15118AuthorizeConf& response) +{ + LOG_INFO << "[" << m_identifier << "] - [ISO15118] Authorize requested : idToken = " << request.idToken.str() + << " - certificate = " << (request.certificate.isSet() ? std::to_string(request.certificate.value().size()) : "not set"); + + // Load certificate + ocpp::x509::Certificate certificate(request.certificate.value()); + if (!request.certificate.isSet() || certificate.isValid()) + { + // Notify request + response.idTokenInfo = + m_handler->iso15118Authorize(certificate, request.idToken, request.iso15118CertificateHashData, response.certificateStatus); + } + else + { + response.certificateStatus = AuthorizeCertificateStatusEnumType::CertChainError; + response.idTokenInfo.status = AuthorizationStatus::Invalid; + } + + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Authorize status : " << AuthorizationStatusHelper.toString(response.idTokenInfo.status); +} + +/** @brief Handle a Get15118EVCertificate request */ +void ChargePointHandler::handleMessage(const ocpp::messages::Get15118EVCertificateReq& request, + ocpp::messages::Get15118EVCertificateConf& response) +{ + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Get EV certificate requested : action = " << CertificateActionEnumTypeHelper.toString(request.action) + << " - iso15118SchemaVersion = " << request.iso15118SchemaVersion.str(); + + // Notify request + std::string exi_response; + response.status = m_handler->iso15118GetEVCertificate(request.iso15118SchemaVersion, request.action, request.exiRequest, exi_response); + if (response.status == Iso15118EVCertificateStatusEnumType::Accepted) + { + response.exiResponse.assign(exi_response); + } + + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Get EV certificate status : " << Iso15118EVCertificateStatusEnumTypeHelper.toString(response.status); +} + +/** @brief Handle a GetCertificateStatus request */ +void ChargePointHandler::handleMessage(const ocpp::messages::GetCertificateStatusReq& request, + ocpp::messages::GetCertificateStatusConf& response) +{ + LOG_INFO << "[" << m_identifier << "] - [ISO15118] Get certificate status requested : hashAlgorithm = " + << HashAlgorithmEnumTypeHelper.toString(request.ocspRequestData.hashAlgorithm) + << " - issuerKeyHash = " << request.ocspRequestData.issuerKeyHash.str() + << " - issuerNameHash = " << request.ocspRequestData.issuerNameHash.str() + << " - responderURL = " << request.ocspRequestData.responderURL.str() + << " - serialNumber = " << request.ocspRequestData.serialNumber.str(); + + // Notify request + std::string ocsp_result; + response.status = m_handler->iso15118GetCertificateStatus(request.ocspRequestData, ocsp_result); + if (response.status == GetCertificateStatusEnumType::Accepted) + { + if (!ocsp_result.empty()) + { + response.ocspResult.value().assign(ocsp_result); + } + } + + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Get certificate status : " << GetCertificateStatusEnumTypeHelper.toString(response.status); +} + +/** @brief Handle a SignCertificate request */ +void ChargePointHandler::handleMessage(const ocpp::messages::SignCertificateReq& request, ocpp::messages::SignCertificateConf& response) +{ + LOG_INFO << "[" << m_identifier << "] - [ISO15118] Sign certificate requested : csr size = " << request.csr.size(); + + // Prepare response + response.status = GenericStatusEnumType::Rejected; + + // Load certificate request + ocpp::x509::CertificateRequest certificate_request(request.csr); + if (certificate_request.isValid()) + { + // Notify request + if (m_handler->iso15118SignCertificate(certificate_request)) + { + response.status = GenericStatusEnumType::Accepted; + } + } + + LOG_INFO << "[" << m_identifier << "] - [ISO15118] Sign certificate : " << GenericStatusEnumTypeHelper.toString(response.status); +} + } // namespace centralsystem } // namespace ocpp diff --git a/src/centralsystem/chargepoint/ChargePointHandler.h b/src/centralsystem/chargepoint/ChargePointHandler.h index f93be5d1..f03faf3c 100644 --- a/src/centralsystem/chargepoint/ChargePointHandler.h +++ b/src/centralsystem/chargepoint/ChargePointHandler.h @@ -25,8 +25,13 @@ along with OpenOCPP. If not, see . #include "DiagnosticsStatusNotification.h" #include "FirmwareStatusNotification.h" #include "GenericMessageHandler.h" +#include "Get15118EVCertificate.h" +#include "GetCertificateStatus.h" #include "Heartbeat.h" +#include "Iso15118Authorize.h" #include "LogStatusNotification.h" +#include "Logger.h" +#include "MessagesConverter.h" #include "MeterValues.h" #include "SecurityEventNotification.h" #include "SignCertificate.h" @@ -241,8 +246,84 @@ class ChargePointHandler const std::string m_identifier; /** @brief Stack configuration */ const ocpp::config::ICentralSystemConfig& m_stack_config; + /** @brief Messages converters */ + const ocpp::messages::GenericMessagesConverter& m_messages_converter; /** @brief Request handler */ IChargePointRequestHandler* m_handler; + + // ISO 15118 PnC extensions + + /** + * @brief Generic ISO15118 request handler + * @param type_id Type of message + * @param request_data Data associated to the request + * @param response_data Data associated with the response + * @return Response status (see DataTransferStatus enum) + */ + template + ocpp::types::DataTransferStatus handleMessage(const std::string& type_id, const std::string& request_data, std::string& response_data) + { + ocpp::types::DataTransferStatus status = ocpp::types::DataTransferStatus::Rejected; + ocpp::messages::IMessageConverter* req_converter = m_messages_converter.getRequestConverter(type_id); + ocpp::messages::IMessageConverter* resp_converter = m_messages_converter.getResponseConverter(type_id); + try + { + // Parse JSON + rapidjson::Document request; + request.Parse(request_data.c_str()); + if (!request.HasParseError()) + { + // Convert request from JSON + RequestType req; + std::string error_code; + std::string error_message; + if (req_converter->fromJson(request, req, error_code, error_message)) + { + // Handle message + ResponseType resp; + handleMessage(req, resp); + + // Convert response to JSON + rapidjson::Document response; + response.Parse("{}"); + resp_converter->setAllocator(&response.GetAllocator()); + if (resp_converter->toJson(resp, response)) + { + + // Serialize response + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + response.Accept(writer); + response_data = buffer.GetString(); + + status = ocpp::types::DataTransferStatus::Accepted; + } + } + else + { + LOG_ERROR << "[ISO15118] << " << type_id << " : Invalid JSON received"; + } + } + else + { + LOG_ERROR << "[ISO15118] << " << type_id << " : Invalid JSON received"; + } + } + catch (const std::exception&) + { + LOG_ERROR << "[ISO15118] << " << type_id << " : Invalid JSON received"; + } + return status; + } + + /** @brief Handle an Iso15118Authorize request */ + void handleMessage(const ocpp::messages::Iso15118AuthorizeReq& request, ocpp::messages::Iso15118AuthorizeConf& response); + /** @brief Handle a Get15118EVCertificate request */ + void handleMessage(const ocpp::messages::Get15118EVCertificateReq& request, ocpp::messages::Get15118EVCertificateConf& response); + /** @brief Handle a GetCertificateStatus request */ + void handleMessage(const ocpp::messages::GetCertificateStatusReq& request, ocpp::messages::GetCertificateStatusConf& response); + /** @brief Handle a SignCertificate request */ + void handleMessage(const ocpp::messages::SignCertificateReq& request, ocpp::messages::SignCertificateConf& response); }; } // namespace centralsystem diff --git a/src/centralsystem/chargepoint/ChargePointProxy.cpp b/src/centralsystem/chargepoint/ChargePointProxy.cpp index d6e37101..8d2dea5c 100644 --- a/src/centralsystem/chargepoint/ChargePointProxy.cpp +++ b/src/centralsystem/chargepoint/ChargePointProxy.cpp @@ -34,7 +34,9 @@ along with OpenOCPP. If not, see . #include "GetLog.h" #include "ICentralSystemConfig.h" #include "InstallCertificate.h" -#include "Logger.h" +#include "Iso15118GetInstalledCertificateIds.h" +#include "Iso15118InstallCertificate.h" +#include "Iso15118TriggerMessage.h" #include "RemoteStartTransaction.h" #include "RemoteStopTransaction.h" #include "ReserveNow.h" @@ -66,7 +68,8 @@ ChargePointProxy::ChargePointProxy(ICentralSystem& m_rpc(rpc), m_msg_dispatcher(messages_validator), m_msg_sender(*m_rpc, messages_converter, messages_validator, stack_config.callRequestTimeout()), - m_handler(m_identifier, messages_converter, m_msg_dispatcher, stack_config) + m_handler(m_identifier, messages_converter, m_msg_dispatcher, stack_config), + m_messages_converter(messages_converter) { m_rpc->registerSpy(*this); m_rpc->registerListener(*this); @@ -1038,6 +1041,160 @@ ocpp::types::UpdateFirmwareStatusEnumType ChargePointProxy::signedUpdateFirmware return ret; } +// ISO 15118 PnC extensions + +/** @copydoc bool ICentralSystem::IChargePoint::iso15118CertificateSigned(const ocpp::x509::Certificate&) */ +bool ChargePointProxy::iso15118CertificateSigned(const ocpp::x509::Certificate& certificate_chain) +{ + bool ret = false; + + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Certificate signed : certificate chain size = " << certificate_chain.pemChain().size(); + + // Prepare request + CertificateSignedReq request; + request.certificateChain.assign(certificate_chain.pem()); + + // Send request + CertificateSignedConf response; + if (send("CertificateSigned", CERTIFICATE_SIGNED_ACTION, request, response)) + { + // Extract response + ret = (response.status == CertificateSignedStatusEnumType::Accepted); + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Certificate signed : " << CertificateSignedStatusEnumTypeHelper.toString(response.status); + } + else + { + LOG_ERROR << "[" << m_identifier << "] - [ISO15118] Call failed"; + } + + return ret; +} + +/** @copydoc ocpp::types::DeleteCertificateStatusEnumType ICentralSystem::IChargePoint::iso15118DeleteCertificate( + const ocpp::types::CertificateHashDataType&) */ +ocpp::types::DeleteCertificateStatusEnumType ChargePointProxy::iso15118DeleteCertificate( + const ocpp::types::CertificateHashDataType& certificate) +{ + DeleteCertificateStatusEnumType ret = DeleteCertificateStatusEnumType::Failed; + + LOG_INFO << "[" << m_identifier << "] - [ISO15118] Delete certificate : serialNumber = " << certificate.serialNumber.str(); + + // Prepare request + DeleteCertificateReq request; + request.certificateHashData = certificate; + + // Send request + DeleteCertificateConf response; + if (send("DeleteCertificate", DELETE_CERTIFICATE_ACTION, request, response)) + { + // Extract response + ret = response.status; + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Delete certificate : " << DeleteCertificateStatusEnumTypeHelper.toString(response.status); + } + else + { + LOG_ERROR << "[" << m_identifier << "] - [ISO15118] Call failed"; + } + + return ret; +} + +/** @copydoc bool ICentralSystem::IChargePoint::iso15118GetInstalledCertificateIds(ocpp::types::GetCertificateIdUseEnumType, + std::vector&) */ +bool ChargePointProxy::iso15118GetInstalledCertificateIds(const std::vector& types, + std::vector& certificates) +{ + bool ret = false; + + LOG_INFO << "[" << m_identifier << "] - [ISO15118] Get installed certificate ids : certificateType size = " << types.size(); + + // Prepare request + Iso15118GetInstalledCertificateIdsReq request; + request.certificateType = types; + + // Send request + Iso15118GetInstalledCertificateIdsConf response; + if (send("Iso15118GetInstalledCertificateIds", ISO15118_GET_INSTALLED_CERTIFICATE_IDS_ACTION, request, response)) + { + // Extract response + LOG_INFO << "[" << m_identifier << "] - [ISO15118] Get installed certificate ids : status = " + << GetInstalledCertificateStatusEnumTypeHelper.toString(response.status) + << " - count = " << response.certificateHashDataChain.size(); + certificates = response.certificateHashDataChain; + ret = true; + } + else + { + LOG_ERROR << "[" << m_identifier << "] - [ISO15118] Call failed"; + } + + return ret; +} + +/** @copydoc ocpp::types::InstallCertificateStatusEnumType ICentralSystem::IChargePoint::iso15118CertificateSigned( + ocpp::types::InstallCertificateUseEnumType, + const ocpp::x509::Certificate&) */ +ocpp::types::InstallCertificateStatusEnumType ChargePointProxy::iso15118InstallCertificate(ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate& certificate) +{ + InstallCertificateStatusEnumType ret = InstallCertificateStatusEnumType::Rejected; + + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Install certificate : certificateType = " << InstallCertificateUseEnumTypeHelper.toString(type) + << " - certificate subject = " << certificate.subjectString(); + + // Prepare request + Iso15118InstallCertificateReq request; + request.certificateType = type; + request.certificate.assign(certificate.pem()); + + // Send request + Iso15118InstallCertificateConf response; + if (send("Iso15118InstallCertificate", ISO15118_INSTALL_CERTIFICATE_ACTION, request, response)) + { + // Extract response + ret = response.status; + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Install certificate : " << InstallCertificateStatusEnumTypeHelper.toString(response.status); + } + else + { + LOG_ERROR << "[" << m_identifier << "] - [ISO15118] Call failed"; + } + + return ret; +} + +/** @copydoc bool ICentralSystem::IChargePoint::iso15118TriggerSignCertificate() */ +bool ChargePointProxy::iso15118TriggerSignCertificate() +{ + bool ret = false; + + LOG_INFO << "[" << m_identifier << "] - [ISO15118] Trigger sign certificate"; + + // Prepare request + Iso15118TriggerMessageReq request; + + // Send request + Iso15118TriggerMessageConf response; + if (send("Iso15118TriggerMessage", ISO15118_TRIGGER_MESSAGE_ACTION, request, response)) + { + // Extract response + LOG_INFO << "[" << m_identifier + << "] - [ISO15118] Trigger sign certificate : status = " << TriggerMessageStatusEnumTypeHelper.toString(response.status); + ret = (response.status == TriggerMessageStatusEnumType::Accepted); + } + else + { + LOG_ERROR << "[" << m_identifier << "] - [ISO15118] Call failed"; + } + + return ret; +} + // IRpc::IListener interface /** @copydoc void IRpc::IListener::rpcDisconnected() */ diff --git a/src/centralsystem/chargepoint/ChargePointProxy.h b/src/centralsystem/chargepoint/ChargePointProxy.h index 103797f2..6e476525 100644 --- a/src/centralsystem/chargepoint/ChargePointProxy.h +++ b/src/centralsystem/chargepoint/ChargePointProxy.h @@ -22,7 +22,10 @@ along with OpenOCPP. If not, see . #include "ChargePointHandler.h" #include "GenericMessageSender.h" #include "ICentralSystem.h" +#include "Iso15118.h" +#include "Logger.h" #include "MessageDispatcher.h" +#include "MessagesConverter.h" #include "MessagesValidator.h" #include "RpcServer.h" @@ -255,6 +258,30 @@ class ChargePointProxy : public ICentralSystem::IChargePoint, public ocpp::rpc:: const ocpp::x509::Certificate& signing_certificate, const std::string& signature) override; + // ISO 15118 PnC extensions + + /** @copydoc bool ICentralSystem::IChargePoint::iso15118CertificateSigned(const ocpp::x509::Certificate&) */ + bool iso15118CertificateSigned(const ocpp::x509::Certificate& certificate_chain) override; + + /** @copydoc ocpp::types::DeleteCertificateStatusEnumType ICentralSystem::IChargePoint::iso15118DeleteCertificate( + const ocpp::types::CertificateHashDataType&) */ + ocpp::types::DeleteCertificateStatusEnumType iso15118DeleteCertificate( + const ocpp::types::CertificateHashDataType& certificate) override; + + /** @copydoc bool ICentralSystem::IChargePoint::iso15118GetInstalledCertificateIds(const std::vector&, + std::vector&) */ + bool iso15118GetInstalledCertificateIds(const std::vector& types, + std::vector& certificates) override; + + /** @copydoc ocpp::types::InstallCertificateStatusEnumType ICentralSystem::IChargePoint::iso15118CertificateSigned( + ocpp::types::InstallCertificateUseEnumType, + const ocpp::x509::Certificate&) */ + ocpp::types::InstallCertificateStatusEnumType iso15118InstallCertificate(ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate& certificate) override; + + /** @copydoc bool ICentralSystem::IChargePoint::iso15118TriggerSignCertificate() */ + bool iso15118TriggerSignCertificate() override; + // IRpc::IListener interface /** @copydoc void IRpc::IListener::rpcDisconnected() */ @@ -295,8 +322,79 @@ class ChargePointProxy : public ICentralSystem::IChargePoint, public ocpp::rpc:: ocpp::messages::GenericMessageSender m_msg_sender; /** @brief Request handler */ ChargePointHandler m_handler; + /** @brief Messages converters */ + const ocpp::messages::MessagesConverter& m_messages_converter; /** @brief User request handler */ IChargePointRequestHandler* m_user_handler; + + /** + * @brief Generic ISO15118 request sender + * @param type_id Type of message + * @param action Action correspondin to the message + * @param request Request to send + * @param response Received response + * @return true if the message has been sent and a response has been received, false otherwise + */ + template + bool send(const std::string& type_id, const std::string& action, const RequestType& request, ResponseType& response) + { + bool ret = false; + + // Get converters + ocpp::messages::IMessageConverter* req_converter = m_messages_converter.getRequestConverter(type_id); + ocpp::messages::IMessageConverter* resp_converter = m_messages_converter.getResponseConverter(type_id); + + // Prepare request + ocpp::messages::DataTransferReq req; + req.vendorId.assign(ocpp::messages::ISO15118_VENDOR_ID); + req.messageId.value().assign(action); + + // Convert request to JSON + rapidjson::Document json_req; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + json_req.Parse("{}"); + req_converter->setAllocator(&json_req.GetAllocator()); + req_converter->toJson(request, json_req); + json_req.Accept(writer); + req.data.value() = buffer.GetString(); + + // Send request + ocpp::messages::DataTransferConf resp; + if (m_msg_sender.call(ocpp::messages::DATA_TRANSFER_ACTION, req, resp) == ocpp::messages::CallResult::Ok) + { + if (resp.status == ocpp::types::DataTransferStatus::Accepted) + { + try + { + // Parse JSON + rapidjson::Document json_resp; + json_resp.Parse(resp.data.value().c_str()); + if (!json_resp.HasParseError()) + { + // Convert response from JSON + std::string error_code; + std::string error_message; + ret = resp_converter->fromJson(json_resp, response, error_code, error_message); + } + else + { + LOG_ERROR << "[ISO15118] << " << action << " : Invalid JSON received"; + } + } + catch (const std::exception&) + { + LOG_ERROR << "[ISO15118] << " << action << " : Invalid JSON received"; + } + } + else + { + LOG_ERROR << "[ISO15118] Data transfer error : " << ocpp::types::DataTransferStatusHelper.toString(resp.status); + } + } + + return ret; + } }; } // namespace centralsystem diff --git a/src/centralsystem/interface/ICentralSystem.h b/src/centralsystem/interface/ICentralSystem.h index 6d98dc50..e2dd71c8 100644 --- a/src/centralsystem/interface/ICentralSystem.h +++ b/src/centralsystem/interface/ICentralSystem.h @@ -21,6 +21,7 @@ along with OpenOCPP. If not, see . #include "AuthorizationData.h" #include "Certificate.h" +#include "CertificateHashDataChainType.h" #include "CertificateHashDataType.h" #include "ChargingProfile.h" #include "ICentralSystemConfig.h" @@ -432,6 +433,47 @@ class ICentralSystem const ocpp::types::Optional& install_date, const ocpp::x509::Certificate& signing_certificate, const std::string& signature) = 0; + + // ISO 15118 PnC extensions + + /** + * @brief Send a generated certificate chain after an ISO15118 SignCertificate request from the charge point + * @param certificate_chain Generated certificate chain + * @return true if the certificate chain has been accepted by the charge point, false otherwise + */ + virtual bool iso15118CertificateSigned(const ocpp::x509::Certificate& certificate_chain) = 0; + + /** + * @brief Delete an installed ISO15118 CA certificate + * @param certificate Certificate information + * @return Operation status (see DeleteCertificateStatusEnumType documentation) + */ + virtual ocpp::types::DeleteCertificateStatusEnumType iso15118DeleteCertificate( + const ocpp::types::CertificateHashDataType& certificate) = 0; + + /** + * @brief Get the list of installed ISO15118 CA certificates + * @param types Type of CA certificates + * @param certificates Certificates information + * @return true is the list has been retrieved, false otherwise + */ + virtual bool iso15118GetInstalledCertificateIds(const std::vector& types, + std::vector& certificates) = 0; + + /** + * @brief Install an ISO15118 CA certificate + * @param type Type of CA certificate + * @param certificate CA certificate to install + * @return Operation status (see InstallCertificateStatusEnumType documentation) + */ + virtual ocpp::types::InstallCertificateStatusEnumType iso15118InstallCertificate(ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate& certificate) = 0; + + /** + * @brief Request the send of an ISO15118 SignCertificate request from the charge point + * @return true if the request has been accepted by the charge point, false otherwise + */ + virtual bool iso15118TriggerSignCertificate() = 0; }; }; diff --git a/src/centralsystem/interface/ICentralSystemConfig.h b/src/centralsystem/interface/ICentralSystemConfig.h index 796fb052..7fce9f2f 100644 --- a/src/centralsystem/interface/ICentralSystemConfig.h +++ b/src/centralsystem/interface/ICentralSystemConfig.h @@ -76,6 +76,12 @@ class ICentralSystemConfig /** @brief Maximum number of entries in the log (0 = no logs in database) */ virtual unsigned int logMaxEntriesCount() const = 0; + + // ISO 15118 PnC extensions + + /** @brief If this variable set to true, then the Central System supports ISO 15118 plug and charge messages via the DataTransfer mechanism as + described in this application note. */ + virtual bool iso15118PnCEnabled() const = 0; }; } // namespace config diff --git a/src/centralsystem/interface/IChargePointRequestHandler.h b/src/centralsystem/interface/IChargePointRequestHandler.h index 12992774..53916acf 100644 --- a/src/centralsystem/interface/IChargePointRequestHandler.h +++ b/src/centralsystem/interface/IChargePointRequestHandler.h @@ -19,10 +19,13 @@ along with OpenOCPP. If not, see . #ifndef OPENOCPP_ICHARGEPOINTREQUESTHANDLER_H #define OPENOCPP_ICHARGEPOINTREQUESTHANDLER_H +#include "Certificate.h" #include "CertificateRequest.h" #include "Enums.h" #include "IdTagInfo.h" +#include "IdTokenInfoType.h" #include "MeterValue.h" +#include "OcspRequestDataType.h" #include @@ -187,6 +190,53 @@ class IChargePointRequestHandler */ virtual void signedFirmwareUpdateStatusNotification(ocpp::types::FirmwareStatusEnumType status, const ocpp::types::Optional& request_id) = 0; + + // ISO 15118 PnC extensions + + /** + * @brief Called to authorize an ISO15118 transaction + * @param certificate The X.509 certificated presented by EV + * @param id_token This contains the identifier that needs to be authorized + * @param cert_hash_data Contains the information needed to verify the EV Contract Certificate via OCSP + * @param cert_status Certificate status information. - if all certificates are + * valid: return 'Accepted'. - if one of the certificates was revoked, + * return 'CertificateRevoked + * @return Authorization status (see AuthorizationStatus type) + */ + virtual ocpp::types::IdTokenInfoType iso15118Authorize( + const ocpp::x509::Certificate& certificate, + const std::string& id_token, + const std::vector& cert_hash_data, + ocpp::types::Optional& cert_status) = 0; + + /** + * @brief Called when the Charge Point wants to get or update an ISO15118 EV certificate + * @param iso15118_schema_version Schema version currently used for the 15118 session between EV and Charge Point + * @param action Defines whether certificate needs to be installed or updated + * @param exi_request Raw CertificateInstallationReq request from EV, Base64 encoded + * @param exi_response Raw CertificateInstallationRes response for the EV, Base64 encoded + * @return Operation status (see Iso15118EVCertificateStatusEnumType enum) + */ + virtual ocpp::types::Iso15118EVCertificateStatusEnumType iso15118GetEVCertificate(const std::string& iso15118_schema_version, + ocpp::types::CertificateActionEnumType action, + const std::string& exi_request, + std::string& exi_response) = 0; + + /** + * @brief Called when the CHarge Point wants to get the validity status of an ISO15118 certificate + * @param ocsp_request Indicates the certificate of which the status is requested + * @param ocsp_result OCSPResponse class as defined in IETF RFC 6960. DER encoded (as defined in IETF RFC 6960), and then base64 encoded + * @return Operation status (see GetCertificateStatusEnumType enum) + */ + virtual ocpp::types::GetCertificateStatusEnumType iso15118GetCertificateStatus(const ocpp::types::OcspRequestDataType& ocsp_request, + std::string& ocsp_result) = 0; + + /** + * @brief Called when a request to sign a new ISO15118 client certificat has been received + * @param certificate_request Certificate request + * @return true if the certificate request can be processed, false otherwise + */ + virtual bool iso15118SignCertificate(const ocpp::x509::CertificateRequest& certificate_request) = 0; }; } // namespace centralsystem diff --git a/src/chargepoint/CMakeLists.txt b/src/chargepoint/CMakeLists.txt index 5d27a4d1..5e6a8f74 100644 --- a/src/chargepoint/CMakeLists.txt +++ b/src/chargepoint/CMakeLists.txt @@ -12,6 +12,7 @@ add_library(chargepoint OBJECT config/ConfigManager.cpp connector/Connectors.cpp datatransfer/DataTransferManager.cpp + iso15118/Iso15118Manager.cpp maintenance/MaintenanceManager.cpp metervalues/MeterValuesManager.cpp requestfifo/RequestFifo.cpp @@ -37,6 +38,7 @@ target_include_directories(chargepoint PRIVATE authent connector datatransfer interface + iso15118 maintenance metervalues requestfifo diff --git a/src/chargepoint/ChargePoint.cpp b/src/chargepoint/ChargePoint.cpp index 15e92e00..32f61786 100644 --- a/src/chargepoint/ChargePoint.cpp +++ b/src/chargepoint/ChargePoint.cpp @@ -22,6 +22,7 @@ along with OpenOCPP. If not, see . #include "DataTransferManager.h" #include "GenericMessageSender.h" #include "InternalConfigKeys.h" +#include "Iso15118Manager.h" #include "Logger.h" #include "MaintenanceManager.h" #include "MessageDispatcher.h" @@ -105,6 +106,7 @@ ChargePoint::ChargePoint(const ocpp::config::IChargePointConfig& stack_ m_smart_charging_manager(), m_maintenance_manager(), m_requests_fifo_manager(), + m_iso15118_manager(), m_uptime_timer(*m_timer_pool.get(), "Uptime timer"), m_uptime(0), m_disconnected_time(0), @@ -247,8 +249,8 @@ bool ChargePoint::start() m_ocpp_config, m_events_handler, m_internal_config, - *m_timer_pool.get(), - *m_worker_pool.get(), + *m_timer_pool, + *m_worker_pool, m_connectors, *m_msg_dispatcher, *m_msg_sender, @@ -256,8 +258,8 @@ bool ChargePoint::start() *m_trigger_manager); m_reservation_manager = std::make_unique(m_ocpp_config, m_events_handler, - *m_timer_pool.get(), - *m_worker_pool.get(), + *m_timer_pool, + *m_worker_pool, m_connectors, m_messages_converter, *m_msg_dispatcher, @@ -266,8 +268,8 @@ bool ChargePoint::start() m_meter_values_manager = std::make_unique(m_ocpp_config, m_database, m_events_handler, - *m_timer_pool.get(), - *m_worker_pool.get(), + *m_timer_pool, + *m_worker_pool, m_connectors, *m_msg_sender, m_requests_fifo, @@ -277,8 +279,8 @@ bool ChargePoint::start() m_smart_charging_manager = std::make_unique(m_stack_config, m_ocpp_config, m_database, - *m_timer_pool.get(), - *m_worker_pool.get(), + *m_timer_pool, + *m_worker_pool, m_connectors, m_messages_converter, *m_msg_dispatcher); @@ -298,7 +300,7 @@ bool ChargePoint::start() m_maintenance_manager = std::make_unique(m_stack_config, m_internal_config, m_events_handler, - *m_worker_pool.get(), + *m_worker_pool, m_messages_converter, *m_msg_dispatcher, *m_msg_sender, @@ -308,13 +310,22 @@ bool ChargePoint::start() m_requests_fifo_manager = std::make_unique(m_ocpp_config, m_events_handler, - *m_timer_pool.get(), - *m_worker_pool.get(), + *m_timer_pool, + *m_worker_pool, m_connectors, *m_msg_sender, m_requests_fifo, *m_status_manager, *m_authent_manager); + m_iso15118_manager = std::make_unique(m_ocpp_config, + m_events_handler, + *m_timer_pool, + *m_worker_pool, + m_messages_converter, + *m_msg_sender, + *m_authent_manager, + *m_data_transfer_manager, + m_security_manager); // Register specific configuration checks m_config_manager->registerConfigChangedListener("AuthorizationKey", *this); @@ -365,6 +376,7 @@ bool ChargePoint::stop() m_smart_charging_manager.reset(); m_maintenance_manager.reset(); m_requests_fifo_manager.reset(); + m_iso15118_manager.reset(); // Stop connection ret = m_rpc_client->stop(); @@ -778,6 +790,118 @@ bool ChargePoint::notifySignedUpdateFirmwareStatus(ocpp::types::FirmwareStatusEn return ret; } +// ISO 15118 PnC extensions + +/** @copydoc ocpp::types::AuthorizationStatus iso15118Authorize(const ocpp::x509::Certificate&, + const std::string&, + const std::vector&, + ocpp::types::Optional&) */ +ocpp::types::AuthorizationStatus ChargePoint::iso15118Authorize( + const ocpp::x509::Certificate& certificate, + const std::string& id_token, + const std::vector& cert_hash_data, + ocpp::types::Optional& cert_status) +{ + AuthorizationStatus ret = AuthorizationStatus::Invalid; + + if (m_status_manager) + { + if (m_status_manager->getRegistrationStatus() != RegistrationStatus::Rejected) + { + ret = m_iso15118_manager->authorize(certificate, id_token, cert_hash_data, cert_status); + } + else + { + LOG_ERROR << "Charge Point has not been accepted by Central System"; + } + } + else + { + LOG_ERROR << "Stack is not started"; + } + + return ret; +} + +/** @copydoc bool IChargePoint::iso15118GetEVCertificate(const std::string&, + ocpp::types::CertificateActionEnumType, + const std::string&, + std::string&) */ +bool ChargePoint::iso15118GetEVCertificate(const std::string& iso15118_schema_version, + ocpp::types::CertificateActionEnumType action, + const std::string& exi_request, + std::string& exi_response) +{ + bool ret = false; + + if (m_status_manager) + { + if (m_status_manager->getRegistrationStatus() != RegistrationStatus::Rejected) + { + ret = (m_iso15118_manager->get15118EVCertificate(iso15118_schema_version, action, exi_request, exi_response) == + Iso15118EVCertificateStatusEnumType::Accepted); + } + else + { + LOG_ERROR << "Charge Point has not been accepted by Central System"; + } + } + else + { + LOG_ERROR << "Stack is not started"; + } + + return ret; +} + +/** @copydoc bool IChargePoint::iso15118GetCertificateStatus(const ocpp::types::OcspRequestDataType&, std::string&) */ +bool ChargePoint::iso15118GetCertificateStatus(const ocpp::types::OcspRequestDataType& ocsp_request, std::string& ocsp_result) +{ + bool ret = false; + + if (m_status_manager) + { + if (m_status_manager->getRegistrationStatus() != RegistrationStatus::Rejected) + { + ret = (m_iso15118_manager->getCertificateStatus(ocsp_request, ocsp_result) == GetCertificateStatusEnumType::Accepted); + } + else + { + LOG_ERROR << "Charge Point has not been accepted by Central System"; + } + } + else + { + LOG_ERROR << "Stack is not started"; + } + + return ret; +} + +/** @copydoc bool IChargePoint::iso15118SignCertificate(const ocpp::x509::CertificateRequest&) */ +bool ChargePoint::iso15118SignCertificate(const ocpp::x509::CertificateRequest& csr) +{ + bool ret = false; + + if (m_status_manager) + { + if (m_status_manager->getRegistrationStatus() != RegistrationStatus::Rejected) + { + ret = m_iso15118_manager->signCertificate(csr); + } + else + { + LOG_ERROR << "Charge Point has not been accepted by Central System"; + } + } + else + { + LOG_ERROR << "Stack is not started"; + } + + return ret; +} + /** @copydoc void RpcClient::IListener::rpcClientConnected() */ void ChargePoint::rpcClientConnected() { diff --git a/src/chargepoint/ChargePoint.h b/src/chargepoint/ChargePoint.h index 80ae506b..679809a1 100644 --- a/src/chargepoint/ChargePoint.h +++ b/src/chargepoint/ChargePoint.h @@ -62,6 +62,7 @@ class SmartChargingManager; class MaintenanceManager; class SecurityManager; class RequestFifoManager; +class Iso15118Manager; /** @brief Charge point implementation */ class ChargePoint : public IChargePoint, @@ -175,6 +176,33 @@ class ChargePoint : public IChargePoint, /** @copydoc bool IChargePoint::notifySignedUpdateFirmwareStatus(ocpp::types::FirmwareStatusEnumType) */ bool notifySignedUpdateFirmwareStatus(ocpp::types::FirmwareStatusEnumType status) override; + // ISO 15118 PnC extensions + + /** @copydoc ocpp::types::AuthorizationStatus iso15118Authorize(const ocpp::x509::Certificate&, + const std::string&, + const std::vector&, + ocpp::types::Optional&) */ + ocpp::types::AuthorizationStatus iso15118Authorize( + const ocpp::x509::Certificate& certificate, + const std::string& id_token, + const std::vector& cert_hash_data, + ocpp::types::Optional& cert_status) override; + + /** @copydoc bool IChargePoint::iso15118GetEVCertificate(const std::string&, + ocpp::types::CertificateActionEnumType, + const std::string&, + std::string&) */ + bool iso15118GetEVCertificate(const std::string& iso15118_schema_version, + ocpp::types::CertificateActionEnumType action, + const std::string& exi_request, + std::string& exi_response) override; + + /** @copydoc bool IChargePoint::iso15118GetCertificateStatus(const ocpp::types::OcspRequestDataType&, std::string&) */ + bool iso15118GetCertificateStatus(const ocpp::types::OcspRequestDataType& ocsp_request, std::string& ocsp_result) override; + + /** @copydoc bool IChargePoint::iso15118SignCertificate(const ocpp::x509::CertificateRequest&) */ + bool iso15118SignCertificate(const ocpp::x509::CertificateRequest& csr) override; + // RpcClient::IListener interface /** @copydoc void RpcClient::IListener::rpcClientConnected() */ @@ -278,6 +306,8 @@ class ChargePoint : public IChargePoint, std::unique_ptr m_maintenance_manager; /** @brief Requests FIFO manager */ std::unique_ptr m_requests_fifo_manager; + /** @brief ISO15118 manager */ + std::unique_ptr m_iso15118_manager; /** @brief Uptime timer */ ocpp::helpers::Timer m_uptime_timer; diff --git a/src/chargepoint/authent/AuthentManager.cpp b/src/chargepoint/authent/AuthentManager.cpp index b5c14c1e..4b23d477 100644 --- a/src/chargepoint/authent/AuthentManager.cpp +++ b/src/chargepoint/authent/AuthentManager.cpp @@ -56,7 +56,7 @@ AuthentManager::~AuthentManager() delete &m_local_list; } -/** @brief Ask for authorization of operation */ +/** @copydoc ocpp::types::AuthorizationStatus IAuthentManager::authorize(const std::string&, std::string&) */ ocpp::types::AuthorizationStatus AuthentManager::authorize(const std::string& id_tag, std::string& parent_id) { bool retry; @@ -168,7 +168,7 @@ ocpp::types::AuthorizationStatus AuthentManager::authorize(const std::string& id return status; } -/** @brief Update a tag information */ +/** @copydoc void IAuthentManager::update(const std::string& id_tag, const ocpp::types::IdTagInfo&) */ void AuthentManager::update(const std::string& id_tag, const ocpp::types::IdTagInfo& tag_info) { // Check if the cache is enabled @@ -189,5 +189,82 @@ void AuthentManager::update(const std::string& id_tag, const ocpp::types::IdTagI } } +/** @copydoc ocpp::types::AuthorizationStatus IAuthentManager::iso15118Authorize(const std::string&) */ +ocpp::types::AuthorizationStatus AuthentManager::iso15118Authorize(const std::string& token_id) +{ + AuthorizationStatus status = AuthorizationStatus::Invalid; + + // Check if local authorization is enabled + if (m_ocpp_config.localAuthorizeOffline()) + { + bool found = false; + + // Check local authorization list + IdTagInfo tag_info; + if (m_ocpp_config.localAuthListEnabled()) + { + found = m_local_list.check(token_id, tag_info); + if (found) + { + status = tag_info.status; + } + LOG_DEBUG << "Token [" << token_id << "] found in local list : " << found; + } + + // Check local cache + if (!found) + { + if (m_ocpp_config.authorizationCacheEnabled()) + { + found = m_cache.check(token_id, tag_info); + if (found) + { + status = tag_info.status; + } + LOG_DEBUG << "Token [" << token_id << "] found in cache : " << found; + } + } + + // Check if unknown ids are allowed when offline + if (!found) + { + if (m_ocpp_config.allowOfflineTxForUnknownId()) + { + status = AuthorizationStatus::Accepted; + + LOG_DEBUG << "Token [" << token_id << "] unknown but accepted"; + } + } + } + + LOG_INFO << "Authorization for token [" << token_id << "] : " << AuthorizationStatusHelper.toString(status); + + return status; +} + +/** @copydoc void IAuthentManager::iso15118Update(const std::string&, const ocpp::types::IdTokenInfoType&) */ +void AuthentManager::iso15118Update(const std::string& token_id, const ocpp::types::IdTokenInfoType& token_info) +{ + // Check if the cache is enabled + if (m_ocpp_config.authorizationCacheEnabled()) + { + // Check local authorization list + bool in_local_list = false; + if (m_ocpp_config.localAuthListEnabled()) + { + IdTagInfo unused_tag_info; + in_local_list = m_local_list.check(token_id, unused_tag_info); + } + if (!in_local_list) + { + // Update cache + IdTagInfo tag_info; + tag_info.status = token_info.status; + tag_info.expiryDate = token_info.cacheExpiryDateTime; + m_cache.update(token_id, tag_info); + } + } +} + } // namespace chargepoint } // namespace ocpp diff --git a/src/chargepoint/authent/AuthentManager.h b/src/chargepoint/authent/AuthentManager.h index eb8d14d1..3954c614 100644 --- a/src/chargepoint/authent/AuthentManager.h +++ b/src/chargepoint/authent/AuthentManager.h @@ -19,8 +19,7 @@ along with OpenOCPP. If not, see . #ifndef OPENOCPP_AUTHENTMANAGER_H #define OPENOCPP_AUTHENTMANAGER_H -#include "Enums.h" -#include "IdTagInfo.h" +#include "IAuthentManager.h" namespace ocpp { @@ -50,7 +49,7 @@ class AuthentCache; class AuthentLocalList; /** @brief Handle charge point authentication requests */ -class AuthentManager +class AuthentManager : public IAuthentManager { public: /** @brief Constructor */ @@ -65,20 +64,17 @@ class AuthentManager /** @brief Destructor */ virtual ~AuthentManager(); - /** - * @brief Ask for authorization of operation - * @param id_tag Id of the user's - * @param parent_id If of the user's parent tag - * @return Authorization status (see AuthorizationStatus enum) - */ - ocpp::types::AuthorizationStatus authorize(const std::string& id_tag, std::string& parent_id); - - /** - * @brief Update a tag information - * @param id_tag Id of the tag to update - * @param id_tag_info New tag informations - */ - void update(const std::string& id_tag, const ocpp::types::IdTagInfo& tag_info); + /** @copydoc ocpp::types::AuthorizationStatus IAuthentManager::authorize(const std::string&, std::string&) */ + ocpp::types::AuthorizationStatus authorize(const std::string& id_tag, std::string& parent_id) override; + + /** @copydoc void IAuthentManager::update(const std::string& id_tag, const ocpp::types::IdTagInfo&) */ + void update(const std::string& id_tag, const ocpp::types::IdTagInfo& tag_info) override; + + /** @copydoc ocpp::types::AuthorizationStatus IAuthentManager::iso15118Authorize(const std::string&) */ + ocpp::types::AuthorizationStatus iso15118Authorize(const std::string& token_id) override; + + /** @copydoc void IAuthentManager::iso15118Update(const std::string&, const ocpp::types::IdTokenInfoType&) */ + void iso15118Update(const std::string& token_id, const ocpp::types::IdTokenInfoType& token_info) override; private: /** @brief Standard OCPP configuration */ diff --git a/src/chargepoint/authent/IAuthentManager.h b/src/chargepoint/authent/IAuthentManager.h new file mode 100644 index 00000000..7fa27435 --- /dev/null +++ b/src/chargepoint/authent/IAuthentManager.h @@ -0,0 +1,71 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_IAUTHENTMANAGER_H +#define OPENOCPP_IAUTHENTMANAGER_H + +#include "Enums.h" +#include "IdTagInfo.h" +#include "IdTokenInfoType.h" + +namespace ocpp +{ +namespace chargepoint +{ + +/** @brief Interface for the authentication manager */ +class IAuthentManager +{ + public: + /** @brief Destructor */ + virtual ~IAuthentManager() { } + + /** + * @brief Ask for authorization of operation + * @param id_tag Id of the user's + * @param parent_id If of the user's parent tag + * @return Authorization status (see AuthorizationStatus enum) + */ + virtual ocpp::types::AuthorizationStatus authorize(const std::string& id_tag, std::string& parent_id) = 0; + + /** + * @brief Update a tag information + * @param id_tag Id of the tag to update + * @param id_tag_info New tag informations + */ + virtual void update(const std::string& id_tag, const ocpp::types::IdTagInfo& tag_info) = 0; + + /** + * @brief Ask for authorization of an ISO15118 operation + * @param token_id Token id of the user's + * @return Authorization status (see AuthorizationStatus enum) + */ + virtual ocpp::types::AuthorizationStatus iso15118Authorize(const std::string& token_id) = 0; + + /** + * @brief Update an ISO15118 token information + * @param token_id Id of the token to update + * @param token_info New token informations + */ + virtual void iso15118Update(const std::string& token_id, const ocpp::types::IdTokenInfoType& token_info) = 0; +}; + +} // namespace chargepoint +} // namespace ocpp + +#endif // OPENOCPP_IAUTHENTMANAGER_H diff --git a/src/chargepoint/datatransfer/DataTransferManager.cpp b/src/chargepoint/datatransfer/DataTransferManager.cpp index d80d0403..a72789d1 100644 --- a/src/chargepoint/datatransfer/DataTransferManager.cpp +++ b/src/chargepoint/datatransfer/DataTransferManager.cpp @@ -35,7 +35,8 @@ DataTransferManager::DataTransferManager(IChargePointEventsHandler& ocpp::messages::GenericMessageSender& msg_sender) : GenericMessageHandler(DATA_TRANSFER_ACTION, messages_converter), m_events_handler(events_handler), - m_msg_sender(msg_sender) + m_msg_sender(msg_sender), + m_handlers() { msg_dispatcher.registerHandler(DATA_TRANSFER_ACTION, *this); } @@ -77,6 +78,12 @@ bool DataTransferManager::dataTransfer(const std::string& vendor_i return ret; } +/** @copydoc void IDataTransferManager::registerHandler(const std::string&, IDataTransferHandler&) */ +void DataTransferManager::registerHandler(const std::string& vendor_id, IDataTransferHandler& handler) +{ + m_handlers[vendor_id] = &handler; +} + /** @copydoc bool GenericMessageHandler::handleMessage(const RequestType& request, * ResponseType& response, * std::string& error_code, @@ -90,9 +97,20 @@ bool DataTransferManager::handleMessage(const ocpp::messages::DataTransferReq& r (void)error_code; (void)error_message; - // Notify request - response.status = - m_events_handler.dataTransferRequested(request.vendorId, request.messageId.value(), request.data, response.data.value()); + // Check if a handler has been registered + auto handler = m_handlers.find(request.vendorId); + if (handler != m_handlers.cend()) + { + // Notify handler + response.status = + handler->second->onDataTransferRequest(request.vendorId, request.messageId.value(), request.data, response.data.value()); + } + else + { + // Notify request to user + response.status = + m_events_handler.dataTransferRequested(request.vendorId, request.messageId.value(), request.data, response.data.value()); + } return true; } diff --git a/src/chargepoint/datatransfer/DataTransferManager.h b/src/chargepoint/datatransfer/DataTransferManager.h index 58cbdb61..014477ca 100644 --- a/src/chargepoint/datatransfer/DataTransferManager.h +++ b/src/chargepoint/datatransfer/DataTransferManager.h @@ -20,8 +20,10 @@ along with OpenOCPP. If not, see . #define OPENOCPP_DATATRANSFERMANAGER_H #include "DataTransfer.h" -#include "Enums.h" #include "GenericMessageHandler.h" +#include "IDataTransferManager.h" + +#include namespace ocpp { @@ -40,7 +42,8 @@ namespace chargepoint class IChargePointEventsHandler; /** @brief Handle charge point data transfer requests */ -class DataTransferManager : public ocpp::messages::GenericMessageHandler +class DataTransferManager : public IDataTransferManager, + public ocpp::messages::GenericMessageHandler { public: /** @brief Constructor */ @@ -67,6 +70,11 @@ class DataTransferManager : public ocpp::messages::GenericMessageHandler::handleMessage(const RequestType& request, @@ -84,6 +92,8 @@ class DataTransferManager : public ocpp::messages::GenericMessageHandler m_handlers; }; } // namespace chargepoint diff --git a/src/chargepoint/datatransfer/IDataTransferManager.h b/src/chargepoint/datatransfer/IDataTransferManager.h new file mode 100644 index 00000000..c19bfe70 --- /dev/null +++ b/src/chargepoint/datatransfer/IDataTransferManager.h @@ -0,0 +1,73 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_IDATATRANSFERMANAGER_H +#define OPENOCPP_IDATATRANSFERMANAGER_H + +#include "Enums.h" + +#include + +namespace ocpp +{ +namespace chargepoint +{ + +/** @brief Interface for DataTransfer managers implementation */ +class IDataTransferManager +{ + public: + // Forward declaration + class IDataTransferHandler; + + /** @brief Destructor */ + virtual ~IDataTransferManager() { } + + /** + * @brief Register a handler for a specific data transfer vendor + * @param vendor_id Vendor id of the data transfer + * @param handler Handler to register + */ + virtual void registerHandler(const std::string& vendor_id, IDataTransferHandler& handler) = 0; + + /** @brief Interface for data transfer handlers implementations */ + class IDataTransferHandler + { + public: + /** @brief Destructor */ + virtual ~IDataTransferHandler() { } + + /** + * @brief Called when a data transfer request has been received + * @param vendor_id Identifies the vendor specific implementation + * @param message_id Identifies the message + * @param request_data Data associated to the request + * @param response_data Data associated with the response + * @return Response status (see DataTransferStatus enum) + */ + virtual ocpp::types::DataTransferStatus onDataTransferRequest(const std::string& vendor_id, + const std::string& message_id, + const std::string& request_data, + std::string& response_data) = 0; + }; +}; + +} // namespace chargepoint +} // namespace ocpp + +#endif // OPENOCPP_IDATATRANSFERMANAGER_H diff --git a/src/chargepoint/interface/IChargePoint.h b/src/chargepoint/interface/IChargePoint.h index 07d1183f..ae88b8ca 100644 --- a/src/chargepoint/interface/IChargePoint.h +++ b/src/chargepoint/interface/IChargePoint.h @@ -23,6 +23,7 @@ along with OpenOCPP. If not, see . #include "IChargePointConfig.h" #include "IChargePointEventsHandler.h" #include "IOcppConfig.h" +#include "OcspRequestDataType.h" #include "SecurityEvent.h" #include "SmartChargingSetpoint.h" @@ -253,6 +254,52 @@ class IChargePoint * @return true if the notification has been sent, false otherwise */ virtual bool notifySignedUpdateFirmwareStatus(ocpp::types::FirmwareStatusEnumType status) = 0; + + // ISO 15118 PnC extensions + + /** + * @brief Authorize an ISO15118 transaction + * @param certificate The X.509 certificated presented by EV + * @param id_token This contains the identifier that needs to be authorized + * @param cert_hash_data Contains the information needed to verify the EV Contract Certificate via OCSP + * @param cert_status Certificate status information. - if all certificates are + * valid: return 'Accepted'. - if one of the certificates was revoked, + * return 'CertificateRevoked + * @return Authorization status (see AuthorizationStatus type) + */ + virtual ocpp::types::AuthorizationStatus iso15118Authorize( + const ocpp::x509::Certificate& certificate, + const std::string& id_token, + const std::vector& cert_hash_data, + ocpp::types::Optional& cert_status) = 0; + + /** + * @brief Get or update an ISO15118 EV certificate + * @param iso15118_schema_version Schema version currently used for the 15118 session between EV and Charge Point + * @param action Defines whether certificate needs to be installed or updated + * @param exi_request Raw CertificateInstallationReq request from EV, Base64 encoded + * @param exi_response Raw CertificateInstallationRes response for the EV, Base64 encoded + * @return true if the processing of the message has been successful and an EXI response has been included, false otherwise + */ + virtual bool iso15118GetEVCertificate(const std::string& iso15118_schema_version, + ocpp::types::CertificateActionEnumType action, + const std::string& exi_request, + std::string& exi_response) = 0; + + /** + * @brief Get the status of an ISO15118 certificate + * @param ocsp_request Indicates the certificate of which the status is requested + * @param ocsp_result OCSPResponse class as defined in IETF RFC 6960. DER encoded (as defined in IETF RFC 6960), and then base64 encoded + * @return true if the status of the certificate has been successfully retrieved, false otherwise + */ + virtual bool iso15118GetCertificateStatus(const ocpp::types::OcspRequestDataType& ocsp_request, std::string& ocsp_result) = 0; + + /** + * @brief Send a CSR request to sign an ISO15118 certificate + * @param csr CSR request + * @return true if the request has been sent and accepted, false otherwise + */ + virtual bool iso15118SignCertificate(const ocpp::x509::CertificateRequest& csr) = 0; }; } // namespace chargepoint diff --git a/src/chargepoint/interface/IChargePointEventsHandler.h b/src/chargepoint/interface/IChargePointEventsHandler.h index a04a6c83..849cf078 100644 --- a/src/chargepoint/interface/IChargePointEventsHandler.h +++ b/src/chargepoint/interface/IChargePointEventsHandler.h @@ -24,6 +24,8 @@ along with OpenOCPP. If not, see . #include "Enums.h" #include "MeterValue.h" +#include + namespace ocpp { namespace chargepoint @@ -207,7 +209,7 @@ class IChargePointEventsHandler * @brief Called when a charge point certificate has been received and must be installed * (Not used if InternalCertificateManagementEnabled = true) * @param certificate Charge point certificate to install - * @return true is the certificate has been installed, false otherwise + * @return true if the certificate has been installed, false otherwise */ virtual bool chargePointCertificateReceived(const ocpp::x509::Certificate& certificate) = 0; @@ -277,6 +279,65 @@ class IChargePointEventsHandler */ virtual ocpp::types::UpdateFirmwareStatusEnumType checkFirmwareSigningCertificate( const ocpp::x509::Certificate& signing_certificate) = 0; + + // ISO 15118 PnC extensions + + /** + * @brief Called to check an EV certificate againts the installed MO certificates + * @param certificate EV certificate to check + * @return true if the certificate has been validated against an installed MO certificate, false otherwise + */ + virtual bool iso15118CheckEvCertificate(const ocpp::x509::Certificate& certificate) = 0; + + /** + * @brief Called when an ISO15118 charge point certificate has been received and must be installed + * @param certificate Charge point certificate to install + * @return true if the certificate has been installed, false otherwise + */ + virtual bool iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate& certificate) = 0; + + /** + * @brief Called when the Central System request to delete an installed ISO15118 certificate + * @param hash_algorithm Hash algorithm used for the following parameters + * @param issuer_name_hash Hash of the certificate's issuer's name + * @param issuer_key_hash Hash of the certificate's public key + * @param serial_number Serial number of the certificate + * @return Deletion status (see DeleteCertificateStatusEnumType enum) + */ + virtual ocpp::types::DeleteCertificateStatusEnumType iso15118DeleteCertificate(ocpp::types::HashAlgorithmEnumType hash_algorithm, + const std::string& issuer_name_hash, + const std::string& issuer_key_hash, + const std::string& serial_number) = 0; + + /** + * @brief Called to get the list of installed ISO15118 certificates + * @param v2g_root_certificate Indicate if V2G root certificates must be listed + * @param mo_root_certificate Indicate if MO root certificates must be listed + * @param v2g_certificate_chain Indicate if V2G certificate chains must be listed + * @param certificates Installed certificates with their type + */ + virtual void iso15118GetInstalledCertificates( + bool v2g_root_certificate, + bool mo_root_certificate, + bool v2g_certificate_chain, + std::vector>>& + certificates) = 0; + + /** + * @brief Called when an ISO15118 certificate has been received and must be installed + * @param type Type of certificate + * @param certificate certificate to install + * @return Installation status (see InstallCertificateStatusEnumType enum) + */ + virtual ocpp::types::InstallCertificateStatusEnumType iso15118CertificateReceived(ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate& certificate) = 0; + + /** + * @brief Called to generate a CSR in PEM format which will be used by the Central System + * to generate and sign a certificate for the Charge Point for ISO15118 communications + * @param csr String to store the generated CSR in PEM format + */ + virtual void iso15118GenerateCsr(std::string& csr) = 0; }; } // namespace chargepoint diff --git a/src/chargepoint/iso15118/Iso15118Manager.cpp b/src/chargepoint/iso15118/Iso15118Manager.cpp new file mode 100644 index 00000000..ffeb4934 --- /dev/null +++ b/src/chargepoint/iso15118/Iso15118Manager.cpp @@ -0,0 +1,518 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "Iso15118Manager.h" +#include "CertificateRequest.h" +#include "CertificateSigned.h" +#include "DeleteCertificate.h" +#include "Get15118EVCertificate.h" +#include "GetCertificateStatus.h" +#include "IAuthentManager.h" +#include "IChargePointEventsHandler.h" +#include "IOcppConfig.h" +#include "ISecurityManager.h" +#include "ITimerPool.h" +#include "Iso15118Authorize.h" +#include "Iso15118GetInstalledCertificateIds.h" +#include "Iso15118InstallCertificate.h" +#include "Iso15118TriggerMessage.h" +#include "SecurityEvent.h" +#include "SignCertificate.h" +#include "WorkerThreadPool.h" + +#include + +using namespace ocpp::x509; +using namespace ocpp::types; +using namespace ocpp::messages; + +namespace ocpp +{ +namespace chargepoint +{ + +/** @brief Constructor */ +Iso15118Manager::Iso15118Manager(ocpp::config::IOcppConfig& ocpp_config, + IChargePointEventsHandler& events_handler, + ocpp::helpers::ITimerPool& timer_pool, + ocpp::helpers::WorkerThreadPool& worker_pool, + const ocpp::messages::GenericMessagesConverter& messages_converter, + ocpp::messages::GenericMessageSender& msg_sender, + IAuthentManager& authent_manager, + IDataTransferManager& datatransfer_manager, + ISecurityManager& security_manager) + : m_ocpp_config(ocpp_config), + m_events_handler(events_handler), + m_worker_pool(worker_pool), + m_messages_converter(messages_converter), + m_msg_sender(msg_sender), + m_authent_manager(authent_manager), + m_security_manager(security_manager), + m_last_csr(), + m_csr_sign_retries(0), + m_csr_timer(timer_pool, "ISO15118 CSR timer") +{ + datatransfer_manager.registerHandler(ISO15118_VENDOR_ID, *this); +} + +/** @brief Destructor */ +Iso15118Manager::~Iso15118Manager() { } + +/** @brief Authorize an ISO15118 transaction */ +ocpp::types::AuthorizationStatus Iso15118Manager::authorize( + const ocpp::x509::Certificate& certificate, + const std::string& id_token, + const std::vector& cert_hash_data, + ocpp::types::Optional& cert_status) +{ + LOG_INFO << "[ISO15118] Authorize : token = " << id_token; + + AuthorizationStatus status = AuthorizationStatus::Invalid; + + // Check certificate + bool cert_valid = false; + cert_valid = m_events_handler.iso15118CheckEvCertificate(certificate); + if (!cert_valid) + { + LOG_WARNING << "EV certificate couldn't be verified"; + } + + // Check offline status + if (m_msg_sender.isConnected()) + { + // Check if the certificate must be check by the Central System + if (cert_valid || (!cert_valid && m_ocpp_config.centralContractValidationAllowed())) + { + // Prepare request + Iso15118AuthorizeReq request; + if (!cert_valid) + { + request.certificate.value().assign(certificate.pem()); + } + request.idToken.assign(id_token); + request.iso15118CertificateHashData = cert_hash_data; + + // Send request + Iso15118AuthorizeConf response; + if (send("Iso15118Authorize", ISO15118_AUTHORIZE_ACTION, request, response)) + { + // Extract response + cert_status = response.certificateStatus; + status = response.idTokenInfo.status; + + // Update cache + m_authent_manager.iso15118Update(id_token, response.idTokenInfo); + } + } + } + else + { + // Check if offline validation is enabled + if (m_ocpp_config.contractValidationOffline()) + { + // Offline check + status = m_authent_manager.iso15118Authorize(id_token); + } + } + + LOG_INFO << "[ISO15118] Authorize : " << AuthorizationStatusHelper.toString(status); + + return status; +} + +/** @brief Get or update an ISO15118 EV certificate */ +ocpp::types::Iso15118EVCertificateStatusEnumType Iso15118Manager::get15118EVCertificate(const std::string& iso15118_schema_version, + ocpp::types::CertificateActionEnumType action, + const std::string& exi_request, + std::string& exi_response) +{ + LOG_INFO << "[ISO15118] Get EV certificate : schema version = " << iso15118_schema_version + << " - action = " << CertificateActionEnumTypeHelper.toString(action); + + Iso15118EVCertificateStatusEnumType result = Iso15118EVCertificateStatusEnumType::Failed; + + // Prepare request + Get15118EVCertificateReq request; + request.iso15118SchemaVersion.assign(iso15118_schema_version); + request.action = action; + request.exiRequest.assign(exi_request); + + // Send request + Get15118EVCertificateConf response; + if (send("Get15118EVCertificate", GET_15118_EV_CERTIFICATE_ACTION, request, response)) + { + // Extract response + exi_response = response.exiResponse; + result = response.status; + } + + LOG_INFO << "[ISO15118] Get EV certificate : " << Iso15118EVCertificateStatusEnumTypeHelper.toString(result); + + return result; +} + +/** @brief Get the status of an ISO15118 certificate */ +ocpp::types::GetCertificateStatusEnumType Iso15118Manager::getCertificateStatus(const ocpp::types::OcspRequestDataType& ocsp_request, + std::string& ocsp_result) +{ + LOG_INFO << "[ISO15118] Get certificate status : serial number = " << ocsp_request.serialNumber.c_str() + << " - responder = " << ocsp_request.responderURL.c_str(); + + GetCertificateStatusEnumType result = GetCertificateStatusEnumType::Failed; + + // Prepare request + GetCertificateStatusReq request; + request.ocspRequestData = ocsp_request; + + // Send request + GetCertificateStatusConf response; + if (send("GetCertificateStatus", GET_CERTIFICATE_STATUS_ACTION, request, response)) + { + // Extract response + ocsp_result = response.ocspResult.value(); + result = response.status; + } + + LOG_INFO << "[ISO15118] Get certificate status : " << GetCertificateStatusEnumTypeHelper.toString(result); + + return result; +} + +/** @brief Send a CSR request to sign an ISO15118 certificate */ +bool Iso15118Manager::signCertificate(const ocpp::x509::CertificateRequest& csr) +{ + LOG_INFO << "Sign certificate : valid = " << csr.isValid() << " - subject = " << csr.subjectString(); + + // Reset retry counter + m_last_csr = csr.pem(); + m_csr_sign_retries = 0; + m_csr_timer.stop(); + + // Send request + return sendSignCertificate(); +} + +// IDataTransferManager::IDataTransferHandler interface + +/** @copydoc ocpp::types::DataTransferStatus IDataTransferHandler::onDataTransferRequest(const std::string&, + const std::string&, + const std::string&, + std::string&) */ +ocpp::types::DataTransferStatus Iso15118Manager::onDataTransferRequest(const std::string& vendor_id, + const std::string& message_id, + const std::string& request_data, + std::string& response_data) +{ + (void)vendor_id; + (void)request_data; + (void)response_data; + + DataTransferStatus status = DataTransferStatus::Accepted; + + // Check if ISO15518 support is enabled + if (m_ocpp_config.iso15118PnCEnabled()) + { + // Known messages + if (message_id == CERTIFICATE_SIGNED_ACTION) + { + // CertificateSigned + status = handle("CertificateSigned", request_data, response_data); + } + else if (message_id == DELETE_CERTIFICATE_ACTION) + { + // DeleteCertificate + status = handle("DeleteCertificate", request_data, response_data); + } + else if (message_id == ISO15118_GET_INSTALLED_CERTIFICATE_IDS_ACTION) + { + // GetInstalledCertificateIds + status = handle( + "Iso15118GetInstalledCertificateIds", request_data, response_data); + } + else if (message_id == ISO15118_INSTALL_CERTIFICATE_ACTION) + { + // InstallCertificate + status = handle( + "Iso15118InstallCertificate", request_data, response_data); + } + else if (message_id == ISO15118_TRIGGER_MESSAGE_ACTION) + { + // TriggerMessage + status = handle("Iso15118TriggerMessage", request_data, response_data); + } + else + { + // Unknown message + LOG_ERROR << "[ISO15118] Unknown message : " << message_id; + status = DataTransferStatus::UnknownMessageId; + } + } + else + { + // Not supported + LOG_ERROR << "[ISO15118] Not supported : message_id = " << message_id; + status = DataTransferStatus::UnknownVendorId; + } + + return status; +} + +/** @brief Handle a CertificateSigned request */ +void Iso15118Manager::handle(const ocpp::messages::CertificateSignedReq& request, ocpp::messages::CertificateSignedConf& response) +{ + LOG_INFO << "[ISO15118] Certificate signed message received : certificate size = " << request.certificateChain.size(); + + // Prepare response + bool send_security_event = true; + response.status = CertificateSignedStatusEnumType::Rejected; + + // Check certificate's size + if (request.certificateChain.size() < m_ocpp_config.certificateSignedMaxChainSize()) + { + // Check certificate's validity + Certificate certificate(request.certificateChain); + if (certificate.isValid() && certificate.verify()) + { + // Notify new certificate + if (m_events_handler.iso15118ChargePointCertificateReceived(certificate)) + { + // Stop timeout timer + m_csr_timer.stop(); + send_security_event = false; + response.status = CertificateSignedStatusEnumType::Accepted; + } + } + } + + // Triggers a security event + if (send_security_event) + { + m_security_manager.logSecurityEvent(SECEVT_INVALID_CHARGE_POINT_CERT, ""); + } + + LOG_INFO << "[ISO15118] Certificate signed message : " << CertificateSignedStatusEnumTypeHelper.toString(response.status); +} + +/** @brief Handle a DeleteCertificate request */ +void Iso15118Manager::handle(const ocpp::messages::DeleteCertificateReq& request, ocpp::messages::DeleteCertificateConf& response) +{ + LOG_INFO << "[ISO15118] Delete certificate request received : hashAlgorithm = " + << HashAlgorithmEnumTypeHelper.toString(request.certificateHashData.hashAlgorithm) + << " - issuerKeyHash = " << request.certificateHashData.issuerKeyHash.str() + << " - issuerNameHash = " << request.certificateHashData.issuerNameHash.str() + << " - serialNumber = " << request.certificateHashData.serialNumber.str(); + + // Notify handler to delete the certificate + response.status = m_events_handler.iso15118DeleteCertificate(request.certificateHashData.hashAlgorithm, + request.certificateHashData.issuerNameHash, + request.certificateHashData.issuerKeyHash, + request.certificateHashData.serialNumber); + + LOG_INFO << "[ISO15118] Delete certificate : " << DeleteCertificateStatusEnumTypeHelper.toString(response.status); +} + +/** @brief Handle an Iso15118GetInstalledCertificateIds request */ +void Iso15118Manager::handle(const ocpp::messages::Iso15118GetInstalledCertificateIdsReq& request, + ocpp::messages::Iso15118GetInstalledCertificateIdsConf& response) +{ + LOG_INFO << "[ISO15118] Get installed certificate ids request received : certificateType count = " << request.certificateType.size(); + + // Prepare response + response.status = GetInstalledCertificateStatusEnumType::NotFound; + + // Get certificate types + bool v2g_root_certificate = false; + bool mo_root_certificate = false; + bool v2g_certificate_chain = false; + if (request.certificateType.empty()) + { + // All types requested + v2g_root_certificate = true; + mo_root_certificate = true; + v2g_certificate_chain = true; + } + else + { + // Check selected types + for (const GetCertificateIdUseEnumType& cert_type : request.certificateType) + { + switch (cert_type) + { + case GetCertificateIdUseEnumType::V2GRootCertificate: + v2g_root_certificate = true; + break; + case GetCertificateIdUseEnumType::MORootCertificate: + mo_root_certificate = true; + break; + case GetCertificateIdUseEnumType::V2GCertificateChain: + // Intended fallthrough + default: + v2g_certificate_chain = true; + break; + } + } + } + + // Notify handler to get the list of installed certificates + std::vector>> certificates; + m_events_handler.iso15118GetInstalledCertificates(v2g_root_certificate, mo_root_certificate, v2g_certificate_chain, certificates); + if (!certificates.empty()) + { + // Compute hashes for each certificate + for (const auto& [cert_type, certificate, child_certificates] : certificates) + { + if (certificate.isValid()) + { + response.certificateHashDataChain.emplace_back(); + CertificateHashDataChainType& hash_data = response.certificateHashDataChain.back(); + hash_data.certificateType = cert_type; + fillHashInfo(certificate, hash_data.certificateHashData); + for (const auto& cert : child_certificates) + { + hash_data.childCertificateHashData.emplace_back(); + fillHashInfo(cert, hash_data.childCertificateHashData.back()); + } + } + } + if (!response.certificateHashDataChain.empty()) + { + response.status = GetInstalledCertificateStatusEnumType::Accepted; + } + } + + LOG_INFO << "[ISO15118] Get installed certificate ids : status = " + << GetInstalledCertificateStatusEnumTypeHelper.toString(response.status) + << " - count = " << response.certificateHashDataChain.size(); +} + +/** @brief Handle an InstallCertificate request */ +void Iso15118Manager::handle(const ocpp::messages::Iso15118InstallCertificateReq& request, + ocpp::messages::Iso15118InstallCertificateConf& response) +{ + LOG_INFO << "[ISO15118] Install certificate request received : certificateType = " + << InstallCertificateUseEnumTypeHelper.toString(request.certificateType) + << " - certificate size = " << request.certificate.size(); + + // Prepare response + response.status = InstallCertificateStatusEnumType::Rejected; + + // Check certificate + Certificate certificate(request.certificate.str()); + if (certificate.isValid()) + { + // Notify new certificate + response.status = m_events_handler.iso15118CertificateReceived(request.certificateType, certificate); + } + + LOG_INFO << "Install certificate : " << InstallCertificateStatusEnumTypeHelper.toString(response.status); +} + +/** @brief Handle a TriggerMessage request */ +void Iso15118Manager::handle(const ocpp::messages::Iso15118TriggerMessageReq& request, ocpp::messages::Iso15118TriggerMessageConf& response) +{ + (void)request; + + m_worker_pool.run( + [this] + { + // To let some time for the trigger message reply + std::this_thread::sleep_for(std::chrono::milliseconds(100u)); + + // Notify application to generate a CSR + std::string csr_pem; + m_events_handler.iso15118GenerateCsr(csr_pem); + + // Create request + CertificateRequest csr(csr_pem); + + // Send the request + signCertificate(csr); + }); + + response.status = TriggerMessageStatusEnumType::Accepted; +} + +/** @brief Fill the hash information of a certificat */ +void Iso15118Manager::fillHashInfo(const ocpp::x509::Certificate& certificate, ocpp::types::CertificateHashDataType& info) +{ + // Compute hashes with SHA-256 algorithm + Sha2 sha256; + info.hashAlgorithm = HashAlgorithmEnumType::SHA256; + sha256.compute(certificate.issuerString().c_str(), certificate.issuerString().size()); + info.issuerNameHash.assign(sha256.resultString()); + sha256.compute(&certificate.publicKey()[0], certificate.publicKey().size()); + info.issuerKeyHash.assign(sha256.resultString()); + info.serialNumber.assign(certificate.serialNumberHexString()); +} + +/** @brief Send a CSR request to sign an ISO15118 certificate */ +bool Iso15118Manager::sendSignCertificate() +{ + LOG_INFO << "Sending sign certificate : retries = " << m_csr_sign_retries; + + GenericStatusEnumType result = GenericStatusEnumType::Rejected; + + // Prepare request + SignCertificateReq request; + request.csr.assign(m_last_csr); + + // Send request + SignCertificateConf response; + if (send("SignCertificate", SIGN_CERTIFICATE_ACTION, request, response)) + { + // Extract response + result = response.status; + if (result == GenericStatusEnumType::Accepted) + { + // Start timer to retry if no response has been received + if (m_csr_sign_retries < m_ocpp_config.certSigningRepeatTimes()) + { + m_csr_sign_retries++; + if (m_ocpp_config.certSigningWaitMinimum().count() != 0) + { + LOG_INFO << "Setting timeout for sign certificate to " << m_ocpp_config.certSigningWaitMinimum().count() << "s"; + m_csr_timer.setCallback( + [this]() + { + m_worker_pool.run( + [this] + { + LOG_ERROR << "Sign certificate timeout, triggering retry..."; + sendSignCertificate(); + }); + }); + m_csr_timer.start(m_ocpp_config.certSigningWaitMinimum(), true); + } + } + else + { + if (m_csr_sign_retries != 0u) + { + LOG_WARNING << "Max sign certificate retries reached : " << m_ocpp_config.certSigningRepeatTimes(); + } + } + } + } + + LOG_INFO << "Sign certificate : " << GenericStatusEnumTypeHelper.toString(result); + + return (result == GenericStatusEnumType::Accepted); +} + +} // namespace chargepoint +} // namespace ocpp diff --git a/src/chargepoint/iso15118/Iso15118Manager.h b/src/chargepoint/iso15118/Iso15118Manager.h new file mode 100644 index 00000000..afd67b2f --- /dev/null +++ b/src/chargepoint/iso15118/Iso15118Manager.h @@ -0,0 +1,318 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_ISO15118MANAGER_H +#define OPENOCPP_ISO15118MANAGER_H + +#include "Certificate.h" +#include "CertificateHashDataType.h" +#include "DataTransfer.h" +#include "GenericMessageSender.h" +#include "GenericMessagesConverter.h" +#include "IDataTransferManager.h" +#include "IdTokenInfoType.h" +#include "Iso15118.h" +#include "Logger.h" +#include "OcspRequestDataType.h" +#include "Timer.h" +#include "json.h" + +#include + +namespace ocpp +{ +// Forward declarations +namespace config +{ +class IOcppConfig; +} // namespace config +namespace messages +{ +struct CertificateSignedReq; +struct CertificateSignedConf; +struct DeleteCertificateReq; +struct DeleteCertificateConf; +struct Iso15118GetInstalledCertificateIdsReq; +struct Iso15118GetInstalledCertificateIdsConf; +struct Iso15118InstallCertificateReq; +struct Iso15118InstallCertificateConf; +struct Iso15118TriggerMessageReq; +struct Iso15118TriggerMessageConf; +} // namespace messages +namespace helpers +{ +class WorkerThreadPool; +} // namespace helpers + +// Main namespace +namespace chargepoint +{ + +class IAuthentManager; +class IChargePointEventsHandler; +class ISecurityManager; + +/** @brief Handle charge point ISO15118 requests */ +class Iso15118Manager : public IDataTransferManager::IDataTransferHandler +{ + public: + /** @brief Constructor */ + Iso15118Manager(ocpp::config::IOcppConfig& ocpp_config, + IChargePointEventsHandler& events_handler, + ocpp::helpers::ITimerPool& timer_pool, + ocpp::helpers::WorkerThreadPool& worker_pool, + const ocpp::messages::GenericMessagesConverter& messages_converter, + ocpp::messages::GenericMessageSender& msg_sender, + IAuthentManager& authent_manager, + IDataTransferManager& datatransfer_manager, + ISecurityManager& security_manager); + + /** @brief Destructor */ + virtual ~Iso15118Manager(); + + /** + * @brief Authorize an ISO15118 transaction + * @param certificate The X.509 certificated presented by EV + * @param id_token This contains the identifier that needs to be authorized + * @param cert_hash_data Contains the information needed to verify the EV Contract Certificate via OCSP + * @param cert_status Certificate status information. - if all certificates are + * valid: return 'Accepted'. - if one of the certificates was revoked, + * return 'CertificateRevoked + * @return Authorization status (see AuthorizationStatus type) + */ + ocpp::types::AuthorizationStatus authorize(const ocpp::x509::Certificate& certificate, + const std::string& id_token, + const std::vector& cert_hash_data, + ocpp::types::Optional& cert_status); + /** + * @brief Get or update an ISO15118 EV certificate + * @param iso15118_schema_version Schema version currently used for the 15118 session between EV and Charge Point + * @param action Defines whether certificate needs to be installed or updated + * @param exi_request Raw CertificateInstallationReq request from EV, Base64 encoded + * @param exi_response Raw CertificateInstallationRes response for the EV, Base64 encoded + * @return Operation status (see Iso15118EVCertificateStatusEnumType enum) + */ + ocpp::types::Iso15118EVCertificateStatusEnumType get15118EVCertificate(const std::string& iso15118_schema_version, + ocpp::types::CertificateActionEnumType action, + const std::string& exi_request, + std::string& exi_response); + + /** + * @brief Get the status of an ISO15118 certificate + * @param ocsp_request Indicates the certificate of which the status is requested + * @param ocsp_result OCSPResponse class as defined in IETF RFC 6960. DER encoded (as defined in IETF RFC 6960), and then base64 encoded + * @return Operation status (see GetCertificateStatusEnumType enum) + */ + ocpp::types::GetCertificateStatusEnumType getCertificateStatus(const ocpp::types::OcspRequestDataType& ocsp_request, + std::string& ocsp_result); + + /** + * @brief Send a CSR request to sign an ISO15118 certificate + * @param csr CSR request + * @return true if the request has been sent and accepted, false otherwise + */ + bool signCertificate(const ocpp::x509::CertificateRequest& csr); + + // IDataTransferManager::IDataTransferHandler interface + + /** @copydoc ocpp::types::DataTransferStatus IDataTransferHandler::onDataTransferRequest(const std::string&, + const std::string&, + const std::string&, + std::string&) */ + ocpp::types::DataTransferStatus onDataTransferRequest(const std::string& vendor_id, + const std::string& message_id, + const std::string& request_data, + std::string& response_data) override; + + private: + /** @brief Standard OCPP configuration */ + ocpp::config::IOcppConfig& m_ocpp_config; + /** @brief User defined events handler */ + IChargePointEventsHandler& m_events_handler; + /** @brief Worker thread pool */ + ocpp::helpers::WorkerThreadPool& m_worker_pool; + /** @brief Messages converters */ + const ocpp::messages::GenericMessagesConverter& m_messages_converter; + /** @brief Message sender */ + ocpp::messages::GenericMessageSender& m_msg_sender; + /** @brief Authentication manager */ + IAuthentManager& m_authent_manager; + /** @brief Security manager */ + ISecurityManager& m_security_manager; + /** @brief Last CSR request to sign */ + std::string m_last_csr; + /** @brief Number of retries to sign a CSR request */ + unsigned int m_csr_sign_retries; + /** @brief Timer for sign certificate operations */ + ocpp::helpers::Timer m_csr_timer; + + /** + * @brief Generic ISO15118 request sender + * @param type_id Type of message + * @param action Action correspondin to the message + * @param request Request to send + * @param response Received response + * @return true if the message has been sent and a response has been received, false otherwise + */ + template + bool send(const std::string& type_id, const std::string& action, const RequestType& request, ResponseType& response) + { + bool ret = false; + + // Get converters + ocpp::messages::IMessageConverter* req_converter = m_messages_converter.getRequestConverter(type_id); + ocpp::messages::IMessageConverter* resp_converter = m_messages_converter.getResponseConverter(type_id); + + // Prepare request + ocpp::messages::DataTransferReq req; + req.vendorId.assign(ocpp::messages::ISO15118_VENDOR_ID); + req.messageId.value().assign(action); + + // Convert request to JSON + rapidjson::Document json_req; + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + json_req.Parse("{}"); + req_converter->setAllocator(&json_req.GetAllocator()); + req_converter->toJson(request, json_req); + json_req.Accept(writer); + req.data.value() = buffer.GetString(); + + // Send request + ocpp::messages::DataTransferConf resp; + if (m_msg_sender.call(ocpp::messages::DATA_TRANSFER_ACTION, req, resp) == ocpp::messages::CallResult::Ok) + { + if (resp.status == ocpp::types::DataTransferStatus::Accepted) + { + try + { + // Parse JSON + rapidjson::Document json_resp; + json_resp.Parse(resp.data.value().c_str()); + if (!json_resp.HasParseError()) + { + // Convert response from JSON + std::string error_code; + std::string error_message; + ret = resp_converter->fromJson(json_resp, response, error_code, error_message); + } + else + { + LOG_ERROR << "[ISO15118] << " << action << " : Invalid JSON received"; + } + } + catch (const std::exception&) + { + LOG_ERROR << "[ISO15118] << " << action << " : Invalid JSON received"; + } + } + else + { + LOG_ERROR << "[ISO15118] Data transfer error : " << ocpp::types::DataTransferStatusHelper.toString(resp.status); + } + } + + return ret; + } + + /** + * @brief Generic ISO15118 request handler + * @param type_id Type of message + * @param request_data Data associated to the request + * @param response_data Data associated with the response + * @return Response status (see DataTransferStatus enum) + */ + template + ocpp::types::DataTransferStatus handle(const std::string& type_id, const std::string& request_data, std::string& response_data) + { + ocpp::types::DataTransferStatus status = ocpp::types::DataTransferStatus::Rejected; + ocpp::messages::IMessageConverter* req_converter = m_messages_converter.getRequestConverter(type_id); + ocpp::messages::IMessageConverter* resp_converter = m_messages_converter.getResponseConverter(type_id); + try + { + // Parse JSON + rapidjson::Document request; + request.Parse(request_data.c_str()); + if (!request.HasParseError()) + { + // Convert request from JSON + RequestType req; + std::string error_code; + std::string error_message; + if (req_converter->fromJson(request, req, error_code, error_message)) + { + // Handle message + ResponseType resp; + handle(req, resp); + + // Convert response to JSON + rapidjson::Document response; + response.Parse("{}"); + resp_converter->setAllocator(&response.GetAllocator()); + if (resp_converter->toJson(resp, response)) + { + + // Serialize response + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + response.Accept(writer); + response_data = buffer.GetString(); + + status = ocpp::types::DataTransferStatus::Accepted; + } + } + else + { + LOG_ERROR << "[ISO15118] << " << type_id << " : Invalid JSON received"; + } + } + else + { + LOG_ERROR << "[ISO15118] << " << type_id << " : Invalid JSON received"; + } + } + catch (const std::exception&) + { + LOG_ERROR << "[ISO15118] << " << type_id << " : Invalid JSON received"; + } + return status; + } + + /** @brief Handle a CertificateSigned request */ + void handle(const ocpp::messages::CertificateSignedReq& request, ocpp::messages::CertificateSignedConf& response); + /** @brief Handle a DeleteCertificate request */ + void handle(const ocpp::messages::DeleteCertificateReq& request, ocpp::messages::DeleteCertificateConf& response); + /** @brief Handle an Iso15118GetInstalledCertificateIds request */ + void handle(const ocpp::messages::Iso15118GetInstalledCertificateIdsReq& request, + ocpp::messages::Iso15118GetInstalledCertificateIdsConf& response); + /** @brief Handle an InstallCertificate request */ + void handle(const ocpp::messages::Iso15118InstallCertificateReq& request, ocpp::messages::Iso15118InstallCertificateConf& response); + /** @brief Handle a TriggerMessage request */ + void handle(const ocpp::messages::Iso15118TriggerMessageReq& request, ocpp::messages::Iso15118TriggerMessageConf& response); + + /** @brief Fill the hash information of a certificat */ + void fillHashInfo(const ocpp::x509::Certificate& certificate, ocpp::types::CertificateHashDataType& info); + + /** @brief Send a CSR request to sign an ISO15118 certificate */ + bool sendSignCertificate(); +}; + +} // namespace chargepoint +} // namespace ocpp + +#endif // OPENOCPP_ISO15118MANAGER_H diff --git a/src/chargepoint/requestfifo/RequestFifoManager.cpp b/src/chargepoint/requestfifo/RequestFifoManager.cpp index 7d5ddbb1..abb3e00e 100644 --- a/src/chargepoint/requestfifo/RequestFifoManager.cpp +++ b/src/chargepoint/requestfifo/RequestFifoManager.cpp @@ -17,9 +17,9 @@ along with OpenOCPP. If not, see . */ #include "RequestFifoManager.h" -#include "AuthentManager.h" #include "Connectors.h" #include "GenericMessageSender.h" +#include "IAuthentManager.h" #include "IChargePointEventsHandler.h" #include "IOcppConfig.h" #include "IStatusManager.h" @@ -47,7 +47,7 @@ RequestFifoManager::RequestFifoManager(ocpp::config::IOcppConfig& ocp ocpp::messages::GenericMessageSender& msg_sender, ocpp::messages::IRequestFifo& requests_fifo, IStatusManager& status_manager, - AuthentManager& authent_manager) + IAuthentManager& authent_manager) : m_ocpp_config(ocpp_config), m_events_handler(events_handler), m_worker_pool(worker_pool), diff --git a/src/chargepoint/requestfifo/RequestFifoManager.h b/src/chargepoint/requestfifo/RequestFifoManager.h index 5cc6a26e..be2ae61d 100644 --- a/src/chargepoint/requestfifo/RequestFifoManager.h +++ b/src/chargepoint/requestfifo/RequestFifoManager.h @@ -42,7 +42,7 @@ class WorkerThreadPool; namespace chargepoint { -class AuthentManager; +class IAuthentManager; class Connectors; class IStatusManager; class IChargePointEventsHandler; @@ -60,7 +60,7 @@ class RequestFifoManager : public ocpp::messages::IRequestFifo::IListener ocpp::messages::GenericMessageSender& msg_sender, ocpp::messages::IRequestFifo& requests_fifo, IStatusManager& status_manager, - AuthentManager& authent_manager); + IAuthentManager& authent_manager); /** @brief Destructor */ virtual ~RequestFifoManager(); @@ -90,7 +90,7 @@ class RequestFifoManager : public ocpp::messages::IRequestFifo::IListener /** @brief Status manager */ IStatusManager& m_status_manager; /** @brief Authentication manager */ - AuthentManager& m_authent_manager; + IAuthentManager& m_authent_manager; /** @brief Requests FIFO */ ocpp::messages::IRequestFifo& m_requests_fifo; diff --git a/src/chargepoint/reservation/ReservationManager.cpp b/src/chargepoint/reservation/ReservationManager.cpp index 31138780..5dc0a411 100644 --- a/src/chargepoint/reservation/ReservationManager.cpp +++ b/src/chargepoint/reservation/ReservationManager.cpp @@ -17,8 +17,8 @@ along with OpenOCPP. If not, see . */ #include "ReservationManager.h" -#include "AuthentManager.h" #include "Connectors.h" +#include "IAuthentManager.h" #include "IChargePointEventsHandler.h" #include "IOcppConfig.h" #include "IRpc.h" @@ -45,7 +45,7 @@ ReservationManager::ReservationManager(ocpp::config::IOcppConfig& const ocpp::messages::GenericMessagesConverter& messages_converter, ocpp::messages::IMessageDispatcher& msg_dispatcher, IStatusManager& status_manager, - AuthentManager& authent_manager) + IAuthentManager& authent_manager) : GenericMessageHandler(RESERVE_NOW_ACTION, messages_converter), GenericMessageHandler(CANCEL_RESERVATION_ACTION, messages_converter), m_ocpp_config(ocpp_config), diff --git a/src/chargepoint/reservation/ReservationManager.h b/src/chargepoint/reservation/ReservationManager.h index 2acba000..fc8e63b6 100644 --- a/src/chargepoint/reservation/ReservationManager.h +++ b/src/chargepoint/reservation/ReservationManager.h @@ -46,7 +46,7 @@ class WorkerThreadPool; namespace chargepoint { -class AuthentManager; +class IAuthentManager; class IStatusManager; class Connectors; class IChargePointEventsHandler; @@ -66,7 +66,7 @@ class ReservationManager const ocpp::messages::GenericMessagesConverter& messages_converter, ocpp::messages::IMessageDispatcher& msg_dispatcher, IStatusManager& status_manager, - AuthentManager& authent_manager); + IAuthentManager& authent_manager); /** @brief Destructor */ virtual ~ReservationManager(); @@ -119,7 +119,7 @@ class ReservationManager /** @brief Status manager */ IStatusManager& m_status_manager; /** @brief Authentication manager */ - AuthentManager& m_authent_manager; + IAuthentManager& m_authent_manager; /** @brief Periodic timer to check reservations expiry */ ocpp::helpers::Timer m_expiry_timer; diff --git a/src/chargepoint/status/StatusManager.cpp b/src/chargepoint/status/StatusManager.cpp index da49cffa..c756cecb 100644 --- a/src/chargepoint/status/StatusManager.cpp +++ b/src/chargepoint/status/StatusManager.cpp @@ -519,6 +519,15 @@ void StatusManager::sendBootNotification() std::chrono::seconds interval(boot_conf.interval); m_ocpp_config.heartbeatInterval(interval); m_heartbeat_timer.restart(std::chrono::milliseconds(interval)); + + // Save registration status + m_force_boot_notification = false; + m_internal_config.setKey(LAST_REGISTRATION_STATUS_KEY, RegistrationStatusHelper.toString(m_registration_status)); + if (m_registration_status == RegistrationStatus::Accepted) + { + // Cancel next retry + m_boot_notification_timer.stop(); + } } return; diff --git a/src/chargepoint/transaction/TransactionManager.cpp b/src/chargepoint/transaction/TransactionManager.cpp index d7acce6d..0fad4f79 100644 --- a/src/chargepoint/transaction/TransactionManager.cpp +++ b/src/chargepoint/transaction/TransactionManager.cpp @@ -17,9 +17,9 @@ along with OpenOCPP. If not, see . */ #include "TransactionManager.h" -#include "AuthentManager.h" #include "Connectors.h" #include "GenericMessageSender.h" +#include "IAuthentManager.h" #include "IChargePointEventsHandler.h" #include "IMeterValuesManager.h" #include "IOcppConfig.h" @@ -46,7 +46,7 @@ TransactionManager::TransactionManager(ocpp::config::IOcppConfig& ocpp::messages::IMessageDispatcher& msg_dispatcher, ocpp::messages::GenericMessageSender& msg_sender, ocpp::messages::IRequestFifo& requests_fifo, - AuthentManager& authent_manager, + IAuthentManager& authent_manager, ReservationManager& reservation_manager, IMeterValuesManager& meter_values_manager, ISmartChargingManager& smart_charging_manager) diff --git a/src/chargepoint/transaction/TransactionManager.h b/src/chargepoint/transaction/TransactionManager.h index 767c06de..7cba60ba 100644 --- a/src/chargepoint/transaction/TransactionManager.h +++ b/src/chargepoint/transaction/TransactionManager.h @@ -43,7 +43,7 @@ class IRequestFifo; namespace chargepoint { -class AuthentManager; +class IAuthentManager; class Connectors; class ReservationManager; class IChargePointEventsHandler; @@ -64,7 +64,7 @@ class TransactionManager ocpp::messages::IMessageDispatcher& msg_dispatcher, ocpp::messages::GenericMessageSender& msg_sender, ocpp::messages::IRequestFifo& requests_fifo, - AuthentManager& authent_manager, + IAuthentManager& authent_manager, ReservationManager& reservation_manager, IMeterValuesManager& meter_values_manager, ISmartChargingManager& smart_charging_manager); @@ -121,7 +121,7 @@ class TransactionManager /** @brief Message sender */ ocpp::messages::GenericMessageSender& m_msg_sender; /** @brief Authentication manager */ - AuthentManager& m_authent_manager; + IAuthentManager& m_authent_manager; /** @brief Reservation manager */ ReservationManager& m_reservation_manager; /** @brief Meter values manager */ diff --git a/src/config/IOcppConfig.h b/src/config/IOcppConfig.h index b847fd1f..5aa5a3bb 100644 --- a/src/config/IOcppConfig.h +++ b/src/config/IOcppConfig.h @@ -261,6 +261,37 @@ class IOcppConfig /** @brief Comma separated list of supported file transfer protocols for upload AND download Allowed values : FTP, FTPS, HTTP, HTTPS, SFTP */ virtual std::string supportedFileTransferProtocols() const = 0; + + // + // ISO 15118 PnC extensions + // + + /** @brief If this variable exists and has the value true, then the Charge Point can provide a contract certificate that it cannot + validate to the Central System for validation as part of the Authorize.req */ + virtual bool centralContractValidationAllowed() const = 0; + + /** @brief This configuration key defines how long the Charge Point has to wait (in seconds) before generating another CSR, in the case the + Central System accepts the SignCertificate.req, but never returns the signed certificate back. This value will be doubled after every + attempt. The amount of attempts is configured at CertSigningRepeatTimes. If the certificate signing process is slow, this setting + allows the Central System to tell the Charge Point to allow more time. + Negative values must be rejected. The value 0 means that the Charge Point does not generate another CSR (leaving it up to the + Central System to trigger another certificate installation). */ + virtual std::chrono::seconds certSigningWaitMinimum() const = 0; + + /** @brief This configuration key can be used to configure the amount of times the Charge Point SHALL double the previous back-off time, + starting with the number of seconds configured at CertSigningWaitMinimum, every time the back-off time expires without having + received the CertificateSigned.req containing the signed certificate based on the CSR generated. When the maximum number of + increments is reached, the Charge Point SHALL stop resending the SignCertificate.req, until it is requested by the Central System + using a TriggerMessage.req. + Negative values must be rejected. The value 0 means that the Charge Point does not double the back-off time. */ + virtual unsigned int certSigningRepeatTimes() const = 0; + + /** @brief If this variable is true, then the Charge Point will try to validate a contract certificate when it is offline. */ + virtual bool contractValidationOffline() const = 0; + + /** @brief If this variable set to true, then the Charge Point supports ISO 15118 plug and charge messages via the DataTransfer mechanism as + described in this application note. */ + virtual bool iso15118PnCEnabled() const = 0; }; } // namespace config diff --git a/src/messages/CMakeLists.txt b/src/messages/CMakeLists.txt index a50712c1..59908062 100644 --- a/src/messages/CMakeLists.txt +++ b/src/messages/CMakeLists.txt @@ -23,6 +23,8 @@ add_library(messages OBJECT ExtendedTriggerMessage.cpp FirmwareStatusNotification.cpp GetConfiguration.cpp + Get15118EVCertificate.cpp + GetCertificateStatus.cpp GetCompositeSchedule.cpp GetDiagnostics.cpp GetInstalledCertificateIds.cpp @@ -30,6 +32,10 @@ add_library(messages OBJECT GetLog.cpp Heartbeat.cpp InstallCertificate.cpp + Iso15118Authorize.cpp + Iso15118GetInstalledCertificateIds.cpp + Iso15118InstallCertificate.cpp + Iso15118TriggerMessage.cpp LogStatusNotification.cpp MeterValues.cpp RemoteStartTransaction.cpp @@ -50,11 +56,14 @@ add_library(messages OBJECT UpdateFirmware.cpp types/AuthorizationDataConverter.cpp + types/CertificateHashDataChainTypeConverter.cpp types/CertificateHashDataTypeConverter.cpp types/ChargingProfileConverter.cpp types/ChargingScheduleConverter.cpp types/IdTagInfoConverter.cpp + types/IdTokenInfoTypeConverter.cpp types/MeterValueConverter.cpp + types/OcspRequestDataTypeConverter.cpp ) # Exported includes diff --git a/src/messages/Get15118EVCertificate.cpp b/src/messages/Get15118EVCertificate.cpp new file mode 100644 index 00000000..b0bc054c --- /dev/null +++ b/src/messages/Get15118EVCertificate.cpp @@ -0,0 +1,85 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "Get15118EVCertificate.h" +#include "IRpc.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace types +{ +/** @brief Helper to convert a enum class CertificateActionEnumType enum to string */ +const EnumToStringFromString CertificateActionEnumTypeHelper = {{CertificateActionEnumType::Install, "Install"}, + {CertificateActionEnumType::Update, "Update"}}; + +/** @brief Helper to convert a enum class Iso15118EVCertificateStatusEnumType enum to string */ +const EnumToStringFromString Iso15118EVCertificateStatusEnumTypeHelper = { + {Iso15118EVCertificateStatusEnumType::Accepted, "Accepted"}, {Iso15118EVCertificateStatusEnumType::Failed, "Failed"}}; + +} // namespace types +namespace messages +{ + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Get15118EVCertificateReqConverter::fromJson(const rapidjson::Value& json, + Get15118EVCertificateReq& data, + std::string& error_code, + std::string& error_message) +{ + (void)error_code; + (void)error_message; + extract(json, "iso15118SchemaVersion", data.iso15118SchemaVersion); + data.action = CertificateActionEnumTypeHelper.fromString(json["action"].GetString()); + extract(json, "exiRequest", data.exiRequest); + return true; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Get15118EVCertificateReqConverter::toJson(const Get15118EVCertificateReq& data, rapidjson::Document& json) +{ + fill(json, "iso15118SchemaVersion", data.iso15118SchemaVersion); + fill(json, "action", CertificateActionEnumTypeHelper.toString(data.action)); + fill(json, "exiRequest", data.exiRequest); + return true; +} + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Get15118EVCertificateConfConverter::fromJson(const rapidjson::Value& json, + Get15118EVCertificateConf& data, + std::string& error_code, + std::string& error_message) +{ + (void)error_code; + (void)error_message; + data.status = Iso15118EVCertificateStatusEnumTypeHelper.fromString(json["status"].GetString()); + extract(json, "exiResponse", data.exiResponse); + return true; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Get15118EVCertificateConfConverter::toJson(const Get15118EVCertificateConf& data, rapidjson::Document& json) +{ + fill(json, "status", Iso15118EVCertificateStatusEnumTypeHelper.toString(data.status)); + fill(json, "exiResponse", data.exiResponse); + return true; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/Get15118EVCertificate.h b/src/messages/Get15118EVCertificate.h new file mode 100644 index 00000000..ccb82b67 --- /dev/null +++ b/src/messages/Get15118EVCertificate.h @@ -0,0 +1,62 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_GET15118EVCERTIFICATE_H +#define OPENOCPP_GET15118EVCERTIFICATE_H + +#include "CiStringType.h" +#include "Enums.h" +#include "IMessageConverter.h" + +namespace ocpp +{ +namespace messages +{ + +/** @brief Action corresponding to the Get15118EVCertificate messages */ +static const std::string GET_15118_EV_CERTIFICATE_ACTION = "Get15118EVCertificate"; + +/** @brief Get15118EVCertificate.req message */ +struct Get15118EVCertificateReq +{ + /** @brief Required. Schema version currently used for the 15118 session + between EV and Charge Point. Needed for parsing of the EXI + stream by the Central System */ + ocpp::types::CiStringType<50u> iso15118SchemaVersion; + /** @brief Required. Defines whether certificate needs to be installed or updated */ + ocpp::types::CertificateActionEnumType action; + /** @brief Required. Raw CertificateInstallationReq request from EV, Base64 encoded */ + ocpp::types::CiStringType<5600u> exiRequest; +}; + +/** @brief Get15118EVCertificate.conf message */ +struct Get15118EVCertificateConf +{ + /** @brief Required. Indicates whether the message was processed properly */ + ocpp::types::Iso15118EVCertificateStatusEnumType status; + /** @brief Required. Raw CertificateInstallationRes response for the EV, Base64 encoded */ + ocpp::types::CiStringType<5600u> exiResponse; +}; + +// Message converters +MESSAGE_CONVERTERS(Get15118EVCertificate) + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_GET15118EVCERTIFICATE_H diff --git a/src/messages/GetCertificateStatus.cpp b/src/messages/GetCertificateStatus.cpp new file mode 100644 index 00000000..608c44ec --- /dev/null +++ b/src/messages/GetCertificateStatus.cpp @@ -0,0 +1,87 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "GetCertificateStatus.h" +#include "IRpc.h" +#include "IdTokenInfoTypeConverter.h" +#include "OcspRequestDataTypeConverter.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace types +{ +/** @brief Helper to convert a GetCertificateStatusEnumType enum to string */ +const EnumToStringFromString GetCertificateStatusEnumTypeHelper = { + {GetCertificateStatusEnumType::Accepted, "Accepted"}, {GetCertificateStatusEnumType::Failed, "Failed"}}; +} // namespace types +namespace messages +{ + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool GetCertificateStatusReqConverter::fromJson(const rapidjson::Value& json, + GetCertificateStatusReq& data, + std::string& error_code, + std::string& error_message) +{ + const rapidjson::Value& ocspRequestData = json["ocspRequestData"]; + OcspRequestDataTypeConverter ocsp_request_converter; + bool ret = ocsp_request_converter.fromJson(ocspRequestData, data.ocspRequestData, error_code, error_message); + return ret; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool GetCertificateStatusReqConverter::toJson(const GetCertificateStatusReq& data, rapidjson::Document& json) +{ + OcspRequestDataTypeConverter ocsp_request_converter; + ocsp_request_converter.setAllocator(allocator); + + rapidjson::Document value; + value.Parse("{}"); + bool ret = ocsp_request_converter.toJson(data.ocspRequestData, value); + if (ret) + { + json.AddMember(rapidjson::StringRef("ocspRequestData"), value.Move(), *allocator); + } + return ret; +} + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool GetCertificateStatusConfConverter::fromJson(const rapidjson::Value& json, + GetCertificateStatusConf& data, + std::string& error_code, + std::string& error_message) +{ + (void)error_code; + (void)error_message; + data.status = GetCertificateStatusEnumTypeHelper.fromString(json["status"].GetString()); + extract(json, "ocspResult", data.ocspResult); + return true; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool GetCertificateStatusConfConverter::toJson(const GetCertificateStatusConf& data, rapidjson::Document& json) +{ + fill(json, "status", GetCertificateStatusEnumTypeHelper.toString(data.status)); + fill(json, "ocspResult", data.ocspResult); + return true; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/GetCertificateStatus.h b/src/messages/GetCertificateStatus.h new file mode 100644 index 00000000..80e67e13 --- /dev/null +++ b/src/messages/GetCertificateStatus.h @@ -0,0 +1,60 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_GETCERTIFICATESTATUS_H +#define OPENOCPP_GETCERTIFICATESTATUS_H + +#include "CiStringType.h" +#include "Enums.h" +#include "IMessageConverter.h" +#include "OcspRequestDataType.h" +#include "Optional.h" + +namespace ocpp +{ +namespace messages +{ + +/** @brief Action corresponding to the GetCertificateStatus messages */ +static const std::string GET_CERTIFICATE_STATUS_ACTION = "GetCertificateStatus"; + +/** @brief GetCertificateStatus.req message */ +struct GetCertificateStatusReq +{ + /** @brief Required. Indicates the certificate of which the status is requested */ + ocpp::types::OcspRequestDataType ocspRequestData; +}; + +/** @brief GetCertificateStatus.conf message */ +struct GetCertificateStatusConf +{ + /** @brief Required. This indicates whether the charging station was able to retrieve the OCSP certificate status */ + ocpp::types::GetCertificateStatusEnumType status; + /** @brief Optional. OCSPResponse class as defined in IETF RFC 6960. + DER encoded (as defined in IETF RFC 6960), and then base64 + encoded. MAY only be omitted when status is not Accepted */ + ocpp::types::Optional> ocspResult; +}; + +// Message converters +MESSAGE_CONVERTERS(GetCertificateStatus) + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_GETCERTIFICATESTATUS_H diff --git a/src/messages/Iso15118.h b/src/messages/Iso15118.h new file mode 100644 index 00000000..870c34d4 --- /dev/null +++ b/src/messages/Iso15118.h @@ -0,0 +1,35 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_ISO15118_H +#define OPENOCPP_ISO15118_H + +#include + +namespace ocpp +{ +namespace messages +{ + +/** @brief Vendor id for ISO 15118 PnC extensions messages */ +static const std::string ISO15118_VENDOR_ID = "org.openchargealliance.iso15118pnc"; + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_ISO15118_H diff --git a/src/messages/Iso15118Authorize.cpp b/src/messages/Iso15118Authorize.cpp new file mode 100644 index 00000000..1d0f7464 --- /dev/null +++ b/src/messages/Iso15118Authorize.cpp @@ -0,0 +1,124 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "Iso15118Authorize.h" +#include "IRpc.h" +#include "IdTokenInfoTypeConverter.h" +#include "OcspRequestDataTypeConverter.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace types +{ +/** @brief Helper to convert a AuthorizeCertificateStatusEnumType enum to string */ +const EnumToStringFromString AuthorizeCertificateStatusEnumTypeHelper = { + {AuthorizeCertificateStatusEnumType::Accepted, "Accepted"}, + {AuthorizeCertificateStatusEnumType::CertChainError, "CertChainError"}, + {AuthorizeCertificateStatusEnumType::CertificateExpired, "CertificateExpired"}, + {AuthorizeCertificateStatusEnumType::CertificateRevoked, "CertificateRevoked"}, + {AuthorizeCertificateStatusEnumType::ContractCancelled, "ContractCancelled"}, + {AuthorizeCertificateStatusEnumType::NoCertificateAvailable, "NoCertificateAvailable"}, + {AuthorizeCertificateStatusEnumType::SignatureError, "SignatureError"}}; +} // namespace types +namespace messages +{ + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Iso15118AuthorizeReqConverter::fromJson(const rapidjson::Value& json, + Iso15118AuthorizeReq& data, + std::string& error_code, + std::string& error_message) +{ + bool ret = true; + extract(json, "certificate", data.certificate); + extract(json, "idToken", data.idToken); + if (json.HasMember("iso15118CertificateHashData")) + { + const rapidjson::Value& certificateHashData = json["iso15118CertificateHashData"]; + OcspRequestDataTypeConverter certificate_hash_converter; + for (auto it_cert = certificateHashData.Begin(); ret && (it_cert != certificateHashData.End()); ++it_cert) + { + data.iso15118CertificateHashData.emplace_back(); + OcspRequestDataType& certificate_hash = data.iso15118CertificateHashData.back(); + ret = ret && certificate_hash_converter.fromJson(*it_cert, certificate_hash, error_code, error_message); + } + } + return ret; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Iso15118AuthorizeReqConverter::toJson(const Iso15118AuthorizeReq& data, rapidjson::Document& json) +{ + bool ret = true; + if (data.certificate.isSet()) + { + fill(json, "certificate", data.certificate); + } + fill(json, "idToken", data.idToken); + if (!data.iso15118CertificateHashData.empty()) + { + rapidjson::Value certificateHashData(rapidjson::kArrayType); + OcspRequestDataTypeConverter certificate_hash_converter; + certificate_hash_converter.setAllocator(allocator); + for (const OcspRequestDataType& certificate_hash : data.iso15118CertificateHashData) + { + rapidjson::Document value; + value.Parse("{}"); + ret = ret && certificate_hash_converter.toJson(certificate_hash, value); + certificateHashData.PushBack(value.Move(), *allocator); + } + json.AddMember(rapidjson::StringRef("iso15118CertificateHashData"), certificateHashData.Move(), *allocator); + } + return ret; +} + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Iso15118AuthorizeConfConverter::fromJson(const rapidjson::Value& json, + Iso15118AuthorizeConf& data, + std::string& error_code, + std::string& error_message) +{ + IdTokenInfoTypeConverter id_token_info_converter; + bool ret = id_token_info_converter.fromJson(json["idTokenInfo"], data.idTokenInfo, error_code, error_message); + if (json.HasMember("certificateStatus")) + { + data.certificateStatus = AuthorizeCertificateStatusEnumTypeHelper.fromString(json["certificateStatus"].GetString()); + } + return ret; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Iso15118AuthorizeConfConverter::toJson(const Iso15118AuthorizeConf& data, rapidjson::Document& json) +{ + IdTokenInfoTypeConverter id_token_info_converter; + id_token_info_converter.setAllocator(allocator); + rapidjson::Document id_token_info; + id_token_info.Parse("{}"); + bool ret = id_token_info_converter.toJson(data.idTokenInfo, id_token_info); + json.AddMember(rapidjson::StringRef("idTokenInfo"), id_token_info.Move(), *allocator); + if (data.certificateStatus.isSet()) + { + fill(json, "certificateStatus", AuthorizeCertificateStatusEnumTypeHelper.toString(data.certificateStatus)); + } + return ret; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/Iso15118Authorize.h b/src/messages/Iso15118Authorize.h new file mode 100644 index 00000000..151a7431 --- /dev/null +++ b/src/messages/Iso15118Authorize.h @@ -0,0 +1,67 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_ISO15118AUTHORIZE_H +#define OPENOCPP_ISO15118AUTHORIZE_H + +#include "IMessageConverter.h" +#include "IdToken.h" +#include "IdTokenInfoType.h" +#include "OcspRequestDataType.h" +#include "Optional.h" + +#include + +namespace ocpp +{ +namespace messages +{ + +/** @brief Action corresponding to the Iso15118Authorize messages */ +static const std::string ISO15118_AUTHORIZE_ACTION = "Authorize"; + +/** @brief Iso15118Authorize.req message */ +struct Iso15118AuthorizeReq +{ + /** @brief Optional. The X.509 certificated presented by EV and encoded in PEM format */ + ocpp::types::Optional> certificate; + /** @brief Required. This contains the identifier that needs to be authorized */ + ocpp::types::IdToken idToken; + /** @brief Optional. Contains the information needed to verify the EV Contract Certificate via OCSP */ + std::vector iso15118CertificateHashData; +}; + +/** @brief Iso15118Authorize.conf message */ +struct Iso15118AuthorizeConf +{ + /** @brief Optional. Certificate status information. - if all certificates are + valid: return 'Accepted'. - if one of the certificates was revoked, + return 'CertificateRevoked' */ + ocpp::types::Optional certificateStatus; + /** @brief Required. This contains information about authorization status, + expiry and group id */ + ocpp::types::IdTokenInfoType idTokenInfo; +}; + +// Message converters +MESSAGE_CONVERTERS(Iso15118Authorize) + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_ISO15118AUTHORIZE_H diff --git a/src/messages/Iso15118GetInstalledCertificateIds.cpp b/src/messages/Iso15118GetInstalledCertificateIds.cpp new file mode 100644 index 00000000..98d08139 --- /dev/null +++ b/src/messages/Iso15118GetInstalledCertificateIds.cpp @@ -0,0 +1,110 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "Iso15118GetInstalledCertificateIds.h" +#include "CertificateHashDataChainTypeConverter.h" +#include "IRpc.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace messages +{ + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Iso15118GetInstalledCertificateIdsReqConverter::fromJson(const rapidjson::Value& json, + Iso15118GetInstalledCertificateIdsReq& data, + std::string& error_code, + std::string& error_message) +{ + (void)error_code; + (void)error_message; + if (json.HasMember("certificateType")) + { + const rapidjson::Value& certificateType = json["certificateType"]; + for (auto it_cert = certificateType.Begin(); it_cert != certificateType.End(); ++it_cert) + { + data.certificateType.push_back(GetCertificateIdUseEnumTypeHelper.fromString(it_cert->GetString())); + } + } + return true; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Iso15118GetInstalledCertificateIdsReqConverter::toJson(const Iso15118GetInstalledCertificateIdsReq& data, rapidjson::Document& json) +{ + if (!data.certificateType.empty()) + { + rapidjson::Value certificateType(rapidjson::kArrayType); + for (const GetCertificateIdUseEnumType& cert_type : data.certificateType) + { + rapidjson::Value value(GetCertificateIdUseEnumTypeHelper.toString(cert_type).c_str(), *allocator); + certificateType.PushBack(value.Move(), *allocator); + } + json.AddMember(rapidjson::StringRef("certificateType"), certificateType.Move(), *allocator); + } + return true; +} + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Iso15118GetInstalledCertificateIdsConfConverter::fromJson(const rapidjson::Value& json, + Iso15118GetInstalledCertificateIdsConf& data, + std::string& error_code, + std::string& error_message) +{ + bool ret = true; + data.status = GetInstalledCertificateStatusEnumTypeHelper.fromString(json["status"].GetString()); + if (json.HasMember("certificateHashDataChain")) + { + const rapidjson::Value& certificateHashDataChain = json["certificateHashDataChain"]; + CertificateHashDataChainTypeConverter certificate_hash_converter; + for (auto it_cert = certificateHashDataChain.Begin(); ret && (it_cert != certificateHashDataChain.End()); ++it_cert) + { + data.certificateHashDataChain.emplace_back(); + CertificateHashDataChainType& certificate_hash = data.certificateHashDataChain.back(); + ret = ret && certificate_hash_converter.fromJson(*it_cert, certificate_hash, error_code, error_message); + } + } + return ret; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Iso15118GetInstalledCertificateIdsConfConverter::toJson(const Iso15118GetInstalledCertificateIdsConf& data, rapidjson::Document& json) +{ + bool ret = true; + fill(json, "status", GetInstalledCertificateStatusEnumTypeHelper.toString(data.status)); + if (!data.certificateHashDataChain.empty()) + { + rapidjson::Value certificateHashDataChain(rapidjson::kArrayType); + CertificateHashDataChainTypeConverter certificate_hash_converter; + certificate_hash_converter.setAllocator(allocator); + for (const CertificateHashDataChainType& certificate_hash : data.certificateHashDataChain) + { + rapidjson::Document value; + value.Parse("{}"); + ret = ret && certificate_hash_converter.toJson(certificate_hash, value); + certificateHashDataChain.PushBack(value.Move(), *allocator); + } + json.AddMember(rapidjson::StringRef("certificateHashDataChain"), certificateHashDataChain.Move(), *allocator); + } + return ret; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/Iso15118GetInstalledCertificateIds.h b/src/messages/Iso15118GetInstalledCertificateIds.h new file mode 100644 index 00000000..3e1d3168 --- /dev/null +++ b/src/messages/Iso15118GetInstalledCertificateIds.h @@ -0,0 +1,59 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_ISO15118GETINSTALLEDCERTIFICATEIDS_H +#define OPENOCPP_ISO15118GETINSTALLEDCERTIFICATEIDS_H + +#include "CertificateHashDataChainType.h" +#include "Enums.h" +#include "IMessageConverter.h" + +#include + +namespace ocpp +{ +namespace messages +{ + +/** @brief Action corresponding to the Iso15118GetInstalledCertificateIds messages */ +static const std::string ISO15118_GET_INSTALLED_CERTIFICATE_IDS_ACTION = "GetInstalledCertificateIds"; + +/** @brief Iso15118GetInstalledCertificateIds.req message */ +struct Iso15118GetInstalledCertificateIdsReq +{ + /** @brief Optional. Indicates the type of certificates requested. When omitted, all certificate types are requested */ + std::vector certificateType; +}; + +/** @brief Iso15118GetInstalledCertificateIds.conf message */ +struct Iso15118GetInstalledCertificateIdsConf +{ + /** @brief Required. Charge Point indicates if it can process the request */ + ocpp::types::GetInstalledCertificateStatusEnumType status; + /** @brief Optional. The Charge Point includes the Certificate information + for each available certificate */ + std::vector certificateHashDataChain; +}; + +// Message converters +MESSAGE_CONVERTERS(Iso15118GetInstalledCertificateIds) + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_ISO15118GETINSTALLEDCERTIFICATEIDS_H diff --git a/src/messages/Iso15118InstallCertificate.cpp b/src/messages/Iso15118InstallCertificate.cpp new file mode 100644 index 00000000..200140d6 --- /dev/null +++ b/src/messages/Iso15118InstallCertificate.cpp @@ -0,0 +1,85 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "Iso15118InstallCertificate.h" +#include "IRpc.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace types +{ + +/** @brief Helper to convert a enum class InstallCertificateUseEnumType enum to string */ +const EnumToStringFromString InstallCertificateUseEnumTypeHelper = { + {InstallCertificateUseEnumType::MORootCertificate, "MORootCertificate"}, + {InstallCertificateUseEnumType::V2GRootCertificate, "V2GRootCertificate"}}; + +/** @brief Helper to convert a enum class InstallCertificateStatusEnumType enum to string */ +const EnumToStringFromString InstallCertificateStatusEnumTypeHelper = { + {InstallCertificateStatusEnumType::Accepted, "Accepted"}, + {InstallCertificateStatusEnumType::Failed, "Failed"}, + {InstallCertificateStatusEnumType::Rejected, "Rejected"}}; + +} // namespace types +namespace messages +{ + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Iso15118InstallCertificateReqConverter::fromJson(const rapidjson::Value& json, + Iso15118InstallCertificateReq& data, + std::string& error_code, + std::string& error_message) +{ + (void)error_code; + (void)error_message; + data.certificateType = InstallCertificateUseEnumTypeHelper.fromString(json["certificateType"].GetString()); + extract(json, "certificate", data.certificate); + return true; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Iso15118InstallCertificateReqConverter::toJson(const Iso15118InstallCertificateReq& data, rapidjson::Document& json) +{ + fill(json, "certificateType", InstallCertificateUseEnumTypeHelper.toString(data.certificateType)); + fill(json, "certificate", data.certificate); + return true; +} + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Iso15118InstallCertificateConfConverter::fromJson(const rapidjson::Value& json, + Iso15118InstallCertificateConf& data, + std::string& error_code, + std::string& error_message) +{ + (void)error_code; + (void)error_message; + data.status = InstallCertificateStatusEnumTypeHelper.fromString(json["status"].GetString()); + return true; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Iso15118InstallCertificateConfConverter::toJson(const Iso15118InstallCertificateConf& data, rapidjson::Document& json) +{ + fill(json, "status", InstallCertificateStatusEnumTypeHelper.toString(data.status)); + return true; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/Iso15118InstallCertificate.h b/src/messages/Iso15118InstallCertificate.h new file mode 100644 index 00000000..7a76f5df --- /dev/null +++ b/src/messages/Iso15118InstallCertificate.h @@ -0,0 +1,58 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_ISO15118INSTALLCERTIFICATE_H +#define OPENOCPP_ISO15118INSTALLCERTIFICATE_H + +#include "CiStringType.h" +#include "Enums.h" +#include "IMessageConverter.h" + +#include + +namespace ocpp +{ +namespace messages +{ + +/** @brief Action corresponding to the Iso15118InstallCertificate messages */ +static const std::string ISO15118_INSTALL_CERTIFICATE_ACTION = "InstallCertificate"; + +/** @brief Iso15118InstallCertificate.req message */ +struct Iso15118InstallCertificateReq +{ + /** @brief Required. Indicates the certificate type that is sent */ + ocpp::types::InstallCertificateUseEnumType certificateType; + /** @brief Required. An PEM encoded X.509 certificate */ + ocpp::types::CiStringType<5500u> certificate; +}; + +/** @brief Iso15118InstallCertificate.conf message */ +struct Iso15118InstallCertificateConf +{ + /** @brief Required. Charge Point indicates if installation was successful */ + ocpp::types::InstallCertificateStatusEnumType status; +}; + +// Message converters +MESSAGE_CONVERTERS(Iso15118InstallCertificate) + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_ISO15118INSTALLCERTIFICATE_H diff --git a/src/messages/Iso15118TriggerMessage.cpp b/src/messages/Iso15118TriggerMessage.cpp new file mode 100644 index 00000000..c65b4377 --- /dev/null +++ b/src/messages/Iso15118TriggerMessage.cpp @@ -0,0 +1,70 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "Iso15118TriggerMessage.h" +#include "IRpc.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace messages +{ + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Iso15118TriggerMessageReqConverter::fromJson(const rapidjson::Value& json, + Iso15118TriggerMessageReq& data, + std::string& error_code, + std::string& error_message) +{ + (void)json; + (void)data; + (void)error_code; + (void)error_message; + return true; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Iso15118TriggerMessageReqConverter::toJson(const Iso15118TriggerMessageReq& data, rapidjson::Document& json) +{ + (void)json; + (void)data; + return true; +} + +/** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, DataType&, std::string&, std::string&) */ +bool Iso15118TriggerMessageConfConverter::fromJson(const rapidjson::Value& json, + Iso15118TriggerMessageConf& data, + std::string& error_code, + std::string& error_message) +{ + (void)error_code; + (void)error_message; + data.status = TriggerMessageStatusEnumTypeHelper.fromString(json["status"].GetString()); + return true; +} + +/** @copydoc bool IMessageConverter::toJson(DataType&, rapidjson::Document&, std::string&, std::string&) */ +bool Iso15118TriggerMessageConfConverter::toJson(const Iso15118TriggerMessageConf& data, rapidjson::Document& json) +{ + fill(json, "status", TriggerMessageStatusEnumTypeHelper.toString(data.status)); + return true; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/Iso15118TriggerMessage.h b/src/messages/Iso15118TriggerMessage.h new file mode 100644 index 00000000..343675a2 --- /dev/null +++ b/src/messages/Iso15118TriggerMessage.h @@ -0,0 +1,53 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_ISO15118TRIGGERMESSAGE_H +#define OPENOCPP_ISO15118TRIGGERMESSAGE_H + +#include "Enums.h" +#include "IMessageConverter.h" + +namespace ocpp +{ +namespace messages +{ + +/** @brief Action corresponding to the Iso15118TriggerMessage messages */ +static const std::string ISO15118_TRIGGER_MESSAGE_ACTION = "TriggerMessage"; + +/** @brief Iso15118TriggerMessage.req message */ +struct Iso15118TriggerMessageReq +{ + // No fields are defined +}; + +/** @brief Iso15118TriggerMessage.conf message */ +struct Iso15118TriggerMessageConf +{ + /** @brief Required. Indicates whether the Charge Point will send the requested + notification or not */ + ocpp::types::TriggerMessageStatusEnumType status; +}; + +// Message converters +MESSAGE_CONVERTERS(Iso15118TriggerMessage) + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_ISO15118TRIGGERMESSAGE_H diff --git a/src/messages/MessagesConverter.cpp b/src/messages/MessagesConverter.cpp index e168af93..22c5d001 100644 --- a/src/messages/MessagesConverter.cpp +++ b/src/messages/MessagesConverter.cpp @@ -31,6 +31,8 @@ along with OpenOCPP. If not, see . #include "DiagnosticsStatusNotification.h" #include "ExtendedTriggerMessage.h" #include "FirmwareStatusNotification.h" +#include "Get15118EVCertificate.h" +#include "GetCertificateStatus.h" #include "GetCompositeSchedule.h" #include "GetConfiguration.h" #include "GetDiagnostics.h" @@ -39,6 +41,10 @@ along with OpenOCPP. If not, see . #include "GetLog.h" #include "Heartbeat.h" #include "InstallCertificate.h" +#include "Iso15118Authorize.h" +#include "Iso15118GetInstalledCertificateIds.h" +#include "Iso15118InstallCertificate.h" +#include "Iso15118TriggerMessage.h" #include "LogStatusNotification.h" #include "MeterValues.h" #include "RemoteStartTransaction.h" @@ -89,6 +95,8 @@ MessagesConverter::MessagesConverter() REGISTER_CONVERTER(DiagnosticsStatusNotification); REGISTER_CONVERTER(ExtendedTriggerMessage); REGISTER_CONVERTER(FirmwareStatusNotification); + REGISTER_CONVERTER(Get15118EVCertificate); + REGISTER_CONVERTER(GetCertificateStatus); REGISTER_CONVERTER(GetCompositeSchedule); REGISTER_CONVERTER(GetConfiguration); REGISTER_CONVERTER(GetDiagnostics); @@ -97,6 +105,10 @@ MessagesConverter::MessagesConverter() REGISTER_CONVERTER(GetLog); REGISTER_CONVERTER(Heartbeat); REGISTER_CONVERTER(InstallCertificate); + REGISTER_CONVERTER(Iso15118Authorize); + REGISTER_CONVERTER(Iso15118GetInstalledCertificateIds); + REGISTER_CONVERTER(Iso15118InstallCertificate); + REGISTER_CONVERTER(Iso15118TriggerMessage); REGISTER_CONVERTER(LogStatusNotification); REGISTER_CONVERTER(MeterValues); REGISTER_CONVERTER(RemoteStartTransaction); @@ -133,6 +145,8 @@ MessagesConverter::~MessagesConverter() DELETE_CONVERTER(DiagnosticsStatusNotification); DELETE_CONVERTER(ExtendedTriggerMessage); DELETE_CONVERTER(FirmwareStatusNotification); + DELETE_CONVERTER(Get15118EVCertificate); + DELETE_CONVERTER(GetCertificateStatus); DELETE_CONVERTER(GetCompositeSchedule); DELETE_CONVERTER(GetConfiguration); DELETE_CONVERTER(GetDiagnostics); @@ -141,6 +155,10 @@ MessagesConverter::~MessagesConverter() DELETE_CONVERTER(GetLog); DELETE_CONVERTER(Heartbeat); DELETE_CONVERTER(InstallCertificate); + DELETE_CONVERTER(Iso15118Authorize); + DELETE_CONVERTER(Iso15118GetInstalledCertificateIds); + DELETE_CONVERTER(Iso15118InstallCertificate); + DELETE_CONVERTER(Iso15118TriggerMessage); DELETE_CONVERTER(LogStatusNotification); DELETE_CONVERTER(MeterValues); DELETE_CONVERTER(RemoteStartTransaction); diff --git a/src/messages/types/CertificateHashDataChainTypeConverter.cpp b/src/messages/types/CertificateHashDataChainTypeConverter.cpp new file mode 100644 index 00000000..15f4fbf5 --- /dev/null +++ b/src/messages/types/CertificateHashDataChainTypeConverter.cpp @@ -0,0 +1,102 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "CertificateHashDataChainTypeConverter.h" +#include "CertificateHashDataTypeConverter.h" +#include "Enums.h" +#include "IRpc.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace types +{ + +/** @brief Helper to convert a enum class GetCertificateIdUseEnumType enum to string */ +const EnumToStringFromString GetCertificateIdUseEnumTypeHelper = { + {GetCertificateIdUseEnumType::MORootCertificate, "MORootCertificate"}, + {GetCertificateIdUseEnumType::V2GCertificateChain, "V2GCertificateChain"}, + {GetCertificateIdUseEnumType::V2GRootCertificate, "V2GRootCertificate"}}; + +} // namespace types + +namespace messages +{ + +/** @bcopydoc bool IMessageConverter::fromJson(const rapidjson::Value&, + * ocpp::types::CertificateHashDataChainType&, + * std::string&, + * std::string&) */ +bool CertificateHashDataChainTypeConverter::fromJson(const rapidjson::Value& json, + ocpp::types::CertificateHashDataChainType& data, + std::string& error_code, + std::string& error_message) +{ + CertificateHashDataTypeConverter certificate_hash_data_type_converter; + + data.certificateType = GetCertificateIdUseEnumTypeHelper.fromString(json["certificateType"].GetString()); + bool ret = + certificate_hash_data_type_converter.fromJson(json["certificateHashData"], data.certificateHashData, error_code, error_message); + if (ret) + { + std::vector& child_certificates = data.childCertificateHashData; + const rapidjson::Value& childCertificateHashData = json["childCertificateHashData"]; + for (auto it_cert = childCertificateHashData.Begin(); ret && (it_cert != childCertificateHashData.End()); ++it_cert) + { + child_certificates.emplace_back(); + CertificateHashDataType& child_certificate = child_certificates.back(); + ret = ret && certificate_hash_data_type_converter.fromJson(*it_cert, child_certificate, error_code, error_message); + } + } + return ret; +} + +/** @copydoc bool IMessageConverter::toJson(const ocpp::types::CertificateHashDataChainType&, + * rapidjson::Document&) */ +bool CertificateHashDataChainTypeConverter::toJson(const ocpp::types::CertificateHashDataChainType& data, rapidjson::Document& json) +{ + fill(json, "certificateType", GetCertificateIdUseEnumTypeHelper.toString(data.certificateType)); + + CertificateHashDataTypeConverter certificate_hash_data_type_converter; + certificate_hash_data_type_converter.setAllocator(allocator); + rapidjson::Document hash_data; + hash_data.Parse("{}"); + bool ret = certificate_hash_data_type_converter.toJson(data.certificateHashData, hash_data); + if (ret) + { + json.AddMember(rapidjson::StringRef("certificateHashData"), hash_data.Move(), *allocator); + + rapidjson::Value childCertificateHashData(rapidjson::kArrayType); + for (const auto& child_certificate : data.childCertificateHashData) + { + rapidjson::Document value; + value.Parse("{}"); + ret = ret && certificate_hash_data_type_converter.toJson(child_certificate, value); + if (ret) + { + childCertificateHashData.PushBack(value.Move(), *allocator); + } + } + json.AddMember(rapidjson::StringRef("childCertificateHashData"), childCertificateHashData.Move(), *allocator); + } + return ret; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/types/CertificateHashDataChainTypeConverter.h b/src/messages/types/CertificateHashDataChainTypeConverter.h new file mode 100644 index 00000000..75a26a15 --- /dev/null +++ b/src/messages/types/CertificateHashDataChainTypeConverter.h @@ -0,0 +1,51 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_CERTIFICATEHASHDATACHAINTYPECONVERTER_H +#define OPENOCPP_CERTIFICATEHASHDATACHAINTYPECONVERTER_H + +#include "CertificateHashDataChainType.h" +#include "IMessageConverter.h" + +namespace ocpp +{ +namespace messages +{ + +/** @brief Converter class for CertificateHashDataChainType type */ +class CertificateHashDataChainTypeConverter : public IMessageConverter +{ + public: + /** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, + * ocpp::types::CertificateHashDataChainType&, + * std::string&, + * std::string&) */ + bool fromJson(const rapidjson::Value& json, + ocpp::types::CertificateHashDataChainType& data, + std::string& error_code, + std::string& error_message) override; + + /** @copydoc bool IMessageConverter::toJson(const ocpp::types::CertificateHashDataChainType&, + * rapidjson::Document&) */ + bool toJson(const ocpp::types::CertificateHashDataChainType& data, rapidjson::Document& json) override; +}; + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_CERTIFICATEHASHDATACHAINTYPECONVERTER_H diff --git a/src/messages/types/IdTokenInfoTypeConverter.cpp b/src/messages/types/IdTokenInfoTypeConverter.cpp new file mode 100644 index 00000000..a26866a5 --- /dev/null +++ b/src/messages/types/IdTokenInfoTypeConverter.cpp @@ -0,0 +1,61 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "IdTokenInfoTypeConverter.h" +#include "Enums.h" +#include "IRpc.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace messages +{ + +/** @bcopydoc bool IMessageConverter::fromJson(const rapidjson::Value&, + * ocpp::types::IdTokenInfoType&, + * std::string&, + * std::string&) */ +bool IdTokenInfoTypeConverter::fromJson(const rapidjson::Value& json, + ocpp::types::IdTokenInfoType& data, + std::string& error_code, + std::string& error_message) +{ + data.status = AuthorizationStatusHelper.fromString(json["status"].GetString()); + bool ret = extract(json, "cacheExpiryDateTime", data.cacheExpiryDateTime, error_message); + if (!ret) + { + error_code = ocpp::rpc::IRpc::RPC_ERROR_TYPE_CONSTRAINT_VIOLATION; + } + return ret; +} + +/** @copydoc bool IMessageConverter::toJson(const ocpp::types::IdTokenInfoType&, + * rapidjson::Document&) */ +bool IdTokenInfoTypeConverter::toJson(const ocpp::types::IdTokenInfoType& data, rapidjson::Document& json) +{ + fill(json, "status", AuthorizationStatusHelper.toString(data.status)); + if (data.cacheExpiryDateTime.isSet()) + { + fill(json, "cacheExpiryDateTime", data.cacheExpiryDateTime); + } + return true; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/types/IdTokenInfoTypeConverter.h b/src/messages/types/IdTokenInfoTypeConverter.h new file mode 100644 index 00000000..55d31ef9 --- /dev/null +++ b/src/messages/types/IdTokenInfoTypeConverter.h @@ -0,0 +1,51 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_IDTOKENINFOTYPECONVERTER_H +#define OPENOCPP_IDTOKENINFOTYPECONVERTER_H + +#include "IMessageConverter.h" +#include "IdTokenInfoType.h" + +namespace ocpp +{ +namespace messages +{ + +/** @brief Converter class for IdTokenInfoType type */ +class IdTokenInfoTypeConverter : public IMessageConverter +{ + public: + /** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, + * ocpp::types::IdTokenInfoType&, + * std::string&, + * std::string&) */ + bool fromJson(const rapidjson::Value& json, + ocpp::types::IdTokenInfoType& data, + std::string& error_code, + std::string& error_message) override; + + /** @copydoc bool IMessageConverter::toJson(const ocpp::types::IdTokenInfoType&, + * rapidjson::Document&) */ + bool toJson(const ocpp::types::IdTokenInfoType& data, rapidjson::Document& json) override; +}; + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_IDTOKENINFOTYPECONVERTER_H diff --git a/src/messages/types/OcspRequestDataTypeConverter.cpp b/src/messages/types/OcspRequestDataTypeConverter.cpp new file mode 100644 index 00000000..331260e4 --- /dev/null +++ b/src/messages/types/OcspRequestDataTypeConverter.cpp @@ -0,0 +1,62 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#include "OcspRequestDataTypeConverter.h" +#include "Enums.h" +#include "IRpc.h" + +using namespace ocpp::types; + +namespace ocpp +{ +namespace messages +{ + +/** @bcopydoc bool IMessageConverter::fromJson(const rapidjson::Value&, + * ocpp::types::OcspRequestDataType&, + * std::string&, + * std::string&) */ +bool OcspRequestDataTypeConverter::fromJson(const rapidjson::Value& json, + ocpp::types::OcspRequestDataType& data, + std::string& error_code, + std::string& error_message) +{ + (void)error_code; + (void)error_message; + data.hashAlgorithm = HashAlgorithmEnumTypeHelper.fromString(json["hashAlgorithm"].GetString()); + extract(json, "issuerKeyHash", data.issuerKeyHash); + extract(json, "issuerNameHash", data.issuerNameHash); + extract(json, "serialNumber", data.serialNumber); + extract(json, "responderURL", data.responderURL); + return true; +} + +/** @copydoc bool IMessageConverter::toJson(const ocpp::types::OcspRequestDataType&, + * rapidjson::Document&) */ +bool OcspRequestDataTypeConverter::toJson(const ocpp::types::OcspRequestDataType& data, rapidjson::Document& json) +{ + fill(json, "hashAlgorithm", HashAlgorithmEnumTypeHelper.toString(data.hashAlgorithm)); + fill(json, "issuerKeyHash", data.issuerKeyHash); + fill(json, "issuerNameHash", data.issuerNameHash); + fill(json, "serialNumber", data.serialNumber); + fill(json, "responderURL", data.responderURL); + return true; +} + +} // namespace messages +} // namespace ocpp diff --git a/src/messages/types/OcspRequestDataTypeConverter.h b/src/messages/types/OcspRequestDataTypeConverter.h new file mode 100644 index 00000000..4f75f42c --- /dev/null +++ b/src/messages/types/OcspRequestDataTypeConverter.h @@ -0,0 +1,51 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_OCSPREQUESTDATATYPECONVERTER_H +#define OPENOCPP_OCSPREQUESTDATATYPECONVERTER_H + +#include "IMessageConverter.h" +#include "OcspRequestDataType.h" + +namespace ocpp +{ +namespace messages +{ + +/** @brief Converter class for OcspRequestDataType type */ +class OcspRequestDataTypeConverter : public IMessageConverter +{ + public: + /** @copydoc bool IMessageConverter::fromJson(const rapidjson::Value&, + * ocpp::types::OcspRequestDataType&, + * std::string&, + * std::string&) */ + bool fromJson(const rapidjson::Value& json, + ocpp::types::OcspRequestDataType& data, + std::string& error_code, + std::string& error_message) override; + + /** @copydoc bool IMessageConverter::toJson(const ocpp::types::OcspRequestDataType&, + * rapidjson::Document&) */ + bool toJson(const ocpp::types::OcspRequestDataType& data, rapidjson::Document& json) override; +}; + +} // namespace messages +} // namespace ocpp + +#endif // OPENOCPP_OCSPREQUESTDATATYPECONVERTER_H diff --git a/src/types/CertificateHashDataChainType.h b/src/types/CertificateHashDataChainType.h new file mode 100644 index 00000000..d45c7981 --- /dev/null +++ b/src/types/CertificateHashDataChainType.h @@ -0,0 +1,46 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_CERTIFICATEHASHDATACHAINTYPE_H +#define OPENOCPP_CERTIFICATEHASHDATACHAINTYPE_H + +#include "CertificateHashDataType.h" +#include "Enums.h" + +#include + +namespace ocpp +{ +namespace types +{ + +/** @brief CertificateHashDataChainType is used by: GetInstalledCertificateIds.conf */ +struct CertificateHashDataChainType +{ + /** @brief Required. Indicates the type of the requested certificate(s) */ + GetCertificateIdUseEnumType certificateType; + /** @brief Required. Information to identify a certificate */ + CertificateHashDataType certificateHashData; + /** @brief Optional. Information to identify the child certificate(s) */ + std::vector childCertificateHashData; +}; + +} // namespace types +} // namespace ocpp + +#endif // OPENOCPP_CERTIFICATEHASHDATACHAINTYPE_H diff --git a/src/types/CiStringType.h b/src/types/CiStringType.h index a4f31a53..3155df92 100644 --- a/src/types/CiStringType.h +++ b/src/types/CiStringType.h @@ -38,6 +38,7 @@ class ICiStringType * @return Size limit in bytes of the string */ virtual size_t max() const = 0; + /** * @brief Assign a new value to the string * @param value New string value @@ -45,6 +46,13 @@ class ICiStringType */ virtual bool assign(const std::string& value) = 0; + /** + * @brief Assign a new value to the string + * @param value New string value + * @return true if the new value respects the max string size, false otherwise + */ + virtual bool assign(std::string&& value) = 0; + /** * @brief Implicit conversion operator * @return Underlying string @@ -104,6 +112,12 @@ class CiStringType : public ICiStringType */ CiStringType(const CiStringType& copy) : m_string(copy.m_string) { } + /** + * @brief Move constructor + * @param copy String to move + */ + CiStringType(CiStringType&& move) : m_string(std::move(move.m_string)) { } + /** @brief Destructor */ virtual ~CiStringType() { } @@ -133,6 +147,27 @@ class CiStringType : public ICiStringType return ret; } + /** + * @brief Assign a new value to the string + * @param value New string value + * @return true if the new value respects the max string size, false otherwise + */ + bool assign(std::string&& value) override + { + bool ret = false; + if (value.size() <= MAX_STRING_SIZE) + { + m_string.assign(value); + ret = true; + } + else + { + m_string.assign(value); + m_string.resize(MAX_STRING_SIZE); + } + return ret; + } + /** * @brief Copy operator * @param copy String to copy @@ -144,6 +179,17 @@ class CiStringType : public ICiStringType return *this; } + /** + * @brief Copy/move operator + * @param copy String to copy/move + * @return Reference to itself + */ + CiStringType& operator=(CiStringType&& copy) + { + m_string.assign(std::move(copy.m_string)); + return *this; + } + /** * @brief Implicit conversion operator * @return Underlying string diff --git a/src/types/Enums.h b/src/types/Enums.h index aa667bf1..a80c40ad 100644 --- a/src/types/Enums.h +++ b/src/types/Enums.h @@ -978,6 +978,110 @@ enum class UpdateFirmwareStatusEnumType /** @brief Helper to convert a UpdateFirmwareStatusEnumType enum to string */ extern const EnumToStringFromString UpdateFirmwareStatusEnumTypeHelper; +/** @brief Status of the EV Contract certificate */ +enum class AuthorizeCertificateStatusEnumType +{ + /** @brief Positive response */ + Accepted, + /** @brief Identifier has been blocked. Not allowed for charging */ + SignatureError, + /** @brief If the OEMProvisioningCert in the CertificateInstallationReq, the Contract Certificate in the CertificateUpdateReq, or the + ContractCertificate in the PaymentDetailsReq is expired */ + CertificateExpired, + /** @brief Used when the SECC or Central System matches the ContractCertificate contained in a CertificateUpdateReq or PaymentDetailsReq + with a CRL and the Contract Certificate is marked as revoked, OR when the SECC or Central System matches the OEM Provisioning + Certificate contained in a CertificateInstallationReq with a CRL and the OEM Provisioning Certificate is marked as revoked. + The revocation status can alternatively be obtained through an OCSP responder */ + CertificateRevoked, + /** @brief If the new certificate cannot be retrieved from secondary actor within the specified timeout */ + NoCertificateAvailable, + /** @brief If the ContractSignatureCertChain contained in the CertificateInstallationReq message is not valid */ + CertChainError, + /** @brief If the EMAID provided by EVCC during CertificateUpdateReq is not accepted by secondary actor */ + ContractCancelled +}; + +/** @brief Helper to convert a AuthorizeCertificateStatusEnumType enum to string */ +extern const EnumToStringFromString AuthorizeCertificateStatusEnumTypeHelper; + +/** @brief Action to apply on a certificate */ +enum class CertificateActionEnumType +{ + /** @brief Install the provided certificate */ + Install, + /** @brief Update the provided certificate */ + Update +}; + +/** @brief Helper to convert a CertificateActionEnumType enum to string */ +extern const EnumToStringFromString CertificateActionEnumTypeHelper; + +/** @brief Usage of a certificate */ +enum class GetCertificateIdUseEnumType +{ + /** @brief Use for certificate of the V2G Root */ + V2GRootCertificate, + /** @brief Use for certificate from an eMobility Service provider. To support PnC charging with contracts from service providers that not derived + their certificates from the V2G root */ + MORootCertificate, + /** @brief ISO 15118 V2G certificate chain (excluding the V2GRootCertificate) */ + V2GCertificateChain +}; + +/** @brief Helper to convert a GetCertificateIdUseEnumType enum to string */ +extern const EnumToStringFromString GetCertificateIdUseEnumTypeHelper; + +/** @brief Status of a certificate */ +enum class GetCertificateStatusEnumType +{ + /** @brief Successfully retrieved the OCSP certificate status */ + Accepted, + /** @brief Failed to retrieve the OCSP certificate status */ + Failed +}; + +/** @brief Helper to convert a GetCertificateStatusEnumType enum to string */ +extern const EnumToStringFromString GetCertificateStatusEnumTypeHelper; + +/** @brief Status of a certificate installation */ +enum class InstallCertificateStatusEnumType +{ + /** @brief The installation of the certificate succeeded */ + Accepted, + /** @brief The certificate is invalid and/or incorrect OR the CSO tries to install more certificates than allowed */ + Rejected, + /** @brief The certificate is valid and correct, but there is another reason the installation did not succeed */ + Failed +}; + +/** @brief Helper to convert a InstallCertificateStatusEnumType enum to string */ +extern const EnumToStringFromString InstallCertificateStatusEnumTypeHelper; + +/** @brief Usage of a certificate to install */ +enum class InstallCertificateUseEnumType +{ + /** @brief Use for certificate of the V2G Root, a V2G Charge Point Certificate MUST be derived from one of the installed V2GRootCertificate + certificates */ + V2GRootCertificate, + /** @brief Use for certificate from an eMobility Service */ + MORootCertificate +}; + +/** @brief Helper to convert a InstallCertificateUseEnumType enum to string */ +extern const EnumToStringFromString InstallCertificateUseEnumTypeHelper; + +/** @brief Iso15118EVCertificateStatusEnumType is used by: get15118EVCertificate:Get15118EVCertificate.conf */ +enum class Iso15118EVCertificateStatusEnumType +{ + /** @brief exiResponse included. This is no indication whether the update was successful, just that the message was processed properly */ + Accepted, + /** @brief Processing of the message was not successful, no exiResponse included */ + Failed +}; + +/** @brief Helper to convert a Iso15118EVCertificateStatusEnumType enum to string */ +extern const EnumToStringFromString Iso15118EVCertificateStatusEnumTypeHelper; + } // namespace types } // namespace ocpp diff --git a/src/types/IdTokenInfoType.h b/src/types/IdTokenInfoType.h new file mode 100644 index 00000000..1f6d1358 --- /dev/null +++ b/src/types/IdTokenInfoType.h @@ -0,0 +1,43 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_IDTOKENINFOTYPE_H +#define OPENOCPP_IDTOKENINFOTYPE_H + +#include "DateTime.h" +#include "Enums.h" +#include "Optional.h" + +namespace ocpp +{ +namespace types +{ + +/** @brief Contains status information about an identifier */ +struct IdTokenInfoType +{ + /** @brief Required. Current status of the ID Token */ + AuthorizationStatus status; + /** @brief Optional. Date and Time after which the token must be considered invalid */ + Optional cacheExpiryDateTime; +}; + +} // namespace types +} // namespace ocpp + +#endif // OPENOCPP_IDTOKENINFOTYPE_H diff --git a/src/types/OcspRequestDataType.h b/src/types/OcspRequestDataType.h new file mode 100644 index 00000000..a2f1797d --- /dev/null +++ b/src/types/OcspRequestDataType.h @@ -0,0 +1,48 @@ +/* +Copyright (c) 2020 Cedric Jimenez +This file is part of OpenOCPP. + +OpenOCPP is free software: you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +OpenOCPP is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with OpenOCPP. If not, see . +*/ + +#ifndef OPENOCPP_OCSPREQUESTDATATYPE_H +#define OPENOCPP_OCSPREQUESTDATATYPE_H + +#include "CiStringType.h" +#include "Enums.h" + +namespace ocpp +{ +namespace types +{ + +/** @brief OcspRequestDataType is used by: Authorize.req, GetCertificateStatus.req */ +struct OcspRequestDataType +{ + /** @brief Required. Used algorithms for the hashes provided */ + HashAlgorithmEnumType hashAlgorithm; + /** @brief Required. Hashed value of the Issuer DN (Distinguished Name) */ + CiStringType<128u> issuerNameHash; + /** @brief Required. Hashed value of the issuers public key */ + CiStringType<128u> issuerKeyHash; + /** @brief Required. The serial number of the certificate */ + CiStringType<40u> serialNumber; + /** @brief Required. This contains the responder URL (Case insensitive) */ + CiStringType<512u> responderURL; +}; + +} // namespace types +} // namespace ocpp + +#endif // OPENOCPP_OCSPREQUESTDATATYPE_H diff --git a/tests/deploy/main.cpp b/tests/deploy/main.cpp index c70b446e..467359f4 100644 --- a/tests/deploy/main.cpp +++ b/tests/deploy/main.cpp @@ -77,6 +77,12 @@ class CentralSystemConfig : public ICentralSystemConfig /** @brief Maximum number of entries in the log (0 = no logs in database) */ unsigned int logMaxEntriesCount() const override { return 1000; } + + // ISO 15118 PnC extensions + + /** @brief If this variable set to true, then the Central System supports ISO 15118 plug and charge messages via the DataTransfer mechanism as + described in this application note. */ + bool iso15118PnCEnabled() const override { return true; } }; /** @brief Dummy implementation of central system event handler */ @@ -463,6 +469,37 @@ class OcppConfig : public IOcppConfig /** @brief Comma separated list of supported file transfer protocols for upload AND download Allowed values : FTP, FTPS, HTTP, HTTPS, SFTP */ std::string supportedFileTransferProtocols() const override { return ""; } + + // + // ISO 15118 PnC extensions + // + + /** @brief If this variable exists and has the value true, then the Charge Point can provide a contract certificate that it cannot + validate to the Central System for validation as part of the Authorize.req */ + bool centralContractValidationAllowed() const override { return true; } + + /** @brief This configuration key defines how long the Charge Point has to wait (in seconds) before generating another CSR, in the case the + Central System accepts the SignCertificate.req, but never returns the signed certificate back. This value will be doubled after every + attempt. The amount of attempts is configured at CertSigningRepeatTimes. If the certificate signing process is slow, this setting + allows the Central System to tell the Charge Point to allow more time. + Negative values must be rejected. The value 0 means that the Charge Point does not generate another CSR (leaving it up to the + Central System to trigger another certificate installation). */ + std::chrono::seconds certSigningWaitMinimum() const override { return std::chrono::seconds(1); } + + /** @brief This configuration key can be used to configure the amount of times the Charge Point SHALL double the previous back-off time, + starting with the number of seconds configured at CertSigningWaitMinimum, every time the back-off time expires without having + received the CertificateSigned.req containing the signed certificate based on the CSR generated. When the maximum number of + increments is reached, the Charge Point SHALL stop resending the SignCertificate.req, until it is requested by the Central System + using a TriggerMessage.req. + Negative values must be rejected. The value 0 means that the Charge Point does not double the back-off time. */ + unsigned int certSigningRepeatTimes() const override { return 0; } + + /** @brief If this variable is true, then the Charge Point will try to validate a contract certificate when it is offline. */ + bool contractValidationOffline() const override { return true; } + + /** @brief If this variable set to true, then the Charge Point supports ISO 15118 plug and charge messages via the DataTransfer mechanism as + described in this application note. */ + bool iso15118PnCEnabled() const override { return true; } }; /** @brief Dummy implementation of charge point event handler */ @@ -804,6 +841,91 @@ class ChargePointEventsHandler : public IChargePointEventsHandler (void)signing_certificate; return UpdateFirmwareStatusEnumType::Rejected; } + + // ISO 15118 PnC extensions + + /** + * @brief Called to check an EV certificate againts the installed MO certificates + * @param certificate EV certificate to check + * @return true if the certificate has been validated against an installed MO certificate, false otherwise + */ + bool iso15118CheckEvCertificate(const ocpp::x509::Certificate& certificate) override + { + (void)certificate; + return true; + } + + /** + * @brief Called when an ISO15118 charge point certificate has been received and must be installed + * @param certificate Charge point certificate to install + * @return true if the certificate has been installed, false otherwise + */ + bool iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate& certificate) override + { + (void)certificate; + return true; + } + + /** + * @brief Called when the Central System request to delete an installed ISO15118 certificate + * @param hash_algorithm Hash algorithm used for the following parameters + * @param issuer_name_hash Hash of the certificate's issuer's name + * @param issuer_key_hash Hash of the certificate's public key + * @param serial_number Serial number of the certificate + * @return Deletion status (see DeleteCertificateStatusEnumType enum) + */ + ocpp::types::DeleteCertificateStatusEnumType iso15118DeleteCertificate(ocpp::types::HashAlgorithmEnumType hash_algorithm, + const std::string& issuer_name_hash, + const std::string& issuer_key_hash, + const std::string& serial_number) override + { + (void)hash_algorithm; + (void)issuer_name_hash; + (void)issuer_key_hash; + (void)serial_number; + return DeleteCertificateStatusEnumType::Accepted; + } + + /** + * @brief Called to get the list of installed ISO15118 certificates + * @param v2g_root_certificate Indicate if V2G root certificates must be listed + * @param mo_root_certificate Indicate if MO root certificates must be listed + * @param v2g_certificate_chain Indicate if V2G certificate chains must be listed + * @param certificates Installed certificates with their type + */ + void iso15118GetInstalledCertificates( + bool v2g_root_certificate, + bool mo_root_certificate, + bool v2g_certificate_chain, + std::vector>>& + certificates) override + { + (void)v2g_root_certificate; + (void)mo_root_certificate; + (void)v2g_certificate_chain; + (void)certificates; + } + + /** + * @brief Called when an ISO15118 certificate has been received and must be installed + * @param type Type of certificate + * @param certificate certificate to install + * @return Installation status (see InstallCertificateStatusEnumType enum) + */ + ocpp::types::InstallCertificateStatusEnumType iso15118CertificateReceived(ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate& certificate) override + { + (void)type; + (void)certificate; + return InstallCertificateStatusEnumType::Accepted; + } + + /** + * @brief Called to generate a CSR in PEM format which will be used by the Central System + * to generate and sign a certificate for the Charge Point for ISO15118 communications + * @param csr String to store the generated CSR in PEM format + */ + void iso15118GenerateCsr(std::string& csr) override { (void)csr; } }; /** @brief Entry point */ diff --git a/tests/stubs/ChargePointEventsHandlerStub.cpp b/tests/stubs/ChargePointEventsHandlerStub.cpp index a8cbc989..8ab510b6 100755 --- a/tests/stubs/ChargePointEventsHandlerStub.cpp +++ b/tests/stubs/ChargePointEventsHandlerStub.cpp @@ -292,6 +292,74 @@ ocpp::types::UpdateFirmwareStatusEnumType ChargePointEventsHandlerStub::checkFir return UpdateFirmwareStatusEnumType::Accepted; } +// ISO 15118 PnC extensions + +/** @copydoc bool IChargePointEventsHandler::iso15118CheckEvCertificate(const ocpp::x509::Certificate&) */ +bool ChargePointEventsHandlerStub::iso15118CheckEvCertificate(const ocpp::x509::Certificate& certificate) +{ + m_calls["iso15118CheckEvCertificate"] = {{"certificate", certificate.pem()}}; + return true; +} + +/** @copydoc bool IChargePointEventsHandler::iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate&) */ +bool ChargePointEventsHandlerStub::iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate& certificate) +{ + m_calls["iso15118ChargePointCertificateReceived"] = {{"certificate", certificate.pem()}}; + return true; +} + +/** @copydoc ocpp::types::DeleteCertificateStatusEnumType IChargePointEventsHandler::iso15118DeleteCertificate(ocpp::types::HashAlgorithmEnumType, + const std::string&, + const std::string&, + const std::string&) */ +ocpp::types::DeleteCertificateStatusEnumType ChargePointEventsHandlerStub::iso15118DeleteCertificate( + ocpp::types::HashAlgorithmEnumType hash_algorithm, + const std::string& issuer_name_hash, + const std::string& issuer_key_hash, + const std::string& serial_number) +{ + m_calls["iso15118ChargePointCertificateReceived"] = {{"hash_algorithm", HashAlgorithmEnumTypeHelper.toString(hash_algorithm)}, + {"issuer_name_hash", issuer_name_hash}, + {"issuer_key_hash", issuer_key_hash}, + {"serial_number", serial_number}}; + return DeleteCertificateStatusEnumType::Accepted; +} + +/** @copydoc void IChargePointEventsHandler::iso15118GetInstalledCertificates( + bool, + bool, + bool, + std::vector>>&) */ +void ChargePointEventsHandlerStub::iso15118GetInstalledCertificates( + bool v2g_root_certificate, + bool mo_root_certificate, + bool v2g_certificate_chain, + std::vector>>& + certificates) +{ + (void)certificates; + m_calls["iso15118GetInstalledCertificates"] = {{"v2g_root_certificate", std::to_string(v2g_root_certificate)}, + {"mo_root_certificate", std::to_string(mo_root_certificate)}, + {"v2g_certificate_chain", std::to_string(v2g_certificate_chain)}}; +} + +/** @copydoc ocpp::types::InstallCertificateStatusEnumType IChargePointEventsHandler::iso15118CertificateReceived( + ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate&) */ +ocpp::types::InstallCertificateStatusEnumType ChargePointEventsHandlerStub::iso15118CertificateReceived( + ocpp::types::InstallCertificateUseEnumType type, const ocpp::x509::Certificate& certificate) +{ + m_calls["iso15118CertificateReceived"] = {{"type", InstallCertificateUseEnumTypeHelper.toString(type)}, + {"certificate", certificate.pem()}}; + return InstallCertificateStatusEnumType::Accepted; +} + +/** @copydoc void IChargePointEventsHandler::iso15118GenerateCsr(std::string&) */ +void ChargePointEventsHandlerStub::iso15118GenerateCsr(std::string& csr) +{ + m_calls["iso15118GenerateCsr"] = {{"csr", csr}}; +} + /** @brief Indicate if a method has been called and returns the parameters used for the call */ bool ChargePointEventsHandlerStub::methodCalled(const std::string method_name, std::map& params) { diff --git a/tests/stubs/ChargePointEventsHandlerStub.h b/tests/stubs/ChargePointEventsHandlerStub.h index eb9ddc21..ff8e4ad4 100755 --- a/tests/stubs/ChargePointEventsHandlerStub.h +++ b/tests/stubs/ChargePointEventsHandlerStub.h @@ -149,6 +149,44 @@ class ChargePointEventsHandlerStub : public ocpp::chargepoint::IChargePointEvent * const ocpp::x509::Certificate&) */ ocpp::types::UpdateFirmwareStatusEnumType checkFirmwareSigningCertificate(const ocpp::x509::Certificate& signing_certificate) override; + // ISO 15118 PnC extensions + + /** @copydoc bool IChargePointEventsHandler::iso15118CheckEvCertificate(const ocpp::x509::Certificate&) */ + bool iso15118CheckEvCertificate(const ocpp::x509::Certificate& certificate) override; + + /** @copydoc bool IChargePointEventsHandler::iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate&) */ + bool iso15118ChargePointCertificateReceived(const ocpp::x509::Certificate& certificate) override; + + /** @copydoc ocpp::types::DeleteCertificateStatusEnumType IChargePointEventsHandler::iso15118DeleteCertificate(ocpp::types::HashAlgorithmEnumType, + const std::string&, + const std::string&, + const std::string&) */ + ocpp::types::DeleteCertificateStatusEnumType iso15118DeleteCertificate(ocpp::types::HashAlgorithmEnumType hash_algorithm, + const std::string& issuer_name_hash, + const std::string& issuer_key_hash, + const std::string& serial_number) override; + + /** @copydoc void IChargePointEventsHandler::iso15118GetInstalledCertificates( + bool, + bool, + bool, + std::vector>>&) */ + void iso15118GetInstalledCertificates( + bool v2g_root_certificate, + bool mo_root_certificate, + bool v2g_certificate_chain, + std::vector>>& + certificates) override; + + /** @copydoc ocpp::types::InstallCertificateStatusEnumType IChargePointEventsHandler::iso15118CertificateReceived( + ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate&) */ + ocpp::types::InstallCertificateStatusEnumType iso15118CertificateReceived(ocpp::types::InstallCertificateUseEnumType type, + const ocpp::x509::Certificate& certificate) override; + + /** @copydoc void IChargePointEventsHandler::iso15118GenerateCsr(std::string&) */ + void iso15118GenerateCsr(std::string& csr) override; + // API /** @brief Indicate if a method has been called and returns the parameters used for the call */ diff --git a/tests/stubs/OcppConfigStub.h b/tests/stubs/OcppConfigStub.h index 31a5c563..132247f0 100755 --- a/tests/stubs/OcppConfigStub.h +++ b/tests/stubs/OcppConfigStub.h @@ -278,6 +278,37 @@ class OcppConfigStub : public IOcppConfig Allowed values : FTP, FTPS, HTTP, HTTPS, SFTP */ std::string supportedFileTransferProtocols() const override { return getString("SupportedFileTransferProtocols"); } + // + // ISO 15118 PnC extensions + // + + /** @brief If this variable exists and has the value true, then the Charge Point can provide a contract certificate that it cannot + validate to the Central System for validation as part of the Authorize.req */ + bool centralContractValidationAllowed() const override { return getBool("CentralContractValidationAllowed"); } + + /** @brief This configuration key defines how long the Charge Point has to wait (in seconds) before generating another CSR, in the case the + Central System accepts the SignCertificate.req, but never returns the signed certificate back. This value will be doubled after every + attempt. The amount of attempts is configured at CertSigningRepeatTimes. If the certificate signing process is slow, this setting + allows the Central System to tell the Charge Point to allow more time. + Negative values must be rejected. The value 0 means that the Charge Point does not generate another CSR (leaving it up to the + Central System to trigger another certificate installation). */ + std::chrono::seconds certSigningWaitMinimum() const override { return get("CertSigningWaitMinimum"); } + + /** @brief This configuration key can be used to configure the amount of times the Charge Point SHALL double the previous back-off time, + starting with the number of seconds configured at CertSigningWaitMinimum, every time the back-off time expires without having + received the CertificateSigned.req containing the signed certificate based on the CSR generated. When the maximum number of + increments is reached, the Charge Point SHALL stop resending the SignCertificate.req, until it is requested by the Central System + using a TriggerMessage.req. + Negative values must be rejected. The value 0 means that the Charge Point does not double the back-off time. */ + unsigned int certSigningRepeatTimes() const override { return get("CertSigningRepeatTimes"); } + + /** @brief If this variable is true, then the Charge Point will try to validate a contract certificate when it is offline. */ + bool contractValidationOffline() const override { return getBool("ContractValidationOffline"); } + + /** @brief If this variable set to true, then the Charge Point supports ISO 15118 plug and charge messages via the DataTransfer mechanism as + described in this application note. */ + bool iso15118PnCEnabled() const override { return getBool("Iso15118PnCEnabled"); } + private: /** @brief Configuration */ std::map m_config;