Skip to content

Commit

Permalink
OCPP1.6 P&C Handling adjustments (#538)
Browse files Browse the repository at this point in the history
* ocpp16 adjustments for plug&charge token validation
* update libevse-security dependency

Signed-off-by: pietfried <[email protected]>

* Bump libocpp version to 0.10.0

Signed-off-by: Kai-Uwe Hermann <[email protected]>

* Update libevse-security to v0.5.0 tag

This points to the same commit as the git rev used before

Signed-off-by: Kai-Uwe Hermann <[email protected]>

---------

Signed-off-by: pietfried <[email protected]>
Signed-off-by: Kai-Uwe Hermann <[email protected]>
Co-authored-by: Kai-Uwe Hermann <[email protected]>
  • Loading branch information
Pietfried and hikinggrass authored Apr 2, 2024
1 parent ce79223 commit e1ae0bc
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 79 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.14)

project(ocpp
VERSION 0.9.8
VERSION 0.10.0
DESCRIPTION "A C++ implementation of the Open Charge Point Protocol"
LANGUAGES CXX
)
Expand Down
2 changes: 1 addition & 1 deletion dependencies.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ websocketpp:
git_tag: 0.8.2
libevse-security:
git: https://github.com/EVerest/libevse-security.git
git_tag: bce1ba4
git_tag: v0.5.0
libwebsockets:
git: https://github.com/warmcat/libwebsockets.git
git_tag: v4.3.3
Expand Down
1 change: 1 addition & 0 deletions include/ocpp/common/evse_security.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ ocpp::v201::DeleteCertificateStatusEnum to_ocpp_v201(ocpp::DeleteCertificateResu
ocpp::v201::CertificateHashDataType to_ocpp_v201(ocpp::CertificateHashDataType other);
ocpp::v201::CertificateHashDataChain to_ocpp_v201(ocpp::CertificateHashDataChain other);
ocpp::v201::OCSPRequestData to_ocpp_v201(ocpp::OCSPRequestData other);
std::vector<ocpp::v201::OCSPRequestData> to_ocpp_v201(const std::vector<ocpp::OCSPRequestData>& ocsp_request_data);

ocpp::CertificateType from_ocpp_v201(ocpp::v201::GetCertificateIdUseEnum other);
std::vector<ocpp::CertificateType> from_ocpp_v201(const std::vector<ocpp::v201::GetCertificateIdUseEnum>& other);
Expand Down
5 changes: 3 additions & 2 deletions include/ocpp/v16/charge_point_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -415,9 +415,10 @@ class ChargePointImpl : ocpp::ChargingStationBase {

/// \brief Authorizes the provided \p id_token against the central system, LocalAuthorizationList or
/// AuthorizationCache depending on the values of the ConfigurationKeys LocalPreAuthorize, LocalAuthorizeOffline,
/// LocalAuthListEnabled and AuthorizationCacheEnabled
/// LocalAuthListEnabled and AuthorizationCacheEnabled. If \p authorize_only_locally is true, no Authorize.req will
/// be sent to the CSMS but only LocalAuthorizationList and LocalAuthorizationCache will be used for the validation
/// \returns the IdTagInfo
IdTagInfo authorize_id_token(CiString<20> id_token);
IdTagInfo authorize_id_token(CiString<20> id_token, const bool authorize_only_locally = false);

// for plug&charge 1.6 whitepaper

Expand Down
5 changes: 0 additions & 5 deletions include/ocpp/v201/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -664,11 +664,6 @@ class ChargePoint : ocpp::ChargingStationBase {
/// \return True on success. False if evse id does not exist.
bool on_charging_state_changed(const uint32_t evse_id, ChargingStateEnum charging_state);

/// \brief Generates OCSP request data from a (contract) certificate chain
/// \param certificate
/// \return vector with OCSP request data
std::vector<OCSPRequestData> generate_mo_ocsp_data(const CiString<5500>& certificate);

/// \brief Validates provided \p id_token \p certificate and \p ocsp_request_data using CSMS, AuthCache or AuthList
/// \param id_token
/// \param certificate
Expand Down
9 changes: 9 additions & 0 deletions lib/ocpp/common/evse_security.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,15 @@ ocpp::v201::OCSPRequestData to_ocpp_v201(ocpp::OCSPRequestData other) {
return lhs;
}

std::vector<ocpp::v201::OCSPRequestData> to_ocpp_v201(const std::vector<ocpp::OCSPRequestData>& ocsp_request_data) {
std::vector<ocpp::v201::OCSPRequestData> ocsp_request_data_list;
for (const auto& ocsp_data : ocsp_request_data) {
ocpp::v201::OCSPRequestData request = to_ocpp_v201(ocsp_data);
ocsp_request_data_list.push_back(request);
}
return ocsp_request_data_list;
}

ocpp::CertificateType from_ocpp_v201(ocpp::v201::GetCertificateIdUseEnum other) {
switch (other) {
case ocpp::v201::GetCertificateIdUseEnum::V2GRootCertificate:
Expand Down
167 changes: 124 additions & 43 deletions lib/ocpp/v16/charge_point_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <ocpp/v16/charge_point.hpp>
#include <ocpp/v16/charge_point_configuration.hpp>
#include <ocpp/v16/charge_point_impl.hpp>
#include <ocpp/v201/utils.hpp>

#include <optional>

Expand Down Expand Up @@ -2629,7 +2630,7 @@ void ChargePointImpl::status_notification(const int32_t connector, const ChargeP

// public API for Core profile

IdTagInfo ChargePointImpl::authorize_id_token(CiString<20> idTag) {
IdTagInfo ChargePointImpl::authorize_id_token(CiString<20> idTag, const bool authorize_only_locally) {
// only do authorize req when authorization locally not enabled or fails
// proritize auth list over auth cache for same idTags

Expand All @@ -2656,6 +2657,10 @@ IdTagInfo ChargePointImpl::authorize_id_token(CiString<20> idTag) {
}
}

if (authorize_only_locally) {
return {AuthorizationStatus::Invalid};
}

AuthorizeRequest req;
req.idTag = idTag;

Expand Down Expand Up @@ -2743,8 +2748,6 @@ ocpp::v201::AuthorizeResponse ChargePointImpl::data_transfer_pnc_authorize(
return authorize_response;
}

// FIXME(piet): Handle C07.FR.06 - C07.FR.12

DataTransferRequest req;
req.vendorId = ISO15118_PNC_VENDOR_ID;
req.messageId.emplace(CiString<50>(std::string("Authorize")));
Expand All @@ -2754,58 +2757,136 @@ ocpp::v201::AuthorizeResponse ChargePointImpl::data_transfer_pnc_authorize(

id_token.type = ocpp::v201::IdTokenEnum::eMAID;
id_token.idToken = emaid;
authorize_req.iso15118CertificateHashData = iso15118_certificate_hash_data;
authorize_req.idToken = id_token;

// C07.FR.06: If Charging Station is not able to validate a contract certificate, because it does not have the
// associated root certificate AND CentralContractValidationAllowed is true
// certificate.has_value() implies that ISO module could not validate certificate, otherwise certificate would not
// be set
if (certificate.has_value() and this->configuration->getCentralContractValidationAllowed().has_value() and
this->configuration->getCentralContractValidationAllowed().value()) {
authorize_req.certificate = certificate;
}

req.data.emplace(json(authorize_req).dump());

Call<DataTransferRequest> call(req, this->message_queue->createMessageId());
auto authorize_future = this->send_async<DataTransferRequest>(call);

auto enhanced_message = authorize_future.get();

if (enhanced_message.messageType == MessageType::DataTransferResponse) {
try {
// parse and return authorize response
ocpp::CallResult<DataTransferResponse> call_result = enhanced_message.message;
if (call_result.msg.data.has_value()) {
authorize_response = json::parse(call_result.msg.data.value());
return authorize_response;
// Temporary variable that is set to true to avoid immediate response to allow the local auth list
// or auth cache to be tried
bool try_local_auth_list_or_cache = false;
bool forward_to_csms = false;

if (this->websocket->is_connected() and iso15118_certificate_hash_data.has_value()) {
authorize_req.iso15118CertificateHashData = iso15118_certificate_hash_data;
forward_to_csms = true;
} else if (certificate.has_value()) {
// First try to validate the contract certificate locally
CertificateValidationResult local_verify_result =
this->evse_security->verify_certificate(certificate.value(), ocpp::LeafCertificateType::MO);
EVLOG_info << "Local contract validation result: " << local_verify_result;
const auto central_contract_validation_allowed =
this->configuration->getCentralContractValidationAllowed().value_or(false);
const auto contract_validation_offline = this->configuration->getContractValidationOffline();
const auto local_authorize_offline = this->configuration->getLocalAuthorizeOffline();

// C07.FR.01: When CS is online, it shall send an AuthorizeRequest
// C07.FR.02: The AuthorizeRequest shall at least contain the OCSP data
if (this->websocket->is_connected()) {
if (local_verify_result == CertificateValidationResult::IssuerNotFound) {
// C07.FR.06: Pass contract validation to CSMS when no contract root is found
if (central_contract_validation_allowed) {
EVLOG_info << "Online: No local contract root found. Pass contract validation to CSMS";
authorize_req.certificate = certificate.value();
forward_to_csms = true;
} else {
EVLOG_warning << "Online: Central Contract Validation not allowed";
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Invalid;
}
} else {
EVLOG_warning << "CSMS response of DataTransferRequest(Authorize) did not include data";
// Try to generate the OCSP data from the certificate chain and use that
const auto generated_ocsp_request_data_list = ocpp::evse_security_conversions::to_ocpp_v201(
this->evse_security->get_mo_ocsp_request_data(certificate.value()));
if (generated_ocsp_request_data_list.size() > 0) {
EVLOG_info << "Online: Pass generated OCSP data to CSMS";
authorize_req.iso15118CertificateHashData = generated_ocsp_request_data_list;
forward_to_csms = true;
} else {
EVLOG_warning << "Online: OCSP data could not be generated";
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Invalid;
}
}
} else { // Offline
// C07.FR.08: CS shall try to validate the contract locally
if (contract_validation_offline) {
EVLOG_info << "Offline: contract " << local_verify_result;
switch (local_verify_result) {
// C07.FR.09: CS shall lookup the eMAID in Local Auth List or Auth Cache when
// local validation succeeded
case CertificateValidationResult::Valid:
// In C07.FR.09 LocalAuthorizeOffline is mentioned, this seems to be a generic config item
// that applies to Local Auth List and Auth Cache, but since there are no requirements about
// it, lets check it here
if (local_authorize_offline) {
try_local_auth_list_or_cache = true;
} else {
// No requirement states what to do when ContractValidationOffline is true
// and LocalAuthorizeOffline is false
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Unknown;
authorize_response.certificateStatus = ocpp::v201::AuthorizeCertificateStatusEnum::Accepted;
}
break;
case CertificateValidationResult::Expired:
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Expired;
authorize_response.certificateStatus =
ocpp::v201::AuthorizeCertificateStatusEnum::CertificateExpired;
break;
default:
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Unknown;
break;
}
} else {
EVLOG_warning << "Offline: ContractValidationOffline is disabled. Validation not allowed";
// C07.FR.07: CS shall not allow charging
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::NotAtThisTime;
}
} catch (const json::exception& e) {
EVLOG_warning << "Could not parse data of DataTransfer message Authorize.conf: " << e.what();
} catch (const std::exception& e) {
EVLOG_error << "Unknown Error while handling DataTransfer message Authorize.conf: " << e.what();
}
} else {
EVLOG_warning << "Can not validate eMAID without certificate chain";
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Invalid;
}

} else if (enhanced_message.offline) {
if (this->configuration->getContractValidationOffline()) {
// C07.FR.08
// The Charging Station SHALL try to validate the contract certificate locally.
if (forward_to_csms) {
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Unknown;
// AuthorizeRequest sent to CSMS, let's show the results
req.data.emplace(json(authorize_req).dump());

// Send the DataTransfer(Authorize) to the CSMS
Call<DataTransferRequest> call(req, this->message_queue->createMessageId());
auto authorize_future = this->send_async<DataTransferRequest>(call);

// if valid: C07.FR.09
if (this->configuration->getLocalAuthorizeOffline()) {
// use auth cache to look up emaid
auto enhanced_message = authorize_future.get();

if (enhanced_message.messageType == MessageType::DataTransferResponse) {
try {
// parse and return authorize response
ocpp::CallResult<DataTransferResponse> call_result = enhanced_message.message;
if (call_result.msg.data.has_value()) {
authorize_response = json::parse(call_result.msg.data.value());
} else {
EVLOG_warning << "CSMS response of DataTransferRequest(Authorize) did not include data";
}
} catch (const json::exception& e) {
EVLOG_warning << "Could not parse data of DataTransfer message Authorize.conf: " << e.what();
} catch (const std::exception& e) {
EVLOG_error << "Unknown Error while handling DataTransfer message Authorize.conf: " << e.what();
}
} else if (enhanced_message.offline) {
EVLOG_warning << "No response received for DataTransfer.req(Authorize) from CSMS";
} else {
// C07.FR.07
// The Charging Station SHALL NOT allow charging.
EVLOG_warning << "CSMS response of DataTransferRequest(Authorize) was not DataTransferResponse";
}
return authorize_response;
}
// For eMAID, we will respond here, unless the local auth list or auth cache is tried
if (!try_local_auth_list_or_cache) {
return authorize_response;
} else {
EVLOG_warning << "CSMS response of DataTransferRequest(Authorize) was not DataTransferResponse";
const auto local_authorize_result = this->authorize_id_token(emaid, true);
if (local_authorize_result.status == AuthorizationStatus::Accepted) {
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Accepted;
} else {
authorize_response.idTokenInfo.status = ocpp::v201::AuthorizationStatusEnum::Invalid;
}
return authorize_response;
}
return authorize_response; // FIXME(piet)
}

void ChargePointImpl::data_transfer_pnc_sign_certificate() {
Expand Down
29 changes: 2 additions & 27 deletions lib/ocpp/v201/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -544,31 +544,6 @@ bool ChargePoint::on_charging_state_changed(const uint32_t evse_id, ChargingStat
return false;
}

std::vector<OCSPRequestData> ChargePoint::generate_mo_ocsp_data(const CiString<5500>& certificate) {
std::vector<OCSPRequestData> ocsp_request_data_list;
const auto ocsp_data_list = this->evse_security->get_mo_ocsp_request_data(certificate.get());
for (const auto& ocsp_data : ocsp_data_list) {
OCSPRequestData request;
switch (ocsp_data.hashAlgorithm) {
case HashAlgorithmEnumType::SHA256:
request.hashAlgorithm = ocpp::v201::HashAlgorithmEnum::SHA256;
break;
case HashAlgorithmEnumType::SHA384:
request.hashAlgorithm = ocpp::v201::HashAlgorithmEnum::SHA384;
break;
case HashAlgorithmEnumType::SHA512:
request.hashAlgorithm = ocpp::v201::HashAlgorithmEnum::SHA512;
break;
}
request.issuerKeyHash = ocsp_data.issuerKeyHash;
request.issuerNameHash = ocsp_data.issuerNameHash;
request.responderURL = ocsp_data.responderUrl;
request.serialNumber = ocsp_data.serialNumber;
ocsp_request_data_list.push_back(request);
}
return ocsp_request_data_list;
}

AuthorizeResponse ChargePoint::validate_token(const IdToken id_token, const std::optional<CiString<5500>>& certificate,
const std::optional<std::vector<OCSPRequestData>>& ocsp_request_data) {
// TODO(piet): C01.FR.14
Expand Down Expand Up @@ -634,8 +609,8 @@ AuthorizeResponse ChargePoint::validate_token(const IdToken id_token, const std:
}
} else {
// Try to generate the OCSP data from the certificate chain and use that
std::vector<OCSPRequestData> generated_ocsp_request_data_list =
generate_mo_ocsp_data(certificate.value());
const auto generated_ocsp_request_data_list = ocpp::evse_security_conversions::to_ocpp_v201(
this->evse_security->get_mo_ocsp_request_data(certificate.value()));
if (generated_ocsp_request_data_list.size() > 0) {
EVLOG_info << "Online: Pass generated OCSP data to CSMS";
response = this->authorize_req(id_token, std::nullopt, generated_ocsp_request_data_list);
Expand Down

0 comments on commit e1ae0bc

Please sign in to comment.