diff --git a/src/app_config.cpp b/src/app_config.cpp index dd9a642e..a8a30bd5 100644 --- a/src/app_config.cpp +++ b/src/app_config.cpp @@ -82,6 +82,7 @@ String ocpp_server; String ocpp_chargeBoxId; String ocpp_authkey; String ocpp_idtag; +String ocpp_certificate_id; // Time String time_zone; @@ -195,6 +196,7 @@ ConfigOpt *opts[] = new ConfigOptDefinition(ocpp_chargeBoxId, "", "ocpp_chargeBoxId", "cid"), new ConfigOptDefinition(ocpp_authkey, "", "ocpp_authkey", "oky"), new ConfigOptDefinition(ocpp_idtag, "DefaultIdTag", "ocpp_idtag", "idt"), + new ConfigOptDefinition(ocpp_certificate_id, "", "ocpp_certificate_id", "oci"), // Ohm Connect Settings new ConfigOptDefinition(ohm, "", "ohm", "o"), @@ -252,6 +254,7 @@ ConfigOpt *opts[] = new ConfigOptVirtualMaskedBool(flagsOpt, flagsChanged, CONFIG_SERVICE_OCPP, CONFIG_SERVICE_OCPP, "ocpp_enabled", "ope"), new ConfigOptVirtualMaskedBool(flagsOpt, flagsChanged, CONFIG_OCPP_AUTO_AUTH, CONFIG_OCPP_AUTO_AUTH, "ocpp_auth_auto", "oaa"), new ConfigOptVirtualMaskedBool(flagsOpt, flagsChanged, CONFIG_OCPP_OFFLINE_AUTH, CONFIG_OCPP_OFFLINE_AUTH, "ocpp_auth_offline", "ooa"), + new ConfigOptVirtualMaskedBool(flagsOpt, flagsChanged, CONFIG_OCPP_ALLOW_ANY_CERT, 0, "ocpp_reject_unauthorized", "oru"), new ConfigOptVirtualMaskedBool(flagsOpt, flagsChanged, CONFIG_OCPP_ACCESS_SUSPEND, CONFIG_OCPP_ACCESS_SUSPEND, "ocpp_suspend_evse", "ops"), new ConfigOptVirtualMaskedBool(flagsOpt, flagsChanged, CONFIG_OCPP_ACCESS_ENERGIZE, CONFIG_OCPP_ACCESS_ENERGIZE, "ocpp_energize_plug", "opn"), new ConfigOptVirtualMaskedBool(flagsOpt, flagsChanged, CONFIG_RFID, CONFIG_RFID, "rfid_enabled", "rf"), diff --git a/src/app_config.h b/src/app_config.h index 1209596c..c2bdf7e9 100644 --- a/src/app_config.h +++ b/src/app_config.h @@ -70,6 +70,7 @@ extern String ocpp_server; extern String ocpp_chargeBoxId; extern String ocpp_authkey; extern String ocpp_idtag; +extern String ocpp_certificate_id; // RFID storage extern String rfid_storage; @@ -129,6 +130,7 @@ extern uint32_t flags; #define CONFIG_THREEPHASE (1 << 24) #define CONFIG_WIZARD (1 << 25) #define CONFIG_DEFAULT_STATE (1 << 26) +#define CONFIG_OCPP_ALLOW_ANY_CERT (1 << 27) #define INITIAL_CONFIG_VERSION 1 @@ -180,6 +182,10 @@ inline bool config_ocpp_offline_authorization() { return CONFIG_OCPP_OFFLINE_AUTH == (flags & CONFIG_OCPP_OFFLINE_AUTH); } +inline bool config_ocpp_reject_unauthorized() { + return 0 == (flags & CONFIG_OCPP_ALLOW_ANY_CERT); +} + inline bool config_divert_enabled() { return CONFIG_SERVICE_DIVERT == (flags & CONFIG_SERVICE_DIVERT); } diff --git a/src/ocpp.cpp b/src/ocpp.cpp index 2a9e27ca..4115d989 100644 --- a/src/ocpp.cpp +++ b/src/ocpp.cpp @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include "app_config.h" #include "http_update.h" @@ -131,6 +133,64 @@ void OcppTask::notifyConfigChanged() { } } +OcppTask::TlsConfig OcppTask::resolveTlsConfig() { + TlsConfig cfg{nullptr, config_ocpp_reject_unauthorized()}; + + if (!cfg.rejectUnauthorized) { + return cfg; + } + + const char *selected = nullptr; + + if (ocpp_certificate_id.length() > 0) { + char *end = nullptr; + uint64_t cert_id = strtoull(ocpp_certificate_id.c_str(), &end, 16); + if (end != ocpp_certificate_id.c_str() && *end == '\0') { + const char *cert = certs.getCertificate(cert_id); + if (cert && cert[0] != '\0') { + selected = cert; + } else { + DBUGF("[ocpp] certificate %s not found, falling back to default root CA", ocpp_certificate_id.c_str()); + } + } else { + DBUGF("[ocpp] certificate id %s is invalid, falling back to default root CA", ocpp_certificate_id.c_str()); + } + } + + if (!selected) { + selected = certs.getRootCa(); + } + + cfg.caCert = selected; + return cfg; +} + +bool OcppTask::applySecurityConfig() { + if (!connection) { + return false; + } + + TlsConfig desired = resolveTlsConfig(); + + bool caChanged = false; + if (desired.caCert == nullptr || appliedCaCert == nullptr) { + caChanged = desired.caCert != appliedCaCert; + } else { + caChanged = std::strcmp(desired.caCert, appliedCaCert) != 0; + } + + bool changed = !tlsConfigApplied || desired.rejectUnauthorized != appliedRejectUnauthorized || caChanged; + + if (changed) { + connection->setCaCert(desired.caCert); + appliedRejectUnauthorized = desired.rejectUnauthorized; + appliedCaCert = desired.caCert; + tlsConfigApplied = true; + } + + return changed; +} + void OcppTask::reconfigure() { if (config_ocpp_enabled()) { @@ -141,11 +201,17 @@ void OcppTask::reconfigure() { initializeMicroOcpp(); } - if (!ocpp_server.equals(connection->getBackendUrl()) || + if (connection) { + bool credentialsChanged = !ocpp_server.equals(connection->getBackendUrl()) || !ocpp_chargeBoxId.equals(connection->getChargeBoxId()) || - !ocpp_authkey.equals(connection->getAuthKey())) { - //OpenEVSE WS URL configs have been updated - these must be applied manually - connection->reloadConfigs(); + !ocpp_authkey.equals(connection->getAuthKey()); + + bool tlsChanged = applySecurityConfig(); + + if (credentialsChanged || tlsChanged) { + //OpenEVSE WS URL configs have been updated - these must be applied manually + connection->reloadConfigs(); + } } } else { //OCPP disabled via OpenEVSE config @@ -240,7 +306,14 @@ void OcppTask::initializeMicroOcpp() { MicroOcpp::declareConfiguration( MO_CONFIG_EXT_PREFIX "SilentOfflineTransactions", true); //disable transaction journaling when being offline for a long time (can lead to data loss) - connection = new MicroOcpp::MOcppMongooseClient(Mongoose.getMgr(), nullptr, nullptr, nullptr, nullptr, filesystem); + TlsConfig tlsConfig = resolveTlsConfig(); + const char *initialCa = tlsConfig.caCert; + + connection = new MicroOcpp::MOcppMongooseClient(Mongoose.getMgr(), nullptr, nullptr, nullptr, initialCa, filesystem); + + appliedRejectUnauthorized = tlsConfig.rejectUnauthorized; + appliedCaCert = tlsConfig.caCert; + tlsConfigApplied = true; /* * Initialize the OCPP library and provide it with the charger credentials @@ -264,6 +337,9 @@ void OcppTask::deinitializeMicroOcpp() { mocpp_deinitialize(); delete connection; connection = nullptr; + tlsConfigApplied = false; + appliedCaCert = nullptr; + appliedRejectUnauthorized = false; } void OcppTask::setup() { diff --git a/src/ocpp.h b/src/ocpp.h index 546aa753..eadca6fc 100644 --- a/src/ocpp.h +++ b/src/ocpp.h @@ -41,10 +41,19 @@ class OcppTask: public MicroTasks::Task { void initializeMicroOcpp(); void deinitializeMicroOcpp(); void loadEvseBehavior(); + bool applySecurityConfig(); + struct TlsConfig { + const char *caCert; + bool rejectUnauthorized; + }; + TlsConfig resolveTlsConfig(); static OcppTask *instance; bool synchronizationLock = false; + bool tlsConfigApplied = false; + const char *appliedCaCert = nullptr; + bool appliedRejectUnauthorized = false; protected: //hook method of MicroTask::Task