- Added support to send signed sales tariffs (SECC side)
- Corrected the verification process of received (signed) sales tariffs (EVCC side) - changed default value for XMLRepresentationOfMessages to false (SECC side)
This commit is contained in:
parent
4a8a8024f7
commit
458babda11
|
@ -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");
|
||||
|
|
|
@ -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<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
|
||||
List<SAScheduleTupleType> 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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<String, byte[]> 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<RelativeTimeIntervalType>(
|
||||
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<RelativeTimeIntervalType>(
|
||||
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<RelativeTimeIntervalType>(
|
||||
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
|
||||
|
|
|
@ -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<String, byte[]> xmlSignatureRefElements);
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue