1
0
Эх сурвалжийг харах

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
Marc Mültin 9 жил өмнө
parent
commit
e0f2d36bbc
30 өөрчлөгдсөн 524 нэмэгдсэн , 170 устгасан
  1. 12 1
      RISE-V2G-EVCC/EVCCConfig.properties
  2. 6 5
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/evController/DummyEVController.java
  3. 15 0
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/session/V2GCommunicationSessionEVCC.java
  4. 5 0
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/session/V2GCommunicationSessionHandlerEVCC.java
  5. 7 1
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/ClientState.java
  6. 0 1
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForCertificateInstallationRes.java
  7. 39 3
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForChargeParameterDiscoveryRes.java
  8. 8 2
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForChargingStatusRes.java
  9. 7 1
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForCurrentDemandRes.java
  10. 1 1
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForPaymentDetailsRes.java
  11. 27 23
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForPaymentServiceSelectionRes.java
  12. 13 40
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForServiceDiscoveryRes.java
  13. 0 4
      RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/transportLayer/TLSClient.java
  14. 11 0
      RISE-V2G-SECC/SECCConfig.properties
  15. 124 26
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/DummyBackendInterface.java
  16. 5 1
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/backend/IBackendInterface.java
  17. 2 1
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/evseController/DummyDCEVSEController.java
  18. 1 2
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/session/V2GCommunicationSessionHandlerSECC.java
  19. 0 11
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/session/V2GCommunicationSessionSECC.java
  20. 2 0
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForAuthorizationReq.java
  21. 10 4
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForCertificateInstallationReq.java
  22. 10 4
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForCertificateUpdateReq.java
  23. 14 3
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForChargeParameterDiscoveryReq.java
  24. 3 0
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForPaymentServiceSelectionReq.java
  25. 13 2
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForServiceDiscoveryReq.java
  26. 9 1
      RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForSupportedAppProtocolReq.java
  27. 35 17
      RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/exiCodec/ExiCodec.java
  28. 10 7
      RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/misc/V2GCommunicationSession.java
  29. 5 1
      RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/utils/MiscUtils.java
  30. 130 8
      RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/utils/SecurityUtils.java

+ 12 - 1
RISE-V2G-EVCC/EVCCConfig.properties

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

+ 6 - 5
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/evController/DummyEVController.java

@@ -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 
-			return PaymentOptionType.EXTERNAL_PAYMENT;
+		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;
 	}
 
 

+ 15 - 0
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/session/V2GCommunicationSessionEVCC.java

@@ -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);
@@ -127,6 +129,9 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
 		 * TODO check if this timing requirement is still up to date
 		 */
 		setV2gEVCCCommunicationSetupTimer(System.currentTimeMillis());
+		
+		// Set default value for contract certificate status to UNKNOWN
+		setContractCertStatus(ContractCertificateStatus.UNKNOWN);
 			
 		getLogger().debug("\n*******************************************" +
 						  "\n* New V2G communication session initialized" +
@@ -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;
+	}
+
 }

+ 5 - 0
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");

+ 7 - 1
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/ClientState.java

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

+ 0 - 1
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForCertificateInstallationRes.java

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

+ 39 - 3
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;
@@ -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;
 	}
 	

+ 8 - 2
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForChargingStatusRes.java

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

+ 7 - 1
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForCurrentDemandRes.java

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

+ 1 - 1
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForPaymentDetailsRes.java

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

+ 27 - 23
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForPaymentServiceSelectionRes.java

@@ -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,34 +33,27 @@ 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");
-				} 
-					
+				}
+
 				return getSendMessage(getPaymentDetailsReq(), V2GMessages.PAYMENT_DETAILS_RES);
 			} else if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.EXTERNAL_PAYMENT)) {
 				return getSendMessage(getAuthorizationReq(null), V2GMessages.AUTHORIZATION_RES);
@@ -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(),

+ 13 - 40
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/states/WaitForServiceDiscoveryRes.java

@@ -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,27 +93,16 @@ 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)) {
-						addSelectedService(2, (short) 1); 
-					} else {
-						if (isContractCertificateUpdateNeeded(contractCertificateChain)) {
-							addSelectedService(2, (short) 2); 
-						} 
-					}
-				} else {
+				if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.INSTALLATION_NEEDED))
 					addSelectedService(2, (short) 1); 
-				}
+				else if (getCommSessionContext().getContractCertStatus().equals(ContractCertificateStatus.UPDATE_NEEDED))
+					addSelectedService(2, (short) 2); 
 			}
 			
 			// Optionally, other value added services can be checked for here ...
@@ -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;
-	}
 }

