Merge branch 'master' into develop

# Conflicts:
#	RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/evController/DummyEVController.java
#	RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/session/V2GCommunicationSessionEVCC.java
#	RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForChargeParameterDiscoveryRes.java
#	RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/session/V2GCommunicationSessionSECC.java
#	RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/utils/SecurityUtils.java
This commit is contained in:
Marc Mültin 2016-07-28 15:03:04 +02:00
commit e0f2d36bbc
30 changed files with 524 additions and 170 deletions

View File

@ -68,4 +68,15 @@ RequestedPaymentOption =
# - DC_extended
# - DC_combo_core
# - DC_unique
RequestedEnergyTransferMode = AC_three_phase_core
RequestedEnergyTransferMode =
# XML representation of messages
#-------------------------------
#
# Possible values:
# - true
# - 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 = false

View File

@ -49,11 +49,12 @@ public class DummyEVController implements IACEVController, IDCEVController {
@Override
public PaymentOptionType getPaymentOption(PaymentOptionListType paymentOptionsOffered) {
// Contract payment option may only be chosen if offered by SECC AND if communication is secured by TLS
if (paymentOptionsOffered.getPaymentOption().contains(PaymentOptionType.CONTRACT) &&
getCommSessionContext().isSecureCommunication())
return PaymentOptionType.CONTRACT;
else
if (paymentOptionsOffered.getPaymentOption().contains(PaymentOptionType.CONTRACT)) {
if (!getCommSessionContext().isTlsConnection()) {
getLogger().warn("SECC offered CONTRACT based payment although no TLS connectionis used. Choosing EIM instead");
return PaymentOptionType.EXTERNAL_PAYMENT;
} else return PaymentOptionType.CONTRACT;
} else return PaymentOptionType.EXTERNAL_PAYMENT;
}

View File

@ -48,6 +48,7 @@ import org.eclipse.risev2g.shared.messageHandling.SendMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.V2GCommunicationSession;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
@ -91,6 +92,7 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
private long saSchedulesReceived;
private CPStates changeToState; // signals a needed state change (checked when sending the request message)
private StatefulTransportLayerClient transportLayerClient;
private ContractCertificateStatus contractCertStatus;
public V2GCommunicationSessionEVCC(StatefulTransportLayerClient transportLayerClient) {
setTransportLayerClient(transportLayerClient);
@ -128,6 +130,9 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
*/
setV2gEVCCCommunicationSetupTimer(System.currentTimeMillis());
// Set default value for contract certificate status to UNKNOWN
setContractCertStatus(ContractCertificateStatus.UNKNOWN);
getLogger().debug("\n*******************************************" +
"\n* New V2G communication session initialized" +
"\n*******************************************");
@ -444,4 +449,14 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
return false;
}
public ContractCertificateStatus getContractCertStatus() {
return contractCertStatus;
}
public void setContractCertStatus(ContractCertificateStatus contractCertStatus) {
this.contractCertStatus = contractCertStatus;
}
}

View File

@ -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");

View File

