Some minor bugfixing after a successful test session with Verisco. SECC part is now fully tested with good and error test cases and has now very high probability to being fully conform to the ISO 15118-2 standard.

This commit is contained in:
Marc Mültin 2017-08-21 22:13:09 +02:00
parent f301e82367
commit 777a2934ba
16 changed files with 242 additions and 66 deletions

View File

@ -24,7 +24,9 @@
package org.v2gclarity.risev2g.secc.backend;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.util.ArrayList;
import java.util.HashMap;
import javax.xml.bind.JAXBElement;
@ -36,6 +38,7 @@ import org.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.v2gclarity.risev2g.shared.enumerations.GlobalValues;
import org.v2gclarity.risev2g.shared.utils.SecurityUtils;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleEntryType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.PMaxScheduleType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
@ -56,7 +59,10 @@ public class DummyBackendInterface implements IBackendInterface {
}
@Override
public SAScheduleListType getSAScheduleList(int maxEntriesSAScheduleTuple, HashMap<String, byte[]> xmlSignatureRefElements) {
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements) {
/*
* Some important requirements:
*
@ -99,7 +105,11 @@ public class DummyBackendInterface implements IBackendInterface {
// PMaxSchedule
// IMPORTANT: check that you do not add more pMax entries than parameter maxEntriesSAScheduleTuple
PMaxScheduleType pMaxSchedule = new PMaxScheduleType();
pMaxSchedule.getPMaxScheduleEntry().add(createPMaxScheduleEntry("3", (short) 11, 0, 86400L));
if (departureTime != 0)
pMaxSchedule.getPMaxScheduleEntry().add(createPMaxScheduleEntry("3", (short) 11, 0, departureTime));
else
pMaxSchedule.getPMaxScheduleEntry().add(createPMaxScheduleEntry("3", (short) 11, 0, 86400L));
/*
* SalesTariff (add some meaningful things)
@ -118,9 +128,9 @@ public class DummyBackendInterface implements IBackendInterface {
salesTariff.setId("ID1");
salesTariff.setSalesTariffID((short) 1);
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(0L, (short) 1));
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(1800L, (short) 4));
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(3600L, (short) 2));
salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(5400L, (short) 3));
// salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(1800L, (short) 4));
// salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(3600L, (short) 2));
// salesTariff.getSalesTariffEntry().add(createSalesTariffEntry(5400L, (short) 3));
// Put 'em all together
SAScheduleTupleType saScheduleTuple = new SAScheduleTupleType();
@ -131,7 +141,7 @@ public class DummyBackendInterface implements IBackendInterface {
SAScheduleListType saScheduleList = new SAScheduleListType();
saScheduleList.getSAScheduleTuple().add(saScheduleTuple);
// Set xml reference elements for SalesTariff elements (repeat this for every sales tariff) if they are sent
// Set XML reference elements for SalesTariff elements (repeat this for every sales tariff) if they are sent
if (saScheduleTuple.getSalesTariff() != null) {
xmlSignatureRefElements.put(
salesTariff.getId(),
@ -183,10 +193,59 @@ public class DummyBackendInterface implements IBackendInterface {
@Override
public CertificateChainType getContractCertificateChain() {
public CertificateChainType getContractCertificateChain(X509Certificate oemProvisioningCert) {
/*
* Normally, a backend protocol such as OCPP would be used to retrieve the contract certificate chain
* based on the OEM provisioning certificate
*/
return SecurityUtils.getCertificateChain("./moCertChain.p12");
}
@Override
public CertificateChainType getContractCertificateChain(CertificateChainType oldContractCertChain) {
/*
* Normally, a backend protocol such as OCPP would be used to retrieve the new contract certificate chain
* based on the to-be-updated old contract certificate chain
*/
EMAIDType providedEMAID = SecurityUtils.getEMAID(oldContractCertChain);
/*
* NOTE 1: You need to agree with your test partner on valid, authorized EMAIDs that you put into this list.
*
* NOTE 2: Not the EMAID given as a parameter of CertificateUpdateReq is checked (error prone), but the EMAID
* provided in the common name field of the to-be-updated contract certificate
*/
ArrayList<EMAIDType> authorizedEMAIDs = new ArrayList<EMAIDType>();
EMAIDType authorizedEMAID1 = new EMAIDType();
authorizedEMAID1.setId("id1");
authorizedEMAID1.setValue("DE1ABCD2EF357A");
EMAIDType authorizedEMAID2 = new EMAIDType();
authorizedEMAID2.setId("id2");
authorizedEMAID2.setValue("DE1ABCD2EF357C");
authorizedEMAIDs.add(authorizedEMAID1);
authorizedEMAIDs.add(authorizedEMAID2);
boolean emaidFound = false;
for (EMAIDType emaid : authorizedEMAIDs) {
if (emaid.getValue().equals(providedEMAID.getValue()))
emaidFound = true;
}
if (emaidFound)
return SecurityUtils.getCertificateChain("./moCertChain.p12");
else {
getLogger().warn("EMAID '" + providedEMAID.getValue() + "' (read from common name field of contract "
+ "certificate) is not authorized");
return null;
}
}
@Override
public ECPrivateKey getContractCertificatePrivateKey() {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(

View File

@ -23,10 +23,12 @@
*******************************************************************************/
package org.v2gclarity.risev2g.secc.backend;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPrivateKey;
import java.util.HashMap;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.EMAIDType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.SAScheduleListType;
public interface IBackendInterface {
@ -36,18 +38,34 @@ public interface IBackendInterface {
* and optional tariff incentives which shall influence the charging behaviour of the EV.
*
* @param maxEntriesSAScheduleTuple The maximum number of PMaxEntries and SalesTariff entries allowed by EVCC
* @param departureTime The departure time provided by the EV
* @param xmlSignatureRefElements Signature reference parameter provided to put sales tariff IDs and sales tariffs in
* @return An SASchedulesType element with a list of secondary actor schedules
*/
public SAScheduleListType getSAScheduleList(int maxEntriesSAScheduleTuple, HashMap<String, byte[]> xmlSignatureRefElements);
public SAScheduleListType getSAScheduleList(
int maxEntriesSAScheduleTuple,
long departureTime,
HashMap<String, byte[]> xmlSignatureRefElements);
/**
* Provides a certificate chain coming from a secondary actor with the leaf certificate being
* the contract certificate and possible intermediate certificates (sub CAs) included.
* the contract certificate and possible intermediate certificates (Sub-CAs) included.
*
* @param oldContractCertificateChain The to-be-updated contract certificate chain
* @return Certificate chain for contract certificate
*/
public CertificateChainType getContractCertificateChain();
public CertificateChainType getContractCertificateChain(CertificateChainType oldContractCertChain);
/**
* Provides a certificate chain coming from a secondary actor with the leaf certificate being
* the contract certificate and possible intermediate certificates (Sub-CAs) included.
*
* @param oemProvisioningCert The OEM provisioning certificate
* @return Certificate chain for contract certificate
*/
public CertificateChainType getContractCertificateChain(X509Certificate oemProvisioningCert);
/**

View File

@ -46,9 +46,11 @@ public class DummyDCEVSEController implements IDCEVSEController {
private PhysicalValueType maximumEVVoltageLimit;
private PhysicalValueType maximumEVCurrentLimit;
private PhysicalValueType maximumEVPowerLimit;
private IsolationLevelType isolationLevel;
public DummyDCEVSEController(V2GCommunicationSessionSECC commSessionContext) {
setCommSessionContext(commSessionContext);
setIsolationLevel(IsolationLevelType.INVALID);
}
@Override
@ -101,7 +103,7 @@ public class DummyDCEVSEController implements IDCEVSEController {
dcEvseStatus.setNotificationMaxDelay(0);
dcEvseStatus.setEVSENotification((notification != null) ? notification : EVSENotificationType.NONE);
dcEvseStatus.setEVSEStatusCode(DCEVSEStatusCodeType.EVSE_READY);
dcEvseStatus.setEVSEIsolationStatus(IsolationLevelType.INVALID);
dcEvseStatus.setEVSEIsolationStatus(getIsolationLevel());
return dcEvseStatus;
}
@ -237,4 +239,14 @@ public class DummyDCEVSEController implements IDCEVSEController {
return peakCurrentRipple;
}
@Override
public IsolationLevelType getIsolationLevel() {
return isolationLevel;
}
@Override
public void setIsolationLevel(IsolationLevelType isolationLevel) {
this.isolationLevel = isolationLevel;
}
}

View File

@ -28,6 +28,7 @@ import javax.xml.bind.JAXBElement;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.IsolationLevelType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
public interface IDCEVSEController extends IEVSEController {
@ -153,8 +154,22 @@ public interface IDCEVSEController extends IEVSEController {
/**
* Returns the EVSE status for AC charging comprising notification, maxDelay and RCD
* Returns the EVSE status for DC charging comprising notification, maxDelay and RCD
* @return The EVSE specific status
*/
public DCEVSEStatusType getDCEVSEStatus(EVSENotificationType notification);
/**
* Returns the peak-to-peak magnitude of the current ripple of the EVSE
* @return Peak given as a PhyiscalValueType
*/
public IsolationLevelType getIsolationLevel();
/**
* Sets the IsolationLevel DC charging
* @param isolationLevel The IsolationLevel status of the charge equipment
*/
public void setIsolationLevel(IsolationLevelType isolationLevel);
}

View File

@ -185,7 +185,7 @@ public abstract class ServerState extends State {
}
getLogger().debug("Preparing to send " + messageName);
return new SendMessage(message, getCommSessionContext().getStates().get(nextExpectedMessage), TimeRestrictions.V2G_SECC_SEQUENCE_TIMEOUT);
return new SendMessage(message, getCommSessionContext().getStates().get(nextExpectedMessage), TimeRestrictions.V2G_EVCC_COMMUNICATION_SETUP_TIMEOUT);
}
@ -302,10 +302,16 @@ public abstract class ServerState extends State {
break;
case "PreChargeResType":
PreChargeResType preChargeRes = (PreChargeResType) responseMessage;
IDCEVSEController evseController = (IDCEVSEController) getCommSessionContext().getDCEvseController();
IDCEVSEController evseController = getCommSessionContext().getDCEvseController();
preChargeRes.setDCEVSEStatus(evseController.getDCEVSEStatus(EVSENotificationType.NONE));
preChargeRes.setEVSEPresentVoltage(evseController.getPresentVoltage());
PhysicalValueType evsePresentVoltage = new PhysicalValueType();
evsePresentVoltage.setMultiplier(new Byte("0"));
evsePresentVoltage.setUnit(UnitSymbolType.V);
evsePresentVoltage.setValue((short) 0);
preChargeRes.setEVSEPresentVoltage(evsePresentVoltage);
preChargeRes.setResponseCode(responseCode);
break;
case "PowerDeliveryResType":
@ -352,7 +358,13 @@ public abstract class ServerState extends State {
IDCEVSEController evseController3 = (IDCEVSEController) getCommSessionContext().getDCEvseController();
weldingDetectionRes.setDCEVSEStatus(evseController3.getDCEVSEStatus(EVSENotificationType.NONE));
weldingDetectionRes.setEVSEPresentVoltage(evseController3.getPresentVoltage());
PhysicalValueType evsePresentVoltage2 = new PhysicalValueType();
evsePresentVoltage2.setMultiplier(new Byte("0"));
evsePresentVoltage2.setUnit(UnitSymbolType.V);
evsePresentVoltage2.setValue((short) 0);
weldingDetectionRes.setEVSEPresentVoltage(evsePresentVoltage2);
weldingDetectionRes.setResponseCode(responseCode);
break;
case "SessionStopResType":

View File

@ -32,6 +32,7 @@ import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckReqType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.CableCheckResType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.IsolationLevelType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
@ -63,6 +64,9 @@ public class WaitForCableCheckReq extends ServerState {
setEvseProcessingFinished(true);
if (isEvseProcessingFinished()) {
// As soon as EVSEProcessing is set to Finished, the IsolationLevelType should be set to valid
getCommSessionContext().getDCEvseController().setIsolationLevel(IsolationLevelType.VALID);
cableCheckRes.setEVSEProcessing(EVSEProcessingType.FINISHED);
cableCheckRes.setDCEVSEStatus(
((IDCEVSEController) getCommSessionContext().getDCEvseController()).getDCEVSEStatus(EVSENotificationType.NONE)

View File

@ -24,10 +24,12 @@
package org.v2gclarity.risev2g.secc.states;
import java.security.KeyPair;
import java.security.cert.X509Certificate;
import java.security.interfaces.ECPublicKey;
import java.util.HashMap;
import org.v2gclarity.risev2g.secc.session.V2GCommunicationSessionSECC;
import org.v2gclarity.risev2g.shared.enumerations.PKI;
import org.v2gclarity.risev2g.shared.enumerations.V2GMessages;
import org.v2gclarity.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.v2gclarity.risev2g.shared.utils.SecurityUtils;
@ -54,7 +56,10 @@ public class WaitForCertificateInstallationReq extends ServerState {
if (isIncomingMessageValid(message, CertificateInstallationReqType.class, certificateInstallationRes)) {
V2GMessage v2gMessageReq = (V2GMessage) message;
CertificateInstallationReqType certificateInstallationReq = (CertificateInstallationReqType) v2gMessageReq.getBody().getBodyElement().getValue();
CertificateChainType saContractCertificateChain = getCommSessionContext().getBackendInterface().getContractCertificateChain();
CertificateChainType saContractCertificateChain =
getCommSessionContext().getBackendInterface().getContractCertificateChain(
SecurityUtils.getCertificate(certificateInstallationReq.getOEMProvisioningCert())
);
if (isResponseCodeOK(
certificateInstallationReq,
@ -128,6 +133,11 @@ public class WaitForCertificateInstallationReq extends ServerState {
CertificateInstallationReqType certificateInstallationReq,
CertificateChainType saContractCertificateChain,
SignatureType signature) {
ResponseCodeType responseCode = null;
X509Certificate oemProvCert = SecurityUtils.getCertificate(
certificateInstallationReq.getOEMProvisioningCert()
);
// Check for FAILED_NoCertificateAvailable
if (saContractCertificateChain == null || saContractCertificateChain.getCertificate() == null) {
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED_NO_CERTIFICATE_AVAILABLE);
@ -135,13 +145,17 @@ public class WaitForCertificateInstallationReq extends ServerState {
}
// Check for FAILED_CertificateExpired
ResponseCodeType validityResponseCode = SecurityUtils.verifyValidityPeriod(
SecurityUtils.getCertificate(
certificateInstallationReq.getOEMProvisioningCert()
)
);
if (!validityResponseCode.equals(ResponseCodeType.OK)) {
certificateInstallationRes.setResponseCode(validityResponseCode);
responseCode = SecurityUtils.verifyValidityPeriod(oemProvCert);
if (!responseCode.equals(ResponseCodeType.OK)) {
certificateInstallationRes.setResponseCode(responseCode);
return false;
}
// Check for correct attributes (e.g. correct domain component)
responseCode = SecurityUtils.verifyLeafCertificateAttributes(oemProvCert, PKI.OEM);
if (!responseCode.equals(ResponseCodeType.OK)) {
// FAILED_CertChainError is not defined for CertificateInstallationRes, that's why I set it to FAILED
certificateInstallationRes.setResponseCode(ResponseCodeType.FAILED);
return false;
}

View File

@ -56,11 +56,9 @@ public class WaitForCertificateUpdateReq extends ServerState {
V2GMessage v2gMessageReq = (V2GMessage) message;
CertificateUpdateReqType certificateUpdateReq =
(CertificateUpdateReqType) v2gMessageReq.getBody().getBodyElement().getValue();
CertificateChainType contractCertificateChain = getCommSessionContext().getBackendInterface().getContractCertificateChain();
if (isResponseCodeOK(
certificateUpdateReq,
contractCertificateChain,
v2gMessageReq.getHeader().getSignature())) {
// The ECDH (elliptic curve Diffie Hellman) key pair is also needed for the generation of the shared secret
KeyPair ecdhKeyPair = SecurityUtils.getECKeyPair();
@ -76,7 +74,6 @@ public class WaitForCertificateUpdateReq extends ServerState {
ecdhKeyPair,
getCommSessionContext().getBackendInterface().getContractCertificatePrivateKey());
certificateUpdateRes.setContractSignatureCertChain(contractCertificateChain);
/*
* Experience from the test symposium in San Diego (April 2016):
* The Id element of the signature is not restricted in size by the standard itself. But on embedded
@ -135,12 +132,17 @@ public class WaitForCertificateUpdateReq extends ServerState {
private boolean isResponseCodeOK(
CertificateUpdateReqType certificateUpdateReq,
CertificateChainType saContractCertificateChain,
SignatureType signature) {
// Check for FAILED_NoCertificateAvailable
CertificateChainType saContractCertificateChain =
getCommSessionContext().getBackendInterface().getContractCertificateChain(
certificateUpdateReq.getContractSignatureCertChain()
);
if (saContractCertificateChain == null || saContractCertificateChain.getCertificate() == null) {
certificateUpdateRes.setResponseCode(ResponseCodeType.FAILED_NO_CERTIFICATE_AVAILABLE);
return false;
} else {
certificateUpdateRes.setContractSignatureCertChain(saContractCertificateChain);
}
// Check complete contract certificate chain
@ -153,9 +155,6 @@ public class WaitForCertificateUpdateReq extends ServerState {
return false;
}
// Check for FAILED_ContractCancelled
// TODO how to check if the EMAID provided by EVCC is not accepted by secondary actor?
// Check for FAILED_CertificateRevoked
// TODO check for revocation with OCSP

View File

@ -80,9 +80,12 @@ public class WaitForChargeParameterDiscoveryReq extends ServerState {
int maxEntriesSAScheduleTuple = (chargeParameterDiscoveryReq.getMaxEntriesSAScheduleTuple() != null) ?
chargeParameterDiscoveryReq.getMaxEntriesSAScheduleTuple() : 1024;
Long departureTime = chargeParameterDiscoveryReq.getEVChargeParameter().getValue().getDepartureTime();
getCommSessionContext().setSaSchedules(
getCommSessionContext().getBackendInterface().getSAScheduleList(
maxEntriesSAScheduleTuple,
(departureTime != null) ? departureTime.longValue() : 0,
getXMLSignatureRefElements())
);
}

View File

@ -32,6 +32,7 @@ import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusReqType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.ChargingStatusResType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
public class WaitForChargingStatusReq extends ServerState {
@ -56,13 +57,17 @@ public class WaitForChargingStatusReq extends ServerState {
* but then make sure the EV is stopping the charge loop
*/
chargingStatusRes.setACEVSEStatus(
((IACEVSEController) getCommSessionContext().getACEvseController())
.getACEVSEStatus(EVSENotificationType.NONE)
);
getCommSessionContext().getACEvseController().getACEVSEStatus(EVSENotificationType.NONE)
);
// Optionally indicate that the EVCC is required to send a MeteringReceiptReq message
// (only in PnC mode according to [V2G2-691])
chargingStatusRes.setReceiptRequired(false);
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
// In EIM, there is never a MeteringReceiptReq/-Res message pair, therefore it is set to false here
chargingStatusRes.setReceiptRequired(false);
} else {
// Only in PnC mode according to [V2G2-691]
chargingStatusRes.setReceiptRequired(true);
}
// Optionally set EVSEMaxCurrent (if NOT in AC PnC mode) -> check with AC station

View File

@ -31,6 +31,7 @@ import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.BodyBaseType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandReqType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.CurrentDemandResType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.v2gclarity.risev2g.shared.v2gMessages.msgDef.V2GMessage;
@ -50,7 +51,7 @@ public class WaitForCurrentDemandReq extends ServerState {
CurrentDemandReqType currentDemandReq =
(CurrentDemandReqType) v2gMessageReq.getBody().getBodyElement().getValue();
IDCEVSEController evseController = (IDCEVSEController) getCommSessionContext().getDCEvseController();
IDCEVSEController evseController = getCommSessionContext().getDCEvseController();
evseController.setEVMaximumCurrentLimit(currentDemandReq.getEVMaximumCurrentLimit());
evseController.setEVMaximumVoltageLimit(currentDemandReq.getEVMaximumVoltageLimit());
@ -80,8 +81,14 @@ public class WaitForCurrentDemandReq extends ServerState {
getCommSessionContext().setSentMeterInfo(evseController.getMeterInfo());
currentDemandRes.setSAScheduleTupleID(getCommSessionContext().getChosenSAScheduleTuple());
// TODO how to determine if a receipt is required or not?
currentDemandRes.setReceiptRequired(true);
// Optionally indicate that the EVCC is required to send a MeteringReceiptReq message
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
// In EIM, there is never a MeteringReceiptReq/-Res message pair, therefore it is set to false here
currentDemandRes.setReceiptRequired(false);
} else {
// Optionally set to true, but only in PnC mode according to [V2G2-691]
currentDemandRes.setReceiptRequired(true);
}
if (currentDemandRes.isReceiptRequired()) {
return getSendMessage(currentDemandRes, V2GMessages.METERING_RECEIPT_REQ);

View File

@ -97,6 +97,8 @@ public class WaitForMeteringReceiptReq extends ServerState {
* CurrentDemandRes (DC charging) is equal to the received MeterInfo
*/
if (!meterInfoEquals(getCommSessionContext().getSentMeterInfo(), meteringReceiptReq.getMeterInfo())) {
getLogger().error("The metering values sent by the EVCC do not match the ones sent previously by the SECC. "
+ "This is not a signature verification error.");
meteringReceiptRes.setResponseCode(ResponseCodeType.FAILED_METERING_SIGNATURE_NOT_VALID);
return false;
}

View File

@ -109,7 +109,11 @@ public class WaitForPaymentDetailsReq extends ServerState {
// Check for FAILED_ContractCancelled
// TODO how to check if the EMAID provided by EVCC is not accepted by secondary actor?
if (!SecurityUtils.isEMAIDSynstaxValid(paymentDetailsReq.getContractSignatureCertChain())) {
if (!SecurityUtils.isEMAIDSynstaxValid(
SecurityUtils.getCertificate(
paymentDetailsReq.getContractSignatureCertChain().getCertificate())
)
) {
// There is no good FAILED response code for this situation, but ContractCanceled is still better than FAILED
paymentDetailsRes.setResponseCode(ResponseCodeType.FAILED_CONTRACT_CANCELED);
return false;

View File

@ -113,6 +113,14 @@ public class WaitForPowerDeliveryReq extends ServerState {
public boolean isResponseCodeOK(PowerDeliveryReqType powerDeliveryReq) {
SAScheduleTupleType chosenSASchedule = getChosenSASCheduleTuple(powerDeliveryReq.getSAScheduleTupleID());
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.RENEGOTIATE) &&
!getCommSessionContext().isChargeProgressStarted()) {
getLogger().error("EVCC wants to renegotiate, but charge progress has not started yet (no "
+ "PowerDeliveryReq with ChargeProgress=START has been received before)");
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED);
return false;
}
if (chosenSASchedule == null) {
getLogger().warn("Chosen SAScheduleTupleID in PowerDeliveryReq is null, but parameter is mandatory");
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED_TARIFF_SELECTION_INVALID);
@ -120,8 +128,8 @@ public class WaitForPowerDeliveryReq extends ServerState {
}
// Important to call this AFTER checking for valid tariff selection because of possible null-value!
// Check ChargingProfile only if EV does not want to stop the charging process
if (!powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.STOP) &&
// Check ChargingProfile only if EV wants to start (not stop or renegotiate) the charging process
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.START) &&
!isChargingProfileValid(chosenSASchedule, powerDeliveryReq.getChargingProfile())) {
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED_CHARGING_PROFILE_INVALID);
return false;
@ -157,14 +165,6 @@ public class WaitForPowerDeliveryReq extends ServerState {
return false;
}
if (powerDeliveryReq.getChargeProgress().equals(ChargeProgressType.RENEGOTIATE) &&
!getCommSessionContext().isChargeProgressStarted()) {
getLogger().error("EVCC wants to renegotiate, but charge progress has not started yet (no "
+ "PowerDeliveryReq with ChargeProgress=START has been received before)");
powerDeliveryRes.setResponseCode(ResponseCodeType.FAILED);
return false;
}
return true;
}
@ -219,7 +219,7 @@ public class WaitForPowerDeliveryReq extends ServerState {
}
for (ProfileEntryType profileEntry : chargingProfile.getProfileEntry()) {
if (profileEntry.getChargingProfileEntryMaxNumberOfPhasesInUse() == 2) {
if (profileEntry.getChargingProfileEntryMaxNumberOfPhasesInUse() != null && profileEntry.getChargingProfileEntryMaxNumberOfPhasesInUse() == 2) {
getLogger().error("Parameter MaxNumberOfPhasesInUse of one ChargingProfile entry element is 2 which is not allowed. Only 1 or 3 are valid values.");
return false;
}

View File

@ -73,7 +73,8 @@ public class WaitForWeldingDetectionReq extends ServerState {
return getSendMessage(weldingDetectionRes,
(weldingDetectionRes.getResponseCode().toString().startsWith("OK") ?
V2GMessages.FORK : V2GMessages.NONE)
V2GMessages.FORK : V2GMessages.NONE),
weldingDetectionRes.getResponseCode()
);
}

View File

@ -325,6 +325,7 @@ public final class SecurityUtils {
X509Certificate leafCertificate = null;
X509Certificate subCA1Certificate = null;
X509Certificate subCA2Certificate = null;
ResponseCodeType responseCode = null;
// Get leaf certificate
if (certChain != null) {
@ -418,17 +419,27 @@ public final class SecurityUtils {
}
/*
* *****************************************
* PKI SPECIFIC CERTIFICATE ATTRIBUTES CHECK
* *****************************************
*/
responseCode = verifyLeafCertificateAttributes(leafCertificate, pki);
if (responseCode.equals(ResponseCodeType.OK))
return responseCode;
return ResponseCodeType.OK;
}
/**
* Checks certificate attributes for a given leaf certificate belonging to an ISO 15118 PKI.
*
* @param certificate The X.509 certificate whose attributes need to be checked
* @param pki The PKI to which the certificate belongs
* @return
*/
public static ResponseCodeType verifyLeafCertificateAttributes(X509Certificate leafCertificate, PKI pki) {
switch (pki) {
case CPO:
if (!verifyDomainComponent(leafCertificate, "CPO")) {
getLogger().error("SECC leaf certificate with distinguished name '" +
leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
"domain component. Should be 'CPO'");
return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
}
@ -436,20 +447,20 @@ public final class SecurityUtils {
case CPS:
if (!verifyDomainComponent(leafCertificate, "CPS")) {
getLogger().error("CPS leaf certificate with distinguished name '" +
leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
"domain component. Should be 'CPS'");
return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
}
break;
case MO:
if (!isEMAIDSynstaxValid(certChain)) {
if (!isEMAIDSynstaxValid(leafCertificate)) {
return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
}
break;
case OEM:
if (!verifyDomainComponent(leafCertificate, "OEM")) {
getLogger().error("OEM provisioning certificate with distinguished name '" +
leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
leafCertificate.getSubjectX500Principal().getName() + "' has incorrect value for " +
"domain component. Should be 'OEM'");
return ResponseCodeType.FAILED_CERT_CHAIN_ERROR;
}
@ -457,7 +468,6 @@ public final class SecurityUtils {
default:
break;
}
return ResponseCodeType.OK;
}
@ -1541,7 +1551,7 @@ public final class SecurityUtils {
/**
* Returns the EMAID (e-mobility account identifier) from the contract certificate.
* Returns the EMAID (e-mobility account identifier) from the contract certificate as part of the contract certificate chain.
*
* @param contractCertificateChain The certificate chain holding the contract certificate
* @return The EMAID
@ -1552,6 +1562,17 @@ public final class SecurityUtils {
}
/**
* Returns the EMAID (e-mobility account identifier) from the contract certificate.
*
* @param contractCertificate The contract certificate
* @return The EMAID
*/
public static EMAIDType getEMAID(X509Certificate contractCertificate) {
return getEMAIDFromDistinguishedName(contractCertificate.getSubjectX500Principal().getName());
}
/**
* Returns the EMAID (e-mobility account identifier) from the contract certificate.
*
@ -1598,7 +1619,7 @@ public final class SecurityUtils {
for(Rdn rdn : ln.getRdns()) {
if (rdn.getType().equalsIgnoreCase("CN")) {
// Optional hyphens used for better human readability must be omitted here
emaid.setId(rdn.getValue().toString().replace("-", ""));
emaid.setId("id1");
emaid.setValue(rdn.getValue().toString().replace("-", ""));
break;
}
@ -2107,8 +2128,8 @@ public final class SecurityUtils {
* @param certChain The contract certificate chain. The EMAID is read from the contract certificate's common name
* @return True, if the syntax is valid, false otherwise
*/
public static boolean isEMAIDSynstaxValid(CertificateChainType certChain) {
String emaid = getEMAID(certChain).getValue().toUpperCase();
public static boolean isEMAIDSynstaxValid(X509Certificate contractCertificate) {
String emaid = getEMAID(contractCertificate).getValue().toUpperCase();
if (emaid.length() < 14 || emaid.length() > 18) {
getLogger().error("EMAID is invalid. Its length (" + emaid.length() + ") mus be between "