Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OCPP1.6 P&C Handling adjustments #538

Merged
merged 5 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Pietfried marked this conversation as resolved.
Show resolved Hide resolved
// 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
Loading