Enhanced TLS-related features:
- added ResponseCode 'OK_CertificateExpiresSoon' on SECC side when receiving ContractCertificate with PaymentDetailsReq - modified TLSClient to initiate TLS handshake right when initializing TLSClient (instead of when sending first message), thus enabling the check for the correct domain component "CPO" of the SECC certificate - validates now provisioning certificate chain and checks for correct domain component "CPS" of provisioning leaf certificate - added SecurityUtils function to get contract certificate from EVCC keystore - added SecurityUtils function to get validity period of contract certificate - added SecurityUtils function to also check for correct domain component when checking validity of certificate - implemented a correct check if certificate update or installation is needed upon receiving PaymentServiceSelectionRes on EVCC side
This commit is contained in:
parent
6329658d4a
commit
ac7a9095f0
|
@ -1,3 +1,11 @@
|
|||
*.jks
|
||||
*.p12
|
||||
*.pem
|
||||
*.cert
|
||||
*.cer
|
||||
*.key
|
||||
*.pfx
|
||||
*.bin
|
||||
*.class
|
||||
**/bin
|
||||
*/bin
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
# This file shall serve you with all information needed to create your own certificate chains.
|
||||
#
|
||||
# Helpful information about using OpenSSL is provided by Ivan Ristic's book "Bulletproof SSL and TLS".
|
||||
# Furthermore, you should have OpenSSL 1.0.2 (or above) installed to comply with all security requirements imposed by ISO/IEC 15118. For example, OpenSSL 0.9.8 does not come with SHA-2 for SHA-256 signature algorithms.
|
||||
# Furthermore, you should have OpenSSL 1.0.2 (or above) installed to comply with all security requirements imposed by ISO 15118. For example, OpenSSL 0.9.8 does not come with SHA-2 for SHA-256 signature algorithms.
|
||||
#
|
||||
# Author: Marc Mültin (marc.mueltin@chargepartner.com)
|
||||
|
||||
|
@ -49,7 +49,7 @@ openssl x509 -req -in csrs/cpoSub1CA.csr -extfile configs/cpoSub1CA.cnf -extensi
|
|||
# 3) Create a second intermediate CPO sub-CA certificate just the way the previous intermedia certificate was created which is directly signed by the CPOSub1CA
|
||||
# Differences to CPOSub1CA
|
||||
# - basicConstraints in config file sets pathlength to 0 (meaning that no further sub CA's certificate may be signed with this certificate, a leaf certificate must follow this certificate in a certificate chain)
|
||||
# - validity is set to 1 year (1 - 2 years are allowed according to ISO/IEC 15118)
|
||||
# - validity is set to 1 year (1 - 2 years are allowed according to ISO 15118)
|
||||
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/cpoSub2CA.key -aes128 -passout file:passphrase.txt
|
||||
openssl req -new -key privateKeys/cpoSub2CA.key -passin file:passphrase.txt -config configs/cpoSub2CA.cnf -extensions ext -out csrs/cpoSub2CA.csr
|
||||
openssl x509 -req -in csrs/cpoSub2CA.csr -extfile configs/cpoSub2CA.cnf -extensions ext -CA certs/cpoSub1CA.pem -CAkey privateKeys/cpoSub1CA.key -set_serial 03 -passin file:passphrase.txt -days 365 -out certs/cpoSub2CA.pem
|
||||
|
@ -59,7 +59,7 @@ openssl x509 -req -in csrs/cpoSub2CA.csr -extfile configs/cpoSub2CA.cnf -extensi
|
|||
# Differences to CPOSub1CA and CPOSub2CA
|
||||
# - basicConstraints sets CA to false, no pathlen is therefore set
|
||||
# - keyusage is set to digitalSignature instead of keyCertSign and cRLSign
|
||||
# - validity is set to 60 days (2 - 3 months are allowed according to ISO/IEC 15118)
|
||||
# - validity is set to 60 days (2 - 3 months are allowed according to ISO 15118)
|
||||
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/seccCert.key -aes128 -passout file:passphrase.txt
|
||||
openssl req -new -key privateKeys/seccCert.key -passin file:passphrase.txt -config configs/seccCert.cnf -extensions ext -out csrs/seccCert.csr
|
||||
openssl x509 -req -in csrs/seccCert.csr -extfile configs/seccCert.cnf -extensions ext -CA certs/cpoSub2CA.pem -CAkey privateKeys/cpoSub2CA.key -set_serial 04 -passin file:passphrase.txt -days 60 -out certs/seccCert.pem
|
||||
|
@ -98,7 +98,7 @@ cat certs/oemSub2CA.pem certs/oemSub1CA.pem > certs/intermediateOEMCAs.pem
|
|||
openssl pkcs12 -export -inkey privateKeys/oemProvCert.key -in certs/oemProvCert.pem -certfile certs/intermediateOEMCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name oem_prov_cert -out certs/oemProvCert.p12
|
||||
|
||||
|
||||
# 9) Create a self-signed MORootCA (mobility operator) certificate (validity is up to the OEM, this example applies the same validity as the V2GRootCA)
|
||||
# 9) Create a self-signed MORootCA (mobility operator) certificate (validity is up to the MO, this example applies the same validity as the V2GRootCA)
|
||||
openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys/moRootCA.key -aes128 -passout file:passphrase.txt
|
||||
openssl req -new -x509 -days 14600 -sha256 -key privateKeys/moRootCA.key -set_serial 09 -passin file:passphrase.txt -config configs/moRootCA.cnf -extensions ext -out certs/moRootCA.pem
|
||||
|
||||
|
@ -145,7 +145,7 @@ cat certs/provSub2CA.pem certs/provSub1CA.pem > certs/intermediateProvCAs.pem
|
|||
openssl pkcs12 -export -inkey privateKeys/provServiceCert.key -in certs/provServiceCert.pem -certfile certs/intermediateProvCAs.pem -aes128 -passin file:passphrase.txt -passout file:passphrase2.txt -name prov_service_cert -out certs/provServiceCert.p12
|
||||
|
||||
|
||||
# XX) Finally we need to convert the certificates from PEM format to DER format (PEM is the default format, but ISO/IEC 15118 only allows DER format)
|
||||
# XX) Finally we need to convert the certificates from PEM format to DER format (PEM is the default format, but ISO 15118 only allows DER format)
|
||||
openssl x509 -inform PEM -in certs/v2gRootCA.pem -outform DER -out certs/v2gRootCA.crt
|
||||
openssl x509 -inform PEM -in certs/oemRootCA.pem -outform DER -out certs/oemRootCA.crt
|
||||
openssl x509 -inform PEM -in certs/moRootCA.pem -outform DER -out certs/moRootCA.crt
|
||||
|
@ -155,7 +155,7 @@ openssl x509 -inform PEM -in certs/moRootCA.pem -outform DER -out certs/moRootCA
|
|||
# XX) Create the initial Java truststores and keystores
|
||||
# XX.1) truststore for the EVCC which needs to hold the V2GRootCA certificate (the EVCC does not verify the received certificate chain, therefore no MORootCA needs to be imported in evccTruststore.jks )
|
||||
keytool -import -keystore keystores/evccTruststore.jks -alias v2g_root_ca -file certs/v2gRootCA.crt -storepass:file passphrase.txt -noprompt
|
||||
# XX.2) truststore for the SECC which needs to hold the V2GRootCA certificate and the MORootCA which signed the MOSub1CA (needed for verifying the contract certificate signature chain which will be sent from the EVCC to the SECC with PaymentDetailsReq message). According to ISO/IEC 15118-2, MORootCA is not necessarily needed as the MOSub1CA could instead be signed by a V2GRootCA.
|
||||
# XX.2) truststore for the SECC which needs to hold the V2GRootCA certificate and the MORootCA which signed the MOSub1CA (needed for verifying the contract certificate signature chain which will be sent from the EVCC to the SECC with PaymentDetailsReq message). According to ISO 15118-2, MORootCA is not necessarily needed as the MOSub1CA could instead be signed by a V2GRootCA.
|
||||
keytool -import -keystore keystores/seccTruststore.jks -alias v2g_root_ca -file certs/v2gRootCA.crt -storepass:file passphrase.txt -noprompt
|
||||
keytool -import -keystore keystores/seccTruststore.jks -alias mo_root_ca -file certs/moRootCA.crt -storepass:file passphrase.txt -noprompt
|
||||
# XX.3) keystore for the SECC which needs to hold the CPOSub1CA, CPOSub1CA and SECCCert certificates
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
# The network interface name like en3 or eth1 of the network interface on which to communicate with the SECC via a
|
||||
# link-local IPv6 address
|
||||
NetworkInterface = en3
|
||||
NetworkInterface = en0
|
||||
|
||||
|
||||
# Security
|
||||
|
@ -28,7 +28,7 @@ NetworkInterface = en3
|
|||
# - false
|
||||
# If this value is set to 'false', TCP will be used on transport layer
|
||||
# If no correct value is provided here, 'false' will be chosen
|
||||
TLSSecurity = false
|
||||
TLSSecurity = true
|
||||
|
||||
|
||||
# Contract certificate update timespan
|
||||
|
|
|
@ -10,10 +10,12 @@
|
|||
*******************************************************************************/
|
||||
package org.eclipse.risev2g.evcc.session;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Observable;
|
||||
import java.util.Observer;
|
||||
|
||||
import org.eclipse.risev2g.evcc.evController.DummyEVController;
|
||||
import org.eclipse.risev2g.evcc.evController.IEVController;
|
||||
import org.eclipse.risev2g.evcc.states.WaitForAuthorizationRes;
|
||||
|
@ -47,6 +49,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;
|
||||
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.AppProtocolType;
|
||||
import org.eclipse.risev2g.shared.v2gMessages.appProtocol.SupportedAppProtocolRes;
|
||||
import org.eclipse.risev2g.shared.v2gMessages.msgDef.ChargeParameterDiscoveryReqType;
|
||||
|
@ -425,26 +428,21 @@ public class V2GCommunicationSessionEVCC extends V2GCommunicationSession impleme
|
|||
}
|
||||
|
||||
|
||||
public boolean isCertificateInstallationNeeded() {
|
||||
/**
|
||||
* Checks if the respective service for installing or updating a certificate is offered by the SECC and
|
||||
* has been selected by the EVCC.
|
||||
*
|
||||
* @param parameterSetID 1 for installing a certificate, 2 for updating a certificate
|
||||
* @return True, if the respective certificate service is available, false otherwise
|
||||
*/
|
||||
public boolean isCertificateServiceAvailable(short parameterSetID) {
|
||||
for (SelectedServiceType service : getSelectedServices().getSelectedService()) {
|
||||
if (service.getServiceID() == 2 &&
|
||||
if (service.getServiceID() == 2 && // ServiceID 2 refers to the 'Certificate' service
|
||||
service.getParameterSetID() != null &&
|
||||
service.getParameterSetID() == 1)
|
||||
service.getParameterSetID() == parameterSetID)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public boolean isCertificateUpdateNeeded() {
|
||||
for (SelectedServiceType service : getSelectedServices().getSelectedService()) {
|
||||
if (service.getServiceID() == 2 &&
|
||||
service.getParameterSetID() != null &&
|
||||
service.getParameterSetID() == 2)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ 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;
|
||||
|
@ -41,6 +42,15 @@ public class WaitForCertificateInstallationRes extends ClientState {
|
|||
return new TerminateSession("Signature verification failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check
|
||||
* - validity of each certificate in the chain
|
||||
* - that the signer certificate has a DC (Domain Component) field with the content "CPS" set
|
||||
*/
|
||||
if (!SecurityUtils.isCertificateChainValid(certificateInstallationRes.getSAProvisioningCertificateChain(), "CPS")) {
|
||||
return new TerminateSession("Provisioning certificate chain is not valid");
|
||||
}
|
||||
|
||||
ECPrivateKey oemProvCertPrivateKey = SecurityUtils.getPrivateKey(
|
||||
SecurityUtils.getKeyStore(
|
||||
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
|
||||
|
@ -89,4 +99,5 @@ public class WaitForCertificateInstallationRes extends ClientState {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -41,6 +41,15 @@ public class WaitForCertificateUpdateRes extends ClientState {
|
|||
return new TerminateSession("Signature verification failed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check
|
||||
* - validity of each certificate in the chain
|
||||
* - that the signer certificate has a DC (Domain Component) field with the content "CPS" set
|
||||
*/
|
||||
if (!SecurityUtils.isCertificateChainValid(certificateUpdateRes.getSAProvisioningCertificateChain(), "CPS")) {
|
||||
return new TerminateSession("Provisioning certificate chain is not valid");
|
||||
}
|
||||
|
||||
ECPrivateKey contractCertPrivateKey = SecurityUtils.getPrivateKey(
|
||||
SecurityUtils.getKeyStore(
|
||||
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
|
||||
|
|
|
@ -18,6 +18,7 @@ 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 {
|
||||
|
@ -33,9 +34,14 @@ public class WaitForPaymentDetailsRes extends ClientState {
|
|||
PaymentDetailsResType paymentDetailsRes =
|
||||
(PaymentDetailsResType) v2gMessageRes.getBody().getBodyElement().getValue();
|
||||
|
||||
if (paymentDetailsRes.getGenChallenge() == null)
|
||||
/*
|
||||
* A reaction on the response code OK_CERTIFICATE_EXPIRES_SOON is not needed as this check
|
||||
* is already done by EVCC itself before deciding to send CertificateUpdateReq/CertificateInstallationReq
|
||||
*/
|
||||
|
||||
if (paymentDetailsRes.getGenChallenge() == null) {
|
||||
return new TerminateSession("GenChallenge not provided in PaymentDetailsRes");
|
||||
else {
|
||||
} else {
|
||||
// Set xml reference element
|
||||
AuthorizationReqType authorizationReq = getAuthorizationReq(paymentDetailsRes.getGenChallenge());
|
||||
getXMLSignatureRefElements().put(
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
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;
|
||||
|
@ -32,12 +34,35 @@ public class WaitForPaymentServiceSelectionRes extends ClientState {
|
|||
public ReactionToIncomingMessage processIncomingMessage(Object message) {
|
||||
if (isIncomingMessageValid(message, PaymentServiceSelectionResType.class)) {
|
||||
if (getCommSessionContext().getSelectedPaymentOption().equals(PaymentOptionType.CONTRACT)) {
|
||||
if (getCommSessionContext().isCertificateInstallationNeeded())
|
||||
return getSendMessage(getCertificateInstallationReq(), V2GMessages.CERTIFICATE_INSTALLATION_RES);
|
||||
else if (getCommSessionContext().isCertificateUpdateNeeded())
|
||||
return getSendMessage(getCertificateUpdateReq(), V2GMessages.CERTIFICATE_UPDATE_RES);
|
||||
else
|
||||
return getSendMessage(getPaymentDetailsReq(), V2GMessages.PAYMENT_DETAILS_RES);
|
||||
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().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");
|
||||
|
||||
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().isCertificateServiceAvailable((short) 2)) {
|
||||
getLogger().info("Stored contract certificate is about to expire in " + validityOfContractCert +
|
||||
" days, 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);
|
||||
} else {
|
||||
|
|
|
@ -14,7 +14,12 @@ import java.io.IOException;
|
|||
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;
|
||||
|
||||
|
@ -93,19 +98,32 @@ public class TLSClient extends StatefulTransportLayerClient {
|
|||
// Set the supported TLS protocol
|
||||
String[] enabledProtocols = {"TLSv1.2"};
|
||||
getTlsSocketToServer().setEnabledProtocols(enabledProtocols);
|
||||
|
||||
|
||||
getTlsSocketToServer().startHandshake();
|
||||
Certificate[] seccCertificates = getTlsSocketToServer().getSession().getPeerCertificates();
|
||||
X509Certificate seccLeafCertificate = (X509Certificate) seccCertificates[0];
|
||||
|
||||
// Check domain component of SECC certificate
|
||||
if (!SecurityUtils.isCertificateValid(seccLeafCertificate, "CPO")) {
|
||||
getLogger().error("TLS client connection failed. \n\t" +
|
||||
"Reason: Domain component of SECC certificate not valid, expected 'DC=CPO'. \n\t" +
|
||||
"Distinuished name of SECC certificate: " + seccLeafCertificate.getSubjectX500Principal().getName());
|
||||
return false;
|
||||
}
|
||||
|
||||
getLogger().debug("TLS client connection established \n\t from link-local address " +
|
||||
getClientAddress() + " and port " + getClientPort() +
|
||||
"\n\t to host " + host.getHostAddress() + " and port " + port);
|
||||
|
||||
return true;
|
||||
} catch (UnknownHostException e) {
|
||||
getLogger().error("TCP client connection failed (UnknownHostException)!", e);
|
||||
getLogger().error("TLS client connection failed (UnknownHostException)!", e);
|
||||
} catch (SSLHandshakeException e) {
|
||||
getLogger().error("TLS client connection failed (SSLHandshakeException)", e);
|
||||
} catch (IOException e) {
|
||||
getLogger().error("TCP client connection failed (IOException)!", e);
|
||||
getLogger().error("TLS client connection failed (IOException)!", e);
|
||||
} catch (NullPointerException e) {
|
||||
getLogger().fatal("NullPointerException while trying to set keystores, resource path to keystore/truststore might be incorrect");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -142,8 +160,10 @@ public class TLSClient extends StatefulTransportLayerClient {
|
|||
getOutStream().flush();
|
||||
getLogger().debug("Message sent");
|
||||
setTimeout(timeout);
|
||||
} catch (IOException e) {
|
||||
getLogger().error("An undefined IOException occurred while trying to send message", e);
|
||||
} catch (SSLHandshakeException e1) {
|
||||
stopAndNotify("An SSLHandshakeException occurred", e1);
|
||||
} catch (IOException e2) {
|
||||
stopAndNotify("An undefined IOException occurred while trying to send message", e2);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#
|
||||
# The network interface name like en3 or eth1 of the network interface on which to communicate with the EVCC via a
|
||||
# link-local IPv6 address
|
||||
NetworkInterface = en3
|
||||
NetworkInterface = en0
|
||||
|
||||
|
||||
# Supported energy transfer modes
|
||||
|
|
Binary file not shown.
|
@ -146,7 +146,7 @@ public class V2GCommunicationSessionSECC extends V2GCommunicationSession impleme
|
|||
if (obs instanceof ConnectionHandler && obj instanceof byte[]) {
|
||||
processIncomingMessage((byte[]) obj);
|
||||
} else if (obs instanceof ConnectionHandler && obj == null) {
|
||||
terminateSession("ConnectionHanlder has notified an error", false);
|
||||
terminateSession("ConnectionHandler has notified an error", false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -79,6 +79,14 @@ public class WaitForPaymentDetailsReq extends ServerState {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check if certificate expires soon (in 21 days or fewer) according to V2G2-690
|
||||
// A check for general validity has already been done above and does not need to be checked again here
|
||||
if (SecurityUtils.getValidityPeriod(
|
||||
SecurityUtils.getCertificate(paymentDetailsReq.getContractSignatureCertChain().getCertificate())
|
||||
) <= GlobalValues.CERTIFICATE_EXPIRES_SOON_PERIOD.getShortValue()) {
|
||||
paymentDetailsRes.setResponseCode(ResponseCodeType.OK_CERTIFICATE_EXPIRES_SOON);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,10 @@ import java.net.Socket;
|
|||
import java.net.SocketTimeoutException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Observable;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.eclipse.risev2g.shared.misc.TimeRestrictions;
|
||||
|
@ -120,8 +123,11 @@ public class ConnectionHandler extends Observable implements Runnable {
|
|||
} catch (SocketTimeoutException e) {
|
||||
stopAndNotify("A SocketTimeoutException occurred", null);
|
||||
break;
|
||||
} catch (IOException e1) {
|
||||
stopAndNotify("IOException occurred", e1);
|
||||
} catch (SSLHandshakeException e1) {
|
||||
stopAndNotify("An SSLHandshakeException occurred", e1);
|
||||
break;
|
||||
} catch (IOException e2) {
|
||||
stopAndNotify("IOException occurred", e2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ public enum GlobalValues {
|
|||
ALIAS_CONTRACT_CERTIFICATE("contract_cert"),
|
||||
ALIAS_OEM_PROV_CERTIFICATE("oem_prov_cert"),
|
||||
|
||||
// Period of time in days in which contract certificate update is recommended
|
||||
CERTIFICATE_EXPIRES_SOON_PERIOD((short) 21, GlobalTypes.SECURITY),
|
||||
|
||||
/*
|
||||
* Relative file path to the EVCC and SECC keystore and truststore.
|
||||
* Since at least the evccKeystore needs to be modified upon certificate installation / update, the
|
||||
|
@ -186,6 +189,8 @@ public enum GlobalValues {
|
|||
return "./seccKeystore.jks";
|
||||
case SECC_TRUSTSTORE_FILEPATH:
|
||||
return "./seccTruststore.jks";
|
||||
case CERTIFICATE_EXPIRES_SOON_PERIOD:
|
||||
return "21 days";
|
||||
default: return "Invalid GlobalValue type";
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,9 +53,12 @@ import java.security.spec.InvalidParameterSpecException;
|
|||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
|
@ -201,21 +204,78 @@ public final class SecurityUtils {
|
|||
*/
|
||||
public static boolean isCertificateValid(X509Certificate certificate) {
|
||||
try {
|
||||
certificate.checkValidity();
|
||||
certificate.checkValidity();
|
||||
return true;
|
||||
} catch (CertificateExpiredException e) {
|
||||
X500Principal subject = certificate.getSubjectX500Principal();
|
||||
|
||||
getLogger().warn("Certificate with distinguished name '" + subject.getName().toString() +
|
||||
"' already expired (not after " + certificate.getNotAfter().toString() + ")");
|
||||
} catch (CertificateNotYetValidException e) {
|
||||
X500Principal subject = certificate.getSubjectX500Principal();
|
||||
getLogger().warn("Certificate with distinguished name '" + subject.getName().toString() +
|
||||
"' not yet valid (not before " + certificate.getNotBefore().toString() + ")");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* [V2G2-925] states:
|
||||
* A leaf certificate shall be treated as invalid, if the trust anchor at the end of the chain does not
|
||||
* match the specific root certificate required for a certain use, or if the required Domain
|
||||
* Component value is not present.
|
||||
*
|
||||
* Domain Component restrictions:
|
||||
* - SECC certificate: "CPO" (verification by EVCC)
|
||||
* - provisioning certificate (signer certificate of a contract certificate: "CPS" (verification by EVCC)
|
||||
* - OEM Provisioning Certificate: "OEM" (verification by provisioning service (not EVCC or SECC))
|
||||
*
|
||||
* @param certificate The X509Certificiate to be checked for validity
|
||||
* @param domainComponent The domain component to be checked for in the distinguished name of the certificate
|
||||
* @return True, if the current date lies within the notBefore and notAfter attribute of the
|
||||
* certificate and the given domain component is present in the distinguished name, false otherwise
|
||||
*/
|
||||
public static boolean isCertificateValid(X509Certificate certificate, String domainComponent) {
|
||||
if (isCertificateValid(certificate)) {
|
||||
String dn = certificate.getSubjectX500Principal().getName();
|
||||
LdapName ln;
|
||||
|
||||
try {
|
||||
ln = new LdapName(dn);
|
||||
|
||||
for (Rdn rdn : ln.getRdns()) {
|
||||
if (rdn.getType().equalsIgnoreCase("DC") && rdn.getValue().equals(domainComponent)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (InvalidNameException e) {
|
||||
getLogger().warn("InvalidNameException occurred while trying to check domain component of certificate", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks how many days a given certificate is still valid.
|
||||
* If the certificate is not valid any more, a negative number will be returned according to the number
|
||||
* of days the certificate is already expired.
|
||||
*
|
||||
* @param certificate The X509Certificiate to be checked for validity period
|
||||
* @return The number of days the given certificate is still valid, a negative number if already expired.
|
||||
*/
|
||||
public static short getValidityPeriod(X509Certificate certificate) {
|
||||
Date today = Calendar.getInstance().getTime();
|
||||
Date certificateExpirationDate = certificate.getNotAfter();
|
||||
long diff = certificateExpirationDate.getTime() - today.getTime();
|
||||
|
||||
return (short) TimeUnit.DAYS.convert(diff, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether each certificate in the given certificate chain is currently valid.
|
||||
|
@ -241,6 +301,23 @@ public final class SecurityUtils {
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether each certificate in the given certificate chain is currently valid and if a given
|
||||
* domain component (DC) in the distinguished name is set.
|
||||
*
|
||||
* @param certChain The certificate chain to iterate over to check for validity
|
||||
* @param domainComponent The domain component
|
||||
* @return True, if the domain component is correctly set and if the current date lies within the notBefore
|
||||
* and notAfter attribute of each certificate contained in the provided certificate chain,
|
||||
* false otherwise
|
||||
*/
|
||||
public static boolean isCertificateChainValid(CertificateChainType certChain, String domainComponent) {
|
||||
if (isCertificateChainValid(certChain)) {
|
||||
if (isCertificateValid(getCertificate(certChain.getCertificate()), domainComponent)) return true;
|
||||
else return false;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that the given certificate was signed using the private key that corresponds to the
|
||||
|
@ -761,6 +838,24 @@ public final class SecurityUtils {
|
|||
}
|
||||
|
||||
|
||||
public static X509Certificate getContractCertificate() {
|
||||
X509Certificate contractCertificate = null;
|
||||
|
||||
KeyStore evccKeyStore = getKeyStore(
|
||||
GlobalValues.EVCC_KEYSTORE_FILEPATH.toString(),
|
||||
GlobalValues.PASSPHRASE_FOR_CERTIFICATES_AND_KEYS.toString()
|
||||
);
|
||||
|
||||
try {
|
||||
contractCertificate = (X509Certificate) evccKeyStore.getCertificate(GlobalValues.ALIAS_CONTRACT_CERTIFICATE.toString());
|
||||
} catch (KeyStoreException e) {
|
||||
getLogger().error("KeyStoreException occurred while trying to get contract certificate from keystore", e);
|
||||
}
|
||||
|
||||
return contractCertificate;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a list of certificates from the given CertificateChainType with the leaf certificate
|
||||
* being the first element and potential subcertificates (intermediate CA certificatess)
|
||||
|
|
Loading…
Reference in New Issue