+ 0 - 4
RISE-V2G-EVCC/src/main/java/org/eclipse/risev2g/evcc/transportLayer/TLSClient.java

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

+ 11 - 0
RISE-V2G-SECC/SECCConfig.properties

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

+ 124 - 26
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;
@@ -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,50 +48,129 @@ 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
@@ -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");

+ 5 - 1
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<String, byte[]> xmlSignatureRefElements);
 	
 	
 	/**

+ 2 - 1
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/evseController/DummyDCEVSEController.java

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

+ 1 - 2
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/session/V2GCommunicationSessionHandlerSECC.java

@@ -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;
 
@@ -142,7 +141,7 @@ public class V2GCommunicationSessionHandlerSECC implements Observer {
 			 */
 			byte[] seccAddress = (isSecureCommunication()) ? TLSServer.getInstance().getServerAddress().getAddress() : TCPServer.getInstance().getServerAddress().getAddress();
 			int seccPort = (isSecureCommunication()) ? TLSServer.getInstance().getServerPort() : TCPServer.getInstance().getServerPort();
-			
+					
 			SECCDiscoveryRes seccDiscoveryRes = new SECCDiscoveryRes(
 														seccAddress,
 														ByteUtils.toByteArrayFromInt(seccPort, true),

+ 0 - 11
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/session/V2GCommunicationSessionSECC.java

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

+ 2 - 0
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForAuthorizationReq.java

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

+ 10 - 4
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForCertificateInstallationReq.java

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

+ 10 - 4
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForCertificateUpdateReq.java

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

+ 14 - 3
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 

+ 3 - 0
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForPaymentServiceSelectionReq.java

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

+ 13 - 2
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForServiceDiscoveryReq.java

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

+ 9 - 1
RISE-V2G-SECC/src/main/java/org/eclipse/risev2g/secc/states/WaitForSupportedAppProtocolReq.java

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

+ 35 - 17
RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/exiCodec/ExiCodec.java

@@ -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,24 +79,26 @@ public abstract class ExiCodec {
 			setInStream(new ByteArrayInputStream(baos.toByteArray()));
 			baos.close();
 			
-			// For debugging purposes, you can view the XML representation of marshalled messages
-			StringWriter sw = new StringWriter();
-			String className = "";
-
-			if (jaxbObject instanceof V2GMessage) {
-				className = ((V2GMessage) jaxbObject).getBody().getBodyElement().getName().getLocalPart();
-			} else if (jaxbObject instanceof SupportedAppProtocolReq) {
-				className = "SupportedAppProtocolReq"; 
-			} else if (jaxbObject instanceof SupportedAppProtocolRes) {
-				className = "SupportedAppProtocolRes";
-			} else {
-				className = "marshalled JAXBElement";
+			if (isXMLRepresentation()) {
+				// For debugging purposes, you can view the XML representation of marshalled messages
+				StringWriter sw = new StringWriter();
+				String className = "";
+	
+				if (jaxbObject instanceof V2GMessage) {
+					className = ((V2GMessage) jaxbObject).getBody().getBodyElement().getName().getLocalPart();
+				} else if (jaxbObject instanceof SupportedAppProtocolReq) {
+					className = "SupportedAppProtocolReq"; 
+				} else if (jaxbObject instanceof SupportedAppProtocolRes) {
+					className = "SupportedAppProtocolRes";
+				} else {
+					className = "marshalled JAXBElement";
+				}
+				
+				getMarshaller().marshal(jaxbObject, sw);
+				getLogger().debug("XML representation of " + className + ":\n" + sw.toString());
+				sw.close();
 			}
 			
-			getMarshaller().marshal(jaxbObject, sw);
-			getLogger().debug("XML representation of " + className + ":\n" + sw.toString());
-			sw.close();
-			
 			return getInStream();
 		} catch (JAXBException | IOException e) {
 			getLogger().error(e.getClass().getSimpleName() + " occurred while trying to marshal to InputStream from JAXBElement", 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;
+	}
 }

+ 10 - 7
RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/misc/V2GCommunicationSession.java

@@ -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);
 		
@@ -209,14 +213,13 @@ public abstract class V2GCommunicationSession extends Observable {
 	public void setV2gMessage(V2GMessage v2gMessage) {
 		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;
 	}
 }

+ 5 - 1
RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/utils/MiscUtils.java

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

+ 130 - 8
RISE-V2G-Shared/src/main/java/org/eclipse/risev2g/shared/utils/SecurityUtils.java

@@ -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;
 		
@@ -885,7 +908,12 @@ public final class SecurityUtils {
 		return true;
 	}
 	
-	
+
+	/**
+	 * 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;
 			}
 		}