Skip to content

Commit ff1c891

Browse files
authored
Merge pull request #39 from c-jimenez/dev/secure_connection_internal_certificates
Secure connection with internal certificate management
2 parents a513961 + 81970ef commit ff1c891

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+2361
-247
lines changed

README.md

Lines changed: 89 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ This implementation is based on the following libraries :
1717
+ [Supported OCPP feature profiles](#supported-ocpp-feature-profiles)
1818
+ [Supported OCPP configuration keys](#supported-ocpp-configuration-keys)
1919
+ [OCPP security extensions](#ocpp-security-extensions)
20+
* [Internal configuration keys](#internal-configuration-keys)
2021
* [Build](#build)
2122
* [Quick start](#quick-start)
2223
+ [Charge Point role](#charge-point-role)
@@ -38,7 +39,7 @@ As of this version :
3839

3940
* All the messages defined in the OCPP 1.6 edition 2 protocol have been implemented except GetCompositeSchedule for Charge Point role
4041
* All the configuration keys defined in the OCPP 1.6 edition 2 protocol have been implemented for the Charge Point role
41-
* Some Charge Point and Central System behavior related to the OCPP 1.6 security whitepaper edition 2 has been implemented (work in progress, see [OCPP security extensions](#ocpp-security-extensions))
42+
* Most of Charge Point and Central System behavior related to the OCPP 1.6 security whitepaper edition 2 has been implemented (work in progress, see [OCPP security extensions](#ocpp-security-extensions))
4243

4344
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...).
4445

@@ -51,6 +52,7 @@ The persistent data handled by **Open OCPP** is stored into a single file which
5152
+ Badge cache and local list
5253
+ Smart charging profile
5354
+ Logs
55+
* X.509 Certificates
5456

5557
* For Central System role :
5658

@@ -121,10 +123,10 @@ In the "Owner" column, "S" means that the configuration key behavior is handled
121123
| ChargingScheduleMaxPeriods | S | None |
122124
| ConnectorSwitch3to1PhaseSupported | S | None |
123125
| MaxChargingProfilesInstalled | S | None |
124-
| AdditionalRootCertificateCheck | U | None |
126+
| AdditionalRootCertificateCheck | U/S | Not implemented yet : implemented behavior is the same as if AdditionalRootCertificateCheck = False |
125127
| AuthorizationKey | S | None |
126128
| CertificateSignedMaxChainSize | S | None |
127-
| CertificateStoreMaxLength | U | None |
129+
| CertificateStoreMaxLength | U/S | If internal certificate management is enabled, the stack handle this parameter, otherwise it must be the user application |
128130
| CpoName | S | None |
129131
| SecurityProfile | S | None |
130132

@@ -139,34 +141,112 @@ In the "Owner" column, "S" means that the configuration key behavior is handled
139141
* 2 : TLS with HTTP Basic Authentication
140142
* 3 : TLS with Client Side Certificates
141143

142-
In Charge Point role, the stack will automatically disconnect and then reconnect to the Central System after one of the following parameters has been modified :
144+
In Charge Point role, the stack will automatically disconnect and then reconnect using the new parameters to the Central System after one of the following parameters has been modified :
143145
* **AuthorizationKey**
144146
* **Security Profile**
145147

146148
#### Security events
147149

148150
**Open OCPP** support the whole use cases of security events and logging.
149151

150-
In Charge Point role, it can optionnaly handle the storage of the security event log and the generation of the security log export when the Central System asks it. To enable/disable this feature, you have to modify the **SecurityLogMaxEntriesCount** charge point configuration value :
152+
In Charge Point role, it can optionnaly handle the storage of the security event log and the generation of the security log export when the Central System asks it. To enable/disable this feature, you have to modify the **SecurityLogMaxEntriesCount** charge point configuration key :
151153

152154
* 0 = **Open OCPP** will not store security event and the security log must be generated by the user application
153155
* \>0 = **Open OCPP** will store at max **SecurityLogMaxEntriesCount** (circular log) and will automatically generate the security log as a CSV file
154156

155157
In Charge Point role, the user application can generate custom security events and defines its criticity so that they are forwarded to the Central System.
156158

157-
In Charge Point role, the notification of security events can be enabled or disabled with the IChargePointConfig::securityEventNotificationEnabled() configuration. This can be usefull to disable them when the Central System does not implement the security extensions.
159+
In Charge Point role, the notification of security events can be enabled or disabled with the **SecurityEventNotificationEnabled** configuration key. This can be usefull to disable them when the Central System does not implement the security extensions.
158160

159161
#### Extended trigger messages
160162

161163
**Open OCPP** support this feature for both Charge Point and Central System roles.
162164

163-
#### Certificate management messages
165+
#### Certificate management
164166

165167
**Open OCPP** support this feature for both Charge Point and Central System roles.
166168

167-
The actual storage of the certificates and their keys must be done by the user application.
169+
The behavior of this feature is controlled by the **InternalCertificateManagementEnabled** configuration key.
168170

169-
**Open OCPP** provides callbacks and helper classes to ease certificate manipulation and installation.
171+
If **InternalCertificateManagementEnabled** is set to **false**, the actual storage of the certificates and their keys must be done by the user application. **Open OCPP** provides callbacks and helper classes to ease certificate manipulation and installation. The user application also has to configure the path to the installed certificates for the establishment of the secure connections using the following configuration keys :
172+
173+
* TlsServerCertificateCa
174+
* TlsClientCertificate
175+
* TlsClientCertificatePrivateKey
176+
* TlsClientCertificatePrivateKeyPassphrase
177+
178+
If **InternalCertificateManagementEnabled** is set to **true**, the storage of certificates and their keys is fully handled by **Open OCPP**. The user application just has to provide a passphrase using the **TlsClientCertificatePrivateKeyPassphrase** configuration key to securily encrypt the certicates' private keys using AES-256-CBC algorithm. **Open OCPP** will automatically use the installed corresponding certificates depending on the configured Security Profile and the certificates validity dates.
179+
180+
### Internal configuration keys
181+
182+
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.
183+
184+
#### Common keys
185+
186+
| Key | Type | Description |
187+
| :---: | :---: | :--- |
188+
| DatabasePath | string | Path to the database to store persistent data |
189+
| JsonSchemasPath | string | Path to the JSON schemas to validate the messages |
190+
| CallRequestTimeout | uint | Call request timeout in milliseconds |
191+
| Tlsv12CipherList | string | List of authorized ciphers for TLSv1.2 connections (OpenSSL format) |
192+
| Tlsv13CipherList | string | List of authorized ciphers for TLSv1.3 connections (OpenSSL format) |
193+
| LogMaxEntriesCount | uint | Maximum number of entries in the log (0 = no logs in database) |
194+
195+
#### Charge Point keys
196+
197+
| Key | Type | Description |
198+
| :---: | :---: | :--- |
199+
| ConnexionUrl | string | URL of the Central System |
200+
| ChargePointIdentifier | string | OCPP Charge Point identifier. Will be concatanated with the **ConnexionUrl** key |
201+
| ConnectionTimeout | uint | Connection timeout in milliseconds |
202+
| RetryInterval | uint | Retry interval when connection has failed in milliseconds |
203+
| ChargeBoxSerialNumber | string | Deprecated. Charge Box serial number for BootNotification message |
204+
| ChargePointModel | string | Charge Point model for BootNotification message |
205+
| ChargePointSerialNumber | string | Charge Point serial number for BootNotification message |
206+
| ChargePointVendor | string | Charge Point vendor for BootNotification message |
207+
| FirmwareVersion | string | Charge Point firmware version for BootNotification message |
208+
| Iccid | string | ICCID of the moden's SIM card for BootNotification message |
209+
| Imsi | string | IMSI of the moden's SIM card for BootNotification message |
210+
| MeterSerialNumber | string | Main electrical meter serial number for BootNotification message |
211+
| MeterType | string | Main electrical meter type for BootNotification message |
212+
| OperatingVoltage | float | Nominal operating voltage (needed for Watt to Amp conversions in smart charging profiles) |
213+
| AuthentCacheMaxEntriesCount | uint | Maximum number of entries in the authentication cache |
214+
| TlsServerCertificateCa | string | Path to Certification Authority signing chain to validate the Central System certificate |
215+
| TlsClientCertificate | string | Path to Charge Point certificate |
216+
| TlsClientCertificatePrivateKey | string | Path to Charge Point's certificate's private key |
217+
| TlsClientCertificatePrivateKeyPassphrase | string | Charge Point certificate's private key passphrase |
218+
| TlsAllowSelfSignedCertificates | bool | Allow TLS connections using self-signed certificates (Warning : enabling this feature is not recommended in production) |
219+
| TlsAllowExpiredCertificates | bool | Allow TLS connections using expired certificates (Warning : enabling this feature is not recommended in production) |
220+
| TlsAcceptNonTrustedCertificates | bool | Accept non trusted certificates for TLS connections (Warning : enabling this feature is not recommended in production) |
221+
| TlsSkipServerNameCheck | bool | Skip server name check in certificates for TLS connections (Warning : enabling this feature is not recommended in production) |
222+
| InternalCertificateManagementEnabled | bool | If true, certificates are stored inside **Open OCPP** databasen otherwise user application has to handle them|
223+
| SecurityEventNotificationEnabled | bool | Enable security event notification |
224+
| SecurityLogMaxEntriesCount | uint | Maximum number of entries in the security log (0 = no security logs in database) |
225+
| ClientCertificateRequestHashType | string | Hash type for certificate request generation : sha256, sha384 or sha512 |
226+
| ClientCertificateRequestKeyType | string | Key type for certificate request generation : ec or rsa |
227+
| ClientCertificateRequestRsaKeyLength | uint | Length in bits of the key for certificate request generation if rsa has been selected for key type : minimum 2048 |
228+
| ClientCertificateRequestEcCurve | string | Name of the elliptic curve for certificate request generation if ec has been selected for key type : prime256v1, secp256k1, secp384r1, secp521r1, brainpoolP256t1, brainpoolP384t1 or brainpoolP512t1 |
229+
| ClientCertificateRequestSubjectCountry | string | Country for the subject field of certificate request generation (can be left empty) |
230+
| ClientCertificateRequestSubjectState | string | State for the subject field of certificate request generation (can be left empty) |
231+
| ClientCertificateRequestSubjectLocation | string | Location for the subject field of certificate request generation (can be left empty) |
232+
| ClientCertificateRequestSubjectOrganizationUnit | string | Organization unit for the subject field of certificate request generation (can be left empty) |
233+
| ClientCertificateRequestSubjectEmail | string | Email for the subject field of certificate request generation (can be left empty) |
234+
235+
#### Central System keys
236+
237+
| Key | Type | Description |
238+
| :---: | :---: | :--- |
239+
| ListenUrl | string | URL to listen to incomming websocket connections |
240+
| WebSocketPingInterval | uint | Websocket PING interval in seconds |
241+
| BootNotificationRetryInterval | uint | Boot notification retry interval in second (sent in BootNotificationConf when status is Pending or Rejected) |
242+
| HeartbeatInterval | uint | Heartbeat interval in seconds (sent in BootNotificationConf when status is Accepted) |
243+
| HttpBasicAuthent | bool | If set to true, the Charge Points must autenticate themselves using HTTP Basic Authentication method |
244+
| TlsEcdhCurve | string | ECDH curve to use for TLS connections with EC keys |
245+
| TlsServerCertificate | string | Path to the Central System's certificate |
246+
| TlsServerCertificatePrivateKey | string | Path to the Central System's certificate's private key |
247+
| TlsServerCertificatePrivateKeyPassphrase | string | Central System's certificate's private key passphrase |
248+
| TlsServerCertificateCa | string | Path to the Certification Authority signing chain for the Central System's certificate |
249+
| TlsClientCertificateAuthent | bool | If set to true, the Charge Points must authenticate themselves using an X.509 certificate |
170250

171251
## Build
172252

examples/common/DefaultChargePointEventsHandler.cpp

Lines changed: 25 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ SOFTWARE.
2323
*/
2424

2525
#include "DefaultChargePointEventsHandler.h"
26+
#include "CertificateRequest.h"
2627
#include "ChargePointDemoConfig.h"
28+
#include "PrivateKey.h"
2729
#include "Sha2.h"
2830
#include "String.h"
2931

@@ -35,9 +37,10 @@ using namespace ocpp::types;
3537
using namespace ocpp::x509;
3638

3739
/** @brief Constructor */
38-
DefaultChargePointEventsHandler::DefaultChargePointEventsHandler(ChargePointDemoConfig& config)
40+
DefaultChargePointEventsHandler::DefaultChargePointEventsHandler(ChargePointDemoConfig& config, const std::filesystem::path& working_dir)
3941
: m_config(config),
4042
m_chargepoint(nullptr),
43+
m_working_dir(working_dir),
4144
m_remote_start_pending(config.ocppConfig().numberOfConnectors()),
4245
m_remote_stop_pending(m_remote_start_pending.size()),
4346
m_remote_start_id_tag(m_remote_start_pending.size())
@@ -338,7 +341,7 @@ ocpp::types::CertificateStatusEnumType DefaultChargePointEventsHandler::caCertif
338341

339342
std::stringstream name;
340343
name << "fw_" << sha256.resultString() << ".pem";
341-
ca_filename = name.str();
344+
ca_filename = m_working_dir / name.str();
342345
}
343346
else
344347
{
@@ -355,7 +358,7 @@ ocpp::types::CertificateStatusEnumType DefaultChargePointEventsHandler::caCertif
355358

356359
std::stringstream name;
357360
name << "cs_" << sha256.resultString() << ".pem";
358-
ca_filename = name.str();
361+
ca_filename = m_working_dir / name.str();
359362
}
360363

361364
// Check if the certificate must be saved
@@ -405,7 +408,7 @@ bool DefaultChargePointEventsHandler::chargePointCertificateReceived(const ocpp:
405408

406409
std::stringstream name;
407410
name << "cp_" << sha256.resultString() << ".pem";
408-
std::string cert_filename = name.str();
411+
std::string cert_filename = m_working_dir / name.str();
409412

410413
// Save certificate
411414
if (certificate.toFile(cert_filename))
@@ -465,7 +468,7 @@ ocpp::types::DeleteCertificateStatusEnumType DefaultChargePointEventsHandler::de
465468
}
466469

467470
// Look for installed certificates
468-
for (auto const& dir_entry : std::filesystem::directory_iterator{std::filesystem::current_path()})
471+
for (auto const& dir_entry : std::filesystem::directory_iterator{m_working_dir})
469472
{
470473
if (!dir_entry.is_directory())
471474
{
@@ -507,63 +510,22 @@ void DefaultChargePointEventsHandler::generateCsr(std::string& csr)
507510
cout << "Generate CSR requested" << endl;
508511

509512
// Generata a new public/private key pair
510-
std::string generate_params_cmd_line = "openssl ecparam -name prime256v1 -out /tmp/charge_point_key.param";
511-
system(generate_params_cmd_line.c_str());
512-
std::string generate_key_cmd_line = "openssl ecparam -in /tmp/charge_point_key.param -genkey -noout -out /tmp/charge_point_key.key";
513-
system(generate_key_cmd_line.c_str());
514-
515-
// Create configuration file to generate the CSR
516-
std::stringstream csr_config;
517-
csr_config << R"([req]
518-
distinguished_name = req_distinguished_name
519-
520-
# Stop confirmation prompts. All information is contained below.
521-
prompt = no
522-
523-
# The extensions to add to a certificate request
524-
x509_extensions = v3_ca
525-
526-
[req_distinguished_name]
527-
countryName = FR
528-
stateOrProvinceName = Savoie
529-
localityName = Chambery
530-
organizationName =)"
531-
<< m_config.ocppConfig().cpoName() << R"(
532-
organizationalUnitName = Open OCPP Charge Points
533-
commonName =)"
534-
<< m_config.stackConfig().chargePointSerialNumber() << R"(
535-
emailAddress = [email protected]
536-
537-
[v3_ca]
538-
basicConstraints = CA:FALSE
539-
subjectAltName = @alt_names
540-
541-
[alt_names]
542-
DNS.1 = localhost
543-
DNS.2 = IP:127.0.0.1)";
544-
std::fstream csr_config_file("/tmp/charge_point_csr.cnf", csr_config_file.out);
545-
if (csr_config_file.is_open())
546-
{
547-
csr_config_file << csr_config.str();
548-
csr_config_file.close();
549-
}
513+
PrivateKey private_key(PrivateKey::Type::EC,
514+
static_cast<unsigned int>(PrivateKey::Curve::PRIME256_V1),
515+
m_config.stackConfig().tlsClientCertificatePrivateKeyPassphrase());
516+
private_key.privateToFile("/tmp/charge_point_key.key");
550517

551518
// Generate the CSR
552-
std::string generate_csr_cmd_line = "openssl req -new -sha256 -key /tmp/charge_point_key.key -extensions v3_ca -config "
553-
"/tmp/charge_point_csr.cnf -out /tmp/charge_point.csr";
554-
system(generate_csr_cmd_line.c_str());
555-
556-
// Read generated CSR file
557-
std::fstream csr_file("/tmp/charge_point.csr", csr_config_file.in | csr_config_file.binary | csr_config_file.ate);
558-
if (csr_file.is_open())
559-
{
560-
auto filesize = csr_file.tellg();
561-
csr_file.seekg(0, csr_file.beg);
562-
csr.resize(filesize);
563-
csr_file.read(&csr[0], filesize);
564-
565-
csr_file.close();
566-
}
519+
CertificateRequest::Subject subject;
520+
subject.country = m_config.stackConfig().clientCertificateRequestSubjectCountry();
521+
subject.state = m_config.stackConfig().clientCertificateRequestSubjectState();
522+
subject.location = m_config.stackConfig().clientCertificateRequestSubjectLocation();
523+
subject.organization = m_config.ocppConfig().cpoName();
524+
subject.organization_unit = m_config.stackConfig().clientCertificateRequestSubjectOrganizationUnit();
525+
subject.common_name = m_config.stackConfig().chargePointSerialNumber();
526+
subject.email_address = m_config.stackConfig().clientCertificateRequestSubjectEmail();
527+
CertificateRequest certificate_request(subject, private_key);
528+
csr = certificate_request.pem();
567529
}
568530

569531
/** @copydoc void IChargePointEventsHandler::getInstalledCertificates(ocpp::types::CertificateUseEnumType,
@@ -573,7 +535,7 @@ void DefaultChargePointEventsHandler::getInstalledCertificates(ocpp::types::Cert
573535
{
574536
cout << "Get installed CA certificates requested : type = " << CertificateUseEnumTypeHelper.toString(type) << endl;
575537

576-
for (auto const& dir_entry : std::filesystem::directory_iterator{std::filesystem::current_path()})
538+
for (auto const& dir_entry : std::filesystem::directory_iterator{m_working_dir})
577539
{
578540
if (!dir_entry.is_directory())
579541
{
@@ -648,7 +610,7 @@ bool DefaultChargePointEventsHandler::hasCentralSystemCaCertificateInstalled()
648610
bool DefaultChargePointEventsHandler::hasChargePointCertificateInstalled()
649611
{
650612
// A better implementation would also check the validity dates of the certificates
651-
for (auto const& dir_entry : std::filesystem::directory_iterator{std::filesystem::current_path()})
613+
for (auto const& dir_entry : std::filesystem::directory_iterator{m_working_dir})
652614
{
653615
if (!dir_entry.is_directory())
654616
{
@@ -671,7 +633,7 @@ bool DefaultChargePointEventsHandler::hasChargePointCertificateInstalled()
671633
unsigned int DefaultChargePointEventsHandler::getNumberOfCaCertificateInstalled(bool manufacturer, bool central_system)
672634
{
673635
unsigned int count = 0;
674-
for (auto const& dir_entry : std::filesystem::directory_iterator{std::filesystem::current_path()})
636+
for (auto const& dir_entry : std::filesystem::directory_iterator{m_working_dir})
675637
{
676638
if (!dir_entry.is_directory())
677639
{

0 commit comments

Comments
 (0)