@ -253,7 +253,13 @@ public abstract class ClientState extends State {
if (genChallenge != null) {
authorizationReq.setGenChallenge(genChallenge);
authorizationReq.setId("authorizationReq");
/*
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
authorizationReq.setId("id1");
}
return authorizationReq;

View File

@ -20,7 +20,6 @@ import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SignatureType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;

View File

@ -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;
@ -78,7 +79,7 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState {
SAScheduleListType saSchedules = (SAScheduleListType) chargeParameterDiscoveryRes.getSASchedules().getValue();
// If TLS is used, verify each sales tariff (if present) with the mobility operator sub 2 certificate
if (getCommSessionContext().isSecureCommunication() && saSchedules != null) {
if (getCommSessionContext().isTlsConnection() && saSchedules != null) {
if (!verifySalesTariffs(saSchedules, v2gMessageRes.getHeader().getSignature()))
getLogger().warn("Verification of sales tariffs failed. They are therefore ignored in the "
+ "charge process.");
@ -115,8 +116,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;
@ -125,6 +149,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(
@ -136,7 +167,7 @@ public class WaitForChargeParameterDiscoveryRes extends ClientState {
X509Certificate moSub2Certificate = SecurityUtils.getMOSub2Certificate(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString());
if (moSub2Certificate == null) {
getLogger().warn("No MOSub2Certificate found.");
getLogger().error("No MOSub2Certificate found, signature of sales tariff could therefore not be verified");
return false;
} else {
ECPublicKey ecPublicKey = (ECPublicKey) moSub2Certificate.getPublicKey();
@ -146,6 +177,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;
}

View File

@ -42,9 +42,15 @@ public class WaitForChargingStatusRes extends ClientState {
* a MeteringReceiptRequest. If no TLS is used, a MeteringReceiptRequest may not be sent because
* a signature cannot be applied without private key of the contract certificate.
*/
if (chargingStatusRes.isReceiptRequired() && getCommSessionContext().isSecureCommunication()) {
if (chargingStatusRes.isReceiptRequired() && getCommSessionContext().isTlsConnection()) {
MeteringReceiptReqType meteringReceiptReq = new MeteringReceiptReqType();
meteringReceiptReq.setId("meteringReceiptReq");
/*
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
meteringReceiptReq.setId("id1");
meteringReceiptReq.setMeterInfo(chargingStatusRes.getMeterInfo());
meteringReceiptReq.setSAScheduleTupleID(chargingStatusRes.getSAScheduleTupleID());
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());

View File

@ -37,7 +37,13 @@ public class WaitForCurrentDemandRes extends ClientState {
// ReceiptRequired has higher priority than a possible EVSENotification=Renegotiate
if (currentDemandRes.isReceiptRequired()) {
MeteringReceiptReqType meteringReceiptReq = new MeteringReceiptReqType();
meteringReceiptReq.setId("MeterInfo");
/*
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
meteringReceiptReq.setId("id1");
meteringReceiptReq.setMeterInfo(currentDemandRes.getMeterInfo());
meteringReceiptReq.setSAScheduleTupleID(currentDemandRes.getSAScheduleTupleID());
meteringReceiptReq.setSessionID(getCommSessionContext().getSessionID());

View File

@ -18,7 +18,6 @@ import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.AuthorizationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentDetailsResType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ResponseCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
public class WaitForPaymentDetailsRes extends ClientState {
@ -44,6 +43,7 @@ public class WaitForPaymentDetailsRes extends ClientState {
} else {
// Set xml reference element
AuthorizationReqType authorizationReq = getAuthorizationReq(paymentDetailsRes.getGenChallenge());
getXMLSignatureRefElements().put(
authorizationReq.getId(),
SecurityUtils.generateDigest(authorizationReq, false));

View File

@ -11,14 +11,13 @@
package org.eclipse.risev2g.evcc.states;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateInstallationReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateUpdateReqType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
@ -34,30 +33,23 @@ public class WaitForPaymentServiceSelectionRes extends ClientState {
public ReactionToIncomingMessage processIncomingMessage(Object message) {
if (isIncomingMessageValid(message, PaymentServiceSelectionResType.class)) {
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) {
X509Certificate contractCert = SecurityUtils.getContractCertificate();
/*
* 1. Check if certificate installation is needed
* No valid contract certificate means:
* - no contract certificate is stored, or
* - existing contract certificates are expired or revoked
*/
if (contractCert == null || (contractCert != null && !SecurityUtils.isCertificateValid(contractCert))) {
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UNKNOWN)) {
getCommSessionContext().setContractCertStatus(SecurityUtils.getContractCertificateStatus());
}
// 1. Check if certificate installation is needed
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.INSTALLATION_NEEDED)) {
if (getCommSessionContext().isCertificateServiceAvailable((short) 1)) {
if (contractCert == null) getLogger().info("No contract certificate stored, trying to install contract certificate");
else getLogger().info("Stored contract certificate not valid, trying to install new contract certificate");
getLogger().info("Trying to install new contract certificate");
return getSendMessage(getCertificateInstallationReq(), V2GMessages.CERTIFICATE_INSTALLATION_RES);
} else return new TerminateSession("Certificate installation needed but service is not available");
}
// 2. Check if certificate update is needed (means: certificate is available but expires soon)
short validityOfContractCert = SecurityUtils.getValidityPeriod(contractCert);
if (validityOfContractCert <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UPDATE_NEEDED)) {
if (getCommSessionContext().isCertificateServiceAvailable((short) 2)) {
getLogger().info("Stored contract certificate is about to expire in " + validityOfContractCert +
" days, trying to update contract certificate");
getLogger().info("Trying to update contract certificate");
return getSendMessage(getCertificateUpdateReq(), V2GMessages.CERTIFICATE_UPDATE_RES);
} else return new TerminateSession("Certificate update needed but service is not available");
}
@ -80,7 +72,13 @@ public class WaitForPaymentServiceSelectionRes extends ClientState {
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
CertificateInstallationReqType certInstallationReq = new CertificateInstallationReqType();
certInstallationReq.setId("certificateInstallationReq");
/*
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certInstallationReq.setId("id1");
certInstallationReq.setListOfRootCertificateIDs(
SecurityUtils.getListOfRootCertificateIDs(
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),
@ -113,7 +111,13 @@ public class WaitForPaymentServiceSelectionRes extends ClientState {
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()),
GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString()));
certificateUpdateReq.setEMAID(SecurityUtils.getEMAID(GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()).getValue());
certificateUpdateReq.setId("certificateUpdateReq");
/*
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certificateUpdateReq.setId("id1");
certificateUpdateReq.setListOfRootCertificateIDs(
SecurityUtils.getListOfRootCertificateIDs(
GlobalValues.EVCC_TRUSTSTORE_FILEPATH.toString(),

View File

@ -10,18 +10,13 @@
*******************************************************************************/
package org.eclipse.risev2g.evcc.states;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.Date;
import org.eclipse.risev2g.evcc.session.V2GCommunicationSessionEVCC;
import org.eclipse.risev2g.evcc.transportLayer.TLSClient;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.enumerations.V2GMessages;
import org.eclipse.risev2g.shared.messageHandling.ReactionToIncomingMessage;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.utils.MiscUtils;
import org.eclipse.risev2g.shared.utils.SecurityUtils;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.CertificateChainType;
import org.eclipse.risev2g.shared.utils.SecurityUtils.ContractCertificateStatus;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EnergyTransferModeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PaymentOptionType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.SelectedServiceType;
@ -98,28 +93,17 @@ public class WaitForServiceDiscoveryRes extends ClientState {
* Furthermore, it must be checked if VAS are allowed (-> only if TLS connection is used)
*/
private boolean useVAS(ServiceDiscoveryResType serviceDiscoveryRes) {
if (getCommSessionContext().getTransportLayerClient() instanceof TLSClient) {
if (serviceDiscoveryRes.getServiceList() != null &&
getCommSessionContext().getTransportLayerClient() instanceof TLSClient) {
// Check if certificate service is needed
if (isCertificateServiceOffered(serviceDiscoveryRes.getServiceList())) {
KeyStore evccKeyStore = SecurityUtils.getKeyStore(
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
getCommSessionContext().setContractCertStatus(SecurityUtils.getContractCertificateStatus());
CertificateChainType contractCertificateChain =
SecurityUtils.getCertificateChain(evccKeyStore, GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
if (contractCertificateChain != null) {
if (!SecurityUtils.isCertificateChainValid(contractCertificateChain)) {
if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.INSTALLATION_NEEDED))
addSelectedService(2, (short) 1);
} else {
if (isContractCertificateUpdateNeeded(contractCertificateChain)) {
else if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UPDATE_NEEDED))
addSelectedService(2, (short) 2);
}
}
} else {
addSelectedService(2, (short) 1);
}
}
// Optionally, other value added services can be checked for here ...
} else return false;
@ -143,7 +127,13 @@ public class WaitForServiceDiscoveryRes extends ClientState {
getCommSessionContext().getServiceDetailsToBeRequested().add((short) serviceID);
}
private boolean isCertificateServiceOffered(ServiceListType offeredServiceList) {
if (offeredServiceList == null) {
getLogger().debug("No value added services offered by EVCC");
return false;
}
for (ServiceType service : offeredServiceList.getService()) {
if (service.getServiceCategory().equals(ServiceCategoryType.CONTRACT_CERTIFICATE))
return true;
@ -151,21 +141,4 @@ public class WaitForServiceDiscoveryRes extends ClientState {
return false;
}
private boolean isContractCertificateUpdateNeeded(CertificateChainType contractCertificateChain) {
Date today = new Date();
X509Certificate contractCertificate = SecurityUtils.getCertificate(contractCertificateChain.getCertificate());
long validityDays = contractCertificate.getNotAfter().getTime() - today.getTime();
if (contractCertificate != null && validityDays <
( ((long) (int) MiscUtils.getPropertyValue("ContractCertificateUpdateTimespan")) * 24 * 60 * 60 * 1000 )) {
getLogger().info("Contract certificate with distinguished name '" +
contractCertificate.getSubjectX500Principal().getName() +
"' is only valid for " + validityDays / (1000 * 60 * 60 * 24) +
" days and needs to be updated");
return true;
} else return false;
}
}

View File

@ -15,14 +15,10 @@ import java.net.Inet6Address;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.security.cert.Certificate;
import java.security.cert.CertificateExpiredException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import org.eclipse.risev2g.shared.enumerations.GlobalValues;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.SecurityUtils;

View File

@ -63,3 +63,14 @@ SupportedPaymentOptions = Contract, ExternalPayment
# - false
PrivateEnvironment = false
# XML representation of messages
#-------------------------------
#
# Possible values:
# - true
# - 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 = false

View File

@ -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;
@ -28,8 +29,15 @@ 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;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
public class DummyBackendInterface implements IBackendInterface {
private V2GCommunicationSessionSECC commSessionContext;
@ -40,51 +48,130 @@ 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();
/*
* Put 'em all together
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
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));
// 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
public CertificateChainType getContractCertificateChain() {
@ -96,7 +183,12 @@ public class DummyBackendInterface implements IBackendInterface {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(
"./contractCert.p12",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
return SecurityUtils.getPrivateKey(keyStore);
ECPrivateKey privateKey = SecurityUtils.getPrivateKey(keyStore);
if (privateKey == null)
getLogger().error("No private key available from contract certificate keystore");
return privateKey;
}
@ -105,9 +197,15 @@ public class DummyBackendInterface implements IBackendInterface {
KeyStore keyStore = SecurityUtils.getPKCS12KeyStore(
"./provServiceCert.p12",
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString());
return SecurityUtils.getPrivateKey(keyStore);
ECPrivateKey privateKey = SecurityUtils.getPrivateKey(keyStore);
if (privateKey == null)
getLogger().error("No private key available from provisioning service keystore");
return privateKey;
}
@Override
public CertificateChainType getSAProvisioningCertificateChain() {
return SecurityUtils.getCertificateChain("./provServiceCert.p12");

View File

@ -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);
/**

View File

@ -20,6 +20,7 @@ import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEChargeParameterType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusCodeType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.DCEVSEStatusType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.EVSENotificationType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.IsolationLevelType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.MeterInfoType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.PhysicalValueType;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.UnitSymbolType;
@ -87,7 +88,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(IsolationLevelType.VALID);
return dcEvseStatus;
}

View File

@ -29,7 +29,6 @@ import org.eclipse.risev2g.shared.messageHandling.MessageHandler;
import org.eclipse.risev2g.shared.messageHandling.TerminateSession;
import org.eclipse.risev2g.shared.misc.V2GTPMessage;
import org.eclipse.risev2g.shared.utils.ByteUtils;
import org.eclipse.risev2g.shared.utils.MiscUtils;
import org.eclipse.risev2g.shared.v2gMessages.SECCDiscoveryReq;
import org.eclipse.risev2g.shared.v2gMessages.SECCDiscoveryRes;

View File

@ -88,7 +88,6 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
private PaymentOptionType selectedPaymentOption;
private CertificateChainType contractSignatureCertChain;
private MeterInfoType sentMeterInfo;
private boolean tlsConnection;
public V2GCommunicationSessionSECC(ConnectionHandler connectionHandler) {
setConnectionHandler(connectionHandler);
@ -452,16 +451,6 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
}
public boolean isTlsConnection() {
return tlsConnection;
}
public void setTlsConnection(boolean tlsConnection) {
this.tlsConnection = tlsConnection;
}
public PaymentOptionType getSelectedPaymentOption() {
return selectedPaymentOption;
}

View File

@ -76,9 +76,11 @@ public class WaitForAuthorizationReq extends ServerState {
// Verify signature
HashMap<String, byte[]> verifyXMLSigRefElements = new HashMap<String, byte[]>();
verifyXMLSigRefElements.put(authorizationReq.getId(), SecurityUtils.generateDigest(authorizationReq, false));
ECPublicKey ecPublicKey = (ECPublicKey) SecurityUtils.getCertificate(
getCommSessionContext().getContractSignatureCertChain().getCertificate())
.getPublicKey();
if (!SecurityUtils.verifySignature(signature, verifyXMLSigRefElements, ecPublicKey)) {
authorizationRes.setResponseCode(ResponseCodeType.FAILED_SIGNATURE_ERROR);
return false;

View File

@ -61,13 +61,19 @@ public class WaitForCertificateInstallationReq extends ServerState {
getCommSessionContext().getBackendInterface().getContractCertificatePrivateKey());
certificateInstallationRes.setContractSignatureCertChain(saContractCertificateChain);
certificateInstallationRes.getContractSignatureCertChain().setId("contractSignatureCertChain");
/*
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certificateInstallationRes.getContractSignatureCertChain().setId("id1"); // contractSignatureCertChain
certificateInstallationRes.setContractSignatureEncryptedPrivateKey(encryptedContractCertPrivateKey);
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().setId("contractSignatureEncryptedPrivateKey");
certificateInstallationRes.getContractSignatureEncryptedPrivateKey().setId("id2"); // contractSignatureEncryptedPrivateKey
certificateInstallationRes.setDHpublickey(SecurityUtils.getDHPublicKey(ecdhKeyPair));
certificateInstallationRes.getDHpublickey().setId("dhPublicKey");
certificateInstallationRes.getDHpublickey().setId("id3"); // dhPublicKey
certificateInstallationRes.setEMAID(SecurityUtils.getEMAID(saContractCertificateChain));
certificateInstallationRes.getEMAID().setId("emaid");
certificateInstallationRes.getEMAID().setId("id4"); // emaid
certificateInstallationRes.setSAProvisioningCertificateChain(
getCommSessionContext().getBackendInterface().getSAProvisioningCertificateChain());

View File

@ -63,13 +63,19 @@ public class WaitForCertificateUpdateReq extends ServerState {
getCommSessionContext().getBackendInterface().getContractCertificatePrivateKey());
certificateUpdateRes.setContractSignatureCertChain(contractCertificateChain);
certificateUpdateRes.getContractSignatureCertChain().setId("contractSignatureCertChain");
/*
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
certificateUpdateRes.getContractSignatureCertChain().setId("id1"); // contractSignatureCertChain
certificateUpdateRes.setContractSignatureEncryptedPrivateKey(encryptedContractCertPrivateKey);
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().setId("contractSignatureEncryptedPrivateKey");
certificateUpdateRes.getContractSignatureEncryptedPrivateKey().setId("id2"); // contractSignatureEncryptedPrivateKey
certificateUpdateRes.setDHpublickey(SecurityUtils.getDHPublicKey(ecdhKeyPair));
certificateUpdateRes.getDHpublickey().setId("dhPublicKey");
certificateUpdateRes.getDHpublickey().setId("id3"); // dhPublicKey
certificateUpdateRes.setEMAID(SecurityUtils.getEMAID(contractCertificateChain));
certificateUpdateRes.getEMAID().setId("emaid");
certificateUpdateRes.getEMAID().setId("id4"); // emaid
certificateUpdateRes.setSAProvisioningCertificateChain(getCommSessionContext().getBackendInterface().getSAProvisioningCertificateChain());
// In case of negative response code, try at next charging (retryCounter = 0)

View File

@ -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

View File

@ -37,6 +37,9 @@ public class WaitForPaymentServiceSelectionReq extends ServerState {
PaymentServiceSelectionReqType paymentServiceSelectionReq =
(PaymentServiceSelectionReqType) v2gMessageReq.getBody().getBodyElement().getValue();
getLogger().info("Payment option " + paymentServiceSelectionReq.getSelectedPaymentOption().toString() +
" has been chosen by EVCC");
if (isResponseCodeOK(paymentServiceSelectionReq)) {
// see [V2G2-551]
if (paymentServiceSelectionReq.getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) {

View File

@ -78,8 +78,19 @@ public class WaitForServiceDiscoveryReq extends ServerState {
chargeService.setSupportedEnergyTransferMode(supportedEnergyTransferModes);
chargeService.setServiceCategory(ServiceCategoryType.EV_CHARGING);
chargeService.setServiceID(1); // according to Table 105 ISO/IEC 15118-2
chargeService.setServiceName("EV charging (AC/DC)"); // optional value
chargeService.setServiceScope(""); // optional value
/*
* Is an optional value, but fill it with a non-empty string if used,
* otherwise an EXI decoding error could occur on the other side!
*/
chargeService.setServiceName("EV charging (AC/DC)");
/*
* Is an optional value, but fill it with a non-empty string if used,
* otherwise an EXI decoding error could occur on the other side!
*/
// chargeService.setServiceScope("");
chargeService.setFreeService(false); // it is supposed that charging is by default not for free
return chargeService;

View File

@ -77,10 +77,18 @@ public class WaitForSupportedAppProtocolReq extends ServerState {
} else if (message instanceof SECCDiscoveryReq) {
getLogger().debug("Another SECCDiscoveryReq was received, changing to state WaitForSECCDiscoveryReq");
return new ChangeProcessingState(message, getCommSessionContext().getStates().get(V2GMessages.SECC_DISCOVERY_REQ));
} else {
} else if (message != null) {
/*
* This check has been introduced to make sure the application can deal with incoming messages which rely
* on the DINSPEC 70121 XSD schema (which is different from the ISO 15118-2 schema. Without this check,
* the message.getClass() would throw a NullPointerException and the application would die.
*/
getLogger().error("Invalid message (" + message.getClass().getSimpleName() +
") at this state (" + this.getClass().getSimpleName() + ")");
supportedAppProtocolRes.setResponseCode(ResponseCodeType.FAILED_NO_NEGOTIATION);
} else {
getLogger().error("Invalid message at this state, message seems to be null. Check if same XSD schema is used on EVCC side.");
supportedAppProtocolRes.setResponseCode(ResponseCodeType.FAILED_NO_NEGOTIATION);
}
return getSendMessage(supportedAppProtocolRes,

View File

@ -17,7 +17,6 @@ import java.io.InputStream;
import java.io.StringWriter;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
@ -26,6 +25,7 @@ import javax.xml.bind.ValidationEventHandler;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.eclipse.risev2g.shared.utils.MiscUtils;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolReq;
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
import org.eclipse.risev2g.shared.v2gMessages.msgDef.V2GMessage;
@ -39,6 +39,7 @@ public abstract class ExiCodec {
private InputStream inStream;
private Object decodedMessage;
private String decodedExi;
private boolean xmlRepresentation;
public ExiCodec() {
try {
@ -57,6 +58,12 @@ public abstract class ExiCodec {
event.getLinkedException());
}
});
// Check if XML representation of sent messages is to be shown (for debug purposes)
if ((boolean) MiscUtils.getPropertyValue("XMLRepresentationOfMessages"))
setXMLRepresentation(true);
else
setXMLRepresentation(false);
} catch (JAXBException e) {
getLogger().error("A JAXBException occurred while trying to instantiate " + this.getClass().getSimpleName(), e);
}
@ -72,6 +79,7 @@ public abstract class ExiCodec {
setInStream(new ByteArrayInputStream(baos.toByteArray()));
baos.close();
if (isXMLRepresentation()) {
// For debugging purposes, you can view the XML representation of marshalled messages
StringWriter sw = new StringWriter();
String className = "";
@ -89,6 +97,7 @@ public abstract class ExiCodec {
getMarshaller().marshal(jaxbObject, sw);
getLogger().debug("XML representation of " + className + ":\n" + sw.toString());
sw.close();
}
return getInStream();
} catch (JAXBException | IOException e) {
@ -171,4 +180,13 @@ public abstract class ExiCodec {
public void setInStream(InputStream inStream) {
this.inStream = inStream;
}
private void setXMLRepresentation(boolean showXMLRepresentation) {
this.xmlRepresentation = showXMLRepresentation;
}
public boolean isXMLRepresentation() {
return xmlRepresentation;
}
}

View File

@ -38,7 +38,7 @@ public abstract class V2GCommunicationSession extends Observable {
private byte[] sessionID;
private V2GTPMessage v2gTpMessage;
private V2GMessage v2gMessage;
private boolean secureCommunication;
private boolean tlsConnection;
public V2GCommunicationSession() {
setStates(new HashMap<V2GMessages, State>());
@ -120,6 +120,10 @@ public abstract class V2GCommunicationSession extends Observable {
paymentOptions = new ArrayList<PaymentOptionType>();
}
// Contract-based payment may only be offered if TLS is used
if (!isTlsConnection())
paymentOptions.remove(PaymentOptionType.CONTRACT);
PaymentOptionListType paymentOptionList = new PaymentOptionListType();
paymentOptionList.getPaymentOption().addAll(paymentOptions);
@ -210,13 +214,12 @@ public abstract class V2GCommunicationSession extends Observable {
this.v2gMessage = v2gMessage;
}
public boolean isSecureCommunication() {
return secureCommunication;
public boolean isTlsConnection() {
return tlsConnection;
}
public void setSecureCommunication(boolean secureCommunication) {
this.secureCommunication = secureCommunication;
public void setTlsConnection(boolean tlsConnection) {
this.tlsConnection = tlsConnection;
}
}

View File

@ -212,6 +212,10 @@ public final class MiscUtils {
case "PrivateEnvironment": // EVSE property
returnValue = Boolean.parseBoolean(propertyValue);
break;
case "XMLRepresentationOfMessages": // EV + EVSE property
if (Boolean.parseBoolean(propertyValue)) returnValue = true;
else returnValue = false;
break;
default:
getLogger().error("No property with name '" + propertyName + "' found");
}
@ -260,7 +264,7 @@ public final class MiscUtils {
* (I don't know how to infer the type correctly)
*
* @param messageOrField The message or field for which a digest is to be generated
* @return
* @return The JAXBElement of the provided message or field
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static JAXBElement getJaxbElement(Object messageOrField) {

View File

@ -105,6 +105,13 @@ public final class SecurityUtils {
static Logger logger = LogManager.getLogger(SecurityUtils.class.getSimpleName());
static ExiCodec exiCodec;
public static enum ContractCertificateStatus {
UPDATE_NEEDED,
INSTALLATION_NEEDED,
OK,
UNKNOWN // is used as default for communication session context
}
public static Logger getLogger() {
return logger;
}
@ -594,7 +601,13 @@ public final class SecurityUtils {
*/
public static DiffieHellmanPublickeyType getDHPublicKey(KeyPair ecdhKeyPair) {
DiffieHellmanPublickeyType dhPublicKey = new DiffieHellmanPublickeyType();
dhPublicKey.setId("dhPublicKey");
/*
* 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
* systems, the memory is very limited which is why we should not use long IDs for the signature reference
* element. A good size would be 3 characters max (like the example in the ISO 15118-2 annex J)
*/
dhPublicKey.setId("id1"); // dhPublicKey
dhPublicKey.setValue(ecdhKeyPair.getPublic().getEncoded());
return dhPublicKey;
@ -828,9 +841,12 @@ public final class SecurityUtils {
keyStore.store(fos, GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString().toCharArray());
fos.close();
X509Certificate contractCert = getCertificate(contractCertChain.getCertificate());
getLogger().info("Contract certificate with distinguished name '" +
getCertificate(contractCertChain.getCertificate())
.getSubjectX500Principal().getName() + "' saved");
contractCert.getSubjectX500Principal().getName() + "' saved. " +
"Valid until " + contractCert.getNotAfter()
);
} else {
return false;
}
@ -844,6 +860,13 @@ public final class SecurityUtils {
}
/**
* Checks if the private key is a valid key (according to requirement [V2G2-823]) for the received contract
* certificate before saving it to the keystore.
* @param privateKey The private key corresponding to the contract certificate
* @param contractCertChain The received contract certificate chain
* @return True, if the private key is a valid key, false otherwise.
*/
private static boolean isPrivateKeyValid(ECPrivateKey privateKey, CertificateChainType contractCertChain) {
AlgorithmParameters parameters;
@ -886,6 +909,11 @@ public final class SecurityUtils {
}
/**
* Gets the contract certificate from the EVCC keystore.
*
* @return The contract certificate if present, null otherwise
*/
public static X509Certificate getContractCertificate() {
X509Certificate contractCertificate = null;
@ -904,6 +932,89 @@ public final class SecurityUtils {
}
/**
* A convenience function which checks if a contract certificate installation is needed.
* Normally not needed because of function getContractCertificateStatus().
*
* @return True, if no contract certificate is store or if the stored certificate is not valid, false otherwise
*/
public static boolean isContractCertificateInstallationNeeded() {
X509Certificate contractCert = getContractCertificate();
if (contractCert == null) {
getLogger().info("No contract certificate stored");
return true;
} else if (contractCert != null && !isCertificateValid(contractCert)) {
getLogger().info("Stored contract certificate with distinguished name '" +
contractCert.getSubjectX500Principal().getName() + "' is not valid");
return true;
} else return false;
}
/**
* A convenience function which checks if a contract certificate update is needed.
* Normally not needed because of function getContractCertificateStatus().
*
* @return True, if contract certificate is still valid but about to expire, false otherwise.
* The expiration period is given in GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.
*/
public static boolean isContractCertificateUpdateNeeded() {
X509Certificate contractCert = getContractCertificate();
short validityOfContractCert = getValidityPeriod(contractCert);
if (validityOfContractCert < 0) {
getLogger().warn("Contract certificate with distinguished name '" +
contractCert.getSubjectX500Principal().getName() + "' is not valid any more, expired " +
Math.abs(validityOfContractCert) + " days ago");
return false;
} else if (validityOfContractCert <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
getLogger().info("Contract certificate with distinguished name '" +
contractCert.getSubjectX500Principal().getName() + "' is about to expire in " +
validityOfContractCert + " days");
return true;
} else return false;
}
/**
* Checks whether a contract certificate
* - is stored
* - in case it is stored, if it is valid
* - in case it is valid, if it expires soon
*
* This method is intended to reduce cryptographic computation overhead by checking both, if installation or
* update is needed, at the same time. When executing either method by itself (isContractCertificateUpdateNeeded() and
* isContractCertificateInstallationNeeded()), each time the certificate is read anew from the Java keystore
* holding the contract certificate. With this method the contract certificate is read just once from the keystore.
*
* @return An enumeration value ContractCertificateStatus (either UPDATE_NEEDED, INSTALLATION_NEEDED, or OK)
*/
public static ContractCertificateStatus getContractCertificateStatus() {
X509Certificate contractCert = getContractCertificate();
if (contractCert == null) {
getLogger().info("No contract certificate stored");
return ContractCertificateStatus.INSTALLATION_NEEDED;
} else if (contractCert != null && !isCertificateValid(contractCert)) {
getLogger().info("Stored contract certificate with distinguished name '" +
contractCert.getSubjectX500Principal().getName() + "' is not valid");
return ContractCertificateStatus.INSTALLATION_NEEDED;
} else {
short validityOfContractCert = getValidityPeriod(contractCert);
// Checking for a negative value of validityOfContractCert is not needed because the method
// isCertificateValid() already checks for that
if (validityOfContractCert <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
getLogger().info("Contract certificate with distinguished name '" +
contractCert.getSubjectX500Principal().getName() + "' is about to expire in " +
validityOfContractCert + " days");
return ContractCertificateStatus.UPDATE_NEEDED;
}
return ContractCertificateStatus.OK;
}
}
/**
* Returns a list of certificates from the given CertificateChainType with the leaf certificate
* being the first element and potential subcertificates (intermediate CA certificatess)
@ -1487,7 +1598,7 @@ public final class SecurityUtils {
SignatureType signature,
HashMap<String, byte[]> verifyXMLSigRefElements,
ECPublicKey ecPublicKey) {
byte[] providedDigest;
byte[] calculatedReferenceDigest;
boolean match;
/*
@ -1498,17 +1609,28 @@ public final class SecurityUtils {
for (String id : verifyXMLSigRefElements.keySet()) {
getLogger().debug("Verifying digest for element '" + id + "'");
match = false;
providedDigest = verifyXMLSigRefElements.get(id);
calculatedReferenceDigest = verifyXMLSigRefElements.get(id);
// A bit inefficient, but there are max. 4 elements to iterate over (what would be more efficient?)
for (ReferenceType reference : signature.getSignedInfo().getReference()) {
if (reference.getId().equals(id) && Arrays.equals(reference.getDigestValue(), providedDigest))
if (reference == null) {
getLogger().warn("Reference element to check is null");
continue;
}
// We need to check the URI attribute, the Id attribute is likely to be null
if (reference.getURI() == null) {
getLogger().warn("Reference ID element is null");
continue;
}
if (reference.getURI().equals('#' + id) && Arrays.equals(reference.getDigestValue(), calculatedReferenceDigest))
match = true;
}
if (!match) {
getLogger().error("No matching signature found for ID '" + id + "' and digest value " +
ByteUtils.toHexString(providedDigest));
ByteUtils.toHexString(calculatedReferenceDigest));
return false;
}
}