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:
commit
e0f2d36bbc
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue