diff --git a/CHANGELOG.md b/CHANGELOG.md index 453ff535..7d64aca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,14 @@ ## [Unreleased] +### Added + +- Operation GetInstalledCertificateIds, UC M03 ([#262](https://github.com/matth-x/MicroOcpp/pull/262)) +- Operation DeleteCertificate, UC M04 ([#262](https://github.com/matth-x/MicroOcpp/pull/262)) +- Operation InstallCertificate, UC M05 ([#262](https://github.com/matth-x/MicroOcpp/pull/262)) + +## [1.1.0] - 2024-02-27 + ### Fixed - Allow `nullptr` as parameter for `mocpp_set_console_out` ([#224](https://github.com/matth-x/MicroOcpp/issues/224)) diff --git a/CMakeLists.txt b/CMakeLists.txt index 95810e11..d5b66fee 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ cmake_minimum_required(VERSION 3.15) +set(CMAKE_CXX_STANDARD 11) set(MO_SRC src/MicroOcpp/Core/Configuration.cpp @@ -30,11 +31,13 @@ set(MO_SRC src/MicroOcpp/Operations/ClearChargingProfile.cpp src/MicroOcpp/Operations/CustomOperation.cpp src/MicroOcpp/Operations/DataTransfer.cpp + src/MicroOcpp/Operations/DeleteCertificate.cpp src/MicroOcpp/Operations/DiagnosticsStatusNotification.cpp src/MicroOcpp/Operations/FirmwareStatusNotification.cpp src/MicroOcpp/Operations/GetCompositeSchedule.cpp src/MicroOcpp/Operations/GetConfiguration.cpp src/MicroOcpp/Operations/GetDiagnostics.cpp + src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp src/MicroOcpp/Operations/GetLocalListVersion.cpp src/MicroOcpp/Operations/Heartbeat.cpp src/MicroOcpp/Operations/MeterValues.cpp @@ -48,6 +51,7 @@ set(MO_SRC src/MicroOcpp/Operations/StatusNotification.cpp src/MicroOcpp/Operations/StopTransaction.cpp src/MicroOcpp/Operations/TriggerMessage.cpp + src/MicroOcpp/Operations/InstallCertificate.cpp src/MicroOcpp/Operations/UnlockConnector.cpp src/MicroOcpp/Operations/UpdateFirmware.cpp src/MicroOcpp/Platform.cpp @@ -57,6 +61,10 @@ set(MO_SRC src/MicroOcpp/Model/Authorization/AuthorizationList.cpp src/MicroOcpp/Model/Authorization/AuthorizationService.cpp src/MicroOcpp/Model/Boot/BootService.cpp + src/MicroOcpp/Model/Certificates/Certificate.cpp + src/MicroOcpp/Model/Certificates/Certificate_c.cpp + src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp + src/MicroOcpp/Model/Certificates/CertificateService.cpp src/MicroOcpp/Model/ConnectorBase/ConnectorsCommon.cpp src/MicroOcpp/Model/ConnectorBase/Connector.cpp src/MicroOcpp/Model/ConnectorBase/Notification.cpp @@ -127,6 +135,7 @@ set(MO_SRC_UNIT tests/Configuration.cpp tests/Reservation.cpp tests/LocalAuthList.cpp + #tests/Certificates.cpp # add if MbedTLS is available ) add_executable(mo_unit_tests @@ -135,6 +144,14 @@ add_executable(mo_unit_tests ./tests/catch2/catchMain.cpp ) +# add MbedTLS for testing (TODO integrate properly into build system) +#add_subdirectory(lib/mbedtls) +#target_link_libraries(mo_unit_tests PUBLIC +# mbedtls +# mbedcrypto +# mbedx509 +#) + target_include_directories(mo_unit_tests PUBLIC "./tests/catch2" "./tests/helpers" @@ -153,6 +170,7 @@ target_compile_definitions(mo_unit_tests PUBLIC MO_FILENAME_PREFIX="./mo_store/" MO_LocalAuthListMaxLength=8 MO_SendLocalListMaxLength=4 + #MO_ENABLE_MBEDTLS=1 ) target_compile_options(mo_unit_tests PUBLIC diff --git a/README.md b/README.md index fad7a2fd..89d7d434 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,10 @@ If compiled with the Arduino integration: - [Links2004/arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) (version `2.4.1`) +If using the built-in certificate store (to enable, set build flag `MO_ENABLE_MBEDTLS=1`): + +- [Mbed-TLS/mbedtls](https://github.com/Mbed-TLS/mbedtls) (version `2.28.1`) + In case you use PlatformIO, you can copy all dependencies from `platformio.ini` into your own configuration file. Alternatively, you can install the full library with dependencies by adding `matth-x/MicroOcpp@1.0.0` in the PIO library manager. ## OCPP 2.0.1 and ISO 15118 diff --git a/src/MicroOcpp.cpp b/src/MicroOcpp.cpp index 7d23f5a3..b1490fd2 100644 --- a/src/MicroOcpp.cpp +++ b/src/MicroOcpp.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include //default CertStore implementation depends on MbedTLS #include #include #include @@ -199,7 +201,7 @@ ChargerCredentials::ChargerCredentials(const char *cpModel, const char *cpVendor } } -void mocpp_initialize(Connection& connection, const char *bootNotificationCredentials, std::shared_ptr fs, bool autoRecover) { +void mocpp_initialize(Connection& connection, const char *bootNotificationCredentials, std::shared_ptr fs, bool autoRecover, std::unique_ptr certStore) { if (context) { MO_DBG_WARN("already initialized. To reinit, call mocpp_deinitialize() before"); return; @@ -257,6 +259,21 @@ void mocpp_initialize(Connection& connection, const char *bootNotificationCreden model.setResetService(std::unique_ptr( new ResetService(*context))); + std::unique_ptr certStoreUse; + if (certStore) { + certStoreUse = std::move(certStore); + } +#if MO_ENABLE_MBEDTLS + else { + certStoreUse = makeCertificateStoreMbedTLS(filesystem); + } +#endif + + if (certStoreUse) { + model.setCertificateService(std::unique_ptr( + new CertificateService(*context, std::move(certStoreUse)))); + } + #if !defined(MO_CUSTOM_UPDATER) && !defined(MO_CUSTOM_WS) model.setFirmwareService(std::unique_ptr( EspWiFi::makeFirmwareService(*context))); //instantiate FW service + ESP installation routine diff --git a/src/MicroOcpp.h b/src/MicroOcpp.h index db672b35..f821d7ed 100644 --- a/src/MicroOcpp.h +++ b/src/MicroOcpp.h @@ -18,6 +18,7 @@ #include #include #include +#include using MicroOcpp::OnReceiveConfListener; using MicroOcpp::OnReceiveReqListener; @@ -93,7 +94,8 @@ void mocpp_initialize( const char *bootNotificationCredentials = ChargerCredentials("Demo Charger", "My Company Ltd."), //e.g. '{"chargePointModel":"Demo Charger","chargePointVendor":"My Company Ltd."}' (refer to OCPP 1.6 Specification - Edition 2 p. 60) std::shared_ptr filesystem = MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail), //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h - bool autoRecover = false); //automatically sanitize the local data store when the lib detects recurring crashes. Not recommended during development + bool autoRecover = false, //automatically sanitize the local data store when the lib detects recurring crashes. Not recommended during development + std::unique_ptr certStore = nullptr); //optionally use custom Cert Store (default depends on MbedTLS) /* * Stop the OCPP library and release allocated resources. diff --git a/src/MicroOcpp/Model/Certificates/Certificate.cpp b/src/MicroOcpp/Model/Certificates/Certificate.cpp new file mode 100644 index 00000000..3caa9dd6 --- /dev/null +++ b/src/MicroOcpp/Model/Certificates/Certificate.cpp @@ -0,0 +1,40 @@ +#include + +#include + +#include + +using namespace MicroOcpp; + +const char *CertificateHash::getHashAlgorithmCStr() { + switch(hashAlgorithm) { + case HashAlgorithmEnumType::SHA256: + return "SHA256"; + case HashAlgorithmEnumType::SHA384: + return "SHA384"; + case HashAlgorithmEnumType::SHA512: + return "SHA512"; + } + + MO_DBG_ERR("internal error"); + return ""; +} + +const char *CertificateHash::getIssuerNameHash() { + return issuerNameHash; +} + +const char *CertificateHash::getIssuerKeyHash() { + return issuerKeyHash; +} + +const char *CertificateHash::getSerialNumber() { + return serialNumber; +} + +bool CertificateHash::equals(const CertificateHash& other) { + return hashAlgorithm == other.hashAlgorithm && + !strncmp(serialNumber, other.serialNumber, sizeof(serialNumber)) && + !strncmp(issuerNameHash, other.issuerNameHash, sizeof(issuerNameHash)) && + !strncmp(issuerKeyHash, other.issuerKeyHash, sizeof(issuerKeyHash)); +} diff --git a/src/MicroOcpp/Model/Certificates/Certificate.h b/src/MicroOcpp/Model/Certificates/Certificate.h new file mode 100644 index 00000000..6dcf0cc6 --- /dev/null +++ b/src/MicroOcpp/Model/Certificates/Certificate.h @@ -0,0 +1,105 @@ +#ifndef MO_CERTIFICATE_H +#define MO_CERTIFICATE_H + +#include +#include + +namespace MicroOcpp { + +#define MO_MAX_CERT_SIZE 5500 //limit of field `certificate` in InstallCertificateRequest, not counting terminating '\0'. See OCPP 2.0.1 part 2 Data Type 1.30.1 + +/* + * See OCPP 2.0.1 part 2 Data Type 3.36 + */ +enum class GetCertificateIdType : uint8_t { + V2GRootCertificate, + MORootCertificate, + CSMSRootCertificate, + V2GCertificateChain, + ManufacturerRootCertificate +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.40 + */ +enum class GetInstalledCertificateStatus : uint8_t { + Accepted, + NotFound +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.45 + */ +enum class InstallCertificateType : uint8_t { + V2GRootCertificate, + MORootCertificate, + CSMSRootCertificate, + ManufacturerRootCertificate +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.28 + */ +enum class InstallCertificateStatus : uint8_t { + Accepted, + Rejected, + Failed +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.28 + */ +enum class DeleteCertificateStatus : uint8_t { + Accepted, + Failed, + NotFound +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.42 + */ +enum class HashAlgorithmEnumType : uint8_t { + SHA256, + SHA384, + SHA512 +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 2.6 + */ +struct CertificateHash { + HashAlgorithmEnumType hashAlgorithm; + char issuerNameHash [128 + 1]; + char issuerKeyHash [128 + 1]; + char serialNumber [40 + 1]; + + const char *getHashAlgorithmCStr(); + const char *getIssuerNameHash(); + const char *getIssuerKeyHash(); + const char *getSerialNumber(); + + bool equals(const CertificateHash& other); +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 2.5 + */ +struct CertificateChainHash { + GetCertificateIdType certificateType; + CertificateHash certificateHashData; + std::vector childCertificateHashData; +}; + +/* + * Interface which allows MicroOcpp to interact with the certificates managed by the local TLS library + */ +class CertificateStore { +public: + virtual GetInstalledCertificateStatus getCertificateIds(GetCertificateIdType certificateType, std::vector& out) = 0; + virtual DeleteCertificateStatus deleteCertificate(const CertificateHash& hash) = 0; + virtual InstallCertificateStatus installCertificate(InstallCertificateType certificateType, const char *certificate) = 0; +}; + +} //namespace MicroOcpp + +#endif diff --git a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp new file mode 100644 index 00000000..a20cb0f4 --- /dev/null +++ b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.cpp @@ -0,0 +1,437 @@ +#include + +#if MO_ENABLE_MBEDTLS + +#include + +#include +#include +#include +#include +#include + +#include + +#define MO_X509_OID_COMMON_NAME "2.5.4.3" //object-identifier of x509 common-name + +bool ocpp_get_cert_hash(const unsigned char *buf, size_t len, HashAlgorithmEnumType_c hashAlg, char *issuerNameHash, char *issuerKeyHash, char *serialNumber) { + + mbedtls_x509_crt cacert; + mbedtls_x509_crt_init(&cacert); + + int ret; + + if((ret = mbedtls_x509_crt_parse(&cacert, buf, len + 1)) < 0) { + char err [100]; + mbedtls_strerror(ret, err, 100); + MO_DBG_ERR("mbedtls_x509_crt_parse: %i -- %s", ret, err); + return false; + } + + if (cacert.next) { + MO_DBG_ERR("only sole root certs supported"); + return false; + } + + const unsigned char *subject_cn_p = nullptr; + size_t subject_cn_len = 0; + for (mbedtls_x509_name *it = &cacert.subject; it; it = it->next) { + char oid_cstr [50]; + if ((ret = mbedtls_oid_get_numeric_string(oid_cstr, 50, &it->oid)) < 0) { + MO_DBG_ERR("internal error: %i", ret); + continue; //there is an oid which exceeds the bufsize, but the target oid does fit so continue + } + + if (!strcmp(oid_cstr, MO_X509_OID_COMMON_NAME)) { + subject_cn_p = it->val.p; + subject_cn_len = it->val.len; + break; + } + } + + if (!subject_cn_p || !subject_cn_len) { + MO_DBG_ERR("could not find subject common name"); + return false; + } + + const unsigned char *issuer_cn_p = nullptr; + size_t issuer_cn_len = 0; + for (mbedtls_x509_name *it = &cacert.issuer; it; it = it->next) { + char oid_cstr [50]; + if ((ret = mbedtls_oid_get_numeric_string(oid_cstr, 50, &it->oid)) < 0) { + MO_DBG_ERR("internal error: %i", ret); + continue; //there is an oid which exceeds the bufsize, but the target oid does fit so continue + } + + if (!strcmp(oid_cstr, MO_X509_OID_COMMON_NAME)) { + issuer_cn_p = it->val.p; + issuer_cn_len = it->val.len; + break; + } + } + + if (!issuer_cn_p || !issuer_cn_len) { + MO_DBG_ERR("could not find issuer common name"); + return false; + } + + if (subject_cn_len != issuer_cn_len || strncmp((const char*) subject_cn_p, (const char*) issuer_cn_p, subject_cn_len)) { + MO_DBG_ERR("only support self-signed root certs"); + return false; + } + + mbedtls_md_type_t hash_alg_mbed; + + switch (hashAlg) { + case HashAlgorithmEnumType_c::ENUM_HA_SHA256: + hash_alg_mbed = MBEDTLS_MD_SHA256; + break; + case HashAlgorithmEnumType_c::ENUM_HA_SHA384: + hash_alg_mbed = MBEDTLS_MD_SHA384; + break; + case HashAlgorithmEnumType_c::ENUM_HA_SHA512: + hash_alg_mbed = MBEDTLS_MD_SHA512; + break; + default: + MO_DBG_ERR("internal error"); + return false; + } + + const mbedtls_md_info_t *md_info; + + md_info = mbedtls_md_info_from_type(hash_alg_mbed); + if (!md_info) { + MO_DBG_ERR("hash algorithmus not supported"); + return false; + } + + unsigned char hash_buf [64]; //at most 512 Bits (SHA512), equalling 64 Bytes + + size_t hash_size = mbedtls_md_get_size(md_info); + if (hash_size > sizeof(hash_buf)) { + MO_DBG_ERR("internal error"); + return false; + } + + if ((ret = mbedtls_md(md_info, issuer_cn_p, issuer_cn_len, hash_buf))) { + MO_DBG_ERR("mbedtls_md: %i", ret); + return false; + } + + for (size_t i = 0; i < hash_size; i ++) { + sprintf(issuerNameHash + 2 * i, "%02X", hash_buf[i]); + } + + unsigned char *pk_p; + size_t pk_len; + +#if MBEDTLS_VERSION_MAJOR == 2 && MBEDTLS_VERSION_MINOR <= 16 + unsigned char pk_buf [256]; + if ((ret = mbedtls_pk_write_pubkey_pem(&cacert.pk, pk_buf, 256)) < 0) { + MO_DBG_ERR("mbedtls_md: %i", ret); + return false; + } + pk_p = pk_buf; + pk_len = strnlen((const char*)pk_buf, 256); +#else //tested on MbedTLS 2.28.1 + pk_p = cacert.pk_raw.p; + pk_len = cacert.pk_raw.len; +#endif // MbedTLS version + + if ((ret = mbedtls_md(md_info, pk_p, pk_len, hash_buf))) { + MO_DBG_ERR("mbedtls_md: %i", ret); + return false; + } + + for (size_t i = 0; i < hash_size; i ++) { + sprintf(issuerKeyHash + 2*i, "%02X", hash_buf[i]); + } + + size_t serial_begin = 0; //trunicate leftmost 0x00 bytes + for (; serial_begin < cacert.serial.len - 1; serial_begin++) { //keep at least 1 byte, even if 0x00 + if (cacert.serial.p[serial_begin] != 0) { + break; + } + } + + for (size_t i = 0; i < std::min(cacert.serial.len - serial_begin, (size_t) ((MO_CERT_HASH_SERIAL_NUMBER_SIZE - 1)/2)); i++) { + sprintf(serialNumber + 2*i, "%02X", cacert.serial.p[i + serial_begin]); + } + + return true; +} + +namespace MicroOcpp { + +class CertificateStoreMbedTLS : public CertificateStore { +private: + std::shared_ptr filesystem; + + bool getCertHash(const char *fn, HashAlgorithmEnumType hashAlg, CertificateHash& out) { + size_t fsize; + if (filesystem->stat(fn, &fsize) != 0) { + MO_DBG_ERR("certificate does not exist: %s", fn); + return false; + } + + if (fsize >= MO_MAX_CERT_SIZE) { + MO_DBG_ERR("cert file exceeds limit: %s, %zuB", fn, fsize); + return false; + } + + auto file = filesystem->open(fn, "r"); + if (!file) { + MO_DBG_ERR("could not open file: %s", fn); + return false; + } + + unsigned char *buf = new unsigned char[fsize + 1]; + if (!buf) { + MO_DBG_ERR("OOM"); + return false; + } + + bool success = true; + + size_t ret; + if ((ret = file->read((char*) buf, fsize)) != fsize) { + MO_DBG_ERR("read error: %zu (expect %zu)", ret, fsize); + success = false; + } + + buf[fsize] = '\0'; + + if (success) { + success &= getCertHash(buf, fsize, hashAlg, out); + } + + if (!success) { + MO_DBG_ERR("could not read cert: %s", fn); + (void)0; + } + + delete[] buf; + return success; + } + + bool getCertHash(const unsigned char *buf, size_t len, HashAlgorithmEnumType hashAlg, CertificateHash& out) { + + HashAlgorithmEnumType_c ha; + + switch (hashAlg) { + case HashAlgorithmEnumType::SHA256: + ha = ENUM_HA_SHA256; + break; + case HashAlgorithmEnumType::SHA384: + ha = ENUM_HA_SHA384; + break; + case HashAlgorithmEnumType::SHA512: + ha = ENUM_HA_SHA512; + break; + default: + MO_DBG_ERR("internal error"); + return false; + } + + out.hashAlgorithm = hashAlg; + return ocpp_get_cert_hash(buf, len, ha, out.issuerNameHash, out.issuerKeyHash, out.serialNumber); + } +public: + CertificateStoreMbedTLS(std::shared_ptr filesystem) + : filesystem(filesystem) { + + } + + GetInstalledCertificateStatus getCertificateIds(GetCertificateIdType certificateType, std::vector& out) override { + const char *certTypeFnStr = nullptr; + switch (certificateType) { + case GetCertificateIdType::CSMSRootCertificate: + certTypeFnStr = MO_CERT_FN_CSMS_ROOT; + break; + case GetCertificateIdType::ManufacturerRootCertificate: + certTypeFnStr = MO_CERT_FN_MANUFACTURER_ROOT; + break; + default: + MO_DBG_ERR("only CSMS / Manufacturer root supported"); + break; + } + + if (!certTypeFnStr) { + return GetInstalledCertificateStatus::NotFound; + } + + out.clear(); + + for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) { + char fn [MO_MAX_PATH_SIZE]; + if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) { + MO_DBG_ERR("internal error"); + return GetInstalledCertificateStatus::NotFound; + } + + size_t msize; + if (filesystem->stat(fn, &msize) != 0) { + continue; //no cert installed at this slot + } + + out.emplace_back(); + CertificateChainHash& rootCert = out.back(); + + rootCert.certificateType = certificateType; + + if (!getCertHash(fn, HashAlgorithmEnumType::SHA256, rootCert.certificateHashData)) { + MO_DBG_ERR("could not create hash: %s", fn); + out.pop_back(); + continue; + } + } + + return out.empty() ? + GetInstalledCertificateStatus::NotFound : + GetInstalledCertificateStatus::Accepted; + } + + DeleteCertificateStatus deleteCertificate(const CertificateHash& hash) override { + bool err = false; + + //enumerate all certs possibly installed by this CertStore implementation + for (const char *certTypeFnStr : {MO_CERT_FN_CSMS_ROOT, MO_CERT_FN_MANUFACTURER_ROOT}) { + for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) { + + char fn [MO_MAX_PATH_SIZE] = {'\0'}; //cert fn on flash storage + + if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) { + MO_DBG_ERR("internal error"); + return DeleteCertificateStatus::Failed; + } + + size_t msize; + if (filesystem->stat(fn, &msize) != 0) { + continue; //no cert installed at this slot + } + + CertificateHash probe; + if (!getCertHash(fn, hash.hashAlgorithm, probe)) { + MO_DBG_ERR("could not create hash: %s", fn); + err = true; + continue; + } + + if (probe.equals(hash)) { + //found, delete + + bool success = filesystem->remove(fn); + return success ? + DeleteCertificateStatus::Accepted : + DeleteCertificateStatus::Failed; + } + } + } + + return err ? + DeleteCertificateStatus::Failed : + DeleteCertificateStatus::NotFound; + } + + InstallCertificateStatus installCertificate(InstallCertificateType certificateType, const char *certificate) override { + const char *certTypeFnStr = nullptr; + switch (certificateType) { + case InstallCertificateType::CSMSRootCertificate: + certTypeFnStr = MO_CERT_FN_CSMS_ROOT; + break; + case InstallCertificateType::ManufacturerRootCertificate: + certTypeFnStr = MO_CERT_FN_MANUFACTURER_ROOT; + break; + default: + MO_DBG_ERR("only CSMS / Manufacturer root supported"); + break; + } + + if (!certTypeFnStr) { + return InstallCertificateStatus::Failed; + } + + //check if this implementation is able to parse incoming cert + { + CertificateHash certId; + if (!getCertHash((const unsigned char*)certificate, strlen(certificate), HashAlgorithmEnumType::SHA256, certId)) { + MO_DBG_ERR("unable to parse cert"); + return InstallCertificateStatus::Rejected; + } + MO_DBG_DEBUG("Cert ID:"); + MO_DBG_DEBUG("hashAlgorithm: %s", certId.getHashAlgorithmCStr()); + MO_DBG_DEBUG("issuerNameHash: %s", certId.issuerNameHash); + MO_DBG_DEBUG("issuerKeyHash: %s", certId.issuerKeyHash); + MO_DBG_DEBUG("serialNumber: %s", certId.serialNumber); + } + + char fn [MO_MAX_PATH_SIZE] = {'\0'}; //cert fn on flash storage + + //check for free cert slot + for (size_t i = 0; i < MO_CERT_STORE_SIZE; i++) { + if (!printCertFn(certTypeFnStr, i, fn, MO_MAX_PATH_SIZE)) { + MO_DBG_ERR("invalid cert fn"); + return InstallCertificateStatus::Failed; + } + + size_t msize; + if (filesystem->stat(fn, &msize) != 0) { + //found free slot; fn contains result + break; + } else { + //this slot is already occupied; invalidate fn and try next + fn[0] = '\0'; + } + } + + if (fn[0] == '\0') { + MO_DBG_ERR("exceed maximum number of certs; must delete before"); + return InstallCertificateStatus::Rejected; + } + + auto file = filesystem->open(fn, "w"); + if (!file) { + MO_DBG_ERR("could not open file"); + return InstallCertificateStatus::Failed; + } + + size_t cert_len = strlen(certificate); + auto written = file->write(certificate, cert_len); + if (written < cert_len) { + MO_DBG_ERR("file write error"); + file.reset(); + filesystem->remove(fn); + return InstallCertificateStatus::Failed; + } + + MO_DBG_INFO("installed certificate: %s", fn); + return InstallCertificateStatus::Accepted; + } +}; + +std::unique_ptr makeCertificateStoreMbedTLS(std::shared_ptr filesystem) { + if (!filesystem) { + MO_DBG_WARN("default Certificate Store requires FS"); + return nullptr; + } + return std::unique_ptr(new CertificateStoreMbedTLS(filesystem)); +} + +bool printCertFn(const char *certType, size_t index, char *buf, size_t bufsize) { + if (!certType || !*certType || index >= MO_CERT_STORE_SIZE || !buf) { + MO_DBG_ERR("invalid args"); + return false; + } + + auto ret = snprintf(buf, bufsize, MO_FILENAME_PREFIX MO_CERT_FN_PREFIX "%s" "-%zu" MO_CERT_FN_SUFFIX, + certType, index); + if (ret < 0 || ret >= (int)bufsize) { + MO_DBG_ERR("fn error: %i", ret); + return false; + } + return true; +} + +} //namespace MicroOcpp + +#endif //MO_ENABLE_MBEDTLS diff --git a/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h new file mode 100644 index 00000000..f06b4c2c --- /dev/null +++ b/src/MicroOcpp/Model/Certificates/CertificateMbedTLS.h @@ -0,0 +1,64 @@ +#ifndef MO_CERTIFICATE_MBEDTLS_H +#define MO_CERTIFICATE_MBEDTLS_H + +/* + * Built-in implementation of the Certificate interface for MbedTLS + */ + +#ifndef MO_ENABLE_MBEDTLS +#define MO_ENABLE_MBEDTLS 0 +#endif + +#if MO_ENABLE_MBEDTLS + +/* + * Provide certificate interpreter to facilitate cert store in C. A full implementation is only available for C++ + */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +bool ocpp_get_cert_hash(const unsigned char *cert, size_t len, enum HashAlgorithmEnumType_c hashAlg, ocpp_certificate_hash *out); + +#ifdef __cplusplus +} //extern "C" + +#include + +#include +#include + +#ifndef MO_CERT_FN_PREFIX +#define MO_CERT_FN_PREFIX "cert-" +#endif + +#ifndef MO_CERT_FN_SUFFIX +#define MO_CERT_FN_SUFFIX ".pem" +#endif + +#ifndef MO_CERT_FN_CSMS_ROOT +#define MO_CERT_FN_CSMS_ROOT "csms" +#endif + +#ifndef MO_CERT_FN_MANUFACTURER_ROOT +#define MO_CERT_FN_MANUFACTURER_ROOT "mfact" +#endif + +#ifndef MO_CERT_STORE_SIZE +#define MO_CERT_STORE_SIZE 3 //max number of certs per certificate type (e.g. CSMS root CA, Manufacturer root CA) +#endif + +namespace MicroOcpp { + +std::unique_ptr makeCertificateStoreMbedTLS(std::shared_ptr filesystem); + +bool printCertFn(const char *certType, size_t index, char *buf, size_t bufsize); + +} //namespace MicroOcpp + +#endif //def __cplusplus +#endif //MO_ENABLE_MBEDTLS + +#endif diff --git a/src/MicroOcpp/Model/Certificates/CertificateService.cpp b/src/MicroOcpp/Model/Certificates/CertificateService.cpp new file mode 100644 index 00000000..57f502f7 --- /dev/null +++ b/src/MicroOcpp/Model/Certificates/CertificateService.cpp @@ -0,0 +1,22 @@ +#include +#include +#include +#include +#include + +using namespace MicroOcpp; + +CertificateService::CertificateService(Context& context, std::unique_ptr certStore) + : context(context), certStore(std::move(certStore)) { + + context.getOperationRegistry().registerOperation("DeleteCertificate", [this] () { + return new Ocpp201::DeleteCertificate(*this);}); + context.getOperationRegistry().registerOperation("GetInstalledCertificateIds", [this] () { + return new Ocpp201::GetInstalledCertificateIds(*this);}); + context.getOperationRegistry().registerOperation("InstallCertificate", [this] () { + return new Ocpp201::InstallCertificate(*this);}); +} + +CertificateStore *CertificateService::getCertificateStore() { + return certStore.get(); +} diff --git a/src/MicroOcpp/Model/Certificates/CertificateService.h b/src/MicroOcpp/Model/Certificates/CertificateService.h new file mode 100644 index 00000000..ce9b1e64 --- /dev/null +++ b/src/MicroOcpp/Model/Certificates/CertificateService.h @@ -0,0 +1,34 @@ +/* + * Functional Block M: ISO 15118 Certificate Management + * + * Implementation of UC: + * - M03 + * - M04 + * - M05 + */ + +#ifndef MO_CERTIFICATESERVICE_H +#define MO_CERTIFICATESERVICE_H + +#include +#include + +#include + +namespace MicroOcpp { + +class Context; + +class CertificateService { +private: + Context& context; + std::unique_ptr certStore; +public: + CertificateService(Context& context, std::unique_ptr certStore); + + CertificateStore *getCertificateStore(); +}; + +} + +#endif diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.cpp b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp new file mode 100644 index 00000000..285d0078 --- /dev/null +++ b/src/MicroOcpp/Model/Certificates/Certificate_c.cpp @@ -0,0 +1,227 @@ +#include +#include + +namespace MicroOcpp { + +/* + * C++ wrapper for the C-style certificate interface + */ +class CertificateStoreC : public CertificateStore { +private: + ocpp_certificate_store *certstore = nullptr; +public: + CertificateStoreC(ocpp_certificate_store *certstore) : certstore(certstore) { + + } + + ~CertificateStoreC() = default; + + GetInstalledCertificateStatus getCertificateIds(GetCertificateIdType certificateType, std::vector& out) override { + GetCertificateIdType_c ct_c; + switch (certificateType) { + case GetCertificateIdType::V2GRootCertificate: + ct_c = ENUM_GCI_V2GRootCertificate; + break; + case GetCertificateIdType::MORootCertificate: + ct_c = ENUM_GCI_MORootCertificate; + break; + case GetCertificateIdType::CSMSRootCertificate: + ct_c = ENUM_GCI_CSMSRootCertificate; + break; + case GetCertificateIdType::V2GCertificateChain: + ct_c = ENUM_GCI_V2GCertificateChain; + break; + case GetCertificateIdType::ManufacturerRootCertificate: + ct_c = ENUM_GCI_ManufacturerRootCertificate; + break; + default: + MO_DBG_ERR("internal error"); + return GetInstalledCertificateStatus::NotFound; + } + + ocpp_certificate_chain_hash *cch; + + auto ret = certstore->getCertificateIds(certstore->user_data, ct_c, &cch); + if (ret == ENUM_GICS_NotFound || !cch) { + return GetInstalledCertificateStatus::NotFound; + } + + bool err = false; + + for (ocpp_certificate_chain_hash *it = cch; it && !err; it = it->next) { + out.emplace_back(); + auto &chd_el = out.back(); + switch (it->certificateType) { + case ENUM_GCI_V2GRootCertificate: + chd_el.certificateType = GetCertificateIdType::V2GRootCertificate; + break; + case ENUM_GCI_MORootCertificate: + chd_el.certificateType = GetCertificateIdType::MORootCertificate; + break; + case ENUM_GCI_CSMSRootCertificate: + chd_el.certificateType = GetCertificateIdType::CSMSRootCertificate; + break; + case ENUM_GCI_V2GCertificateChain: + chd_el.certificateType = GetCertificateIdType::V2GCertificateChain; + break; + case ENUM_GCI_ManufacturerRootCertificate: + chd_el.certificateType = GetCertificateIdType::ManufacturerRootCertificate; + break; + default: + MO_DBG_ERR("internal error"); + err = true; + break; + } + + switch (it->certificateHashData.hashAlgorithm) { + case ENUM_HA_SHA256: + chd_el.certificateHashData.hashAlgorithm = HashAlgorithmEnumType::SHA256; + break; + case ENUM_HA_SHA384: + chd_el.certificateHashData.hashAlgorithm = HashAlgorithmEnumType::SHA384; + break; + case ENUM_HA_SHA512: + chd_el.certificateHashData.hashAlgorithm = HashAlgorithmEnumType::SHA512; + break; + default: + MO_DBG_ERR("internal error"); + err = true; + break; + } + + bool success = true; + int ret; + ret = snprintf(chd_el.certificateHashData.issuerNameHash, sizeof(chd_el.certificateHashData.issuerNameHash), "%s", it->certificateHashData.issuerNameHash); + success &= ret >= 0 && (size_t)ret < sizeof(chd_el.certificateHashData.issuerNameHash); + ret = snprintf(chd_el.certificateHashData.issuerKeyHash, sizeof(chd_el.certificateHashData.issuerKeyHash), "%s", it->certificateHashData.issuerKeyHash); + success &= ret >= 0 && (size_t)ret < sizeof(chd_el.certificateHashData.issuerKeyHash); + ret = snprintf(chd_el.certificateHashData.serialNumber, sizeof(chd_el.certificateHashData.serialNumber), "%s", it->certificateHashData.serialNumber); + success &= ret >= 0 && (size_t)ret < sizeof(chd_el.certificateHashData.serialNumber); + + if (!success) { + MO_DBG_ERR("error copying C-style struct"); + err = true; + } + } + + while (cch) { + ocpp_certificate_chain_hash *el = cch; + cch = cch->next; + el->invalidate(el); + } + + if (err) { + out.clear(); + } + + return out.empty() ? + GetInstalledCertificateStatus::NotFound : + GetInstalledCertificateStatus::Accepted; + } + + DeleteCertificateStatus deleteCertificate(const CertificateHash& hash) override { + ocpp_certificate_hash ch; + + HashAlgorithmEnumType_c ha; + + switch (hash.hashAlgorithm) { + case HashAlgorithmEnumType::SHA256: + ha = ENUM_HA_SHA256; + break; + case HashAlgorithmEnumType::SHA384: + ha = ENUM_HA_SHA384; + break; + case HashAlgorithmEnumType::SHA512: + ha = ENUM_HA_SHA512; + break; + default: + MO_DBG_ERR("internal error"); + return DeleteCertificateStatus::Failed; + } + + ch.hashAlgorithm = ha; + + bool success = true; + int ret; + ret = snprintf(ch.issuerNameHash, sizeof(ch.issuerNameHash), "%s", hash.issuerNameHash); + success &= ret >= 0 && (size_t)ret < sizeof(ch.issuerNameHash); + ret = snprintf(ch.issuerKeyHash, sizeof(ch.issuerKeyHash), "%s", hash.issuerKeyHash); + success &= ret >= 0 && (size_t)ret < sizeof(ch.issuerKeyHash); + ret = snprintf(ch.serialNumber, sizeof(ch.serialNumber), "%s", hash.serialNumber); + success &= ret >= 0 && (size_t)ret < sizeof(ch.serialNumber); + if (!success) { + MO_DBG_ERR("error copying C-style struct"); + return DeleteCertificateStatus::Failed; + } + + auto status = certstore->deleteCertificate(certstore->user_data, &ch); + + DeleteCertificateStatus dcs; + + switch (status) { + case ENUM_DCS_Accepted: + dcs = DeleteCertificateStatus::Accepted; + break; + case ENUM_DCS_Failed: + dcs = DeleteCertificateStatus::Failed; + break; + case ENUM_DCS_NotFound: + dcs = DeleteCertificateStatus::NotFound; + break; + default: + MO_DBG_ERR("could not convert status type"); + return DeleteCertificateStatus::Failed; + } + + return dcs; + } + + InstallCertificateStatus installCertificate(InstallCertificateType certificateType, const char *certificate) override { + InstallCertificateType_c ic; + + switch (certificateType) { + case InstallCertificateType::V2GRootCertificate: + ic = ENUM_IC_V2GRootCertificate; + break; + case InstallCertificateType::MORootCertificate: + ic = ENUM_IC_MORootCertificate; + break; + case InstallCertificateType::CSMSRootCertificate: + ic = ENUM_IC_CSMSRootCertificate; + break; + case InstallCertificateType::ManufacturerRootCertificate: + ic = ENUM_IC_ManufacturerRootCertificate; + break; + default: + MO_DBG_ERR("internal error"); + return InstallCertificateStatus::Failed; + } + + auto ret = certstore->installCertificate(certstore->user_data, ic, certificate); + + InstallCertificateStatus ics; + + switch (ret) { + case ENUM_ICS_Accepted: + ics = InstallCertificateStatus::Accepted; + break; + case ENUM_ICS_Rejected: + ics = InstallCertificateStatus::Rejected; + break; + case ENUM_ICS_Failed: + ics = InstallCertificateStatus::Failed; + break; + default: + MO_DBG_ERR("could not convert status type"); + return InstallCertificateStatus::Rejected; + } + + return ics; + } +}; + +std::unique_ptr makeCertificateStoreCwrapper(ocpp_certificate_store *certstore) { + return std::unique_ptr(new CertificateStoreC(certstore)); +} + +} //namespace MicroOcpp diff --git a/src/MicroOcpp/Model/Certificates/Certificate_c.h b/src/MicroOcpp/Model/Certificates/Certificate_c.h new file mode 100644 index 00000000..cbc256bf --- /dev/null +++ b/src/MicroOcpp/Model/Certificates/Certificate_c.h @@ -0,0 +1,111 @@ +#ifndef MO_CERTIFICATE_C_H +#define MO_CERTIFICATE_C_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +enum GetCertificateIdType_c { + ENUM_GCI_V2GRootCertificate, + ENUM_GCI_MORootCertificate, + ENUM_GCI_CSMSRootCertificate, + ENUM_GCI_V2GCertificateChain, + ENUM_GCI_ManufacturerRootCertificate +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.40 + */ +enum GetInstalledCertificateStatus_c { + ENUM_GICS_Accepted, + ENUM_GICS_NotFound +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.45 + */ +enum InstallCertificateType_c { + ENUM_IC_V2GRootCertificate, + ENUM_IC_MORootCertificate, + ENUM_IC_CSMSRootCertificate, + ENUM_IC_ManufacturerRootCertificate +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.28 + */ +enum InstallCertificateStatus_c { + ENUM_ICS_Accepted, + ENUM_ICS_Rejected, + ENUM_ICS_Failed +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.28 + */ +enum DeleteCertificateStatus_c { + ENUM_DCS_Accepted, + ENUM_DCS_Failed, + ENUM_DCS_NotFound +}; + +/* + * See OCPP 2.0.1 part 2 Data Type 3.42 + */ +enum HashAlgorithmEnumType_c { + ENUM_HA_SHA256, + ENUM_HA_SHA384, + ENUM_HA_SHA512 +}; + +#define MO_CERT_HASH_ISSUER_NAME_SIZE (128 + 1) +#define MO_CERT_HASH_ISSUER_KEY_SIZE (128 + 1) +#define MO_CERT_HASH_SERIAL_NUMBER_SIZE (40 + 1) + +typedef struct ocpp_certificate_hash { + enum HashAlgorithmEnumType_c hashAlgorithm; + char issuerNameHash [MO_CERT_HASH_ISSUER_NAME_SIZE]; + char issuerKeyHash [MO_CERT_HASH_ISSUER_KEY_SIZE]; + char serialNumber [MO_CERT_HASH_SERIAL_NUMBER_SIZE]; + + //ocpp_certificate_hash *next; //link to next list element if part of ocpp_certificate_chain_hash +} ocpp_certificate_hash; + +typedef struct ocpp_certificate_chain_hash { + void *user_data; //set this at your choice. MO passes it back to the functions below + + enum GetCertificateIdType_c certificateType; + ocpp_certificate_hash certificateHashData; + //ocpp_certificate_hash *childCertificateHashData; + + struct ocpp_certificate_chain_hash *next; //link to next list element if result of getCertificateIds + + void (*invalidate)(void *user_data); //free resources here. Guaranteed to be called +} ocpp_certificate_chain_hash; + +typedef struct ocpp_certificate_store { + void *user_data; //set this at your choice. MO passes it back to the functions below + + enum GetInstalledCertificateStatus_c (*getCertificateIds)(void *user_data, enum GetCertificateIdType_c certificateType, ocpp_certificate_chain_hash **out); + enum DeleteCertificateStatus_c (*deleteCertificate)(void *user_data, const ocpp_certificate_hash *hash); + enum InstallCertificateStatus_c (*installCertificate)(void *user_data, enum InstallCertificateType_c certificateType, const char *certificate); +} ocpp_certificate_store; + +#ifdef __cplusplus +} //extern "C" + +#include + +#include + +namespace MicroOcpp { + +std::unique_ptr makeCertificateStoreCwrapper(ocpp_certificate_store *certstore); + +} //namespace MicroOcpp + +#endif //defined __cplusplus + +#endif diff --git a/src/MicroOcpp/Model/Model.cpp b/src/MicroOcpp/Model/Model.cpp index f0bd7beb..d97bedd7 100644 --- a/src/MicroOcpp/Model/Model.cpp +++ b/src/MicroOcpp/Model/Model.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include @@ -187,6 +188,15 @@ ResetService *Model::getResetService() const { return resetService.get(); } +void Model::setCertificateService(std::unique_ptr cs) { + this->certService = std::move(cs); + capabilitiesUpdated = true; +} + +CertificateService *Model::getCertificateService() const { + return certService.get(); +} + Clock& Model::getClock() { return clock; } diff --git a/src/MicroOcpp/Model/Model.h b/src/MicroOcpp/Model/Model.h index 1273153f..81ec653e 100644 --- a/src/MicroOcpp/Model/Model.h +++ b/src/MicroOcpp/Model/Model.h @@ -23,6 +23,7 @@ class AuthorizationService; class ReservationService; class BootService; class ResetService; +class CertificateService; class Model { private: @@ -38,6 +39,7 @@ class Model { std::unique_ptr reservationService; std::unique_ptr bootService; std::unique_ptr resetService; + std::unique_ptr certService; Clock clock; bool capabilitiesUpdated = true; @@ -92,6 +94,9 @@ class Model { void setResetService(std::unique_ptr rs); ResetService *getResetService() const; + void setCertificateService(std::unique_ptr cs); + CertificateService *getCertificateService() const; + Clock &getClock(); uint16_t getBootNr(); diff --git a/src/MicroOcpp/Operations/DeleteCertificate.cpp b/src/MicroOcpp/Operations/DeleteCertificate.cpp new file mode 100644 index 00000000..27a76d10 --- /dev/null +++ b/src/MicroOcpp/Operations/DeleteCertificate.cpp @@ -0,0 +1,91 @@ +#include +#include +#include +#include + +using MicroOcpp::Ocpp201::DeleteCertificate; + +DeleteCertificate::DeleteCertificate(CertificateService& certService) : certService(certService) { + +} + +void DeleteCertificate::processReq(JsonObject payload) { + + if (!payload.containsKey("hashAlgorithm") || + !payload.containsKey("issuerNameHash") || + !payload.containsKey("issuerKeyHash") || + !payload.containsKey("serialNumber")) { + errorCode = "FormationViolation"; + return; + } + + const char *hashAlgorithm = payload["hashAlgorithm"] | "_Invalid"; + + if (!payload["issuerNameHash"].is() || + !payload["issuerKeyHash"].is() || + !payload["serialNumber"].is()) { + errorCode = "FormationViolation"; + return; + } + + CertificateHash cert; + + if (!strcmp(hashAlgorithm, "SHA256")) { + cert.hashAlgorithm = HashAlgorithmEnumType::SHA256; + } else if (!strcmp(hashAlgorithm, "SHA384")) { + cert.hashAlgorithm = HashAlgorithmEnumType::SHA384; + } else if (!strcmp(hashAlgorithm, "SHA512")) { + cert.hashAlgorithm = HashAlgorithmEnumType::SHA512; + } else { + errorCode = "FormationViolation"; + return; + } + + auto retIN = snprintf(cert.issuerNameHash, sizeof(cert.issuerNameHash), "%s", payload["issuerNameHash"] | "_Invalid"); + auto retIK = snprintf(cert.issuerKeyHash, sizeof(cert.issuerKeyHash), "%s", payload["issuerKeyHash"] | "_Invalid"); + auto retSN = snprintf(cert.serialNumber, sizeof(cert.serialNumber), "%s", payload["serialNumber"] | "_Invalid"); + if (retIN < 0 || retIK < 0 || retSN < 0) { + MO_DBG_ERR("could not parse CertId: %i %i %i", retIN, retIK, retSN); + errorCode = "InternalError"; + return; + } + if ((size_t)retIN >= sizeof(cert.issuerNameHash) || + (size_t)retIK >= sizeof(cert.issuerKeyHash) || + (size_t)retSN >= sizeof(cert.serialNumber)) { + errorCode = "FormationViolation"; + return; + } + + auto certStore = certService.getCertificateStore(); + if (!certStore) { + errorCode = "NotSupported"; + return; + } + + auto status = certStore->deleteCertificate(cert); + + switch (status) { + case DeleteCertificateStatus::Accepted: + this->status = "Accepted"; + break; + case DeleteCertificateStatus::Failed: + this->status = "Failed"; + break; + case DeleteCertificateStatus::NotFound: + this->status = "NotFound"; + break; + default: + MO_DBG_ERR("internal error"); + errorCode = "InternalError"; + return; + } + + //operation executed successfully +} + +std::unique_ptr DeleteCertificate::createConf(){ + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); + JsonObject payload = doc->to(); + payload["status"] = status; + return doc; +} diff --git a/src/MicroOcpp/Operations/DeleteCertificate.h b/src/MicroOcpp/Operations/DeleteCertificate.h new file mode 100644 index 00000000..e0b3f66f --- /dev/null +++ b/src/MicroOcpp/Operations/DeleteCertificate.h @@ -0,0 +1,32 @@ +#ifndef MO_DELETECERTIFICATE_H +#define MO_DELETECERTIFICATE_H + +#include + +namespace MicroOcpp { + +class CertificateService; + +namespace Ocpp201 { + +class DeleteCertificate : public Operation { +private: + CertificateService& certService; + const char *status = nullptr; + const char *errorCode = nullptr; +public: + DeleteCertificate(CertificateService& certService); + + const char* getOperationType() override {return "DeleteCertificate";} + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} +}; + +} //end namespace Ocpp201 +} //end namespace MicroOcpp + +#endif diff --git a/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp b/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp new file mode 100644 index 00000000..fae8e150 --- /dev/null +++ b/src/MicroOcpp/Operations/GetInstalledCertificateIds.cpp @@ -0,0 +1,105 @@ +#include +#include +#include +#include + +using MicroOcpp::Ocpp201::GetInstalledCertificateIds; + +GetInstalledCertificateIds::GetInstalledCertificateIds(CertificateService& certService) : certService(certService) { + +} + +void GetInstalledCertificateIds::processReq(JsonObject payload) { + + if (!payload.containsKey("certificateType")) { + errorCode = "FormationViolation"; + return; + } + + const char *certificateTypeCstr = payload["certificateType"] | "_Invalid"; + GetCertificateIdType certificateType; + if (!strcmp(certificateTypeCstr, "V2GRootCertificate")) { + certificateType = GetCertificateIdType::V2GRootCertificate; + } else if (!strcmp(certificateTypeCstr, "MORootCertificate")) { + certificateType = GetCertificateIdType::MORootCertificate; + } else if (!strcmp(certificateTypeCstr, "CSMSRootCertificate")) { + certificateType = GetCertificateIdType::CSMSRootCertificate; + } else if (!strcmp(certificateTypeCstr, "V2GCertificateChain")) { + certificateType = GetCertificateIdType::V2GCertificateChain; + } else if (!strcmp(certificateTypeCstr, "ManufacturerRootCertificate")) { + certificateType = GetCertificateIdType::ManufacturerRootCertificate; + } else { + errorCode = "FormationViolation"; + return; + } + + auto certStore = certService.getCertificateStore(); + if (!certStore) { + errorCode = "NotSupported"; + return; + } + + auto status = certStore->getCertificateIds(certificateType, certificateHashDataChain); + + switch (status) { + case GetInstalledCertificateStatus::Accepted: + this->status = "Accepted"; + break; + case GetInstalledCertificateStatus::NotFound: + this->status = "NotFound"; + break; + default: + MO_DBG_ERR("internal error"); + errorCode = "InternalError"; + return; + } + + //operation executed successfully +} + +std::unique_ptr GetInstalledCertificateIds::createConf(){ + auto doc = std::unique_ptr(new DynamicJsonDocument( + JSON_OBJECT_SIZE(2) + //payload root + JSON_ARRAY_SIZE(certificateHashDataChain.size()) + //array for field certificateHashDataChain + certificateHashDataChain.size() * ( + JSON_OBJECT_SIZE(2) + //certificateHashDataChain root + JSON_OBJECT_SIZE(4)) //certificateHashData + )); + JsonObject payload = doc->to(); + payload["status"] = status; + + for (auto& chainElem : certificateHashDataChain) { + JsonObject certHashJson = payload["certificateHashDataChain"].createNestedObject(); + + const char *certificateTypeCstr = ""; + switch (chainElem.certificateType) { + case GetCertificateIdType::V2GRootCertificate: + certificateTypeCstr = "V2GRootCertificate"; + break; + case GetCertificateIdType::MORootCertificate: + certificateTypeCstr = "MORootCertificate"; + break; + case GetCertificateIdType::CSMSRootCertificate: + certificateTypeCstr = "CSMSRootCertificate"; + break; + case GetCertificateIdType::V2GCertificateChain: + certificateTypeCstr = "V2GCertificateChain"; + break; + case GetCertificateIdType::ManufacturerRootCertificate: + certificateTypeCstr = "ManufacturerRootCertificate"; + break; + } + + certHashJson["certificateType"] = (const char*) certificateTypeCstr; //use JSON zero-copy mode + certHashJson["certificateHashData"]["hashAlgorithm"] = chainElem.certificateHashData.getHashAlgorithmCStr(); + certHashJson["certificateHashData"]["issuerNameHash"] = chainElem.certificateHashData.getIssuerNameHash(); + certHashJson["certificateHashData"]["issuerKeyHash"] = chainElem.certificateHashData.getIssuerKeyHash(); + certHashJson["certificateHashData"]["serialNumber"] = chainElem.certificateHashData.getSerialNumber(); + + if (!chainElem.childCertificateHashData.empty()) { + MO_DBG_ERR("only sole root certs supported"); + } + } + + return doc; +} diff --git a/src/MicroOcpp/Operations/GetInstalledCertificateIds.h b/src/MicroOcpp/Operations/GetInstalledCertificateIds.h new file mode 100644 index 00000000..58c34d87 --- /dev/null +++ b/src/MicroOcpp/Operations/GetInstalledCertificateIds.h @@ -0,0 +1,34 @@ +#ifndef MO_GETINSTALLEDCERTIFICATEIDS_H +#define MO_GETINSTALLEDCERTIFICATEIDS_H + +#include +#include + +namespace MicroOcpp { + +class CertificateService; + +namespace Ocpp201 { + +class GetInstalledCertificateIds : public Operation { +private: + CertificateService& certService; + std::vector certificateHashDataChain; + const char *status = nullptr; + const char *errorCode = nullptr; +public: + GetInstalledCertificateIds(CertificateService& certService); + + const char* getOperationType() override {return "GetInstalledCertificateIds";} + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} +}; + +} //end namespace Ocpp201 +} //end namespace MicroOcpp + +#endif diff --git a/src/MicroOcpp/Operations/InstallCertificate.cpp b/src/MicroOcpp/Operations/InstallCertificate.cpp new file mode 100644 index 00000000..69be0d4d --- /dev/null +++ b/src/MicroOcpp/Operations/InstallCertificate.cpp @@ -0,0 +1,75 @@ +#include +#include +#include + +using MicroOcpp::Ocpp201::InstallCertificate; + +InstallCertificate::InstallCertificate(CertificateService& certService) : certService(certService) { + +} + +void InstallCertificate::processReq(JsonObject payload) { + + if (!payload.containsKey("certificateType") || + !payload.containsKey("certificate")) { + errorCode = "FormationViolation"; + return; + } + + InstallCertificateType certificateType; + + const char *certificateTypeCstr = payload["certificateType"] | "_Invalid"; + + if (!strcmp(certificateTypeCstr, "V2GRootCertificate")) { + certificateType = InstallCertificateType::V2GRootCertificate; + } else if (!strcmp(certificateTypeCstr, "MORootCertificate")) { + certificateType = InstallCertificateType::MORootCertificate; + } else if (!strcmp(certificateTypeCstr, "CSMSRootCertificate")) { + certificateType = InstallCertificateType::CSMSRootCertificate; + } else if (!strcmp(certificateTypeCstr, "ManufacturerRootCertificate")) { + certificateType = InstallCertificateType::ManufacturerRootCertificate; + } else { + errorCode = "FormationViolation"; + return; + } + + if (!payload["certificate"].is()) { + errorCode = "FormationViolation"; + return; + } + + const char *certificate = payload["certificate"]; + + auto certStore = certService.getCertificateStore(); + if (!certStore) { + errorCode = "NotSupported"; + return; + } + + auto status = certStore->installCertificate(certificateType, certificate); + + switch (status) { + case InstallCertificateStatus::Accepted: + this->status = "Accepted"; + break; + case InstallCertificateStatus::Rejected: + this->status = "Rejected"; + break; + case InstallCertificateStatus::Failed: + this->status = "Failed"; + break; + default: + MO_DBG_ERR("internal error"); + errorCode = "InternalError"; + return; + } + + //operation executed successfully +} + +std::unique_ptr InstallCertificate::createConf(){ + auto doc = std::unique_ptr(new DynamicJsonDocument(JSON_OBJECT_SIZE(1))); + JsonObject payload = doc->to(); + payload["status"] = status; + return doc; +} diff --git a/src/MicroOcpp/Operations/InstallCertificate.h b/src/MicroOcpp/Operations/InstallCertificate.h new file mode 100644 index 00000000..24fb725e --- /dev/null +++ b/src/MicroOcpp/Operations/InstallCertificate.h @@ -0,0 +1,32 @@ +#ifndef MO_INSTALLCERTIFICATE_H +#define MO_INSTALLCERTIFICATE_H + +#include + +namespace MicroOcpp { + +class CertificateService; + +namespace Ocpp201 { + +class InstallCertificate : public Operation { +private: + CertificateService& certService; + const char *status = nullptr; + const char *errorCode = nullptr; +public: + InstallCertificate(CertificateService& certService); + + const char* getOperationType() override {return "InstallCertificate";} + + void processReq(JsonObject payload) override; + + std::unique_ptr createConf() override; + + const char *getErrorCode() override {return errorCode;} +}; + +} //end namespace Ocpp201 +} //end namespace MicroOcpp + +#endif diff --git a/src/MicroOcpp/Version.h b/src/MicroOcpp/Version.h index d4604681..696caef9 100644 --- a/src/MicroOcpp/Version.h +++ b/src/MicroOcpp/Version.h @@ -1 +1 @@ -#define MO_VERSION "1.0.0" +#define MO_VERSION "1.1.0" diff --git a/src/MicroOcpp_c.cpp b/src/MicroOcpp_c.cpp index 4041a368..50475f5b 100644 --- a/src/MicroOcpp_c.cpp +++ b/src/MicroOcpp_c.cpp @@ -5,15 +5,18 @@ #include "MicroOcpp_c.h" #include "MicroOcpp.h" +#include + +#include #include MicroOcpp::Connection *ocppSocket = nullptr; void ocpp_initialize(OCPP_Connection *conn, const char *chargePointModel, const char *chargePointVendor, struct OCPP_FilesystemOpt fsopt, bool autoRecover) { - ocpp_initialize_full(conn, ChargerCredentials(chargePointModel, chargePointVendor), fsopt, autoRecover); + ocpp_initialize_full(conn, ChargerCredentials(chargePointModel, chargePointVendor), fsopt, autoRecover, NULL); } -void ocpp_initialize_full(OCPP_Connection *conn, const char *bootNotificationCredentials, struct OCPP_FilesystemOpt fsopt, bool autoRecover) { +void ocpp_initialize_full(OCPP_Connection *conn, const char *bootNotificationCredentials, struct OCPP_FilesystemOpt fsopt, bool autoRecover, ocpp_certificate_store *certs) { if (!conn) { MO_DBG_ERR("conn is null"); } @@ -22,7 +25,12 @@ void ocpp_initialize_full(OCPP_Connection *conn, const char *bootNotificationCre MicroOcpp::FilesystemOpt adaptFsopt = fsopt; - mocpp_initialize(*ocppSocket, bootNotificationCredentials, MicroOcpp::makeDefaultFilesystemAdapter(adaptFsopt), autoRecover); + std::unique_ptr certsCwrapper; + if (certs) { + certsCwrapper = MicroOcpp::makeCertificateStoreCwrapper(certs); + } + + mocpp_initialize(*ocppSocket, bootNotificationCredentials, MicroOcpp::makeDefaultFilesystemAdapter(adaptFsopt), autoRecover, std::move(certsCwrapper)); } void ocpp_deinitialize() { diff --git a/src/MicroOcpp_c.h b/src/MicroOcpp_c.h index df9d4402..f3a259bf 100644 --- a/src/MicroOcpp_c.h +++ b/src/MicroOcpp_c.h @@ -10,6 +10,7 @@ #include #include #include +#include struct OCPP_Connection; typedef struct OCPP_Connection OCPP_Connection; @@ -63,7 +64,8 @@ void ocpp_initialize_full( OCPP_Connection *conn, //WebSocket adapter for MicroOcpp const char *bootNotificationCredentials, //e.g. '{"chargePointModel":"Demo Charger","chargePointVendor":"My Company Ltd."}' (refer to OCPP 1.6 Specification - Edition 2 p. 60) struct OCPP_FilesystemOpt fsopt, //If this library should format the flash if necessary. Find further options in ConfigurationOptions.h - bool autoRecover); //automatically sanitize the local data store when the lib detects recurring crashes. During development, `false` is recommended + bool autoRecover, //automatically sanitize the local data store when the lib detects recurring crashes. During development, `false` is recommended + ocpp_certificate_store *certs); //optional. If provided, use given Cert Store, if NULL, use default (default depends on MbedTLS) void ocpp_deinitialize(); diff --git a/tests/Certificates.cpp b/tests/Certificates.cpp new file mode 100644 index 00000000..01535e67 --- /dev/null +++ b/tests/Certificates.cpp @@ -0,0 +1,244 @@ +#include +#include +#include "./catch2/catch.hpp" +#include "./helpers/testHelper.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include + +#if !MO_ENABLE_MBEDTLS +#error Certificates unit tests depend on MbedTLS +#endif + + +#define BASE_TIME "2023-01-01T00:00:00.000Z" + +//ISRG Root X1 +const char *root_cert = R"(-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 +WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu +ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc +h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ +0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U +A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW +T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH +B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC +B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv +KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn +OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn +jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw +qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI +rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq +hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL +ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ +3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK +NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 +ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur +TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC +jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc +oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq +4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA +mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d +emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= +-----END CERTIFICATE----- +)"; + +//precomputed identifiers of root cert above, based on Open Certificate Status Protocol (OCSP) +const char *root_cert_hash_algorithm = "SHA256"; //algorithm used for the following hashes +const char *root_cert_hash_issuer_name = "998AC9E24B24D4008398DFA1375FD5D3DA7F2827E547ABFE5DB367B9E2CD5B20"; +const char *root_cert_hash_issuer_key = "0B9FA5A59EED715C26C1020C711B4F6EC42D58B0015E14337A39DAD301C5AFC3"; +const char *root_cert_hash_serial_number = "8210CFB0D240E3594463E0BB63828B00"; + +using namespace MicroOcpp; + +TEST_CASE( "M - Certificates" ) { + printf("\nRun %s\n", "M - Certificates"); + + //clean state + auto filesystem = makeDefaultFilesystemAdapter(FilesystemOpt::Use_Mount_FormatOnFail); + FilesystemUtils::remove_if(filesystem, [] (const char*) {return true;}); + + //initialize Context with dummy socket + LoopbackConnection loopback; + + mocpp_set_timer(custom_timer_cb); + + mocpp_initialize(loopback, ChargerCredentials("test-runner")); + auto& model = getOcppContext()->getModel(); + auto certService = model.getCertificateService(); + SECTION("CertificateService initialized") { + REQUIRE(certService != nullptr); + } + auto certs = certService->getCertificateStore(); + SECTION("CertificateStore initialized") { + REQUIRE(certs != nullptr); + } + + auto connector = model.getConnector(1); + model.getClock().setTime(BASE_TIME); + + loop(); + + SECTION("M05 Install CA cert -- sent cert is valid") { + auto ret = certs->installCertificate(InstallCertificateType::CSMSRootCertificate, root_cert); + REQUIRE(ret == InstallCertificateStatus::Accepted); + + size_t msize; + char fn [MO_MAX_PATH_SIZE]; + printCertFn(MO_CERT_FN_CSMS_ROOT, 0, fn, MO_MAX_PATH_SIZE); + REQUIRE(filesystem->stat(fn, &msize) == 0); + REQUIRE(msize == strlen(root_cert)); + } + + SECTION("M03 Retrieve list of available certs -- one cert available") { + auto ret1 = certs->installCertificate(InstallCertificateType::CSMSRootCertificate, root_cert); + REQUIRE(ret1 == InstallCertificateStatus::Accepted); + + std::vector chain; + auto ret2 = certs->getCertificateIds(GetCertificateIdType::CSMSRootCertificate, chain); + + REQUIRE(ret2 == GetInstalledCertificateStatus::Accepted); + REQUIRE(chain.size() == 1); + + auto& chainElem = chain.front(); + + REQUIRE(chainElem.certificateType == GetCertificateIdType::CSMSRootCertificate); + auto& certHash = chainElem.certificateHashData; + + REQUIRE(!strcmp(certHash.getHashAlgorithmCStr(), root_cert_hash_algorithm)); //if this fails, please update the precomputed test hashes + REQUIRE(!strcmp(certHash.getIssuerNameHash(), root_cert_hash_issuer_name)); + REQUIRE(!strcmp(certHash.getIssuerKeyHash(), root_cert_hash_issuer_key)); + REQUIRE(!strcmp(certHash.getSerialNumber(), root_cert_hash_serial_number)); + REQUIRE(chainElem.childCertificateHashData.empty()); //no sub certs sent + } + + SECTION("M04 Delete a specific cert -- specified cert exists") { + auto ret1 = certs->installCertificate(InstallCertificateType::CSMSRootCertificate, root_cert); + REQUIRE(ret1 == InstallCertificateStatus::Accepted); + + std::vector chain; + auto ret2 = certs->getCertificateIds(GetCertificateIdType::CSMSRootCertificate, chain); + REQUIRE(ret2 == GetInstalledCertificateStatus::Accepted); + + REQUIRE(chain.size() == 1); + + auto ret3 = certs->deleteCertificate(chain.front().certificateHashData); + REQUIRE(ret3 == DeleteCertificateStatus::Accepted); + + ret2 = certs->getCertificateIds(GetCertificateIdType::CSMSRootCertificate, chain); + REQUIRE(ret2 == GetInstalledCertificateStatus::NotFound); + + REQUIRE(chain.size() == 0); + + size_t msize; + char fn [MO_MAX_PATH_SIZE]; + printCertFn(MO_CERT_FN_CSMS_ROOT, 0, fn, MO_MAX_PATH_SIZE); + REQUIRE(filesystem->stat(fn, &msize) != 0); + } + + SECTION("M05 InstallCertificate operation") { + + bool checkProcessed = false; + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "InstallCertificate", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument( + JSON_OBJECT_SIZE(2))); + auto payload = doc->to(); + payload["certificateType"] = "CSMSRootCertificate"; //of InstallCertificateTypeEnumType + payload["certificate"] = root_cert; + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE( !strcmp(payload["status"] | "_Undefined", "Accepted") ); + } + ))); + loop(); + REQUIRE( checkProcessed ); + + size_t msize; + char fn [MO_MAX_PATH_SIZE]; + printCertFn(MO_CERT_FN_CSMS_ROOT, 0, fn, MO_MAX_PATH_SIZE); + REQUIRE(filesystem->stat(fn, &msize) == 0); + REQUIRE(msize == strlen(root_cert)); + } + + SECTION("M04 DeleteCertificate operation") { + auto ret = certs->installCertificate(InstallCertificateType::CSMSRootCertificate, root_cert); + REQUIRE(ret == InstallCertificateStatus::Accepted); + + bool checkProcessed = false; + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "DeleteCertificate", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument( + JSON_OBJECT_SIZE(4))); + auto payload = doc->to(); + payload["hashAlgorithm"] = root_cert_hash_algorithm; //of HashAlgorithmEnumType + payload["issuerNameHash"] = root_cert_hash_issuer_name; + payload["issuerKeyHash"] = root_cert_hash_issuer_key; + payload["serialNumber"] = root_cert_hash_serial_number; + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE( !strcmp(payload["status"] | "_Undefined", "Accepted") ); + } + ))); + loop(); + REQUIRE( checkProcessed ); + } + + SECTION("M03 GetInstalledCertificateIds operation") { + auto ret = certs->installCertificate(InstallCertificateType::CSMSRootCertificate, root_cert); + REQUIRE(ret == InstallCertificateStatus::Accepted); + + bool checkProcessed = false; + getOcppContext()->initiateRequest(makeRequest(new Ocpp16::CustomOperation( + "GetInstalledCertificateIds", + [] () { + //create req + auto doc = std::unique_ptr(new DynamicJsonDocument( + JSON_OBJECT_SIZE(1))); + auto payload = doc->to(); + payload["certificateType"] = "CSMSRootCertificate"; //of GetCertificateIdTypeEnumType + return doc;}, + [&checkProcessed] (JsonObject payload) { + //receive conf + checkProcessed = true; + + REQUIRE( !strcmp(payload["status"] | "_Undefined", "Accepted") ); + REQUIRE( payload["certificateHashDataChain"].size() == 1 ); + JsonObject certificateHashDataChain = payload["certificateHashDataChain"][0]; + REQUIRE( !strcmp(certificateHashDataChain["certificateType"] | "_Undefined", "CSMSRootCertificate") ); + JsonObject certificateHashData = certificateHashDataChain["certificateHashData"]; + REQUIRE( !strcmp(certificateHashData["hashAlgorithm"] | "_Undefined", root_cert_hash_algorithm) ); //if this fails, please update the precomputed test hashes + REQUIRE( !strcmp(certificateHashData["issuerNameHash"] | "_Undefined", root_cert_hash_issuer_name) ); + REQUIRE( !strcmp(certificateHashData["issuerKeyHash"] | "_Undefined", root_cert_hash_issuer_key) ); + REQUIRE( !strcmp(certificateHashData["serialNumber"] | "_Undefined", root_cert_hash_serial_number) ); + REQUIRE( !certificateHashDataChain.containsKey("childCertificateHashData") ); + } + ))); + loop(); + REQUIRE( checkProcessed ); + } + + mocpp_deinitialize(); +} diff --git a/tests/LocalAuthList.cpp b/tests/LocalAuthList.cpp index 9104ad87..208b12e6 100644 --- a/tests/LocalAuthList.cpp +++ b/tests/LocalAuthList.cpp @@ -152,7 +152,7 @@ TEST_CASE( "LocalAuth" ) { REQUIRE( connector->getStatus() == ChargePointStatus::Available ); - ulong t_before = mocpp_tick_ms(); + unsigned long t_before = mocpp_tick_ms(); beginTransaction("mIdTag"); loop(); @@ -219,7 +219,7 @@ TEST_CASE( "LocalAuth" ) { REQUIRE( connector->getStatus() == ChargePointStatus::Available ); - ulong t_before = mocpp_tick_ms(); + unsigned long t_before = mocpp_tick_ms(); beginTransaction("unknownIdTag"); loop(); @@ -327,7 +327,7 @@ TEST_CASE( "LocalAuth" ) { REQUIRE( connector->getStatus() == ChargePointStatus::Available ); - ulong t_before = mocpp_tick_ms(); + unsigned long t_before = mocpp_tick_ms(); beginTransaction("mIdTagExpired"); loop();