diff --git a/RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/session/V2GCommunicationSessionHandlerEVCC.java b/RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/session/V2GCommunicationSessionHandlerEVCC.java index bb8b979..4764657 100644 --- a/RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/session/V2GCommunicationSessionHandlerEVCC.java +++ b/RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/session/V2GCommunicationSessionHandlerEVCC.java @@ -138,6 +138,11 @@ public class V2GCommunicationSessionHandlerEVCC implements Observer { getTransportLayerClient().addObserver(getV2gCommunicationSessionEVCC()); getV2gCommunicationSessionEVCC().addObserver(this); + + // Set TLS security flag for communication session + boolean secureConn = (((Byte) getSecurity()).compareTo((Byte) GlobalValues.V2G_SECURITY_WITH_TLS.getByteValue()) == 0) ? true : false; + getV2gCommunicationSessionEVCC().setTlsConnection(secureConn); + sendSupportedAppProtocolReq(); } else { getLogger().fatal("Maximum number of SECCDiscoveryReq messages reached"); diff --git a/RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForChargeParameterDiscoveryRes.java b/RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForChargeParameterDiscoveryRes.java index e8f4dbd..59f6755 100644 --- a/RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForChargeParameterDiscoveryRes.java +++ b/RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForChargeParameterDiscoveryRes.java @@ -28,6 +28,7 @@ import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeProgressType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSEProcessingType; +import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType; @@ -77,9 +78,8 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState { SAScheduleListType saSchedules = (SAScheduleListType) chargeParameterDiscoveryRes.getSASchedules().getValue(); - // Verify each sales tariff with the mobility operator sub 2 certificate - if (saSchedules != null && !verifySalesTariffs(saSchedules, v2gMessageRes.getHeader().getSignature())) - return new TerminateSession("Verification of sales tariffs failed"); + if (saSchedules != null && getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) + verifySalesTariffs(saSchedules, v2gMessageRes.getHeader().getSignature()); // Save the list of SASchedules (saves the time of reception as well) getCommSessionContext().setSaSchedules(saSchedules); @@ -111,8 +111,31 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState { } } - + /** + * Verifies each sales tariff given with the ChargeParameterDiscoveryRes message with the + * mobility operator sub 2 certificate. + * + * @param saSchedules The SASchedule list which holds all PMaxSchedules and SalesTariffs + * @param signature The signature for the sales tariffs + * @return True, if the verification of the sales tariffs was successful, false otherwise + */ private boolean verifySalesTariffs(SAScheduleListType saSchedules, SignatureType signature) { + /* + * Some important requirements: + * + * 1. In case of PnC, and if a Tariff Table is used by the secondary actor, the secondary actor SHALL + * sign the field SalesTariff of type SalesTariffType. In case of EIM, the secondary actor MAY sign + * this field. + * + * 2. If the EVCC treats the SalesTariff as invalid, it shall ignore the SalesTariff table, i.e. the + * behaviour of the EVCC shall be the same as if no tariff tables were received. Furthermore, the + * EVCC MAY close the connection. It then may reopen the connection again. + */ + + boolean salesTariffSignatureAvailable = (signature == null) ? false : true; + boolean ignoreSalesTariffs = (getCommSessionContext().isTlsConnection() && !salesTariffSignatureAvailable) ? true : false; + short ignoredSalesTariffs = 0; + HashMap verifyXMLSigRefElements = new HashMap(); List saScheduleTuples = saSchedules.getSAScheduleTuple(); int salesTariffCounter = 0; @@ -121,6 +144,13 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState { // verification regards only sales tariffs, not PMaxSchedules if (saScheduleTuple.getSalesTariff() == null) continue; + // Check if signature is given during TLS communication. If no signature is given, delete SalesTariff + if (ignoreSalesTariffs) { + ignoredSalesTariffs++; + saScheduleTuple.setSalesTariff(null); + continue; + } + salesTariffCounter++; verifyXMLSigRefElements.put( @@ -142,6 +172,11 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState { } } + if (ignoredSalesTariffs > 0) { + getLogger().info("Sales tariffs could not be verified because of missing signature and will therefore be ignored"); + return false; + } + return true; } } diff --git a/RISE-V2G-SECC/SECCConfig.properties b/RISE-V2G-SECC/SECCConfig.properties index fc39eb5..a63d299 100644 --- a/RISE-V2G-SECC/SECCConfig.properties +++ b/RISE-V2G-SECC/SECCConfig.properties @@ -72,5 +72,5 @@ PrivateEnvironment = false # - false # If this value is set to 'true', the EXICodec will print each message's XML representation (for debugging purposes) # If no correct value is provided here, 'false' will be chosen -XMLRepresentationOfMessages = true +XMLRepresentationOfMessages = false diff --git a/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/DummyBackendInterface.java b/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/DummyBackendInterface.java index 339cf52..452b7ec 100644 --- a/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/DummyBackendInterface.java +++ b/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/DummyBackendInterface.java @@ -12,6 +12,7 @@ package org.eclipse.risev2g.secc.backend; import java.security.KeyStore; import java.security.interfaces.ECPrivateKey; +import java.util.HashMap; import javax.xml.bind.JAXBElement; import javax.xml.namespace.QName; @@ -19,6 +20,7 @@ import javax.xml.namespace.QName; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.eclipse.risev2g.secc.session.V2GCommunicationSessionSECC; +import org.eclipse.risev2g.secc.states.ServerState; import org.eclipse.risev2g.shared.enumerations.GlobalValues; import org.eclipse.risev2g.shared.utils.SecurityUtils; import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType; @@ -28,6 +30,8 @@ import org.eclipse.risev2g.shared.v2gMessages.msgDef.PhysicalValueType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.RelativeTimeIntervalType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleTupleType; +import org.eclipse.risev2g.shared.v2gMessages.msgDef.SalesTariffEntryType; +import org.eclipse.risev2g.shared.v2gMessages.msgDef.SalesTariffType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.UnitSymbolType; public class DummyBackendInterface implements IBackendInterface { @@ -40,50 +44,123 @@ public class DummyBackendInterface implements IBackendInterface { } @Override - public SAScheduleListType getSAScheduleList() { + public SAScheduleListType getSAScheduleList(int maxEntriesSAScheduleTuple, HashMap xmlSignatureRefElements) { /* - * PMaxSchedule + * Some important requirements: + * + * 1. The sum of the individual time intervals described in the PMaxSchedule and + * SalesTariff provided in the ChargeParameterDiscoveryRes message shall match + * the period of time indicated by the EVCC in the message element DepartureTime of the + * ChargeParameterDiscoveryReq message. + * + * 2. If the EVCC did not provide a DepartureTime Target Setting in the ChargeParameterDiscoveryReq + * message, the sum of the individual time intervals described in the PMaxSchedule and SalesTariff + * provided in the ChargeParameterDiscoveryRes message, shall be greater or equal to 24 hours. + * + * 3. If the number of SalesTariffEntry elements in the SalesTariff or the number of + * PMaxScheduleEntry elements in the PMaxSchedule provided by the secondary actor(s) are not + * covering the entire period of time until DepartureTime, the Target Setting EAmount has not + * been met and the communication session has not been finished, it is the responsibility of + * the EVCC to request a new element of type SAScheduleListType as soon as the last + * SalesTariffEntry element or the last PMaxScheduleEntry element becomes active by sending + * a new ChargeParameterDiscoveryReq message. + * + * 4. In case of PnC, and if a Tariff Table is used by the secondary actor, the secondary actor SHALL + * sign the field SalesTariff of type SalesTariffType. In case of EIM, the secondary actor MAY sign + * this field. + * + * 5. The SECC shall 'copy' (not change!) the signature value received from the SA and transmit this value in the + * header of the ChargeParameterDiscoveryRes message. + * + * 6. + * If the element SalesTariff is signed, it shall be signed by the same private key that was used to + * issue the leaf contract certificate that the EVCC used during this connection for contract + * authentication (PnC). + * + * 7. An EVCC shall support 12 entries for PMaxScheduleEntry and SalesTariffEntry elements inside + * one SAScheduleTuple if MaxEntriesSAScheduleTuple is not transmitted in ChargeParameterDiscoveryReq. + * + * 8. The valid range for the value of EPriceLevel element shall be defined as being between 0 and + * the value of NumEPriceLevels element including the boundary values. */ - PhysicalValueType pMaxValue = new PhysicalValueType(); - pMaxValue.setMultiplier(new Byte("3")); - pMaxValue.setUnit(UnitSymbolType.W); - pMaxValue.setValue((short) 11); - - RelativeTimeIntervalType timeInterval = new RelativeTimeIntervalType(); - timeInterval.setStart(0); - timeInterval.setDuration(3600L); - - PMaxScheduleEntryType pMaxScheduleEntry = new PMaxScheduleEntryType(); - pMaxScheduleEntry.setTimeInterval(new JAXBElement( - new QName("urn:iso:15118:2:2013:MsgDataTypes", "RelativeTimeInterval"), - RelativeTimeIntervalType.class, - timeInterval)); - pMaxScheduleEntry.setPMax(pMaxValue); + // PMaxSchedule + // IMPORTANT: check that you do not add more pMax entries than parameter maxEntriesSAScheduleTuple PMaxScheduleType pMaxSchedule = new PMaxScheduleType(); - pMaxSchedule.getPMaxScheduleEntry().add(pMaxScheduleEntry); - + pMaxSchedule.getPMaxScheduleEntry().add(createPMaxScheduleEntry("3", (short) 11, 0, 7200L)); /* * SalesTariff (add some meaningful things) * But: If it is instantiated, it must be filled with meaningful data, otherwise there will * occur an error with the EXIDecoder (at least at Vector) + * + * IMPORTANT: check that you do not add more sales tariff entries than parameter maxEntriesSAScheduleTuple */ + SalesTariffType salesTariff = new SalesTariffType(); + salesTariff.setId("salesTariff"); + 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)); - - /* - * Put 'em all together - */ + // Put 'em all together SAScheduleTupleType saScheduleTuple = new SAScheduleTupleType(); saScheduleTuple.setSAScheduleTupleID((short) 1); saScheduleTuple.setPMaxSchedule(pMaxSchedule); -// saScheduleTuple.setSalesTariff(salesTariff); + saScheduleTuple.setSalesTariff(salesTariff); SAScheduleListType saScheduleList = new SAScheduleListType(); saScheduleList.getSAScheduleTuple().add(saScheduleTuple); - + + // Set xml reference elements (repeat this for every sales tariff) + xmlSignatureRefElements.put( + salesTariff.getId(), + SecurityUtils.generateDigest(salesTariff, false)); + return saScheduleList; } + + private SalesTariffEntryType createSalesTariffEntry(long start, short ePriceLevel) { + RelativeTimeIntervalType salesTariffTimeInterval = new RelativeTimeIntervalType(); + salesTariffTimeInterval.setStart(start); + + SalesTariffEntryType salesTariffEntry = new SalesTariffEntryType(); + salesTariffEntry.setTimeInterval(new JAXBElement( + new QName("urn:iso:15118:2:2013:MsgDataTypes", "RelativeTimeInterval"), + RelativeTimeIntervalType.class, + salesTariffTimeInterval)); + salesTariffEntry.setEPriceLevel(ePriceLevel); + + return salesTariffEntry; + } + + private PMaxScheduleEntryType createPMaxScheduleEntry(String multiplier, short pMax, long start) { + PhysicalValueType pMaxValue = new PhysicalValueType(); + pMaxValue.setMultiplier(new Byte(multiplier)); + pMaxValue.setUnit(UnitSymbolType.W); + pMaxValue.setValue(pMax); + + RelativeTimeIntervalType pMaxTimeInterval = new RelativeTimeIntervalType(); + pMaxTimeInterval.setStart(0); + pMaxTimeInterval.setDuration(7200L); // 2 hours + + PMaxScheduleEntryType pMaxScheduleEntry = new PMaxScheduleEntryType(); + pMaxScheduleEntry.setTimeInterval(new JAXBElement( + new QName("urn:iso:15118:2:2013:MsgDataTypes", "RelativeTimeInterval"), + RelativeTimeIntervalType.class, + pMaxTimeInterval)); + pMaxScheduleEntry.setPMax(pMaxValue); + + return pMaxScheduleEntry; + } + + private PMaxScheduleEntryType createPMaxScheduleEntry(String multiplier, short pMax, long start, long duration) { + PMaxScheduleEntryType pMaxScheduleEntry = createPMaxScheduleEntry(multiplier, pMax, start); + ((RelativeTimeIntervalType) pMaxScheduleEntry.getTimeInterval().getValue()).setDuration(duration); + + return pMaxScheduleEntry; + } @Override diff --git a/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/IBackendInterface.java b/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/IBackendInterface.java index 346e287..6f8eecc 100644 --- a/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/IBackendInterface.java +++ b/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/IBackendInterface.java @@ -11,6 +11,7 @@ package org.eclipse.risev2g.secc.backend; import java.security.interfaces.ECPrivateKey; +import java.util.HashMap; import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType; import org.eclipse.risev2g.shared.v2gMessages.msgDef.SAScheduleListType; @@ -20,9 +21,12 @@ public interface IBackendInterface { /** * Provides a list of schedules coming from a secondary actor (SAScheduleList) with pMax values * 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 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(); + public SAScheduleListType getSAScheduleList(int maxEntriesSAScheduleTuple, HashMap xmlSignatureRefElements); /** diff --git a/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForChargeParameterDiscoveryReq.java b/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForChargeParameterDiscoveryReq.java index 632d892..d3aa606 100644 --- a/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForChargeParameterDiscoveryReq.java +++ b/RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForChargeParameterDiscoveryReq.java @@ -55,14 +55,22 @@ public class WaitForChargeParameterDiscoveryReq extends ServerState { /* * Request a new schedule in case of first ChargeParameterDiscoveryReq. - * If EVSEProcessingType.ONGOING was sent in previous ChargeParameterDiscoveryReq - * response message, do not request again. + * If EVSEProcessingType.ONGOING was sent in previous ChargeParameterDiscoveryRes + * message, do not request again. */ if (!isWaitingForSchedule()) { // TODO we need a timeout mechanism here so that a response can be sent within 2s setWaitingForSchedule(true); + + // The max. number of PMaxScheduleEntries and SalesTariffEntries is 1024 if not provided otherwise by EVCC + int maxEntriesSAScheduleTuple = (chargeParameterDiscoveryReq.getMaxEntriesSAScheduleTuple() != null) ? + chargeParameterDiscoveryReq.getMaxEntriesSAScheduleTuple() : 1024; + getCommSessionContext().setSaSchedules( - getCommSessionContext().getBackendInterface().getSAScheduleList()); + getCommSessionContext().getBackendInterface().getSAScheduleList( + maxEntriesSAScheduleTuple, + getXMLSignatureRefElements()) + ); } // Wait a bit and check if the schedule has already been provided @@ -98,6 +106,9 @@ public class WaitForChargeParameterDiscoveryReq extends ServerState { chargeParameterDiscoveryRes.setSASchedules( getSASchedulesAsJAXBElement(getCommSessionContext().getSaSchedules())); + // Set signing private key + setSignaturePrivateKey(getCommSessionContext().getBackendInterface().getSAProvisioningCertificatePrivateKey()); + if (chargeParameterDiscoveryReq.getRequestedEnergyTransferMode().toString().startsWith("AC")) return getSendMessage(chargeParameterDiscoveryRes, V2GMessages.POWER_DELIVERY_REQ); else